Archive version checking and regeneration (#822)

* Add archive version checking and regeneration

* update building docs and copy assets for visual studio
This commit is contained in:
Archez 2024-11-09 21:15:10 -05:00 committed by GitHub
parent be70a5a123
commit 449a2ebc70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 298 additions and 205 deletions

View File

@ -22,7 +22,7 @@ It is recommended that you install Python and Git standalone, the install proces
_Note: Be sure to either clone with the ``--recursive`` flag or do ``git submodule update --init`` after cloning to pull in the libultraship submodule!_
2. Place one or more [compatible](#compatible-roms) roms in the `OTRExporter` directory with namings of your choice
2. After setup and initial build, use the built-in O2R extraction to make your mm.o2r file.
_Note: Instructions assume using powershell_
```powershell
@ -30,22 +30,17 @@ _Note: Instructions assume using powershell_
cd 2ship2harkinian
# Setup cmake project
& 'C:\Program Files\CMake\bin\cmake' -S . -B "build/x64" -G "Visual Studio 17 2022" -T v143 -A x64 # -DCMAKE_BUILD_TYPE:STRING=Release (if you're packaging)
# Extract assets & generate OTR (run this anytime you need to regenerate OTR)
& 'C:\Program Files\CMake\bin\cmake.exe' --build .\build\x64 --target ExtractAssets # --config Release (if you're packaging)
# Compile project
& 'C:\Program Files\CMake\bin\cmake.exe' --build .\build\x64 # --config Release (if you're packaging)
# Add `-DCMAKE_BUILD_TYPE:STRING=Release` if you're packaging
& 'C:\Program Files\CMake\bin\cmake' -S . -B "build/x64" -G "Visual Studio 17 2022" -T v143 -A x64
# Now you can run the executable in .\build\x64
# If you need to clean the project you can run
& 'C:\Program Files\CMake\bin\cmake.exe' --build .\build\x64 --target clean
# If you need to regenerate the asset headers to check them into source
& 'C:\Program Files\CMake\bin\cmake.exe' --build .\build\x64 --target ExtractAssetHeaders
# If you need a newer 2ship.o2r only
# Generate 2ship.o2r
& 'C:\Program Files\CMake\bin\cmake.exe' --build .\build\x64 --target Generate2ShipOtr
# Compile project
# Add `--config Release` if you're packaging
& 'C:\Program Files\CMake\bin\cmake.exe' --build .\build\x64
# Now you can run the executable in .\build\x64 or run in Visual Studio
```
### Developing 2S2H
@ -76,6 +71,19 @@ cd "build/x64"
& 'C:\Program Files\CMake\bin\cpack.exe' -G ZIP
```
### Additional CMake Targets
#### Clean
```powershell
# If you need to clean the project you can run
C:\Program Files\CMake\bin\cmake.exe --build build-cmake --target clean
```
#### Regenerate Asset Headers
```powershell
# If you need to regenerate the asset headers to check them into source
C:\Program Files\CMake\bin\cmake.exe --build build-cmake --target ExtractAssetHeaders
```
## Linux
### Install dependencies
#### Debian/Ubuntu
@ -124,13 +132,16 @@ cd 2ship2harkinian
git submodule update --init
# Generate Ninja project
cmake -H. -Bbuild-cmake -GNinja # -DCMAKE_BUILD_TYPE:STRING=Release (if you're packaging) -DPython3_EXECUTABLE=$(which python3) (if you are using non-standard Python installations such as PyEnv)
# Add `-DCMAKE_BUILD_TYPE:STRING=Release` if you're packaging
# Add `-DPython3_EXECUTABLE=$(which python3)` if you are using non-standard Python installations such as PyEnv
cmake -H. -Bbuild-cmake -GNinja
# Generate 2ship.o2r
cmake --build build-cmake --target Generate2ShipOtr
# Compile the project
cmake --build build-cmake # --config Release (if you're packaging)
# Add `--config Release` if you're packaging
cmake --build build-cmake
# Now you can run the executable in ./build-cmake/mm/2s2h.elf
# To develop the project open the repository in VSCode (or your preferred editor)
@ -157,7 +168,6 @@ cmake --build build-cmake --target clean
#### Regenerate Asset Headers
```bash
# If you need to regenerate the asset headers to check them into source
cp <path to your ROM> OTRExporter
cmake --build build-cmake --target ExtractAssetHeaders
```
@ -174,27 +184,21 @@ git clone https://github.com/HarbourMasters/2ship2harkinian.git
cd 2ship2harkinian
# Clone the submodule libultraship
git submodule update --init
# Copy the baserom to the OTRExporter folder
cp <path to your ROM> OTRExporter
# Generate Ninja project
cmake -H. -Bbuild-cmake -GNinja # -DCMAKE_BUILD_TYPE:STRING=Release (if you're packaging)
# Extract assets & generate OTR (run this anytime you need to regenerate OTR)
cmake --build build-cmake --target ExtractAssets
# Add `-DCMAKE_BUILD_TYPE:STRING=Release` if you're packaging
cmake -H. -Bbuild-cmake -GNinja
# Generate 2ship.o2r
cmake --build build-cmake --target Generate2ShipOtr
# Compile the project
cmake --build build-cmake # --config Release (if you're packaging)
# Add `--config Release` if you're packaging
cmake --build build-cmake
# Now you can run the executable file:
./build-cmake/mm/2s2h-macos
# To develop the project open the repository in VSCode (or your preferred editor)
# If you need to clean the project you can run
cmake --build build-cmake --target clean
# If you need to regenerate the asset headers to check them into source
cmake --build build-cmake --target ExtractAssetHeaders
# If you need a newer 2ship.o2r only
cmake --build build-cmake --target Generate2ShipOtr
```
### Generating a distributable
@ -206,6 +210,19 @@ cd build-cmake
cpack
```
### Additional CMake Targets
#### Clean
```bash
# If you need to clean the project you can run
cmake --build build-cmake --target clean
```
#### Regenerate Asset Headers
```bash
# If you need to regenerate the asset headers to check them into source
cmake --build build-cmake --target ExtractAssetHeaders
```
# Compatible Roms
See [`supportedHashes.json`](supportedHashes.json)

View File

@ -10,7 +10,6 @@
#include <File.h>
#include <DisplayList.h>
#include <Window.h>
#include <GameVersions.h>
#include "z64animation.h"
#include "z64bgcheck.h"
@ -27,6 +26,7 @@
#include "macros.h"
#include <utils/StringHelper.h>
#include <nlohmann/json.hpp>
#include "build.h"
#include <Fast3D/gfx_pc.h>
#include <Fast3D/gfx_rendering_api.h>
@ -145,10 +145,9 @@ OTRGlobals::OTRGlobals() {
}
}
}
std::unordered_set<uint32_t> ValidHashes = { OOT_PAL_MQ, OOT_NTSC_JP_MQ, OOT_NTSC_US_MQ, OOT_PAL_GC_MQ_DBG,
OOT_NTSC_US_10, OOT_NTSC_US_11, OOT_NTSC_US_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 };
std::unordered_set<uint32_t> validHashes = { MM_NTSC_US_10, MM_NTSC_US_GC };
// tell LUS to reserve 3 SoH specific threads (Game, Audio, Save)
context =
Ship::Context::CreateInstance("2 Ship 2 Harkinian", appShortName, "2ship2harkinian.json", archiveFiles, {}, 3,
@ -162,8 +161,6 @@ OTRGlobals::OTRGlobals() {
(spdlog::level::level_enum)CVarGetInteger("gDeveloperTools.LogLevel", 1));
Ship::Context::GetInstance()->GetLogger()->set_pattern("[%H:%M:%S.%e] [%s:%#] [%l] %v");
// context = Ship::Context::CreateUninitializedInstance("Ship of Harkinian", appShortName, "shipofharkinian.json");
auto overlay = context->GetInstance()->GetWindow()->GetGui()->GetGameOverlay();
overlay->LoadFont("Press Start 2P", "fonts/PressStart2P-Regular.ttf", 12.0f);
overlay->LoadFont("Fipps", "fonts/Fipps-Regular.otf", 32.0f);
@ -230,59 +227,22 @@ OTRGlobals::OTRGlobals() {
// gSaveStateMgr = std::make_shared<SaveStateMgr>();
// gRandomizer = std::make_shared<Randomizer>();
hasMasterQuest = hasOriginal = false;
// Move the camera strings from read only memory onto the heap (writable memory)
// This is in OTRGlobals right now because this is a place that will only ever be run once at the beginning of
// startup. We should probably find some code in db_camera that does initialization and only run once, and then
// dealloc on deinitialization.
// cameraStrings = (char**)malloc(sizeof(constCameraStrings));
// for (int32_t i = 0; i < sizeof(constCameraStrings) / sizeof(char*); i++) {
// // OTRTODO: never deallocated...
// auto dup = strdup(constCameraStrings[i]);
// cameraStrings[i] = dup;
//}
auto versions = context->GetResourceManager()->GetArchiveManager()->GetGameVersions();
#if 0
for (uint32_t version : versions) {
if (!ValidHashes.contains(version)) {
if (!validHashes.contains(version)) {
#if defined(__SWITCH__)
SPDLOG_ERROR("Invalid OTR File!");
SPDLOG_ERROR("Invalid O2R File!");
#elif defined(__WIIU__)
Ship::WiiU::ThrowInvalidOTR();
#else
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Invalid OTR File",
"Attempted to load an invalid OTR file. Try regenerating.", nullptr);
SPDLOG_ERROR("Invalid OTR File!");
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Invalid O2R File",
"Attempted to load an invalid O2R file. Try regenerating.", nullptr);
SPDLOG_ERROR("Invalid O2R File!");
#endif
exit(1);
}
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_US_10:
case OOT_NTSC_US_11:
case OOT_NTSC_US_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;
}
}
#endif
fontMono = CreateFontWithSize(16.0f, "fonts/Inconsolata-Regular.ttf");
fontMonoLarger = CreateFontWithSize(20.0f, "fonts/Inconsolata-Regular.ttf");
@ -296,14 +256,6 @@ OTRGlobals::OTRGlobals() {
OTRGlobals::~OTRGlobals() {
}
bool OTRGlobals::HasMasterQuest() {
return hasMasterQuest;
}
bool OTRGlobals::HasOriginal() {
return hasOriginal;
}
uint32_t OTRGlobals::GetInterpolationFPS() {
if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) {
return CVarGetInteger("gInterpolationFPS", 20);
@ -479,23 +431,198 @@ void Ben_ProcessDroppedFiles(std::string filePath) {
}
}
extern "C" void InitOTR() {
typedef struct {
uint16_t major;
uint16_t minor;
uint16_t patch;
} ArchiveVersion;
// Read the port version from an archive file
ArchiveVersion ReadPortVersionFromArchive(std::string archivePath, bool isO2rType) {
ArchiveVersion version = {};
// Use a temporary archive instance to load the archive appropriately and read the version file
std::shared_ptr<Ship::Archive> archive;
if (isO2rType) {
archive = make_shared<Ship::O2rArchive>(archivePath);
} else {
archive = make_shared<Ship::OtrArchive>(archivePath);
}
if (archive->Open()) {
auto t = archive->LoadFile("portVersion", std::make_shared<Ship::ResourceInitData>());
if (t != nullptr && t->IsLoaded) {
auto stream = std::make_shared<Ship::MemoryStream>(t->Buffer->data(), t->Buffer->size());
auto reader = std::make_shared<Ship::BinaryReader>(stream);
Ship::Endianness endianness = (Ship::Endianness)reader->ReadUByte();
reader->SetEndianness(endianness);
version.major = reader->ReadUInt16();
version.minor = reader->ReadUInt16();
version.patch = reader->ReadUInt16();
}
archive->Close();
}
return version;
}
// Check that a 2ship.o2r exists and matches the version of 2ship running
// Otherwise show a message and exit
void Check2ShipArchiveVersion(std::string archivePath) {
std::string msg;
#if defined(__SWITCH__)
msg = "\x1b[4;2HPlease re-extract it from the download."
"\x1b[6;2HPress the Home button to exit...";
#elif defined(__WIIU__)
msg = "Please extract the 2ship.o2r from the 2 Ship 2 Harkinian download\nto your folder.\n\n"
"Press and hold the power button to shutdown...";
#else
msg = "Please extract the 2ship.o2r from the 2 Ship 2 Harkinian download to your folder.\n\nExiting...";
#endif
if (!std::filesystem::exists(archivePath)) {
#if not defined(__SWITCH__) && not defined(__WIIU__)
if (!std::filesystem::exists(Ship::Context::LocateFileAcrossAppDirs("mm.o2r", appShortName)) &&
!std::filesystem::exists(Ship::Context::LocateFileAcrossAppDirs("mm.zip", appShortName)) &&
!std::filesystem::exists(Ship::Context::LocateFileAcrossAppDirs("mm.otr", appShortName))) {
Extractor::ShowErrorBox("2ship.o2r file is missing", msg.c_str());
exit(1);
#elif defined(__SWITCH__)
Ship::Switch::PrintErrorMessageToScreen(("\x1b[2;2HYou are missing the 2ship.o2r file." + msg).c_str());
#elif defined(__WIIU__)
OSFatal(("You are missing the 2ship.o2r file\n\n" + msg).c_str());
#endif
}
ArchiveVersion archiveVer = ReadPortVersionFromArchive(archivePath, true);
if (archiveVer.major != gBuildVersionMajor || archiveVer.minor != gBuildVersionMinor ||
archiveVer.patch != gBuildVersionPatch) {
#if not defined(__SWITCH__) && not defined(__WIIU__)
Extractor::ShowErrorBox("2ship.o2r file version does not match", msg.c_str());
exit(1);
#elif defined(__SWITCH__)
Ship::Switch::PrintErrorMessageToScreen(("\x1b[2;2HYou have an old 2ship.o2r file." + msg).c_str());
#elif defined(__WIIU__)
OSFatal(("You have an old 2ship.o2r file\n\n" + msg).c_str());
#endif
}
}
// Checks the program version stored in the o2r and compares the major/minor value to 2ship
// For Windows/Mac/Linux if the version doesn't match, offer to regenerate it
void DetectArchiveVersion(std::string fileName, bool isO2rType) {
bool isArchiveOld = false;
std::string archivePath = Ship::Context::LocateFileAcrossAppDirs(fileName, appShortName);
// Doesn't exist so nothing to do here
if (!std::filesystem::exists(archivePath)) {
return;
}
ArchiveVersion archiveVer = ReadPortVersionFromArchive(archivePath, isO2rType);
// Check both major and minor for game archives
if (archiveVer.major != gBuildVersionMajor || archiveVer.minor != gBuildVersionMinor) {
isArchiveOld = true;
}
if (isArchiveOld) {
#if not defined(__SWITCH__) && not defined(__WIIU__)
char msgBuf[250];
char version[18]; // 5 digits for int16_max (x3) + separators + terminator
if (archiveVer.major != 0 || archiveVer.minor != 0 || archiveVer.patch != 0) {
snprintf(version, 18, "%d.%d.%d", archiveVer.major, archiveVer.minor, archiveVer.patch);
} else {
snprintf(version, 18, "no version found");
}
snprintf(msgBuf, 250,
"The %s file was generated with a different version of 2 Ship 2 Harkinian.\n"
"O2R version: %s\n\n"
"You must regenerate to be able to play, otherwise the program will exit.\n"
"Would you like to regenerate it now?",
fileName.c_str(), version);
if (Extractor::ShowYesNoBox("Old O2R File Found", msgBuf) == IDYES) {
std::string installPath = Ship::Context::GetAppBundlePath();
if (!std::filesystem::exists(installPath + "/assets/extractor")) {
Extractor::ShowErrorBox(
"Extractor assets not found",
"Unable to regenerate. Missing assets/extractor folder needed to generate O2R file.\n\nExiting...");
exit(1);
}
Extractor extract;
if (!extract.Run(Ship::Context::GetAppDirectoryPath(appShortName))) {
Extractor::ShowErrorBox("Error", "An error occurred, no O2R file was generated.\n\nExiting...");
exit(1);
}
// We can only regenerate O2R archives, so we should just delete the old OTR file
if (!isO2rType) {
std::filesystem::remove(archivePath);
}
extract.CallZapd(installPath, Ship::Context::GetAppDirectoryPath(appShortName));
// Rename the new O2R with the previously used extension
if (isO2rType) {
std::filesystem::rename(Ship::Context::LocateFileAcrossAppDirs("mm.o2r", appShortName), archivePath);
}
} else {
exit(1);
}
#elif defined(__SWITCH__)
Ship::Switch::PrintErrorMessageToScreen("\x1b[2;2HYou've launched the 2Ship with an old game O2R file."
"\x1b[4;2HPlease regenerate a new game O2R and relaunch."
"\x1b[6;2HPress the Home button to exit...");
#elif defined(__WIIU__)
OSFatal("You've launched the 2Ship with an old a game O2R file.\n\n"
"Please generate a game O2R and relaunch.\n\n"
"Press and hold the Power button to shutdown...");
#endif
}
}
extern "C" void InitOTR() {
#ifdef __SWITCH__
Ship::Switch::Init(Ship::PreInitPhase);
#elif defined(__WIIU__)
Ship::WiiU::Init(appShortName);
#endif
// BENTODO: OTRExporter is filling the version file with garbage. Uncomment once fixed.
// Check2ShipArchiveVersion(Ship::Context::GetPathRelativeToAppBundle("2ship.o2r"));
std::string mmPathO2R = Ship::Context::LocateFileAcrossAppDirs("mm.o2r", appShortName);
std::string mmPathZIP = Ship::Context::LocateFileAcrossAppDirs("mm.zip", appShortName);
std::string mmPathOtr = Ship::Context::LocateFileAcrossAppDirs("mm.otr", appShortName);
// Check game archives in preferred order
if (std::filesystem::exists(mmPathO2R)) {
DetectArchiveVersion("mm.o2r", true);
} else if (std::filesystem::exists(mmPathZIP)) {
DetectArchiveVersion("mm.zip", true);
} else if (std::filesystem::exists(mmPathOtr)) {
DetectArchiveVersion("mm.otr", false);
}
#if not defined(__SWITCH__) && not defined(__WIIU__)
if (!std::filesystem::exists(mmPathO2R) && !std::filesystem::exists(mmPathZIP) &&
!std::filesystem::exists(mmPathOtr)) {
std::string installPath = Ship::Context::GetAppBundlePath();
if (!std::filesystem::exists(installPath + "/assets/extractor")) {
Extractor::ShowErrorBox(
"Extractor assets not found",
"No game O2R files found. Missing assets/extractor folder needed to generate O2R file. Exiting...");
"No game O2R file found. Missing assets/extractor folder needed to generate O2R file. Exiting...");
exit(1);
}
if (Extractor::ShowYesNoBox("No O2R Files", "No O2R files found. Generate one now?") == IDYES) {
if (Extractor::ShowYesNoBox("No O2R File", "No O2R files found. Generate one now?") == IDYES) {
Extractor extract;
if (!extract.Run()) {
Extractor::ShowErrorBox("Error", "An error occurred, no OTR file was generated. Exiting...");
if (!extract.Run(Ship::Context::GetAppDirectoryPath(appShortName))) {
Extractor::ShowErrorBox("Error", "An error occurred, no O2R file was generated. Exiting...");
exit(1);
}
extract.CallZapd(installPath, Ship::Context::GetAppDirectoryPath(appShortName));
@ -505,12 +632,6 @@ extern "C" void InitOTR() {
}
#endif
#ifdef __SWITCH__
Ship::Switch::Init(Ship::PreInitPhase);
#elif defined(__WIIU__)
Ship::WiiU::Init("soh");
#endif
OTRGlobals::Instance = new OTRGlobals();
GameInteractor::Instance = new GameInteractor();
LoadGuiTextures();
@ -839,22 +960,8 @@ extern "C" uint32_t ResourceMgr_GetGamePlatform(int index) {
Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->GetGameVersions()[index];
switch (version) {
case OOT_NTSC_US_10:
case OOT_NTSC_US_11:
case OOT_NTSC_US_12:
case OOT_PAL_10:
case OOT_PAL_11:
case MM_NTSC_US_10:
return GAME_PLATFORM_N64;
case OOT_NTSC_JP_GC:
case OOT_NTSC_US_GC:
case OOT_PAL_GC:
case OOT_NTSC_JP_MQ:
case OOT_NTSC_US_MQ:
case OOT_PAL_MQ:
case OOT_PAL_GC_DBG1:
case OOT_PAL_GC_DBG2:
case OOT_PAL_GC_MQ_DBG:
case MM_NTSC_US_GC:
return GAME_PLATFORM_GC;
}
@ -865,24 +972,9 @@ extern "C" uint32_t ResourceMgr_GetGameRegion(int index) {
Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->GetGameVersions()[index];
switch (version) {
case OOT_NTSC_US_10:
case OOT_NTSC_US_11:
case OOT_NTSC_US_12:
case OOT_NTSC_JP_GC:
case OOT_NTSC_US_GC:
case OOT_NTSC_JP_MQ:
case OOT_NTSC_US_MQ:
case MM_NTSC_US_10:
case MM_NTSC_US_GC:
return GAME_REGION_NTSC;
case OOT_PAL_10:
case OOT_PAL_11:
case OOT_PAL_GC:
case OOT_PAL_MQ:
case OOT_PAL_GC_DBG1:
case OOT_PAL_GC_DBG2:
case OOT_PAL_GC_MQ_DBG:
return GAME_REGION_PAL;
}
}

View File

@ -36,16 +36,12 @@ class OTRGlobals {
OTRGlobals();
~OTRGlobals();
bool HasMasterQuest();
bool HasOriginal();
uint32_t GetInterpolationFPS();
std::shared_ptr<std::vector<std::string>> ListFiles(std::string path);
private:
ImFont* CreateFontWithSize(float size, std::string fontPath = "");
void CheckSaveFile(size_t sramSize) const;
bool hasMasterQuest;
bool hasOriginal;
};
uint32_t IsGameMasterQuest();

View File

@ -51,42 +51,18 @@
extern "C" uint32_t CRC32C(unsigned char* data, size_t dataSize);
static constexpr uint32_t OOT_PAL_GC = 0x09465AC3;
static constexpr uint32_t OOT_PAL_MQ = 0x1D4136F3;
static constexpr uint32_t OOT_PAL_GC_DBG1 = 0x871E1C92; // 03-21-2002 build
static constexpr uint32_t OOT_PAL_GC_DBG2 = 0x87121EFE; // 03-13-2002 build
static constexpr uint32_t OOT_PAL_GC_MQ_DBG = 0x917D18F6;
static constexpr uint32_t OOT_PAL_10 = 0xB044B569;
static constexpr uint32_t OOT_PAL_11 = 0xB2055FBD;
static constexpr uint32_t MM_US_10 = 0x5354631C;
static constexpr uint32_t MM_US_GC = 0xB443EB08;
static const std::unordered_map<uint32_t, const char*> verMap = {
{ MM_US_10, "US 1.0" }, { MM_US_GC, "US GC" },
//{ OOT_PAL_GC, "PAL Gamecube" },
//{ OOT_PAL_MQ, "PAL MQ" },
//{ OOT_PAL_GC_DBG1, "PAL Debug 1" },
//{ OOT_PAL_GC_DBG2, "PAL Debug 2" },
//{ OOT_PAL_GC_MQ_DBG, "PAL MQ Debug" },
//{ OOT_PAL_10, "PAL N64 1.0" },
//{ OOT_PAL_11, "PAL N64 1.1" },
{ MM_US_10, "US 1.0" },
{ MM_US_GC, "US GC" },
};
// TODO only check the first 54MB of the rom.
static constexpr std::array<const uint32_t, 10> goodCrcs = {
0x96F49400, // MM US 1.0 32MB
0xBB434787, // MM GC
// 0xfa8c0555, // MQ DBG 64MB (Original overdump)
// 0x8652ac4c, // MQ DBG 64MB
// 0x5B8A1EB7, // MQ DBG 64MB (Empty overdump)
// 0x1f731ffe, // MQ DBG 54MB
// 0x044b3982, // NMQ DBG 54MB
// 0xEB15D7B9, // NMQ DBG 64MB
// 0xDA8E61BF, // GC PAL
// 0x7A2FAE68, // GC MQ PAL
// 0xFD9913B1, // N64 PAL 1.0
// 0xE033FBBA, // N64 PAL 1.1
};
enum class ButtonId : int {
@ -112,7 +88,13 @@ void Extractor::ShowSizeErrorBox() const {
}
void Extractor::ShowCrcErrorBox() const {
ShowErrorBox("Rom CRC invalid", "Rom CRC did not match the list of known good roms. Please find another.");
ShowErrorBox("Rom CRC invalid",
"Rom CRC did not match the list of known compatible roms. Please find another.\n\n"
"Visit https://2ship.equipment/ to validate your ROM and see a list of compatible versions");
}
void Extractor::ShowCompressedErrorBox() const {
ShowErrorBox("File is Compressed", "The selected file appears to be compressed. Please extract before using.");
}
int Extractor::ShowRomPickBox(uint32_t verCrc) const {
@ -225,7 +207,7 @@ void Extractor::GetRoms(std::vector<std::string>& roms) {
//}
#elif unix
// Open the directory of the app.
DIR* d = opendir(".");
DIR* d = opendir(mSearchPath.c_str());
struct dirent* dir;
if (d != NULL) {
@ -247,7 +229,7 @@ void Extractor::GetRoms(std::vector<std::string>& roms) {
}
closedir(d);
#else
for (const auto& file : std::filesystem::directory_iterator("./")) {
for (const auto& file : std::filesystem::directory_iterator(mSearchPath)) {
if (file.is_directory())
continue;
if ((file.path().extension() == ".n64") || (file.path().extension() == ".z64") ||
@ -297,7 +279,7 @@ bool Extractor::GetRomPathFromBox() {
}
mCurrentRomPath = nameBuffer;
#else
auto selection = pfd::open_file("Select a file", ".", { "N64 Roms", "*.z64 *.n64 *.v64" }).result();
auto selection = pfd::open_file("Select a file", mSearchPath, { "N64 Roms", "*.z64 *.n64 *.v64" }).result();
if (selection.empty()) {
return false;
@ -318,11 +300,6 @@ size_t Extractor::GetCurRomSize() const {
}
bool Extractor::ValidateAndFixRom() {
// The MQ debug rom sometimes has the header patched to look like a US rom. Change it back
if (GetRomVerCrc() == OOT_PAL_GC_MQ_DBG) {
mRomData[0x3E] = 'P';
}
const uint32_t actualCrc = CRC32C(mRomData.get(), mCurRomSize);
for (const uint32_t crc : goodCrcs) {
@ -333,6 +310,25 @@ bool Extractor::ValidateAndFixRom() {
return false;
}
// The file box will only allow selecting an n64 rom but typing in the file name will allow selecting anything.
bool Extractor::ValidateNotCompressed() const {
// ZIP file header
if (mRomData[0] == 'P' && mRomData[1] == 'K' && mRomData[2] == 0x03 && mRomData[3] == 0x04) {
return false;
}
// RAR file header. Only the first 4 bytes.
if (mRomData[0] == 'R' && mRomData[1] == 'a' && mRomData[2] == 'r' && mRomData[3] == 0x21) {
return false;
}
// 7z file header. 37 7A BC AF 27 1C
if (mRomData[0] == '7' && mRomData[1] == 'z' && mRomData[2] == 0xBC && mRomData[3] == 0xAF && mRomData[4] == 0x27 &&
mRomData[5] == 0x1C) {
return false;
}
return true;
}
bool Extractor::ValidateRomSize() const {
if (mCurRomSize != MB32 && mCurRomSize != MB54 && mCurRomSize != MB64) {
return false;
@ -341,6 +337,10 @@ bool Extractor::ValidateRomSize() const {
}
bool Extractor::ValidateRom(bool skipCrcTextBox) {
if (!ValidateNotCompressed()) {
ShowCompressedErrorBox();
return false;
}
if (!ValidateRomSize()) {
ShowSizeErrorBox();
return false;
@ -410,10 +410,12 @@ bool Extractor::ManuallySearchForRomMatchingType(RomSearchMode searchMode) {
return true;
}
bool Extractor::Run(RomSearchMode searchMode) {
bool Extractor::Run(std::string searchPath, RomSearchMode searchMode) {
std::vector<std::string> roms;
std::ifstream inFile;
mSearchPath = searchPath;
GetRoms(roms);
FilterRoms(roms, searchMode);
@ -456,8 +458,10 @@ bool Extractor::Run(RomSearchMode searchMode) {
if (rom == roms.back()) {
ShowCrcErrorBox();
} else {
ShowErrorBox("Rom CRC invalid",
"Rom CRC did not match the list of known good roms. Trying the next one...");
ShowErrorBox(
"Rom CRC invalid",
"Rom CRC did not match the list of known compatible roms. Trying the next one...\n\n"
"Visit https://2ship.equipment/ to validate your ROM and see a list of compatible versions");
}
continue;
}
@ -480,34 +484,11 @@ bool Extractor::Run(RomSearchMode searchMode) {
}
bool Extractor::IsMasterQuest() const {
switch (GetRomVerCrc()) {
case OOT_PAL_MQ:
case OOT_PAL_GC_MQ_DBG:
return true;
case OOT_PAL_10:
case OOT_PAL_11:
case OOT_PAL_GC:
case OOT_PAL_GC_DBG1:
return false;
default:
UNREACHABLE;
}
return false;
}
const char* Extractor::GetZapdVerStr() const {
switch (GetRomVerCrc()) {
case OOT_PAL_GC:
return "GC_NMQ_PAL_F";
case OOT_PAL_MQ:
return "GC_MQ_PAL_F";
case OOT_PAL_GC_DBG1:
return "GC_NMQ_D";
case OOT_PAL_GC_MQ_DBG:
return "GC_MQ_D";
case OOT_PAL_10:
return "N64_PAL_10";
case OOT_PAL_11:
return "N64_PAL_11";
case MM_US_10:
return "N64_US";
case MM_US_GC:

View File

@ -28,6 +28,7 @@ enum class RomSearchMode {
class Extractor {
std::unique_ptr<unsigned char[]> mRomData = std::make_unique<unsigned char[]>(MB64);
std::string mCurrentRomPath;
std::string mSearchPath;
size_t mCurRomSize = 0;
bool GetRomPathFromBox();
@ -38,6 +39,7 @@ class Extractor {
bool ValidateRomSize() const;
bool ValidateRom(bool skipCrcBox = false);
bool ValidateNotCompressed() const;
const char* GetZapdVerStr() const;
void SetRomInfo(const std::string& path);
@ -46,6 +48,7 @@ class Extractor {
void GetRoms(std::vector<std::string>& roms);
void ShowSizeErrorBox() const;
void ShowCrcErrorBox() const;
void ShowCompressedErrorBox() const;
int ShowRomPickBox(uint32_t verCrc) const;
bool ManuallySearchForRom();
bool ManuallySearchForRomMatchingType(RomSearchMode searchMode);
@ -56,7 +59,7 @@ class Extractor {
static void ShowErrorBox(const char* title, const char* text);
bool IsMasterQuest() const;
bool Run(RomSearchMode searchMode = RomSearchMode::Both);
bool Run(std::string searchPath, RomSearchMode searchMode = RomSearchMode::Both);
bool CallZapd(std::string installPath, std::string exportdir);
const char* GetZapdStr();
std::string Mkdtemp();

View File

@ -524,18 +524,22 @@ endif()
################################################################################
# Pre build events
################################################################################
if (CMAKE_GENERATOR MATCHES "Visual Studio")
set(VS_COPY_ASSETS_CMD ${CMAKE_COMMAND} -E copy_directory $<TARGET_FILE_DIR:2ship>/assets ${CMAKE_BINARY_DIR}/mm/assets)
endif()
if(NOT CMAKE_SYSTEM_NAME MATCHES "NintendoSwitch|CafeOS")
add_custom_command(
TARGET ${PROJECT_NAME}
POST_BUILD
COMMENT "Copying asset xmls..."
TARGET ${PROJECT_NAME}
POST_BUILD
COMMENT "Copying asset xmls..."
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/mm/assets/extractor $<TARGET_FILE_DIR:2ship>/assets/extractor
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/mm/assets/xml $<TARGET_FILE_DIR:2ship>/assets/extractor/xmls
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/mm/assets/xml $<TARGET_FILE_DIR:2ship>/assets/extractor/xmls
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/OTRExporter/CFG/filelists $<TARGET_FILE_DIR:2ship>/assets/extractor/filelists
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:2ship>/assets/extractor/symbols
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/OTRExporter/CFG/ActorList_MM.txt $<TARGET_FILE_DIR:2ship>/assets/extractor/symbols
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/OTRExporter/CFG/ObjectList_MM.txt $<TARGET_FILE_DIR:2ship>/assets/extractor/symbols
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/OTRExporter/CFG/SymbolMap_MM.txt $<TARGET_FILE_DIR:2ship>/assets/extractor/symbols
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/OTRExporter/CFG/SymbolMap_MM.txt $<TARGET_FILE_DIR:2ship>/assets/extractor/symbols
COMMAND ${VS_COPY_ASSETS_CMD}
)
endif()
################################################################################