From 7b08f98b8c85a29e4a0d73ecd2184c963332905e Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Sun, 16 Oct 2022 23:07:35 -0400 Subject: [PATCH] Dual OTR MQ and Vanilla Support (#1694) * Changes OTR Extraction to have specific mq and nonmq paths. Also updates the game to load resources according to whether or not Master Quest or Vanilla is loaded. * Removes unneeded code from the last commit. * Fixes some weird formatting in ZRom.c * Loads oot-mq.otr and patches oot.otr on top, if both are present. If only one or the other are present, it becomes the only and main OTR. * Adds ImGui Logic for whether or an MQ Checkbox. Checkbox checked only specifies whether new saves should be MQ or not. Checkbox is disabled or force-enabled according to which OTRs are loaded. Also as a necessity includes tracking what game versions have been loaded from the OTRs. * Adds MQ settings logic for Randomizer's ImGui menu. * Writes Master Quest dungeons to the spoiler log * Loads MQ Dungeons from spoiler, persists in save, and loads when appropriate. * Adds logic to prevent loading or creating incompatible rando saves. * Fixdes some linux build issues and new rando save issues * Makes appimage create both vanilla and mq otrs If either rom is present, it makes the corresponding OTR. If both are present, it will make both. If one OTR is present but both roms are present, it will create the missing OTR. * Makes it so a randomized save file will not be marked as MQ. * Refactors to load all OTRs from MainPath or a specific list. Also adds the ability to take a std::unordered_set of hashes to validate each OTR's version file against. * Fixes a syntax error * Makes ExtractAssets output Vanilla and MQ OTRs if both roms are present * Fixes asset generation bug. * Partially working fix for dual OTR extract_assets Currently the cmake ExtractAssets target will return with a 1 if you only end up exporting one type of OTR isntead of both. Haven't found a great way to only attempt to copy a file if it exists from within cmake. It does actually correctly copy the OTR that is generated, despite the error from copying the other one. Pushing as is for now but will keep investigating. * Adds oot-mq.otr to the gitignore. * Makes ExtractAssets not fail on only one rom/OTR. * Removes PatchesPath from the constructors requiring OTRFiles vector. * Renames OOT_UNKNOWN to just UNKNOWN to remove OOT specific reference. * Removes randomizing MQ Dungeons and re-disables MQ rando. Doing this so the PR can get merged quicker with just the Dual OTR support and won't need to wait on rando logic to be updated. That will happen in another PR directly after the merge. * Update mac startup script for dual otr * Update soh/macosx/soh-macos.sh * Update soh/macosx/soh-macos.sh * Update soh/macosx/soh-macos.sh * Implements new BinaryReader to fix Linux build issue. BinaryReader itself comes from https://github.com/Moneyl/BinaryTools I added a wrapper to adapt it to the ABI from ZAPD's Binary Reader and add Endianness checking. I also had to copy a handful of other bits and pieces from ZAPD to make it all function as expected. * A few edits to the updatream BinaryReader to compile it on Linux. * Adds the Endianness to the first byte of the version file. * Fixes Jenkins * Addresses some of Kenix's comments * Renames `ReadNullTerminatedString` to `ReadCString` * Refactors Archive::LoadFile into a private method with more arguments. * Removes BitConverter and extends existing endianness.h instead. * Fixes an endianness issue with the version file. Co-authored-by: Garrett Cox --- .gitignore | 1 + CMakeLists.txt | 9 +- .../OTRExporter/DisplayListExporter.cpp | 8 +- OTRExporter/OTRExporter/Main.cpp | 10 +- OTRExporter/extract_assets.py | 15 +- OTRExporter/rom_chooser.py | 20 +- OTRExporter/rom_info.py | 9 + ZAPDTR/ZAPD/ZResource.cpp | 5 +- ZAPDTR/ZAPD/ZRom.cpp | 28 +- ZAPDTR/ZAPD/ZRom.h | 1 + copy-existing-otrs.cmake | 11 + libultraship/libultraship/Animation.cpp | 2 +- libultraship/libultraship/Archive.cpp | 273 ++++++++++++----- libultraship/libultraship/Archive.h | 18 +- libultraship/libultraship/Audio.cpp | 40 +-- libultraship/libultraship/BinaryReader.cpp | 216 ++++++++++++++ libultraship/libultraship/BinaryReader.h | 61 ++++ libultraship/libultraship/CMakeLists.txt | 16 + libultraship/libultraship/CrashHandler.cpp | 2 +- libultraship/libultraship/Cutscene.cpp | 16 +- libultraship/libultraship/DisplayList.cpp | 2 +- .../libultraship/Factories/ResourceLoader.cpp | 7 +- libultraship/libultraship/GameVersions.h | 2 +- .../libultraship/Lib/BinaryTools/.gitignore | 276 ++++++++++++++++++ .../Lib/BinaryTools/BinaryTools.sln | 31 ++ .../Lib/BinaryTools/BinaryTools/Binary.cpp | 23 ++ .../Lib/BinaryTools/BinaryTools/Binary.h | 5 + .../BinaryTools/BinaryTools/BinaryReader.cpp | 266 +++++++++++++++++ .../BinaryTools/BinaryTools/BinaryReader.h | 64 ++++ .../BinaryTools/BinaryTools/BinaryTools.cpp | 162 ++++++++++ .../BinaryTools/BinaryTools.vcxproj | 177 +++++++++++ .../BinaryTools/BinaryTools.vcxproj.filters | 60 ++++ .../BinaryTools/BinaryTools/BinaryWriter.cpp | 183 ++++++++++++ .../BinaryTools/BinaryTools/BinaryWriter.h | 72 +++++ .../BinaryTools/BinaryTools/MemoryBuffer.h | 250 ++++++++++++++++ .../Lib/BinaryTools/BinaryTools/Span.h | 41 +++ .../Lib/BinaryTools/BinaryTools/TestBin1.bin | Bin 0 -> 20 bytes .../Lib/BinaryTools/BinaryTools/TestBin3.bin | Bin 0 -> 20 bytes .../libultraship/Lib/BinaryTools/License.txt | 21 ++ .../libultraship/Lib/BinaryTools/README.md | 80 +++++ libultraship/libultraship/Material.cpp | 4 +- libultraship/libultraship/Model.cpp | 2 +- libultraship/libultraship/Model.h | 1 + libultraship/libultraship/Path.h | 1 + libultraship/libultraship/Resource.cpp | 1 - libultraship/libultraship/Resource.h | 2 +- libultraship/libultraship/ResourceMgr.cpp | 29 +- libultraship/libultraship/ResourceMgr.h | 10 +- libultraship/libultraship/Scene.cpp | 94 +++--- libultraship/libultraship/Skeleton.cpp | 6 +- libultraship/libultraship/SkeletonLimb.cpp | 16 +- libultraship/libultraship/Window.cpp | 18 +- libultraship/libultraship/Window.h | 8 +- libultraship/libultraship/endianness.h | 26 ++ scripts/linux/appimage/soh.sh | 134 ++++++--- soh/include/z64save.h | 1 + soh/macosx/soh-macos.sh | 195 +++++++++---- soh/soh/Enhancements/bootcommands.c | 7 +- .../randomizer/3drando/settings.cpp | 7 + .../randomizer/3drando/spoiler_log.cpp | 21 +- .../Enhancements/randomizer/randomizer.cpp | 207 +++++++++---- soh/soh/Enhancements/randomizer/randomizer.h | 4 + .../Enhancements/randomizer/randomizerTypes.h | 2 + soh/soh/GameMenuBar.cpp | 34 +++ soh/soh/OTRGlobals.cpp | 188 ++++++++---- soh/soh/OTRGlobals.h | 14 +- soh/soh/SaveManager.cpp | 30 +- soh/soh/SaveManager.h | 3 +- soh/soh/z_play_otr.cpp | 8 +- soh/soh/z_scene_otr.cpp | 49 ++-- soh/src/code/z_sram.c | 5 +- .../ovl_file_choose/z_file_choose.c | 24 +- 72 files changed, 3149 insertions(+), 485 deletions(-) create mode 100644 copy-existing-otrs.cmake create mode 100644 libultraship/libultraship/BinaryReader.cpp create mode 100644 libultraship/libultraship/BinaryReader.h create mode 100644 libultraship/libultraship/Lib/BinaryTools/.gitignore create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools.sln create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/Binary.cpp create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/Binary.h create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryReader.cpp create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryReader.h create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.cpp create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.vcxproj create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.vcxproj.filters create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryWriter.cpp create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryWriter.h create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/MemoryBuffer.h create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/Span.h create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/TestBin1.bin create mode 100644 libultraship/libultraship/Lib/BinaryTools/BinaryTools/TestBin3.bin create mode 100644 libultraship/libultraship/Lib/BinaryTools/License.txt create mode 100644 libultraship/libultraship/Lib/BinaryTools/README.md diff --git a/.gitignore b/.gitignore index 820fd48b7..ff0a8688e 100644 --- a/.gitignore +++ b/.gitignore @@ -410,6 +410,7 @@ ReleaseObj/* .tags tags oot.otr +oot-mq.otr *.sav shipofharkinian.ini shipofharkinian.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 367360081..237ba294d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,14 +136,13 @@ find_package(Python3 COMPONENTS Interpreter) 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 - COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$" - COMMAND ${CMAKE_COMMAND} -E copy oot.otr ${CMAKE_SOURCE_DIR} - COMMAND ${CMAKE_COMMAND} -E copy oot.otr ${CMAKE_BINARY_DIR}/soh + COMMAND ${CMAKE_COMMAND} -E $,remove,rm> -f oot.otr oot-mq.otr + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$" --non-interactive + COMMAND ${CMAKE_COMMAND} -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..." DEPENDS ZAPD - BYPRODUCTS oot.otr ${CMAKE_SOURCE_DIR}/oot.otr + BYPRODUCTS oot.otr ${CMAKE_SOURCE_DIR}/oot.otr oot-mq.otr ${CMAKE_SOURCE_DIR}/oot-mq.otr ) if(CMAKE_SYSTEM_NAME MATCHES "Linux") diff --git a/OTRExporter/OTRExporter/DisplayListExporter.cpp b/OTRExporter/OTRExporter/DisplayListExporter.cpp index 9a9bd6350..54c29afe1 100644 --- a/OTRExporter/OTRExporter/DisplayListExporter.cpp +++ b/OTRExporter/OTRExporter/DisplayListExporter.cpp @@ -882,8 +882,14 @@ std::string OTRExporter_DisplayList::GetPrefix(ZResource* res) std::string prefix = ""; std::string xmlPath = StringHelper::Replace(res->parent->GetXmlFilePath().string(), "\\", "/"); - if (StringHelper::Contains(oName, "_scene") || StringHelper::Contains(oName, "_room")) + if (StringHelper::Contains(oName, "_scene") || StringHelper::Contains(oName, "_room")) { prefix = "scenes"; + if (Globals::Instance->rom->IsMQ()) { + prefix += "/mq"; + } else { + prefix += "/nonmq"; + } + } else if (StringHelper::Contains(xmlPath, "objects/")) prefix = "objects"; else if (StringHelper::Contains(xmlPath, "textures/")) diff --git a/OTRExporter/OTRExporter/Main.cpp b/OTRExporter/OTRExporter/Main.cpp index b9602966a..6a62a9ac4 100644 --- a/OTRExporter/OTRExporter/Main.cpp +++ b/OTRExporter/OTRExporter/Main.cpp @@ -21,6 +21,7 @@ #include #include #include +#include std::string otrFileName = "oot.otr"; std::shared_ptr otrArchive; @@ -69,13 +70,20 @@ static void ExporterProgramEnd() std::string romPath = Globals::Instance->baseRomPath.string(); std::vector romData = File::ReadAllBytes(romPath); uint32_t crc = BitConverter::ToUInt32BE(romData, 0x10); + uint8_t endianness = (uint8_t)Endianness::Big; // Write crc to version file fs::path versionPath("Extract/version"); + MemoryStream* versionStream = new MemoryStream(); + BinaryWriter writer(versionStream); + writer.SetEndianness(Endianness::Big); + writer.Write(endianness); + writer.Write(crc); std::ofstream versionFile(versionPath.c_str(), std::ios::out | std::ios::binary); - versionFile.write((char*)&crc, sizeof(crc)); + versionFile.write(versionStream->ToVector().data(), versionStream->GetLength()); versionFile.flush(); versionFile.close(); + writer.Close(); printf("Created version file.\n"); diff --git a/OTRExporter/extract_assets.py b/OTRExporter/extract_assets.py index ada474212..4e17d45a1 100755 --- a/OTRExporter/extract_assets.py +++ b/OTRExporter/extract_assets.py @@ -16,7 +16,8 @@ def BuildOTR(xmlPath, rom, zapd_exe=None): exec_cmd = [zapd_exe, "ed", "-i", xmlPath, "-b", rom, "-fl", "CFG/filelists", "-o", "placeholder", "-osf", "placeholder", "-gsf", "1", - "-rconf", "CFG/Config.xml", "-se", "OTR"] + "-rconf", "CFG/Config.xml", "-se", "OTR", "--otrfile", + "oot-mq.otr" if Z64Rom.isMqRom(rom) else "oot.otr"] print(exec_cmd) exitValue = subprocess.call(exec_cmd) @@ -30,16 +31,18 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument("-z", "--zapd", help="Path to ZAPD executable", dest="zapd_exe", type=str) parser.add_argument("rom", help="Path to the rom", type=str, nargs="?") + parser.add_argument("--non-interactive", help="Runs the script non-interactively for use in build scripts.", dest="non_interactive", action="store_true") args = parser.parse_args() - rom_path = args.rom if args.rom else rom_chooser.chooseROM() - rom = Z64Rom(rom_path) + rom_paths = [ args.rom ] if args.rom else rom_chooser.chooseROM(args.non_interactive) + for rom_path in rom_paths: + rom = Z64Rom(rom_path) - if (os.path.exists("Extract")): - shutil.rmtree("Extract") + if (os.path.exists("Extract")): + shutil.rmtree("Extract") - BuildOTR("../soh/assets/xml/" + rom.version.xml_ver + "/", rom_path, zapd_exe=args.zapd_exe) + BuildOTR("../soh/assets/xml/" + rom.version.xml_ver + "/", rom_path, zapd_exe=args.zapd_exe) if __name__ == "__main__": main() diff --git a/OTRExporter/rom_chooser.py b/OTRExporter/rom_chooser.py index 5c5f875d0..5e79add7e 100644 --- a/OTRExporter/rom_chooser.py +++ b/OTRExporter/rom_chooser.py @@ -2,7 +2,7 @@ import os, sys, glob from rom_info import Z64Rom -def chooseROM(): +def chooseROM(non_interactive=False): roms = [] for file in glob.glob("*.z64"): @@ -14,7 +14,21 @@ def chooseROM(): sys.exit(1) if (len(roms) == 1): - return roms[0] + return roms + + if non_interactive: + romsToExtract = [] + foundMq = False + foundOot = False + for rom in roms: + isMq = Z64Rom.isMqRom(rom) + if isMq and not foundMq: + romsToExtract.append(rom) + foundMq = True + elif not isMq and not foundOot: + romsToExtract.append(rom) + foundOot = True + return romsToExtract print(str(len(roms))+ " roms found, please select one by pressing 1-"+str(len(roms))) @@ -34,4 +48,4 @@ def chooseROM(): else: break - return roms[selection - 1] + return [ roms[selection - 1] ] diff --git a/OTRExporter/rom_info.py b/OTRExporter/rom_info.py index 93597281e..68114b2d0 100644 --- a/OTRExporter/rom_info.py +++ b/OTRExporter/rom_info.py @@ -73,6 +73,11 @@ class Z64Rom: if self.checksum == Checksums.OOT_UNKNOWN: self.is_valid = False return + + if self.checksum in [Checksums.OOT_NTSC_JP_MQ, Checksums.OOT_NTSC_US_MQ, Checksums.OOT_PAL_GC_MQ_DBG, Checksums.OOT_PAL_MQ]: + self.isMq = True + else: + self.isMq = False # get rom version self.version = ROM_INFO_TABLE[self.checksum] @@ -86,3 +91,7 @@ class Z64Rom: @staticmethod def isValidRom(rom_path): return Z64Rom(rom_path).is_valid + + @staticmethod + def isMqRom(rom_path): + return Z64Rom(rom_path).isMq diff --git a/ZAPDTR/ZAPD/ZResource.cpp b/ZAPDTR/ZAPD/ZResource.cpp index 7536fa019..d5c76ba27 100644 --- a/ZAPDTR/ZAPD/ZResource.cpp +++ b/ZAPDTR/ZAPD/ZResource.cpp @@ -328,7 +328,7 @@ std::string ZResource::GetSourceOutputHeader([[maybe_unused]] const std::string& std::string xmlPath = StringHelper::Replace(parent->GetXmlFilePath().string(), "\\", "/"); if (StringHelper::Contains(outName, "_room_") || StringHelper::Contains(outName, "_scene")) - prefix = "scenes"; + prefix = "scenes/nonmq"; else if (StringHelper::Contains(xmlPath, "objects/")) prefix = "objects"; else if (StringHelper::Contains(xmlPath, "textures/")) @@ -342,8 +342,9 @@ std::string ZResource::GetSourceOutputHeader([[maybe_unused]] const std::string& else if (StringHelper::Contains(xmlPath, "text/")) prefix = "text"; - if (prefix != "") + if (prefix != "") { str += StringHelper::Sprintf("#define d%s \"__OTR__%s/%s/%s\"", name.c_str(), prefix.c_str(), outName.c_str(), nameStr.c_str()); + } else str += StringHelper::Sprintf("#define d%s \"__OTR__%s/%s\"", name.c_str(), outName.c_str(), nameStr.c_str()); diff --git a/ZAPDTR/ZAPD/ZRom.cpp b/ZAPDTR/ZAPD/ZRom.cpp index f13a6e2f8..5dfad57ae 100644 --- a/ZAPDTR/ZAPD/ZRom.cpp +++ b/ZAPDTR/ZAPD/ZRom.cpp @@ -64,7 +64,33 @@ namespace fs = std::filesystem; #define OOT_PAL_GC_MQ_DBG 0x917D18F6 #define OOT_IQUE_TW 0x3D81FB3E #define OOT_IQUE_CN 0xB1E1E07B -#define OOT_UNKNOWN 0xFFFFFFFF +#define UNKNOWN 0xFFFFFFFF + +bool ZRom::IsMQ() { + int crc = BitConverter::ToInt32BE(romData, 0x10); + switch (crc) { + case OOT_NTSC_10: + case OOT_NTSC_11: + case OOT_NTSC_12: + case OOT_PAL_10: + case OOT_PAL_11: + case OOT_NTSC_JP_GC: + case OOT_NTSC_JP_GC_CE: + case OOT_NTSC_US_GC: + case OOT_PAL_GC: + case OOT_PAL_GC_DBG1: + case OOT_PAL_GC_DBG2: + case OOT_IQUE_CN: + case OOT_IQUE_TW: + default: + return false; + case OOT_NTSC_JP_MQ: + case OOT_NTSC_US_MQ: + case OOT_PAL_MQ: + case OOT_PAL_GC_MQ_DBG: + return true; + } +} ZRom::ZRom(std::string romPath) { diff --git a/ZAPDTR/ZAPD/ZRom.h b/ZAPDTR/ZAPD/ZRom.h index ae3cac156..6225a1ada 100644 --- a/ZAPDTR/ZAPD/ZRom.h +++ b/ZAPDTR/ZAPD/ZRom.h @@ -11,6 +11,7 @@ public: ZRom(std::string romPath); std::vector GetFile(std::string fileName); + bool IsMQ(); protected: std::vector romData; diff --git a/copy-existing-otrs.cmake b/copy-existing-otrs.cmake new file mode 100644 index 000000000..b3510830d --- /dev/null +++ b/copy-existing-otrs.cmake @@ -0,0 +1,11 @@ +if(EXISTS ${SOURCE_DIR}/OTRExporter/oot.otr) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy oot.otr ${SOURCE_DIR}) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy oot.otr ${BINARY_DIR}/soh/) +endif() +if(EXISTS ${SOURCE_DIR}/OTRExporter/oot-mq.otr) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy oot-mq.otr ${SOURCE_DIR}) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy oot-mq.otr ${BINARY_DIR}/soh/) +endif() +if(NOT EXISTS ${SOURCE_DIR}/oot.otr AND NOT EXISTS ${SOURCE_DIR}/oot-mq.otr) + message(FATAL_ERROR, "No OTR files found.") +endif() \ No newline at end of file diff --git a/libultraship/libultraship/Animation.cpp b/libultraship/libultraship/Animation.cpp index f9a6e2ee8..ef7608d1f 100644 --- a/libultraship/libultraship/Animation.cpp +++ b/libultraship/libultraship/Animation.cpp @@ -48,7 +48,7 @@ void Ship::AnimationV0::ParseFileBinary(BinaryReader* reader, Resource* res) data.unk_02 = reader->ReadInt16(); data.unk_04 = reader->ReadInt16(); data.unk_06 = reader->ReadInt16(); - data.unk_08 = reader->ReadSingle(); + data.unk_08 = reader->ReadFloat(); anim->transformDataArr.push_back(data); } diff --git a/libultraship/libultraship/Archive.cpp b/libultraship/libultraship/Archive.cpp index 2740d9d78..fb4890c16 100644 --- a/libultraship/libultraship/Archive.cpp +++ b/libultraship/libultraship/Archive.cpp @@ -1,27 +1,38 @@ #include "Archive.h" #include "Resource.h" #include "File.h" +#include "Window.h" +#include "ResourceMgr.h" #include "spdlog/spdlog.h" #include "Utils/StringHelper.h" #include "Lib/StrHash64.h" #include +#include "Lib/BinaryTools/BinaryTools/BinaryReader.h" #ifdef __SWITCH__ #include "SwitchImpl.h" #endif namespace Ship { - Archive::Archive(const std::string& MainPath, bool enableWriting) : Archive(MainPath, "", enableWriting) + Archive::Archive(const std::string& MainPath, bool enableWriting) : Archive(MainPath, "", std::unordered_set(), enableWriting) { mainMPQ = nullptr; } - Archive::Archive(const std::string& MainPath, const std::string& PatchesPath, bool enableWriting, bool genCRCMap) : MainPath(MainPath), PatchesPath(PatchesPath) { + Archive::Archive(const std::string& MainPath, const std::string& PatchesPath, const std::unordered_set& ValidHashes, bool enableWriting, bool genCRCMap) + : MainPath(MainPath), PatchesPath(PatchesPath), OTRFiles({}), ValidHashes(ValidHashes) { mainMPQ = nullptr; Load(enableWriting, genCRCMap); } - Archive::~Archive() { + Archive::Archive(const std::vector &OTRFiles, const std::unordered_set &ValidHashes, bool enableWriting, bool genCRCMap) + : OTRFiles(OTRFiles), ValidHashes(ValidHashes) + { + mainMPQ = nullptr; + Load(enableWriting, genCRCMap); + } + + Archive::~Archive() { Unload(); } @@ -55,48 +66,63 @@ namespace Ship { } } + std::shared_ptr Archive::LoadFileFromHandle(const std::string& filePath, bool includeParent, std::shared_ptr FileToLoad, HANDLE mpqHandle) + { + HANDLE fileHandle = NULL; + + if (FileToLoad == nullptr) + { + FileToLoad = std::make_shared(); + FileToLoad->path = filePath; + } + + if (mpqHandle == nullptr) + { + mpqHandle = mainMPQ; + } + + bool attempt = SFileOpenFileEx(mpqHandle, filePath.c_str(), 0, &fileHandle); + + if (!attempt) + { + SPDLOG_ERROR("({}) Failed to open file {} from mpq archive {}.", GetLastError(), filePath.c_str(), MainPath.c_str()); + std::unique_lock Lock(FileToLoad->FileLoadMutex); + FileToLoad->bHasLoadError = true; + return FileToLoad; + } + + DWORD dwFileSize = SFileGetFileSize(fileHandle, 0); + std::shared_ptr fileData(new char[dwFileSize]); + DWORD dwBytes; + + if (!SFileReadFile(fileHandle, fileData.get(), dwFileSize, &dwBytes, NULL)) + { + SPDLOG_ERROR("({}) Failed to read file {} from mpq archive {}", GetLastError(), filePath.c_str(), MainPath.c_str()); + if (!SFileCloseFile(fileHandle)) + { + SPDLOG_ERROR("({}) Failed to close file {} from mpq after read failure in archive {}", GetLastError(), filePath.c_str(), MainPath.c_str()); + } + std::unique_lock Lock(FileToLoad->FileLoadMutex); + FileToLoad->bHasLoadError = true; + return FileToLoad; + } + + if (!SFileCloseFile(fileHandle)) + { + SPDLOG_ERROR("({}) Failed to close file {} from mpq archive {}", GetLastError(), filePath.c_str(), MainPath.c_str()); + } + + std::unique_lock Lock(FileToLoad->FileLoadMutex); + FileToLoad->parent = includeParent ? shared_from_this() : nullptr; + FileToLoad->buffer = fileData; + FileToLoad->dwBufferSize = dwFileSize; + FileToLoad->bIsLoaded = true; + + return FileToLoad; + } + std::shared_ptr Archive::LoadFile(const std::string& filePath, bool includeParent, std::shared_ptr FileToLoad) { - HANDLE fileHandle = NULL; - - if (FileToLoad == nullptr) { - FileToLoad = std::make_shared(); - FileToLoad->path = filePath; - } - - bool attempt = SFileOpenFileEx(mainMPQ, filePath.c_str(), 0, &fileHandle); - - if (!attempt) { - SPDLOG_ERROR("({}) Failed to open file {} from mpq archive {}.", GetLastError(), filePath.c_str(), MainPath.c_str()); - std::unique_lock Lock(FileToLoad->FileLoadMutex); - FileToLoad->bHasLoadError = true; - return FileToLoad; - } - - DWORD dwFileSize = SFileGetFileSize(fileHandle, 0); - std::shared_ptr fileData(new char[dwFileSize]); - DWORD dwBytes; - - if (!SFileReadFile(fileHandle, fileData.get(), dwFileSize, &dwBytes, NULL)) { - SPDLOG_ERROR("({}) Failed to read file {} from mpq archive {}", GetLastError(), filePath.c_str(), MainPath.c_str()); - if (!SFileCloseFile(fileHandle)) { - SPDLOG_ERROR("({}) Failed to close file {} from mpq after read failure in archive {}", GetLastError(), filePath.c_str(), MainPath.c_str()); - } - std::unique_lock Lock(FileToLoad->FileLoadMutex); - FileToLoad->bHasLoadError = true; - return FileToLoad; - } - - if (!SFileCloseFile(fileHandle)) { - SPDLOG_ERROR("({}) Failed to close file {} from mpq archive {}", GetLastError(), filePath.c_str(), MainPath.c_str()); - } - - std::unique_lock Lock(FileToLoad->FileLoadMutex); - FileToLoad->parent = includeParent ? shared_from_this() : nullptr; - FileToLoad->buffer = fileData; - FileToLoad->dwBufferSize = dwFileSize; - FileToLoad->bIsLoaded = true; - - return FileToLoad; + return LoadFileFromHandle(filePath, includeParent, FileToLoad, nullptr); } std::shared_ptr Archive::LoadPatchFile(const std::string& filePath, bool includeParent, std::shared_ptr FileToLoad) { @@ -320,53 +346,129 @@ namespace Ship { return true; } + void Archive::GenerateCRCMap() { + auto listFile = LoadFile("(listfile)", false); + + std::vector lines = StringHelper::Split(std::string(listFile->buffer.get(), listFile->dwBufferSize), "\n"); + + for (size_t i = 0; i < lines.size(); i++) + { + std::string line = StringHelper::Replace(StringHelper::Strip(lines[i], "\r"), "/", "\\"); + std::string line2 = StringHelper::Replace(line, "\\", "/"); + + uint64_t hash = CRC64(StringHelper::Replace(line, "/", "\\").c_str()); + uint64_t hash2 = CRC64(StringHelper::Replace(line, "\\", "/").c_str()); + hashes[hash] = line; + hashes[hash2] = line2; + } + } + + bool Archive::PushGameVersion(HANDLE mpqHandle) { + auto t = LoadFileFromHandle("version", false, nullptr, mpqHandle); + if (!t->bHasLoadError) + { + BinaryReader *reader = new BinaryReader(t->buffer.get(), t->dwBufferSize); + Ship::Endianness endianness = (Ship::Endianness)reader->ReadUByte(); + reader->SetEndianness(endianness); + uint32_t version = reader->ReadUInt32(); + if (ValidHashes.empty() || ValidHashes.contains(version)) { + gameVersions.push_back(version); + return true; + } + } + return false; + } + bool Archive::LoadMainMPQ(bool enableWriting, bool genCRCMap) { - HANDLE mpqHandle = NULL; + HANDLE mpqHandle = NULL; + if (OTRFiles.empty()) { + if (MainPath.length() > 0) { + if (std::filesystem::is_directory(MainPath)) { + for (const auto &p : std::filesystem::recursive_directory_iterator(MainPath)) { + if (StringHelper::IEquals(p.path().extension().string(), ".otr")) { + SPDLOG_ERROR("Reading {} mpq", p.path().string().c_str()); + OTRFiles.push_back(p.path().string()); + } + } + } else if (std::filesystem::is_regular_file(MainPath)) { + OTRFiles.push_back(MainPath); + } else { + SPDLOG_ERROR("The directory {} does not exist", MainPath.c_str()); + return false; + } + } else { + SPDLOG_ERROR("No OTR file list or Main Path provided."); + return false; + } + if (OTRFiles.empty()) { + SPDLOG_ERROR("No OTR files present in {}", MainPath.c_str()); + return false; + } + } + bool baseLoaded = false; + int i = 0; + while (!baseLoaded && i < OTRFiles.size()) { #ifdef _WIN32 - std::wstring wfullPath = std::filesystem::absolute(MainPath).wstring(); + std::wstring wfullPath = std::filesystem::absolute(OTRFiles[i]).wstring(); #endif #if defined(__SWITCH__) - std::string fullPath = MainPath; -#else - std::string fullPath = std::filesystem::absolute(MainPath).string(); + std::string fullPath = OTRFiles[0]; +#else + std::string fullPath = std::filesystem::absolute(OTRFiles[i]).string(); #endif - #ifdef _WIN32 - if (!SFileOpenArchive(wfullPath.c_str(), 0, enableWriting ? 0 : MPQ_OPEN_READ_ONLY, &mpqHandle)) { + if (SFileOpenArchive(wfullPath.c_str(), 0, enableWriting ? 0 : MPQ_OPEN_READ_ONLY, &mpqHandle)) + { #else - if (!SFileOpenArchive(fullPath.c_str(), 0, enableWriting ? 0 : MPQ_OPEN_READ_ONLY, &mpqHandle)) { + if (SFileOpenArchive(fullPath.c_str(), 0, enableWriting ? 0 : MPQ_OPEN_READ_ONLY, &mpqHandle)) + { #endif - - #ifdef __SWITCH__ - Switch::ThrowMissingOTR(fullPath); - #endif - SPDLOG_ERROR("({}) Failed to open main mpq file {}.", GetLastError(), fullPath.c_str()); - return false; - } - - mpqHandles[fullPath] = mpqHandle; - mainMPQ = mpqHandle; - - if (genCRCMap) { - auto listFile = LoadFile("(listfile)", false); - - std::vector lines = StringHelper::Split(std::string(listFile->buffer.get(), listFile->dwBufferSize), "\n"); - - for (size_t i = 0; i < lines.size(); i++) { - std::string line = StringHelper::Replace(StringHelper::Strip(lines[i], "\r"), "/", "\\"); - std::string line2 = StringHelper::Replace(line, "\\", "/"); - - uint64_t hash = CRC64(StringHelper::Replace(line, "/", "\\").c_str()); - uint64_t hash2 = CRC64(StringHelper::Replace(line, "\\", "/").c_str()); - hashes[hash] = line; - hashes[hash2] = line2; - } - } - + SPDLOG_INFO("Opened mpq file {}.", fullPath.c_str()); + mainMPQ = mpqHandle; + if (!PushGameVersion()) { + SPDLOG_WARN("Attempted to load invalid OTR file {}", OTRFiles[i].c_str()); + SFileCloseArchive(mpqHandle); + mainMPQ = nullptr; + } else { + mpqHandles[fullPath] = mpqHandle; + if (genCRCMap) + { + GenerateCRCMap(); + } + baseLoaded = true; + } + } + i++; + } + // If we exited the above loop without setting baseLoaded to true, then we've + // attemtped to load all the OTRs available to us. + if (!baseLoaded) { + SPDLOG_ERROR("No valid OTR file was provided."); + return false; + } + for (int j = i; j < OTRFiles.size(); j++) { +#ifdef _WIN32 + std::wstring wfullPath = std::filesystem::absolute(OTRFiles[i]).wstring(); +#endif +#if defined(__SWITCH__) + std::string fullPath = OTRFiles[i]; +#else + std::string fullPath = std::filesystem::absolute(OTRFiles[i]).string(); +#endif + if (LoadPatchMPQ(fullPath, true)) + { + SPDLOG_INFO("({}) Patched in mpq file.", fullPath.c_str()); + } + if (genCRCMap) + { + GenerateCRCMap(); + } + } + return true; } - bool Archive::LoadPatchMPQ(const std::string& path) { + bool Archive::LoadPatchMPQ(const std::string& path, bool validateVersion) { HANDLE patchHandle = NULL; #if defined(__SWITCH__) std::string fullPath = path; @@ -386,7 +488,16 @@ namespace Ship { #endif SPDLOG_ERROR("({}) Failed to open patch mpq file {} while applying to {}.", GetLastError(), path.c_str(), MainPath.c_str()); return false; - } + } else { + // We don't always want to validate the "version" file, only when we're loading standalone OTRs as patches + // i.e. Ocarina of Time along with Master Quest. + if (validateVersion) { + if (!PushGameVersion(patchHandle)) { + SPDLOG_WARN("({}) Invalid MQP file.", path.c_str()); + return false; + } + } + } #ifdef _WIN32 if (!SFileOpenPatchArchive(mainMPQ, wPath.c_str(), "", 0)) { #else diff --git a/libultraship/libultraship/Archive.h b/libultraship/libultraship/Archive.h index 1cd6fb78e..3a3568777 100644 --- a/libultraship/libultraship/Archive.h +++ b/libultraship/libultraship/Archive.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "Resource.h" #include "StormLib.h" @@ -21,8 +22,9 @@ namespace Ship { public: Archive(const std::string& MainPath, bool enableWriting); - Archive(const std::string& MainPath, const std::string& PatchesPath, bool enableWriting, bool genCRCMap = true); - ~Archive(); + Archive(const std::string &MainPath, const std::string &PatchesPath, const std::unordered_set &ValidHashes, bool enableWriting, bool genCRCMap = true); + Archive(const std::vector &OTRFiles, const std::unordered_set &ValidHashes, bool enableWriting, bool genCRCMap = true); + ~Archive(); bool IsMainMPQValid(); @@ -37,19 +39,25 @@ namespace Ship std::vector ListFiles(const std::string& searchMask) const; bool HasFile(const std::string& searchMask) const; const std::string* HashToString(uint64_t hash) const; + std::vector gameVersions; protected: bool Load(bool enableWriting, bool genCRCMap); bool Unload(); private: std::string MainPath; std::string PatchesPath; - std::map mpqHandles; + std::vector OTRFiles; + std::unordered_set ValidHashes; + std::map mpqHandles; std::vector addedFiles; std::unordered_map hashes; HANDLE mainMPQ; bool LoadMainMPQ(bool enableWriting, bool genCRCMap); bool LoadPatchMPQs(); - bool LoadPatchMPQ(const std::string& path); - }; + bool LoadPatchMPQ(const std::string& path, bool validateVersion = false); + void GenerateCRCMap(); + bool PushGameVersion(HANDLE mpqHandle = nullptr); + std::shared_ptr LoadFileFromHandle(const std::string &filePath, bool includeParent = true, std::shared_ptr FileToLoad = nullptr, HANDLE mpqHandle = nullptr); + }; } \ No newline at end of file diff --git a/libultraship/libultraship/Audio.cpp b/libultraship/libultraship/Audio.cpp index 6afe90c53..05436fa7c 100644 --- a/libultraship/libultraship/Audio.cpp +++ b/libultraship/libultraship/Audio.cpp @@ -31,10 +31,10 @@ namespace Ship ResourceFile::ParseFileBinary(reader, res); - entry->codec = reader->ReadByte(); - entry->medium = reader->ReadByte(); - entry->unk_bit26 = reader->ReadByte(); - entry->unk_bit25 = reader->ReadByte(); + entry->codec = reader->ReadInt8(); + entry->medium = reader->ReadInt8(); + entry->unk_bit26 = reader->ReadInt8(); + entry->unk_bit25 = reader->ReadInt8(); uint32_t dataSize = reader->ReadInt32(); @@ -66,8 +66,8 @@ namespace Ship ResourceFile::ParseFileBinary(reader, res); soundFont->id = reader->ReadInt32(); - soundFont->medium = reader->ReadByte(); - soundFont->cachePolicy = reader->ReadByte(); + soundFont->medium = reader->ReadInt8(); + soundFont->cachePolicy = reader->ReadInt8(); soundFont->data1 = reader->ReadInt16(); soundFont->data2 = reader->ReadInt16(); soundFont->data3 = reader->ReadInt16(); @@ -85,9 +85,9 @@ namespace Ship drum.env = ReadEnvelopeData(reader); - bool hasSample = reader->ReadByte(); + bool hasSample = reader->ReadInt8(); drum.sampleFileName = reader->ReadString(); - drum.tuning = reader->ReadSingle(); + drum.tuning = reader->ReadFloat(); soundFont->drums.push_back(drum); } @@ -105,38 +105,38 @@ namespace Ship entry.env = ReadEnvelopeData(reader); { - bool hasSFEntry = reader->ReadByte(); + bool hasSFEntry = reader->ReadInt8(); if (hasSFEntry) { entry.lowNotesSound = new SoundFontEntry(); - bool hasSampleRef = reader->ReadByte(); + bool hasSampleRef = reader->ReadInt8(); entry.lowNotesSound->sampleFileName = reader->ReadString(); - entry.lowNotesSound->tuning = reader->ReadSingle(); + entry.lowNotesSound->tuning = reader->ReadFloat(); } } { - bool hasSFEntry = reader->ReadByte(); + bool hasSFEntry = reader->ReadInt8(); if (hasSFEntry) { entry.normalNotesSound = new SoundFontEntry(); - bool hasSampleRef = reader->ReadByte(); + bool hasSampleRef = reader->ReadInt8(); entry.normalNotesSound->sampleFileName = reader->ReadString(); - entry.normalNotesSound->tuning = reader->ReadSingle(); + entry.normalNotesSound->tuning = reader->ReadFloat(); } } { - bool hasSFEntry = reader->ReadByte(); + bool hasSFEntry = reader->ReadInt8(); if (hasSFEntry) { entry.highNotesSound = new SoundFontEntry(); - bool hasSampleRef = reader->ReadByte(); + bool hasSampleRef = reader->ReadInt8(); entry.highNotesSound->sampleFileName = reader->ReadString(); - entry.highNotesSound->tuning = reader->ReadSingle(); + entry.highNotesSound->tuning = reader->ReadFloat(); } } @@ -147,13 +147,13 @@ namespace Ship { SoundFontEntry* entry = new SoundFontEntry(); - bool hasSFEntry = reader->ReadByte(); + bool hasSFEntry = reader->ReadInt8(); if (hasSFEntry) { - bool hasSampleRef = reader->ReadByte(); + bool hasSampleRef = reader->ReadInt8(); entry->sampleFileName = reader->ReadString(); - entry->tuning = reader->ReadSingle(); + entry->tuning = reader->ReadFloat(); } soundFont->soundEffects.push_back(entry); diff --git a/libultraship/libultraship/BinaryReader.cpp b/libultraship/libultraship/BinaryReader.cpp new file mode 100644 index 000000000..a970a0c09 --- /dev/null +++ b/libultraship/libultraship/BinaryReader.cpp @@ -0,0 +1,216 @@ +#include "BinaryReader.h" +#include +#include "Lib/BinaryTools/BinaryTools/BinaryReader.h" + +Ship::BinaryReader::BinaryReader(char* buffer, uint32_t size) +{ + this->buffer = std::vector(buffer, buffer + size); + this->reader = std::make_shared<::BinaryReader>(this->buffer.data(), size); +} + +void Ship::BinaryReader::SetEndianness(Endianness endianness) +{ + this->endianness = endianness; +} + +Ship::Endianness Ship::BinaryReader::GetEndianness() const +{ + return endianness; +} + +void Ship::BinaryReader::Seek(int32_t offset, SeekOffsetType seekType) { + switch(seekType) { + case SeekOffsetType::Current: + if (offset < 0) { + reader->SeekReverse(-1 * offset); + break; + } + reader->SeekCur(offset); + break; + case SeekOffsetType::Start: + reader->SeekBeg(offset); + break; + case SeekOffsetType::End: + reader->SeekReverse(offset); + break; + } +} + +uint32_t Ship::BinaryReader::GetBaseAddress() { + return reader->Position(); +} + +void Ship::BinaryReader::Read(int32_t length) +{ + reader->ReadFixedLengthString(length); +} + +void Ship::BinaryReader::Read(char *buffer, int32_t length) +{ + reader->ReadToMemory(buffer, length); +} + +char Ship::BinaryReader::ReadChar() +{ + return reader->ReadChar(); +} + +int8_t Ship::BinaryReader::ReadInt8() +{ + return reader->ReadInt8(); +} + +int16_t Ship::BinaryReader::ReadInt16() +{ + int16_t result = 0; + this->Read((char*)&result, sizeof(int16_t)); + if (endianness != Endianness::Native) + result = BSWAP16(result); + + return result; +} + +int32_t Ship::BinaryReader::ReadInt32() +{ + int32_t result = 0; + + this->Read((char *)&result, sizeof(int32_t)); + + if (endianness != Endianness::Native) + result = BSWAP32(result); + + return result; +} + +uint8_t Ship::BinaryReader::ReadUByte() +{ + return reader->ReadUint8(); +} + +uint16_t Ship::BinaryReader::ReadUInt16() +{ + uint16_t result = 0; + + this->Read((char *)&result, sizeof(uint16_t)); + + if (endianness != Endianness::Native) + result = BSWAP16(result); + + return result; +} + +uint32_t Ship::BinaryReader::ReadUInt32() +{ + uint32_t result = 0; + + this->Read((char *)&result, sizeof(uint32_t)); + + if (endianness != Endianness::Native) + result = BSWAP32(result); + + return result; +} + +uint64_t Ship::BinaryReader::ReadUInt64() +{ + uint64_t result = 0; + + this->Read((char *)&result, sizeof(uint64_t)); + + if (endianness != Endianness::Native) + result = BSWAP64(result); + + return result; +} + +float Ship::BinaryReader::ReadFloat() +{ + float result = NAN; + + this->Read((char *)&result, sizeof(float)); + + if (endianness != Endianness::Native) + { + float tmp; + char *dst = (char *)&tmp; + char *src = (char *)&result; + dst[3] = src[0]; + dst[2] = src[1]; + dst[1] = src[2]; + dst[0] = src[3]; + result = tmp; + } + + if (std::isnan(result)) + throw std::runtime_error("BinaryReader::ReadSingle(): Error reading stream"); + + return result; +} + +double Ship::BinaryReader::ReadDouble() +{ + double result = NAN; + + this->Read((char *)&result, sizeof(double)); + + if (endianness != Endianness::Native) + { + double tmp; + char *dst = (char *)&tmp; + char *src = (char *)&result; + dst[7] = src[0]; + dst[6] = src[1]; + dst[5] = src[2]; + dst[4] = src[3]; + dst[3] = src[4]; + dst[2] = src[5]; + dst[1] = src[6]; + dst[0] = src[7]; + result = tmp; + } + + if (std::isnan(result)) + throw std::runtime_error("BinaryReader::ReadDouble(): Error reading stream"); + + return result; +} + +Vec3f Ship::BinaryReader::ReadVec3f() +{ + return Vec3f(); +} + +Vec3s Ship::BinaryReader::ReadVec3s() +{ + return Vec3s(0, 0, 0); +} + +Vec3s Ship::BinaryReader::ReadVec3b() +{ + return Vec3s(0, 0, 0); +} + +Vec2f Ship::BinaryReader::ReadVec2f() +{ + return Vec2f(); +} + +Color3b Ship::BinaryReader::ReadColor3b() +{ + return Color3b(); +} + +std::string Ship::BinaryReader::ReadString() +{ + std::string res; + int numChars = reader->ReadInt32(); + for (int i = 0; i < numChars; i++) { + res += reader->ReadChar(); + } + return res; +} + +std::string Ship::BinaryReader::ReadCString() +{ + return reader->ReadNullTerminatedString(); +} \ No newline at end of file diff --git a/libultraship/libultraship/BinaryReader.h b/libultraship/libultraship/BinaryReader.h new file mode 100644 index 000000000..953922a07 --- /dev/null +++ b/libultraship/libultraship/BinaryReader.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include "endianness.h" +#include "Vec2f.h" +#include "Vec3f.h" +#include "Vec3s.h" +#include "Color3b.h" + +class BinaryReader; + +namespace Ship { + +enum class SeekOffsetType +{ + Start, + Current, + End +}; + +class BinaryReader +{ +public: + BinaryReader(char* buffer, uint32_t size); + + void SetEndianness(Endianness endianness); + Endianness GetEndianness() const; + + void Seek(int32_t offset, SeekOffsetType seekType); + uint32_t GetBaseAddress(); + + void Read(int32_t length); + void Read(char *buffer, int32_t length); + char ReadChar(); + int8_t ReadInt8(); + int16_t ReadInt16(); + int32_t ReadInt32(); + uint8_t ReadUByte(); + uint16_t ReadUInt16(); + uint32_t ReadUInt32(); + uint64_t ReadUInt64(); + float ReadFloat(); + double ReadDouble(); + Vec3f ReadVec3f(); + Vec3s ReadVec3s(); + Vec3s ReadVec3b(); + Vec2f ReadVec2f(); + Color3b ReadColor3b(); + std::string ReadString(); + std::string ReadCString(); + +protected: + Endianness endianness = Endianness::Native; + +private: + std::vector buffer; + std::shared_ptr<::BinaryReader> reader; +}; +} \ No newline at end of file diff --git a/libultraship/libultraship/CMakeLists.txt b/libultraship/libultraship/CMakeLists.txt index c00e6ec2b..f4090cef5 100644 --- a/libultraship/libultraship/CMakeLists.txt +++ b/libultraship/libultraship/CMakeLists.txt @@ -77,6 +77,21 @@ endif () source_group("Source Files\\Audio" FILES ${Source_Files__Audio} ${Source_Files__Audio__extra}) +set (Source_Files__BinaryTools + "BinaryReader.cpp" + "BinaryReader.h" + "Lib/BinaryTools/BinaryTools/Binary.h" + "Lib/BinaryTools/BinaryTools/Binary.cpp" + "Lib/BinaryTools/BinaryTools/BinaryReader.cpp" + "Lib/BinaryTools/BinaryTools/BinaryReader.h" + "Lib/BinaryTools/BinaryTools/BinaryWriter.cpp" + "Lib/BinaryTools/BinaryTools/BinaryWriter.h" + "Lib/BinaryTools/BinaryTools/MemoryBuffer.h" + "Lib/BinaryTools/BinaryTools/Span.h" +) + +source_group("Source Files\\BinaryTools" FILES ${Source_Files__BinaryTools}) + set(Source_Files__Controller "ControlDeck.cpp" "ControlDeck.h" @@ -376,6 +391,7 @@ set(ALL_FILES ${Header_Files__Resources__Files} ${Source_Files__Audio} ${Source_Files__Audio__extra} + ${Source_Files__BinaryTools} ${Source_Files__Controller} ${Source_Files__Controller__Attachment} ${Source_Files__CustomImpl} diff --git a/libultraship/libultraship/CrashHandler.cpp b/libultraship/libultraship/CrashHandler.cpp index 3485be2f2..b60d7c0c6 100644 --- a/libultraship/libultraship/CrashHandler.cpp +++ b/libultraship/libultraship/CrashHandler.cpp @@ -55,7 +55,7 @@ static const char* GetGameVersionString() { return "IQUE_TW"; case OOT_IQUE_CN: return "IQUE_CN"; - case OOT_UNKNOWN: + case UNKNOWN: return "UNKNOWN"; } diff --git a/libultraship/libultraship/Cutscene.cpp b/libultraship/libultraship/Cutscene.cpp index 79b42b3f1..42443fcdc 100644 --- a/libultraship/libultraship/Cutscene.cpp +++ b/libultraship/libultraship/Cutscene.cpp @@ -37,7 +37,7 @@ enum class CutsceneCommands Error = 0xFEAF, }; -static inline uint32_t read_CMD_BBBB(BinaryReader* reader) +static inline uint32_t read_CMD_BBBB(Ship::BinaryReader* reader) { uint32_t v; reader->Read((char*)&v, sizeof(uint32_t)); @@ -45,13 +45,13 @@ static inline uint32_t read_CMD_BBBB(BinaryReader* reader) return v; } -static inline uint32_t read_CMD_BBH(BinaryReader* reader) +static inline uint32_t read_CMD_BBH(Ship::BinaryReader* reader) { uint32_t v; reader->Read((char*)&v, sizeof(uint32_t)); // swap the half word to match endianness - if (reader->GetEndianness() != Endianness::Native) + if (reader->GetEndianness() != Ship::Endianness::Native) { uint8_t* b = (uint8_t*)&v; uint8_t tmp = b[2]; @@ -62,13 +62,13 @@ static inline uint32_t read_CMD_BBH(BinaryReader* reader) return v; } -static inline uint32_t read_CMD_HBB(BinaryReader* reader) +static inline uint32_t read_CMD_HBB(Ship::BinaryReader* reader) { uint32_t v; reader->Read((char*)&v, sizeof(uint32_t)); // swap the half word to match endianness - if (reader->GetEndianness() != Endianness::Native) + if (reader->GetEndianness() != Ship::Endianness::Native) { uint8_t* b = (uint8_t*)&v; uint8_t tmp = b[0]; @@ -79,13 +79,13 @@ static inline uint32_t read_CMD_HBB(BinaryReader* reader) return v; } -static inline uint32_t read_CMD_HH(BinaryReader* reader) +static inline uint32_t read_CMD_HH(Ship::BinaryReader* reader) { uint32_t v; reader->Read((char*)&v, sizeof(uint32_t)); // swap the half words to match endianness - if (reader->GetEndianness() != Endianness::Native) + if (reader->GetEndianness() != Ship::Endianness::Native) { uint8_t* b = (uint8_t*)&v; uint8_t tmp = b[0]; @@ -99,7 +99,7 @@ static inline uint32_t read_CMD_HH(BinaryReader* reader) return v; } -void Ship::CutsceneV0::ParseFileBinary(BinaryReader* reader, Resource* res) +void Ship::CutsceneV0::ParseFileBinary(Ship::BinaryReader* reader, Resource* res) { Cutscene* cs = (Cutscene*)res; diff --git a/libultraship/libultraship/DisplayList.cpp b/libultraship/libultraship/DisplayList.cpp index b2934ee68..79dd3741b 100644 --- a/libultraship/libultraship/DisplayList.cpp +++ b/libultraship/libultraship/DisplayList.cpp @@ -10,7 +10,7 @@ namespace Ship ResourceFile::ParseFileBinary(reader, res); while (reader->GetBaseAddress() % 8 != 0) - reader->ReadByte(); + reader->ReadInt8(); while (true) { diff --git a/libultraship/libultraship/Factories/ResourceLoader.cpp b/libultraship/libultraship/Factories/ResourceLoader.cpp index 194fc74e8..ac0c14705 100644 --- a/libultraship/libultraship/Factories/ResourceLoader.cpp +++ b/libultraship/libultraship/Factories/ResourceLoader.cpp @@ -22,13 +22,12 @@ namespace Ship { Resource* ResourceLoader::LoadResource(std::shared_ptr FileToLoad) { - auto memStream = std::make_shared(FileToLoad->buffer.get(), FileToLoad->dwBufferSize); - auto reader = std::make_shared(memStream); + auto reader = std::make_shared(FileToLoad->buffer.get(), FileToLoad->dwBufferSize); - Endianness endianness = (Endianness)reader->ReadByte(); + Endianness endianness = (Endianness)reader->ReadInt8(); for (int i = 0; i < 3; i++) - reader->ReadByte(); + reader->ReadInt8(); reader->SetEndianness(endianness); diff --git a/libultraship/libultraship/GameVersions.h b/libultraship/libultraship/GameVersions.h index 8d3753b21..2b4f6df29 100644 --- a/libultraship/libultraship/GameVersions.h +++ b/libultraship/libultraship/GameVersions.h @@ -20,6 +20,6 @@ #define OOT_PAL_GC_MQ_DBG 0x917D18F6 #define OOT_IQUE_TW 0x3D81FB3E #define OOT_IQUE_CN 0xB1E1E07B -#define OOT_UNKNOWN 0xFFFFFFFF +#define UNKNOWN 0xFFFFFFFF #endif diff --git a/libultraship/libultraship/Lib/BinaryTools/.gitignore b/libultraship/libultraship/Lib/BinaryTools/.gitignore new file mode 100644 index 000000000..7a1be1693 --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/.gitignore @@ -0,0 +1,276 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc +CodeGraphData/ + +# Documentation build files and tool test output +\.vscode/settings\.json +Docs/_build/ +\.vscode/ +Tools/Player\.rst +Tools/TestFile\.h +Proxy DLL Loader/Appveyor/ + +RSL/Appveyor/ + +Appveyor/ + +TODO + +BinaryTools/TestBin2.bin diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools.sln b/libultraship/libultraship/Lib/BinaryTools/BinaryTools.sln new file mode 100644 index 000000000..db54750cf --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29509.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BinaryTools", "BinaryTools\BinaryTools.vcxproj", "{DC3D45C9-4E30-4E00-9E95-76FCF3754849}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DC3D45C9-4E30-4E00-9E95-76FCF3754849}.Debug|x64.ActiveCfg = Debug|x64 + {DC3D45C9-4E30-4E00-9E95-76FCF3754849}.Debug|x64.Build.0 = Debug|x64 + {DC3D45C9-4E30-4E00-9E95-76FCF3754849}.Debug|x86.ActiveCfg = Debug|Win32 + {DC3D45C9-4E30-4E00-9E95-76FCF3754849}.Debug|x86.Build.0 = Debug|Win32 + {DC3D45C9-4E30-4E00-9E95-76FCF3754849}.Release|x64.ActiveCfg = Release|x64 + {DC3D45C9-4E30-4E00-9E95-76FCF3754849}.Release|x64.Build.0 = Release|x64 + {DC3D45C9-4E30-4E00-9E95-76FCF3754849}.Release|x86.ActiveCfg = Release|Win32 + {DC3D45C9-4E30-4E00-9E95-76FCF3754849}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A5347320-02AF-4DBE-B357-2B183ABC44F7} + EndGlobalSection +EndGlobal diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/Binary.cpp b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/Binary.cpp new file mode 100644 index 000000000..d70e2eff8 --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/Binary.cpp @@ -0,0 +1,23 @@ +#include "Binary.h" +#include + +Span ReadAllBytes(const std::string& filePath) +{ + std::ifstream file(filePath, std::ios::ate | std::ios::binary); + + if (!file.is_open()) + { + throw std::runtime_error("Failed to open file!"); //Todo: Note file name/path in error. Maybe better to just return an optional + } + + size_t fileSize = (size_t)file.tellg(); + char* buffer = new char[fileSize]; + + file.seekg(0); + file.read(buffer, fileSize); + file.close(); + + + + return Span(buffer, fileSize); +} \ No newline at end of file diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/Binary.h b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/Binary.h new file mode 100644 index 000000000..8f23a35ac --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/Binary.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include "Span.h" + +Span ReadAllBytes(const std::string& filePath); \ No newline at end of file diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryReader.cpp b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryReader.cpp new file mode 100644 index 000000000..8b2950c23 --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryReader.cpp @@ -0,0 +1,266 @@ +#include "BinaryReader.h" +#include "MemoryBuffer.h" + +BinaryReader::BinaryReader(std::string_view inputPath) +{ + stream_ = new std::ifstream(std::string(inputPath), std::ifstream::in | std::ifstream::binary); +} + +BinaryReader::BinaryReader(char* buffer, uint32_t sizeInBytes) +{ + buffer_ = new basic_memstreambuf(buffer, sizeInBytes); + stream_ = new std::istream(buffer_); +} + +BinaryReader::BinaryReader(std::span buffer) +{ + buffer_ = new basic_memstreambuf((char*)buffer.data(), buffer.size_bytes()); + stream_ = new std::istream(buffer_); +} + +BinaryReader::~BinaryReader() +{ + delete stream_; + if (buffer_) + delete buffer_; +} + +uint8_t BinaryReader::ReadUint8() +{ + uint8_t output; + stream_->read(reinterpret_cast(&output), 1); + return output; +} + +uint16_t BinaryReader::ReadUint16() +{ + uint16_t output; + stream_->read(reinterpret_cast(&output), 2); + return output; +} + +uint32_t BinaryReader::ReadUint32() +{ + //Todo: See if using static or class var speeds these up + uint32_t output; + stream_->read(reinterpret_cast(&output), 4); + return output; +} + +uint64_t BinaryReader::ReadUint64() +{ + uint64_t output; + stream_->read(reinterpret_cast(&output), 8); + return output; +} + +int8_t BinaryReader::ReadInt8() +{ + int8_t output; + stream_->read(reinterpret_cast(&output), 1); + return output; +} + +int16_t BinaryReader::ReadInt16() +{ + int16_t output; + stream_->read(reinterpret_cast(&output), 2); + return output; +} + +int32_t BinaryReader::ReadInt32() +{ + int32_t output; + stream_->read(reinterpret_cast(&output), 4); + return output; +} + +int64_t BinaryReader::ReadInt64() +{ + int64_t output; + stream_->read(reinterpret_cast(&output), 8); + return output; +} + +char BinaryReader::ReadChar() +{ + char output; + stream_->read(&output, 1); + return output; +} + +wchar_t BinaryReader::ReadCharWide() +{ + wchar_t output; + stream_->read((char*)&output, 2); + return output; +} + +std::string BinaryReader::ReadNullTerminatedString() +{ + std::string output; + char charBuffer = 0; + while(PeekChar() != '\0') + { + stream_->read(&charBuffer, 1); + output.push_back(charBuffer); + } + Skip(1); //Move past null terminator + return output; +} + +std::string BinaryReader::ReadFixedLengthString(size_t length) +{ + std::string output; + output.reserve(length); + for (int i = 0; i < length; i++) + { + char charBuffer; + stream_->read(&charBuffer, 1); + output.push_back(charBuffer); + } + return output; +} + +std::wstring BinaryReader::ReadNullTerminatedStringWide() +{ + std::wstring output; + wchar_t charBuffer = 0; + while (PeekCharWide() != '\0') + { + stream_->read((char*)&charBuffer, 2); + output.push_back(charBuffer); + } + Skip(2); //Move past null terminator + return output; +} + +std::wstring BinaryReader::ReadFixedLengthStringWide(size_t length) +{ + std::wstring output; + output.reserve(length); + for (int i = 0; i < length; i++) + { + wchar_t charBuffer; + stream_->read((char*)&charBuffer, 2); + output.push_back(charBuffer); + } + return output; +} + +std::vector BinaryReader::ReadSizedStringList(size_t listSize) +{ + std::vector stringList = { }; + if (listSize == 0) + return stringList; + + size_t startPos = Position(); + while (Position() - startPos < listSize) + { + stringList.push_back(ReadNullTerminatedString()); + while (Position() - startPos < listSize) + { + //TODO: See if Align(4) would accomplish the same. This is really for RfgTools++ since many RFG formats have sized string lists + //Sometimes names have extra null bytes after them for some reason. Simple way to handle this + if (PeekChar() == '\0') + Skip(1); + else + break; + } + } + + return stringList; +} + +char BinaryReader::PeekChar() +{ + char output = ReadChar(); + SeekReverse(1); + return output; +} + +uint32_t BinaryReader::PeekUint32() +{ + uint32_t output = ReadUint32(); + SeekReverse(4); + return output; +} + + +wchar_t BinaryReader::PeekCharWide() +{ + wchar_t output = ReadCharWide(); + SeekReverse(2); + return output; +} + +float BinaryReader::ReadFloat() +{ + float output; + stream_->read(reinterpret_cast(&output), 4); + return output; +} + +double BinaryReader::ReadDouble() +{ + double output; + stream_->read(reinterpret_cast(&output), 8); + return output; +} + +void BinaryReader::ReadToMemory(void* destination, size_t size) +{ + stream_->read(static_cast(destination), size); +} + +void BinaryReader::SeekBeg(size_t absoluteOffset) +{ + stream_->seekg(absoluteOffset, std::ifstream::beg); +} + +void BinaryReader::SeekCur(size_t relativeOffset) +{ + stream_->seekg(relativeOffset, std::ifstream::cur); +} + +void BinaryReader::SeekReverse(size_t relativeOffset) +{ + const size_t delta = std::min(Position(), relativeOffset); //Don't allow seeking before the beginning of the stream + const size_t targetOffset = Position() - delta; + SeekBeg(targetOffset); +} + +void BinaryReader::Skip(size_t bytesToSkip) +{ + stream_->seekg(bytesToSkip, std::ifstream::cur); +} + +size_t BinaryReader::Align(size_t alignmentValue) +{ + //Todo: Test that this math is working as expected. Had bug here in C# version + const size_t remainder = stream_->tellg() % alignmentValue; + size_t paddingSize = remainder > 0 ? alignmentValue - remainder : 0; + Skip(paddingSize); + return paddingSize; +} + +size_t BinaryReader::Position() const +{ + return stream_->tellg(); +} + +size_t BinaryReader::Length() +{ + //Save current position + size_t realPosition = Position(); + + //Seek to end of file and get position (the length) + stream_->seekg(0, std::ios::end); + size_t endPosition = Position(); + + //Seek back to real pos and return length + if(realPosition != endPosition) + SeekBeg(realPosition); + + return endPosition; +} diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryReader.h b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryReader.h new file mode 100644 index 000000000..7d6c78f8c --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryReader.h @@ -0,0 +1,64 @@ +#pragma once +#include "MemoryBuffer.h" +#include +#include +#include +#include +#include + +struct MemoryBuffer; + + +//Class that can read binary data either from a file or from a fixed size buffer +//depending on the constructor used. +class BinaryReader +{ +public: + //Reads binary data from file at path + BinaryReader(std::string_view inputPath); + //Reads binary data from fixed size memory buffer + BinaryReader(char* buffer, uint32_t sizeInBytes); + //Reads binary data from fixed size memory buffer + BinaryReader(std::span buffer); + ~BinaryReader(); + + [[nodiscard]] uint8_t ReadUint8(); + [[nodiscard]] uint16_t ReadUint16(); + [[nodiscard]] uint32_t ReadUint32(); + [[nodiscard]] uint64_t ReadUint64(); + + [[nodiscard]] int8_t ReadInt8(); + [[nodiscard]] int16_t ReadInt16(); + [[nodiscard]] int32_t ReadInt32(); + [[nodiscard]] int64_t ReadInt64(); + + [[nodiscard]] char ReadChar(); + [[nodiscard]] wchar_t ReadCharWide(); + [[nodiscard]] std::string ReadNullTerminatedString(); + [[nodiscard]] std::string ReadFixedLengthString(size_t length); + [[nodiscard]] std::wstring ReadNullTerminatedStringWide(); + [[nodiscard]] std::wstring ReadFixedLengthStringWide(size_t length); + [[nodiscard]] std::vector ReadSizedStringList(size_t listSize); + [[nodiscard]] char PeekChar(); + [[nodiscard]] uint32_t PeekUint32(); + [[nodiscard]] wchar_t PeekCharWide(); + + [[nodiscard]] float ReadFloat(); + [[nodiscard]] double ReadDouble(); + + void ReadToMemory(void* destination, size_t size); + + void SeekBeg(size_t absoluteOffset); + void SeekCur(size_t relativeOffset); + void SeekReverse(size_t relativeOffset); //Move backwards from the current stream position + void Skip(size_t bytesToSkip); + size_t Align(size_t alignmentValue = 2048); + + size_t Position() const; + size_t Length(); + +private: + std::istream* stream_ = nullptr; + basic_memstreambuf* buffer_ = nullptr; +}; + diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.cpp b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.cpp new file mode 100644 index 000000000..cf05829d0 --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.cpp @@ -0,0 +1,162 @@ +#include "BinaryWriter.h" +#include "BinaryReader.h" +#include "Binary.h" +#include + +struct TestPod +{ + float x; + float y; + float z; + uint32_t cash; + int32_t score; +}; + +int main() +{ + printf("**** Test 1 - Write + Read a few values ****\n"); + //Test writing a few values and reading them back + { + { + printf("Writing some values to file... "); + BinaryWriter writer("./TestBin1.bin"); + writer.WriteFloat(1232.3f); + writer.WriteFloat(300.7f); + writer.WriteFloat(1680.0f); + writer.WriteUint32(8000); + writer.WriteInt32(-2003443); + printf("Done!\n"); + } + { + printf("Reading those values back...\n"); + BinaryReader reader("./TestBin1.bin"); + printf("Float: %f\n", reader.ReadFloat()); + printf("Float: %f\n", reader.ReadFloat()); + printf("Float: %f\n", reader.ReadFloat()); + printf("Uint32: %d\n", reader.ReadUint32()); + printf("Int32: %d\n", reader.ReadInt32()); + printf("Done!\n"); + } + + } + + printf("\n\n**** Test 2 - Write + Read a POD struct directly to/from memory ****\n"); + //Test writing a struct from memory, reading it back and casting the data onto it + { + //Write data + { + TestPod writeData = {}; + writeData.x = 1234.44f; + writeData.y = 1734.44f; + writeData.z = 22334.44f; + writeData.cash = 1003; + writeData.score = -64230; + printf("sizeof(TestPod) = %zd\n", sizeof(TestPod)); + + printf("Writing POD struct from memory... "); + BinaryWriter writer("./TestBin2.bin"); + writer.WriteFromMemory(&writeData, sizeof(TestPod)); + printf("Done!\n\n"); + } + //Read it back + { + TestPod readData = {}; + readData.x = 0.00000000f; + readData.y = 0.00000000f; + readData.z = 0.00000000f; + readData.cash = 0; + readData.score = 0; + + printf("Reading back data directly into POD struct location in memory... "); + BinaryReader reader("./TestBin2.bin"); + reader.ReadToMemory(&readData, sizeof(TestPod)); + printf("Done!\n"); + printf("Printing values...\n"); + printf("Float: %f\n", readData.x); + printf("Float: %f\n", readData.y); + printf("Float: %f\n", readData.z); + printf("Uint32: %d\n", readData.cash); + printf("Int32: %d\n", readData.score); + } + } + + printf("\n\n**** Test 3 - Read a POD struct directly to/from memory from handmade binary file ****\n"); + //Test reading data from handmade binary file straight into POD struct memory location + { + { + TestPod readData = {}; + readData.x = 0.00000000f; + readData.y = 0.00000000f; + readData.z = 0.00000000f; + readData.cash = 0; + readData.score = 0; + + printf("Reading data directly into POD struct location in memory... "); + BinaryReader reader("./TestBin3.bin"); + reader.ReadToMemory(&readData, sizeof(TestPod)); + printf("Done!\n"); + printf("Printing values...\n"); + printf("Float: %f\n", readData.x); + printf("Float: %f\n", readData.y); + printf("Float: %f\n", readData.z); + printf("Uint32: %d\n", readData.cash); + printf("Int32: %d\n", readData.score); + } + } + + printf("\n\n**** Test 4 - Read a POD struct from a file to memory and read data from that memory area with BinaryReader ****\n"); + //Test reading data from handmade binary file straight into POD struct memory location + { + { + TestPod readData = {}; + readData.x = 0.00000000f; + readData.y = 0.00000000f; + readData.z = 0.00000000f; + readData.cash = 0; + readData.score = 0; + + printf("Reading data directly into memory... "); + printf("Done!\n"); + auto span = ReadAllBytes("./TestBin3.bin"); + printf("Reading values of memory buffer with BinaryReader... "); + printf("Done!\n"); + + BinaryReader reader(span.Data(), (uint32_t)span.Size()); + printf("Printing values...\n"); + printf("Float: %f\n", reader.ReadFloat()); + printf("Float: %f\n", reader.ReadFloat()); + printf("Float: %f\n", reader.ReadFloat()); + printf("Uint32: %d\n", reader.ReadUint32()); + printf("Int32: %d\n", reader.ReadInt32()); + delete span.Data(); + } + } + + struct test + { + int a; + int b; + }; + + std::array testArray; + testArray[0] = test{ 2, 3 }; + testArray[1] = test{ 4, 5 }; + testArray[2] = test{ 6, 7 }; + //Intentionally specifying size of 2 here to see if end() actually points to the end of last element of the span + Span testSpan(testArray.data(), 2); + + test* begin = testSpan.begin(); + test* end = testSpan.end(); + auto& front = testSpan.front(); + auto& back = testSpan.back(); + + auto& zero = testSpan[0]; + auto& one = testSpan[1]; + auto& two = testSpan[2]; + + printf("Testing use of range based for loops with Span...\n"); + for (auto& val : testSpan) + printf("value: {a: %d, b: %d}\n", val.a, val.b); + + auto a = 2; +} diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.vcxproj b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.vcxproj new file mode 100644 index 000000000..ef8f1fc1e --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.vcxproj @@ -0,0 +1,177 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {DC3D45C9-4E30-4E00-9E95-76FCF3754849} + Win32Proj + BinaryTools + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpplatest + true + + + Console + true + + + + + + + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpplatest + true + + + Console + true + + + + + + + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpplatest + true + + + Console + true + true + true + + + + + + + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpplatest + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.vcxproj.filters b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.vcxproj.filters new file mode 100644 index 000000000..47f325083 --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryTools.vcxproj.filters @@ -0,0 +1,60 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {54101738-c0ef-40a7-be61-cf8ae2cbcae3} + + + {5568f67e-fdd2-4c16-bb85-bf72217b642c} + + + {03c417c9-055a-45f5-83d9-42357834c20e} + + + {063757fd-06fb-4f35-8362-005b4a8261b9} + + + + + src + + + src\BinaryReader + + + src\BinaryWriter + + + src\helpers + + + + + src\BinaryReader + + + src\BinaryWriter + + + src\helpers + + + src\helpers + + + src\helpers + + + \ No newline at end of file diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryWriter.cpp b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryWriter.cpp new file mode 100644 index 000000000..d2630a41f --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryWriter.cpp @@ -0,0 +1,183 @@ +#include "BinaryWriter.h" +#include "MemoryBuffer.h" +#include +#include + +BinaryWriter::BinaryWriter(std::string_view inputPath, bool truncate) +{ + //Can't simply exclude the truncate flag when !truncate. More details here: https://stackoverflow.com/a/57070159 + int flags = 0; + if (truncate) + flags = std::ofstream::out | std::ofstream::binary | std::ofstream::trunc; //Clears existing contents of the file + else + flags = std::ofstream::in | std::ofstream::out | std::ofstream::binary; + + //If not truncating and the file doesn't exist, then opening will fail. So we create the file first if it doesn't exist + if (!truncate && !std::filesystem::exists(inputPath)) + { + std::fstream f; + f.open(std::string(inputPath), std::fstream::out); + f.close(); + } + + stream_ = new std::ofstream(std::string(inputPath), (std::ios_base::openmode)flags); +} + +BinaryWriter::BinaryWriter(char* buffer, uint32_t sizeInBytes) +{ + buffer_ = new MemoryBuffer(buffer, sizeInBytes); + stream_ = new std::ostream(buffer_); +} + +BinaryWriter::~BinaryWriter() +{ + delete stream_; + if (buffer_) + delete[] buffer_; +} + +void BinaryWriter::Flush() +{ + stream_->flush(); +} + +void BinaryWriter::WriteUint8(uint8_t value) +{ + stream_->write(reinterpret_cast(&value), 1); +} + +void BinaryWriter::WriteUint16(uint16_t value) +{ + stream_->write(reinterpret_cast(&value), 2); +} + +void BinaryWriter::WriteUint32(uint32_t value) +{ + stream_->write(reinterpret_cast(&value), 4); +} + +void BinaryWriter::WriteUint64(uint64_t value) +{ + stream_->write(reinterpret_cast(&value), 8); +} + +void BinaryWriter::WriteInt8(int8_t value) +{ + stream_->write(reinterpret_cast(&value), 1); +} + +void BinaryWriter::WriteInt16(int16_t value) +{ + stream_->write(reinterpret_cast(&value), 2); +} + +void BinaryWriter::WriteInt32(int32_t value) +{ + stream_->write(reinterpret_cast(&value), 4); +} + +void BinaryWriter::WriteInt64(int64_t value) +{ + stream_->write(reinterpret_cast(&value), 8); +} + +void BinaryWriter::WriteChar(char value) +{ + stream_->write(reinterpret_cast(&value), 1); +} + +void BinaryWriter::WriteNullTerminatedString(const std::string& value) +{ + stream_->write(value.data(), value.size()); + WriteChar('\0'); +} + +void BinaryWriter::WriteFixedLengthString(const std::string& value) +{ + stream_->write(value.data(), value.size()); +} + +void BinaryWriter::WriteFloat(float value) +{ + stream_->write(reinterpret_cast(&value), 4); +} + +void BinaryWriter::WriteDouble(double value) +{ + stream_->write(reinterpret_cast(&value), 8); +} + +void BinaryWriter::WriteFromMemory(const void* data, size_t size) +{ + stream_->write(reinterpret_cast(data), size); +} + +void BinaryWriter::SeekBeg(size_t absoluteOffset) +{ + stream_->seekp(absoluteOffset, std::ifstream::beg); +} + +void BinaryWriter::SeekCur(size_t relativeOffset) +{ + stream_->seekp(relativeOffset, std::ifstream::cur); +} + +void BinaryWriter::Skip(size_t bytesToSkip) +{ + size_t position = Position(); + size_t length = Length(); + + //If we're skipped past the end of the stream then skip what's available and write null bytes for the rest + if (position + bytesToSkip > length) + { + size_t bytesAvailable = length - position; + size_t bytesNeeded = bytesToSkip - bytesAvailable; + + stream_->seekp(bytesAvailable, std::ifstream::cur); + WriteNullBytes(bytesNeeded); + } + else + stream_->seekp(bytesToSkip, std::ifstream::cur); +} + +void BinaryWriter::WriteNullBytes(size_t bytesToWrite) +{ + //Todo: See if quicker to allocate array of zeros and use WriteFromMemory + for (size_t i = 0; i < bytesToWrite; i++) + WriteUint8(0); +} + +size_t BinaryWriter::CalcAlign(size_t position, size_t alignmentValue) +{ + const size_t remainder = position % alignmentValue; + size_t paddingSize = remainder > 0 ? alignmentValue - remainder : 0; + return paddingSize; +} + +size_t BinaryWriter::Align(size_t alignmentValue) +{ + const size_t paddingSize = CalcAlign(stream_->tellp(), alignmentValue); + Skip(paddingSize); + return paddingSize; +} + +size_t BinaryWriter::Position() const +{ + return stream_->tellp(); +} + +size_t BinaryWriter::Length() +{ + //Save current position + size_t realPosition = Position(); + + //Seek to end of file and get position (the length) + stream_->seekp(0, std::ios::end); + size_t endPosition = Position(); + + //Seek back to real pos and return length + if (realPosition != endPosition) + SeekBeg(realPosition); + + return endPosition; +} diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryWriter.h b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryWriter.h new file mode 100644 index 000000000..a3ac5795b --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/BinaryWriter.h @@ -0,0 +1,72 @@ +#pragma once +#include +#include +#include + +struct MemoryBuffer; + +//Class that can write binary data either from a file or from a fixed size buffer +//depending on the constructor used. +class BinaryWriter +{ +public: + //Writes binary data from file at path. If truncate == true any existing file contents will be cleared + BinaryWriter(std::string_view inputPath, bool truncate = true); + //Writes binary data from fixed size memory buffer + BinaryWriter(char* buffer, uint32_t sizeInBytes); + ~BinaryWriter(); + + void Flush(); + + void WriteUint8(uint8_t value); + void WriteUint16(uint16_t value); + void WriteUint32(uint32_t value); + void WriteUint64(uint64_t value); + + void WriteInt8(int8_t value); + void WriteInt16(int16_t value); + void WriteInt32(int32_t value); + void WriteInt64(int64_t value); + + void WriteChar(char value); + //Write string to output with null terminator + void WriteNullTerminatedString(const std::string& value); + //Write string to output without null terminator + void WriteFixedLengthString(const std::string& value); + + void WriteFloat(float value); + void WriteDouble(double value); + + void WriteFromMemory(const void* data, size_t size); + + template + void Write(const T& data) + { + //Don't allow T to be a pointer to avoid accidentally writing the value of a pointer instead of what it points to. + static_assert(!std::is_pointer(), "BinaryWriter::Write requires T to be a non pointer type."); + WriteFromMemory(&data, sizeof(T)); + } + + template + void WriteSpan(std::span data) + { + WriteFromMemory(data.data(), data.size_bytes()); + } + + void SeekBeg(size_t absoluteOffset); + void SeekCur(size_t relativeOffset); + void Skip(size_t bytesToSkip); + void WriteNullBytes(size_t bytesToWrite); + //Static method for calculating alignment pad from pos and alignment. Does not change position since static + static size_t CalcAlign(size_t position, size_t alignmentValue = 2048); + //Aligns stream to alignment value. Returns padding byte count + size_t Align(size_t alignmentValue = 2048); + + size_t Position() const; + size_t Length(); + +private: + std::ostream* stream_ = nullptr; + MemoryBuffer* buffer_ = nullptr; +}; + diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/MemoryBuffer.h b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/MemoryBuffer.h new file mode 100644 index 000000000..4128f3289 --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/MemoryBuffer.h @@ -0,0 +1,250 @@ +#pragma once +//Somehow the windows min and max macros are being leaked into here regardless of where I define NOMINMAX. This works as a fix for the moment +#undef min +#undef max +#include +#include +#include +#include +#include + +//Simple wrapper around std::streambuf representing a memory buffer. +//Used by BinaryReader and BinaryWriter +struct MemoryBuffer : std::streambuf +{ + MemoryBuffer(char* begin, char* end) + { + this->setg(begin, begin, end); + } + MemoryBuffer(char* begin, uint32_t sizeInBytes) + { + this->setg(begin, begin, begin + sizeInBytes); + } +}; + +//Used by BinaryReader for reading from in memory buffers. +//Source: https://gist.github.com/polyvertex/ce86fddfa28edcfb19a77f9024a5461c + +// A memory stream buffer class compliant with `std::basic_streambuf`. +// +// Usage example: +// +// std::vector data; +// // ... fill-in *data* here ... +// xx::basic_memstreambuf sbuf(&data[0], data.size()); +// std::istream in(&sbuf); +// // ... read data from *in* ... +// +// Useful references: +// * Deriving from std::streambuf +// https://artofcode.wordpress.com/2010/12/12/deriving-from-stdstreambuf/ +// * A beginner's guide to writing a custom stream buffer (std::streambuf) +// http://www.voidcn.com/article/p-vjnlygmc-gy.html +// * membuf.cpp +// https://gist.github.com/mlfarrell/28ea0e7b10756042956b579781ac0dd8 +// * Memory streambuf and stream +// https://codereview.stackexchange.com/questions/138479/memory-streambuf-and-stream + +class basic_memstreambuf : public std::streambuf +{ +public: + using BaseT = std::streambuf; + + using BaseT::int_type; + using BaseT::traits_type; + +public: + basic_memstreambuf() + : BaseT() + { } + + explicit basic_memstreambuf(const basic_memstreambuf& rhs) + : BaseT(rhs) + { } + + // non-standard + explicit basic_memstreambuf(const std::basic_string& s) + : BaseT() + { + //assert(!s.empty()); + setg( + const_cast(&s.front()), + const_cast(&s.front()), + const_cast(&s.back())); + } + + // non-standard + basic_memstreambuf(const char_type* s, std::streamsize n) : BaseT() + { + // assert(s); + //assert(n > 0); + setg( + const_cast(s), + const_cast(s), + const_cast(s + n)); + } + + // non-standard + basic_memstreambuf(const char_type* begin, const char_type* end) + : BaseT() + { + //assert(begin); + //assert(end); + //assert(begin < end); + + // check size + const std::uintmax_t count = end - begin; + const std::uintmax_t maxValue = static_cast(std::numeric_limits::max()); + if (count > maxValue) + { + throw std::invalid_argument("basic_memstreambuf too big"); + } + + setg( + const_cast(begin), + const_cast(begin), + const_cast(end)); + } + + basic_memstreambuf& operator=(const basic_memstreambuf&) = delete; + + +protected: + virtual std::streamsize showmanyc() override + { + const auto* ptr = gptr(); + const auto* end = egptr(); + + //assert(ptr <= end); + + return (ptr <= end) ? (end - ptr) : 0; + } + + virtual int_type underflow() override + { + const auto* ptr = gptr(); + + if (ptr >= egptr()) + return traits_type::eof(); + + return traits_type::to_int_type(*ptr); + } + + virtual std::streamsize xsgetn(char_type* s, std::streamsize count) override + { + if (count == 0) + return 0; + + const char* ptr = gptr(); + const std::streamsize to_read = std::min( + count, + static_cast(egptr() - ptr)); + + if (to_read == 0) + { + return traits_type::eof(); + } + else + { + std::memcpy(s, ptr, to_read); + gbump((int)to_read); + return to_read; + } + } + + virtual pos_type seekoff( + off_type off, + std::ios_base::seekdir dir, + std::ios_base::openmode which = std::ios_base::in) override + { + if (which != std::ios_base::in) + { + //assert(0); + throw std::invalid_argument("basic_memstreambuf::seekoff[which]"); + } + + if (dir == std::ios_base::beg) + { + if (off >= 0 && off < egptr() - eback()) + { + setg(eback(), eback() + off, egptr()); + } + else + { + //assert(0); + throw std::out_of_range("basic_memstreambuf::seekoff[beg]"); + } + } + else if (dir == std::ios_base::cur) + { + if ((off >= 0 && off <= egptr() - gptr()) || + (off < 0 && std::abs(off) < gptr() - eback())) + { + gbump((int)off); + } + else + { + //assert(0); + throw std::out_of_range("basic_memstreambuf::seekoff[cur]"); + } + } + else if (dir == std::ios_base::end) + { + if (off <= 0 && std::abs(off) < egptr() - eback()) + { + setg(eback(), egptr() + (int)off, egptr()); + } + else + { + //assert(0); + throw std::out_of_range("basic_memstreambuf::seekoff[end]"); + } + } + else + { + //assert(0); + throw std::invalid_argument("basic_memstreambuf::seekoff[dir]"); + } + + return gptr() - eback(); + } + + virtual pos_type seekpos( + pos_type pos, + std::ios_base::openmode which = std::ios_base::in) override + { + if (which != std::ios_base::in) + { + //assert(0); + throw std::invalid_argument("basic_memstreambuf::seekpos[which]"); + } + + if (pos < egptr() - eback()) + { + setg(eback(), eback() + pos, egptr()); + } + else + { + ////assert(0); + throw std::out_of_range("memstreambuf::seekpos"); + } + + return pos; + } + +#if 0 + virtual int_type pbackfail(int_type c = traits_type::eof()) override + { + const auto* begin = eback(); + const auto* ptr = gptr(); + const auto gc = *(ptr - 1); + + if (ptr == begin || (c != traits_type::eof() && c != gc)) + return traits_type::eof(); + + gbump(-1); + + return traits_type::to_int_type(gc); + } +#endif +}; \ No newline at end of file diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/Span.h b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/Span.h new file mode 100644 index 000000000..c258e6622 --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/Span.h @@ -0,0 +1,41 @@ +#pragma once + +//Simple wrapper around a contiguous area of memory +template +struct Span +{ +public: + Span(T* ptr, size_t size) : ptr_(ptr), size_(size) {} + + T* Data() { return ptr_; } + size_t Size() { return size_; } + + //Returns pointer to start of contiguous memory area + T* begin() { return ptr_; } + //Returns pointer to start of contiguous memory area + const T* begin() const { return ptr_; } + //Returns pointer to end of contiguous memory area (the end of the last memory). + //To get the last member, use back() instead. + T* end() { return ptr_ + size_; } + //Returns pointer to end of contiguous memory area (the end of the last memory). + //To get the last member, use back() instead. + const T* end() const { return ptr_ + size_; } + //Returns reference to first member of the span + T& front() { return *ptr_; } + //Returns reference to first member of the span + const T& front() const { return *ptr_; } + //Returns reference to last member of the span + T& back() { return *(ptr_ + size_); } + //Returns reference to last member of the span + const T& back() const { return *(ptr_ + size_); } + + //Todo: Add optional bounds checking for debug builds + //Returns reference to element at provided index. Does no bounds checking + T& operator[](size_t index) { return ptr_[index]; } + //Returns reference to element at provided index. Does no bounds checking + const T& operator[](size_t index) const { return ptr_[index]; } + +private: + T* ptr_ = nullptr; + size_t size_ = 0; +}; \ No newline at end of file diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/TestBin1.bin b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/TestBin1.bin new file mode 100644 index 0000000000000000000000000000000000000000..74fb7cb88069ab1a350c8622b8daa87a938e3aa8 GIT binary patch literal 20 ccmbQmIm=~M`: A very simple wrapper around a fixed sized memory region used by ReadAllBytes. You must free the memory the span points to if it's heap allocated. +- `MemoryBuffer`: A simple class which inherits std::streambuf. Used by BinaryReader/Writer when interacting with a memory buffer. +- `ReadAllBytes(const std::string& filePath)`: Function that reads all bytes from a file and returns them in a Span. Since it's using a span you must free the memory it returns once you're done with it. + +## Example +This example shows how to read/write files and in memory buffers using `BinaryReader` and `BinaryWriter`. +```c++ +#include +#include +#include +#include +#include + +//You can specify data layouts with a struct and read/write them in one go +struct ExampleHeader +{ + uint32_t Signature = 0xF00F; + uint32_t Version = 10; + uint32_t Value0 = 1024; + float Value1 = 45.2f; + float Value2 = 800.9f; +}; + +int main() +{ + //Write test file to your project directory + std::string testFilePath = "TestFile0.bin"; + + //File writing + { + BinaryWriter writer(testFilePath); + writer.WriteUint32(100); + writer.WriteFloat(512.0f); + writer.WriteNullTerminatedString("Hello binary!"); + //Write padding bytes to align to value. E.g. position = 13. After align(4), position = 16, the next multiple of 4. + writer.Align(4); + + //Can also write whole structs/classes to files + ExampleHeader header; + writer.WriteFromMemory(&header, sizeof(ExampleHeader)); + } + + //File reading + { + BinaryReader reader(testFilePath); //Assumes that the file already exists + assert(reader.ReadUint32() == 100); + assert(reader.ReadFloat() == 512.0f); + assert(reader.ReadNullTerminatedString() == "Hello binary!"); + reader.Align(4); + + //Can also read whole structs/classes from files + ExampleHeader header; + reader.ReadToMemory(&header, sizeof(ExampleHeader)); + assert(header.Signature == 0xF00F); + assert(header.Version == 10); + assert(header.Value0 == 1024); + assert(header.Value1 == 45.2f); + assert(header.Value2 == 800.9f); + } + + //Reading from memory + { + uint32_t someBuffer[5] = { 256, 700, 12, 895, 5784 }; + BinaryReader reader2((char*)&someBuffer, 5 * sizeof(uint32_t)); + assert(reader2.ReadUint32() == 256); + assert(reader2.ReadUint32() == 700); + assert(reader2.ReadUint32() == 12); + assert(reader2.ReadUint32() == 895); + assert(reader2.ReadUint32() == 5784); + } +} + +``` diff --git a/libultraship/libultraship/Material.cpp b/libultraship/libultraship/Material.cpp index ccf1b4d65..33ecc1bcf 100644 --- a/libultraship/libultraship/Material.cpp +++ b/libultraship/libultraship/Material.cpp @@ -42,7 +42,7 @@ namespace Ship ShaderParam::ShaderParam(BinaryReader* reader) { name = reader->ReadUInt32(); - dataType = (DataType)reader->ReadByte(); + dataType = (DataType)reader->ReadInt8(); switch (dataType) { @@ -50,7 +50,7 @@ namespace Ship value = reader->ReadUByte(); break; case DataType::S8: - value = reader->ReadByte(); + value = reader->ReadInt8(); break; case DataType::U16: value = reader->ReadUInt16(); diff --git a/libultraship/libultraship/Model.cpp b/libultraship/libultraship/Model.cpp index c59f38e8b..47d01cc03 100644 --- a/libultraship/libultraship/Model.cpp +++ b/libultraship/libultraship/Model.cpp @@ -24,7 +24,7 @@ namespace Ship uint32_t headerStart = reader->GetBaseAddress(); - modelType = (ModelType)reader->ReadByte(); + modelType = (ModelType)reader->ReadInt8(); numVerts = reader->ReadUInt32(); numPolys = reader->ReadUInt32(); diff --git a/libultraship/libultraship/Model.h b/libultraship/libultraship/Model.h index 241a2932a..80ac0d6c2 100644 --- a/libultraship/libultraship/Model.h +++ b/libultraship/libultraship/Model.h @@ -5,6 +5,7 @@ #include "Resource.h" #include "Vec2f.h" #include "Vec3f.h" +#include "Vec3s.h" #include "Color3b.h" namespace Ship diff --git a/libultraship/libultraship/Path.h b/libultraship/libultraship/Path.h index 41902f669..e509292c2 100644 --- a/libultraship/libultraship/Path.h +++ b/libultraship/libultraship/Path.h @@ -5,6 +5,7 @@ #include "Resource.h" #include "Vec2f.h" #include "Vec3f.h" +#include "Vec3s.h" #include "Color3b.h" namespace Ship diff --git a/libultraship/libultraship/Resource.cpp b/libultraship/libultraship/Resource.cpp index cbeedb44b..6ef484f5a 100644 --- a/libultraship/libultraship/Resource.cpp +++ b/libultraship/libultraship/Resource.cpp @@ -2,7 +2,6 @@ #include "DisplayList.h" #include "ResourceMgr.h" #include "spdlog/spdlog.h" -#include "Utils/BinaryReader.h" #include "Lib/tinyxml2/tinyxml2.h" #include "Lib/Fast3D/U64/PR/ultra64/gbi.h" diff --git a/libultraship/libultraship/Resource.h b/libultraship/libultraship/Resource.h index 9e89d19f5..9b08ef609 100644 --- a/libultraship/libultraship/Resource.h +++ b/libultraship/libultraship/Resource.h @@ -1,7 +1,7 @@ #pragma once #include -#include "Utils/BinaryReader.h" +#include "BinaryReader.h" #include "Utils/BinaryWriter.h" #include "File.h" #include "Lib/tinyxml2/tinyxml2.h" diff --git a/libultraship/libultraship/ResourceMgr.cpp b/libultraship/libultraship/ResourceMgr.cpp index 5e1d6f693..888581358 100644 --- a/libultraship/libultraship/ResourceMgr.cpp +++ b/libultraship/libultraship/ResourceMgr.cpp @@ -9,17 +9,30 @@ namespace Ship { - ResourceMgr::ResourceMgr(std::shared_ptr Context, const std::string& MainPath, const std::string& PatchesPath) : Context(Context), bIsRunning(false), FileLoadThread(nullptr) { - OTR = std::make_shared(MainPath, PatchesPath, false); + ResourceMgr::ResourceMgr(std::shared_ptr Context, const std::string& MainPath, const std::string& PatchesPath, const std::unordered_set& ValidHashes) + : Context(Context), bIsRunning(false), FileLoadThread(nullptr) { + OTR = std::make_shared(MainPath, PatchesPath, ValidHashes, false); - gameVersion = OOT_UNKNOWN; + gameVersion = UNKNOWN; if (OTR->IsMainMPQValid()) { Start(); } } - ResourceMgr::~ResourceMgr() { + ResourceMgr::ResourceMgr(std::shared_ptr Context, const std::vector &OTRFiles, const std::unordered_set &ValidHashes) + : Context(Context), bIsRunning(false), FileLoadThread(nullptr) + { + OTR = std::make_shared(OTRFiles, ValidHashes, false); + + gameVersion = UNKNOWN; + + if (OTR->IsMainMPQValid()) { + Start(); + } + } + + ResourceMgr::~ResourceMgr() { SPDLOG_INFO("destruct ResourceMgr"); Stop(); @@ -168,6 +181,14 @@ namespace Ship { gameVersion = newGameVersion; } + std::vector ResourceMgr::GetGameVersions() { + return OTR->gameVersions; + } + + void ResourceMgr::PushGameVersion(uint32_t newGameVersion) { + OTR->gameVersions.push_back(newGameVersion); + } + std::shared_ptr ResourceMgr::LoadFileAsync(const std::string& FilePath) { const std::lock_guard Lock(FileLoadMutex); // File NOT already loaded...? diff --git a/libultraship/libultraship/ResourceMgr.h b/libultraship/libultraship/ResourceMgr.h index f98b8cb8b..a8a5891f7 100644 --- a/libultraship/libultraship/ResourceMgr.h +++ b/libultraship/libultraship/ResourceMgr.h @@ -17,8 +17,9 @@ namespace Ship { // It works with the original game's assets because the entire ROM is 64MB and fits into RAM of any semi-modern PC. class ResourceMgr { public: - ResourceMgr(std::shared_ptr Context, const std::string& MainPath, const std::string& PatchesPath); - ~ResourceMgr(); + ResourceMgr(std::shared_ptr Context, const std::string& MainPath, const std::string& PatchesPath, const std::unordered_set &ValidHashes); + ResourceMgr(std::shared_ptr Context, const std::vector &OTRFiles, const std::unordered_set &ValidHashes); + ~ResourceMgr(); bool IsRunning(); bool DidLoadSuccessfully(); @@ -29,7 +30,9 @@ namespace Ship { void InvalidateResourceCache(); uint32_t GetGameVersion(); void SetGameVersion(uint32_t newGameVersion); - std::shared_ptr LoadFileAsync(const std::string& FilePath); + std::vector GetGameVersions(); + void PushGameVersion(uint32_t newGameVersion); + std::shared_ptr LoadFileAsync(const std::string& FilePath); std::shared_ptr LoadFile(const std::string& FilePath); std::shared_ptr GetCachedFile(const char* FilePath) const; std::shared_ptr LoadResource(const char* FilePath); @@ -61,5 +64,6 @@ namespace Ship { std::condition_variable FileLoadNotifier; std::condition_variable ResourceLoadNotifier; uint32_t gameVersion; + std::vector gameVersions; }; } \ No newline at end of file diff --git a/libultraship/libultraship/Scene.cpp b/libultraship/libultraship/Scene.cpp index 17d2b6976..8b71bed98 100644 --- a/libultraship/libultraship/Scene.cpp +++ b/libultraship/libultraship/Scene.cpp @@ -69,10 +69,10 @@ namespace Ship SetWind::SetWind(BinaryReader* reader) : SceneCommand(reader) { - windWest = reader->ReadByte(); - windVertical = reader->ReadByte(); - windSouth = reader->ReadByte(); - clothFlappingStrength = reader->ReadByte(); + windWest = reader->ReadInt8(); + windVertical = reader->ReadInt8(); + windSouth = reader->ReadInt8(); + clothFlappingStrength = reader->ReadInt8(); } ExitList::ExitList(BinaryReader* reader) : SceneCommand(reader) @@ -86,46 +86,46 @@ namespace Ship SetTimeSettings::SetTimeSettings(BinaryReader* reader) : SceneCommand(reader) { - hour = reader->ReadByte(); - min = reader->ReadByte(); - unk = reader->ReadByte(); + hour = reader->ReadInt8(); + min = reader->ReadInt8(); + unk = reader->ReadInt8(); } SetSkyboxModifier::SetSkyboxModifier(BinaryReader* reader) : SceneCommand(reader) { - disableSky = reader->ReadByte(); - disableSunMoon = reader->ReadByte(); + disableSky = reader->ReadInt8(); + disableSunMoon = reader->ReadInt8(); } SetEchoSettings::SetEchoSettings(BinaryReader* reader) : SceneCommand(reader) { - echo = reader->ReadByte(); + echo = reader->ReadInt8(); } SetSoundSettings::SetSoundSettings(BinaryReader* reader) : SceneCommand(reader) { - reverb = reader->ReadByte(); - nightTimeSFX = reader->ReadByte(); - musicSequence = reader->ReadByte(); + reverb = reader->ReadInt8(); + nightTimeSFX = reader->ReadInt8(); + musicSequence = reader->ReadInt8(); } SetSkyboxSettings::SetSkyboxSettings(BinaryReader* reader) : SceneCommand(reader) { - unk1 = reader->ReadByte(); - skyboxNumber = reader->ReadByte(); - cloudsType = reader->ReadByte(); - isIndoors = reader->ReadByte(); + unk1 = reader->ReadInt8(); + skyboxNumber = reader->ReadInt8(); + cloudsType = reader->ReadInt8(); + isIndoors = reader->ReadInt8(); } SetRoomBehavior::SetRoomBehavior(BinaryReader* reader) : SceneCommand(reader) { - gameplayFlags = reader->ReadByte(); + gameplayFlags = reader->ReadInt8(); gameplayFlags2 = reader->ReadInt32(); } SetCsCamera::SetCsCamera(BinaryReader* reader) : SceneCommand(reader) { - reader->ReadByte(); // camSize + reader->ReadInt8(); // camSize reader->ReadInt32(); // segOffset // OTRTODO: FINISH! @@ -143,13 +143,13 @@ namespace Ship SetMesh::SetMesh(BinaryReader* reader) : SceneCommand(reader) { - data = reader->ReadByte(); - meshHeaderType = reader->ReadByte(); + data = reader->ReadInt8(); + meshHeaderType = reader->ReadInt8(); uint32_t numPoly = 1; if (meshHeaderType != 1) - numPoly = reader->ReadByte(); + numPoly = reader->ReadInt8(); meshes.reserve(numPoly); for (uint32_t i = 0; i < numPoly; i++) @@ -158,7 +158,7 @@ namespace Ship if (meshHeaderType == 0) { - int polyType = reader->ReadByte(); + int polyType = reader->ReadInt8(); mesh.x = 0; mesh.y = 0; mesh.z = 0; @@ -166,7 +166,7 @@ namespace Ship } else if (meshHeaderType == 2) { - int polyType = reader->ReadByte(); + int polyType = reader->ReadInt8(); mesh.x = reader->ReadInt16(); mesh.y = reader->ReadInt16(); mesh.z = reader->ReadInt16(); @@ -201,7 +201,7 @@ namespace Ship mesh.images.push_back(img); } - int polyType = reader->ReadByte(); + int polyType = reader->ReadInt8(); int bp = 0; } @@ -216,7 +216,7 @@ namespace Ship SetCameraSettings::SetCameraSettings(BinaryReader* reader) : SceneCommand(reader) { - cameraMovement = reader->ReadByte(); + cameraMovement = reader->ReadInt8(); mapHighlights = reader->ReadInt32(); } @@ -229,29 +229,29 @@ namespace Ship { LightingSettings entry = LightingSettings(); - entry.ambientClrR = reader->ReadByte(); - entry.ambientClrG = reader->ReadByte(); - entry.ambientClrB = reader->ReadByte(); + entry.ambientClrR = reader->ReadInt8(); + entry.ambientClrG = reader->ReadInt8(); + entry.ambientClrB = reader->ReadInt8(); - entry.diffuseDirA_X = reader->ReadByte(); - entry.diffuseDirA_Y = reader->ReadByte(); - entry.diffuseDirA_Z = reader->ReadByte(); + entry.diffuseDirA_X = reader->ReadInt8(); + entry.diffuseDirA_Y = reader->ReadInt8(); + entry.diffuseDirA_Z = reader->ReadInt8(); - entry.diffuseClrA_R = reader->ReadByte(); - entry.diffuseClrA_G = reader->ReadByte(); - entry.diffuseClrA_B = reader->ReadByte(); + entry.diffuseClrA_R = reader->ReadInt8(); + entry.diffuseClrA_G = reader->ReadInt8(); + entry.diffuseClrA_B = reader->ReadInt8(); - entry.diffuseDirB_X = reader->ReadByte(); - entry.diffuseDirB_Y = reader->ReadByte(); - entry.diffuseDirB_Z = reader->ReadByte(); + entry.diffuseDirB_X = reader->ReadInt8(); + entry.diffuseDirB_Y = reader->ReadInt8(); + entry.diffuseDirB_Z = reader->ReadInt8(); - entry.diffuseClrB_R = reader->ReadByte(); - entry.diffuseClrB_G = reader->ReadByte(); - entry.diffuseClrB_B = reader->ReadByte(); + entry.diffuseClrB_R = reader->ReadInt8(); + entry.diffuseClrB_G = reader->ReadInt8(); + entry.diffuseClrB_B = reader->ReadInt8(); - entry.fogClrR = reader->ReadByte(); - entry.fogClrG = reader->ReadByte(); - entry.fogClrB = reader->ReadByte(); + entry.fogClrR = reader->ReadInt8(); + entry.fogClrG = reader->ReadInt8(); + entry.fogClrB = reader->ReadInt8(); entry.fogNear = reader->ReadInt16(); entry.fogFar = reader->ReadUInt16(); @@ -290,8 +290,8 @@ namespace Ship for (uint32_t i = 0; i < cnt; i++) { EntranceEntry entry = EntranceEntry(); - entry.startPositionIndex = reader->ReadByte(); - entry.roomToLoad = reader->ReadByte(); + entry.startPositionIndex = reader->ReadInt8(); + entry.roomToLoad = reader->ReadInt8(); entrances.push_back(entry); } @@ -299,7 +299,7 @@ namespace Ship SetSpecialObjects::SetSpecialObjects(BinaryReader* reader) : SceneCommand(reader) { - elfMessage = reader->ReadByte(); + elfMessage = reader->ReadInt8(); globalObject = reader->ReadInt16(); } diff --git a/libultraship/libultraship/Skeleton.cpp b/libultraship/libultraship/Skeleton.cpp index a28429350..1835f6d6a 100644 --- a/libultraship/libultraship/Skeleton.cpp +++ b/libultraship/libultraship/Skeleton.cpp @@ -8,13 +8,13 @@ namespace Ship ResourceFile::ParseFileBinary(reader, skel); - skel->type = (SkeletonType)reader->ReadByte(); - skel->limbType = (LimbType)reader->ReadByte(); + skel->type = (SkeletonType)reader->ReadInt8(); + skel->limbType = (LimbType)reader->ReadInt8(); skel->limbCount = reader->ReadUInt32(); skel->dListCount = reader->ReadUInt32(); - skel->limbTableType = (LimbType)reader->ReadByte(); + skel->limbTableType = (LimbType)reader->ReadInt8(); uint32_t limbTblCnt = reader->ReadUInt32(); skel->limbTable.reserve(limbTblCnt); diff --git a/libultraship/libultraship/SkeletonLimb.cpp b/libultraship/libultraship/SkeletonLimb.cpp index 75fd46780..dd793e495 100644 --- a/libultraship/libultraship/SkeletonLimb.cpp +++ b/libultraship/libultraship/SkeletonLimb.cpp @@ -8,8 +8,8 @@ namespace Ship ResourceFile::ParseFileBinary(reader, limb); - limb->limbType = (LimbType)reader->ReadByte(); - limb->skinSegmentType = (ZLimbSkinType)reader->ReadByte(); + limb->limbType = (LimbType)reader->ReadInt8(); + limb->skinSegmentType = (ZLimbSkinType)reader->ReadInt8(); limb->skinDList = reader->ReadString(); limb->skinVtxCnt = reader->ReadUInt16(); @@ -30,9 +30,9 @@ namespace Ship struc2.unk_0 = reader->ReadInt16(); struc2.unk_2 = reader->ReadInt16(); struc2.unk_4 = reader->ReadInt16(); - struc2.unk_6 = reader->ReadByte(); - struc2.unk_7 = reader->ReadByte(); - struc2.unk_8 = reader->ReadByte(); + struc2.unk_6 = reader->ReadInt8(); + struc2.unk_7 = reader->ReadInt8(); + struc2.unk_8 = reader->ReadInt8(); struc2.unk_9 = reader->ReadUByte(); struc.unk_8_arr.push_back(struc2); @@ -58,9 +58,9 @@ namespace Ship limb->skinDList2 = reader->ReadString(); - limb->legTransX = reader->ReadSingle(); - limb->legTransY = reader->ReadSingle(); - limb->legTransZ = reader->ReadSingle(); + limb->legTransX = reader->ReadFloat(); + limb->legTransY = reader->ReadFloat(); + limb->legTransZ = reader->ReadFloat(); limb->rotX = reader->ReadUInt16(); limb->rotY = reader->ReadUInt16(); diff --git a/libultraship/libultraship/Window.cpp b/libultraship/libultraship/Window.cpp index 821f9d87f..d731b7972 100644 --- a/libultraship/libultraship/Window.cpp +++ b/libultraship/libultraship/Window.cpp @@ -232,11 +232,11 @@ namespace Ship { return Context.lock(); } - std::shared_ptr Window::CreateInstance(const std::string Name) { + std::shared_ptr Window::CreateInstance(const std::string Name, const std::vector& OTRFiles, const std::unordered_set& ValidHashes) { if (Context.expired()) { auto Shared = std::make_shared(Name); Context = Shared; - Shared->Initialize(); + Shared->Initialize(OTRFiles, ValidHashes); return Shared; } @@ -279,10 +279,10 @@ namespace Ship { } } - void Window::Initialize() { + void Window::Initialize(const std::vector& OTRFiles, const std::unordered_set& ValidHashes) { InitializeLogging(); InitializeConfiguration(); - InitializeResourceManager(); + InitializeResourceManager(OTRFiles, ValidHashes); CreateDefaults(); InitializeControlDeck(); @@ -579,10 +579,14 @@ namespace Ship { } } - void Window::InitializeResourceManager() { - MainPath = Config->getString("Game.Main Archive", GetPathRelativeToAppDirectory("oot.otr")); + void Window::InitializeResourceManager(const std::vector& OTRFiles, const std::unordered_set& ValidHashes) { + MainPath = Config->getString("Game.Main Archive", GetAppDirectoryPath()); PatchesPath = Config->getString("Game.Patches Archive", GetAppDirectoryPath() + "/mods"); - ResMan = std::make_shared(GetInstance(), MainPath, PatchesPath); + if (OTRFiles.empty()) { + ResMan = std::make_shared(GetInstance(), MainPath, PatchesPath, ValidHashes); + } else { + ResMan = std::make_shared(GetInstance(), OTRFiles, ValidHashes); + } if (!ResMan->DidLoadSuccessfully()) { diff --git a/libultraship/libultraship/Window.h b/libultraship/libultraship/Window.h index c989aa394..490a1e0c3 100644 --- a/libultraship/libultraship/Window.h +++ b/libultraship/libultraship/Window.h @@ -2,6 +2,7 @@ #include #include +#include #include "spdlog/spdlog.h" #include "ControlDeck.h" #include "AudioPlayer.h" @@ -17,7 +18,7 @@ namespace Ship { class Window { public: static std::shared_ptr GetInstance(); - static std::shared_ptr CreateInstance(const std::string Name); + static std::shared_ptr CreateInstance(const std::string Name, const std::vector& OTRFiles = {}, const std::unordered_set& ValidHashes = {}); static std::string GetAppDirectoryPath(); static std::string GetPathRelativeToAppDirectory(const char* path); @@ -27,7 +28,7 @@ namespace Ship { void ReadSaveFile(std::filesystem::path savePath, uintptr_t addr, void* dramAddr, size_t size); void CreateDefaults(); void MainLoop(void (*MainFunction)(void)); - void Initialize(); + void Initialize(const std::vector& OTRFiles = {}, const std::unordered_set& ValidHashes = {}); void StartFrame(); void SetTargetFps(int32_t fps); void SetMaximumFrameLatency(int32_t latency); @@ -64,7 +65,7 @@ namespace Ship { void InitializeControlDeck(); void InitializeAudioPlayer(); void InitializeLogging(); - void InitializeResourceManager(); + void InitializeResourceManager(const std::vector& OTRFiles = {}, const std::unordered_set& ValidHashes = {}); void InitializeWindowManager(); std::shared_ptr Logger; @@ -84,6 +85,7 @@ namespace Ship { int32_t lastScancode; std::string Name; std::string MainPath; + std::string BasePath; std::string PatchesPath; }; } diff --git a/libultraship/libultraship/endianness.h b/libultraship/libultraship/endianness.h index 87bec316b..b08a0d57c 100644 --- a/libultraship/libultraship/endianness.h +++ b/libultraship/libultraship/endianness.h @@ -1,9 +1,30 @@ #ifndef ENDIANESS_H #define ENDIANESS_H +#ifdef __cplusplus +namespace Ship +{ + enum class Endianness + { + Little = 0, + Big = 1, + +#if (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) || defined(__BIG_ENDIAN__) + Native = Big, +#else + Native = Little, +#endif + }; +} +#endif + #ifdef _MSC_VER #include +#define BSWAP16 _byteswap_ushort +#define BSWAP32 _byteswap_ulong +#define BSWAP64 _byteswap_uint64 + #define BOMSWAP16 _byteswap_ushort #define BOMSWAP32 _byteswap_ulong #define BOMSWAP64 _byteswap_uint64 @@ -19,6 +40,11 @@ (((x) << 8) & 0x000000FF00000000) | (((x) << 24) & 0x0000FF0000000000) | \ (((x) << 40) & 0x00FF000000000000) | (((x) << 56) & 0xFF00000000000000)) #else + +#define BSWAP16 __builtin_bswap16 +#define BSWAP32 __builtin_bswap32 +#define BSWAP64 __builtin_bswap64 + #define BOMSWAP16 __builtin_bswap16 #define BOMSWAP32 __builtin_bswap32 #define BOMSWAP64 __builtin_bswap64 diff --git a/scripts/linux/appimage/soh.sh b/scripts/linux/appimage/soh.sh index 007d2aa40..9d86053dd 100644 --- a/scripts/linux/appimage/soh.sh +++ b/scripts/linux/appimage/soh.sh @@ -9,54 +9,92 @@ if [ -z ${SHIP_HOME+x} ]; then export SHIP_HOME=$PWD fi -while [[ ! -e "$SHIP_HOME"/oot.otr ]]; do - export ASSETDIR="$(mktemp -d /tmp/assets-XXXXX)" - ln -s "$HERE"/usr/bin/{assets,soh.elf,OTRGui} "$ASSETDIR" - export OLDPWD="$PWD" - mkdir -p "$ASSETDIR"/tmp - mkdir -p "$ASSETDIR"/Extract - if [ -e "$SHIP_HOME"/*.*64 ]; then - ln -s "$SHIP_HOME"/*.*64 "$ASSETDIR"/tmp/rom.z64 - cp -r "$ASSETDIR"/assets/game "$ASSETDIR"/Extract/assets - cd "$ASSETDIR" - ROMHASH=$(sha1sum -b "$ASSETDIR"/tmp/rom.z64 | awk '{ print $1 }') - case "$ROMHASH" in - cee6bc3c2a634b41728f2af8da54d9bf8cc14099) - ROM=GC_NMQ_D;; - 0227d7c0074f2d0ac935631990da8ec5914597b4) - ROM=GC_NMQ_PAL_F;; - 50bebedad9e0f10746a52b07239e47fa6c284d03) - ROM=GC_MQ_D;; - 079b855b943d6ad8bd1eb026c0ed169ecbdac7da) - ROM=GC_MQ_D;; - *) - if [ -n "$ZENITY" ]; then - zenity --error --timeout=10 --text="ROM hash $ROMHASH does not match" --title="Incorrect ROM file" --width=500 --width=200 - else - echo -e "\nrom hash does not match\n" - fi - exit;; - esac - if [ -n "$ZENITY" ]; then - (echo "# 25%"; echo "25"; sleep 2; echo "# 50%"; echo "50"; sleep 3; echo "# 75%"; echo "75"; sleep 2; echo "# 100%"; echo "100"; sleep 3) | - zenity --progress --title="OTR Generating..." --timeout=10 --percentage=0 --icon-name=soh --window-icon=soh.png --height=80 --width=400 & - 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 > /dev/null 2>&1 - cp "$ASSETDIR"/oot.otr "$SHIP_HOME" - echo "Restart $APPIMAGE to play!" - sleep 3 - rm -r "$ASSETDIR" - break - else - if [ -n "$ZENITY" ]; then - zenity --error --timeout=5 --text="Place ROM in $SHIP_HOME" --title="Missing ROM file" --width=500 --width=200 - else - echo -e "\nPlace ROM in this folder\n" - fi - exit - fi +while [[ (! -e "$SHIP_HOME"/oot.otr) || (! -e "$SHIP_HOME"/oot-mq.otr) ]]; do + for romfile in "$SHIP_HOME"/*.*64 + do + if [[ -e $romfile ]]; then + export ASSETDIR="$(mktemp -d /tmp/assets-XXXXX)" + ln -s "$HERE"/usr/bin/{assets,soh.elf,OTRGui} "$ASSETDIR" + export OLDPWD="$PWD" + mkdir -p "$ASSETDIR"/tmp + mkdir -p "$ASSETDIR"/Extract + ln -s $romfile "$ASSETDIR"/tmp/rom.z64 + cd "$ASSETDIR" + ROMHASH=$(sha1sum -b "$ASSETDIR"/tmp/rom.z64 | awk '{ print $1 }') + case "$ROMHASH" in + cee6bc3c2a634b41728f2af8da54d9bf8cc14099) + if [[ ! -e "$SHIP_HOME"/oot.otr ]]; then + ROM=GC_NMQ_D + OTRNAME="oot.otr" + fi + ;; + 0227d7c0074f2d0ac935631990da8ec5914597b4) + if [[ ! -e "$SHIP_HOME"/oot.otr ]]; then + ROM=GC_NMQ_PAL_F + OTRNAME="oot.otr" + else + continue + fi + ;; + 50bebedad9e0f10746a52b07239e47fa6c284d03) + if [[ ! -e "$SHIP_HOME"/oot-mq.otr ]]; then + ROM=GC_MQ_D + OTRNAME="oot-mq.otr" + else + continue + fi + ;; + 079b855b943d6ad8bd1eb026c0ed169ecbdac7da) + if [[ ! -e "$SHIP_HOME"/oot-mq.otr ]]; then + ROM=GC_MQ_D + OTRNAME="oot-mq.otr" + else + continue + fi + ;; + 517bd9714c73cb96c21e7c2ef640d7b55186102f) + if [[ ! -e "$SHIP_HOME"/oot-mq.otr ]]; then + ROM=GC_MQ_D + OTRNAME="oot-mq.otr" + else + continue + fi + ;; + *) + echo -e "\n$romfile - $ROMHASH rom hash does not match\n" + continue;; + esac + cp -r "$ASSETDIR"/assets/game "$ASSETDIR"/Extract/assets + if [ -n "$ZENITY" ]; then + (echo "# 25%"; echo "25"; sleep 2; echo "# 50%"; echo "50"; sleep 3; echo "# 75%"; echo "75"; sleep 2; echo "# 100%"; echo "100"; sleep 3) | + zenity --progress --title="OTR Generating..." --timeout=10 --percentage=0 --icon-name=soh --window-icon=soh.png --height=80 --width=400 & + 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 + cp "$ASSETDIR"/"$OTRNAME" "$SHIP_HOME" + else + if [ -n "$ZENITY" ]; then + zenity --error --timeout=5 --text="Place ROM in $SHIP_HOME" --title="Missing ROM file" --width=500 --width=200 + else + echo -e "\nPlace ROM in this folder\n" + fi + exit + fi + done + if [[ (! -e "$SHIP_HOME"/oot.otr) && (! -e "$SHIP_HOME"/oot-mq.otr) ]]; then + if [ -n "$ZENITY" ]; then + zenity --error --timeout=10 --text="No valid ROMs were provided, No OTR was generated." --title="Incorrect ROM file" --width=500 --width=200 + else + echo "No valid roms provided, no OTR was generated." + fi + rm -r "$ASSETDIR" + exit + else + (cd "$HERE/usr/bin"; ./soh.elf) + exit + fi + rm -r "$ASSETDIR" done (cd "$HERE/usr/bin"; ./soh.elf) exit diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 86dcff960..fb3f7ec23 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -188,6 +188,7 @@ typedef struct { u8 temporaryWeapon; u16 adultTradeItems; u8 pendingIceTrapCount; + u8 mqDungeonCount; } SaveContext; // size = 0x1428 typedef enum { diff --git a/soh/macosx/soh-macos.sh b/soh/macosx/soh-macos.sh index d2e9523df..50745ddb8 100755 --- a/soh/macosx/soh-macos.sh +++ b/soh/macosx/soh-macos.sh @@ -7,67 +7,150 @@ export RESPATH="${SNAME%/MacOS*}/Resources" export LIBPATH="${SNAME%/MacOS*}/Frameworks" export DYLD_FALLBACK_LIBRARY_PATH="$LIBPATH" -while [ ! -e "$DATA_SHARE/oot.otr" ]; do - ASSETDIR="$(mktemp -d /tmp/assets-XXXXX)" - export ASSETDIR - cp -r "$RESPATH/assets" "$ASSETDIR" - mkdir -p "$ASSETDIR"/tmp - mkdir -p "$ASSETDIR"/Extract - DROPROM="$(osascript -ss - "$ASSETDIR" <<-EOF - set romFile to choose file of type {"b64","n64","v64","z64"} with prompt "Please select your ROM:" - set destinationFolder to POSIX file "$ASSETDIR" - tell application "Finder" - duplicate romFile to destinationFolder - end tell - EOF - )" - "$DROPROM" - for rom in "$ASSETDIR"/*.*64 +if [ ! -e "$DATA_SHARE" ]; then mkdir "$DATA_SHARE"; fi + +# If either OTR doesn't exist kick off the OTR gen process +if [ ! -e "$DATA_SHARE"/oot.otr ] || [ ! -e "$DATA_SHARE"/oot-mq.otr ]; then + + # If no ROMs exist kick off the file selection prompts + while [ ! -e "$DATA_SHARE"/*.*64 ] && [ ! -e "$DATA_SHARE"/oot*.otr ]; do + + SHOULD_PROMPT_FOR_ROM=1 + while [ $SHOULD_PROMPT_FOR_ROM -eq 1 ]; do + SHOULD_PROMPT_FOR_ROM=0 + # Use osascript to prompt the user to chose a file + DROPROM=`osascript <<-EOF + set romFile to choose file of type {"b64","n64","v64","z64"} with prompt "Please select your ROM:" + return POSIX path of romFile + EOF` + + # If no rom was selected, the user cancelled, so exit + if [[ -z $DROPROM ]] && [[ -z "$UPLOAD_ANOTHER_RESULT" ]]; then + echo "No ROM selected. Exiting..." + exit 1 + elif [[ -z $DROPROM ]]; then + break; + fi + + # If an invalid rom was selected, let the user know and ask to try again + ROMHASH="$(shasum "$DROPROM" | awk '{ print $1 }')" + case "$ROMHASH" in + cee6bc3c2a634b41728f2af8da54d9bf8cc14099) + ROM_TYPE=0;; + 0227d7c0074f2d0ac935631990da8ec5914597b4) + ROM_TYPE=0;; + 50bebedad9e0f10746a52b07239e47fa6c284d03) + ROM_TYPE=1;; + 079b855b943d6ad8bd1eb026c0ed169ecbdac7da) + ROM_TYPE=1;; + 517bd9714c73cb96c21e7c2ef640d7b55186102f) + ROM_TYPE=1;; + *) + TRY_AGAIN_RESULT=`osascript <<-EOF + set alertText to "Incompatible ROM hash" + set alertMessage to "Incompatible ROM provided, would you like to try again?" + return display alert alertText \ + message alertMessage \ + as critical \ + buttons {"Cancel", "Try Again"} + EOF` + if [[ "$TRY_AGAIN_RESULT" == "button returned:Try Again" ]]; then + SHOULD_PROMPT_FOR_ROM=1 + continue; + else + echo "No ROM selected. Exiting..." + exit 1 + fi + esac + + cp "$DROPROM" "$DATA_SHARE" + + # Ask user if they would also like to select the other variant (MQ/Vanilla) + if [ $ROM_TYPE -eq 0 ] && [[ -z "$UPLOAD_ANOTHER_RESULT" ]]; then + UPLOAD_ANOTHER_RESULT=`osascript <<-EOF + set alertText to "Success" + set alertMessage to "Would you also like to provide a Master Quest ROM?" + return display alert alertText \ + message alertMessage \ + buttons {"No", "Yes"} + EOF` + elif [[ -z "$UPLOAD_ANOTHER_RESULT" ]]; then + UPLOAD_ANOTHER_RESULT=`osascript <<-EOF + set alertText to "Success" + set alertMessage to "Would you also like to provide a Vanilla (Non Master Quest) ROM?" + return display alert alertText \ + message alertMessage \ + buttons {"No", "Yes"} + EOF` + fi + + if [[ "$UPLOAD_ANOTHER_RESULT" == "button returned:Yes" ]]; then + UPLOAD_ANOTHER_RESULT="button returned:No" + SHOULD_PROMPT_FOR_ROM=1 + continue; + fi + break + done + done + + # At this point we should now have 1 or more valid roms in $DATA_SHARE directory + + # Prepare tmp dir + for ROMPATH in "$DATA_SHARE"/*.*64 do - if [ ! -e "$rom" ]; then - echo "no ROM" - osascript -e 'display dialog "Select ROM to generate OTR" giving up after 5' + ASSETDIR="$(mktemp -d /tmp/assets-XXXXX)" + export ASSETDIR + cp -r "$RESPATH/assets" "$ASSETDIR" + mkdir -p "$ASSETDIR"/tmp + mkdir -p "$ASSETDIR"/Extract + cp "$ROMPATH" "$ASSETDIR"/tmp/rom.z64 + cp -r "$ASSETDIR"/assets/game "$ASSETDIR"/Extract/assets/ + cd "$ASSETDIR" || return + + # If an invalid rom was detected, let the user know + ROMHASH="$(shasum "$ASSETDIR"/tmp/rom.z64 | awk '{ print $1 }')" + case "$ROMHASH" in + cee6bc3c2a634b41728f2af8da54d9bf8cc14099) + ROM=GC_NMQ_D + OTRNAME="oot.otr";; + 0227d7c0074f2d0ac935631990da8ec5914597b4) + ROM=GC_NMQ_PAL_F + OTRNAME="oot.otr";; + 50bebedad9e0f10746a52b07239e47fa6c284d03) + ROM=GC_MQ_D + OTRNAME="oot-mq.otr";; + 079b855b943d6ad8bd1eb026c0ed169ecbdac7da) + ROM=GC_MQ_D + OTRNAME="oot-mq.otr";; + 517bd9714c73cb96c21e7c2ef640d7b55186102f) + ROM=GC_MQ_D + OTRNAME="oot-mq.otr";; + *) + osascript -e 'display notification "One or more invalid ROM provided" with title "Ship Of Harkinian"' + rm -r "$ASSETDIR" + continue; + esac + + # Only generate OTR if we don't have on of this type yet + if [ -e "$DATA_SHARE"/"$OTRNAME" ]; then + rm -r "$ASSETDIR" + continue; + 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 + if [ -e "$ASSETDIR"/oot.otr ]; then + osascript -e 'display notification "OTR successfully generated" with title "Ship Of Harkinian"' + cp "$ASSETDIR"/oot.otr "$DATA_SHARE"/"$OTRNAME" rm -r "$ASSETDIR" - exit fi done - cp "$ASSETDIR"/*.*64 "$ASSETDIR"/tmp/rom.z64 - cp -r "$ASSETDIR"/assets/game "$ASSETDIR"/Extract/assets/ - cd "$ASSETDIR" || return - ROMHASH="$(shasum "$ASSETDIR"/tmp/rom.z64 | awk '{ print $1 }')" - case "$ROMHASH" in - cee6bc3c2a634b41728f2af8da54d9bf8cc14099) - export ROM=GC_NMQ_D;; - 0227d7c0074f2d0ac935631990da8ec5914597b4) - export ROM=GC_NMQ_PAL_F;; - 50bebedad9e0f10746a52b07239e47fa6c284d03) - export ROM=GC_MQ_D;; - 079b855b943d6ad8bd1eb026c0ed169ecbdac7da) - export ROM=GC_MQ_D;; - *) - WRONGHASH="$(osascript -ss - "$ROMHASH" <<-EOF - display dialog "Incompatible ROM hash $ROMHASH" \ - with title "Incompatible ROM hash" \ - with icon caution \ - giving up after 5 - EOF - )" - "$WRONGHASH" - rm -r "$ASSETDIR" - exit;; - esac - echo "$ROM" - osascript -e 'display notification "Processing OTR..." with title "SOH: Generating 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 - if [ -e "$PWD"/oot.otr ]; then - osascript -e 'display notification "OTR Successfully Generated" with title "SOH: Generating OTR"' - if [ ! -e "$DATA_SHARE" ]; then mkdir "$DATA_SHARE"; fi - cp "$ASSETDIR"/oot.otr "$DATA_SHARE" - rm -r "$ASSETDIR" - fi - break -done + if [ ! -e "$DATA_SHARE"/oot*.otr ]; then + osascript -e 'display notification "OTR failed to generate" with title "Ship Of Harkinian"' + exit 1; + fi +fi arch_name="$(uname -m)" launch_arch="arm64" diff --git a/soh/soh/Enhancements/bootcommands.c b/soh/soh/Enhancements/bootcommands.c index c8c162639..711e42986 100644 --- a/soh/soh/Enhancements/bootcommands.c +++ b/soh/soh/Enhancements/bootcommands.c @@ -27,10 +27,11 @@ void BootCommands_Init() CVar_RegisterS32("gHudColors", 0); //0 = N64 / 1 = NGC / 2 = Custom CVar_RegisterS32("gInvertYAxis", 1); CVar_RegisterS32("gTrailDuration", 4); // 4 = Default trail duration - if (ResourceMgr_IsGameMasterQuest()) { + if (ResourceMgr_GameHasMasterQuest() && !ResourceMgr_GameHasOriginal()) { + CVar_SetS32("gMasterQuest", 1); CVar_SetS32("gRandomizer", 0); - } else { - CVar_RegisterS32("gRandomizer", 0); + } else if (!ResourceMgr_GameHasMasterQuest()) { + CVar_SetS32("gMasterQuest", 0); } #if defined(__SWITCH__) || defined(__WIIU__) CVar_RegisterS32("gControlNav", 1); // always enable controller nav on switch/wii u diff --git a/soh/soh/Enhancements/randomizer/3drando/settings.cpp b/soh/soh/Enhancements/randomizer/3drando/settings.cpp index 293a33273..763d53f8f 100644 --- a/soh/soh/Enhancements/randomizer/3drando/settings.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/settings.cpp @@ -2535,6 +2535,13 @@ namespace Settings { } else { GanonsTrialsCount.SetSelectedIndex(cvarSettings[RSK_TRIAL_COUNT]); } + if (cvarSettings[RSK_RANDOM_MQ_DUNGEONS] == 2) { + MQDungeonCount.SetSelectedIndex(13); + } else if (cvarSettings[RSK_RANDOM_MQ_DUNGEONS] == 0) { + MQDungeonCount.SetSelectedIndex(0); + } else { + MQDungeonCount.SetSelectedIndex(cvarSettings[RSK_MQ_DUNGEON_COUNT]); + } ShuffleRewards.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_DUNGEON_REWARDS]); ShuffleSongs.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_SONGS]); Tokensanity.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_TOKENS]); diff --git a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp index c0111eb38..8c8736303 100644 --- a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp @@ -477,20 +477,13 @@ static void WriteEnabledGlitches(tinyxml2::XMLDocument& spoilerLog) { // Writes the Master Quest dungeons to the spoiler log, if there are any. static void WriteMasterQuestDungeons(tinyxml2::XMLDocument& spoilerLog) { - auto parentNode = spoilerLog.NewElement("master-quest-dungeons"); - - for (const auto* dungeon : Dungeon::dungeonList) { - if (dungeon->IsVanilla()) { - continue; + for (const auto* dungeon : Dungeon::dungeonList) { + std::string dungeonName; + if (dungeon->IsVanilla()) { + continue; + } + jsonData["masterQuestDungeons"].push_back(dungeon->GetName()); } - - auto node = parentNode->InsertNewChildElement("dungeon"); - node->SetAttribute("name", dungeon->GetName().c_str()); - } - - if (!parentNode->NoChildren()) { - spoilerLog.RootElement()->InsertEndChild(parentNode); - } } // Writes the required trials to the spoiler log, if there are any. @@ -741,7 +734,7 @@ const char* SpoilerLog_Write(int language) { //if (Settings::Logic.Is(LOGIC_GLITCHED)) { // WriteEnabledGlitches(spoilerLog); //} - //WriteMasterQuestDungeons(spoilerLog); + WriteMasterQuestDungeons(spoilerLog); WriteRequiredTrials(); WritePlaythrough(); //WriteWayOfTheHeroLocation(spoilerLog); diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index bdb216374..b2c7d4dfd 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -121,48 +121,62 @@ std::unordered_map spoilerFileTrialToEnum = { { "l'épreuve de la Lumière", RAND_INF_TRIALS_DONE_LIGHT_TRIAL } }; -std::unordered_map getItemIdToItemId = { - { GI_BOW, ITEM_BOW }, - { GI_ARROW_FIRE, ITEM_ARROW_FIRE }, - { GI_DINS_FIRE, ITEM_DINS_FIRE }, - { GI_SLINGSHOT, ITEM_SLINGSHOT }, - { GI_OCARINA_FAIRY, ITEM_OCARINA_FAIRY }, - { GI_OCARINA_OOT, ITEM_OCARINA_TIME }, - { GI_HOOKSHOT, ITEM_HOOKSHOT }, - { GI_LONGSHOT, ITEM_LONGSHOT }, - { GI_ARROW_ICE, ITEM_ARROW_ICE }, - { GI_FARORES_WIND, ITEM_FARORES_WIND }, - { GI_BOOMERANG, ITEM_BOOMERANG }, - { GI_LENS, ITEM_LENS }, - { GI_HAMMER, ITEM_HAMMER }, - { GI_ARROW_LIGHT, ITEM_ARROW_LIGHT }, - { GI_NAYRUS_LOVE, ITEM_NAYRUS_LOVE }, - { GI_BOTTLE, ITEM_BOTTLE }, - { GI_POTION_RED, ITEM_POTION_RED }, - { GI_POTION_GREEN, ITEM_POTION_GREEN }, - { GI_POTION_BLUE, ITEM_POTION_BLUE }, - { GI_FAIRY, ITEM_FAIRY }, - { GI_FISH, ITEM_FISH }, - { GI_MILK_BOTTLE, ITEM_MILK_BOTTLE }, - { GI_LETTER_RUTO, ITEM_LETTER_RUTO }, - { GI_BLUE_FIRE, ITEM_BLUE_FIRE }, - { GI_BUGS, ITEM_BUG }, - { GI_BIG_POE, ITEM_BIG_POE }, - { GI_POE, ITEM_POE }, - { GI_WEIRD_EGG, ITEM_WEIRD_EGG }, - { GI_LETTER_ZELDA, ITEM_LETTER_ZELDA }, - { GI_POCKET_EGG, ITEM_POCKET_EGG }, - { GI_COJIRO, ITEM_COJIRO }, - { GI_ODD_MUSHROOM, ITEM_ODD_MUSHROOM }, - { GI_ODD_POTION, ITEM_ODD_POTION }, - { GI_SAW, ITEM_SAW }, - { GI_SWORD_BROKEN, ITEM_SWORD_BROKEN }, - { GI_PRESCRIPTION, ITEM_PRESCRIPTION }, - { GI_FROG, ITEM_FROG }, - { GI_EYEDROPS, ITEM_EYEDROPS }, - { GI_CLAIM_CHECK, ITEM_CLAIM_CHECK } +std::unordered_map spoilerFileDungeonToScene = { + { "Deku Tree", SCENE_YDAN }, + { "Dodongo's Cavern", SCENE_DDAN }, + { "Jabu Jabu's Belly", SCENE_BDAN }, + { "Forest Temple", SCENE_BMORI1 }, + { "Fire Temple", SCENE_HIDAN }, + { "Water Temple", SCENE_MIZUSIN }, + { "Spirit Temple", SCENE_JYASINZOU }, + { "Shadow Temple", SCENE_HAKADAN }, + { "Bottom of the Well", SCENE_HAKADANCH }, + { "Ice Cavern", SCENE_ICE_DOUKUTO }, + { "Gerudo Training Grounds", SCENE_MEN }, + { "Ganon's Castle", SCENE_GANONTIKA } }; +std::unordered_map + getItemIdToItemId = { { GI_BOW, ITEM_BOW }, + { GI_ARROW_FIRE, ITEM_ARROW_FIRE }, + { GI_DINS_FIRE, ITEM_DINS_FIRE }, + { GI_SLINGSHOT, ITEM_SLINGSHOT }, + { GI_OCARINA_FAIRY, ITEM_OCARINA_FAIRY }, + { GI_OCARINA_OOT, ITEM_OCARINA_TIME }, + { GI_HOOKSHOT, ITEM_HOOKSHOT }, + { GI_LONGSHOT, ITEM_LONGSHOT }, + { GI_ARROW_ICE, ITEM_ARROW_ICE }, + { GI_FARORES_WIND, ITEM_FARORES_WIND }, + { GI_BOOMERANG, ITEM_BOOMERANG }, + { GI_LENS, ITEM_LENS }, + { GI_HAMMER, ITEM_HAMMER }, + { GI_ARROW_LIGHT, ITEM_ARROW_LIGHT }, + { GI_NAYRUS_LOVE, ITEM_NAYRUS_LOVE }, + { GI_BOTTLE, ITEM_BOTTLE }, + { GI_POTION_RED, ITEM_POTION_RED }, + { GI_POTION_GREEN, ITEM_POTION_GREEN }, + { GI_POTION_BLUE, ITEM_POTION_BLUE }, + { GI_FAIRY, ITEM_FAIRY }, + { GI_FISH, ITEM_FISH }, + { GI_MILK_BOTTLE, ITEM_MILK_BOTTLE }, + { GI_LETTER_RUTO, ITEM_LETTER_RUTO }, + { GI_BLUE_FIRE, ITEM_BLUE_FIRE }, + { GI_BUGS, ITEM_BUG }, + { GI_BIG_POE, ITEM_BIG_POE }, + { GI_POE, ITEM_POE }, + { GI_WEIRD_EGG, ITEM_WEIRD_EGG }, + { GI_LETTER_ZELDA, ITEM_LETTER_ZELDA }, + { GI_POCKET_EGG, ITEM_POCKET_EGG }, + { GI_COJIRO, ITEM_COJIRO }, + { GI_ODD_MUSHROOM, ITEM_ODD_MUSHROOM }, + { GI_ODD_POTION, ITEM_ODD_POTION }, + { GI_SAW, ITEM_SAW }, + { GI_SWORD_BROKEN, ITEM_SWORD_BROKEN }, + { GI_PRESCRIPTION, ITEM_PRESCRIPTION }, + { GI_FROG, ITEM_FROG }, + { GI_EYEDROPS, ITEM_EYEDROPS }, + { GI_CLAIM_CHECK, ITEM_CLAIM_CHECK } }; + std::unordered_map SpoilerfileSettingNameToEnum = { { "Open Settings:Forest", RSK_FOREST }, { "Open Settings:Kakariko Gate", RSK_KAK_GATE }, @@ -212,6 +226,7 @@ std::unordered_map SpoilerfileSettingNameToEn { "Timesaver Settings:Complete Mask Quest", RSK_COMPLETE_MASK_QUEST }, { "Timesaver Settings:Skip Scarecrow's Song", RSK_SKIP_SCARECROWS_SONG }, { "Timesaver Settings:Enable Glitch-Useful Cutscenes", RSK_ENABLE_GLITCH_CUTSCENES }, + { "World Settings:MQ Dungeon Count", RSK_MQ_DUNGEON_COUNT } }; std::string sanitize(std::string stringValue) { @@ -491,6 +506,13 @@ void Randomizer::LoadRequiredTrials(const char* spoilerFileName) { } } +void Randomizer::LoadMasterQuestDungeons(const char* spoilerFileName) { + if (strcmp(spoilerFileName, "") != 0) { + ParseMasterQuestDungeonsFile(spoilerFileName); + } + gSaveContext.mqDungeonCount = this->masterQuestDungeons.size(); +} + void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) { std::ifstream spoilerFileStream(sanitize(spoilerFileName)); if (!spoilerFileStream) @@ -631,6 +653,7 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) { case RSK_SHUFFLE_ADULT_TRADE: case RSK_SHUFFLE_MAGIC_BEANS: case RSK_RANDOM_TRIALS: + case RSK_RANDOM_MQ_DUNGEONS: case RSK_STARTING_DEKU_SHIELD: case RSK_STARTING_KOKIRI_SWORD: case RSK_COMPLETE_MASK_QUEST: @@ -806,6 +829,13 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) { gSaveContext.randoSettings[index].value = 3; } break; + case RSK_MQ_DUNGEON_COUNT: + if (it.value() == "Random") { + gSaveContext.randoSettings[index].value = 13; + } + numericValueString = it.value(); + gSaveContext.randoSettings[index].value = std::stoi(numericValueString); + break; } } } @@ -987,6 +1017,27 @@ void Randomizer::ParseRequiredTrialsFile(const char* spoilerFileName) { } } +void Randomizer::ParseMasterQuestDungeonsFile(const char* spoilerFileName) { + std::ifstream spoilerFileStream(sanitize(spoilerFileName)); + if (!spoilerFileStream) { + return; + } + + this->masterQuestDungeons.clear(); + + try { + json spoilerFileJson; + spoilerFileStream >> spoilerFileJson; + json mqDungeonsJson = spoilerFileJson["masterQuestDungeons"]; + + for (auto it = mqDungeonsJson.begin(); it != mqDungeonsJson.end(); it++) { + this->masterQuestDungeons.emplace(spoilerFileDungeonToScene[it.value()]); + } + } catch (const std::exception& e) { + return; + } +} + void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent) { std::ifstream spoilerFileStream(sanitize(spoilerFileName)); if (!spoilerFileStream) @@ -3733,23 +3784,36 @@ void GenerateRandomizerImgui() { // Link's Pocket has to have a dungeon reward if the other rewards are shuffled to end of dungeon. cvarSettings[RSK_LINKS_POCKET] = CVar_GetS32("gRandomizeShuffleDungeonReward", 0) != 0 ? CVar_GetS32("gRandomizeLinksPocket", 0) : 0; - - // todo: this efficently when we build out cvar array support - std::set excludedLocations; - std::stringstream excludedLocationStringStream(CVar_GetString("gRandomizeExcludedLocations", "")); - std::string excludedLocationString; - while(getline(excludedLocationStringStream, excludedLocationString, ',')) { - excludedLocations.insert((RandomizerCheck)std::stoi(excludedLocationString)); + if (OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal()) { + // If both OTRs are loaded. + cvarSettings[RSK_RANDOM_MQ_DUNGEONS] = CVar_GetS32("gRandomizeMqDungeons", 0); + cvarSettings[RSK_MQ_DUNGEON_COUNT] = CVar_GetS32("gRandomizeMqDungeonCount", 0); + } else if (OTRGlobals::Instance->HasMasterQuest()) { + // If only Master Quest is loaded. + cvarSettings[RSK_RANDOM_MQ_DUNGEONS] = 1; + cvarSettings[RSK_MQ_DUNGEON_COUNT] = 12; + } else { + // If only Original Quest is loaded. + cvarSettings[RSK_RANDOM_MQ_DUNGEONS] = 1; + cvarSettings[RSK_MQ_DUNGEON_COUNT] = 0; } - RandoMain::GenerateRando(cvarSettings, excludedLocations); + // todo: this efficently when we build out cvar array support + std::set excludedLocations; + std::stringstream excludedLocationStringStream(CVar_GetString("gRandomizeExcludedLocations", "")); + std::string excludedLocationString; + while (getline(excludedLocationStringStream, excludedLocationString, ',')) { + excludedLocations.insert((RandomizerCheck)std::stoi(excludedLocationString)); + } - CVar_SetS32("gRandoGenerating", 0); - CVar_Save(); - CVar_Load(); + RandoMain::GenerateRando(cvarSettings, excludedLocations); - generated = 1; -} + CVar_SetS32("gRandoGenerating", 0); + CVar_Save(); + CVar_Load(); + + generated = 1; + } void DrawRandoEditor(bool& open) { if (generated) { @@ -3762,11 +3826,6 @@ void DrawRandoEditor(bool& open) { return; } - if (ResourceMgr_IsGameMasterQuest()) { - ImGui::Text("Master Quest Randomizer is not currently supported."); - return; - } - // Randomizer settings // Logic Settings const char* randoLogicRules[2] = { "Glitchless", "No logic" }; @@ -3780,6 +3839,7 @@ void DrawRandoEditor(bool& open) { const char* randoRainbowBridge[7] = { "Vanilla", "Always open", "Stones", "Medallions", "Dungeon rewards", "Dungeons", "Tokens" }; const char* randoGanonsTrial[3] = { "Skip", "Set Number", "Random Number" }; + const char* randoMqDungeons[3] = { "None", "Set Number", "Random Number" }; // World Settings const char* randoStartingAge[3] = { "Child", "Adult", "Random" }; @@ -3830,6 +3890,14 @@ void DrawRandoEditor(bool& open) { return; } + if (OTRGlobals::Instance->HasMasterQuest() && !OTRGlobals::Instance->HasOriginal()) { + ImGui::Text("Coming Soon! Randomizer is currently not compatible with Master Quest Dungeons.\nFor now, please " + "generate an " + "OTR using a non-Master Quest rom to play the Randomizer"); + ImGui::End(); + return; + } + bool disableEditingRandoSettings = CVar_GetS32("gRandoGenerating", 0) || CVar_GetS32("gOnFileSelectNameEntry", 0); ImGui::PushItemFlag(ImGuiItemFlags_Disabled, disableEditingRandoSettings); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * (disableEditingRandoSettings ? 0.5f : 1.0f)); @@ -4071,6 +4139,29 @@ void DrawRandoEditor(bool& open) { UIWidgets::PaddedSeparator(); + //MQ Dungeons - Commented out until Logic can be updated to account for MQ Dungeons + // if (OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal()) { + // ImGui::PushItemWidth(-FLT_MIN); + // ImGui::Text("Master Quest Dungeons"); + // UIWidgets::InsertHelpHoverText( + // "Sets the number of Master Quest Dungeons that are shuffled into the pool.\n" + // "\n" + // "None - All Dungeons will be their Vanilla versions.\n" + // "\n" + // "Set Number - Select a number of dungeons that will be their Master Quest versions" + // "using the slider below. Which dungeons are set to be the Master Quest variety will be random.\n" + // "\n" + // "Random Number - A Random number and set of dungeons will be their Master Quest varieties." + // ); + // UIWidgets::EnhancementCombobox("gRandomizeMqDungeons", randoMqDungeons, 3, 1); + // ImGui::PopItemWidth(); + // if (CVar_GetS32("gRandomizeMqDungeons", 1) == 1) { + // ImGui::Dummy(ImVec2(0.0f, 0.0f)); + // UIWidgets::EnhancementSliderInt("Master Quest Dungeon Count: %d", "##RandoMqDungeonCount", + // "gRandomizeMqDungeonCount", 1, 12, "", 12, true); + // } + // } + ImGui::EndChild(); // COLUMN 3 - Shuffle Entrances diff --git a/soh/soh/Enhancements/randomizer/randomizer.h b/soh/soh/Enhancements/randomizer/randomizer.h index fda7fb016..c474b61e0 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.h +++ b/soh/soh/Enhancements/randomizer/randomizer.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "../../../include/ultra64.h" #include "../../../include/z64item.h" @@ -24,6 +25,7 @@ class Randomizer { void ParseRandomizerSettingsFile(const char* spoilerFileName); void ParseHintLocationsFile(const char* spoilerFileName); void ParseRequiredTrialsFile(const char* spoilerFileName); + void ParseMasterQuestDungeonsFile(const char* spoilerFileName); void ParseItemLocationsFile(const char* spoilerFileName, bool silent); bool IsItemVanilla(RandomizerGet randoGet); GetItemEntry GetItemEntryFromRGData(RandomizerGetData rgData, GetItemID ogItemId, bool checkObtainability = true); @@ -41,6 +43,7 @@ class Randomizer { // Public for now to be accessed by SaveManager, will be made private again soon :tm: std::unordered_map trialsRequired; + std::unordered_set masterQuestDungeons; std::unordered_map merchantPrices; static Sprite* GetSeedTexture(uint8_t index); @@ -52,6 +55,7 @@ class Randomizer { void LoadMerchantMessages(const char* spoilerFileName); void LoadItemLocations(const char* spoilerFileName, bool silent); void LoadRequiredTrials(const char* spoilerFileName); + void LoadMasterQuestDungeons(const char* spoilerFileName); bool IsTrialRequired(RandomizerInf trial); u8 GetRandoSettingValue(RandomizerSettingKey randoSettingKey); RandomizerCheck GetCheckFromActor(s16 actorId, s16 sceneNum, s16 actorParams); diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 8d52d4cd9..e1f8b95f6 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -1034,6 +1034,8 @@ typedef enum { RSK_ENABLE_BOMBCHU_DROPS, RSK_BOMBCHUS_IN_LOGIC, RSK_LINKS_POCKET, + RSK_RANDOM_MQ_DUNGEONS, + RSK_MQ_DUNGEON_COUNT, RSK_MAX } RandomizerSettingKey; diff --git a/soh/soh/GameMenuBar.cpp b/soh/soh/GameMenuBar.cpp index cb8261563..d4059adad 100644 --- a/soh/soh/GameMenuBar.cpp +++ b/soh/soh/GameMenuBar.cpp @@ -26,6 +26,7 @@ #include "include/global.h" #include "include/z64audio.h" #include "soh/SaveManager.h" +#include "OTRGlobals.h" #define EXPERIMENTAL() \ ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 50, 50, 255)); \ @@ -94,6 +95,35 @@ namespace GameMenuBar { Audio_SetGameVolume(SEQ_SFX, CVar_GetFloat("gFanfareVolume", 1)); } + bool MasterQuestCheckboxDisabled() { + return !(OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal()) + || CVar_GetS32("gRandomizer", 0); + } + + std::string MasterQuestDisabledTooltip() { + std::string tooltip = ""; + if (CVar_GetS32("gRandomizer", 0)) { + tooltip = "This option is disabled because you have the Randomizer enabled.\nRandomizer has it's own " + "settings surrounding Master Quest dungeons\nthat you can set from the Randomizer Settings Menu."; + } + if (!OTRGlobals::Instance->HasOriginal()) { + tooltip = "This option is force-enabled because you have only loaded the\noot-mq.otr file. If you wish to " + "play the Original Quest,\nplease provide the oot.otr file."; + } + if (!OTRGlobals::Instance->HasMasterQuest()) { + tooltip = "This option is disabled because you have only loaded the\noot.otr file. If you wish to play " + "the Master Quest,\nplease proivde the oot-mq.otr file."; + } + return tooltip; + } + + UIWidgets::CheckboxGraphics MasterQuestDisabledGraphic() { + if (!OTRGlobals::Instance->HasOriginal()) { + return UIWidgets::CheckboxGraphics::Checkmark; + } + return UIWidgets::CheckboxGraphics::Cross; + } + void applyEnhancementPresetDefault(void) { // D-pad Support on Pause CVar_SetS32("gDpadPause", 0); @@ -1254,6 +1284,10 @@ namespace GameMenuBar { UIWidgets::Tooltip("Holding down B skips text"); UIWidgets::PaddedEnhancementCheckbox("Free Camera", "gFreeCamera", true, false); UIWidgets::Tooltip("Enables camera control\nNote: You must remap C buttons off of the right stick in the controller config menu, and map the camera stick to the right stick."); + UIWidgets::PaddedEnhancementCheckbox("Master Quest", "gMasterQuest", true, false, MasterQuestCheckboxDisabled(), MasterQuestDisabledTooltip().c_str(), MasterQuestDisabledGraphic()); + UIWidgets::Tooltip("Enables Master Quest.\n\nWhen checked, any non-rando save files you create will be " + "Master Quest save files. Master Quest save files will still have Master Quest dungeons " + "regardless of this setting and require oot-mq.otr to be present in order to play."); #ifdef __SWITCH__ UIWidgets::Spacer(0); diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index d2986892a..2fa52a624 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -77,14 +77,78 @@ CustomMessageManager* CustomMessageManager::Instance; ItemTableManager* ItemTableManager::Instance; OTRGlobals::OTRGlobals() { - context = Ship::Window::CreateInstance("Ship of Harkinian"); + std::vector OTRFiles; + std::string mqPath = Ship::Window::GetPathRelativeToAppDirectory("oot-mq.otr"); + if (std::filesystem::exists(mqPath)) { + OTRFiles.push_back(mqPath); + } + std::string ootPath = Ship::Window::GetPathRelativeToAppDirectory("oot.otr"); + if (std::filesystem::exists(ootPath)) { + OTRFiles.push_back(ootPath); + } + std::unordered_set ValidHashes = { + OOT_PAL_MQ, + OOT_NTSC_JP_MQ, + OOT_NTSC_US_MQ, + OOT_PAL_GC_MQ_DBG, + OOT_NTSC_10, + OOT_NTSC_11, + OOT_NTSC_12, + OOT_PAL_10, + OOT_PAL_11, + OOT_NTSC_JP_GC_CE, + OOT_NTSC_JP_GC, + OOT_NTSC_US_GC, + OOT_PAL_GC, + OOT_PAL_GC_DBG1, + OOT_PAL_GC_DBG2 + }; + context = Ship::Window::CreateInstance("Ship of Harkinian", OTRFiles, ValidHashes); gSaveStateMgr = std::make_shared(); gRandomizer = std::make_shared(); + + hasMasterQuest = hasOriginal = false; + + auto versions = context->GetResourceManager()->GetGameVersions(); + + for (uint32_t version : versions) { + switch (version) { + case OOT_PAL_MQ: + case OOT_NTSC_JP_MQ: + case OOT_NTSC_US_MQ: + case OOT_PAL_GC_MQ_DBG: + hasMasterQuest = true; + break; + case OOT_NTSC_10: + case OOT_NTSC_11: + case OOT_NTSC_12: + case OOT_PAL_10: + case OOT_PAL_11: + case OOT_NTSC_JP_GC_CE: + case OOT_NTSC_JP_GC: + case OOT_NTSC_US_GC: + case OOT_PAL_GC: + case OOT_PAL_GC_DBG1: + case OOT_PAL_GC_DBG2: + hasOriginal = true; + break; + default: + break; + } + } } OTRGlobals::~OTRGlobals() { } +bool OTRGlobals::HasMasterQuest() { + return hasMasterQuest; +} + +bool OTRGlobals::HasOriginal() { + return hasOriginal; +} + struct ExtensionEntry { std::string path; std::string ext; @@ -341,7 +405,10 @@ extern "C" void InitOTR() { if (!t->bHasLoadError) { - uint32_t gameVersion = LE32SWAP(*((uint32_t*)t->buffer.get())); + Ship::BinaryReader reader(t->buffer.get(), t->dwBufferSize); + Ship::Endianness endianness = (Ship::Endianness)reader.ReadUByte(); + reader.SetEndianness(endianness); + uint32_t gameVersion = reader.ReadUInt32(); OTRGlobals::Instance->context->GetResourceManager()->SetGameVersion(gameVersion); } @@ -549,32 +616,37 @@ extern "C" uint32_t ResourceMgr_GetGameVersion() return OTRGlobals::Instance->context->GetResourceManager()->GetGameVersion(); } -extern "C" uint32_t ResourceMgr_IsGameMasterQuest() { - uint32_t version = OTRGlobals::Instance->context->GetResourceManager()->GetGameVersion(); - - switch (version) { - case OOT_PAL_MQ: - case OOT_NTSC_JP_MQ: - case OOT_NTSC_US_MQ: - case OOT_PAL_GC_MQ_DBG: - return 1; - case OOT_NTSC_10: - case OOT_NTSC_11: - case OOT_NTSC_12: - case OOT_PAL_10: - case OOT_PAL_11: - case OOT_NTSC_JP_GC_CE: - case OOT_NTSC_JP_GC: - case OOT_NTSC_US_GC: - case OOT_PAL_GC: - case OOT_PAL_GC_DBG1: - case OOT_PAL_GC_DBG2: - return 0; - default: - SPDLOG_WARN("Unknown rom detected. Defaulting to Non-mq {:x}", version); - return 0; - +uint32_t IsGameMasterQuest() { + uint32_t value = 0; + if (OTRGlobals::Instance->HasMasterQuest()) { + if (!OTRGlobals::Instance->HasOriginal()) { + value = 1; + } else if (gSaveContext.isMasterQuest) { + value = 1; + } else { + value = 0; + if (gSaveContext.n64ddFlag) { + if (!OTRGlobals::Instance->gRandomizer->masterQuestDungeons.empty()) { + if (gGlobalCtx != NULL && OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(gGlobalCtx->sceneNum)) { + value = 1; + } + } + } + } } + return value; +} + +extern "C" uint32_t ResourceMgr_GameHasMasterQuest() { + return OTRGlobals::Instance->HasMasterQuest(); +} + +extern "C" uint32_t ResourceMgr_GameHasOriginal() { + return OTRGlobals::Instance->HasOriginal(); +} + +extern "C" uint32_t ResourceMgr_IsGameMasterQuest() { + return IsGameMasterQuest(); } extern "C" void ResourceMgr_CacheDirectory(const char* resName) { @@ -608,6 +680,17 @@ extern "C" void ResourceMgr_LoadFile(const char* resName) { OTRGlobals::Instance->context->GetResourceManager()->LoadResource(resName); } +std::shared_ptr ResourceMgr_LoadResource(const char* path) { + std::string Path = path; + if (ResourceMgr_IsGameMasterQuest()) { + size_t pos = 0; + if ((pos = Path.find("/nonmq/", 0)) != std::string::npos) { + Path.replace(pos, 7, "/mq/"); + } + } + return OTRGlobals::Instance->context->GetResourceManager()->LoadResource(Path.c_str()); +} + extern "C" char* ResourceMgr_LoadFileRaw(const char* resName) { return OTRGlobals::Instance->context->GetResourceManager()->LoadFile(resName)->buffer.get(); } @@ -673,14 +756,22 @@ extern "C" uint16_t ResourceMgr_LoadTexHeightByName(char* texPath); extern "C" uint32_t ResourceMgr_LoadTexSizeByName(const char* texPath); extern "C" char* ResourceMgr_LoadTexOrDListByName(const char* filePath) { - auto res = OTRGlobals::Instance->context->GetResourceManager()->LoadResource(filePath); + auto res = ResourceMgr_LoadResource(filePath); if (res->resType == Ship::ResourceType::DisplayList) return (char*)&((std::static_pointer_cast(res))->instructions[0]); else if (res->resType == Ship::ResourceType::Array) return (char*)(std::static_pointer_cast(res))->vertices.data(); - else - return ResourceMgr_LoadTexByName(filePath); + else { + std::string Path = filePath; + if (ResourceMgr_IsGameMasterQuest()) { + size_t pos = 0; + if ((pos = Path.find("/nonmq/", 0)) != std::string::npos) { + Path.replace(pos, 7, "/mq/"); + } + } + return ResourceMgr_LoadTexByName(Path.c_str()); + } } extern "C" Sprite* GetSeedTexture(uint8_t index) { @@ -688,16 +779,14 @@ extern "C" Sprite* GetSeedTexture(uint8_t index) { } extern "C" char* ResourceMgr_LoadPlayerAnimByName(const char* animPath) { - auto anim = std::static_pointer_cast( - OTRGlobals::Instance->context->GetResourceManager()->LoadResource(animPath)); + auto anim = std::static_pointer_cast(ResourceMgr_LoadResource(animPath)); return (char*)&anim->limbRotData[0]; } extern "C" Gfx* ResourceMgr_LoadGfxByName(const char* path) { - auto res = std::static_pointer_cast( - OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path)); + auto res = std::static_pointer_cast(ResourceMgr_LoadResource(path)); return (Gfx*)&res->instructions[0]; } @@ -753,14 +842,13 @@ extern "C" void ResourceMgr_UnpatchGfxByName(const char* path, const char* patch extern "C" char* ResourceMgr_LoadArrayByName(const char* path) { - auto res = std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path)); + auto res = std::static_pointer_cast(ResourceMgr_LoadResource(path)); return (char*)res->scalars.data(); } extern "C" char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path) { - auto res = - std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path)); + auto res = std::static_pointer_cast(ResourceMgr_LoadResource(path)); if (res->cachedGameAsset != nullptr) return (char*)res->cachedGameAsset; @@ -782,7 +870,7 @@ extern "C" char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path) { extern "C" CollisionHeader* ResourceMgr_LoadColByName(const char* path) { - auto colRes = std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path)); + auto colRes = std::static_pointer_cast(ResourceMgr_LoadResource(path)); if (colRes->cachedGameAsset != nullptr) return (CollisionHeader*)colRes->cachedGameAsset; @@ -876,8 +964,8 @@ extern "C" CollisionHeader* ResourceMgr_LoadColByName(const char* path) extern "C" Vtx* ResourceMgr_LoadVtxByName(const char* path) { - auto res = std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path)); - return (Vtx*)res->vertices.data(); + auto res = std::static_pointer_cast(ResourceMgr_LoadResource(path)); + return (Vtx*)res->vertices.data(); } extern "C" SequenceData ResourceMgr_LoadSeqByName(const char* path) @@ -971,8 +1059,7 @@ extern "C" SoundFontSample* ResourceMgr_LoadAudioSample(const char* path) if (cSample != nullptr) return cSample; - auto sample = std::static_pointer_cast( - OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path)); + auto sample = std::static_pointer_cast(ResourceMgr_LoadResource(path)); if (sample == nullptr) return NULL; @@ -1018,8 +1105,7 @@ extern "C" SoundFontSample* ResourceMgr_LoadAudioSample(const char* path) } extern "C" SoundFont* ResourceMgr_LoadAudioSoundFont(const char* path) { - auto soundFont = - std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path)); + auto soundFont = std::static_pointer_cast(ResourceMgr_LoadResource(path)); if (soundFont == nullptr) return NULL; @@ -1162,8 +1248,7 @@ extern "C" int ResourceMgr_OTRSigCheck(char* imgData) } extern "C" AnimationHeaderCommon* ResourceMgr_LoadAnimByName(const char* path) { - auto res = std::static_pointer_cast( - OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path)); + auto res = std::static_pointer_cast(ResourceMgr_LoadResource(path)); if (res->cachedGameAsset != nullptr) return (AnimationHeaderCommon*)res->cachedGameAsset; @@ -1231,7 +1316,7 @@ extern "C" AnimationHeaderCommon* ResourceMgr_LoadAnimByName(const char* path) { } extern "C" SkeletonHeader* ResourceMgr_LoadSkeletonByName(const char* path) { - auto res = std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path)); + auto res = std::static_pointer_cast(ResourceMgr_LoadResource(path)); if (res->cachedGameAsset != nullptr) return (SkeletonHeader*)res->cachedGameAsset; @@ -1264,8 +1349,7 @@ extern "C" SkeletonHeader* ResourceMgr_LoadSkeletonByName(const char* path) { for (size_t i = 0; i < res->limbTable.size(); i++) { std::string limbStr = res->limbTable[i]; - auto limb = std::static_pointer_cast( - OTRGlobals::Instance->context->GetResourceManager()->LoadResource(limbStr.c_str())); + auto limb = std::static_pointer_cast(ResourceMgr_LoadResource(limbStr.c_str())); if (limb->limbType == Ship::LimbType::LOD) { LodLimb* limbC = (LodLimb*)malloc(sizeof(LodLimb)); @@ -1419,7 +1503,7 @@ extern "C" SkeletonHeader* ResourceMgr_LoadSkeletonByName(const char* path) { extern "C" s32* ResourceMgr_LoadCSByName(const char* path) { - auto res = std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path)); + auto res = std::static_pointer_cast(ResourceMgr_LoadResource(path)); return (s32*)res->commands.data(); } @@ -1670,6 +1754,10 @@ extern "C" void Randomizer_LoadRequiredTrials(const char* spoilerFileName) { OTRGlobals::Instance->gRandomizer->LoadRequiredTrials(spoilerFileName); } +extern "C" void Randomizer_LoadMasterQuestDungeons(const char* spoilerFileName) { + OTRGlobals::Instance->gRandomizer->LoadMasterQuestDungeons(spoilerFileName); +} + extern "C" void Randomizer_LoadItemLocations(const char* spoilerFileName, bool silent) { OTRGlobals::Instance->gRandomizer->LoadItemLocations(spoilerFileName, silent); } diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index 6f4715b8c..1242dfb4d 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -25,13 +25,20 @@ public: OTRGlobals(); ~OTRGlobals(); + bool HasMasterQuest(); + bool HasOriginal(); + private: void CheckSaveFile(size_t sramSize) const; + bool hasMasterQuest; + bool hasOriginal; }; + +uint32_t IsGameMasterQuest(); #endif #ifndef __cplusplus -void InitOTR(void); + void InitOTR(void); void DeinitOTR(void); void VanillaItemTable_Init(); void OTRAudio_Init(); @@ -43,8 +50,10 @@ void OTRGfxPrint(const char* str, void* printer, void (*printImpl)(void*, char)) void OTRGetPixelDepthPrepare(float x, float y); uint16_t OTRGetPixelDepth(float x, float y); int32_t OTRGetLastScancode(); -uint32_t ResourceMgr_GetGameVersion(); uint32_t ResourceMgr_IsGameMasterQuest(); +uint32_t ResourceMgr_GameHasMasterQuest(); +uint32_t ResourceMgr_GameHasOriginal(); +uint32_t ResourceMgr_GetGameVersion(); void ResourceMgr_CacheDirectory(const char* resName); char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize); void ResourceMgr_LoadFile(const char* resName); @@ -106,6 +115,7 @@ ShopItemIdentity Randomizer_IdentifyShopItem(s32 sceneNum, u8 slotIndex); void Randomizer_LoadHintLocations(const char* spoilerFileName); void Randomizer_LoadMerchantMessages(const char* spoilerFileName); void Randomizer_LoadRequiredTrials(const char* spoilerFileName); +void Randomizer_LoadMasterQuestDungeons(const char* spoilerFileName); void Randomizer_LoadItemLocations(const char* spoilerFileName, bool silent); bool Randomizer_IsTrialRequired(RandomizerInf trial); GetItemEntry Randomizer_GetItemFromActor(s16 actorId, s16 sceneNum, s16 actorParams, GetItemID ogId); diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 30bdc7fb5..2bb272ff5 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -50,7 +50,8 @@ SaveManager::SaveManager() { } info.randoSave = 0; - info.isMasterQuest = 0; + info.requiresMasterQuest = 0; + info.requiresOriginal = 0; } } @@ -194,6 +195,15 @@ void SaveManager::LoadRandomizerVersion2() { randomizer->merchantPrices[rc] = price; }); }); + + SaveManager::Instance->LoadData("masterQuestDungeonCount", gSaveContext.mqDungeonCount); + + OTRGlobals::Instance->gRandomizer->masterQuestDungeons.clear(); + SaveManager::Instance->LoadArray("masterQuestDungeons", randomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT), [&](size_t i) { + uint16_t scene; + SaveManager::Instance->LoadData("", scene); + randomizer->masterQuestDungeons.emplace(scene); + }); } void SaveManager::SaveRandomizer() { @@ -245,6 +255,16 @@ void SaveManager::SaveRandomizer() { SaveManager::Instance->SaveData("price", merchantPrices[i].second); }); }); + + SaveManager::Instance->SaveData("masterQuestDungeonCount", gSaveContext.mqDungeonCount); + + std::vector masterQuestDungeons; + for (const auto scene : randomizer->masterQuestDungeons) { + masterQuestDungeons.push_back(scene); + } + SaveManager::Instance->SaveArray("masterQuestDungeons", masterQuestDungeons.size(), [&](size_t i) { + SaveManager::Instance->SaveData("", masterQuestDungeons[i]); + }); } void SaveManager::Init() { @@ -318,7 +338,8 @@ void SaveManager::InitMeta(int fileNum) { } fileMetaInfo[fileNum].randoSave = gSaveContext.n64ddFlag; - fileMetaInfo[fileNum].isMasterQuest = gSaveContext.isMasterQuest; + fileMetaInfo[fileNum].requiresMasterQuest = gSaveContext.isMasterQuest || gSaveContext.mqDungeonCount > 0; + fileMetaInfo[fileNum].requiresOriginal = !gSaveContext.isMasterQuest || gSaveContext.mqDungeonCount < 12; } void SaveManager::InitFile(bool isDebug) { @@ -465,7 +486,7 @@ void SaveManager::InitFileNormal() { gSaveContext.infTable[29] = 1; gSaveContext.sceneFlags[5].swch = 0x40000000; - gSaveContext.isMasterQuest = ResourceMgr_IsGameMasterQuest(); + gSaveContext.isMasterQuest = CVar_GetS32("gMasterQuest", 0) && !CVar_GetS32("gRandomizer", 0); //RANDOTODO (ADD ITEMLOCATIONS TO GSAVECONTEXT) } @@ -1297,7 +1318,8 @@ void SaveManager::CopyZeldaFile(int from, int to) { fileMetaInfo[to].defense = fileMetaInfo[from].defense; fileMetaInfo[to].health = fileMetaInfo[from].health; fileMetaInfo[to].randoSave = fileMetaInfo[from].randoSave; - fileMetaInfo[to].isMasterQuest = fileMetaInfo[from].isMasterQuest; + fileMetaInfo[to].requiresMasterQuest = fileMetaInfo[from].requiresMasterQuest; + fileMetaInfo[to].requiresOriginal = fileMetaInfo[from].requiresOriginal; } void SaveManager::DeleteZeldaFile(int fileNum) { diff --git a/soh/soh/SaveManager.h b/soh/soh/SaveManager.h index 9889806b2..6bcfbb01e 100644 --- a/soh/soh/SaveManager.h +++ b/soh/soh/SaveManager.h @@ -10,7 +10,8 @@ typedef struct { u32 questItems; s8 defense; u16 health; - u32 isMasterQuest; + u32 requiresMasterQuest; + u32 requiresOriginal; u8 seedHash[5]; u8 randoSave; } SaveFileMetaInfo; diff --git a/soh/soh/z_play_otr.cpp b/soh/soh/z_play_otr.cpp index 52b71c905..1e7ee687c 100644 --- a/soh/soh/z_play_otr.cpp +++ b/soh/soh/z_play_otr.cpp @@ -28,7 +28,13 @@ extern "C" void OTRGameplay_SpawnScene(GlobalContext* globalCtx, s32 sceneNum, s //osSyncPrintf("\nSCENE SIZE %fK\n", (scene->sceneFile.vromEnd - scene->sceneFile.vromStart) / 1024.0f); - std::string scenePath = StringHelper::Sprintf("scenes/%s/%s", scene->sceneFile.fileName, scene->sceneFile.fileName); + std::string sceneVersion; + if (IsGameMasterQuest()) { + sceneVersion = "mq"; + } else { + sceneVersion = "nonmq"; + } + std::string scenePath = StringHelper::Sprintf("scenes/%s/%s/%s", sceneVersion.c_str(), scene->sceneFile.fileName, scene->sceneFile.fileName); globalCtx->sceneSegment = (Ship::Scene*)OTRGameplay_LoadFile(globalCtx, scenePath.c_str()); diff --git a/soh/soh/z_scene_otr.cpp b/soh/soh/z_scene_otr.cpp index 97c0b8040..556505d27 100644 --- a/soh/soh/z_scene_otr.cpp +++ b/soh/soh/z_scene_otr.cpp @@ -16,6 +16,20 @@ extern "C" s32 Object_Spawn(ObjectContext* objectCtx, s16 objectId); extern "C" RomFile sNaviMsgFiles[]; s32 OTRScene_ExecuteCommands(GlobalContext* globalCtx, Ship::Scene* scene); +std::shared_ptr ResourceMgr_LoadFile(const char* path) { + std::string Path = path; + if (IsGameMasterQuest()) { + size_t pos = 0; + if ((pos = Path.find("/nonmq/", 0)) != std::string::npos) { + Path.replace(pos, 7, "/mq/"); + } + } + return OTRGlobals::Instance->context->GetResourceManager()->LoadFile(Path.c_str()); +} + +// Forward Declaration of function declared in OTRGlobals.cpp +std::shared_ptr ResourceMgr_LoadResource(const char* path); + bool Scene_CommandSpawnList(GlobalContext* globalCtx, Ship::SceneCommand* cmd) { Ship::SetStartPositionList* cmdStartPos = (Ship::SetStartPositionList*)cmd; @@ -103,7 +117,7 @@ bool Scene_CommandCollisionHeader(GlobalContext* globalCtx, Ship::SceneCommand* { Ship::SetCollisionHeader* cmdCol = (Ship::SetCollisionHeader*)cmd; - auto colRes = std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(cmdCol->filePath)); + auto colRes = std::static_pointer_cast(ResourceMgr_LoadResource(cmdCol->filePath.c_str())); CollisionHeader* colHeader = nullptr; @@ -298,7 +312,8 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd) if (otrMesh->meshes[i].opa != "") { - auto opaFile = std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].opa)); + auto opaFile = + std::static_pointer_cast(ResourceMgr_LoadResource(otrMesh->meshes[i].opa.c_str())); dlist->opaDL = opaFile.get(); dlist->opa = (Gfx*)&dlist->opaDL->instructions[0]; @@ -310,7 +325,8 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd) if (otrMesh->meshes[i].xlu != "") { - auto xluFile = std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].xlu)); + auto xluFile = + std::static_pointer_cast(ResourceMgr_LoadResource(otrMesh->meshes[i].xlu.c_str())); dlist->xluDL = xluFile.get(); dlist->xlu = (Gfx*)&dlist->xluDL->instructions[0]; @@ -330,12 +346,12 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd) PolygonDlist* pType = (PolygonDlist*)malloc(sizeof(PolygonDlist)); if (otrMesh->meshes[0].imgOpa != "") - pType->opa = (Gfx*)&std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[0].imgOpa))->instructions[0]; + pType->opa = (Gfx*)&std::static_pointer_cast(ResourceMgr_LoadResource(otrMesh->meshes[0].imgOpa.c_str()))->instructions[0]; else pType->opa = 0; if (otrMesh->meshes[0].imgXlu != "") - pType->xlu = (Gfx*)&std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[0].imgXlu))->instructions[0]; + pType->xlu = (Gfx*)&std::static_pointer_cast(ResourceMgr_LoadResource(otrMesh->meshes[0].imgXlu.c_str()))->instructions[0]; else pType->xlu = 0; @@ -347,8 +363,7 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd) { globalCtx->roomCtx.curRoom.meshHeader->polygon1.single.fmt = otrMesh->meshes[0].images[0].fmt; globalCtx->roomCtx.curRoom.meshHeader->polygon1.single.source = - (void*)(OTRGlobals::Instance->context->GetResourceManager()->LoadFile( - otrMesh->meshes[0].images[0].sourceBackground)) + (void*)(ResourceMgr_LoadFile(otrMesh->meshes[0].images[0].sourceBackground.c_str())) .get() ->buffer.get(); globalCtx->roomCtx.curRoom.meshHeader->polygon1.single.siz = otrMesh->meshes[0].images[0].siz; @@ -368,8 +383,7 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd) { globalCtx->roomCtx.curRoom.meshHeader->polygon1.multi.list[i].fmt = otrMesh->meshes[0].images[i].fmt; globalCtx->roomCtx.curRoom.meshHeader->polygon1.multi.list[i].source = - (void*)(OTRGlobals::Instance->context->GetResourceManager()->LoadFile( - otrMesh->meshes[0].images[i].sourceBackground)) + (void*)(ResourceMgr_LoadFile(otrMesh->meshes[0].images[i].sourceBackground.c_str())) .get() ->buffer.get(); globalCtx->roomCtx.curRoom.meshHeader->polygon1.multi.list[i].siz = otrMesh->meshes[0].images[i].siz; @@ -395,7 +409,7 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd) if (otrMesh->meshes[i].opa != "") { - auto opaFile = std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].opa)); + auto opaFile = std::static_pointer_cast(ResourceMgr_LoadResource(otrMesh->meshes[i].opa.c_str())); dlist->opaDL = opaFile.get(); dlist->opa = (Gfx*)&dlist->opaDL->instructions[0]; @@ -405,7 +419,7 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd) if (otrMesh->meshes[i].xlu != "") { - auto xluFile = std::static_pointer_cast(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].xlu)); + auto xluFile = std::static_pointer_cast(ResourceMgr_LoadResource(otrMesh->meshes[i].xlu.c_str())); dlist->xluDL = xluFile.get(); dlist->xlu = (Gfx*)&dlist->xluDL->instructions[0]; @@ -515,7 +529,7 @@ bool Scene_CommandPathList(GlobalContext* globalCtx, Ship::SceneCommand* cmd) { Ship::SetPathways* cmdPath = (Ship::SetPathways*)cmd; - Ship::Path* path = (Ship::Path*)OTRGlobals::Instance->context->GetResourceManager()->LoadResource(cmdPath->paths[0]).get(); + Ship::Path* path = (Ship::Path*)ResourceMgr_LoadResource(cmdPath->paths[0].c_str()).get(); globalCtx->setupPathList = (Path*)malloc(path->paths.size() * sizeof(Path)); //for (int i = 0; i < cmdPath->paths.size(); i++) @@ -740,8 +754,7 @@ bool Scene_CommandAlternateHeaderList(GlobalContext* globalCtx, Ship::SceneComma std::string desiredHeader = cmdHeaders->headers[gSaveContext.sceneSetupIndex - 1]; Ship::Scene* headerData = nullptr; if (desiredHeader != "") { - headerData = - (Ship::Scene*)OTRGlobals::Instance->context->GetResourceManager()->LoadResource(desiredHeader).get(); + headerData = (Ship::Scene*)ResourceMgr_LoadResource(desiredHeader.c_str()).get(); } if (headerData != nullptr) @@ -759,9 +772,7 @@ bool Scene_CommandAlternateHeaderList(GlobalContext* globalCtx, Ship::SceneComma std::string desiredHeader = cmdHeaders->headers[gSaveContext.sceneSetupIndex - 2]; Ship::Scene* headerData = nullptr; if (desiredHeader != "") { - headerData = (Ship::Scene*)OTRGlobals::Instance->context->GetResourceManager() - ->LoadResource(desiredHeader) - .get(); + headerData = (Ship::Scene*)ResourceMgr_LoadResource(desiredHeader.c_str()).get(); } // "Using adult day data there!" @@ -782,7 +793,7 @@ bool Scene_CommandCutsceneData(GlobalContext* globalCtx, Ship::SceneCommand* cmd { Ship::SetCutscenes* cmdCS = (Ship::SetCutscenes*)cmd; - Ship::Cutscene* csData = (Ship::Cutscene*)OTRGlobals::Instance->context->GetResourceManager()->LoadResource(cmdCS->cutscenePath).get(); + Ship::Cutscene* csData = (Ship::Cutscene*)ResourceMgr_LoadResource(cmdCS->cutscenePath.c_str()).get(); globalCtx->csCtx.segment = csData->commands.data(); //osSyncPrintf("\ngame_play->demo_play.data=[%x]", globalCtx->csCtx.segment); @@ -921,7 +932,7 @@ extern "C" s32 OTRfunc_8009728C(GlobalContext* globalCtx, RoomContext* roomCtx, //DmaMgr_SendRequest2(&roomCtx->dmaRequest, roomCtx->unk_34, globalCtx->roomList[roomNum].vromStart, size, 0, //&roomCtx->loadQueue, NULL, __FILE__, __LINE__); - auto roomData = OTRGlobals::Instance->context->GetResourceManager()->LoadResource(globalCtx->roomList[roomNum].fileName); + auto roomData = ResourceMgr_LoadResource(globalCtx->roomList[roomNum].fileName); roomCtx->status = 1; roomCtx->roomToLoad = (Ship::Scene*)roomData.get(); diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index 469e6bfc5..1c3cb5b23 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -297,8 +297,9 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { gSaveContext.playerName[offset] = Save_GetSaveMetaInfo(fileChooseCtx->buttonIndex)->playerName[offset]; } - if (CVar_GetS32("gRandomizer", 0) != 0 && - strcmp(CVar_GetString("gSpoilerLog", ""), "") != 0) { + if (CVar_GetS32("gRandomizer", 0) && strnlen(CVar_GetString("gSpoilerLog", ""), 1) != 0 && + !((Save_GetSaveMetaInfo(fileChooseCtx->buttonIndex)->requiresMasterQuest && !ResourceMgr_GameHasMasterQuest()) || + (Save_GetSaveMetaInfo(fileChooseCtx->buttonIndex)->requiresMasterQuest && !ResourceMgr_GameHasOriginal()))) { // Set N64DD Flags for save file fileChooseCtx->n64ddFlags[fileChooseCtx->buttonIndex] = 1; fileChooseCtx->n64ddFlag = 1; diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index 14b01760f..4d5d232fb 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -32,7 +32,14 @@ static s16 sWindowContentColors[2][3] = { }; static int FileChoose_IsSaveCompatible(const SaveFileMetaInfo* restrict meta) { - return meta->isMasterQuest == ResourceMgr_IsGameMasterQuest(); + bool valid = true; + if (meta->requiresMasterQuest) { + valid = valid && ResourceMgr_GameHasMasterQuest(); + } + if (meta->requiresOriginal) { + valid = valid && ResourceMgr_GameHasOriginal(); + } + return valid; } void FileChoose_SetView(FileChooseContext* this, f32 eyeX, f32 eyeY, f32 eyeZ) { @@ -219,13 +226,15 @@ void DrawSeedHashSprites(FileChooseContext* this) { gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0xFF, 0xFF, 0xFF, this->fileButtonAlpha[this->buttonIndex]); - if (CVar_GetS32("gRandomizer", 0) && strnlen(CVar_GetString("gSpoilerLog", ""), 1) != 0) { + if (CVar_GetS32("gRandomizer", 0) && strnlen(CVar_GetString("gSpoilerLog", ""), 1) != 0 && + !((gSaveContext.mqDungeonCount > 0 && !ResourceMgr_GameHasMasterQuest()) + || (gSaveContext.mqDungeonCount < 12 && !ResourceMgr_GameHasOriginal()))) { u16 xStart = 64; for (unsigned int i = 0; i < 5; i++) { SpriteLoad(this, GetSeedTexture(gSaveContext.seedIcons[i])); SpriteDraw(this, GetSeedTexture(gSaveContext.seedIcons[i]), xStart + (40 * i), 10, 24, 24); } - } + } } gDPPipeSync(POLY_OPA_DISP++); @@ -235,6 +244,7 @@ void DrawSeedHashSprites(FileChooseContext* this) { u8 generating; bool fileSelectSpoilerFileLoaded; +bool shouldLoadSpoilerFile; /** * Update the cursor and wait for the player to select a button to change menus accordingly. @@ -270,7 +280,7 @@ void FileChoose_UpdateMainMenu(GameState* thisx) { if ((CVar_GetS32("gNewFileDropped", 0) != 0) || (CVar_GetS32("gNewSeedGenerated", 0) != 0) || - (!fileSelectSpoilerFileLoaded && + (!fileSelectSpoilerFileLoaded && shouldLoadSpoilerFile && SpoilerFileExists(CVar_GetString("gSpoilerLog", "")))) { if (CVar_GetS32("gNewFileDropped", 0) != 0) { CVar_SetString("gSpoilerLog", CVar_GetString("gDroppedFile", "None")); @@ -288,6 +298,7 @@ void FileChoose_UpdateMainMenu(GameState* thisx) { Randomizer_LoadSettings(fileLoc); Randomizer_LoadHintLocations(fileLoc); Randomizer_LoadRequiredTrials(fileLoc); + Randomizer_LoadMasterQuestDungeons(fileLoc); Randomizer_LoadItemLocations(fileLoc, silent); Randomizer_LoadMerchantMessages(fileLoc); fileSelectSpoilerFileLoaded = true; @@ -1118,7 +1129,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) { gSP1Quadrangle(POLY_OPA_DISP++, 8, 10, 11, 9, 0); } //Draw MQ label - if (Save_GetSaveMetaInfo(i)->isMasterQuest && Save_GetSaveMetaInfo(i)->valid) { + if (Save_GetSaveMetaInfo(i)->requiresMasterQuest && !Save_GetSaveMetaInfo(i)->randoSave && Save_GetSaveMetaInfo(i)->valid) { if (CVar_GetS32("gHudColors", 1) == 2 && FileChoose_IsSaveCompatible(Save_GetSaveMetaInfo(i))) { gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, CVar_GetRGB("gCCFileChoosePrim", Background_Color).r, CVar_GetRGB("gCCFileChoosePrim", Background_Color).g, CVar_GetRGB("gCCFileChoosePrim", Background_Color).b, this->nameAlpha[i]); } else if (!FileChoose_IsSaveCompatible(Save_GetSaveMetaInfo(i))) { @@ -1150,7 +1161,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) { G_TX_NOLOD); gSP1Quadrangle(POLY_OPA_DISP++, 12, 14, 15, 13, 0); - if (Save_GetSaveMetaInfo(i)->randoSave || Save_GetSaveMetaInfo(i)->isMasterQuest) { + if (Save_GetSaveMetaInfo(i)->randoSave || Save_GetSaveMetaInfo(i)->requiresMasterQuest) { gSP1Quadrangle(POLY_OPA_DISP++, 16, 18, 19, 17, 0); } } @@ -2091,6 +2102,7 @@ void FileChoose_Init(GameState* thisx) { size_t size = (u32)_title_staticSegmentRomEnd - (u32)_title_staticSegmentRomStart; s32 pad; fileSelectSpoilerFileLoaded = false; + shouldLoadSpoilerFile = true; CVar_SetS32("gOnFileSelectNameEntry", 0); SREG(30) = 1;