diff --git a/CMake/Packaging-2.cmake b/CMake/Packaging-2.cmake index 3525ae1e4..38fcc59f3 100644 --- a/CMake/Packaging-2.cmake +++ b/CMake/Packaging-2.cmake @@ -24,7 +24,6 @@ if (CPACK_GENERATOR MATCHES "Bundle") set(CPACK_BUNDLE_NAME "soh") set(CPACK_BUNDLE_PLIST "macosx/Info.plist") set(CPACK_BUNDLE_ICON "macosx/soh.icns") - set(CPACK_BUNDLE_STARTUP_COMMAND "../soh/macosx/soh-macos.sh") + set(CPACK_BUNDLE_STARTUP_COMMAND "macosx/soh-macos.sh") set(CPACK_BUNDLE_APPLE_CERT_APP "-") endif() - diff --git a/CMakeLists.txt b/CMakeLists.txt index 92ddc528d..ee27c1e02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,7 +97,7 @@ set_property(TARGET soh PROPERTY APPIMAGE_DESKTOP_FILE "${CMAKE_SOURCE_DIR}/scri set_property(TARGET soh PROPERTY APPIMAGE_ICON_FILE "${CMAKE_BINARY_DIR}/sohIcon.png") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") -install(PROGRAMS "${CMAKE_SOURCE_DIR}/scripts/linux/appimage/soh.sh" DESTINATION . COMPONENT appimage) +install(PROGRAMS "${CMAKE_BINARY_DIR}/linux/soh.sh" DESTINATION . COMPONENT appimage) install(FILES "${CMAKE_SOURCE_DIR}/soh.otr" DESTINATION . COMPONENT ship) install(TARGETS ZAPD DESTINATION ./assets/extractor COMPONENT extractor) install(DIRECTORY "${CMAKE_SOURCE_DIR}/soh/assets/extractor/" DESTINATION ./assets/extractor COMPONENT extractor) @@ -124,7 +124,7 @@ add_custom_target( ExtractAssets # CMake versions prior to 3.17 do not have the rm command, use remove instead for older versions COMMAND ${CMAKE_COMMAND} -E $,remove,rm> -f oot.otr oot-mq.otr soh.otr - COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$" --non-interactive --xml-root ../soh/assets/xml --custom-otr-file soh.otr "--custom-assets-path" ${CMAKE_CURRENT_SOURCE_DIR}/soh/assets/custom + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$" --non-interactive --xml-root ../soh/assets/xml --custom-otr-file soh.otr "--custom-assets-path" ${CMAKE_CURRENT_SOURCE_DIR}/soh/assets/custom --port-ver "${CMAKE_PROJECT_VERSION}" COMMAND ${CMAKE_COMMAND} -DSYSTEM_NAME=${CMAKE_SYSTEM_NAME} -DTARGET_DIR="$" -DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -DBINARY_DIR=${CMAKE_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/copy-existing-otrs.cmake WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter COMMENT "Running asset extraction..." @@ -146,7 +146,7 @@ add_custom_target( GenerateSohOtr # CMake versions prior to 3.17 do not have the rm command, use remove instead for older versions COMMAND ${CMAKE_COMMAND} -E $,remove,rm> -f soh.otr - COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$" --norom --custom-otr-file soh.otr "--custom-assets-path" ${CMAKE_CURRENT_SOURCE_DIR}/soh/assets/custom + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$" --norom --custom-otr-file soh.otr "--custom-assets-path" ${CMAKE_CURRENT_SOURCE_DIR}/soh/assets/custom --port-ver "${CMAKE_PROJECT_VERSION}" COMMAND ${CMAKE_COMMAND} -DSYSTEM_NAME=${CMAKE_SYSTEM_NAME} -DTARGET_DIR="$" -DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -DBINARY_DIR=${CMAKE_BINARY_DIR} -DONLYSOHOTR=On -P ${CMAKE_CURRENT_SOURCE_DIR}/copy-existing-otrs.cmake WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter COMMENT "Generating soh.otr..." diff --git a/OTRExporter b/OTRExporter index f1bc0a726..cde9a3b65 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit f1bc0a726813d7e70ad471fdebd080e6fd77996a +Subproject commit cde9a3b655570370e4ed4928e8c9f3a0f631c52e diff --git a/scripts/linux/appimage/soh.sh b/scripts/linux/appimage/soh.sh.in similarity index 98% rename from scripts/linux/appimage/soh.sh rename to scripts/linux/appimage/soh.sh.in index 0e8414ac0..bfc3cfec6 100644 --- a/scripts/linux/appimage/soh.sh +++ b/scripts/linux/appimage/soh.sh.in @@ -171,7 +171,7 @@ while [[ (! -e "$SHIP_HOME"/oot.otr) || (! -e "$SHIP_HOME"/oot-mq.otr) ]]; do else echo "Processing..." fi - assets/extractor/ZAPD.out ed -eh -i assets/extractor/xmls/"${ROM}" -b tmp/rom.z64 -fl assets/extractor/filelists -o placeholder -osf placeholder -gsf 1 -rconf assets/extractor/Config_"${ROM}".xml -se OTR --otrfile "${OTRNAME}" > /dev/null 2>&1 + assets/extractor/ZAPD.out ed -eh -i assets/extractor/xmls/"${ROM}" -b tmp/rom.z64 -fl assets/extractor/filelists -o placeholder -osf placeholder -gsf 1 -rconf assets/extractor/Config_"${ROM}".xml -se OTR --otrfile "${OTRNAME}" --portVer "@CMAKE_PROJECT_VERSION@" > /dev/null 2>&1 cp "$ASSETDIR"/"$OTRNAME" "$SHIP_HOME" fi else diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index 647c491f8..fd0c6ac25 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -757,11 +757,16 @@ INSTALL(FILES $ DESTINATION ./debug COMPONENT ship) INSTALL(FILES ${CMAKE_BINARY_DIR}/soh/soh.otr DESTINATION . COMPONENT ship) endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/appimage/soh.sh.in ${CMAKE_BINARY_DIR}/linux/soh.sh @ONLY) +endif() + find_program(CURL NAMES curl DOC "Path to the curl program. Used to download files.") execute_process(COMMAND ${CURL} -sSfL https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt -o ${CMAKE_BINARY_DIR}/gamecontrollerdb.txt OUTPUT_VARIABLE RESULT) if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/macosx/Info.plist.in ${CMAKE_BINARY_DIR}/macosx/Info.plist @ONLY) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/macosx/soh-macos.sh.in ${CMAKE_BINARY_DIR}/macosx/soh-macos.sh @ONLY) INSTALL(FILES ${CMAKE_BINARY_DIR}/gamecontrollerdb.txt DESTINATION ../MacOS COMPONENT ship) INSTALL(FILES ${CMAKE_BINARY_DIR}/soh/soh.otr DESTINATION ../Resources COMPONENT ship) elseif(NOT "${CMAKE_SYSTEM_NAME}" MATCHES "NintendoSwitch|CafeOS") diff --git a/soh/include/variables.h b/soh/include/variables.h index 200080bfa..2bad8335c 100644 --- a/soh/include/variables.h +++ b/soh/include/variables.h @@ -46,9 +46,9 @@ extern "C" extern OSViMode osViModeFpalLan1; extern u32 __additional_scanline; extern u8 gBuildVersion[]; - extern s16 gBuildVersionMajor; - extern s16 gBuildVersionMinor; - extern s16 gBuildVersionPatch; + extern u16 gBuildVersionMajor; + extern u16 gBuildVersionMinor; + extern u16 gBuildVersionPatch; extern u8 gBuildTeam[]; extern u8 gBuildDate[]; extern u8 gBuildMakeOption[]; diff --git a/soh/macosx/soh-macos.sh b/soh/macosx/soh-macos.sh.in similarity index 99% rename from soh/macosx/soh-macos.sh rename to soh/macosx/soh-macos.sh.in index 4b5871892..177e309f4 100755 --- a/soh/macosx/soh-macos.sh +++ b/soh/macosx/soh-macos.sh.in @@ -229,7 +229,7 @@ if [ ! -e "$SHIP_HOME"/oot.otr ] || [ ! -e "$SHIP_HOME"/oot-mq.otr ]; then fi osascript -e 'display notification "Generating OTR..." with title "Ship Of Harkinian"' - assets/extractor/ZAPD.out ed -i assets/extractor/xmls/"${ROM}" -b tmp/rom.z64 -fl assets/extractor/filelists -o placeholder -osf placeholder -gsf 1 -rconf assets/extractor/Config_"${ROM}".xml -se OTR + assets/extractor/ZAPD.out ed -i assets/extractor/xmls/"${ROM}" -b tmp/rom.z64 -fl assets/extractor/filelists -o placeholder -osf placeholder -gsf 1 -rconf assets/extractor/Config_"${ROM}".xml -se OTR --portVer "@CMAKE_PROJECT_VERSION@" if [ -e "$ASSETDIR"/oot.otr ]; then osascript -e 'display notification "OTR successfully generated" with title "Ship Of Harkinian"' cp "$ASSETDIR"/oot.otr "$SHIP_HOME"/"$OTRNAME" diff --git a/soh/soh/Extractor/Extract.cpp b/soh/soh/Extractor/Extract.cpp index c79d26dad..4769c48f6 100644 --- a/soh/soh/Extractor/Extract.cpp +++ b/soh/soh/Extractor/Extract.cpp @@ -7,6 +7,7 @@ #include "Extract.h" #include "portable-file-dialogs.h" #include +#include "variables.h" #ifdef unix #include @@ -557,9 +558,10 @@ std::string Extractor::Mkdtemp() { extern "C" int zapd_main(int argc, char** argv); bool Extractor::CallZapd(std::string installPath, std::string exportdir) { - constexpr int argc = 16; + constexpr int argc = 18; char xmlPath[1024]; char confPath[1024]; + char portVersion[18]; // 5 digits for int16_max (x3) + separators + terminator std::array argv; const char* version = GetZapdVerStr(); const char* otrFile = IsMasterQuest() ? "oot-mq.otr" : "oot.otr"; @@ -581,6 +583,7 @@ bool Extractor::CallZapd(std::string installPath, std::string exportdir) { snprintf(xmlPath, 1024, "assets/extractor/xmls/%s", version); snprintf(confPath, 1024, "assets/extractor/Config_%s.xml", version); + snprintf(portVersion, 18, "%d.%d.%d", gBuildVersionMajor, gBuildVersionMinor, gBuildVersionPatch); argv[0] = "ZAPD"; argv[1] = "ed"; @@ -598,6 +601,8 @@ bool Extractor::CallZapd(std::string installPath, std::string exportdir) { argv[13] = "OTR"; argv[14] = "--otrfile"; argv[15] = otrFile; + argv[16] = "--portVer"; + argv[17] = portVersion; #ifdef _WIN32 // Grab a handle to the command window. @@ -606,6 +611,9 @@ bool Extractor::CallZapd(std::string installPath, std::string exportdir) { // Normally the command window is hidden. We want the window to be shown here so the user can see the progess of the extraction. ShowWindow(cmdWindow, SW_SHOW); SetWindowPos(cmdWindow, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); +#else + // Show extraction in background message until linux/mac can have visual progress + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Extracting", "Extraction will now begin in the background.\n\nPlease be patient for the process to finish. Do not close the main program.", nullptr); #endif zapd_main(argc, (char**)argv.data()); @@ -623,4 +631,3 @@ bool Extractor::CallZapd(std::string installPath, std::string exportdir) { return 0; } - diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index a0d785909..b96b364dd 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -66,6 +66,7 @@ #include #elif defined(__WIIU__) #include +#include // OSFatal #endif @@ -743,15 +744,156 @@ extern "C" void OTRExtScanner() { } } +typedef struct { + uint16_t major; + uint16_t minor; + uint16_t patch; +} OTRVersion; + +// Read the port version from an OTR file +OTRVersion ReadPortVersionFromOTR(std::string otrPath) { + OTRVersion version = {}; + + // Use a temporary archive instance to load the otr and read the version file + auto archive = std::make_shared(otrPath, "", std::unordered_set(), false); + if (archive->IsMainMPQValid()) { + auto t = archive->LoadFile("portVersion", false); + if (t != nullptr && t->IsLoaded) { + auto stream = std::make_shared(t->Buffer.data(), t->Buffer.size()); + auto reader = std::make_shared(stream); + LUS::Endianness endianness = (LUS::Endianness)reader->ReadUByte(); + reader->SetEndianness(endianness); + version.major = reader->ReadUInt16(); + version.minor = reader->ReadUInt16(); + version.patch = reader->ReadUInt16(); + } + } + + archive = nullptr; + + return version; +} + +// Check that a soh.otr exists and matches the version of soh running +// Otherwise show a message and exit +void CheckSoHOTRVersion(std::string otrPath) { + std::string msg; + +#if defined(__SWITCH__) + msg = "\x1b[4;2HPlease re-extract it from the download." + "\x1b[6;2HPress the Home button to exit..."; +#elif defined(__WIIU__) + msg = "Please extract the soh.otr from the Ship of Harkinian download\nto your folder.\n\nPress and hold the power button to shutdown..."; +#else + msg = "Please extract the soh.otr from the Ship of Harkinian download to your folder.\n\nExiting..."; +#endif + + if (!std::filesystem::exists(otrPath)) { +#if not defined(__SWITCH__) && not defined(__WIIU__) + Extractor::ShowErrorBox("soh.otr file is missing", msg.c_str()); + exit(1); +#elif defined(__SWITCH__) + LUS::Switch::PrintErrorMessageToScreen(("\x1b[2;2HYou are missing the soh.otr file." + msg).c_str()); +#elif defined(__WIIU__) + OSFatal(("You are missing the soh.otr file\n\n" + msg).c_str()); +#endif + } + + OTRVersion otrVersion = ReadPortVersionFromOTR(otrPath); + + if (otrVersion.major != gBuildVersionMajor || otrVersion.minor != gBuildVersionMinor || otrVersion.patch != gBuildVersionPatch) { +#if not defined(__SWITCH__) && not defined(__WIIU__) + Extractor::ShowErrorBox("soh.otr file version does not match", msg.c_str()); + exit(1); +#elif defined(__SWITCH__) + LUS::Switch::PrintErrorMessageToScreen(("\x1b[2;2HYou have an old soh.otr file." + msg).c_str()); +#elif defined(__WIIU__) + OSFatal(("You have an old soh.otr file\n\n" + msg).c_str()); +#endif + } +} + +// Checks the program version stored in the otr and compares the major value to soh +// For Windows/Mac/Linux if the version doesn't match, offer to +void DetectOTRVersion(std::string fileName, bool isMQ) { + bool isOtrOld = false; + std::string otrPath = LUS::Context::LocateFileAcrossAppDirs(fileName, appShortName); + + // Doesn't exist so nothing to do here + if (!std::filesystem::exists(otrPath)) { + return; + } + + OTRVersion otrVersion = ReadPortVersionFromOTR(otrPath); + + if (otrVersion.major != gBuildVersionMajor) { + isOtrOld = true; + } + + if (isOtrOld) { +#if not defined(__SWITCH__) && not defined(__WIIU__) + char msgBuf[250]; + char version[18]; // 5 digits for int16_max (x3) + separators + terminator + + if (otrVersion.major != 0 || otrVersion.minor != 0 || otrVersion.patch != 0) { + snprintf(version, 18, "%d.%d.%d", otrVersion.major, otrVersion.minor, otrVersion.patch); + } else { + snprintf(version, 18, "no version found"); + } + + snprintf(msgBuf, 250, + "The %s file was generated with a different version of Ship of Harkinian.\nOTR version: %s\n\n" + "You must regenerate to be able to play, otherwise the program will exit.\nWould you like to regenerate it now?", + fileName.c_str(), version); + + if (Extractor::ShowYesNoBox("Old OTR File Found", msgBuf) == IDYES) { + std::string installPath = LUS::Context::GetAppBundlePath(); + if (!std::filesystem::exists(installPath + "/assets/extractor")) { + Extractor::ShowErrorBox("Extractor assets not found", + "Unable to regenerate. Missing assets/extractor folder needed to generate OTR file.\n\nExiting..."); + exit(1); + } + + Extractor extract; + if (!extract.Run(isMQ ? RomSearchMode::MQ : RomSearchMode::Vanilla)) { + Extractor::ShowErrorBox("Error", "An error occured, no OTR file was generated.\n\nExiting..."); + exit(1); + } + extract.CallZapd(installPath, LUS::Context::GetAppDirectoryPath(appShortName)); + } else { + exit(1); + } + +#elif defined(__SWITCH__) + LUS::Switch::PrintErrorMessageToScreen("\x1b[2;2HYou've launched the Ship with an old game OTR file." + "\x1b[4;2HPlease regenerate a new game OTR and relaunch." + "\x1b[6;2HPress the Home button to exit..."); +#elif defined(__WIIU__) + OSFatal("You've launched the Ship with an old a game OTR file.\n\n" + "Please generate a game OTR and relaunch.\n\n" + "Press and hold the Power button to shutdown..."); +#endif + } +} + extern "C" void InitOTR() { -#if not defined (__SWITCH__) && not defined(__WIIU__) + +#ifdef __SWITCH__ + LUS::Switch::Init(LUS::PreInitPhase); +#elif defined(__WIIU__) + LUS::WiiU::Init(appShortName); +#endif + + CheckSoHOTRVersion(LUS::Context::GetPathRelativeToAppBundle("soh.otr")); + if (!std::filesystem::exists(LUS::Context::LocateFileAcrossAppDirs("oot-mq.otr", appShortName)) && !std::filesystem::exists(LUS::Context::LocateFileAcrossAppDirs("oot.otr", appShortName))){ +#if not defined(__SWITCH__) && not defined(__WIIU__) std::string installPath = LUS::Context::GetAppBundlePath(); if (!std::filesystem::exists(installPath + "/assets/extractor")) { Extractor::ShowErrorBox("Extractor assets not found", - "No OTR files found. Missing assets/extractor folder needed to generate OTR file. Exiting..."); + "No OTR files found. Missing assets/extractor folder needed to generate OTR file.\n\nExiting..."); exit(1); } @@ -759,7 +901,7 @@ extern "C" void InitOTR() { if (Extractor::ShowYesNoBox("No OTR Files", "No OTR files found. Generate one now?") == IDYES) { Extractor extract; if (!extract.Run()) { - Extractor::ShowErrorBox("Error", "An error occured, no OTR file was generated. Exiting..."); + Extractor::ShowErrorBox("Error", "An error occured, no OTR file was generated.\n\nExiting..."); exit(1); } extract.CallZapd(installPath, LUS::Context::GetAppDirectoryPath(appShortName)); @@ -770,19 +912,25 @@ extern "C" void InitOTR() { if (Extractor::ShowYesNoBox("Extraction Complete", "ROM Extracted. Extract another?") == IDYES) { Extractor extract; if (!extract.Run(generatedOtrIsMQ ? RomSearchMode::Vanilla : RomSearchMode::MQ)) { - Extractor::ShowErrorBox("Error", "An error occured, an OTR file may have been generated by a different step. Continuing..."); + Extractor::ShowErrorBox("Error", "An error occured, an OTR file may have been generated by a different step.\n\nContinuing..."); } else { extract.CallZapd(installPath, LUS::Context::GetAppDirectoryPath(appShortName)); } } - } -#endif -#ifdef __SWITCH__ - LUS::Switch::Init(LUS::PreInitPhase); +#elif defined(__SWITCH__) + LUS::Switch::PrintErrorMessageToScreen("\x1b[2;2HYou've launched the Ship without a game OTR file." + "\x1b[4;2HPlease generate a game OTR and relaunch." + "\x1b[6;2HPress the Home button to exit..."); #elif defined(__WIIU__) - LUS::WiiU::Init("soh"); + OSFatal("You've launched the Ship without a game OTR file.\n\n" + "Please generate a game OTR and relaunch.\n\n" + "Press and hold the Power button to shutdown..."); #endif + } + + DetectOTRVersion("oot.otr", false); + DetectOTRVersion("oot-mq.otr", true); OTRGlobals::Instance = new OTRGlobals(); CustomMessageManager::Instance = new CustomMessageManager(); @@ -2276,4 +2424,4 @@ extern "C" void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* repla extern "C" void CheckTracker_OnMessageClose() { CheckTracker::CheckTrackerDialogClosed(); -} \ No newline at end of file +} diff --git a/soh/src/boot/build.c.in b/soh/src/boot/build.c.in index f4c2899cf..4fc6a17f4 100644 --- a/soh/src/boot/build.c.in +++ b/soh/src/boot/build.c.in @@ -1,8 +1,9 @@ +#include + const char gBuildVersion[] = "@PROJECT_BUILD_NAME@ (@CMAKE_PROJECT_VERSION_MAJOR@.@CMAKE_PROJECT_VERSION_MINOR@.@CMAKE_PROJECT_VERSION_PATCH@)"; -const int gBuildVersionMajor = @CMAKE_PROJECT_VERSION_MAJOR@; -const int gBuildVersionMinor = @CMAKE_PROJECT_VERSION_MINOR@; -const int gBuildVersionPatch = @CMAKE_PROJECT_VERSION_PATCH@; +const u16 gBuildVersionMajor = @CMAKE_PROJECT_VERSION_MAJOR@; +const u16 gBuildVersionMinor = @CMAKE_PROJECT_VERSION_MINOR@; +const u16 gBuildVersionPatch = @CMAKE_PROJECT_VERSION_PATCH@; const char gBuildTeam[] = "@PROJECT_TEAM@"; const char gBuildDate[] = __DATE__ " " __TIME__; const char gBuildMakeOption[] = ""; -