Dual OTR MQ and Vanilla Support (#1694)

* Changes OTR Extraction to have specific mq and nonmq paths.

Also updates the game to load resources according to whether or not
Master Quest or Vanilla is loaded.

* Removes unneeded code from the last commit.

* Fixes some weird formatting in ZRom.c

* Loads oot-mq.otr and patches oot.otr on top, if both are present.

If only one or the other are present, it becomes the only and main OTR.

* Adds ImGui Logic for whether or an MQ Checkbox.

Checkbox checked only specifies whether new saves should be MQ or not.
Checkbox is disabled or force-enabled according to which OTRs are loaded.
Also as a necessity includes tracking what game versions have been loaded
from the OTRs.

* Adds MQ settings logic for Randomizer's ImGui menu.

* Writes Master Quest dungeons to the spoiler log

* Loads MQ Dungeons from spoiler, persists in save, and loads when appropriate.

* Adds logic to prevent loading or creating incompatible rando saves.

* Fixdes some linux build issues and new rando save issues

* Makes appimage create both vanilla and mq otrs

If either rom is present, it makes the corresponding OTR. If both are present,
it will make both. If one OTR is present but both roms are present, it will
create the missing OTR.

* Makes it so a randomized save file will not be marked as MQ.

* Refactors to load all OTRs from MainPath or a specific list.

Also adds the ability to take a std::unordered_set of hashes to
validate each OTR's version file against.

* Fixes a syntax error

* Makes ExtractAssets output Vanilla and MQ OTRs if both roms are present

* Fixes asset generation bug.

* Partially working fix for dual OTR extract_assets

Currently the cmake ExtractAssets target will return with a 1 if you
only end up exporting one type of OTR isntead of both. Haven't found
a great way to only attempt to copy a file if it exists from within
cmake. It does actually correctly copy the OTR that is generated,
despite the error from copying the other one.

Pushing as is for now but will keep investigating.

* Adds oot-mq.otr to the gitignore.

* Makes ExtractAssets not fail on only one rom/OTR.

* Removes PatchesPath from the constructors requiring OTRFiles vector.

* Renames OOT_UNKNOWN to just UNKNOWN to remove OOT specific reference.

* Removes randomizing MQ Dungeons and re-disables MQ rando.

Doing this so the PR can get merged quicker with just the Dual OTR
support and won't need to wait on rando logic to be updated. That
will happen in another PR directly after the merge.

* Update mac startup script for dual otr

* Update soh/macosx/soh-macos.sh

* Update soh/macosx/soh-macos.sh

* Update soh/macosx/soh-macos.sh

* Implements new BinaryReader to fix Linux build issue.

BinaryReader itself comes from https://github.com/Moneyl/BinaryTools
I added a wrapper to adapt it to the ABI from ZAPD's Binary Reader and
add Endianness checking. I also had to copy a handful of other bits and
pieces from ZAPD to make it all function as expected.

* A few edits to the updatream BinaryReader to compile it on Linux.

* Adds the Endianness to the first byte of the version file.

* Fixes Jenkins

* Addresses some of Kenix's comments

* Renames `ReadNullTerminatedString` to `ReadCString`

* Refactors Archive::LoadFile into a private method with more arguments.

* Removes BitConverter and extends existing endianness.h instead.

* Fixes an endianness issue with the version file.

Co-authored-by: Garrett Cox <garrettjcox@gmail.com>
This commit is contained in:
Christopher Leggett 2022-10-16 23:07:35 -04:00 committed by GitHub
parent 350315a5d1
commit 7b08f98b8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 3149 additions and 485 deletions

1
.gitignore vendored
View File

@ -410,6 +410,7 @@ ReleaseObj/*
.tags
tags
oot.otr
oot-mq.otr
*.sav
shipofharkinian.ini
shipofharkinian.json

View File

@ -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 $<IF:$<VERSION_LESS:${CMAKE_VERSION},3.17>,remove,rm> -f oot.otr
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$<TARGET_FILE:ZAPD>"
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 $<IF:$<VERSION_LESS:${CMAKE_VERSION},3.17>,remove,rm> -f oot.otr oot-mq.otr
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$<TARGET_FILE:ZAPD>" --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")

View File

@ -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/"))

View File

@ -21,6 +21,7 @@
#include <Utils/Directory.h>
#include <Utils/MemoryStream.h>
#include <Utils/BinaryWriter.h>
#include <bit>
std::string otrFileName = "oot.otr";
std::shared_ptr<Ship::Archive> otrArchive;
@ -69,13 +70,20 @@ static void ExporterProgramEnd()
std::string romPath = Globals::Instance->baseRomPath.string();
std::vector<uint8_t> 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");

View File

@ -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()

View File

@ -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] ]

View File

@ -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

View File

@ -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());

View File

@ -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)
{

View File

@ -11,6 +11,7 @@ public:
ZRom(std::string romPath);
std::vector<uint8_t> GetFile(std::string fileName);
bool IsMQ();
protected:
std::vector<uint8_t> romData;

11
copy-existing-otrs.cmake Normal file
View File

@ -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()

View File

@ -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);
}

View File

@ -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 <filesystem>
#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<uint32_t>(), 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<uint32_t>& ValidHashes, bool enableWriting, bool genCRCMap)
: MainPath(MainPath), PatchesPath(PatchesPath), OTRFiles({}), ValidHashes(ValidHashes) {
mainMPQ = nullptr;
Load(enableWriting, genCRCMap);
}
Archive::~Archive() {
Archive::Archive(const std::vector<std::string> &OTRFiles, const std::unordered_set<uint32_t> &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<File> Archive::LoadFileFromHandle(const std::string& filePath, bool includeParent, std::shared_ptr<File> FileToLoad, HANDLE mpqHandle)
{
HANDLE fileHandle = NULL;
if (FileToLoad == nullptr)
{
FileToLoad = std::make_shared<File>();
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<std::mutex> Lock(FileToLoad->FileLoadMutex);
FileToLoad->bHasLoadError = true;
return FileToLoad;
}
DWORD dwFileSize = SFileGetFileSize(fileHandle, 0);
std::shared_ptr<char[]> 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<std::mutex> 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<std::mutex> Lock(FileToLoad->FileLoadMutex);
FileToLoad->parent = includeParent ? shared_from_this() : nullptr;
FileToLoad->buffer = fileData;
FileToLoad->dwBufferSize = dwFileSize;
FileToLoad->bIsLoaded = true;
return FileToLoad;
}
std::shared_ptr<File> Archive::LoadFile(const std::string& filePath, bool includeParent, std::shared_ptr<File> FileToLoad) {
HANDLE fileHandle = NULL;
if (FileToLoad == nullptr) {
FileToLoad = std::make_shared<File>();
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<std::mutex> Lock(FileToLoad->FileLoadMutex);
FileToLoad->bHasLoadError = true;
return FileToLoad;
}
DWORD dwFileSize = SFileGetFileSize(fileHandle, 0);
std::shared_ptr<char[]> 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<std::mutex> 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<std::mutex> 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<File> Archive::LoadPatchFile(const std::string& filePath, bool includeParent, std::shared_ptr<File> FileToLoad) {
@ -320,53 +346,129 @@ namespace Ship {
return true;
}
void Archive::GenerateCRCMap() {
auto listFile = LoadFile("(listfile)", false);
std::vector<std::string> 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<std::string> 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

View File

@ -9,6 +9,7 @@
#include <unordered_map>
#include <string>
#include <vector>
#include <unordered_set>
#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<uint32_t> &ValidHashes, bool enableWriting, bool genCRCMap = true);
Archive(const std::vector<std::string> &OTRFiles, const std::unordered_set<uint32_t> &ValidHashes, bool enableWriting, bool genCRCMap = true);
~Archive();
bool IsMainMPQValid();
@ -37,19 +39,25 @@ namespace Ship
std::vector<SFILE_FIND_DATA> ListFiles(const std::string& searchMask) const;
bool HasFile(const std::string& searchMask) const;
const std::string* HashToString(uint64_t hash) const;
std::vector<uint32_t> gameVersions;
protected:
bool Load(bool enableWriting, bool genCRCMap);
bool Unload();
private:
std::string MainPath;
std::string PatchesPath;
std::map<std::string, HANDLE> mpqHandles;
std::vector<std::string> OTRFiles;
std::unordered_set<uint32_t> ValidHashes;
std::map<std::string, HANDLE> mpqHandles;
std::vector<std::string> addedFiles;
std::unordered_map<uint64_t, std::string> 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<File> LoadFileFromHandle(const std::string &filePath, bool includeParent = true, std::shared_ptr<File> FileToLoad = nullptr, HANDLE mpqHandle = nullptr);
};
}

View File

@ -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);

View File

@ -0,0 +1,216 @@
#include "BinaryReader.h"
#include <cmath>
#include "Lib/BinaryTools/BinaryTools/BinaryReader.h"
Ship::BinaryReader::BinaryReader(char* buffer, uint32_t size)
{
this->buffer = std::vector<char>(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();
}

View File

@ -0,0 +1,61 @@
#pragma once
#include <string>
#include <memory>
#include <vector>
#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<char> buffer;
std::shared_ptr<::BinaryReader> reader;
};
}

View File

@ -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}

View File

@ -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";
}

View File

@ -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;

View File

@ -10,7 +10,7 @@ namespace Ship
ResourceFile::ParseFileBinary(reader, res);
while (reader->GetBaseAddress() % 8 != 0)
reader->ReadByte();
reader->ReadInt8();
while (true)
{

View File

@ -22,13 +22,12 @@ namespace Ship
{
Resource* ResourceLoader::LoadResource(std::shared_ptr<File> FileToLoad)
{
auto memStream = std::make_shared<MemoryStream>(FileToLoad->buffer.get(), FileToLoad->dwBufferSize);
auto reader = std::make_shared<BinaryReader>(memStream);
auto reader = std::make_shared<BinaryReader>(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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,23 @@
#include "Binary.h"
#include <fstream>
Span<char> 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<char>(buffer, fileSize);
}

View File

@ -0,0 +1,5 @@
#pragma once
#include <string>
#include "Span.h"
Span<char> ReadAllBytes(const std::string& filePath);

View File

@ -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<uint8_t> 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<char*>(&output), 1);
return output;
}
uint16_t BinaryReader::ReadUint16()
{
uint16_t output;
stream_->read(reinterpret_cast<char*>(&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<char*>(&output), 4);
return output;
}
uint64_t BinaryReader::ReadUint64()
{
uint64_t output;
stream_->read(reinterpret_cast<char*>(&output), 8);
return output;
}
int8_t BinaryReader::ReadInt8()
{
int8_t output;
stream_->read(reinterpret_cast<char*>(&output), 1);
return output;
}
int16_t BinaryReader::ReadInt16()
{
int16_t output;
stream_->read(reinterpret_cast<char*>(&output), 2);
return output;
}
int32_t BinaryReader::ReadInt32()
{
int32_t output;
stream_->read(reinterpret_cast<char*>(&output), 4);
return output;
}
int64_t BinaryReader::ReadInt64()
{
int64_t output;
stream_->read(reinterpret_cast<char*>(&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<std::string> BinaryReader::ReadSizedStringList(size_t listSize)
{
std::vector<std::string> 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<char*>(&output), 4);
return output;
}
double BinaryReader::ReadDouble()
{
double output;
stream_->read(reinterpret_cast<char*>(&output), 8);
return output;
}
void BinaryReader::ReadToMemory(void* destination, size_t size)
{
stream_->read(static_cast<char*>(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;
}

View File

@ -0,0 +1,64 @@
#pragma once
#include "MemoryBuffer.h"
#include <fstream>
#include <string>
#include <span>
#include <vector>
#include <istream>
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<uint8_t> 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<std::string> 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;
};

View File

@ -0,0 +1,162 @@
#include "BinaryWriter.h"
#include "BinaryReader.h"
#include "Binary.h"
#include <array>
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<test, 3> 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<test> 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<T>...\n");
for (auto& val : testSpan)
printf("value: {a: %d, b: %d}\n", val.a, val.b);
auto a = 2;
}

View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{DC3D45C9-4E30-4E00-9E95-76FCF3754849}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>BinaryTools</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<EnableModules>true</EnableModules>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<EnableModules>true</EnableModules>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<EnableModules>true</EnableModules>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<EnableModules>true</EnableModules>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="Binary.cpp" />
<ClCompile Include="BinaryReader.cpp" />
<ClCompile Include="BinaryTools.cpp" />
<ClCompile Include="BinaryWriter.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Binary.h" />
<ClInclude Include="BinaryReader.h" />
<ClInclude Include="BinaryWriter.h" />
<ClInclude Include="MemoryBuffer.h" />
<ClInclude Include="Span.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="src">
<UniqueIdentifier>{54101738-c0ef-40a7-be61-cf8ae2cbcae3}</UniqueIdentifier>
</Filter>
<Filter Include="src\BinaryReader">
<UniqueIdentifier>{5568f67e-fdd2-4c16-bb85-bf72217b642c}</UniqueIdentifier>
</Filter>
<Filter Include="src\BinaryWriter">
<UniqueIdentifier>{03c417c9-055a-45f5-83d9-42357834c20e}</UniqueIdentifier>
</Filter>
<Filter Include="src\helpers">
<UniqueIdentifier>{063757fd-06fb-4f35-8362-005b4a8261b9}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="BinaryTools.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="BinaryReader.cpp">
<Filter>src\BinaryReader</Filter>
</ClCompile>
<ClCompile Include="BinaryWriter.cpp">
<Filter>src\BinaryWriter</Filter>
</ClCompile>
<ClCompile Include="Binary.cpp">
<Filter>src\helpers</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="BinaryReader.h">
<Filter>src\BinaryReader</Filter>
</ClInclude>
<ClInclude Include="BinaryWriter.h">
<Filter>src\BinaryWriter</Filter>
</ClInclude>
<ClInclude Include="Binary.h">
<Filter>src\helpers</Filter>
</ClInclude>
<ClInclude Include="MemoryBuffer.h">
<Filter>src\helpers</Filter>
</ClInclude>
<ClInclude Include="Span.h">
<Filter>src\helpers</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -0,0 +1,183 @@
#include "BinaryWriter.h"
#include "MemoryBuffer.h"
#include <filesystem>
#include <fstream>
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<const char*>(&value), 1);
}
void BinaryWriter::WriteUint16(uint16_t value)
{
stream_->write(reinterpret_cast<const char*>(&value), 2);
}
void BinaryWriter::WriteUint32(uint32_t value)
{
stream_->write(reinterpret_cast<const char*>(&value), 4);
}
void BinaryWriter::WriteUint64(uint64_t value)
{
stream_->write(reinterpret_cast<const char*>(&value), 8);
}
void BinaryWriter::WriteInt8(int8_t value)
{
stream_->write(reinterpret_cast<const char*>(&value), 1);
}
void BinaryWriter::WriteInt16(int16_t value)
{
stream_->write(reinterpret_cast<const char*>(&value), 2);
}
void BinaryWriter::WriteInt32(int32_t value)
{
stream_->write(reinterpret_cast<const char*>(&value), 4);
}
void BinaryWriter::WriteInt64(int64_t value)
{
stream_->write(reinterpret_cast<const char*>(&value), 8);
}
void BinaryWriter::WriteChar(char value)
{
stream_->write(reinterpret_cast<const char*>(&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<const char*>(&value), 4);
}
void BinaryWriter::WriteDouble(double value)
{
stream_->write(reinterpret_cast<const char*>(&value), 8);
}
void BinaryWriter::WriteFromMemory(const void* data, size_t size)
{
stream_->write(reinterpret_cast<const char*>(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;
}

View File

@ -0,0 +1,72 @@
#pragma once
#include <string>
#include <fstream>
#include <span>
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<typename T>
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<T>(), "BinaryWriter::Write<T> requires T to be a non pointer type.");
WriteFromMemory(&data, sizeof(T));
}
template<typename T>
void WriteSpan(std::span<T> 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;
};

View File

@ -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 <streambuf>
#include <algorithm>
#include <numeric>
#include <limits>
#include <cstring>
//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<char> data;
// // ... fill-in *data* here ...
// xx::basic_memstreambuf<data.value_type> 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<char_type>& s)
: BaseT()
{
//assert(!s.empty());
setg(
const_cast<char_type*>(&s.front()),
const_cast<char_type*>(&s.front()),
const_cast<char_type*>(&s.back()));
}
// non-standard
basic_memstreambuf(const char_type* s, std::streamsize n) : BaseT()
{
// assert(s);
//assert(n > 0);
setg(
const_cast<char_type*>(s),
const_cast<char_type*>(s),
const_cast<char_type*>(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::uintmax_t>(std::numeric_limits<std::streamsize>::max());
if (count > maxValue)
{
throw std::invalid_argument("basic_memstreambuf too big");
}
setg(
const_cast<char_type*>(begin),
const_cast<char_type*>(begin),
const_cast<char_type*>(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<std::streamsize>(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
};

View File

@ -0,0 +1,41 @@
#pragma once
//Simple wrapper around a contiguous area of memory
template<class T>
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;
};

View File

@ -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.

View File

@ -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<T>`: 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<T>. 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 <BinaryReader.h>
#include <BinaryWriter.h>
#include <string>
#include <iostream>
#include <assert.h>
//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);
}
}
```

View File

@ -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();

View File

@ -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();

View File

@ -5,6 +5,7 @@
#include "Resource.h"
#include "Vec2f.h"
#include "Vec3f.h"
#include "Vec3s.h"
#include "Color3b.h"
namespace Ship

View File

@ -5,6 +5,7 @@
#include "Resource.h"
#include "Vec2f.h"
#include "Vec3f.h"
#include "Vec3s.h"
#include "Color3b.h"
namespace Ship

View File

@ -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"

View File

@ -1,7 +1,7 @@
#pragma once
#include <stdint.h>
#include "Utils/BinaryReader.h"
#include "BinaryReader.h"
#include "Utils/BinaryWriter.h"
#include "File.h"
#include "Lib/tinyxml2/tinyxml2.h"

View File

@ -9,17 +9,30 @@
namespace Ship {
ResourceMgr::ResourceMgr(std::shared_ptr<Window> Context, const std::string& MainPath, const std::string& PatchesPath) : Context(Context), bIsRunning(false), FileLoadThread(nullptr) {
OTR = std::make_shared<Archive>(MainPath, PatchesPath, false);
ResourceMgr::ResourceMgr(std::shared_ptr<Window> Context, const std::string& MainPath, const std::string& PatchesPath, const std::unordered_set<uint32_t>& ValidHashes)
: Context(Context), bIsRunning(false), FileLoadThread(nullptr) {
OTR = std::make_shared<Archive>(MainPath, PatchesPath, ValidHashes, false);
gameVersion = OOT_UNKNOWN;
gameVersion = UNKNOWN;
if (OTR->IsMainMPQValid()) {
Start();
}
}
ResourceMgr::~ResourceMgr() {
ResourceMgr::ResourceMgr(std::shared_ptr<Window> Context, const std::vector<std::string> &OTRFiles, const std::unordered_set<uint32_t> &ValidHashes)
: Context(Context), bIsRunning(false), FileLoadThread(nullptr)
{
OTR = std::make_shared<Archive>(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<uint32_t> ResourceMgr::GetGameVersions() {
return OTR->gameVersions;
}
void ResourceMgr::PushGameVersion(uint32_t newGameVersion) {
OTR->gameVersions.push_back(newGameVersion);
}
std::shared_ptr<File> ResourceMgr::LoadFileAsync(const std::string& FilePath) {
const std::lock_guard<std::mutex> Lock(FileLoadMutex);
// File NOT already loaded...?

View File

@ -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<Window> Context, const std::string& MainPath, const std::string& PatchesPath);
~ResourceMgr();
ResourceMgr(std::shared_ptr<Window> Context, const std::string& MainPath, const std::string& PatchesPath, const std::unordered_set<uint32_t> &ValidHashes);
ResourceMgr(std::shared_ptr<Window> Context, const std::vector<std::string> &OTRFiles, const std::unordered_set<uint32_t> &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<File> LoadFileAsync(const std::string& FilePath);
std::vector<uint32_t> GetGameVersions();
void PushGameVersion(uint32_t newGameVersion);
std::shared_ptr<File> LoadFileAsync(const std::string& FilePath);
std::shared_ptr<File> LoadFile(const std::string& FilePath);
std::shared_ptr<Resource> GetCachedFile(const char* FilePath) const;
std::shared_ptr<Resource> LoadResource(const char* FilePath);
@ -61,5 +64,6 @@ namespace Ship {
std::condition_variable FileLoadNotifier;
std::condition_variable ResourceLoadNotifier;
uint32_t gameVersion;
std::vector<uint32_t> gameVersions;
};
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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();

View File

@ -232,11 +232,11 @@ namespace Ship {
return Context.lock();
}
std::shared_ptr<Window> Window::CreateInstance(const std::string Name) {
std::shared_ptr<Window> Window::CreateInstance(const std::string Name, const std::vector<std::string>& OTRFiles, const std::unordered_set<uint32_t>& ValidHashes) {
if (Context.expired()) {
auto Shared = std::make_shared<Window>(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<std::string>& OTRFiles, const std::unordered_set<uint32_t>& 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<std::string>& OTRFiles, const std::unordered_set<uint32_t>& ValidHashes) {
MainPath = Config->getString("Game.Main Archive", GetAppDirectoryPath());
PatchesPath = Config->getString("Game.Patches Archive", GetAppDirectoryPath() + "/mods");
ResMan = std::make_shared<ResourceMgr>(GetInstance(), MainPath, PatchesPath);
if (OTRFiles.empty()) {
ResMan = std::make_shared<ResourceMgr>(GetInstance(), MainPath, PatchesPath, ValidHashes);
} else {
ResMan = std::make_shared<ResourceMgr>(GetInstance(), OTRFiles, ValidHashes);
}
if (!ResMan->DidLoadSuccessfully())
{

View File

@ -2,6 +2,7 @@
#include <memory>
#include <filesystem>
#include <unordered_set>
#include "spdlog/spdlog.h"
#include "ControlDeck.h"
#include "AudioPlayer.h"
@ -17,7 +18,7 @@ namespace Ship {
class Window {
public:
static std::shared_ptr<Window> GetInstance();
static std::shared_ptr<Window> CreateInstance(const std::string Name);
static std::shared_ptr<Window> CreateInstance(const std::string Name, const std::vector<std::string>& OTRFiles = {}, const std::unordered_set<uint32_t>& 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<std::string>& OTRFiles = {}, const std::unordered_set<uint32_t>& 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<std::string>& OTRFiles = {}, const std::unordered_set<uint32_t>& ValidHashes = {});
void InitializeWindowManager();
std::shared_ptr<spdlog::logger> Logger;
@ -84,6 +85,7 @@ namespace Ship {
int32_t lastScancode;
std::string Name;
std::string MainPath;
std::string BasePath;
std::string PatchesPath;
};
}

View File

@ -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 <stdlib.h>
#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

View File

@ -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 <b>$ROMHASH</b> 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

View File

@ -188,6 +188,7 @@ typedef struct {
u8 temporaryWeapon;
u16 adultTradeItems;
u8 pendingIceTrapCount;
u8 mqDungeonCount;
} SaveContext; // size = 0x1428
typedef enum {

View File

@ -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"

View File

@ -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

View File

@ -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]);

View File

@ -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);

View File

@ -121,48 +121,62 @@ std::unordered_map<std::string, RandomizerInf> spoilerFileTrialToEnum = {
{ "l'épreuve de la Lumière", RAND_INF_TRIALS_DONE_LIGHT_TRIAL }
};
std::unordered_map<s16, s16> 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<std::string, SceneID> 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<s16, s16>
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<std::string, RandomizerSettingKey> SpoilerfileSettingNameToEnum = {
{ "Open Settings:Forest", RSK_FOREST },
{ "Open Settings:Kakariko Gate", RSK_KAK_GATE },
@ -212,6 +226,7 @@ std::unordered_map<std::string, RandomizerSettingKey> 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<RandomizerCheck> 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<RandomizerCheck> 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

View File

@ -1,6 +1,7 @@
#pragma once
#include <unordered_map>
#include <unordered_set>
#include <string>
#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<RandomizerInf, bool> trialsRequired;
std::unordered_set<uint16_t> masterQuestDungeons;
std::unordered_map<RandomizerCheck, u16> 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);

View File

@ -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;

View File

@ -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);

View File

@ -77,14 +77,78 @@ CustomMessageManager* CustomMessageManager::Instance;
ItemTableManager* ItemTableManager::Instance;
OTRGlobals::OTRGlobals() {
context = Ship::Window::CreateInstance("Ship of Harkinian");
std::vector<std::string> 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<uint32_t> 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<SaveStateMgr>();
gRandomizer = std::make_shared<Randomizer>();
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<Ship::Resource> 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<Ship::DisplayList>(res))->instructions[0]);
else if (res->resType == Ship::ResourceType::Array)
return (char*)(std::static_pointer_cast<Ship::Array>(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<Ship::PlayerAnimation>(
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(animPath));
auto anim = std::static_pointer_cast<Ship::PlayerAnimation>(ResourceMgr_LoadResource(animPath));
return (char*)&anim->limbRotData[0];
}
extern "C" Gfx* ResourceMgr_LoadGfxByName(const char* path)
{
auto res = std::static_pointer_cast<Ship::DisplayList>(
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::DisplayList>(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<Ship::Array>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::Array>(ResourceMgr_LoadResource(path));
return (char*)res->scalars.data();
}
extern "C" char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path) {
auto res =
std::static_pointer_cast<Ship::Array>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::Array>(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<Ship::CollisionHeader>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto colRes = std::static_pointer_cast<Ship::CollisionHeader>(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<Ship::Array>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
return (Vtx*)res->vertices.data();
auto res = std::static_pointer_cast<Ship::Array>(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<Ship::AudioSample>(
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto sample = std::static_pointer_cast<Ship::AudioSample>(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<Ship::AudioSoundFont>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto soundFont = std::static_pointer_cast<Ship::AudioSoundFont>(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<Ship::Animation>(
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::Animation>(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<Ship::Skeleton>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::Skeleton>(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<Ship::SkeletonLimb>(
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(limbStr.c_str()));
auto limb = std::static_pointer_cast<Ship::SkeletonLimb>(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<Ship::Cutscene>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::Cutscene>(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);
}

View File

@ -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);

View File

@ -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<uint16_t> 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) {

View File

@ -10,7 +10,8 @@ typedef struct {
u32 questItems;
s8 defense;
u16 health;
u32 isMasterQuest;
u32 requiresMasterQuest;
u32 requiresOriginal;
u8 seedHash[5];
u8 randoSave;
} SaveFileMetaInfo;

View File

@ -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());

View File

@ -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<Ship::File> 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<Ship::Resource> 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<Ship::CollisionHeader>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(cmdCol->filePath));
auto colRes = std::static_pointer_cast<Ship::CollisionHeader>(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<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].opa));
auto opaFile =
std::static_pointer_cast<Ship::DisplayList>(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<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].xlu));
auto xluFile =
std::static_pointer_cast<Ship::DisplayList>(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<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[0].imgOpa))->instructions[0];
pType->opa = (Gfx*)&std::static_pointer_cast<Ship::DisplayList>(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<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[0].imgXlu))->instructions[0];
pType->xlu = (Gfx*)&std::static_pointer_cast<Ship::DisplayList>(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<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].opa));
auto opaFile = std::static_pointer_cast<Ship::DisplayList>(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<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].xlu));
auto xluFile = std::static_pointer_cast<Ship::DisplayList>(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();

View File

@ -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;

View File

@ -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;