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 000000000..74fb7cb88 Binary files /dev/null and b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/TestBin1.bin differ diff --git a/libultraship/libultraship/Lib/BinaryTools/BinaryTools/TestBin3.bin b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/TestBin3.bin new file mode 100644 index 000000000..7e2c765c7 Binary files /dev/null and b/libultraship/libultraship/Lib/BinaryTools/BinaryTools/TestBin3.bin differ diff --git a/libultraship/libultraship/Lib/BinaryTools/License.txt b/libultraship/libultraship/Lib/BinaryTools/License.txt new file mode 100644 index 000000000..c062b8e5c --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/License.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 moneyl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libultraship/libultraship/Lib/BinaryTools/README.md b/libultraship/libultraship/Lib/BinaryTools/README.md new file mode 100644 index 000000000..2ebf858e5 --- /dev/null +++ b/libultraship/libultraship/Lib/BinaryTools/README.md @@ -0,0 +1,80 @@ +# BinaryTools +C++ classes for reading/writing binary data and some helper functions/classes. Based on C#'s `BinaryReader` and `BinaryWriter`. + +## BinaryReader & BinaryWriter +Classes which can read/write binary data to/from a file or memory buffer. Both have functions for the most common primitive types. Ex: `uint32_t`, `int32_t`, `uint64_t`, `int64_t`, `float`, `double`, etc. See `BinaryReader.h` and `BinaryWriter.h` for a full list. The constructor used determines whether the class reads from a file (the constructor provides a file path), or a memory region (it provides a memory address and size). They can also read and write entire structs to or from memory using `ReadToMemory` and `WriteFromMemory`, respectively. + +## Other helpers and included classes +- `Span`: 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;