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..f64e2b0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,82 +1,152 @@ -cmake_minimum_required(VERSION 3.8) - set(EXECUTABLE re3) -set(PROJECT RE3) +set(STEAMID 12100) +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 cabcc4c2..759b6673 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): @@ -107,18 +104,24 @@ 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 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 = {} 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 00000000..bc58c92e Binary files /dev/null and b/resources/logo.bmp differ diff --git a/resources/logo.ico b/resources/logo.ico new file mode 100644 index 00000000..6d477993 Binary files /dev/null and b/resources/logo.ico differ 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..2104153f 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,15 +31,22 @@ target_link_libraries(${EXECUTABLE} PRIVATE target_include_directories(${EXECUTABLE} PRIVATE $ - $ + $ ) target_compile_definitions(${EXECUTABLE} PRIVATE $,DEBUG,NDEBUG> LIBRW - ${PROJECT}_NO_AUTOLINK + RE3_NO_AUTOLINK + RELOCATABLE ) +if(STEAMID) + target_compile_definitions(${EXECUTABLE} + PRIVATE + STEAMID=${STEAMID} + ) +endif() if(LIBRW_PLATFORM_D3D9) target_compile_definitions(${EXECUTABLE} @@ -46,13 +55,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 +71,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 @@ -107,15 +116,23 @@ 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(${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/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/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; } 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/skel/win/gta3.ico b/src/skel/win/gta3.ico deleted file mode 100644 index 2017c811..00000000 Binary files a/src/skel/win/gta3.ico and /dev/null differ diff --git a/src/skel/win/re3.ico b/src/skel/win/re3.ico new file mode 100644 index 00000000..6d477993 Binary files /dev/null and b/src/skel/win/re3.ico differ 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 +///////////////////////////////////////////////////////////////////////////// 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;