From e8554878abce79cb5b0d06b1fe6682afc7c4c540 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sun, 17 Jan 2021 23:09:19 +0100 Subject: [PATCH 1/4] FileLoader: don't change trailing newline in space --- src/core/FileLoader.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/FileLoader.cpp b/src/core/FileLoader.cpp index 22e0159c..cd363261 100644 --- a/src/core/FileLoader.cpp +++ b/src/core/FileLoader.cpp @@ -69,7 +69,7 @@ CFileLoader::LoadLevel(const char *filename) assert(fd > 0); for(line = LoadLine(fd); line; line = LoadLine(fd)){ - if(*line == '#') + if((*line == '#') || (*line == '\0')) continue; #ifdef FIX_BUGS @@ -167,9 +167,12 @@ CFileLoader::LoadLine(int fd) if(CFileMgr::ReadLine(fd, ms_line, 256) == false) return nil; - for(i = 0; ms_line[i] != '\0'; i++) - if(ms_line[i] < ' ' || ms_line[i] == ',') + for(i = 0; ms_line[i] != '\0'; i++) { + if(ms_line[i] == '\n') + ms_line[i] = '\0'; + else if(ms_line[i] < ' ' || ms_line[i] == ',') ms_line[i] = ' '; + } for(line = ms_line; *line <= ' ' && *line != '\0'; line++); return line; } -- 2.45.2 From f24e2c8a8f584482752f67c49fee1f5e31d55caa Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Wed, 13 Jan 2021 01:30:29 +0100 Subject: [PATCH 2/4] conan: pass with_libsndfile option to cmake --- conanfile.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/conanfile.py b/conanfile.py index cabcc4c2..5e391379 100644 --- a/conanfile.py +++ b/conanfile.py @@ -67,9 +67,6 @@ class Re3Conan(ConanFile): def validate(self): if self.options["librw"].platform == "gl3" and self.options["librw"].gl3_gfxlib != "glfw": raise ConanInvalidConfiguration("Only `glfw` is supported as gl3_gfxlib.") - #if not self.options.with_opus: - # if not self.options["libsndfile"].with_external_libs: - # raise ConanInvalidConfiguration("re3 with opus support requires a libsndfile built with external libs (=ogg/flac/opus/vorbis)") @property def _re3_audio(self): @@ -119,6 +116,7 @@ class Re3Conan(ConanFile): cmake = CMake(self) cmake.definitions["RE3_AUDIO"] = self._re3_audio cmake.definitions["RE3_WITH_OPUS"] = self.options.with_opus + cmake.definitions["RE3_WITH_LIBSNDFILE"] = self.options.with_libsndfile cmake.definitions["RE3_INSTALL"] = True cmake.definitions["RE3_VENDORED_LIBRW"] = False env = {} -- 2.45.2 From 8d0f42a2152bed56f525ce281ff2037d5c29085f Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Wed, 13 Jan 2021 01:35:42 +0100 Subject: [PATCH 3/4] cmake: nsis + logo + sources + source_group + start-up --- .github/workflows/build-cmake-conan.yml | 4 +- CMakeLists.txt | 177 ++-- cmake/CPackProjectConfig.cmake.in | 76 ++ cmake/NSIS.template.in | 1010 +++++++++++++++++++++++ conanfile.py | 7 +- resources/create_ico.py | 17 + resources/logo.bmp | Bin 0 -> 2101302 bytes resources/logo.ico | Bin 0 -> 21338 bytes logo.png => resources/logo.png | Bin logo.svg => resources/logo.svg | 0 src/CMakeLists.txt | 29 +- src/skel/win/gta3.ico | Bin 2238 -> 0 bytes src/skel/win/re3.ico | Bin 0 -> 21338 bytes src/skel/win/win.rc | 8 +- 14 files changed, 1257 insertions(+), 71 deletions(-) create mode 100644 cmake/CPackProjectConfig.cmake.in create mode 100644 cmake/NSIS.template.in create mode 100644 resources/create_ico.py create mode 100644 resources/logo.bmp create mode 100644 resources/logo.ico rename logo.png => resources/logo.png (100%) rename logo.svg => resources/logo.svg (100%) delete mode 100644 src/skel/win/gta3.ico create mode 100644 src/skel/win/re3.ico diff --git a/.github/workflows/build-cmake-conan.yml b/.github/workflows/build-cmake-conan.yml index 9f0d8c91..8bb0c787 100644 --- a/.github/workflows/build-cmake-conan.yml +++ b/.github/workflows/build-cmake-conan.yml @@ -101,6 +101,7 @@ jobs: - name: "Build re3 (conan build)" run: | conan build ${{ github.workspace }} -if build -bf build -pf package + cmake build -DRE3_INSTALL_NSIS=ON - name: "Package re3 (conan package)" run: | conan package ${{ github.workspace }} -if build -bf build -pf package @@ -108,9 +109,10 @@ jobs: working-directory: ./build run: | cpack -C RelWithDebInfo + cmake -E rm -rf dist/_CPack_Packages - name: "Archive binary package (github artifacts)" uses: actions/upload-artifact@v2 with: name: "${{ matrix.os }}-${{ matrix.platform }}" - path: build/*.tar.xz + path: build/dist/* if-no-files-found: error diff --git a/CMakeLists.txt b/CMakeLists.txt index ae6395c8..35830147 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,82 +1,151 @@ -cmake_minimum_required(VERSION 3.8) - set(EXECUTABLE re3) -set(PROJECT RE3) +cmake_minimum_required(VERSION 3.8) project(${EXECUTABLE} C CXX) +set(PROJECT_AUTHOR "GTAModding") +set(PROJECT_URL "https://github.com/gtamodding/re3") + list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") if(WIN32) - set(${PROJECT}_AUDIOS "OAL" "MSS") + set(RE3_AUDIOS "OAL" "MSS") else() - set(${PROJECT}_AUDIOS "OAL") + set(RE3_AUDIOS "OAL") endif() -set(${PROJECT}_AUDIO "OAL" CACHE STRING "Audio") +set(RE3_AUDIO "OAL" CACHE STRING "Audio") + +include(CMakeDependentOption) + +option(RE3_INSTALL "Install ${EXECUTABLE}" OFF) +option(RE3_WITH_OPUS "Build ${EXECUTABLE} with opus support" OFF) +option(RE3_WITH_LIBSNDFILE "Build ${EXECUTABLE} with libsndfile (instead of internal decoder)" OFF) + +cmake_dependent_option(RE3_INSTALL_NSIS "Create NSIS installer" ON "WIN32;RE3_INSTALL" OFF) +cmake_dependent_option(RE3_INSTALL_SRC "Create sources archive" OFF "RE3_INSTALL" OFF) -option(${PROJECT}_WITH_OPUS "Build ${EXECUTABLE} with opus support" OFF) -option(${PROJECT}_WITH_LIBSNDFILE "Build ${EXECUTABLE} with libsndfile (instead of internal decoder)" OFF) +if(RE3_INSTALL_NSIS) + find_program(MAKENSIS_BIN makensis) + if(NOT MAKENSIS_BIN) + message(FATAL_ERROR "Cannot find `makensis` for creating NSIS installer.") + endif() +endif() -set_property(CACHE ${PROJECT}_AUDIO PROPERTY STRINGS ${${PROJECT}_AUDIOS}) -message(STATUS "${PROJECT}_AUDIO = ${${PROJECT}_AUDIO} (choices=${${PROJECT}_AUDIOS})") -set("${PROJECT}_AUDIO_${${PROJECT}_AUDIO}" ON) -if(NOT ${PROJECT}_AUDIO IN_LIST ${PROJECT}_AUDIOS) - message(FATAL_ERROR "Illegal ${PROJECT}_AUDIO=${${PROJECT}_AUDIO}") +set_property(CACHE RE3_AUDIO PROPERTY STRINGS ${RE3_AUDIOS}) +message(STATUS "RE3_AUDIO = ${RE3_AUDIO} (choices=${RE3_AUDIOS})") +set("RE3_AUDIO_${RE3_AUDIO}" ON) +if(NOT RE3_AUDIO IN_LIST RE3_AUDIOS) + message(FATAL_ERROR "Illegal RE3_AUDIO=${RE3_AUDIO}") endif() -option(${PROJECT}_VENDORED_LIBRW "Use vendored librw" ON) -if(${PROJECT}_VENDORED_LIBRW) +option(RE3_VENDORED_LIBRW "Use vendored librw" ON) +if(RE3_VENDORED_LIBRW) add_subdirectory(vendor/librw) else() find_package(librw REQUIRED) endif() add_subdirectory(src) +set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT ${EXECUTABLE}) -if(${PROJECT}_INSTALL) - install(DIRECTORY gamefiles/ DESTINATION ".") - if(LIBRW_PLATFORM_NULL) - set(platform "-null") - elseif(LIBRW_PLATFORM_PS2) - set(platform "-ps2") - elseif(LIBRW_PLATFORM_GL3) - if(LIBRW_GL3_GFXLIB STREQUAL "GLFW") - set(platform "-gl3-glfw") - else() - set(platform "-gl3-sdl2") - endif() - elseif(LIBRW_PLATFORM_D3D9) - set(platform "-d3d9") - endif() - if(${PROJECT}_AUDIO_OAL) - set(audio "-oal") - elseif(${PROJECT}_AUDIO_MSS) - set(audio "-mss") - endif() - if(${PROJECT}_WITH_OPUS) - set(audio "${audio}-opus") - endif() - if(NOT LIBRW_PLATFORM_PS2) - if(WIN32) - set(os "-win") - elseif(APPLE) - set(os "-apple") - elseif(UNIX) - set(os "-linux") - else() - set(compiler "-UNK") - message(WARNING "Unknown os. Created cpack package will be wrong. (override using cpack -P)") - endif() +if(RE3_INSTALL) + install(DIRECTORY gamefiles/ DESTINATION "." COMPONENT comp2_gamedata) + configure_file(cmake/CPackProjectConfig.cmake.in CPackProjectConfig.cmake @ONLY) + #file(GENERATE OUTPUT CPackProjectConfig.cmake INPUT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CPackProjectConfig.cmake.in" TARGET ${EXECUTABLE}) + set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}/dist") + + if(RE3_INSTALL_SRC) + install( + DIRECTORY cmake gamefiles resources src utils + DESTINATION . + COMPONENT compZ_sources + EXCLUDE_FROM_ALL + ) + install( + FILES conanfile.py CMakeLists.txt premake5.lua README.md CODING_STYLE.md + DESTINATION . + COMPONENT compZ_sources + EXCLUDE_FROM_ALL + ) endif() - set(CPACK_PACKAGE_NAME "${PROJECT_NAME}${platform}${audio}${os}${compiler}") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}") + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "GTA III reversed") - set(CPACK_PACKAGE_VENDOR "GTAModding") + set(CPACK_PACKAGE_VENDOR "${PROJECT_AUTHOR}") + set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/CPackProjectConfig.cmake") # FIXME: missing license (https://github.com/GTAmodding/re3/issues/794) # set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/LICENSE") # set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") - set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") - set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") - set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") set(CPACK_GENERATOR "TXZ") + # FIXME: this is displayed in the GUI installer!!! Needs a disclaimer about trademarks etc. + # set(CPACK_RESOURCE_FILE_LICENSE) + set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}\\\\resources\\\\logo.bmp") + set(CPACK_PACKAGE_CHECKSUM "SHA256") + + list(REMOVE_ITEM CPACK_COMPONENTS_ALL "compZ_sources") + + set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) + + if(WIN32) + set(NEED_RUNTIME OFF) + if(MSVC) + if(NOT "-MT" MATCHES CMAKE_C_FLAGS) + # Install MSVC runtime when using dynamic runtime (-MD, -MDd) + if("-MDd" MATCHES CMAKE_C_FLAGS) + set(CMAKE_INSTALL_DEBUG_LIBRARIES_ONLY ON) + endif() + set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ".") + set(CMAKE_INSTALL_SYSTEM_RUNTIME_COMPONENT comp3_runtime) + include(InstallRequiredSystemLibraries) + set(NEED_RUNTIME ON) + endif() + endif() + if(RE3_INSTALL_NSIS) + list(APPEND CPACK_GENERATOR "NSIS") + endif() + + set(CPACK_NSIS_COMPONENT_INSTALL ON) + set(CPACK_NSIS_DEFINES "!RequestExecutionLevel user") + set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) + set(CPACK_NSIS_DISPLAY_NAME "${PROJECT_NAME} (${PROJECT_AUTHOR})") + set(CPACK_NSIS_HELP_LINK "${PROJECT_URL}") + set(CPACK_NSIS_URL_INFO_ABOUT "${PROJECT_URL}") + set(CPACK_NSIS_MUI_ICON "${PROJECT_SOURCE_DIR}\\\\resources\\\\logo.ico") + set(CPACK_NSIS_MUI_UNIICON "${PROJECT_SOURCE_DIR}\\\\resources\\\\logo.ico") + set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") + set(CPACK_NSIS_MUI_FINISHPAGE_RUN "${EXECUTABLE}") + endif() + + include(CPackComponent) + + cpack_add_component(comp1_core + DISPLAY_NAME "Game executable(s)" + DESCRIPTION "Install the game executable(s)." + REQUIRED + ) + cpack_add_component(comp2_gamedata + DISPLAY_NAME "Game data fixes" + DESCRIPTION "Install game data fixes." + REQUIRED + DEPENDS "comp1_core" + ) + cpack_add_component(comp3_runtime + DISPLAY_NAME "System runtime" + DESCRIPTION "Install C runtime. Not required if a suitable MSVC runtime is already available." + DEPENDS "comp1_core" + ) + cpack_add_component(comp4_debug + DISPLAY_NAME "Debug symbols" + DESCRIPTION "Enables debugging your problems. Hopefully not needed." + DEPENDS "comp1_core" + DISABLED + ) + cpack_add_component(compZ_sources + DISPLAY_NAME "sources" + DESCRIPTION "The sources of ${PROJECT_NAME}." + DISABLED + HIDDEN + ) + include(CPack) endif() diff --git a/cmake/CPackProjectConfig.cmake.in b/cmake/CPackProjectConfig.cmake.in new file mode 100644 index 00000000..a6ac5802 --- /dev/null +++ b/cmake/CPackProjectConfig.cmake.in @@ -0,0 +1,76 @@ +set(PROJECT_NAME "@PROJECT_NAME@") +set(PROJECT_AUTHOR "@PROJECT_AUTHOR@") + +set(LIBRW_PLATFORM "@LIBRW_PLATFORM@") +set(LIBRW_GL3_GFXLIB "@LIBRW_GL3_GFXLIB@") +set(RE3_AUDIO "@RE3_AUDIO@") +set(RE3_WITH_OPUS "@RE3_WITH_OPUS@") +set(WIN32 @WIN32@) +set(APPLE @APPLE@) +set(UNIZ @UNIX@) +set(CMAKE_SIZEOF_VOID_P @CMAKE_SIZEOF_VOID_P@) + +if(LIBRW_PLATFORM STREQUAL "NULL") + set(platform "-null") +elseif(LIBRW_PLATFORM STREQUAL "PS2") + set(platform "-ps2") +elseif(LIBRW_PLATFORM STREQUAL "GL3") + if(LIBRW_GL3_GFXLIB STREQUAL "GLFW") + set(platform "-gl3-glfw") + else() + set(platform "-gl3-sdl2") + endif() +elseif(LIBRW_PLATFORM STREQUAL "D3D9") + set(platform "-d3d9") +endif() + +if(RE3_AUDIO STREQUAL "OAL") + set(audio "-oal") +elseif(RE3_AUDIO STREQUAL "MSS") + set(audio "-mss") +endif() +if(RE3_WITH_OPUS) + set(audio "${audio}-opus") +endif() + +if(WIN32) + set(os "-win") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(os "${os}64") + else() + set(os "${os}32") + endif() +elseif(APPLE) + set(os "-apple") +elseif(UNIX) + set(os "-linux") +else() + set(os "-UNK") + message(WARNING "Unknown os. Created cpack package will be wrong. (override using cpack -P)") +endif() + +set(name_osarch "${PROJECT_NAME}${platform}${audio}${os}${compiler}") + +set(CPACK_PACKAGE_FILE_NAME "${name_osarch}") + + +if(CPACK_GENERATOR MATCHES "^(7Z|TBZ|TGZ|TXZ|TZ|TZST|ZIP)$") + set(CPACK_COMPONENTS_GROUPING "ONE_PER_GROUP") + + set(CPACK_COMPONENT_COMP1_CORE_GROUP "BinaryGroup") + set(CPACK_COMPONENT_COMP2_GAMEDATA_GROUP "BinaryGroup") + set(CPACK_COMPONENT_COMP3_RUNTIME_GROUP "BinaryGroup") + set(CPACK_COMPONENT_COMP4_DEBUG_GROUP "BinaryGroup") + set(CPACK_COMPONENT_COMPZ_SOURCES_GROUP "SourceGroup") + + set(CPACK_ARCHIVE_SOURCEGROUP_FILE_NAME "${PROJECT_NAME}-src") + set(CPACK_ARCHIVE_BINARYGROUP_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}") +elseif(CPACK_GENERATOR MATCHES "^NSIS(64)?$") + list(REMOVE_ITEM CPACK_COMPONENTS_ALL "compZ_sources") + + set(CPACK_PACKAGE_FILE_NAME "${name_osarch}-setup") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME} (${PROJECT_AUTHOR})") + + set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut \"$SMPROGRAMS\\$STARTMENU_FOLDER\\${PROJECT_NAME}.lnk\" \"$INSTDIR\\${PROJECT_NAME}.exe\"") + set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete \"$SMPROGRAMS\\$MUI_TEMP\${PROJECT_NAME}.lnk\"") +endif() diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in new file mode 100644 index 00000000..79176fdd --- /dev/null +++ b/cmake/NSIS.template.in @@ -0,0 +1,1010 @@ +; CPack install script designed for a nmake build + +;-------------------------------- +; You must define these values + + !define VERSION "@CPACK_PACKAGE_VERSION@" + !define PATCH "@CPACK_PACKAGE_VERSION_PATCH@" + !define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@" + +;-------------------------------- +;Variables + + Var MUI_TEMP + Var STARTMENU_FOLDER + Var SV_ALLUSERS + Var START_MENU + Var DO_NOT_ADD_TO_PATH + Var ADD_TO_PATH_ALL_USERS + Var ADD_TO_PATH_CURRENT_USER + Var INSTALL_DESKTOP + Var IS_DEFAULT_INSTALLDIR +;-------------------------------- +;Include Modern UI + + !include "MUI.nsh" + + ;Default installation folder + InstallDir "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + +;-------------------------------- +;General + + ;Name and file + Name "@CPACK_NSIS_PACKAGE_NAME@" + OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@" + + ;Set compression + SetCompressor @CPACK_NSIS_COMPRESSOR@ + + ;Require administrator access + RequestExecutionLevel user + +#@CPACK_NSIS_DEFINES@ +@CPACK_NSIS_MANIFEST_DPI_AWARE_CODE@ + + !include Sections.nsh + +;--- Component support macros: --- +; The code for the add/remove functionality is from: +; http://nsis.sourceforge.net/Add/Remove_Functionality +; It has been modified slightly and extended to provide +; inter-component dependencies. +Var AR_SecFlags +Var AR_RegFlags +@CPACK_NSIS_SECTION_SELECTED_VARS@ + +; Loads the "selected" flag for the section named SecName into the +; variable VarName. +!macro LoadSectionSelectedIntoVar SecName VarName + SectionGetFlags ${${SecName}} $${VarName} + IntOp $${VarName} $${VarName} & ${SF_SELECTED} ;Turn off all other bits +!macroend + +; Loads the value of a variable... can we get around this? +!macro LoadVar VarName + IntOp $R0 0 + $${VarName} +!macroend + +; Sets the value of a variable +!macro StoreVar VarName IntValue + IntOp $${VarName} 0 + ${IntValue} +!macroend + +!macro InitSection SecName + ; This macro reads component installed flag from the registry and + ;changes checked state of the section on the components page. + ;Input: section index constant name specified in Section command. + + ClearErrors + ;Reading component status from registry + ReadRegDWORD $AR_RegFlags HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" "Installed" + IfErrors "default_${SecName}" + ;Status will stay default if registry value not found + ;(component was never installed) + IntOp $AR_RegFlags $AR_RegFlags & ${SF_SELECTED} ;Turn off all other bits + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading default section flags + IntOp $AR_SecFlags $AR_SecFlags & 0xFFFE ;Turn lowest (enabled) bit off + IntOp $AR_SecFlags $AR_RegFlags | $AR_SecFlags ;Change lowest bit + + ; Note whether this component was installed before + !insertmacro StoreVar ${SecName}_was_installed $AR_RegFlags + IntOp $R0 $AR_RegFlags & $AR_RegFlags + + ;Writing modified flags + SectionSetFlags ${${SecName}} $AR_SecFlags + + "default_${SecName}:" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected +!macroend + +!macro FinishSection SecName + ; This macro reads section flag set by user and removes the section + ;if it is not selected. + ;Then it writes component installed flag to registry + ;Input: section index constant name specified in Section command. + + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading section flags + ;Checking lowest bit: + IntOp $AR_SecFlags $AR_SecFlags & ${SF_SELECTED} + IntCmp $AR_SecFlags 1 "leave_${SecName}" + ;Section is not selected: + ;Calling Section uninstall macro and writing zero installed flag + !insertmacro "Remove_${${SecName}}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \ + "Installed" 0 + Goto "exit_${SecName}" + + "leave_${SecName}:" + ;Section is selected: + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \ + "Installed" 1 + + "exit_${SecName}:" +!macroend + +!macro RemoveSection_CPack SecName + ; This macro is used to call section's Remove_... macro + ;from the uninstaller. + ;Input: section index constant name specified in Section command. + + !insertmacro "Remove_${${SecName}}" +!macroend + +; Determine whether the selection of SecName changed +!macro MaybeSelectionChanged SecName + !insertmacro LoadVar ${SecName}_selected + SectionGetFlags ${${SecName}} $R1 + IntOp $R1 $R1 & ${SF_SELECTED} ;Turn off all other bits + + ; See if the status has changed: + IntCmp $R0 $R1 "${SecName}_unchanged" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected + + IntCmp $R1 ${SF_SELECTED} "${SecName}_was_selected" + !insertmacro "Deselect_required_by_${SecName}" + goto "${SecName}_unchanged" + + "${SecName}_was_selected:" + !insertmacro "Select_${SecName}_depends" + + "${SecName}_unchanged:" +!macroend +;--- End of Add/Remove macros --- + +;-------------------------------- +;Interface Settings + + !define MUI_HEADERIMAGE + !define MUI_ABORTWARNING + +;---------------------------------------- +; based upon a script of "Written by KiCHiK 2003-01-18 05:57:02" +;---------------------------------------- +!verbose 3 +!include "WinMessages.NSH" +!verbose 4 +;==================================================== +; get_NT_environment +; Returns: the selected environment +; Output : head of the stack +;==================================================== +!macro select_NT_profile UN +Function ${UN}select_NT_profile + StrCmp $ADD_TO_PATH_ALL_USERS "1" 0 environment_single + DetailPrint "Selected environment for all users" + Push "all" + Return + environment_single: + DetailPrint "Selected environment for current user only." + Push "current" + Return +FunctionEnd +!macroend +!insertmacro select_NT_profile "" +!insertmacro select_NT_profile "un." +;---------------------------------------------------- +!define NT_current_env 'HKCU "Environment"' +!define NT_all_env 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + +!ifndef WriteEnvStr_RegKey + !ifdef ALL_USERS + !define WriteEnvStr_RegKey \ + 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + !else + !define WriteEnvStr_RegKey 'HKCU "Environment"' + !endif +!endif + +; AddToPath - Adds the given dir to the search path. +; Input - head of the stack +; Note - Win9x systems requires reboot + +Function AddToPath + Exch $0 + Push $1 + Push $2 + Push $3 + + # don't add if the path doesn't exist + IfFileExists "$0\*.*" "" AddToPath_done + + ReadEnvStr $1 PATH + ; if the path is too long for a NSIS variable NSIS will return a 0 + ; length string. If we find that, then warn and skip any path + ; modification as it will trash the existing path. + StrLen $2 $1 + IntCmp $2 0 CheckPathLength_ShowPathWarning CheckPathLength_Done CheckPathLength_Done + CheckPathLength_ShowPathWarning: + Messagebox MB_OK|MB_ICONEXCLAMATION "Warning! PATH too long installer unable to modify PATH!" + Goto AddToPath_done + CheckPathLength_Done: + Push "$1;" + Push "$0;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + Push "$1;" + Push "$0\;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + GetFullPathName /SHORT $3 $0 + Push "$1;" + Push "$3;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + Push "$1;" + Push "$3\;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + + Call IsNT + Pop $1 + StrCmp $1 1 AddToPath_NT + ; Not on NT + StrCpy $1 $WINDIR 2 + FileOpen $1 "$1\autoexec.bat" a + FileSeek $1 -1 END + FileReadByte $1 $2 + IntCmp $2 26 0 +2 +2 # DOS EOF + FileSeek $1 -1 END # write over EOF + FileWrite $1 "$\r$\nSET PATH=%PATH%;$3$\r$\n" + FileClose $1 + SetRebootFlag true + Goto AddToPath_done + + AddToPath_NT: + StrCmp $ADD_TO_PATH_ALL_USERS "1" ReadAllKey + ReadRegStr $1 ${NT_current_env} "PATH" + Goto DoTrim + ReadAllKey: + ReadRegStr $1 ${NT_all_env} "PATH" + DoTrim: + StrCmp $1 "" AddToPath_NTdoIt + Push $1 + Call Trim + Pop $1 + StrCpy $0 "$1;$0" + AddToPath_NTdoIt: + StrCmp $ADD_TO_PATH_ALL_USERS "1" WriteAllKey + WriteRegExpandStr ${NT_current_env} "PATH" $0 + Goto DoSend + WriteAllKey: + WriteRegExpandStr ${NT_all_env} "PATH" $0 + DoSend: + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + AddToPath_done: + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + +; RemoveFromPath - Remove a given dir from the path +; Input: head of the stack + +Function un.RemoveFromPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + + IntFmt $6 "%c" 26 # DOS EOF + + Call un.IsNT + Pop $1 + StrCmp $1 1 unRemoveFromPath_NT + ; Not on NT + StrCpy $1 $WINDIR 2 + FileOpen $1 "$1\autoexec.bat" r + GetTempFileName $4 + FileOpen $2 $4 w + GetFullPathName /SHORT $0 $0 + StrCpy $0 "SET PATH=%PATH%;$0" + Goto unRemoveFromPath_dosLoop + + unRemoveFromPath_dosLoop: + FileRead $1 $3 + StrCpy $5 $3 1 -1 # read last char + StrCmp $5 $6 0 +2 # if DOS EOF + StrCpy $3 $3 -1 # remove DOS EOF so we can compare + StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "$0$\n" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "$0" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "" unRemoveFromPath_dosLoopEnd + FileWrite $2 $3 + Goto unRemoveFromPath_dosLoop + unRemoveFromPath_dosLoopRemoveLine: + SetRebootFlag true + Goto unRemoveFromPath_dosLoop + + unRemoveFromPath_dosLoopEnd: + FileClose $2 + FileClose $1 + StrCpy $1 $WINDIR 2 + Delete "$1\autoexec.bat" + CopyFiles /SILENT $4 "$1\autoexec.bat" + Delete $4 + Goto unRemoveFromPath_done + + unRemoveFromPath_NT: + StrCmp $ADD_TO_PATH_ALL_USERS "1" unReadAllKey + ReadRegStr $1 ${NT_current_env} "PATH" + Goto unDoTrim + unReadAllKey: + ReadRegStr $1 ${NT_all_env} "PATH" + unDoTrim: + StrCpy $5 $1 1 -1 # copy last char + StrCmp $5 ";" +2 # if last char != ; + StrCpy $1 "$1;" # append ; + Push $1 + Push "$0;" + Call un.StrStr ; Find `$0;` in $1 + Pop $2 ; pos of our dir + StrCmp $2 "" unRemoveFromPath_done + ; else, it is in path + # $0 - path to add + # $1 - path var + StrLen $3 "$0;" + StrLen $4 $2 + StrCpy $5 $1 -$4 # $5 is now the part before the path to remove + StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove + StrCpy $3 $5$6 + + StrCpy $5 $3 1 -1 # copy last char + StrCmp $5 ";" 0 +2 # if last char == ; + StrCpy $3 $3 -1 # remove last char + + StrCmp $ADD_TO_PATH_ALL_USERS "1" unWriteAllKey + WriteRegExpandStr ${NT_current_env} "PATH" $3 + Goto unDoSend + unWriteAllKey: + WriteRegExpandStr ${NT_all_env} "PATH" $3 + unDoSend: + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + unRemoveFromPath_done: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Uninstall stuff +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +########################################### +# Utility Functions # +########################################### + +;==================================================== +; IsNT - Returns 1 if the current system is NT, 0 +; otherwise. +; Output: head of the stack +;==================================================== +; IsNT +; no input +; output, top of the stack = 1 if NT or 0 if not +; +; Usage: +; Call IsNT +; Pop $R0 +; ($R0 at this point is 1 or 0) + +!macro IsNT un +Function ${un}IsNT + Push $0 + ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $0 "" 0 IsNT_yes + ; we are not NT. + Pop $0 + Push 0 + Return + + IsNT_yes: + ; NT!!! + Pop $0 + Push 1 +FunctionEnd +!macroend +!insertmacro IsNT "" +!insertmacro IsNT "un." + +; StrStr +; input, top of stack = string to search for +; top of stack-1 = string to search in +; output, top of stack (replaces with the portion of the string remaining) +; modifies no other variables. +; +; Usage: +; Push "this is a long ass string" +; Push "ass" +; Call StrStr +; Pop $R0 +; ($R0 at this point is "ass string") + +!macro StrStr un +Function ${un}StrStr +Exch $R1 ; st=haystack,old$R1, $R1=needle + Exch ; st=old$R1,haystack + Exch $R2 ; st=old$R1,old$R2, $R2=haystack + Push $R3 + Push $R4 + Push $R5 + StrLen $R3 $R1 + StrCpy $R4 0 + ; $R1=needle + ; $R2=haystack + ; $R3=len(needle) + ; $R4=cnt + ; $R5=tmp + loop: + StrCpy $R5 $R2 $R3 $R4 + StrCmp $R5 $R1 done + StrCmp $R5 "" done + IntOp $R4 $R4 + 1 + Goto loop +done: + StrCpy $R1 $R2 "" $R4 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Exch $R1 +FunctionEnd +!macroend +!insertmacro StrStr "" +!insertmacro StrStr "un." + +Function Trim ; Added by Pelaca + Exch $R1 + Push $R2 +Loop: + StrCpy $R2 "$R1" 1 -1 + StrCmp "$R2" " " RTrim + StrCmp "$R2" "$\n" RTrim + StrCmp "$R2" "$\r" RTrim + StrCmp "$R2" ";" RTrim + GoTo Done +RTrim: + StrCpy $R1 "$R1" -1 + Goto Loop +Done: + Pop $R2 + Exch $R1 +FunctionEnd + +Function ConditionalAddToRegisty + Pop $0 + Pop $1 + StrCmp "$0" "" ConditionalAddToRegisty_EmptyString + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" \ + "$1" "$0" + ;MessageBox MB_OK "Set Registry: '$1' to '$0'" + DetailPrint "Set install registry entry: '$1' to '$0'" + ConditionalAddToRegisty_EmptyString: +FunctionEnd + +;-------------------------------- + +!ifdef CPACK_USES_DOWNLOAD +Function DownloadFile + IfFileExists $INSTDIR\* +2 + CreateDirectory $INSTDIR + Pop $0 + + ; Skip if already downloaded + IfFileExists $INSTDIR\$0 0 +2 + Return + + StrCpy $1 "@CPACK_DOWNLOAD_SITE@" + + try_again: + NSISdl::download "$1/$0" "$INSTDIR\$0" + + Pop $1 + StrCmp $1 "success" success + StrCmp $1 "Cancelled" cancel + MessageBox MB_OK "Download failed: $1" + cancel: + Return + success: +FunctionEnd +!endif + +;-------------------------------- +; Define some macro setting for the gui +@CPACK_NSIS_INSTALLER_MUI_ICON_CODE@ +@CPACK_NSIS_INSTALLER_ICON_CODE@ +@CPACK_NSIS_INSTALLER_MUI_WELCOMEFINISH_CODE@ +@CPACK_NSIS_INSTALLER_MUI_UNWELCOMEFINISH_CODE@ +@CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC@ +@CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE@ + +;-------------------------------- +;Pages + @CPACK_NSIS_INSTALLER_WELCOME_TITLE_CODE@ + @CPACK_NSIS_INSTALLER_WELCOME_TITLE_3LINES_CODE@ + !insertmacro MUI_PAGE_WELCOME + + !insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@" + Page custom InstallOptionsPage + !insertmacro MUI_PAGE_DIRECTORY + + ;Start Menu Folder Page Configuration + !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" + !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" + !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER + + @CPACK_NSIS_PAGE_COMPONENTS@ + + !insertmacro MUI_PAGE_INSTFILES + @CPACK_NSIS_INSTALLER_FINISH_TITLE_CODE@ + @CPACK_NSIS_INSTALLER_FINISH_TITLE_3LINES_CODE@ + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" ;first language is the default language + !insertmacro MUI_LANGUAGE "Albanian" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Basque" + !insertmacro MUI_LANGUAGE "Belarusian" + !insertmacro MUI_LANGUAGE "Bosnian" + !insertmacro MUI_LANGUAGE "Breton" + !insertmacro MUI_LANGUAGE "Bulgarian" + !insertmacro MUI_LANGUAGE "Croatian" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Danish" + !insertmacro MUI_LANGUAGE "Dutch" + !insertmacro MUI_LANGUAGE "Estonian" + !insertmacro MUI_LANGUAGE "Farsi" + !insertmacro MUI_LANGUAGE "Finnish" + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Greek" + !insertmacro MUI_LANGUAGE "Hebrew" + !insertmacro MUI_LANGUAGE "Hungarian" + !insertmacro MUI_LANGUAGE "Icelandic" + !insertmacro MUI_LANGUAGE "Indonesian" + !insertmacro MUI_LANGUAGE "Irish" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Kurdish" + !insertmacro MUI_LANGUAGE "Latvian" + !insertmacro MUI_LANGUAGE "Lithuanian" + !insertmacro MUI_LANGUAGE "Luxembourgish" + !insertmacro MUI_LANGUAGE "Macedonian" + !insertmacro MUI_LANGUAGE "Malay" + !insertmacro MUI_LANGUAGE "Mongolian" + !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "Polish" + !insertmacro MUI_LANGUAGE "Portuguese" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "Serbian" + !insertmacro MUI_LANGUAGE "SerbianLatin" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "Slovak" + !insertmacro MUI_LANGUAGE "Slovenian" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "Swedish" + !insertmacro MUI_LANGUAGE "Thai" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Turkish" + !insertmacro MUI_LANGUAGE "Ukrainian" + !insertmacro MUI_LANGUAGE "Welsh" + +;-------------------------------- +;Reserve Files + + ;These files should be inserted before other files in the data block + ;Keep these lines before any File command + ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA) + + ReserveFile "NSIS.InstallOptions.ini" + !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS + + ; for UserInfo::GetName and UserInfo::GetAccountType + ReserveFile /plugin 'UserInfo.dll' + +;-------------------------------- +; Installation types +@CPACK_NSIS_INSTALLATION_TYPES@ + +;-------------------------------- +; Component sections +@CPACK_NSIS_COMPONENT_SECTIONS@ + +;-------------------------------- +;Installer Sections + +Section "-Core installation" + ;Use the entire tree produced by the INSTALL target. Keep the + ;list of directories here in sync with the RMDir commands below. + SetOutPath "$INSTDIR" + @CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@ + @CPACK_NSIS_FULL_INSTALL@ + + ;Store installation folder + WriteRegStr SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR + + ;Create uninstaller + WriteUninstaller "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + Push "DisplayName" + Push "@CPACK_NSIS_DISPLAY_NAME@" + Call ConditionalAddToRegisty + Push "DisplayVersion" + Push "@CPACK_PACKAGE_VERSION@" + Call ConditionalAddToRegisty + Push "Publisher" + Push "@CPACK_PACKAGE_VENDOR@" + Call ConditionalAddToRegisty + Push "UninstallString" + Push "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + Call ConditionalAddToRegisty + Push "NoRepair" + Push "1" + Call ConditionalAddToRegisty + + !ifdef CPACK_NSIS_ADD_REMOVE + ;Create add/remove functionality + Push "ModifyPath" + Push "$INSTDIR\AddRemove.exe" + Call ConditionalAddToRegisty + !else + Push "NoModify" + Push "1" + Call ConditionalAddToRegisty + !endif + + ; Optional registration + Push "DisplayIcon" + Push "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@" + Call ConditionalAddToRegisty + Push "HelpLink" + Push "@CPACK_NSIS_HELP_LINK@" + Call ConditionalAddToRegisty + Push "URLInfoAbout" + Push "@CPACK_NSIS_URL_INFO_ABOUT@" + Call ConditionalAddToRegisty + Push "Contact" + Push "@CPACK_NSIS_CONTACT@" + Call ConditionalAddToRegisty + !insertmacro MUI_INSTALLOPTIONS_READ $INSTALL_DESKTOP "NSIS.InstallOptions.ini" "Field 5" "State" + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + + ;Create shortcuts + CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER" +@CPACK_NSIS_CREATE_ICONS@ +@CPACK_NSIS_CREATE_ICONS_EXTRA@ + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + + ;Read a value from an InstallOptions INI file + !insertmacro MUI_INSTALLOPTIONS_READ $DO_NOT_ADD_TO_PATH "NSIS.InstallOptions.ini" "Field 2" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_ALL_USERS "NSIS.InstallOptions.ini" "Field 3" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_CURRENT_USER "NSIS.InstallOptions.ini" "Field 4" "State" + + ; Write special uninstall registry entries + Push "StartMenu" + Push "$STARTMENU_FOLDER" + Call ConditionalAddToRegisty + Push "DoNotAddToPath" + Push "$DO_NOT_ADD_TO_PATH" + Call ConditionalAddToRegisty + Push "AddToPathAllUsers" + Push "$ADD_TO_PATH_ALL_USERS" + Call ConditionalAddToRegisty + Push "AddToPathCurrentUser" + Push "$ADD_TO_PATH_CURRENT_USER" + Call ConditionalAddToRegisty + Push "InstallToDesktop" + Push "$INSTALL_DESKTOP" + Call ConditionalAddToRegisty + + !insertmacro MUI_STARTMENU_WRITE_END + +@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ + +SectionEnd + +Section "-Add to path" + Push $INSTDIR\bin + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 doNotAddToPath + StrCmp $DO_NOT_ADD_TO_PATH "1" doNotAddToPath 0 + Call AddToPath + doNotAddToPath: +SectionEnd + +;-------------------------------- +; Create custom pages +Function InstallOptionsPage + !insertmacro MUI_HEADER_TEXT "Install Options" "Choose options for installing @CPACK_NSIS_PACKAGE_NAME@" + !insertmacro MUI_INSTALLOPTIONS_DISPLAY "NSIS.InstallOptions.ini" + +FunctionEnd + +;-------------------------------- +; determine admin versus local install +Function un.onInit + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "Admin" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Admin group' + Goto done + StrCmp $1 "Power" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Power Users group' + Goto done + + noLM: + ;Get installation folder from registry if available + + done: + +FunctionEnd + +;--- Add/Remove callback functions: --- +!macro SectionList MacroName + ;This macro used to perform operation on multiple sections. + ;List all of your components in following manner here. +@CPACK_NSIS_COMPONENT_SECTION_LIST@ +!macroend + +Section -FinishComponents + ;Removes unselected components and writes component status to registry + !insertmacro SectionList "FinishSection" + +!ifdef CPACK_NSIS_ADD_REMOVE + ; Get the name of the installer executable + System::Call 'kernel32::GetModuleFileNameA(i 0, t .R0, i 1024) i r1' + StrCpy $R3 $R0 + + ; Strip off the last 13 characters, to see if we have AddRemove.exe + StrLen $R1 $R0 + IntOp $R1 $R0 - 13 + StrCpy $R2 $R0 13 $R1 + StrCmp $R2 "AddRemove.exe" addremove_installed + + ; We're not running AddRemove.exe, so install it + CopyFiles $R3 $INSTDIR\AddRemove.exe + + addremove_installed: +!endif +SectionEnd +;--- End of Add/Remove callback functions --- + +;-------------------------------- +; Component dependencies +Function .onSelChange + !insertmacro SectionList MaybeSelectionChanged +FunctionEnd + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + ReadRegStr $START_MENU SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "StartMenu" + ;MessageBox MB_OK "Start menu is in: $START_MENU" + ReadRegStr $DO_NOT_ADD_TO_PATH SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "DoNotAddToPath" + ReadRegStr $ADD_TO_PATH_ALL_USERS SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathAllUsers" + ReadRegStr $ADD_TO_PATH_CURRENT_USER SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathCurrentUser" + ;MessageBox MB_OK "Add to path: $DO_NOT_ADD_TO_PATH all users: $ADD_TO_PATH_ALL_USERS" + ReadRegStr $INSTALL_DESKTOP SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "InstallToDesktop" + ;MessageBox MB_OK "Install to desktop: $INSTALL_DESKTOP " + +@CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@ + + ;Remove files we installed. + ;Keep the list of directories here in sync with the File commands above. +@CPACK_NSIS_DELETE_FILES@ +@CPACK_NSIS_DELETE_DIRECTORIES@ + +!ifdef CPACK_NSIS_ADD_REMOVE + ;Remove the add/remove program + Delete "$INSTDIR\AddRemove.exe" +!endif + + ;Remove the uninstaller itself. + Delete "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + ;Remove the installation directory if it is empty. + RMDir "$INSTDIR" + + ; Remove the registry entries. + DeleteRegKey SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + ; Removes all optional components + !insertmacro SectionList "RemoveSection_CPack" + + !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP + + Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" +@CPACK_NSIS_DELETE_ICONS@ +@CPACK_NSIS_DELETE_ICONS_EXTRA@ + + ;Delete empty start menu parent directories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + startMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors startMenuDeleteLoopDone + + StrCmp "$MUI_TEMP" "$SMPROGRAMS" startMenuDeleteLoopDone startMenuDeleteLoop + startMenuDeleteLoopDone: + + ; If the user changed the shortcut, then untinstall may not work. This should + ; try to fix it. + StrCpy $MUI_TEMP "$START_MENU" + Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" +@CPACK_NSIS_DELETE_ICONS_EXTRA@ + + ;Delete empty start menu parent directories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + secondStartMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors secondStartMenuDeleteLoopDone + + StrCmp "$MUI_TEMP" "$SMPROGRAMS" secondStartMenuDeleteLoopDone secondStartMenuDeleteLoop + secondStartMenuDeleteLoopDone: + + DeleteRegKey /ifempty SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + Push $INSTDIR\bin + StrCmp $DO_NOT_ADD_TO_PATH_ "1" doNotRemoveFromPath 0 + Call un.RemoveFromPath + doNotRemoveFromPath: +SectionEnd + +;-------------------------------- +; determine admin versus local install +; Is install for "AllUsers" or "JustMe"? +; Default to "JustMe" - set to "AllUsers" if admin or on Win9x +; This function is used for the very first "custom page" of the installer. +; This custom page does not show up visibly, but it executes prior to the +; first visible page and sets up $INSTDIR properly... +; Choose different default installation folder based on SV_ALLUSERS... +; "Program Files" for AllUsers, "My Documents" for JustMe... + +Function .onInit + StrCmp "@CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL@" "ON" 0 inst + + StrCpy $1 HKEY_LOCAL_MACHINE + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "User" 0 chk_inst_root + SetRegView 32 + ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString" + StrCmp $0 "" 0 askuninst + !include x64.nsh + ${If} ${RunningX64} + SetRegView 64 + ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString" + StrCmp $0 "" inst askuninst + ${EndIf} +chk_inst_root: + SetRegView 32 + ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString" + StrCmp $0 "" 0 askuninst + !include x64.nsh + ${If} ${RunningX64} + SetRegView 64 + ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString" + StrCmp $0 "" inst askuninst + ${EndIf} + +askuninst: + MessageBox MB_YESNOCANCEL|MB_ICONEXCLAMATION \ + "@CPACK_NSIS_PACKAGE_NAME@ is already installed. $\n$\nDo you want to uninstall the old version before installing the new one?" \ + /SD IDYES IDYES uninst IDNO inst + Abort + +;Run the uninstaller +uninst: + ClearErrors + StrLen $2 "\Uninstall.exe" + StrCpy $3 $0 -$2 # remove "\Uninstall.exe" from UninstallString to get path + ExecWait '"$0" /S _?=$3' ;Do not copy the uninstaller to a temp file + + IfErrors uninst_failed inst +uninst_failed: + MessageBox MB_OK|MB_ICONSTOP "Uninstall failed." + Abort + +inst: + SetRegView Default + ; Reads components status for registry + !insertmacro SectionList "InitSection" + + ; check to see if /D has been used to change + ; the install directory by comparing it to the + ; install directory that is expected to be the + ; default + StrCpy $IS_DEFAULT_INSTALLDIR 0 + StrCmp "$INSTDIR" "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" 0 +2 + StrCpy $IS_DEFAULT_INSTALLDIR 1 + + StrCpy $SV_ALLUSERS "JustMe" + ; if default install dir then change the default + ; if it is installed for JustMe + StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 + StrCpy $INSTDIR "$DOCUMENTS\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "User" 0 +4 + StrCpy $INSTDIR "$LOCALAPPDATA\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + ; MessageBox MB_OK 'User "$0" is in the User group' + StrCpy $SV_ALLUSERS "User" + Goto done + StrCmp $1 "Admin" 0 +4 + SetShellVarContext all + ; MessageBox MB_OK 'User "$0" is in the Admin group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + StrCmp $1 "Power" 0 +4 + SetShellVarContext all + ; MessageBox MB_OK 'User "$0" is in the Power Users group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + + noLM: + StrCpy $SV_ALLUSERS "AllUsers" + ; Get installation folder from registry if available + + done: + StrCmp $SV_ALLUSERS "AllUsers" 0 +3 + StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 + StrCpy $INSTDIR "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 noOptionsPage + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "NSIS.InstallOptions.ini" + + noOptionsPage: +FunctionEnd diff --git a/conanfile.py b/conanfile.py index 5e391379..759b6673 100644 --- a/conanfile.py +++ b/conanfile.py @@ -104,12 +104,17 @@ class Re3Conan(ConanFile): """ cmake_minimum_required(VERSION 3.0) project(cmake_wrapper) + + # FIXME: remove and move to main cmake script once conan CMakeToolchain is fully functional. + set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT {}) include("{}/conanbuildinfo.cmake") conan_basic_setup(TARGETS NO_OUTPUT_DIRS) add_subdirectory("{}" re3) - """).format(self.install_folder.replace("\\", "/"), + """).format( + self.name, + self.install_folder.replace("\\", "/"), self.source_folder.replace("\\", "/"))) except FileNotFoundError: pass diff --git a/resources/create_ico.py b/resources/create_ico.py new file mode 100644 index 00000000..42c734f1 --- /dev/null +++ b/resources/create_ico.py @@ -0,0 +1,17 @@ +import os + +from PIL import Image + +os.chdir(os.path.dirname(os.path.realpath(__file__))) +original = Image.open("logo.png") +icon_sizes = [(256, 256), (128, 128), (64, 64), (48, 48), (32, 32), (16, 16)] +original.save("logo.ico", sizes=icon_sizes) + +# Crop logo + make sure that background is white +width, height = original.size +crop_area = (0, height // 6, width, height - height // 6) +cropped_image = original.crop(crop_area) +cropped_size = (crop_area[2] - crop_area[0], crop_area[3] - crop_area[1]) +new_image = Image.new("RGBA", cropped_size, "WHITE") +new_image.paste(cropped_image, (0, 0), cropped_image) +new_image.convert("RGB").save("logo.bmp") diff --git a/resources/logo.bmp b/resources/logo.bmp new file mode 100644 index 0000000000000000000000000000000000000000..bc58c92ea7eace363e5ca2b1a2c6cdbedc5d1f31 GIT binary patch literal 2101302 zcmeFa2fP*4xv(!BqzNb;R6r0f8Wg({v5UP#1K3-PaIpj77Hl|8>Lfo{zsXd+*t^-!=2DHP88xU$$${yz5=hYR}nc_RR5T z9=6LKJN(bjVdj5(|IdqRJM8dk)g5-IvO}}~`L7*zFz(+$}7k=~2H=lm`>1(gO_Sj>O{r&HM|I?rT^t<2v?yR%UI`YUP4?g(d{rBI$ z&A)$Iv}jTP-#Aux67RO9?7ope&|+O@@YTmnG=0uaza0RMMn zFCt9{KmY;|uuH&v)IWUq@b2BaoA2c6*rLe-0uX?J6$1Fb-R*?v5P$##AfTJT#*G`z zCHikClyR`SG{`mYS^%$)t$LcojNUB zwmk5_15Z5h#PiQT|LUu+zUii$ZoT!^0ssCPG-%M^fBy^}I@H`ph#v?*00OcE%*qp7 z;_!c4`wDR(009UcF?h7$BQq%xNqOSk390od+)tx z?*DI|0{3K!yPRFT;^25P$##Am9!GWBzBHaYpdZ-%(1;jexGa z^2%3UdFAJye|D$EWC{TYK;Zuo!2kar3tmA00uX?J?*zt<9ec_tr$q7m?T@KarHZ+> z=kB}jHje=Coe=GV00jIefdBj7$FvXv5P$##{`erR00bc54FUY$o35lG5P$##AP`Dm_3G8P+;U5;TD7+SNI(DU-FDl}d?|SL?Af6K zDTDw7AmB6s{NL&BBY6lw00I!mPr%$XyLt2G`E9@QUGvw0-~8q`AAIn^mMvTIhvi)e zKmY>n62Sl6?K?7u00bZafjk7v1wUt00LGC;QvI^uUWI^0}ni4j#QE}X+Qu15YRvX|JTq9KB_@WKo8%YUz4z1FW^?^CO3 z3j`nl0Vx9bzZ6O0KmY;|fItESHf-4No8SB^Zi2xO3exq(lYE?x4-|E|04${jv4I52231Rzip0sOxxTpSDm2tWV=X%R4A}ts^s$j3of{zqM{<3WN0-6AOL|F1n~bD&^QGG5P$##5+g8a(xjbs+9{X( zUv$w$=2-xVB|}9JfB*zMAb|gS(1|nw0uX?JuLPDZUD~Ein_TjL;)y42+_=%#w$m;M zKmY=k2;l#gz!DY$5P$##N+e*O^JTu(n@j%9-G0`rSyN&aoDKm9Kp=wz@c#^UI!%TE z1R&r9fxdnF=92#o9XhO7vBHT~kR${k00C_T@PBPRLOu|H00bal2#g##vaGBu)c+ea zXz<;4-$4li5P$##;t|0A<8k9G2tWV=5U@vJ`SRs0Teb|9|7z8$ef;sq_Npc_1Rwwb z?+M`l-ghw#ga8B};2Z&SOV3dGH-!&A_@HymAz=tW00OEA;Qy++gJd880SG|g`RAX{ zCI6RPatVAO009Uvsx^m@8!MX?q0SG`K zn*{LxZ1y{?h5!U0pp!uN?%hM>-(2=TYSbv5Ig=FxAOHck2;l#2^%vPf00I#3n827Z zW2#oI8tVUl``h00uYEuphu4$q4IysF~>xVz+n)800bZqodEtHofSty00IzzfH;Bq^XFHuUOnjl zRjO3^^2;y9t0N!;AOHaf;Qx#P2tWV=5J-_guU@@^@^4Bmyzs&lvr4P#wQJX!Cm=6f zy7aq$|4g4geLO!9fPhB?zWVB`w3e6c>&5@Kr@?CoKmY;|NRq&!MTGXnU3lFKi3K>z{}fPgjv>(;Gn(4awZb1y3^n>A~ewrq>a$K26q@ZiB+ zyLL67>IWN02?QXJSpxWfQEMp&LjVF0fPiNN9((MuPzNxV#5meg^M&Pp{rVkr&_U+Q z%b`u95CRa$I05|M(dC`AApijgKtP1R!3Q54Gy!AI-+c3pNc_ZttXQ$aeA>VN{`&`; zLkR>R0D(-6yd_p^sLI^-07Xkd= zi4~qCApijgKtPq_yoI;5(q$`AOZYe zc0nc@1Rwwb2)IIE$dDnSyX1fY12i{b>(;I2I{!L#>VzgwAp{@*fr13^f6a?Lxj_H| z5P*O^0-ZZ|4(^iXnO{Hu{Ik7EON?xuM|Q#qCj`@`1OgC%Ky(85e~C*mr$Yb&5P*P- z1b+PSM{|e&;C6Y|S!bzj&RcK2#mD}^E~5kj5Ga-a{;zhOCou>>00I!ON8tYZ?+@KE zUwY{!dzF?F*KO5_ zz4qE*s+2$g0uYEz0RNA%6mtp$AOHafI8ES%7hVY6H-`-yrmbDK-F91OsuV&10uYEz z0RPvv)RPYcAOHaf*d`GA*e~-e0Bsllm}~xH@9&%l0SG`K7Xkd=_AWzw2tWV=5Ku+H zTu$GhL4)A7c>3w5tIBiy`0=3^DFrj91OgC%Kv4wne^skJ$v^-C5P*Pf0uv@o2;C_E z^{;=~t~z>r^OuNr?b?OrNg)Iv0D%$-;Q!HAV~&IX1Rwwb4+z|M-+iH5=MO*ppr>`$ zTysrmk`zJ!0uU&j0RFFMwI>q@KmY;|NPxgO=bRJVJ=?Zzn?ThOtN8TOPped^5=@d3 z2tWV=A_VaNh^sM&K>z{}fPe=Cwr<_ps8OTf271L6SLkWoQAZsW%#acYKmY<#1n_@7 z3qF}Z00IzzfNcU(r%nysK!*+;YP;%)@!x&--Ovmvga8B}AVmQGkGLRn7z7{y0SI_N z;OVEI4&6ZK%$cL1b>`0#haGlUXoeI*00Iz@B7pyESn$aK0uX=z1d<`pyLa#42HLDy zvt(*7Sj&hJBZBEs0s#m>K$ZahUvM?%00=+;0ubK$ZahuVl?94G2I00uV@mK0o>(r?uyUP;|0uX>ekO2N) za6#q(2tWV=5b%S*ym|A2`=BX#>7|!6H1NqMpA1cpLI^+r0hz00K4$;Qz^NI@AII2tWV=sT26^Z+{!yNl!lchz00K4$;QtzyeR6;R1Rwwb+XS|5-CDhR_25oA zbm&mqRTqqJa;sgtb}%+_xec2}g2tWV=5J-T)n{U1ux{X#;R3uP){wn(S?;n~Q zg%E%M1Z)t%|MRcOybA#cKmY=M5P0dOmx9}9)v8rBw61U8zQNomfdB*`V2J?!uVLjU z2M9m_0uZoG;Mr%N4eq2hYSge@Zja*8 z=9%D5ivI_hPyzu6cuxTTcYQ}Ae+WPT0uYEo0RQ)1X=oq>AP|uN{vT!6;1~!%00Izj zodEtHQ4BZ?0ub4V2tXhT0sP;4rJ;ckfIvh7_=HJBfL z1g^N^ir~)3^M8X(D1iV3yeEMFSMKSnR;?;8FP}DT+L$q8MvNHo!3Q6_{J%d>KmD}6 zt&EKRe*_+V^wDkI_1bH%neH<`pM3Jk`0?W>Po8YLb=|skl^x9s2-qg@_rL$$`@O@P z2IBwTbR`Xe00g2F$d3OTy`MUD>i_va|7YmXq5b>!zvh~2&N}O?Lk~T)b?esk>({SR zrAl;Y9O)u~+O=!%zWeU`@4x@?#~F!&hH@^$91Ou#<@ zM|bbu{hoX7dFP#X=FOXz9bcuTvIOvdhgJ#v-=WSTRR};pihwu%zj*QDVZ(;qeDlr6 z9(!!PdiA9E5GTt78a8a$xpU_mZ@lrv7hjw|f4;0D63t@*_`gHz2LA6*XOSudARtA+ zY5$)xWy(MQ`Oix(xn!@s_L2fZ90=qh(6VLA3opFT{87nVFXXYi(zMbE;QzTMk-~EP zUty<^5(FS%n}BovzkdCC^VQCqZn~*myLPtW5FY}$2sCZl)coRUzHz*4*|O4gkqDj< z!2fei9EIihzrs!-B?v&kHUU@s-&}oRzN&e~8E2SpaoT1=dqc)QR!tG3&2yLIf?@tkwc`NJRnVD1HK z{<84=^Us@GzKk3>a?+$pbLPx37j>*yu_BpepIT%IY}vBKbf5Y8`RAX%`|dmQB@xrD zZ@lrw6Hh#G*Ijq@>C@-;zyH1IdULCvJ@?$RdiClNhfVau%%7;tr9mHm{PEVUTVmY=xZ!TLI47#6VUAcrMJ#po3YP6`~3E|zcu#-GEaUVJ9g}X1q+NH z>RI#2BwYeV(an$;HENXk1+ZVge&?Nc-T?<3Aa#vdn>KCkz4u;zkxaLXQ>h*QPX)Wv zmE!-U@AyQ300g2F@XY^>xgB-XQRXw=S6_W~#*7&i6%|hJ2qd2h0rMN;^y$-Ic;N*z zkj(u*ciwsD=o2bqh~5)&kV{|my* zNGSY2-qOlh5P(2D0*?B>`L5-`2Os>`zy5Xn`0-q2oss=fNi)rN3I-1zeCC;F)~s2x z*hyEVN)>YtD>G=7w1qS*5y1Z~f%P~n{$K2J%JC3@Kq&;=^8W@68ko;u&4n4}+npY7 z1T@VF0_Nsa=KlZZo_nsj!l$S?XnqUm(xuDn*|VK!CP~I1fd9vUmhn^Y|Dsk=4u${( zN+jTf|C>+yPe1+iS6+E#!-fqR-y>+gLIUg7tuyx*I{WOi&G*ft&qs5x`CfpzNs+=9 zky42S@c$A4X7F_UKl(MfzqxzQz<~o7E?k(w?SLjbM_|#SMS}(n+Hb%8 zqEF0*4I4i6&_kOyZ+5P+B%Fr;{+|a9Z-o-T|D!LP90>sklukgW|C_tAUv}ALqeqVp zT_-7ofN}!ni38>eAMW5%prz4{mHA^!;hZwM@1y7Vu9`Aglpbqmg1bJdVJ za?_?w-n2b~4Z;62*y$xU8UHW1HgW(2ARtIUiT{_la&kHZoFHKC_H*5J*G2hWK*x?9 z&4UJxmqZSL00aaH;QyX&A~eSp0_J%Z&6+jKKbOsu!fw9#<}F*c zxY8){+(rQZXCxF$0RPXw81gOzARtQs|1Wlx=6DE55is9&H1^%CZ8RT6EKtPrN{_oi)LUWuW@X05iv}w~O|GaMBzWuk~e(PM* zGMg~|pV_XD-EjOr{~E}<5P*Oz0sKGqa?OblkRo6{-tW<)NB*hZq)C%WlO{>kNF1vK z@PDg#y$p{3=U)JM7XlEFC4m2Xxs}i;7YV%l^2_z>*UvM-&AaAKxh^&>YdPcpS?lrA z+l&9_S^9Vj0uT@y=Kjt!-o%dv1!@K8UN2tUrV(V|If4N z@fHLiAWi`Pms-7v0|6-lTeofFz&-cebMfNE-a~M%f%w03U6y*n_9$QgSQF<*z5F~*A3&PAuDEvRy z5?53?{-2TgLNg%%0SIJ)fcYyxojP@L?Sp^#!ymE$!J#(d{|PPze_W&%f`|~n|3xrnDhU2xl;`I<82``Itf8?GfB*!t zK)`%kzpSh*bmKF>8t`afJ!1g>*V7lSWP<;Pu585?;{UD;c=Ch*1Rwx`=mZ{o@WEUg zVw*N?R<2wbJrqaUBY^+gW9w^V{6E*qR#Z9u@9PAiT@Zi(1Ts$G$}6wTwIN=1*<~4r zqon!xzmkq{C=L8S*RobrIsWg^m?u>TKmY;|h)%%V?dPbYj>@$szVN~e(L-^hB?9=r zC9occ#s71yX+@Re{~pf~ng#&~Kp^7;e*E#r-FM$TbX%-nzy1$D{E%@zsW%`0PrVDA zO91~5UC@dx#Q&We`6LVh2tWV=(FuI^*=M=F3w-LSr$&#%kv0h6|2C-l9TWdAR`o-V z$N&AFCbSL$5P$##`u6P`x&xZRmtTGvJ}DBw|5JqTXw~?CuGOrla{S-X@lVY7>C2e3E=>o2muH{00OgS z&C2y$;A^hA20m#K!2i=C?{2;Lf3d0`dOZH`?i3(%2tWV=5Qt9TzWeSA-3O~ytvY`E z_~>ysQjh@tF9Ad z-raif|6)}?^mzQ=-6=rk5P$##AW#&6Ns}g3t5z*^H+=WqcZ&+e!6F3ke-VtC3WEQK zu3*I$;{TbNH8d6i5P(3&30!~u^`RT(KKtxrz9yV;ILS93|4*K|>owy4p(|Lih4{bg z6M_68009U-uVM@F|4hvq8Vdmk zKmY;{J@ioMUfH5Wi;WvM!Y4@r_@f+OTf(QZJlL0&iwrAU;ldTwbwrV z^wTL*ru_8NPh6c_xd5j4V(Zrb{`u!k)2FZd?6XyGzPa@A#}^G4P;UP9?K|(Lo95kk zA<;)_)?XRcqmG!xgrzxwK{q1&bT zyW4A0rRlVBab}t^2;yp-@pHhFTQB*+P!YwI@z^_Xq5yuZQAQz^LckjIm&94SCW~|$? zrE*;O@M7!Mt%n_USm?s|!V53>&_YMrfd7XMLZ1rpf1SPQkpG)6ZGHCHXXZ2EGtM|8 z*SEKHLMAJ#1kBz1e)X$gU2@4K_uqfN`E|hD;@gpZPidM97Hk|gY}swM{m{Ad)Y`Sj zp!yKlMxaiev7I`7)2r9qr=D6oWy)Hm!?&2U-hA`T(4}d={q{4z9TXFZW8)LR|3e3% zPlfou&fe7T|K`FEhmT&Od+FyYDV@@h@-YQlHSJ%zTERd+z#^9sVCW0(~mP|8@4JHvc!D z*$y5&_<#cr(20wzTqIDpZe4SA(1;Nu{Mj-~ZprM~>&+EDEn18Z>Kr8yh(}=8UB{b0 zAZ)3` zY9-coD#{vxLk>CQ?YG~~@J{dU^(m)Jt5U`1udl|_SI!b4V7@!trOQl*z92AY z(4f$j@b=qp&-hxAO!M*o(BbD(A^xwkH#PYGk|j&bEgvy|og>7*tPVf?@DD%yFqy4Q zRV`DeuItuqCgv|;hb_yRA z2gjagCHTM24o%np&FVUE;6QVEhE6zS=p{L0F^ z;skcwakTl4@aomtZ>RtJ-~T>zt;^;I1qrWX_8y_ulf|cH=sKu6Ig}z#e-{_~;|mH{<&1D|6ecke@5AxFTU0&R}); zf5^!5s|f$s*`KNSzq#w$-FM$zvt~`5Ovoxz1dcuS*zdpp-pg%w#fr_BTrykyLtO$w zpjZOtsO zq(N5MBw!vHU>+@Jdz(u*etG%E0}hy6tfz521ndzw{P3?AEviU(B1Ek2o_p>IUHx8p z<&}sLI4m9k{6BQy`BaGi>+H>h{eS7wrCjr)b0lOuEA!VVb0_e4`em8>rG1de{j@!R)YWQ?9jyg-`wPLpMCbx34yF2u#G^KDpiIK9qRPPYHshp(@w}g zpZu#P(6He+g?|Nj{PD*_S3({gXmv@%|3gQePlfou&fZMM|IJsZI(6!#lK@#kV0!{P z?zrRo@4s(#zq1$IJk_^JlP`V!+?aOxM8I61_QMYw?ahT4krypmWc~=a{c5o7^>x=> z7b6;{L?nR!2Zx+zCHTM24o$%S&E0;^I_oT*%_l1e1PPeG_?!CyM%=vg9A>^**{an9 zpA?a{_)TEneJ5#qG=O+H}3{QrRm9?;o#vVuS^ z0_IvZ=kEL0rOQmeb&=NjN8msIQ`a;2?zrQQ&^2M^%$fd)w~`j&|Di+9r$YQ+XK&i} z{~0r8)UI7yXWPjN0(l7BaKjBs_Qxn`Jn_UL|KyMsWr4su?<|Wl!jm~B*Mql$d9-1f1Ukk)&Dnb+7$ZdRGlcu3If{`FjoS9^2sMoY{zrwZm3spY!+OR zHu^`PdGqm0mTb~9(V91JzWq9|?X|eT=xJ~2nc)Ay@#a~H`1I-BySHbEQfZD=|NqB7 z{*g+fs1yPs1lqN0w|VpC)c41N3H)>nCMyU;C$PsJ zdu-gO>n?@A*jrCOeR@3U;w%X0A@KX(fBTDpoz{PT^UXJ*>%+6pKATobe%FithmJR& z3h{rPy=lq+*REaLv}seFO(rV{6h+{%#~xF=|Be~6T93k!2?Syjs8(&Xd3uG0iDqtC z-KbHc;1Y4;jW=pY&%Ye-|KMQrtOWnp*`b#F-&{4Jv%O>mfno_9a>yZS_uR`an;qM@ zI1vJ>2>kia^OQ`qlTJD*xJsOO;)zPq^DGVgKRDPtE5ZMDcBm!)@7%ew&i0ZO1Y!^{ zmjo!>bJwidQnxO*{o%V0o)Fk=xAB`dZ`Cl-`t<1&TqVpO1vKR5Uk>cb;0JZgDyUR1vuJ z(%Bkj+4Sk=JM|$y@4fe)hV(qk0sjvfYJL^r|2q5AhW{UP%rQC}OI8qwO`-z4A31WQ&X$rD1WF;$wQE;Z+wN_*{h(0ZNC^TZ5_sg1pEOLgGtM|8 zxLllh=9wCD^DGDaKRD7nE5ZMDcBr`j_wV0dXG_Tn0;LdW+_JKg#=8pmza`P+){69F-JS)Nfb#|z@|DS#K**aTFRuCwWz}&fW z_3XTL>Tu^zPwsoEP{LD8scqYd8fKZf-Y>XZ?6lKP8glb22mC)c(mX4{|8;h#xc~3F z@4h-)N>&gkk-$eEeWYaP{pqKQ6b+54Az+Vyc@XfXP3ixk?X}ll3tcYEqwSR}0^X#7 z|A&q=p9=ARoxLgR|Kr{y8VsUwm~0RJy>W72;*{;#u-Wc`2P!i74UNmdXjoq(RFfIa;*PyXWKA8!e0 zcna8z88bpxjPc{gOL;GGwh_SpLr0oVh4{bD-jwzK&p-cMXEVtP0;Ln^)~%b8E&KM{ z=coG#Eww|y9)ZDw)4v7K(xpp7R}3BjCcXyZ|Dhwzr$YQ+XK%{-|C?{Vsk5161%c8D z9CzGt;(K4bfY)9-$DVx=83O4N_^`+<%f9|>GbT*T$AW%Ai!wx%4$$ovs72l?7Xw(h?dj$ISRdJ1d?b@}2i^kBQ zLzOH7-lT#52ZxzwCHTM24wd!)#~yo3XEVtP0;LmZ-@d()J@>rxX4IN++<#9(yR+cF#Ct zdb);2?GUg>;L0n%)iBYTH*X$XG#+^10S&qNlmq@B9A=)C;Qu;1RM!7PC$i3llNAJt zCD5o*BPHAJ@yAcKXJ15yK)M9ZKVQWofLph29b6&?4jiat5%4CBP;F@2wylN+cJ10V zI0h&wmH_@=EU{#cm-YYKZ@)d6w5bIG$rIRRmt8b$NhXJ*kDijQp;0>o>=EeNb*6@i zW)w8I$QW7Gkeg3A;Qzs4=2;2;ud_q(f1Uk8RtgE=|MrxR$PlO`fdBiX_XXR6|0~?= zWK+WbWqC?P!~dm7632T2_^PY2-t z-fx}wzs@ek|8@2YSt%rd|JzeOB152(0RHcj-WO~O{;zPelT8W#m*puH4gZ%SNgVG9 z;Qy6|#tR78BY^)GbY`Cp!2iA9I`My=&|9NC5x0r+h?)KqUeE-zU8<*cSX> z;bteB68%{+cb}|01vtP(cAp!i~p7IeH0+j^tf1mWeU|aBig`1shO8CDlPpN45zZ6O0 zcuxTTuQW7XK)@aW{J)?x`*Z;Q@BP+^|Lg2x{9k9kkd;CL_`f~nBQgXk3E=-e>3zYr z;QtCYJK2=*e_5VV(eQsMlEm?z0RCTTXuN=cJp%ZDL1*^q0Q}$ktrP#(*~R$3&VC^) zg#_?_d&);-2vicl|9#T?f^EV76>fI2DdGRJJf))H|57B0<2?cVztYfn0Reji@c)9& z?9&1GzxP`w{;#u(@qeBDLRJb1;Q#iNkH`?HB!K_>r1u5eg8wVr>||5I|7CefMZ^E4 zND{|;0{DNWq45F&_6Xqr1)bTa1Mq+Ew@&3;wTgvy)8;|Ci+{6%GHFB1s(Y3E=;ghQ+Bb@Qb+**x2JqWhCn3&{NE?NFW46RU*Tpan-cyn%Tp>E z{x3z6INlS$|0@lR7Z9*V0RJ!O%sw4}|9ii6;{Q6k82{JVFJz^V0RC@J`G^dGN&@)5 zPkLXlE%?8}%}zEY{9l%*R5biwiX?HoCxHJ~8X7MkV2=R)U(lI-IspIoe(S{lb#^iS zud`psN+ALK-=6Xj83L6A@PD85zF=GMe}$W!Y)bgQEKjLu_`eiM;&@L0|F1MOUO>Pe z0sOz9Gy8M^{_p+PiT~^DV*FoczmSze0{Fi@YuS`>hlI*V)DRzs`OkD}@B`e|ySDWC&Ce z!2f;H`+{x3{}pa_vMJ&JvOJ}t;r~)3iQ_#1{J+xBcmV->1n~cY&g|0x_`mmCC;qRq zi}8P*{X$j>3E=|s3d^@`=s{;+k*cq-0Wmi!vAG?N=3u}rAQLTdjj}>rJ?Zx z0`>^t{{@}drvvbR@3&6;UuPHN|2q4HtP~Q!|LrLsks(k?0RQ(%?+dmC|5v!#$)<$= z%kq?phW|^EB#!q4@c&9f;{^ol5y1ZoImm*0V?+M`lm4?O(2-qWl{}*&-pANwPz27?V zf1O>7|Lg1*vQkI@|F@@nM20{m0sP-5y)W1n{9oZ_Cz}%fFUwOZ8vZXuk~rQI!2c@^ zjTaEGM*#mX=*&JHfd6~Hb>jazyBPo1*)KcpxMO{xpSpGH>TIKPS>gZolrOEwwQG;5 zUw@p)znV42q}2+~>c#(k())sK!T+7x3X4q`|1XwUGRNcp$#5>JmPU;ltyr<5;IbM0 zfIfZt6qNu6hZ4a5J@d@dPoKVJOY~_};K+)Kt%o1}wP%P@X%7BhV5s@^HvS*F-S||9 z|Lg2c{9j?ebm-7Qc7Y!|p%gGX=%}b#^HJuiV&W)5HH0+rrYS2>(xu zeExdz|NMMXc^Cgr1$)6t@qeAV7&|NcKQ=n6C*uFIf~`Cn{$FtGas2@NUuO^E{|fsB z|5w;4CMEpes`Vuw9RE+Ad6XLQf91w5n;!nJbH5YJ3jY^`DHsa>7lbJl3jdcPDH{j> zk0Ngl9fSYt>@56WR6uv1J*_`gnFjGYz!9~+(3 z6Y+oL#x9#4{;zYt6U++#7lbJo3jY^`DHRI;mm(<}2miNfeaQ#M|C47PrAGW;R6uv1J*_ zQ!o_%F9=gA6#g$oQZ^3$uTvLeXNCXAMrZXz{9jhEl}E$>W%ntC(eQtTonlhL{}uKN z{;#l8OiK8_RqIPWIR2kJ^C&gq|H_SBHa+}b=YA)c75*;>Q!o_%F9=gA6#g$oQZ^3$ zA4T3AItKsO*;)9%tY9mThX2d%QwpQu{|Y=*oBVW*gs@PD1U7&|NcKQ=n6 zC*uFgja@c9{9osOCzuufF9=gG6#g#=Qz{hxFGW%|4*qY|`jQWh|0mBpN{#rxtY9mT zhX2d%QwpQu{|Y=*oBVW*gs@c$_C=Fl=&izg>EBs#& zreG-iUl68LDEwcFq--4gU#Bj{&I=&izg>EBs#&reG-iUl68LDEwcFq--4g zKZ?9LbPWElv$ODjS;1Bw4gZ(jrxZrR{}pzMNeTZ~*f03M!cH+M;r}{yF?Lq?e{6JC zPsIO~8@p_J_`lBmPB1I{Ul68XDEwa#rc@~WUy7t`9Q@y^^(7x1|4*KIlp67WS;1Bw z4gZ(jrxZrR{}pzMNeTZ~*e~M#|NQgM*XO5X0(3_`j-AZ95tKU!jD^ zriA~?@|22(|4We+jD!E%-f@Tz0UHGH|NLSf@m>7i#^6*N6aN>K2c4ntf1Uk;|10bl z{9o->MPd-hLjeDeX!!XL!~gS)G5_Ae{}oDzY)bgQEKjLu_`eiM!8rK8e|sD)$^rrW zKfl;Vd>8-E!epszBmOTa4?08P|2q2x|5w;A_40_6z=>sqK%(dP4yJk7)S$55xbxnGsPM zg8wU&5ZRRQe_5VV(eQsMl7eyY|0uf#$3Q?E0sKF|*hhR9|JOFKt>%OO3(AAeQ24*j ze!>40_6z=Rb-N)r1S}E2|05cH{=@KpOXE{#So~k1gvh3Z|I6}}iiZD7kra%B|LfeR z$O;1a3E==*oBVZY%2o^5b6CnE&#|A>a4|1kVN zBlBfjGx2|g5+a)t{x8c@DjNPTMN%*h{-3GskH&gK0RPV~_7UI3|Gk+JQ5u5(3(AAe zQ24*je!>40_6z`=Avgpq5y1cRi+#j*@qbI>Q)gKGUr-)&hQj}K_6z>6uwU?ho%b;s3HcrJ~{gQX~cA;QyX&a5N_)1n~d-VjuBc{68b}Wm_}x ze?fWB84CZ`*)RCN!hXU3GqwHESZ@g6{}By8|6%yQH!~tiL-2ov5+a)t{x8c@DjNPT zMN%*h{vT!6;1~#KBY^+s7yF3s;{VzPw$*&_e?fWB84CZ`*)RCN&VC^)g#>olWf!aa zAi-THfd5A{{QQUE|E^DjqVmW86>fI2DdGRJJf))H|57B0<2?cVzo>0Jg9qdP`Ncls zyZC>m<_`Yv{nm;9>+E9uUuVCNl|lmef2On~Mq}~+h=!m4F#JEp*mL(3{9oZ_Cz}%f zFUwOZ8vZXuk~rQI!2jKKjASy$|MQD|#CP%kWQMDfTJV4Gw@&|2B4ipJL+w`Ncls zyZFCPlcHc-@PF^OPW)eI7vujr`-Q9&62SipZsi$20RN9@`1udR|1&j@@PCDyooq_t z{(t@T*UM&@&1m?4Hv64c#~^_JXG%+AH1?v4X5}|q-i<;4|Bo^D+&u;VkFgoKb;`Nt zp1Vx$r@XwpQ>RXD5zv_}{;#uN$VwprRsO$u^VVg{Ht|mnf$zWH(7yeoD1OT^`3d0v z?#g_O%<+GPn;j`-fdKxWgy)0RMN?CrR5a0{FkXG9M#z{6Di>Dh*GU0REpYe`%;4q^E0tpZ}`|Ptrr)0cB{6B#%ql&Z$;Qx;LBx$=v zphu7I;%z{(XEkZkB)HT({P4rFwGa*e4-PX*Admoo3opD-aQ(sm6X-IkNQ(ge@0LcA zt)m3~_{X_|yHe~>_3G6NE;Wxo{&?(woES;~{|^o`N+6H`fg5hPA#_T{E5!d3=rXEE ziva%bs85o%TLcCUTo7*q5Mf?;A;Qx;LBx$=vVD#u!F?Ju>Q>IOu7P{0-oH$XoBBJ5{p(Bk# z2-qf2US2M^{^0+%`-}J~62Sl6(nzv(kw975=rwD$2<}SpLVfbdC!tHt{Q2|a1>~$; z1n~dRkwzf|tP*I}tXZxp8Lu4wx7uF>PnQ7x@1jqVvs(oA-+ywv-ADGUC!TmBxZ0R+ z1Z>>6QMMwY;s3#rMhOIL6FC3;^JNzw{NHw$5kEx&_`h2wNwzK$=+$ek?5-3q+O4IOEdqb~)A#YV9@(=tZQ4|$MvdTVbNAhM%T`1*LjeB|jx5iocD`S#oO;=59efZu%cP3Thd{`>F8h{h@T3E=;sgN;H6SR!!x z>8IzPlJVZf|1I?wVKorI|K0LQvUQ8V?|%1fynRRftY@BiCUn7BxNxC(T?EAcLkAm$ z5U@eO+{Da$zF}k8!T)Xa7crF(!2jLyNwRf_K&@J1=Fiu1Yt5cLdj{8>#*G`>*ffcW z{|5&fB@nPg;O3iewzTHp|CYLpuo?*9{|@;isk%jA&>%%0t#|0qA-Le2amE>z_RWlk z#s7nYjS>jhAaKw@2ibdq3I1=R%ZRCj0RHcmQj)DJ1Uh$~x>dzpfBwhBSFBi3wQAMi znseu!cV=AFEj1ti4-Pg;ARtbldGqGqe{b;y3;4fyml0480sP+;pCnI*2()Q4anYg* zOKuq>>>F>q5xVA#962&ZG)^f<0RImiZxlj6kU+h9^(IW1P;fGqasd7>*k6RwMgaeJ z$SO(I2?9-;d^vlzihKT4ZhTi?eRXiRSzx4hhf?5LjzkaJEL;VD*RvmTYjo(*PsQJe4 zb~-z7;K0!JXX3<(+a1MANdowP=#Zlj0;LexYp=bA4<9Z$Q47Vw|4ZpF&Q(qT|JU!9 zWT>3Lfd@|h;)_*lp7s#FOsMCNf|r3WWM zE&}*}aL7>tffxjiIO2$*Lx(1B{@bQD{vV^uIK>qL_`hboBsZM|s#FL`RjL;~h{g?;<>oi=S+>65XP2>5@*{^Bqf z3E=-a4U?=C5-{3-$RS_q{qFI{AD=gGo|37J|ED-4sMhA56f0s1h@C{u+%qE-vMBXit0RA63@F;}9F9_7FTeoZ1uFpO9+>#|rN}O~yPRIX$ z(O>+hPXzFPJ*r72dI;37KklN7W|=E|R;<`;V_Qik<`Yjm5xQ7C_~3)dB#_x!@c+<( zM-&ex_V{{p(xIzH`SF4&NrkTL6e)ZKo_bgbsQv20GufP6! z=vwsHV~-WYFu0RA63>L`RjJOV9Rv@lmKCVv-D{6AiQah4MV@PEycNp8vs?6S+a zn{S@CeEFv6W|;7i8#Zh(->?g=MW>y1TEdxRsyh5XIOHgSK#2tQ*=L^*Km0Jsl?4AU zvCBAJGXeZxxnYu?egcgfe>r&YPimh9aPGP12A3f7ZM)U0S0}lXW~wgyKRDzlfq)2s z9zA-Pk2R86Pw;<{E+dFe0{Fjvy(B{?2$&~=m6vP$g76D3yb!why#4mu$?TyStOfrM z9dZ;xK!m`-2OpfwJ%aFmk^UlxP6GJ96K+Y8P7r9+Xxt~CtkkBvt5&TtpY{jWpI*Is zX-n0ceDMF^c%uXYf&^N(Zar((EZZvv{x8^NgwjR;|93(xNzxSp=6<0szNqd&Hb)w+P_>vi(IgRRr*VSFDme z9U@S(=9tevx40`6yWfo)H!gJ9dG*y-iw(u`Q3&Awq2rB02#6E7^2#ftOv!l1;Q!+N zML<0S@PCJ_l2jccu-k6qmn_k8U(n5)H;3K@^!n?skGCyyR(=Bbzn(pgOuQ#BY}l~; z^D*|j_`mmqC(8}Q{~c0FQgw?!w{A0I?>piro^Zkm!G+E|TT{Fu0vZDNe{ir-0s%_| z%y)q;Js=qWx71~X)j$CMcS|YB)-3`fMyycc!~Od83obY{YSh@YNy{H8lzW zHcB91kHA9@JtQ)7e-Q-#x7TGvPMrY$@0L%Jty=^RI%x9Nt-sh(()rKf!-t2iHdCig zO(!9*YsLRV2OEVDutcC$t5)VJNAX1k|F_g%gw;R*|98tL$<{dnAAGP}d{>GQu)MrH zbipxC2a6GnQ}PqQ|3e2Gg%Geu;GK8g$v-P&yo>+a>n|dwP5}RR&L>IOEdpnrIX%X{ zW8;)YjT!|PoW6bg+NdkzG4cQ4NTUP-Rta?L)=hlb!T+sx8Nt&ffd9KClVs~00rNP3 zMT;uLH>MZ?k3atS;A(U3x#z|R#VPp-;QzsqMhOJ05~x?Np7^(b@qepbM(}hA;Q!9~ zBndl5V91cg`E@awcYF5i8C-1+KKS5dYR^?wCX~1n~dhNTUP-wh0UvFhF+w!T)V{8Szsj zfd4xulO*gM0rM;{* zPnQ7x@0?7Mu!{s1EZCU3rj8poE_Ah-J$rWQxp|fV{vSHhD1?Aj0(G8mHtTfd7Y%GzuYLo4}GKOY%(2 zV&B65ZTA=PQzU@@yC{?7>>Po9{eCER>#=csty;B$tId!hLu}NQ<(T+?aF|g7fdmN1 zUd@RAC(vb7krn~`-#L>cVHXKpd+i(zK5Xvx6I^N@dE^ldx%rd>{vRA>lt3T>0$+dq zwctvE|0mF8RFM_|{NF{HBxmOcTye#>g1b`eP|ce+4=yziJn%s5fSedg0RImTGfE(k z0D&=M#)MAEc!l_X0$oNGX%WExoij-ic9Fn&=c#xEKCDD3j#u90B~_KgrM2BK+U)ebcnARjXFr4{{^=ob>Z#Mp%0@dU+X@rL|MS?ge{bRce(aBVT5<8k7t4;@ zE3dpVPj$Q%kpTW55fq0(K$ZW?E~i8jA#m1N(-rEpb1C8fBBRljAo#!cd#kwr_wL=> zduRzX5dTjAGFA9N0RMNzDALOF;)`cHC))}W#{bhAz^T-W|NF5&=4l1~p9fBgZ{hzb z!p~K8n>KAmi2mGo_QMeL282`_|kMl16kN-0ZiY0*mC$@#9RT2K57Ww@3;{W;ir1CEQp9=PZ zmE!-!?(A7R9{=cs{{_mfA z$hHXo$NyzVR2HJ)|2p?O!L0CqL70M}@P9#=QlapFDUz~r@c%5>NGWZ^|MCA)#!oiS z#s6jZDTUGSe}$c5Qo{cg_6z>6uv1J*_nH^9f1UfCU{?6QAWXqf_`e`bsZjX86iL}Q_moIKmPB>NKUd9_`lBmPB1I{Ul68XDEwa#rc@~WUy7t`9Q;2? zD{!$c{2%{!aonbxGyX5TPbrLs|10bilM?=~uwU?hg`Hwj!vE9t1pT$+|M=cs{{;%I9(#;V6$N$qEfiBjL|LffE1hc~b1z`$?!v6(fN`=DzrAW%g z!T(*fizIW#|MCAM$D1E@;s3Jxl)`BEzrs#2DdGPL`vw13*eNC@{NE4Xu-6LwAOE*E zx_pg{|LffE1hc~b1z`$?!v6(fN`=DzrAW%gQRV-`hc6#I_$U6ALSWRWRkBWn|KtC% zBPt8g@PFBTN?|noUty=1l<=cudD*r$I^ywn5mE|D4d(V~iEBs$%ICbqh zQ?_5?MQhu(ZEztnvKlWMXXPS*{|ASeXC?T*&JM-@74{4Mudq{0O8CEs-RTK}|Lf_C z?K8pubE&eI<@mqO{Z23|{9h2JU?}`w5T;Zp{9lTsY#jW5dr9-)HU6)&6Y+m9JwI2Y z@PFBTN?|noUty=1l<=cs{{;yLPV`qi`$3|!MMEpOODtlRu|LffE1hc~b z1z`$?!v6(fN`=DzrAW%g!T+sVU-H56|KyoRsS*G8((`jQ3jdegrxZrR{}pzMNeTZ~ z*f03M!cH+M;r~(O&7ouPf1RC$|L0O=FU#?No%@|&R`|alOuMhW{(<6q6GEudrY6e}$c5Qo{dr>SFAy@c-E8te%Mf=Tc=a%kh7m`<-A` z_`e`b!BF_WAWW%H_`eiM**N&WRqIPWIR2kJ^C&gq|6Y22u14Yivip?6X!yUvPBAIr z{|fsB|5w;4CMEnoio7{=4F0dPv+)01s_bPs{;zYt6U++#7lbJo3jY^`DHRI;mm(<} z2mjZpi?Or9|6`-GdLsVsrRV2r6#g%}PbrLs|10bilM?=~uwU?hg`Hwj!vC#WU-H56 z|KyoRsS*FrrOIBG}5IruXDc> z%nJV(gee#b{}+TQ6$<~CA}JdO|F>#=$p^>(lV={KM*QDP&(GB;{9ksTQWy>YSJ){g zCH!Auzu^B0JH@1g|3{HGhmOJjb#@m1pG%d!EXV(K?stM&;s1g#1w-Ngf-t2*;r~)3 zW#i!gI(0F2R``ExbXHHq|Go75T#ds2W%ntC(Zv1#oO8}m*i7eA!v7U^S<|LX*R5NZ zYqg9~e%o!gDQu>5DdGQCtuOiDzy0lu7_%wYDORE9cb1wiEn2jwQl(1jE%G1%{NF#-Y}V|{ zHf<(~{A=9!OaJ(!(jxrdOV7{MDE!}p&5G^#5y1a*sj`>l_`k=SCQZ{!0RPwAJLIN{0REpf5v9GU5WxSv^!!|n!v9rmb0m`; z0{DM+`kI!ONC5v&#RjP~YXtEBT&nD4IsRYb#>D9mh(`ebkH?L(GDrab&zgwR-c$(S z|6Y22u14Yi8Qe^1vIYY9zlL5Q2PX*N|Ebs@m1d0q{+~;gy)4K7o!I_J(mw+Dzkhv7 zi!2eq|Fb5dv^Nz3_`jE)pQ};$zoiX}un@>k0RPX=hj$@RNdW&(#RjP~YXtEBT&nD4 zIsRX{#qa_GNfN;SlcY^uP7}cYvnHanHx&Z-zn7k$t5NvB)7v7+yGsE7cen4zJYfR( ze=0UerCB3@|L0O=FU#@&gf}v(gTOWd_&*~70uc$||5+1J+M5ai{NGE@&($dWKjH?& zVGxidfd9+#B$~$r@c&e7kV>;g0RPXW%3hY^{~m9eG)*%B{9kkLkeez3_G^#5y1a*sj`>l_vP1y?&zgwR-c$(S|6Y22u14YimNqQHLLff@{69Y*-i1IV z0sKD|8>G^#5y1a*sj`>l_;g0RPXW z%3hY^|0QlroDP9_1n~cO+&C+P1n~c?i74$&g#iBVrRV2r6#k#V&6Fl;g0RPXW%3hY^|CL(|FCdU40sKEn+SKJV0sKE} zB1(HxA%OpT>G`=Dh5tLfEt0&u1n_@%`;N>LCV>B^VuMtgH3Im5E>-rj9RE*vBcnP9 zY$JgGGZG*WkpTXmH4&w~sSv>bz4ZKCjl%yUZa^Fc0a*h0zbsFpc}xKRPsIkQG;0L# z|6Ho?XH7(DZz=@ve=j{hSEKNMRofiNWQPF$pPjy@ zr6m%;|5LF+D$N=J{6Ci}ds&YEm$)%;It1bo!2jcM;^0{Fj|o}a5x z_Fy_~MH%a?Q#Z<$wFz-`qt( z<`BqFpnm=OF(!qjQ%*m9dMa*4r4X=9K#BkFx#yn21>w#+@3b_dGaDBF4-PX*Admoo zi4!LZE<1PKbyos8Qw0Q4A<(>ebHT|{VyG@%X4=*;;zJ-60+(DeyTsWca(asvErRRJ zz4zWL5}Y8F1n~dhFrx$l2@se$b7tig9R+XQ;{{7!0`loF>&lP1A+#_VyW#N^x{0sKEW%qW3C0tCv-%Y*YVcFB+-LlVfD zDj<*wf&KU2KlaoRKhZdnZ4Dzn1X3Yz!wuhy&zBehciwsD;5zg8*tcGK>7@j6rV0q8Lg0uajwp6U*gM`hQYvmnr4X=9pkKcq>`k2L zk!#eb5nN}Ue){R?5jZjm0sKEW%qW3C0t7a0+!$qsMm*--ci&AQXR3feDg;hE@x+J| zA%Vk;BiYt4;zJ-60s{vwNMHu#ucD%&qDqx2!FA@9S6<0~BJUPU0RImTGfE&}n?U2n zjfCbpe3@ga~Ffy*zyJpcGl^4)*^>!(!Qj7lM3o4_;AEKYJNh1T`sk3WX4 zGar5QQRwj$#wLLOhmJG~Az+oj;fEg{d(MWQxN_x6s|gbv0x1%>{r1~KhkYuAAAGRf zwuTWO0;v%A;)_+O%&F}w{rc;#Lzfy;zWrgmE}a1WA3D+~gn(57=bwLm>67;t5gImZ zXfhyxq&Ty-RsHeDABV0p3l}a-JvYx1 z!2d%>8if$BN}zAwzTzv%p@$x7HD`iDAT0v256dfY>Qz*1Eh`&s+r)?ufrJUvu03Y! z))FU&!0Dm)vM}EWkp0G-K%(x4!2i?Q(Wp1e1Z>>O?1Bp}$TCve?lFO%e)>t(X4|HX z`MS+0{z1Se0{iVZNy97~IB;NaDQnQ6fri{X%K`rnjx zhd}ZKTDNX3K3Pf(c)$JCZ$I+Uz2RmV4P?AM{}}FVE5g}mp(5_k6^y!yW@^K2A7ma z9(knn;6(TZ0sKEW(kOv|RRWVIPyWR;l=@G;0${b{2%bEFJ@?#mtA?AB1g0oo0m!l~ z``7j!Jycx$KWWmW&_(9M4?he{K?+JGfd7XMHVPqNiNLRZ^{Y}RZSM0IEm~BqS~W|V z6BYu=6X@5kU+(dq<~#4cyUafcq(zw}FmmL|G^f-5*8S8|PlYZrW^4Q3L->C&0{DOE zV51NMmIw?OFhFefh6OtH)Ke{GPFM&ePhiH38DYab{jyD)wl;10WoC7ehWkXIWy=W` z?nn`5lJ@T1JGjU+Z{9pkEY2xJ0RImTHcB91iGX>C$=tbf3r)(B-hcMlXDua9SO_Fc z;IP9ED`_%VKDk$~xjsoCZOJTwzJ2Feo-0wqckbLdxX7Gx$|+IL=eS}B;QztFMhOHg z5jgqelZ%<1agJTPc5Ra;O)TY4SO_FZ;L%4PjWY+5I>+1vs7e*D?f{ut0nT#6&BLr` z&t9L@q{&y0xh;0+E$nW(>8526EjYp<~v8A zWy_Z4W*|k3`!o*i(q(3r)seP)K;W{=W~VWALhAhLtFJ{6BQOQ3wHX0wYI`Ol&>5>#n=R^Cut#5+l&6RV)1;{%@Pk zUAyup09WnTgHP{EY{KQ2Yx(fs%qH`Y-{1<#rv{UrMRVTsytfPGd^cFiwX+@&tC?dF=f8S}w6OkNGh7 z`3WvBmtJ~lDbtg4^AN!QgX4`72*?s>)TmMNw=W(&dURP?nQTLd27v?!TzKJydG;)Y zZ$0q9!sO+R8X-_3fhV3&bW{C_6DNkQElykqsBlq=k`n$OI^-yXfFOaFUw%2tWE4N< z#v5-GYz3hpkN|O1eelh%AGrQSJn zHq@^_E=hl*E(pXX(75rJ<>gwg{Mk0|jyU3o;PTR@O`B3?CFkZRfd2=F93>EtB5>%T zhngGm=bwXC-z_gMZ_%QKRC9;}0jmTC4H{&1I#><PXPJQc>>~Ng{)2P>n{U3^WtUyD+!3_h z2?AGLbyaZmx>fSxi%Ym2XzUNAIWYkOW_-T(+EQ)PYRHfwp=;Fp@4v4tRiE;~|3gO| zg%Bu_fVl&|o(mU8j2Ka~X3Y{?#OV;QN5I_r&piFpgT3{hdln>MaZ~|;7zE7Ys{i@V zpA^orlTSW5xVF@)Rm)D3O4!3VXuXB~YbGmD_H+t>8Z8+yNha zu)J>FvG!b!$Pg%sK%F{c-+OPl&Uy5YfBYkKReABn7j-7=O;-4S=)j{80`Ul#k1STN zUafY0`0>XdLvOPXud$p3fl>(6sZ;0KXP;F&SxQXo%P&{AYBixKN#kG$*dWlR&BRHQ z)|5D&?VWz)kw*rXk)3whY0VnFIoqR=WMuq5IPfTeKnw!4YuCR2{`<{C43nI{G3qi` zS(`j-)Tj}ojhq4jDFPikcAPnLW{d&s&?zfdZtl_JI~ztvObA3IaLFaJSFhfzcGgUv zKHdBgFu1BX_Ygp}g!3eZ{|5&iB@if zxTCq|XY*#gPEt8dL|&NdE}AzVAJNu03<6RFnl}CNt+$qmjA460%pE>Mmy}_{hS{r> z$lD0u|Dj`#LI^}8VD7H}Y{3FItG{&&>@{}1f)?It;oSk>(?m+x4 zK$1X%2IH>3e(ty5u6JnG9CXk@!DYw%mC)R(&Y@Pgn=1Yv9DbBQAe2DWs#QC8?)>u0 zFS~fPPn0#vd^OlSDa_bf=-rq@nNSFUTm*LAb=RA2y6J}>euy&mvUbe+^;=$kc}b^E z-+264G)+r}z`+Mk9z6J`wQC)Fe4cr>Z|Krto&}b*)gZ0*;{Tx|kU|Jl5-?BBJ?*s9 z1`i&*aN)wV=4rz9E?BVOkw+det>&r0mBWA+5ee+Q_uhT_^!fbr&;59wukEQfb?Ul1 z?<_y!h;OP^mHad~8S|u;v#x7Q{b9Ve zM*#m19f1^PoPhbLf8Txg?cTlnYp=bQ<+~7@wwu+sXV0D;I&>&2E6ez(p!vB7G;P}S zth3I#_uhNmx(k-QjWtH(MT;t)d~(r67tPvp&xvVSCG{#N@E`v%(cI7TsizkI{ImA& z_U4}g&!FH{M+CDX0Cx*xpHOd zb5mOa=DB8{eDaA|G0Zd0+O=y}y?XVm4HDX0NuXJ?W=9`=bgy2$o_p@OIdkS{8>_D8 zV}2JHHf-4~x6C{7#HoAiF@Zk~DA#gJ>6s7t&7-?cIc3^k{_?{+?<_NSk#c=tSLSa% zE~s2gwtw-@fBv)b7+%;Wfd6kl_IT|#0dqCW?z``9?t6dINhe)&(M9IY!}r~H-@EU= zYd*BBsHm_#*F1}Fz69~zci(;R!3PgN{BZByy)U`sl2cDT_0U5PZPlukd32-S11Srw zGk@+epX#@7-~O0mj_J~+%hgw3-M@eTmtJ~l!h{K{R;}`EFe`7)h7DV$ONz*R8a8|EgbML)( zq5194d{2DiM(t0Nj5l${jvX8N%rCRxtY7cyo+X&QG1mV zIY>aW{|B?B1OgC%00bZa0SG|AMFMZX{dVZzPt7xER;*azVvF3(8UJ^;@5mei5P$## zAOHafI6=T%HM-w^`-S?yxxCPcCis*j{_j(7(iR9n00Izz00bbQkiY{EJP<1XO`0^} zTSdwD7yh3-b83VD1Rwwb2tWV=*&$%QTeQnAyM+2bxBW?F7~ub@V5d?DKmY;|fB*y_ zkP!msop)ZS{I_Y-X5+?<8G)dxnfSk|?jRWmKmY;|fB*y_;1dCJr|3}mH-+XQn?AMF zp|;@v4s{l(LI45~fB*y_00D;xm>c_;=fZ^g|H&tx>`+5IOBMh3tV3xI1Rwwb2tWV= z5KuzEJRLGr{%h5$_3gLcDoHYfY2g1E>~xw80SG_<0uX=z1Z)#{{q@&F<=+(EamO9D zYbSml0{DL(IJ^Y`2tWV=5P$##AP|E~^Zr;54f&~kpq>2Fkuc|vp1_BU(00g86 zY~8xGd-v|!d&sue2OMy~vSrJpY9S5;GEQLm^5y2^{oq8~R${*MJ8IOZj3ZLmeEeTw zr;ri^AOHaflt{pQ@*gVy+nnUrzy3A90+dLY(;;98EMC0W{6d{e{>`Jlh7B7AD1`*@ ze}$bwN)Uhm1Rzif0du8smo8m$Imxzib0JW9d3h)~vnv-uo9GlIK6`)T#5v8*da_3&%qs z;{;xQ`DJrqf1a6jO=*s`;SO zd|5bREgS}cEECwWWlO(){mivJp|fdQ;a~ppmnkw`|rm{fKwol1p*&__+kDF{>}UvG-yy3Pcs@ z+yAGz7N~FEzC5V3^nM|NPXyMjTX(|^H{}2Ixn|9p&pr2?Pp$U5E%?9R{Y&fIB2ZCL zF<`)e>eZ`nFC>+(&6lmryW2Fzt#*+u1Z)s6mm-{f_SuyaB=m*(^V!{Z-@Se9<`e$< z_3MWoTv=#tX*g!gnC;KvH3TwC;EON5DEOWFrcImjkT17J2>$O@f03=n1kB?3t6%-9 z(m-;(IO2#SrcImXF$bCkfr13gcXgkB`ssoiZ@vrs-h1x_qs^Q-v!KtIKLqsX(PQ=M z)xpy#fj~wGtX;eIrkiduzeMNSkG7SY@2Qz<@G`>0n`Yww-gG4mahJf7B}=Zl>MG+K z`PbOWcg>$!&FvS~tXbo3lgS(cu?b9=FribYPL)GiWLR_c3>j6 zZr%E|*IvsNgmMV@O2GVz{o;!+wrtrl*Zit1@7lHNs#U9e<=}C<@PCgxm!|0_uxZn# z!Gi}kZrr$XEsg$Sx7~I#=W;oa{$3_SDFWs7|#Vr(yn@u-9ID{bE`L z|I>VH*!+=h!GZW$M2RNMIMJ=%JLN!WShi$%+aoq(Zw&K^R127qKJ^EHn!#g`%)9h+ca5e__L6 zI3siR%-J(@=FI0j%{gb!p1nV7?LBL+^;^I7zg@d_&6_u` z3Zz-SZr!?)E&t<=JMNiho*7O!+=m~2SSH#z7h^JUB^Tnsg9p2aBN(+Boh)#w&W*Fm{7Y8(JQaKa`MS1mwfL9 z7hI6fN-EL*;dQ2?OyJjFf6bbAC!KUs$vS`e<(Ie6?O%^}jp3z@=MIga%>1ArXBE~?4Plk&7?z>OkiC!t(A&*o0e@G%55EzcYPe1*%V#SK` zGkkL^PFGBQ*1_L?`|Z|SZ!Os}$sK$A`0-hUUAXda%FcT`ERX@Rk3atSmRoK~(|VxQ z(fGw_r=6B1z4FEi4>f~m$!FO3b8G*b*}=(_V#%-yi@>hhDv+u1n>KAq)M#xrZv1-s z^y$+3x3y3@zjN*X&YSzheh^4wV9S;**IaW=nTb>bH{SX4=YRLzcMW{94$I7$GY7UG z@n#E8|K2JuM>{eF{`%{$ zH{X0S&Hf=5B(1%>7OxZP|Kf`;HXE*=Ak5XPSCs-m-^wT z(l*Xq-8pmSG~4V?HO{*}|NQe#DkX$3mv+e*>46I^FU~vfyfnIY?%b*Gw3FrKvkHNX zVI^W`<;s<3pM7?1wJh^G&%F7Xu%z$ku1nhgqq}^aUHb&mk)El4xq>de^wJjE2Du3* zME~ihpZ@pXe_KAtJOyMA34tziOCPjN?(nl`&rVMDufF=qpg_w-;Eg2(vUJzhty}ND z`|e4TCJm~JC4ZeYYgQK%k7Ryv!h2eHV9D8K^evNjGAmz$s$^}kS<3>csF@LyNi^lB z&(~gn2^r0-_6`iLwS0o!Se1bGe^rw9(+>iF{`qHGAdf%(c!KJaZ?N{Z+0^T-oRXC0 zRZSSoWZ|ctdg{zG&ur=#YBwG;W=wL2KJmm8nSGhDjD9Fe=h$w61gfPK@!D&zEnmJo znXk%EU9YW~d7TDH=Fxoe$tUeT;4=H)yLazhcioj`PX`@j`CpS_bjgw>x%{)lYO|w$ zWscA`{U(s0)1-;suwlcZMT@d1eEF(4Xv=(~zhUOLIsB{Fg1#6dUA7Jkk8M4647k=Y?Xz1a;CV~_=f^I+1#Q4M-a&h)06ZpzIe z=NR;f#oF+_>?92OhZWvdd05;eKmcs|hnmvmvqZ2_!4IN>_dDI_h6?_2o(&;SG{R?qzf%^ATDe+NeMGOd!ho zfC(0Q_~D0Nc;SUCN%rlx-yS-2XgE=VjSJL&N2WkZJPXY2-n~0ZM&|J_NqZ8~bJ0Z? zop|Dj_0^`ezhq=Bi%V|ayjga*ky!`YeN;zn|0kR{U6g(Z^t3?o8)uL`KbdKm&bs_O z`sky%i{5k3J-Lz2ojW(P^Adqya?!(JOA~ahnlP!mbnQPHcsjh(t(*w zMCY7yPFjE&GRWFV5q{*6M+)#nB^5=GhGI|GrBQ$IZ@lqFQBsK_D9SDwY*Li>-+zCW z2fF&|tJ5f*GG$7$S*_|TOyj>~$RLldJ9g}7_ZMHa`~UIBA5-UtKOE2)Cqa726)U&qTq^erSuNvp)Vzy)f*Eb zHk{zn#8k}>-U6Te)%OwlDMO` zJ_t0R@Gk<51rko5e4fc_n}^n}UoR~mKmzbS`|Ptc%bU&3+Ze}x2~;JJ{;t%|)Y59# z%WL_P@y6~7X#ZDrW!Vpb3?*bZb^ZGFBca(}e`85+Pdel?@R3e?g+-w20tp$q^Ugcp zdFP$1Mp|E8d-pGS5XcI`nFNt({#{3Q00a_EmzDC8HCG-ubKXzoGmxseDfJ+ zoYA+H!HKs|Ak#xKF(l8MnV#Pd57#~Y%{vkfUi-g&7o$Co8iCBbOFVyOCMFLqy zGH++@6*;e*UA{h+EnAkfggNU`Q-V&UlLDDtn3;^35}OC)tVYzC+S^;7^84?P{|b)KxZZQC}&lV$bR<=H1ktNlMZZUxSwDuJZU zOg5XWHd!y_+$VQ~`qRw0fArBuNiC8Af|#6p?zt6apR20K?5A4-SqwK_ z*_T{$NfxwPv0_DXn|%HC*MI;0cYQ_jm&O7a7TL3BPe$UBb}4frGD&Ch9|j_ljF@5?X0 zeE^m&o98!4_p+1p@b25fz%#S`k#xE8aHlSQFfWt zlA@eBbLN#-URh+D%<|C54w`J?$^2g=rPl%}bG@{@O+q*lo|or>%qGq(>BMrU?U!lA zS#Y7q$eIGm)Xt*3a+fPgu&oJ=YwL{HEeK@(OHoY87F!ft@vU_6#TTc8<@VcefAGNv z)1UwH%P(g!n(f=SCz7YvLrhz)|K3P|009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk f1PBlyK!5-N0t5&UAV7cs0RjXF5FkLHHG%&Fd^lV# literal 0 HcmV?d00001 diff --git a/resources/logo.ico b/resources/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..6d477993d0c6ad5100d188e73c8f16a7fad3a98e GIT binary patch literal 21338 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 z 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 literal 0 HcmV?d00001 diff --git a/logo.png b/resources/logo.png similarity index 100% rename from logo.png rename to resources/logo.png diff --git a/logo.svg b/resources/logo.svg similarity index 100% rename from logo.svg rename to resources/logo.svg diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dc204d17..4b4660a6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,9 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -file(GLOB_RECURSE ${PROJECT}_SOURCES "*.cpp" "*.h" "*.rc") +file(GLOB_RECURSE RE3_SOURCES "*.cpp" "*.h" "*.rc") + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${RE3_SOURCES}) function(header_directories RETURN_LIST) file(GLOB_RECURSE ALL_SRCS *.h *.cpp *.c) @@ -15,10 +17,10 @@ function(header_directories RETURN_LIST) set(${RETURN_LIST} ${RELDIRS} PARENT_SCOPE) endfunction() -header_directories(${PROJECT}_INCLUDES) +header_directories(RE3_INCLUDES) add_executable(${EXECUTABLE} WIN32 - ${${PROJECT}_SOURCES} + ${RE3_SOURCES} ) target_link_libraries(${EXECUTABLE} PRIVATE @@ -29,14 +31,14 @@ target_link_libraries(${EXECUTABLE} PRIVATE target_include_directories(${EXECUTABLE} PRIVATE $ - $ + $ ) target_compile_definitions(${EXECUTABLE} PRIVATE $,DEBUG,NDEBUG> LIBRW - ${PROJECT}_NO_AUTOLINK + RE3_NO_AUTOLINK ) if(LIBRW_PLATFORM_D3D9) @@ -46,13 +48,13 @@ if(LIBRW_PLATFORM_D3D9) ) endif() -if(${PROJECT}_AUDIO STREQUAL "OAL") +if(RE3_AUDIO STREQUAL "OAL") find_package(OpenAL REQUIRED) target_include_directories(${EXECUTABLE} PRIVATE ${OPENAL_INCLUDE_DIR}) target_link_libraries(${EXECUTABLE} PRIVATE ${OPENAL_LIBRARY}) target_compile_definitions(${EXECUTABLE} PRIVATE ${OPENAL_DEFINITIONS}) target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_OAL) -elseif(${PROJECT}_AUDIO STREQUAL "MSS") +elseif(RE3_AUDIO STREQUAL "MSS") find_package(MilesSDK REQUIRED) target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_MSS) target_link_libraries(${EXECUTABLE} PRIVATE MilesSDK::MilesSDK) @@ -62,14 +64,14 @@ find_package(mpg123 REQUIRED) target_link_libraries(${EXECUTABLE} PRIVATE MPG123::libmpg123 ) -if(${PROJECT}_WITH_OPUS) +if(RE3_WITH_OPUS) find_package(opusfile REQUIRED) target_link_libraries(${EXECUTABLE} PRIVATE opusfile::opusfile ) target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_OPUS) endif() -if(${PROJECT}_WITH_LIBSNDFILE) +if(RE3_WITH_LIBSNDFILE) find_package(SndFile REQUIRED) target_link_libraries(${EXECUTABLE} PRIVATE SndFile::SndFile @@ -109,13 +111,18 @@ set_target_properties(${EXECUTABLE} CXX_STANDARD_REQUIRED ON ) -if(${PROJECT}_INSTALL) +if(RE3_INSTALL) install( TARGETS ${EXECUTABLE} EXPORT ${EXECUTABLE}-targets RUNTIME DESTINATION "." + COMPONENT comp1_core ) if(MSVC) - install(FILES $ DESTINATION "." OPTIONAL) + install( + FILES $ + DESTINATION "." OPTIONAL + COMPONENT comp4_debug + ) endif() endif() diff --git a/src/skel/win/gta3.ico b/src/skel/win/gta3.ico deleted file mode 100644 index 2017c8116bb76bf612babe81eb013c2c0250dbc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2238 zcmb7_F=!)46o!A3fEKP$AP^PT7@U*V-j%ezD-^gQ0o%xMg^LZixI)DhA}HX(g$jqr zphATT1zc>Q!Uh!zIfM$C7~+6TRLCHRw73w73@Qv!p~8l>w1tbwzpJ&debR-@=-Zi{ ze`nsC_hwhX0_)-;-14&oe8HYf7Rfhc0UTu6|I}?JmxTv?>{I0Z{{E!lm0g^jow0QU z;r<|m(<7|T5HAhhw@Hr-h(+9I0s)9<6hhY_Qb~wR1|pw_Fbs%N38GqsI6Z}EHX+)i z*Mk@gAS?^w&W57sh{Y0ETT5YUD~;{#4BoxVV`s-eCR0W}UxQ)PQ7*SptMyQ?572C0 zqusWlD+&^^7*cC%NN;T+`}Qpg$s~%qyC~C-8vUr#j~4yt(vK_pF>Ew&cX5HJ9>wd# zYs6D==xH5^Yyzo53h81R*>Vp+5#?4HwQdddt2$cOEp+d?$k8{0zLg6ns1+M% zyuU#Eq>o->h`h0bgVG)jt2w+sJ-|ux5UpMf-9a5!RtwK|itz81@OiF^0OvpF`~dg% zoO@g1-jtSwSeJ3P$dL5O1!tf?hxqr0mg`d1{gEcs$c?uq5&8MFfl+n4I~mkJPy2i#WP|P z=2bAif_WCqx!@TP8KUF*8o0Uwx?P~v0`tgaYXDON;{eEKfxSH-nF5$b#NPl*OLR$C z^p*P)+@EOE-&2%C2|1BNQX~-*F+3CAfu!r84Tge72yK8i7!Kf&&<=7WL+CzsNRlLo zPPi4s2<_U8|B&(T_ZcU~ztIq=RvD|J;F%QMzCbc5pzDHXS0EY{2n2+;$TnMN;a%bw z%M*i?2xAGxl-YXvM*V2HYRe{geigUicH zoS&cL`1lw{M@Rqp{z7_O0L2~P7sPAn&chOPzAQj!*$ImGVR3q1Ss(k{o}wvEJNfW< zn$s*tO!rPUU5S^9|6#cen_8Fy;g0I(gCFRAD`ILRzhCV{X6yd3)`?nv)r^j2&i8Ak z`O+RmO>KOyzi=IRVNLuPYpb1zt(lrV(II*Md99+EFIR({AM$kX0qMTg%#MoSs#ck! zM@*HwR-*HC*^QjNGL7EE@O1qRH}dh7ijzCqxVL-XR^(tPWI$b~fmu2XwbL;plCzAX zSDviDKH5}GQ`)pA^F7(`m)rnZD{-I1vmr~<_<8a1!e@_d`}VdtvYmtQVg7u+TS*#+ zM?Pt`J{pgu6)mDkl9yjRTHbF-KFLtbNYHn$)6Byng{2BqIp~hL`+umS5?0~{WMiI= z=yC;NRo!?rYkIR0T$b#Rrm3On(4)@A#tKv;sK|44N%mnJGz>$2G;Mk#Id+2~S^k)= vQs)_(SzNzWWqGE^!6*CLpZDCH@iX8P@_)+0weT9EE;_T%^a z_RN_%^UXfzzwHdmdDmLZ^S)2q_jO;_bpt^#2o3trKRU>g34*M^1p@K!9Xg1E7=i}C zdlC}=x&Dd;K^KA$#LfMmYZ(j(YLNopr2EfxEh+@vDMAnd!GEsHkRYg96@sEvU&`TP zQGnlua24cb)F3eGzaNZ$LmRl3f-#xc6=WneyfSy^y^~0$<^>P-KeuSkXHP7x^aG8u6pWqU~)YqxTu7LNFuv= zgM)~hjGs*~$;BE%t+ZxO{gDvs`kz0Itn zusdM{=*|x$+A6jqaJqk5!h5W`xf>>DW@weKgEZ?r^)EkRXH$xL9XuSfsS`<%H?Y^8 zo~qb3f-R{*{?G}U3AB_hT*vA1MUEJO23flsikrZDDpXnOe(?`|_VDoNeVRt|c`QNe zqghN|d3m{$C=IQp?zA<>KuiCN0<26G#<-=z1Q|B}*U+oNqe0eg?lX9Jb%@*DVU=lj zL*T`7M0MRc|I;<4?U&F`Q+Uro0;8PNMBSSOEx5r<*ou&io!!@FxAoDGDow$ldUKqO`D(N!6CAD0D@rihV#O$AKb4{=G8y&M6Jhru(DzKr7V%z;tsXXJJmBahPg^KQ{*UuRe4jUW-XI(OF zp?;Xj&ni>6pkpSLc)%BZL&I3f5VY!c|7!-Ks47*KZ|;U8-{~9sJPdy}aF|!ahcks4 z#cxm4n;mAK{2SlM zbb^E+WRx|zxi~bNYjQDL{gD9Yw;}lW>5~i-k!i6lnn0`tLxP05dZMVy9~3-VG4{sS zzrIda8do~aH4)0iQN7~&EP%5@+Vk@`#j?y~vqX-78%h%oY)3QE*VIgoku9i5Qp2KW zN-$d=&Kl+$5jBseeySy~C$BuA72dr}33c)hEoUA-Jm9?>&6ECn-f{209)d6C{JzgZ z@?rjVi`n+{=V4+MQBz~LA%OrN=i$Yr%=i}r+MBUu+&C+>na4Ae+m$U_Y`W^|=7Bed zy>Tl-sL8Uiv9Wt^NgrpT(|C1m&Q#_o7RYvUXG?A!;me~2Nw91|bfGq9&W73|af}YL zDRCbkav&tYat{`fR$#R>G|Wm9bjq%^op>ak_j;By!(E-+ka{1S z$!`vg^Hz)=3RS6Y?bL`&FcEE@`$l8GeX+6#@^$IF*0Vh42Dm!rB-t=-P!Um7=rAnE zaI~geTPeLoU^CrsZ?5_7;rJ)r+MC*d0X=CR1$-2_c$#El=9hKvu#zh2r;PHiaNYH} z_fkoO3}@(zLbqE+`Eey*mkmD}77mr4hxuJ?mIw^g$e5cm2AqC0-c(b>pht3h%tZz_ zU&`!up~9gOA{a>J5_D|8w7R}MU+n1Sj=8UKxSo|yHY{lyu~RuLc1vg77vY9C}u9%)N4dUPF}CMyL=K`}*V%^}yNoajAq)E}Q+Z4~U?TpnNlY`Eov z&j0vT_Hp*>@rM<6-E#xW0GN2Ypx=mlAUCtKn8hJvIwA_VLnh7klv$7@3qWL!q`LjpsOb1Kp~bd$DwJBcUF3rGoNAG>b`?1Flsi{%cFd$AzJGU z+IZ%}uh?;bn&f{Ny_B2`WzcHt=lpE}Ih#A+W`%a(daqTbk$syGGTxjPd$W9M$KII& zGF^i2`5x)RhYvH@HaN<5`z(mMBk+5dyCYg_JAw*rDcUc-bN$JSp!rJeIR~RlK76W% z`?BM1zRDCiJK)Op=5{~O$zfD}0V7N*O~iwZ#%F{6-z0Tj`i(x^^2z3rS@gy4%?_Zz zb!x|aoI)W(7D>aq`_2|0jc&3-ob$FZu)9O_gg`3Qcp;B2_vo#qE{H}H*JrygQc_dn zZES3WA~_pSE$W+--SB$gAajjiU0$d*9to}AaeSWosBQkc&bA3W*x2akXrA6npMs6} zfD=ZgOd-O`_A9phvC{j?Emyjk-np@pQ4ebHaFrc*BE>JW-s;v`(WfiDXgM*F) zt2&*}x&`{$XW<%t?w;E3s)dZ`Ke)Ujkh+i#nL5zcgi<&S-%>h#A;iVSz2h;CDStSa zDNHPW*{5>z1m#rvX&my5$L;oJuGsiQ_i2Sa*n$mGlVSEb6KwBo4K?HTjwPr44%NRtBhAOIiEF5W!|H@%n* zasE*GRHKA>#=|t@Q3{uFoE(k!Hz&r;gLMp8@wSjipjaH2KH3Dox@50Mhzs~`VW>M5 zccr22{TFYc>$BNiG}u(&pRJFT)3UJ?sH}fI{y~{{SWp({2npnqW~sZBxst4H)YYH- zIB)w371wi1Dez?DmFU?|^SibMKdy_<)ee*`6)Vl_+1<=Je&cpNb^VWTqMrFWyDv+@ zTkvdWFO3iMiI4`L7E8OWOZ|RR+e-QQa6EqAxf_=g5oxKLbDX{9&WC}Dz%h+kzFg07 zIu-VKa5KLj5K&xQTwzSR9&{3C%=P6Uxa0HuxwozRpN@OVILja&Ew8XeMnf`#8N&G% z(siwh6E3IUd;vEXlS+^KR^d*>s0cdMnHKx$Pm<>51>z4RUd_0G2+g(ewXzh^X!aco zV6kEE*4{yP>UTuAPHm{M)sq4N^yWD4c{06kPbU{03h~X$T*dq@oYA1T6`XT$m$LcL zI~ZGcj*tt|_4z)!^WCQspMHTSPt4Bu<^ve@=+%1zHb&{Gp1%3?t0DRe%mn9+AvMwU zd5CvxEUL01H!I=n7jo$L7aUbsZs7eLMnv~JwY%uXSR@J6(w>Y&W)&nG8VFiC*_^n` z3YJP;AS<-=F<8Zgp!G@CNE`mq^K;*~NkljZwWEv z-}_#Ee}M&&zp378V-L4;cq}rDpSw{SfK0uLs->l_K2h|R2o;ig3;&;9bh=)uf82Z8 zSPXaSc;(|RL20W|+Q@|c1X3+_j2kcTcPM(5cP7jJ6H?XELaY2>!KbD;IW-Z*Zx{F?>MC&Og-B;Wt~%oHHTDu-4U*+)j51IP(^ z(mX!5KHS|0=!^=zuoWuiz+3yB+l6xzXPQj%2w(XwRH#V1(ujqF;}kof#mpeDYXwzx`}$8XlC8m~yRUlZYtRTT zU<&DYFJp>m)QYWEmT!V7BvgyZM?!;fAr5OxXG zB#>TUhY_?N^&9NVN)|v8!5kkNZGD}?*8L=52o!z72mP<}AyuZyoo>5&qbXQ9+$rk) zG1e0ZgFfT)y!Os5q%`p_A7-FzEl#(fqbqRwE|l%3pw8*nnIKCjC`5JmIJHbSkcJe< z#pi>)>P*#}Q)4jWK_%+NJqk72#Jz2PDk!4Ul(P6Jm*29f2hd(5(V)@j;`DUL>`xKW<1wkbJq8(wayFsL>QL(63)it@`9YpA_`RTYc)m0y0d(i7Vaq7e_sO9pR#Swh3- zVocxGhgxm$;0Q6n`OtoHKIgvF4jeB&{7dQgTv9_Db#Hd{s_kbuvv?Y|Cd<@&6GYNI zTc$qPLGy)<-zTT1&AP*Jz0T)7$2NxOK2&n-GldkQAk&%geDmCua^Kba_GSkfJ@W{B zksy!a!=`hxHN2+^ZNMtF|8xc+U#!sHNBZu!kpbBL?do(p+HBf%T;QcNGQ~n5Ejn!= z6>U6uNca8Se%Wk*RGoLL`sv!u+`3pvEX^|twSI-$>vP)R$zM5pNUK=pI4u<+ZgHRW z8~^lBI(>_yewq>cg;B;dMskKfEi(i&C`A006>m5l*=vRW_n%@%!kRMmk``#thF@gN z_brk{MJc%_b#IU<9AKT!OkuZDTtwZ=ehqwSr*C*8Fn z&YDlvhV!@}&YH|V;aBbkzk#3LGEf(~4!=^VSq(mN;%Ly2S8{Dior&&vD9DZPJ=t(?B(*HczjEib28!F++eh2+C>drwcay0PakruUGIP+c7-LY);8_3*gRsM# zt$y)R>DOTx%wRjFR|R83Y$37ad}zj3d`XuO+5>T5TU}e|L9U zSMMIp=#aO?#hJU(#iXyHQU0NQfucLj^eXuN&`8!(GDf+$!BfWzkt@o&89}jvLM8t| zSo|7DQxX|Z-PU4^ugBbUN?qq4NM;m(II1CNIb|tqXu=g$IJh8_8RKEc{W32Xl5o{y zv`G}SMeC!&f}ShZhcOXhzW!N7|JYMHlmO0Wh0{MUFzXn7DAK9Z8%N*n>uDDhr?i(t zFzRzI+xN2?_1E~~gMYtB+6m(9pKwaYAHUBl44R1m76>Q!lP`e3*eM_W9YkHtz6hkwi)Bb6;tzTe;%Wk<_CbY5%~hMbt?A9_Wg2qZHL&bp;~#un-dg0d9wt%t+-Q)W z4OiEmc}dpg9Wlk7eUt2(#rqxMYPv9k=ib;8q|HW zjg=jrV*wLB2t!sgbdJsq!86ApLX0(5ipzCZtf%X!Nf}}1Yag6A-+EwaAU^O~V&QZnR)idAYl|fnqYg&%Sinhg zH6LJl_Y4x+_W$Hsyqcv z@Z(l&n=N$)UBusC;5SulkNi}ZhnwG#2LiU`V#iv1b~#KTtwI_rsJ}9RG+DjmYr`&{ zVBBBgwW&97L|(@QYMN=?Q|Eyf;>|v%cGMDc+%foV|85Yw{_UY>$F-r~`qRZAhgraU zxMiO^AZ@D0>!yT}^I0vGfZ%K}vpZLq8v`=3Y2XENe@~3$cte-_Gmkfe=O$&gWgh5Z zh{q@u26Y&giOkov1cosgtuBD$`$yQl=V>TwZ@5NQG@yRefg#}?SI12a9vw}TOo|UI_eB_+ zJq*DU4hRKCq9}=((->5k;f(3*6E-%sn|)l7sYlM=Nwt>%arkv5UOWzDr)5Bkc20lR zcL4O&+J~9O)>5$P%i=(W)N>*~f=f(HiEuT4Me#dehTW_J7b6=hJ zK5Sf!d*Rs8_G_SU1?A()yJEvS=NG-gi^XGjtPlB>rP&{JgE^?+>B$7LL{%$u=qDqj zQK}OM^<6T%YCKYQO>26?8D#R&$@cPF&Fc)z0Ml2G(S6mu_pK+|wdv|}jCOMi zsQ%#zFF+Dg@9*5Zf`Db^zOsXFAq-j6(m$pxzayKdfpOn1(y2+ZeY9Qp-QOjtT;N#Y z`U&ekL%{x9qKa7AW=jAY3D&bMp}25I&JSr*(7TZ^UTu~kkvwRk--XI~*k$zx;T+iC z6$C(#SP264HSxyUFWTGyh`eNY?lQnB$0CRAhFK;18%N`uSs>s%(boO)EOi5A@ks&@ z#i%3jsm1#~=A3WuAHKMetu`yV5(TnRN0-Np;ml@oaCDTN`f{c@D29(JTK8NV9A>SY zhXb8v&(0IrZk~-(D8cBYC(}C`8XnZw*9$LvSOQ4|8m)eUt4VNFWUlR5)+(_z+&^CQ z;yc((ku5DEK&W}YB6Pmu7m-6_dyosgGu3sX!e-G#-WX(J*%Uw#2(B6p4q`jmY_kA^ zKDM{Bn|t^o#Toj_KP1O7;Dc1w{&Lg*ZOi9b7@dFCr2&>*F^1G{lrWJ3&sC3%V$^^7c=qLc8w50)hV1-(NM$rJ*?0SDAFKU80AD+c5*u22G(6V(u7`xas-G)XP< znXlH#*3@Hb^WQ~D6y`XfDkTZUQc_UBbT>@(J=>`!<2110G-x77HGD%Z8&4B!U)eTI z&0`k&<>qMM)vH%sfa=r`bbCCuph7!EFhjtjptN<#dohntJ1|7bH6&V zC2QVQ;U28{gkfk+#)K&`!N04@{@|JtvG_8Lo4&?veR#RS!Ps*4OcTh5Yb|>SFHxfh z@x8wE$t59#FtzJ_s0xxHH@6mA&v6~bPb+R#pE=DsJw26km<@NB-TKy_z;;5;aiM69 zj9{}w>dcaGq%``1`9z#o`B<+fO~N_AYdpkY2Dj_+Pmn^QkuSdaLKtsWj73-dxar%`pvw`ncZ@d-^*C&kNJiHz_ZsC<&H^pA*#emf?r zw)K{O{8)BOME~O$XzrQE&!8%xo?zBdLmELHo5deW58n~2dP46Y0Ext42wci1b-p6n z6Au2D1I9go`@^J{p)2{xz@&d91|=mW`C-siTMbcABVK=$`1l6lEEH3c?Nk0M(>Ii) zzz+{Xf}Vm}*tF*-^9Mnv9-s+^vaOO8XstG{#64A2R|f*G@ zx)BH%ot*m{Ys}pFvRX|7kb1=ez~K3Hb(8og|4OyJb5@m{gZDJ^fdu1;{1D6I$E`#X zN97+Yh*B&u=|gugdR+>W)bDP#e5 zsL=j-G1>>3lzl!VOz1aIch6jhjTh_fXR<2W&NBGSdvF#KNNqKg|0$e0bvA@FzB@bs zA))QJozpbD{uONA8@CLKVtFPaXT#Jz3U_I3D-?!wZ43`yYJ|R?o=9GGwaacKA>%hW zTK~c#?d915t8!a%?oa|CCLcw(szDnxtPiiwc4r+`lt{mHoYhzMfuL0dj_F~EO3nfT zCq9--HjE(75c1)tFzgjJ2B-6EQ~dsNWZwIDZPQNg?^)^L%I4#v(y|Q27cXAv>18ZS z(=iER(L+$#_KV`}D@L{c4|aBTKJ)Gq%C09HW1k8nA)9svd}q@VhH{vDmZ1An?nIAw zNm7^)5+Yma5k5uup|dDW;PPJUe#1XF)`mtVL=jX7&dV$H%`Ns`j53M_#K=7KDY)Pz*6S* z`4uKTYuE#5^46-?CG#n6w@$m=6q`B7y~E(SRPubTU(wMq6^$Z6O9B{3k-r-JKbXQh ze%D&#=ZJdOoQ2Tdbe2_Crm_(G+Ym&ox|)|GIWKsft*W%2+}AiGgGRhOT#iDW`Rzc%!WsC z7W|NG63Qmi0|3!_b$5F$4U}gehJ?|gr0 zB>oOjR%a6Ex9lb{4oM_9UH|uZV_2E)Y4Is6ABx08C4o=|DLj8EQz2y%{Qou?K?v|M z;=fEr-D0K#5JYtH-zKB1z!;LLl-`c>g{^Gkp&N*bAWV=2=KvynsHjCvXiVZ=6D4kx zWS#%Yz?k3r7d*L_*i)e5Wy-0omY@rd;|;0us`cE|k4{eB8;V7So*&#xRRwW$;udH& z;n{ne6)opGw5K67#ilL3<@Q!50q?QgJyfR3$^q&+&vsLRQ2?XIm<|8`$N$K!s$c=M zC`l{(*bm(*T$^wNynV(hoip9UhZFf^R__L2&OgaMjh=sP+mDiW8IIz)-}Ab5>u)iL#!z3(ask zYYEprN<`vbmv}A}Ils~y-x2CoL6FNpK$u=^+hbPEH)eB!fv|$~q1O+jWRlg4qSX|Y zVb0iy4$LsA#K(^x%cZ8Iq+p)g;;P1Juq6ZkjP=VLF@Ja`ja(ck@eSh`n$DkN($f{i zKxz0(kDyw>sBn?0H+%};J--|uz%1KOBn%b4po-bf!jnih6MTC+FIhzpFjU> z@pvqDxti!@Z@(P~yRfGJHlwB(C)$eAsRcnS6%`eCc^^N%AWV2JV@L30Gjo=SVH@0a zo!_M^R+v;o$;+$^y;>{W^kRj*^WlBVyuU*38wAfzC)F+BvX2qX(3;Q|4(x!r_oDf(3qi891PIQ^$ik%eNoqk(YKpwdiR=Rm>wkux z`JTOFOnolXf7||!*Xlh;e$*Nux>5$S>OM#>)^+xtJ8nA46mpT8_g>FJ zF)1oQxqTCI>j4A45+UyP{Cp`wLX~Y#!-$Vd&cn~#_D);~qzE8+*7Vt_)F*tMl+R3*%e_O(#J{A&T4Z_8j`w=4Jw* zqzHf?ATv?1sNFR@nmoDl$6t$KRC6F;gPe~%x174@JPh*cTTJ3dCTBap(!~5*1m_f- zq7b*Lp#+h=D?jV)$v^^+0Nb50S@DuVj)_Q8SsAzD%aNi|mPma(l_Dz|)JD_JX#A%W z<{o%T`hIu!o%L|0W{jUHjwqGv#-Hd{BIaleZfl6J?gim6f-3fCQZlmIj+?(+J8@6< zyt=q!!hX1nm!{$q#L7lR7OM5{I&A=LlzBZDbx~*?cI5IXJ>h;oLagSvxEkjveim0}lp22;$E+I`sk&L|5c66iT4e z?kBvpy$#Ea_c%}o%l5Aj*!f+L4Y=VnV9l|{gnoX0$8%CwpVwT0*B6Hehj8c-NbXsM zBt&4p-*+OvD^W4up}pVmKv!gihtuza0?Zs_!N39G#mc09A^yP^uq3sG5UxU`rqFIv zpsKJNwvc>y;($ch#f`JG*g*?$JAsr}znc6yKNa2z02w+BugS%LBgJT)fq{G2#re{e z>8S_-CtI-MBT3$E;q**GDFoqigyjJDmS=~w&5q8}jvwyFj~|f{+f>4QZ;(R-hCUD% z+XnN57Z*PQo;Ip*(`^T7X=KRj)>YN@^=t#~n9ZcRC_xY=j66Eee&7K{Pp;pA>k1-ai>VT%60Cpgo4QWEbuU+y zAlk3!WO6mw&ckprbJ}QODom*5P=fromv z*b%{EB(ue0ooKyLcxf6rlsIcddaGV$*Co@_VUw4l{%m#wbN5S3Rqz^2iA|>~iQS## z!$hHK9|3lp*SyRnt52BW-@Y!387r{T?>+!V)CZ+Vh3mV`hu5#2yW4;+AgioAgm8l< zs8x;q342U5G+~B@&H4y_*FJjHVq31|8uNR^iVAm6DM&BCQ;UpHkl72)F#^+iiPH=> z2d0D4va+rZPVG_Pd6^RXV5}1K)T-~t0}#lnh)>RsvSc(#Il*zWthGi~406G<1qP6b z;jE|hMK{LQ-`^wJ<|@dfMJRs2LkN)mS*NrrjbbSso4S;-Xg4zQy;ss`Vx)#WlUr?F4rjij;|M2 zmTD}|D1zKtCPA?S>+~S2@d{!?5PW^{Xy}Re#87vglOEFsoq{pfU{YZR)* zoNJvUd-B&V(#HhX+s3PbHOx9ndw2-I&Ho{YRO5j3i9bX=aHR>VDyjf?`gDu6hY#V`5#kb$ zh?*Zmgq^~JIRRhG!ozp&7ZlJ3NTip}%m^E+p35S=f*SvR0Y*-cWkev8zx~O=k2_KI zjqz%LPm&DO9>TO-^GGt8 z=V(<~*+tjWd7M?|u64S)g@TtPL26RE6ka5}uG8it@Z|MRsGHkWk*~6{vNw&#e3%nw znd+H?!$o!b!ke!(RZxtZPD%@2CuZ+3nLc`kS}ubYbk zfRl~Xge|ESfxmJv>ty|`!K&)9>hoe{>AxI}Kd+G#6WlJJvvu|*GPfmh_vlPx8%|_( zDjA{!kAv&FP)*gNL2hUONn!K!R@|P}gj)e0eI!S`LtCrLQdc)DWP+4#Wru?BM~#Fo z8>IUH1wm`<1(P!h__eT(;u(;#yp$)!zoa!y%S#gx)7v&8KI;LK4$EucEgdoVHsWD9 zs%lzA>qU$9Dx01fcakLA^fMG9kiNP`eAYEj<{oXuZvLFNq#{^^Rv_n_V`zy16ilH$HW`+d)%SJrXUB2h%>Y2;0h91v>@D{vQWHQlLQBbSA2T`H zQ4pFgeDN+VFAsJfs21vO{}V<0>WMWT6QP%o!NGHyLGIi_nWj3Dzi*BrsI{v|5$*SP zzDz_>`$Fq7Z#<60R*RraH>V?})S;0niDFwbV5IXp`(-`9qsf&CssfU6^B^k=mZXZ}YB6oyb$ z_a+FRp6FCOtDrOwBdUxEQt4=##+&OpenQw4FgUoZf@ z*H;qb)c_pM%9@6>#Yy^g09rvY&B8*l`{mTqR2+)*vH4a1BBuULJ4&nDP7 z${rnA!l%J`i3LxFD;4SF%0U~rpqLl1 zq($lfH(C1&R_2dRUEjOqxUH(hqiw`@?BzwP_R5s!wN@A&H@{#AfZkOjsZZ5~ni z@es*>+#2w1`+A^P;^X34E{d<=owV|lfTgg7mkfTKq1B< zcCmyE*a)#V7YEBFVFdSUC)c+=-^eteB8^wp*36*HS(mwdXu;c~YGkyhZ?Bx=g_+@w z{f;xlJYZ~cG88adY1m|(I72z&Ij#FRY+PLD9H&Z(Q9A75adCM6pd4sA_zvo?moy^o zHq?mJP=fnZWB=1Bh4pz);d%>CN$cm&zc;A4bwMECNG0YIvS5#$B63&cdqvp!ro^!ij%vd#=G zLN-XFETKYcBuhUwKbxkLbhfoBFtk+H*77a-prg>e&R8>I$be><@V3)?@0@Smd$Ew% zX($RPvL+}Ny;CegxrzRw2&HT%wcrkUAB0lTh}96VC0CB~_BhWNdg_A1#HJoit)y-w zA1Tk2sL%z+?lMaGEVNE$P>{3k-83=ME&Rr)cV7jj)4n!si(6h;SooAC-W;$yZA#}q ztIZ>f3>IjAaN}UkVktJdw=S-CLPlgCUIsoA-q-%b-6OP~33^S=cXxLY^I_iIy?~h~ zAtSq2OYyxv+oCX_tNVZ-wv(97=l=S4g9GIf3*zZZ8bL>@e=0_$t4=$txa)U7+&n09 z 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 literal 0 HcmV?d00001 diff --git a/src/skel/win/win.rc b/src/skel/win/win.rc index 379c473d..d58ac0e1 100644 --- a/src/skel/win/win.rc +++ b/src/skel/win/win.rc @@ -24,9 +24,9 @@ STYLE DS_MODALFRAME | DS_CENTER | DS_CENTERMOUSE | WS_POPUP | WS_CAPTION | CAPTION "Device Selection" FONT 8, "MS Sans Serif" BEGIN - COMBOBOX IDC_DEVICESEL,7,25,172,33,CBS_DROPDOWNLIST | WS_VSCROLL | + COMBOBOX IDC_DEVICESEL,7,25,172,33,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - COMBOBOX IDC_VIDMODE,7,46,172,74,CBS_DROPDOWNLIST | WS_VSCROLL | + COMBOBOX IDC_VIDMODE,7,46,172,74,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP DEFPUSHBUTTON "EXIT",IDEXIT,103,69,52,14 DEFPUSHBUTTON "OK",IDOK,28,69,50,14 @@ -42,6 +42,6 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -IDI_MAIN_ICON ICON DISCARDABLE "gta3.ico" +IDI_MAIN_ICON ICON DISCARDABLE "re3.ico" -///////////////////////////////////////////////////////////////////////////// \ No newline at end of file +///////////////////////////////////////////////////////////////////////////// -- 2.45.2 From f4db1b337b38d0764cde6b08e04785feb8cba5d6 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sat, 16 Jan 2021 09:28:23 +0100 Subject: [PATCH 4/4] make re3 relocatable --- CMakeLists.txt | 1 + src/CMakeLists.txt | 10 + src/audio/AudioSamples.h | 2 - src/audio/oal/stream.cpp | 22 +- src/audio/oal/stream.h | 2 +- src/audio/sampman_miles.cpp | 41 +-- src/audio/sampman_oal.cpp | 22 +- src/core/CdStream.cpp | 13 +- src/core/CdStreamPosix.cpp | 11 +- src/core/FileMgr.cpp | 46 ++-- src/core/FileMgr.h | 2 + src/core/Frontend.cpp | 15 +- src/core/common.h | 4 +- src/fakerw/fake.cpp | 18 +- src/render/Particle.cpp | 6 +- src/skel/crossplatform.h | 1 + src/skel/glfw/glfw.cpp | 9 +- src/skel/relocatable.cpp | 492 +++++++++++++++++++++++++++++++++++ src/skel/relocatable.h | 45 ++++ src/vehicles/HandlingMgr.cpp | 5 +- 20 files changed, 673 insertions(+), 94 deletions(-) create mode 100644 src/skel/relocatable.cpp create mode 100644 src/skel/relocatable.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 35830147..f64e2b0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ set(EXECUTABLE re3) +set(STEAMID 12100) cmake_minimum_required(VERSION 3.8) project(${EXECUTABLE} C CXX) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4b4660a6..2104153f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,7 +39,14 @@ target_compile_definitions(${EXECUTABLE} $,DEBUG,NDEBUG> LIBRW RE3_NO_AUTOLINK + RELOCATABLE ) +if(STEAMID) + target_compile_definitions(${EXECUTABLE} + PRIVATE + STEAMID=${STEAMID} + ) +endif() if(LIBRW_PLATFORM_D3D9) target_compile_definitions(${EXECUTABLE} @@ -109,6 +116,9 @@ set_target_properties(${EXECUTABLE} CXX_STANDARD 11 CXX_EXTENSIONS OFF CXX_STANDARD_REQUIRED ON + VS_DEBUGGER_ENVIRONMENT "GTA3DIR=$" + VS_DEBUGGER_COMMAND_ARGUMENTS "-console" + VS_JUST_MY_CODE_DEBUGGING ON ) if(RE3_INSTALL) diff --git a/src/audio/AudioSamples.h b/src/audio/AudioSamples.h index df64521c..b36b4de8 100644 --- a/src/audio/AudioSamples.h +++ b/src/audio/AudioSamples.h @@ -1,7 +1,5 @@ #pragma once -#include "common.h" - enum eSfxSample { SFX_CAR_HORN_JEEP = 0, diff --git a/src/audio/oal/stream.cpp b/src/audio/oal/stream.cpp index 74ed86f4..bf715181 100644 --- a/src/audio/oal/stream.cpp +++ b/src/audio/oal/stream.cpp @@ -1,9 +1,11 @@ +#ifdef AUDIO_OAL #include "common.h" -#ifdef AUDIO_OAL #include "stream.h" #include "sampman.h" +#include "relocatable.h" + #if defined _MSC_VER && !defined RE3_NO_AUTOLINK #ifdef AUDIO_OAL_USE_SNDFILE #pragma comment( lib, "libsndfile-1.lib" ) @@ -213,7 +215,7 @@ class CWavFile : public IDecoder public: CWavFile(const char* path) : m_bIsOpen(false), m_DataStartOffset(0), m_nSampleCount(0), m_nSamplesPerBlock(0), m_pAdpcmBuffer(nil), m_ppPcmBuffers(nil), m_pAdpcmDecoders(nil) { - m_pFile = fopen(path, "rb"); + m_pFile = reloc_fopen(path, "rb"); if (!m_pFile) return; #define CLOSE_ON_ERROR(op)\ @@ -402,7 +404,7 @@ public: m_pfSound(nil) { memset(&m_soundInfo, 0, sizeof(m_soundInfo)); - m_pfSound = sf_open(path, SFM_READ, &m_soundInfo); + m_pfSound = sf_open(reloc_realpath(path), SFM_READ, &m_soundInfo); } ~CSndFile() @@ -489,8 +491,8 @@ public: long rate = 0; int channels = 0; int encoding = 0; - - m_bOpened = mpg123_open(m_pMH, path) == MPG123_OK + + m_bOpened = mpg123_open(m_pMH, reloc_realpath(path)) == MPG123_OK && mpg123_getformat(m_pMH, &rate, &channels, &encoding) == MPG123_OK; m_nRate = rate; m_nChannels = channels; @@ -669,7 +671,7 @@ public: CVbFile(const char* path, uint32 nSampleRate = 32000, uint8 nChannels = 2) : m_nSampleRate(nSampleRate), m_nChannels(nChannels), m_pVagDecoders(nil), m_ppVagBuffers(nil), m_ppPcmBuffers(nil), m_FileSize(0), m_nNumberOfBlocks(0), m_bBlockRead(false), m_LineInBlock(0), m_CurrentBlock(0) { - m_pFile = fopen(path, "rb"); + m_pFile = reloc_fopen(path, "rb"); if (!m_pFile) return; fseek(m_pFile, 0, SEEK_END); @@ -819,7 +821,7 @@ public: m_nChannels(0) { int ret; - m_FileH = op_open_file(path, &ret); + m_FileH = op_open_file(reloc_realpath(path), &ret); if (m_FileH) { m_nChannels = op_head(m_FileH, 0)->channel_count; @@ -915,7 +917,7 @@ void CStream::Terminate() #endif } -CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS], uint32 overrideSampleRate) : +CStream::CStream(const char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS], uint32 overrideSampleRate) : m_pAlSources(sources), m_alBuffers(buffers), m_pBuffer(nil), @@ -930,7 +932,7 @@ CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBU { // Be case-insensitive on linux (from https://github.com/OneSadCookie/fcaseopen/) #if !defined(_WIN32) - char *real = casepath(filename); + char *real = reloc_casepath(filename); if (real) { strcpy(m_aFilename, real); free(real); @@ -940,7 +942,7 @@ CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBU #endif strcpy(m_aFilename, filename); } - + DEV("Stream %s\n", m_aFilename); if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".wav")], ".wav")) diff --git a/src/audio/oal/stream.h b/src/audio/oal/stream.h index bcbc5e54..82d2b5d4 100644 --- a/src/audio/oal/stream.h +++ b/src/audio/oal/stream.h @@ -86,7 +86,7 @@ public: static void Initialise(); static void Terminate(); - CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS], uint32 overrideSampleRate = 32000); + CStream(const char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS], uint32 overrideSampleRate = 32000); ~CStream(); void Delete(); diff --git a/src/audio/sampman_miles.cpp b/src/audio/sampman_miles.cpp index 82886c66..22a40813 100644 --- a/src/audio/sampman_miles.cpp +++ b/src/audio/sampman_miles.cpp @@ -14,6 +14,7 @@ #include "MusicManager.h" #include "Frontend.h" #include "Timer.h" +#include "relocatable.h" #pragma comment( lib, "mss32.lib" ) @@ -369,7 +370,7 @@ _ResolveLink(char const *path, char *out) { WCHAR wpath[MAX_PATH]; - MultiByteToWideChar(CP_ACP, 0, path, -1, wpath, MAX_PATH); + MultiByteToWideChar(CP_ACP, 0, reloc_realpath(path), -1, wpath, MAX_PATH); if (SUCCEEDED(ppf->Load(wpath, STGM_READ))) { @@ -378,7 +379,7 @@ _ResolveLink(char const *path, char *out) { strcpy(filepath, path); - if (SUCCEEDED(psl->GetPath(filepath, MAX_PATH, &fd, SLGP_UNCPRIORITY))) + if (SUCCEEDED(psl->GetPath(reloc_realpath(filepath), MAX_PATH, &fd, SLGP_UNCPRIORITY))) { OutputDebugString(fd.cFileName); @@ -429,7 +430,7 @@ _FindMP3s(void) strcat(path, "*"); - hFind = FindFirstFile(path, &fd); + hFind = reloc_FindFirstFile(path, &fd); if ( hFind == INVALID_HANDLE_VALUE ) { @@ -444,7 +445,7 @@ _FindMP3s(void) if ( filepathlen <= 0) { - FindClose(hFind); + reloc_FindClose(hFind); return; } @@ -479,7 +480,7 @@ _FindMP3s(void) bShortcut = false; } - mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + mp3Stream[0] = AIL_open_stream(DIG, reloc_realpath(filepath), 0); if ( mp3Stream[0] ) { AIL_stream_ms_position(mp3Stream[0], &total_ms, NULL); @@ -493,7 +494,7 @@ _FindMP3s(void) if ( _pMP3List == NULL ) { - FindClose(hFind); + reloc_FindClose(hFind); if ( f ) fclose(f); @@ -538,7 +539,7 @@ _FindMP3s(void) while ( true ) { - if ( !FindNextFile(hFind, &fd) ) + if ( !reloc_FindNextFile(hFind, &fd) ) break; if ( bInitFirstEntry ) @@ -583,7 +584,7 @@ _FindMP3s(void) } } - mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + mp3Stream[0] = AIL_open_stream(DIG, reloc_realpath(filepath), 0); if ( mp3Stream[0] ) { AIL_stream_ms_position(mp3Stream[0], &total_ms, NULL); @@ -665,7 +666,7 @@ _FindMP3s(void) } } - mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + mp3Stream[0] = AIL_open_stream(DIG, reloc_realpath(filepath), 0); if ( mp3Stream[0] ) { AIL_stream_ms_position(mp3Stream[0], &total_ms, NULL); @@ -719,7 +720,7 @@ _FindMP3s(void) fclose(f); } - FindClose(hFind); + reloc_FindClose(hFind); } static void @@ -959,7 +960,7 @@ cSampleManager::Initialise(void) #ifdef AUDIO_CACHE TRACE("cache"); - FILE *cacheFile = fopen("audio\\sound.cache", "rb"); + FILE *cacheFile = reloc_fopen("audio\\sound.cache", "rb"); if (cacheFile) { fread(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); fclose(cacheFile); @@ -993,7 +994,7 @@ cSampleManager::Initialise(void) strcpy(filepath, m_szCDRomRootPath); strcat(filepath, StreamedNameTable[0]); - FILE *f = fopen(filepath, "rb"); + FILE *f = reloc_fopen(filepath, "rb"); if ( f ) { @@ -1006,7 +1007,7 @@ cSampleManager::Initialise(void) strcpy(filepath, m_szCDRomRootPath); strcat(filepath, StreamedNameTable[i]); - mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + mp3Stream[0] = AIL_open_stream(DIG, reloc_realpath(filepath), 0); if ( mp3Stream[0] ) { @@ -1076,7 +1077,7 @@ cSampleManager::Initialise(void) strcpy(_aHDDPath, m_szCDRomRootPath); rootpath[0] = '\0'; - FILE *f = fopen(StreamedNameTable[0], "rb"); + FILE *f = reloc_fopen(StreamedNameTable[0], "rb"); if ( f ) { @@ -1087,7 +1088,7 @@ cSampleManager::Initialise(void) strcpy(filepath, rootpath); strcat(filepath, StreamedNameTable[i]); - mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + mp3Stream[0] = AIL_open_stream(DIG, reloc_realpath(filepath), 0); if ( mp3Stream[0] ) { @@ -1123,7 +1124,7 @@ cSampleManager::Initialise(void) } #endif #ifdef AUDIO_CACHE - cacheFile = fopen("audio\\sound.cache", "wb"); + cacheFile = reloc_fopen("audio\\sound.cache", "wb"); fwrite(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); fclose(cacheFile); } @@ -1309,7 +1310,7 @@ cSampleManager::CheckForAnAudioFileOnCD(void) strcat(filepath, StreamedNameTable[AudioManager.GetRandomNumber(1) % TOTAL_STREAMED_SOUNDS]); - FILE *f = fopen(filepath, "rb"); + FILE *f = reloc_fopen(filepath, "rb"); if ( f ) { @@ -2009,7 +2010,7 @@ cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream) strcpy(filepath, m_szCDRomRootPath); strcat(filepath, StreamedNameTable[nFile]); - mp3Stream[nStream] = AIL_open_stream(DIG, filepath, 0); + mp3Stream[nStream] = AIL_open_stream(DIG, reloc_realpath(filepath), 0); if ( mp3Stream[nStream] ) { @@ -2289,11 +2290,11 @@ cSampleManager::InitialiseSampleBanks(void) { int32 nBank = SFX_BANK_0; - fpSampleDescHandle = fopen(SampleBankDescFilename, "rb"); + fpSampleDescHandle = reloc_fopen(SampleBankDescFilename, "rb"); if ( fpSampleDescHandle == NULL ) return false; - fpSampleDataHandle = fopen(SampleBankDataFilename, "rb"); + fpSampleDataHandle = reloc_fopen(SampleBankDataFilename, "rb"); if ( fpSampleDataHandle == NULL ) { fclose(fpSampleDescHandle); diff --git a/src/audio/sampman_oal.cpp b/src/audio/sampman_oal.cpp index 798ea287..fd897450 100644 --- a/src/audio/sampman_oal.cpp +++ b/src/audio/sampman_oal.cpp @@ -14,7 +14,9 @@ #include #include +#ifdef _MSC_VER #pragma comment(lib, "OpenAL32.lib") +#endif // for user MP3s #include @@ -24,8 +26,10 @@ #define _getcwd getcwd #endif +#define WITHWINDOWS #include "common.h" #include "crossplatform.h" +#include "relocatable.h" #include "sampman.h" @@ -562,7 +566,7 @@ _FindMP3s(void) strcat(path, "*"); - hFind = FindFirstFile(path, &fd); + hFind = reloc_FindFirstFile(path, &fd); if ( hFind == INVALID_HANDLE_VALUE ) { @@ -576,7 +580,7 @@ _FindMP3s(void) if ( filepathlen <= 0) { - FindClose(hFind); + reloc_FindClose(hFind); return; } @@ -964,7 +968,7 @@ cSampleManager::Initialise(void) add_providers(); #ifdef AUDIO_CACHE - FILE *cacheFile = fcaseopen("audio\\sound.cache", "rb"); + FILE *cacheFile = reloc_fcaseopen("audio\\sound.cache", "rb"); // FIXME: was fcaseopen if (cacheFile) { debug("Loadind audio cache (If game crashes around here, then your cache is corrupted, remove audio/sound.cache)\n"); fread(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); @@ -987,7 +991,13 @@ cSampleManager::Initialise(void) USERERROR("Can't open '%s'\n", StreamedNameTable[i]); } #ifdef AUDIO_CACHE - cacheFile = fcaseopen("audio\\sound.cache", "wb"); + // FIXME: cross platform mkdir!!! +#ifdef _MSC_VER + ::mkdir("audio"); +#else + ::mkdir("audio", S_IREAD | S_IWRITE | S_IEXEC | S_IXGRP | S_IXOTH); +#endif + cacheFile = reloc_fcaseopen("audio\\sound.cache", "wb"); if(cacheFile) { debug("Saving audio cache\n"); fwrite(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); @@ -1970,11 +1980,11 @@ cSampleManager::InitialiseSampleBanks(void) { int32 nBank = SFX_BANK_0; - fpSampleDescHandle = fcaseopen(SampleBankDescFilename, "rb"); + fpSampleDescHandle = reloc_fcaseopen(SampleBankDescFilename, "rb"); if ( fpSampleDescHandle == NULL ) return false; #ifndef OPUS_SFX - fpSampleDataHandle = fcaseopen(SampleBankDataFilename, "rb"); + fpSampleDataHandle = reloc_fcaseopen(SampleBankDataFilename, "rb"); if ( fpSampleDataHandle == NULL ) { fclose(fpSampleDescHandle); diff --git a/src/core/CdStream.cpp b/src/core/CdStream.cpp index 4bb31ea4..0c8434a7 100644 --- a/src/core/CdStream.cpp +++ b/src/core/CdStream.cpp @@ -6,9 +6,10 @@ #include "rwcore.h" #include "RwHelper.h" #include "MemoryMgr.h" +#include "relocatable.h" #define CDDEBUG(f, ...) debug ("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) -#define CDTRACE(f, ...) printf("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) +#define CDTRACE(f, ...) re3_trace(__FILE__, __LINE__, __FUNCTION__, "cdvd_stream: " f "\n", ## __VA_ARGS__) struct CdReadInfo { @@ -472,18 +473,16 @@ CdStreamAddImage(char const *path) ASSERT(path != nil); ASSERT(gNumImages < MAX_CDIMAGES); - SetLastError(0); - - gImgFiles[gNumImages] = CreateFile(path, + gImgFiles[gNumImages] = CreateFile(reloc_realpath(path), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, _gdwCdStreamFlags | FILE_FLAG_RANDOM_ACCESS | FILE_ATTRIBUTE_READONLY, nil); - - ASSERT( gImgFiles[gNumImages] != nil ); - if ( gImgFiles[gNumImages] == NULL ) + + ASSERT( gImgFiles[gNumImages] != INVALID_HANDLE_VALUE ); + if ( gImgFiles[gNumImages] == INVALID_HANDLE_VALUE ) return false; strcpy(gCdImageNames[gNumImages], path); diff --git a/src/core/CdStreamPosix.cpp b/src/core/CdStreamPosix.cpp index 0854d850..a7ddce27 100644 --- a/src/core/CdStreamPosix.cpp +++ b/src/core/CdStreamPosix.cpp @@ -1,6 +1,7 @@ #ifndef _WIN32 #include "common.h" #include "crossplatform.h" +#include "relocatable.h" #include #include #include @@ -19,7 +20,7 @@ #include "MemoryMgr.h" #define CDDEBUG(f, ...) debug ("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) -#define CDTRACE(f, ...) printf("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) +#define CDTRACE(f, ...) TRACE ("cddvd_stream: " f "\n", ## __VA_ARGS__) // #define ONE_THREAD_PER_CHANNEL // Don't use if you're not on SSD/Flash. (Also you may want to benefit from this via using all channels in Streaming.cpp) @@ -145,7 +146,7 @@ CdStreamInit(int32 numChannels) { struct statvfs fsInfo; - if((statvfs("models/gta3.img", &fsInfo)) < 0) + if((statvfs(reloc_realpath("models/gta3.img"), &fsInfo)) < 0) { CDTRACE("can't get filesystem info"); ASSERT(0); @@ -192,7 +193,7 @@ GetGTA3ImgSize(void) realpath(gImgNames[0], path); if (stat(path, &statbuf) == -1) { // Try case-insensitivity - char* real = casepath(gImgNames[0], false); + char* real = reloc_casepath(gImgNames[0], false); if (real) { realpath(real, path); @@ -483,11 +484,11 @@ CdStreamAddImage(char const *path) ASSERT(path != nil); ASSERT(gNumImages < MAX_CDIMAGES); - gImgFiles[gNumImages] = open(path, _gdwCdStreamFlags); + gImgFiles[gNumImages] = open(reloc_realpath(path), _gdwCdStreamFlags); // Fix case sensitivity and backslashes. if (gImgFiles[gNumImages] == -1) { - char* real = casepath(path, false); + char* real = reloc_casepath(path, false); if (real) { gImgFiles[gNumImages] = open(real, _gdwCdStreamFlags); diff --git a/src/core/FileMgr.cpp b/src/core/FileMgr.cpp index 32aa4041..d532bfac 100644 --- a/src/core/FileMgr.cpp +++ b/src/core/FileMgr.cpp @@ -5,6 +5,7 @@ #endif #include "common.h" #include "crossplatform.h" +#include "relocatable.h" #include "FileMgr.h" @@ -27,23 +28,21 @@ struct myFILE #define NUMFILES 20 static myFILE myfiles[NUMFILES]; - #if !defined(_WIN32) #include #include #include #define _getcwd getcwd - // Case-insensitivity on linux (from https://github.com/OneSadCookie/fcaseopen) void mychdir(char const *path) { - char* r = casepath(path, false); - if (r) { - chdir(r); + char *r = reloc_casepath(path, false); + if(r) { + chdir(r); free(r); - } else { - errno = ENOENT; - } + } else { + errno = ENOENT; + } } #else #define mychdir chdir @@ -70,9 +69,9 @@ found: mode++; *p++ = 'b'; *p = '\0'; - - myfiles[fd].file = fcaseopen(filename, realmode); - if(myfiles[fd].file == nil) + + myfiles[fd].file = reloc_fcaseopen(filename, realmode); + if (myfiles[fd].file == nil) return 0; return fd; } @@ -200,24 +199,30 @@ char CFileMgr::ms_dirName[128]; void CFileMgr::Initialise(void) { + strcpy(ms_dirName, ""); +#ifdef RELOCATABLE + strcpy(ms_rootDirName, ""); +#else _getcwd(ms_rootDirName, 128); strcat(ms_rootDirName, "\\"); +#endif + reloc_initialize(); } void CFileMgr::ChangeDir(const char *dir) { - if(*dir == '\\'){ - strcpy(ms_dirName, ms_rootDirName); - dir++; - } - if(*dir != '\0'){ - strcat(ms_dirName, dir); + strcpy(ms_dirName, dir); + if (*dir != '\0'){ // BUG in the game it seems, it's off by one - if(dir[strlen(dir)-1] != '\\') + if (dir[strlen(dir)-1] != '\\') { strcat(ms_dirName, "\\"); + } } +#ifndef RELOCATABLE + mychdir(ms_rootDirName); mychdir(ms_dirName); +#endif } void @@ -230,14 +235,19 @@ CFileMgr::SetDir(const char *dir) if(dir[strlen(dir)-1] != '\\') strcat(ms_dirName, "\\"); } +#ifndef RELOCATABLE mychdir(ms_dirName); +#endif } void CFileMgr::SetDirMyDocuments(void) { SetDir(""); // better start at the root if user directory is relative + //FIXME: When using USE_MY_DOCUMENTS, the returned path is absolute. +#ifndef RELOCATABLE mychdir(_psGetUserFilesFolder()); +#endif } ssize_t diff --git a/src/core/FileMgr.h b/src/core/FileMgr.h index f70451b7..7a3ad5f4 100644 --- a/src/core/FileMgr.h +++ b/src/core/FileMgr.h @@ -1,5 +1,6 @@ #pragma once + class CFileMgr { static char ms_rootDirName[128]; @@ -20,4 +21,5 @@ public: static int CloseFile(int fd); static int GetErrorReadWrite(int fd); static char *GetRootDirName() { return ms_rootDirName; } + static const char *GetWorkingDir() { return ms_dirName; } }; diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp index 65eab125..580027c0 100644 --- a/src/core/Frontend.cpp +++ b/src/core/Frontend.cpp @@ -4,6 +4,7 @@ #include "common.h" #ifndef PS2_MENU #include "crossplatform.h" +#include "relocatable.h" #include "platform.h" #include "Frontend.h" #include "Font.h" @@ -3081,8 +3082,8 @@ CMenuManager::DrawPlayerSetupScreen() WIN32_FIND_DATA FindFileData; SYSTEMTIME SystemTime; - HANDLE handle = FindFirstFile("skins\\*.bmp", &FindFileData); - for (int i = 1; handle != INVALID_HANDLE_VALUE && i; i = FindNextFile(handle, &FindFileData)) { + HANDLE handle = reloc_FindFirstFile("skins\\*.bmp", &FindFileData); + for (int i = 1; handle != INVALID_HANDLE_VALUE && i; i = reloc_FindNextFile(handle, &FindFileData)) { if (strcmp(FindFileData.cFileName, DEFAULT_SKIN_NAME) != 0) { m_pSelectedSkin->nextSkin = new tSkinInfo; m_pSelectedSkin = m_pSelectedSkin->nextSkin; @@ -3095,7 +3096,7 @@ CMenuManager::DrawPlayerSetupScreen() m_pSelectedSkin->nextSkin = nil; } } - FindClose(handle); + reloc_FindClose(handle); m_nSkinsTotal = nextSkinId; char nameTemp[256]; for (m_pSelectedSkin = m_pSkinListHead.nextSkin; m_pSelectedSkin; m_pSelectedSkin = m_pSelectedSkin->nextSkin) { @@ -3686,7 +3687,7 @@ CMenuManager::LoadSettings() m_nPrefsWidth = 0; m_nPrefsHeight = 0; m_nPrefsDepth = 0; - m_nPrefsWindowed = 0; + m_nPrefsWindowed =0; m_nPrefsSubsystem = 0; } m_nSelectedScreenMode = m_nPrefsWindowed; @@ -3756,14 +3757,14 @@ CMenuManager::LoadSettings() WIN32_FIND_DATA FindFileData; char skinfile[256+16]; // Stack analysis shows 16 bits gap, but I don't trust it. It may very well be MAX_PATH(260). bool SkinFound = false; - HANDLE handle = FindFirstFile("skins\\*.bmp", &FindFileData); - for (int i = 1; handle != INVALID_HANDLE_VALUE && i; i = FindNextFile(handle, &FindFileData)) { + HANDLE handle = reloc_FindFirstFile("skins\\*.bmp", &FindFileData); + for (int i = 1; handle != INVALID_HANDLE_VALUE && i; i = reloc_FindNextFile(handle, &FindFileData)) { strcpy(skinfile, m_PrefsSkinFile); strcat(skinfile, ".bmp"); if (strcmp(FindFileData.cFileName, skinfile) == 0) SkinFound = true; } - FindClose(handle); + reloc_FindClose(handle); if (!SkinFound) { OutputDebugString("Default skin set as no other skins are available OR saved skin not found!"); diff --git a/src/core/common.h b/src/core/common.h index d7facfd1..ab0263a8 100644 --- a/src/core/common.h +++ b/src/core/common.h @@ -1,11 +1,13 @@ #pragma once +#ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS #define _USE_MATH_DEFINES #pragma warning(disable: 4244) // int to float #pragma warning(disable: 4800) // int to bool #pragma warning(disable: 4838) // narrowing conversion #pragma warning(disable: 4996) // POSIX names +#endif #include #include @@ -305,7 +307,7 @@ void re3_usererror(const char *format, ...); #endif #define debug(f, ...) re3_debug("[DBG]: " f, ## __VA_ARGS__) -#define TRACE(f, ...) re3_trace(__FILE__, __LINE__, __FUNCTION__, f, ## __VA_ARGS__) +#define TRACE(f, ...) re3_trace(__FILE__, __LINE__, __FUNCTION__, f "\n", ## __VA_ARGS__) #define Error(f, ...) re3_debug("[ERROR]: " f, ## __VA_ARGS__) #define USERERROR(f, ...) re3_usererror(f, ## __VA_ARGS__) diff --git a/src/fakerw/fake.cpp b/src/fakerw/fake.cpp index c1150931..a5a7a461 100644 --- a/src/fakerw/fake.cpp +++ b/src/fakerw/fake.cpp @@ -7,9 +7,9 @@ #include #include #include -#ifndef _WIN32 + #include "crossplatform.h" -#endif +#include "relocatable.h" using namespace rw; @@ -337,10 +337,6 @@ const RwTexDictionary *RwTexDictionaryStreamWrite(const RwTexDictionary *texDict return texDict; } - - - - RwStream *RwStreamOpen(RwStreamType type, RwStreamAccessType accessType, const void *pData) { StreamFile *file; StreamMemory *mem; @@ -361,9 +357,9 @@ RwStream *RwStreamOpen(RwStreamType type, RwStreamAccessType accessType, const v file = rwNewT(StreamFile, 1, 0); memcpy(file, &fakefile, sizeof(StreamFile)); #ifndef _WIN32 - char *r = casepath((char*)pData); + char *r = reloc_casepath((char*)pData); if (r) { - if (file->open((char*)r, mode)) { + if (file->open(r, mode)) { free(r); return file; } @@ -371,7 +367,7 @@ RwStream *RwStreamOpen(RwStreamType type, RwStreamAccessType accessType, const v } else #endif { - if (file->open((char*)pData, mode)) + if (file->open((const char *)pData, mode)) return file; } rwFree(file); @@ -913,7 +909,7 @@ RwImage * RtBMPImageWrite(RwImage *image, const RwChar *imageName) { #ifndef _WIN32 - char *r = casepath(imageName); + char *r = reloc_casepath(imageName); if (r) { rw::writeBMP(image, r); free(r); @@ -931,7 +927,7 @@ RtBMPImageRead(const RwChar *imageName) { #ifndef _WIN32 RwImage *image; - char *r = casepath(imageName); + char *r = reloc_casepath(imageName); if (r) { image = rw::readBMP(r); free(r); diff --git a/src/render/Particle.cpp b/src/render/Particle.cpp index 6c643caf..15fb0c9a 100644 --- a/src/render/Particle.cpp +++ b/src/render/Particle.cpp @@ -233,11 +233,11 @@ TWEAKFUNC(CParticle::ReloadConfig); void CParticle::ReloadConfig() { - debug("Initialising CParticleMgr..."); + debug("Initialising CParticleMgr...\n"); mod_ParticleSystemManager.Initialise(); - debug("Initialising CParticle..."); + debug("Initialising CParticle...\n"); m_pUnusedListHead = gParticleArray; @@ -622,7 +622,7 @@ CEntity::AddSteamsFromGround(CVector *unused) void CParticle::Shutdown() { - debug("Shutting down CParticle..."); + debug("Shutting down CParticle...\n"); for ( int32 i = 0; i < MAX_SMOKE_FILES; i++ ) { diff --git a/src/skel/crossplatform.h b/src/skel/crossplatform.h index d8807f2b..a10739f7 100644 --- a/src/skel/crossplatform.h +++ b/src/skel/crossplatform.h @@ -1,3 +1,4 @@ +#pragma once #include // This is the common include for platform/renderer specific skeletons(glfw.cpp, win.cpp etc.) and using cross platform things (like Windows directories wrapper, platform specific global arrays etc.) diff --git a/src/skel/glfw/glfw.cpp b/src/skel/glfw/glfw.cpp index 5f87d600..87b189c5 100644 --- a/src/skel/glfw/glfw.cpp +++ b/src/skel/glfw/glfw.cpp @@ -33,6 +33,7 @@ long _dwOperatingSystemVersion; #include "skeleton.h" #include "platform.h" #include "crossplatform.h" +#include "relocatable.h" #include "main.h" #include "FileMgr.h" @@ -860,7 +861,7 @@ void _InputInitialiseJoys() // Load our gamepad mappings. #define SDL_GAMEPAD_DB_PATH "gamecontrollerdb.txt" - FILE *f = fopen(SDL_GAMEPAD_DB_PATH, "rb"); + FILE *f = reloc_fopen(SDL_GAMEPAD_DB_PATH, "rb"); if (f) { fseek(f, 0, SEEK_END); size_t fsize = ftell(f); @@ -936,7 +937,7 @@ void psPostRWinit(void) // Make sure all keys are released CPad::GetPad(0)->Clear(true); CPad::GetPad(1)->Clear(true); -} + } /* ***************************************************************************** @@ -1469,6 +1470,10 @@ WinMain(HINSTANCE instance, if (strstr(cmdLine, "-console")) { AllocConsole(); + SetConsoleTitle("GTA3 Debug Console"); + // Set default big history (to enable scrolling back) + CONSOLE_HISTORY_INFO historyInfo = {sizeof(CONSOLE_HISTORY_INFO), 9999, 4, 0}; + SetConsoleHistoryInfo(&historyInfo); freopen("CONIN$", "r", stdin); freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); diff --git a/src/skel/relocatable.cpp b/src/skel/relocatable.cpp new file mode 100644 index 00000000..a186a7bd --- /dev/null +++ b/src/skel/relocatable.cpp @@ -0,0 +1,492 @@ +#ifdef RELOCATABLE +#include "common.h" +#include "FileMgr.h" + +#include +#ifdef _MSC_VER +#include +#else +#include +#include +#endif + +#ifdef __APPLE__ +#include +#include +#endif + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#endif + +#define WITHWINDOWS +#include "crossplatform.h" +#include "relocatable.h" + + +#ifndef R_OK +#define R_OK 4 +#endif + +#ifdef _WIN32 +#define PATHJOIN '\\' +#define PATHJOIN_S "\\" +#define PATHSEP ';' +#else +#define PATHJOIN '/' +#define PATHJOIN_S "/" +#define PATHSEP ':' +#endif + +bool +FileExists(const char *path) +{ +#if _WIN32 + bool exists = PathFileExistsA(path); + struct stat status; + stat(path, &status); + return (status.st_mode & _S_IFREG) != 0; +#else + return access(path, R_OK) == 0; +#endif +} + + +bool +containsGta3Data(const std::string &dirPath) +{ + std::string path(dirPath); + path.append("models" PATHJOIN_S "gta3.img"); + return FileExists(path.c_str()); +} + +#ifdef _WIN32 +std::string +winReadRegistry(HKEY hParentKey, const char *keyName, const char *valueName) +{ + HKEY hKey; + if (RegOpenKeyExA(hParentKey, keyName, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { + std::string res; + DWORD valueSize = 1024; + res.resize(valueSize); + DWORD keyType; + if (RegQueryValueEx(hKey, valueName, NULL, &keyType, (LPBYTE)res.data(), &valueSize) == ERROR_SUCCESS) { + RegCloseKey(hKey); + if(keyType == REG_SZ) { + res.resize(strlen(res.data())); + return res; + } + } + RegCloseKey(hKey); + } + return ""; +} + +std::string +psExePath() +{ + std::string buf; + buf.resize(MAX_PATH); + if (GetModuleFileName(NULL, (LPSTR)buf.c_str(), MAX_PATH) == 0) { + debug("GetModuleFileNameA failed: cannot get path of executable.\n"); + return ""; + } + buf.resize(strlen(buf.c_str())); + return buf; +} +#endif + +#ifdef __APPLE__ +std::string +psExePath() +{ + std::string buf; + uint32_t bufsize = 0; + _NSGetExecutablePath(NULL, &bufsize); + buf.resize(bufsize + 1); + if(_NSGetExecutablePath((char*)buf.c_str(), &bufsize) != 0) { + debug("_NSGetExecutablePath failed: cannot get path of executable.\n"); + return ""; + } + buf.resize(bufsize); + return buf; +} +#endif + +#ifdef __linux__ +std::string +psExePath() +{ + std::string buf; + buf.resize(MAX_PATH); + ssize_t nb = readlink("/proc/self/exe", (char*)buf.c_str(), MAX_PATH); + if (nb == -1) { + return ""; + } + buf.resize(strlen(buf.c_str())); + return buf; +} +#endif + +std::string +psExeDir() +{ + std::string exePath = psExePath(); + if (!exePath.size()) { + return ""; + } +#ifdef _MSC_VER + std::string::size_type lastSepPos = exePath.rfind(PATHJOIN); + if (lastSepPos >= 0) { + exePath.resize(lastSepPos); + return exePath; + } + return ""; +#else + return dirname((char*)exePath.c_str()); +#endif +} + +#ifdef _WIN32 +std::string +winGetGtaDataFolder() +{ + if (containsGta3Data("C:\\Program Files\\Rockstar Games\\GTAIII\\")) { + return "C:\\Program Files\\Rockstar Games\\GTAIII\\"; + } + std::string location; + location.resize(MAX_PATH); + BOOL success = SHGetSpecialFolderPathA(NULL, (LPSTR)location.c_str(), CSIDL_PROGRAM_FILESX86, FALSE); + if (success) { + location.resize(strlen(location.c_str())); + location.append("\\Rockstar Games\\GTAIII\\"); + if(containsGta3Data(location)) { + return location; + } + } +#if defined(STEAMID) + location = winReadRegistry(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Steam App " STR(STEAMID), "InstallLocation"); + if (location.size()) { + location.push_back('\\'); + if (containsGta3Data(location)) { + return location; + } + } +#endif + return ""; +} +#endif + +void +normalizePathCorrectPathJoiner(std::string &s) +{ +#ifdef _WIN32 +#define PATHJOIN_BAD '/' +#else +#define PATHJOIN_BAD '\\' +#endif + std::string::size_type pos(0); + while((pos = s.find(PATHJOIN_BAD, pos)) != std::string::npos) { + s[pos] = PATHJOIN; + } +} + +void +ensureTrailingPathJoiner(std::string &s) +{ + if(s.size() && *s.rbegin() != PATHJOIN) { + s.push_back(PATHJOIN); + } +} + + +#include +std::vector searchPaths; + +void +reloc_initialize() +{ + // FIXME: is called twice + searchPaths.clear(); + std::string path; + // 1. add search paths from command line + // FIXME + // 2. `GTA_III_RE_DIR` environment variable + const char *pEnv = ::getenv("GTA3DIR"); + if (pEnv) { + std::string env = pEnv; + std::string::size_type start(0); + std::string::size_type end; + while ((end = env.find(PATHSEP, start)) != std::string::npos) { + searchPaths.push_back(env.substr(start, end - start)); + start = end + 1; + } + searchPaths.push_back(env.substr(start)); + } + // 3. add search paths from INI + // FIXME + // 4. add current working directory to search path + path.resize(MAX_PATH); + char *cwd = getcwd((char *)path.c_str(), path.size()); + if (cwd) { + path.resize(strlen(path.c_str())); + searchPaths.push_back(path); + } + // 5. add exe path to search path + path = psExeDir(); + if(path.size()) { + searchPaths.push_back(path); + } +#ifdef _WIN32 + // 6. add default installation paths + path = winGetGtaDataFolder(); + if(path.size()) { + searchPaths.push_back(path); + } +#endif + // 6. built-in path +#ifdef GTA_III_RE_DIR + searchPaths.push_back(GTA_III_RE_DIR); +#endif + + // Normalize paths + for(std::string &path : searchPaths) { + normalizePathCorrectPathJoiner(path); + ensureTrailingPathJoiner(path); + } + + debug("Full search path is:\n"); + for (const std::string &s: searchPaths) { + debug("- %s\n", s.c_str()); + } +} + +std::string +searchPathsFindData(const char *path) +{ + const char *cwd = CFileMgr::GetWorkingDir(); + std::string suffix; + if (cwd && *cwd != '\0') { + suffix.append(cwd); + std::string::size_type pos = 0; + while(suffix[pos] == PATHJOIN) { + ++pos; + } + suffix = suffix.substr(pos, suffix.size() - pos); + if (suffix[suffix.size() - 1] != PATHJOIN) { + suffix.push_back(PATHJOIN); + } + } + suffix.append(path); + normalizePathCorrectPathJoiner(suffix); + for (const auto &searchpath : searchPaths) { + std::string curPath = searchpath + suffix; + if (FileExists(curPath.c_str())) { + return curPath; + } + FILE *f = fopen(curPath.c_str(), "rb"); + if(f) { + fclose(f); + debug("FileExists failed, but fopen succeeded. huh?\n"); + } + } + TRACE("Could not find \"%s\" in search paths (cwd=\"%s\").\n", path, cwd); + std::string res; + if (cwd && *cwd != '\0') { + res.append(cwd); + res.push_back(PATHJOIN); + } + res.append(path); + normalizePathCorrectPathJoiner(res); + return res; +} + +#include "FileMgr.h" +#include "common.h" + +FILE * +reloc_fopen(const char *path, const char *mode) +{ + std::string fullPath = searchPathsFindData(path); + TRACE("reloc_fopen(%s, %s) -> fopen(%s, %s)", path, mode, fullPath.c_str(), mode); + return fopen(fullPath.c_str(), mode); +} + +const char* +reloc_realpath(const char *filename) +{ + static std::string fullPath; + fullPath = searchPathsFindData(filename); + TRACE("reloc_realpath(%s) -> %s", filename, fullPath.c_str()); + return fullPath.c_str(); +} + +struct reloc_HANDLE { + HANDLE hFind; + std::string fullPathName; + int searchPathIndex; +}; + +HANDLE +reloc_FindFirstFile(const char *pathName, WIN32_FIND_DATA *findFileData) +{ + std::string suffix = CFileMgr::GetWorkingDir(); + if (suffix.size()) { + std::string::size_type pos = 0; + while(suffix[pos] == PATHJOIN) { ++pos; } + suffix = suffix.substr(pos, suffix.size() - pos); + if (suffix[suffix.size() - 1] != PATHJOIN) { + suffix.push_back(PATHJOIN); + } + } + suffix.append(pathName); + + for (int i = 0; i < (int)searchPaths.size(); ++i) { + HANDLE hFind = FindFirstFile((searchPaths[i] + suffix).c_str(), findFileData); + if (hFind != INVALID_HANDLE_VALUE) { + return (HANDLE) new reloc_HANDLE {hFind, suffix, i}; + } + } + return INVALID_HANDLE_VALUE; +} + +bool +reloc_FindNextFile(HANDLE hFind, WIN32_FIND_DATA *findFileData) +{ + reloc_HANDLE *relocHandle = (reloc_HANDLE *) hFind; + if (FindNextFile(relocHandle->hFind, findFileData)) { + return true; + } + FindClose(relocHandle->hFind); + relocHandle->hFind = NULL; + + for (int i = relocHandle->searchPathIndex + 1; i < (int)searchPaths.size(); ++i) { + HANDLE newHFind = FindFirstFile((searchPaths[i] + relocHandle->fullPathName).c_str(), findFileData); + if (newHFind != INVALID_HANDLE_VALUE) { + relocHandle->hFind = newHFind; + relocHandle->searchPathIndex = i; + return true; + } + } + return false; +} + +bool +reloc_FindClose(HANDLE hFind) +{ + reloc_HANDLE *relocHandle = (reloc_HANDLE *)hFind; + bool res = true; + if (relocHandle->hFind != NULL) { + res = FindClose(relocHandle->hFind); + } + delete relocHandle; + return res; +} + +#ifndef _WIN32 +static bool +casepath_subpath(const std::string &basePath, const char *path, std::string &result) { + const char *curLevelStart; + size_t curLevelSize; + bool currentIsFile; + + if (path == NULL) { + return false; + } + + // Fail early when the base path does not exist. + DIR *d = opendir(basePath.c_str()); + if (d == NULL) { + return false; + } + + // Split path in parts, ignoring empty levels (e.g. '/a//b' --> {'a', 'b'}) + const char *nextCurLevelStart = path; + while (true) { + curLevelStart = nextCurLevelStart; + const char *delimPos = strpbrk(curLevelStart, "/\\"); + if (delimPos == NULL) { + curLevelSize = strlen(curLevelStart); + currentIsFile = true; + break; + } + curLevelSize = delimPos - curLevelStart; + nextCurLevelStart = delimPos + 1; + currentIsFile = false; + + if (curLevelSize > 0) { + break; + } + } + char *curLevel = (char*) alloca(curLevelSize + 1); + strncpy(curLevel, curLevelStart, curLevelSize); + curLevel[curLevelSize] = '\0'; + + struct dirent *e; + while ((e = readdir(d)) != NULL) { + if (strcasecmp(curLevel, e->d_name) == 0) { + if (currentIsFile) { + result = basePath + e->d_name; + closedir(d); + return true; + } + bool subres = casepath_subpath(basePath + e->d_name + PATHJOIN_S, nextCurLevelStart, result); + if (subres) { + return subres; + } + } + } + closedir(d); + return false; +} + + +char* +reloc_casepath(const char *path, bool checkPathFirst) +{ + std::string realPath; + realPath.reserve(PATH_MAX); + if (checkPathFirst) { + for (const std::string &searchPath : searchPaths) { + realPath = searchPath + CFileMgr::GetWorkingDir() + path; + if (access(realPath.c_str(), F_OK) != -1) { + // File path is correct + debug("reloc_casepath(%s, %d) -> %s\n", path, checkPathFirst, realPath.c_str()); + return strdup(realPath.c_str()); + } + } + } + + std::string suffixPath = std::string{CFileMgr::GetWorkingDir()} + path; + for (const std::string &searchPath : searchPaths) { + bool res = casepath_subpath(searchPath, suffixPath.c_str(), realPath); + if (res) { + debug("reloc_casepath(%s, %d) -> %s\n", path, checkPathFirst, realPath.c_str()); + return strdup(realPath.c_str()); + } + } + debug("reloc_casepath(%s, %d) failed (wd=%s)\n", path, checkPathFirst, CFileMgr::GetWorkingDir()); + return NULL; +} + +FILE * +reloc_fcaseopen(char const *filename, char const *mode) +{ + FILE *result; + char *real = reloc_casepath(filename); + if (real == NULL) { + result = fopen(filename, mode); + } else { + result = fopen(real, mode); + free(real); + } + return result; +} + +#endif +#endif + diff --git a/src/skel/relocatable.h b/src/skel/relocatable.h new file mode 100644 index 00000000..72e6840a --- /dev/null +++ b/src/skel/relocatable.h @@ -0,0 +1,45 @@ +#pragma once + +#if defined _WIN32 +#ifdef WITHWINDOWS +#include +#endif +#else +#include "crossplatform.h" +#endif + +#ifdef RELOCATABLE + +void reloc_initialize(void); + +FILE *reloc_fopen(const char *path, const char *mode); +const char *reloc_realpath(const char *filename); + +#ifdef _WIN32 +#define reloc_casepath(PATH, CHECKFIRST) reloc_realpath(PATH) +#define reloc_fcaseopen reloc_fopen +#else +char *reloc_casepath(char const *path, bool checkPathFirst = true); +FILE *reloc_fcaseopen(char const *filename, char const *mode); +#endif + +#if defined WITHWINDOWS +HANDLE reloc_FindFirstFile(const char *pathName, WIN32_FIND_DATA *findFileData); +bool reloc_FindNextFile(HANDLE hFind, WIN32_FIND_DATA *findFileData); +bool reloc_FindClose(HANDLE hFind); +#endif + +#else +#define reloc_initialize() + +#define reloc_fopen(path, mode) fopen(path, mode) +#define reloc_realpath(filename) (filename) + +#define reloc_casepath casepath +#define reloc_fcaseopen fcaseopen + +#define reloc_FindFirstFile FindFirstFile +#define reloc_FindNextFile FindNextFile +#define reloc_FindClose FindClose + +#endif diff --git a/src/vehicles/HandlingMgr.cpp b/src/vehicles/HandlingMgr.cpp index 00aaa682..22bb0558 100644 --- a/src/vehicles/HandlingMgr.cpp +++ b/src/vehicles/HandlingMgr.cpp @@ -97,7 +97,10 @@ cHandlingDataMgr::LoadHandlingData(void) tHandlingData *handling; CFileMgr::SetDir("DATA"); - CFileMgr::LoadFile(HandlingFilename, work_buff, sizeof(work_buff), "r"); + ssize_t nbBytesRead = CFileMgr::LoadFile(HandlingFilename, work_buff, sizeof(work_buff), "r"); + if(nbBytesRead < 0) { + USERERROR("Cannot open %s\n", HandlingFilename); + } CFileMgr::SetDir(""); start = (char*)work_buff; -- 2.45.2