Files
Shiip-of-Hakinian-Espanol/soh/soh/OTRGlobals.cpp
Ali 99c1f23d5b fix: Fix macOS crashing on first install use (#6412)
With the new imgui OTR generation flow, macOS would crash when Ship
was run for the first time unless com.shipofharkinian.soh had already been created.
Move the call to CheckAndCreateModFolder() earlier in execution to prevent crashing.
2026-03-26 15:12:51 +00:00

2575 lines
120 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "OTRGlobals.h"
#include "OTRAudio.h"
#include <algorithm>
#include <atomic>
#include <filesystem>
#include <fstream>
#include <vector>
#include <chrono>
#include <optional>
#include "ResourceManagerHelpers.h"
#include <fast/Fast3dWindow.h>
#include <ship/resource/File.h>
#include <ship/window/Window.h>
#include <soh/GameVersions.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include "Enhancements/gameconsole.h"
#ifdef _WIN32
#include <Windows.h>
#else
#include <time.h>
#endif
#include <ship/audio/AudioPlayer.h>
#include "Enhancements/speechsynthesizer/SpeechSynthesizer.h"
#include "Enhancements/controls/SohInputEditorWindow.h"
#include "Enhancements/audio/AudioCollection.h"
#include "Enhancements/debugconsole.h"
#include "Enhancements/randomizer/randomizer.h"
#include "Enhancements/randomizer/randomizer_entrance_tracker.h"
#include "Enhancements/randomizer/randomizer_check_tracker.h"
#include "Enhancements/randomizer/static_data.h"
#include "Enhancements/gameplaystats.h"
#include "frame_interpolation.h"
#include "SohGui/SohMenu.h"
#include "SohGui/SohGui.hpp"
#include "variables.h"
#include "z64.h"
#include "macros.h"
#include <ship/window/gui/Fonts.h>
#include <ship/window/FileDropMgr.h>
#include <ship/window/gui/resource/Font.h>
#include <ship/utils/StringHelper.h>
#include "Enhancements/custom-message/CustomMessageManager.h"
#include "util.h"
#if not defined(__SWITCH__) && not defined(__WIIU__)
#include "Extractor/Extract.h"
#endif
#include <fast/interpreter.h>
#ifdef __APPLE__
#include <SDL_scancode.h>
#else
#include <SDL2/SDL_scancode.h>
#endif
#ifdef __SWITCH__
#include <port/switch/SwitchImpl.h>
#elif defined(__WIIU__)
#include <port/wiiu/WiiUImpl.h>
#include <coreinit/debug.h> // OSFatal
#endif
#include <functions.h>
#include "Enhancements/item-tables/ItemTableManager.h"
#include "soh/SohGui/ImGuiUtils.h"
#include "ActorDB.h"
#include "SaveManager.h"
#include "soh/Network/CrowdControl/CrowdControl.h"
#include "soh/Network/Sail/Sail.h"
#include "soh/Network/Anchor/Anchor.h"
#include "Enhancements/mods.h"
#include "Enhancements/game-interactor/GameInteractor.h"
#include "Enhancements/randomizer/draw.h"
#include <libultraship/libultraship.h>
#include <libultraship/controller/controldeck/ControlDeck.h>
#include <fast/resource/ResourceType.h>
// Resource Types/Factories
#include "soh/resource/type/Array.h"
#include <ship/resource/type/Blob.h>
#include <fast/resource/type/DisplayList.h>
#include <fast/resource/type/Matrix.h>
#include <fast/resource/type/Texture.h>
#include <fast/resource/type/Vertex.h>
#include "soh/resource/type/SohResourceType.h"
#include "soh/resource/type/Animation.h"
#include "soh/resource/type/AudioSample.h"
#include "soh/resource/type/AudioSequence.h"
#include "soh/resource/type/AudioSoundFont.h"
#include "soh/resource/type/CollisionHeader.h"
#include "soh/resource/type/Cutscene.h"
#include "soh/resource/type/Path.h"
#include "soh/resource/type/PlayerAnimation.h"
#include "soh/resource/type/Scene.h"
#include "soh/resource/type/Skeleton.h"
#include "soh/resource/type/SkeletonLimb.h"
#include "soh/resource/type/Text.h"
#include <ship/resource/factory/BlobFactory.h>
#include <fast/resource/factory/DisplayListFactory.h>
#include <fast/resource/factory/MatrixFactory.h>
#include <fast/resource/factory/TextureFactory.h>
#include <fast/resource/factory/VertexFactory.h>
#include "soh/resource/importer/ArrayFactory.h"
#include "soh/resource/importer/AnimationFactory.h"
#include "soh/resource/importer/AudioSampleFactory.h"
#include "soh/resource/importer/AudioSequenceFactory.h"
#include "soh/resource/importer/AudioSoundFontFactory.h"
#include "soh/resource/importer/CollisionHeaderFactory.h"
#include "soh/resource/importer/CutsceneFactory.h"
#include "soh/resource/importer/PathFactory.h"
#include "soh/resource/importer/PlayerAnimationFactory.h"
#include "soh/resource/importer/SceneFactory.h"
#include "soh/resource/importer/SkeletonFactory.h"
#include "soh/resource/importer/SkeletonLimbFactory.h"
#include "soh/resource/importer/TextFactory.h"
#include "soh/resource/importer/BackgroundFactory.h"
#include "soh/config/ConfigUpdaters.h"
#include "soh/ShipInit.hpp"
bool SoH_HandleConfigDrop(char* filePath);
OTRGlobals* OTRGlobals::Instance;
SaveManager* SaveManager::Instance;
CustomMessageManager* CustomMessageManager::Instance;
ItemTableManager* ItemTableManager::Instance;
GameInteractor* GameInteractor::Instance;
AudioCollection* AudioCollection::Instance;
SpeechSynthesizer* SpeechSynthesizer::Instance;
CrowdControl* CrowdControl::Instance;
Sail* Sail::Instance;
Anchor* Anchor::Instance;
extern "C" char** cameraStrings;
extern "C" void PadMgr_ThreadEntry(PadMgr* padMgr);
std::vector<std::shared_ptr<std::string>> cameraStdStrings;
Color_RGB8 kokiriColor = { 0x1E, 0x69, 0x1B };
Color_RGB8 goronColor = { 0x64, 0x14, 0x00 };
Color_RGB8 zoraColor = { 0x00, 0xEC, 0x64 };
int32_t previousImGuiScaleIndex;
float previousImGuiScale;
bool prevAltAssets = false;
// Same as NaviColor type from OoT src (z_actor.c), but modified to be sans alpha channel for Controller LED.
typedef struct {
Color_RGB8 inner;
Color_RGB8 outer;
} NaviColor_RGB8;
static NaviColor_RGB8 defaultIdleColor = { { 255, 255, 255 }, { 0, 0, 255 } };
static NaviColor_RGB8 defaultNPCColor = { { 150, 150, 255 }, { 150, 150, 255 } };
static NaviColor_RGB8 defaultEnemyColor = { { 255, 255, 0 }, { 200, 155, 0 } };
static NaviColor_RGB8 defaultPropsColor = { { 0, 255, 0 }, { 0, 255, 0 } };
// Labeled according to ActorCategory (included through ActorDB.h)
const NaviColor_RGB8 LEDColorDefaultNaviColorList[] = {
defaultPropsColor, // ACTORCAT_SWITCH Switch
defaultPropsColor, // ACTORCAT_BG Background (Prop type 1)
defaultIdleColor, // ACTORCAT_PLAYER Player
defaultPropsColor, // ACTORCAT_EXPLOSIVE Bomb
defaultNPCColor, // ACTORCAT_NPC NPC
defaultEnemyColor, // ACTORCAT_ENEMY Enemy
defaultPropsColor, // ACTORCAT_PROP Prop type 2
defaultPropsColor, // ACTORCAT_ITEMACTION Item/Action
defaultPropsColor, // ACTORCAT_MISC Misc.
defaultEnemyColor, // ACTORCAT_BOSS Boss
defaultPropsColor, // ACTORCAT_DOOR Door
defaultPropsColor, // ACTORCAT_CHEST Chest
defaultPropsColor, // ACTORCAT_MAX
};
// OTRTODO: A lot of these left in Japanese are used by the mempak manager. LUS does not currently support mempaks.
// Ignore unused ones.
const char* constCameraStrings[] = {
"INSUFFICIENT",
"KEYFRAMES",
"YOU CAN ADD MORE",
"FINISHED",
"PLAYING",
"DEMO CAMERA TOOL",
"CANNOT PLAY",
"KEYFRAME ",
"PNT / ",
"> >",
"< <",
"< >",
GFXP_KATAKANA "*プレイヤ-*",
"E MODE FIX",
"E MODE ABS",
GFXP_HIRAGANA "ガメン" GFXP_KATAKANA " デモ", // OTRTODO: Unused, get a translation! Number 15
GFXP_HIRAGANA "ガメン フツウ", // OTRTODO: Unused, get a translation! Number 16
"P TIME MAX",
GFXP_KATAKANA "リンク" GFXP_HIRAGANA " キオク", // OTRTODO: Unused, get a translation! Number 18
GFXP_KATAKANA "リンク" GFXP_HIRAGANA " ムシ", // OTRTODO: Unused, get a translation! Number 19
"*VIEWPT*",
"*CAMPOS*",
"DEBUG CAMERA",
"CENTER/LOCK",
"CENTER/FREE",
"DEMO CONTROL",
GFXP_KATAKANA "メモリ" GFXP_HIRAGANA "ガタリマセン",
"p",
"e",
"s",
"l",
"c",
GFXP_KATAKANA "メモリパック",
GFXP_KATAKANA "セーブ",
GFXP_KATAKANA "ロード",
GFXP_KATAKANA "クリア-",
GFXP_HIRAGANA "ヲヌカナイデネ",
"FREE BYTE",
"NEED BYTE",
GFXP_KATAKANA "*メモリ-パック*",
GFXP_HIRAGANA "ヲミツケラレマセン",
GFXP_KATAKANA "ファイル " GFXP_HIRAGANA "",
GFXP_HIRAGANA "シテモイイデスカ?",
GFXP_HIRAGANA "ゲンザイヘンシュウチュウノ", // OTRTODO: Unused, get a translation! Number 43
GFXP_KATAKANA "ファイル" GFXP_HIRAGANA "ハハキサレマス", // OTRTODO: Unused, get a translation! Number 44
GFXP_HIRAGANA "ハイ",
GFXP_HIRAGANA "イイエ",
GFXP_HIRAGANA "シテイマス",
GFXP_HIRAGANA "ウワガキ", // OTRTODO: Unused, get a translation! Number 48
GFXP_HIRAGANA "シマシタ",
"USE BYTE",
GFXP_HIRAGANA "ニシッパイ",
"E MODE REL",
"FRAME ",
"KEY / ",
"(CENTER)",
"(ORIG)",
"(PLAYER)",
"(ALIGN)",
"(SET)",
"(OBJECT)",
GFXP_KATAKANA "ポイントNo. ", // OTRTODO: Unused, need translation. Number 62
"FOV ",
"N FRAME ",
"Z ROT ",
GFXP_KATAKANA "モ-ド ", // OTRTODO: Unused, need translation. Number 65
" R FOCUS ",
"PMAX ",
"DEPTH ",
"XROT ",
"YROT ",
GFXP_KATAKANA "フレ-ム ",
GFXP_KATAKANA "ト-タル ",
GFXP_KATAKANA "キ- / ",
};
typedef struct {
uint16_t major;
uint16_t minor;
uint16_t patch;
} OTRVersion;
std::shared_ptr<Fast::Fast3dWindow> sohFast3dWindow;
static OTRVersion DetectOTRVersion(std::string path, bool isMq);
static bool VerifyArchiveVersion(OTRVersion version);
std::string portArchivePath = "";
static bool sohArchiveVersionMatch = false;
OTRGlobals::OTRGlobals() {
context = Ship::Context::CreateUninitializedInstance("Ship of Harkinian", appShortName, "shipofharkinian.json");
portArchivePath = Ship::Context::LocateFileAcrossAppDirs("soh.o2r");
OTRVersion portArchiveVersion = DetectOTRVersion("soh.o2r", false);
sohArchiveVersionMatch = portArchiveVersion.major == gBuildVersionMajor &&
portArchiveVersion.minor == gBuildVersionMinor &&
portArchiveVersion.patch == gBuildVersionPatch;
context->InitConfiguration();
context->InitConsoleVariables();
auto controlDeck = std::make_shared<LUS::ControlDeck>(std::vector<CONTROLLERBUTTONS_T>({
BTN_CUSTOM_MODIFIER1,
BTN_CUSTOM_MODIFIER2,
BTN_CUSTOM_OCARINA_NOTE_D4,
BTN_CUSTOM_OCARINA_NOTE_F4,
BTN_CUSTOM_OCARINA_NOTE_A4,
BTN_CUSTOM_OCARINA_NOTE_B4,
BTN_CUSTOM_OCARINA_NOTE_D5,
BTN_CUSTOM_OCARINA_DISABLE_SONGS,
BTN_CUSTOM_OCARINA_PITCH_UP,
BTN_CUSTOM_OCARINA_PITCH_DOWN,
}));
context->InitControlDeck(controlDeck);
context->InitResourceManager({ portArchivePath }, {}, 3, true);
context->InitConsole();
auto sohInputEditorWindow =
std::make_shared<SohInputEditorWindow>(CVAR_WINDOW("ControllerConfiguration"), "Configure Controller");
sohFast3dWindow =
std::make_shared<Fast::Fast3dWindow>(std::vector<std::shared_ptr<Ship::GuiWindow>>({ sohInputEditorWindow }));
context->InitWindow(sohFast3dWindow);
SohGui::SetupMenu();
if (sohArchiveVersionMatch) {
auto overlay = context->GetInstance()->GetWindow()->GetGui()->GetGameOverlay();
overlay->LoadFont("Press Start 2P", 12.0f, "fonts/PressStart2P-Regular.ttf");
overlay->LoadFont("Fipps", 32.0f, "fonts/Fipps-Regular.otf");
overlay->SetCurrentFont(CVarGetString(CVAR_GAME_OVERLAY_FONT, "Press Start 2P"));
fontMonoSmall = CreateFontWithSize(14.0f, "fonts/Inconsolata-Regular.ttf");
fontMono = CreateFontWithSize(16.0f, "fonts/Inconsolata-Regular.ttf");
fontMonoLarger = CreateFontWithSize(20.0f, "fonts/Inconsolata-Regular.ttf");
fontMonoLargest = CreateFontWithSize(24.0f, "fonts/Inconsolata-Regular.ttf");
fontStandard = CreateFontWithSize(16.0f, "fonts/Montserrat-Regular.ttf");
fontStandardLarger = CreateFontWithSize(20.0f, "fonts/Montserrat-Regular.ttf");
fontStandardLargest = CreateFontWithSize(24.0f, "fonts/Montserrat-Regular.ttf");
fontJapanese = CreateFontWithSize(24.0f, "fonts/NotoSansJP-Regular.ttf", true);
ImGui::GetIO().FontDefault = fontStandardLarger;
}
previousImGuiScaleIndex = -1;
previousImGuiScale = defaultImGuiScale;
ScaleImGui();
}
typedef enum ExtractSteps {
ES_PORT_ARCHIVE,
ES_WINDOWS,
ES_EXTRACT_ARGS,
ES_EXTRACT,
ES_VERIFY,
} ExtractSteps;
typedef enum PromptSteps {
PS_FILE_CHECK,
PS_LOCAL,
PS_FIRST,
PS_SECOND,
PS_DUPE,
PS_WAIT,
PS_NONE,
} PromptSteps;
typedef enum WindowsSteps {
WS_TEMP,
WS_PERMS,
WS_ONEDRIVE,
WS_DONE,
} WindowsSteps;
bool IsSubpath(const std::filesystem::path& path, const std::filesystem::path& base) {
auto rel = std::filesystem::relative(path, base);
return !rel.empty() && rel.native()[0] != '.';
}
bool PathTestCleanup(FILE* tfile) {
try {
if (std::filesystem::exists("./text.txt"))
std::filesystem::remove("./text.txt");
if (std::filesystem::exists("./test/"))
std::filesystem::remove("./test/");
} catch (std::filesystem::filesystem_error const& ex) { return false; }
return true;
}
void CheckAndCreateModFolder() {
try {
std::string modsPath = Ship::Context::LocateFileAcrossAppDirs("mods", appShortName);
if (!std::filesystem::exists(modsPath)) {
// Create mods folder relative to app dir
modsPath = Ship::Context::GetPathRelativeToAppDirectory("mods", appShortName);
std::string filePath = modsPath + "/custom_mod_files_go_here.txt";
if (std::filesystem::create_directories(modsPath)) {
std::ofstream(filePath).close();
}
}
} catch (std::filesystem::filesystem_error const& ex) {
// Couldn't make the folder, continue silently
return;
}
}
namespace SohGui {
extern std::shared_ptr<SohGui::SohMenu> mSohMenu;
}
void OTRGlobals::RunExtract(int argc, char* argv[]) {
bool extractDone = false;
ExtractSteps extractStep = ES_PORT_ARCHIVE;
WindowsSteps windowsStep = WS_TEMP;
auto wnd = std::dynamic_pointer_cast<Fast::Fast3dWindow>(OTRGlobals::Instance->context->GetWindow());
auto gui = wnd->GetGui();
OTRVersion vanillaVersion = DetectOTRVersion("oot.o2r", false);
OTRVersion mqVersion = DetectOTRVersion("oot-mq.o2r", true);
bool shouldRegen = VerifyArchiveVersion(vanillaVersion) || VerifyArchiveVersion(mqVersion);
std::filesystem::path ownPath;
std::vector<std::string> args;
if (argc > 1) {
for (int i = 1; i < argc; i++) {
args.push_back(argv[argc]);
}
}
Extractor extract;
PromptSteps promptStep = PS_FILE_CHECK;
bool generatedIsMQ = false;
std::atomic<size_t> extractCount = 0, totalExtract = 0;
std::string installPath = Ship::Context::GetAppBundlePath();
std::string dataPath = Ship::Context::GetAppDirectoryPath(appShortName);
std::string file;
#if defined(__SWITCH__)
SohGui::RegisterPopup("Outdated ROM Archives",
"\x1b[2;2HYou've launched the Ship with an old ROM O2R file."
"\x1b[4;2HPlease regenerate a new ROM O2R and relaunch."
"\x1b[6;2HPress the Home button to exit...",
"OK", "", [&]() { exit(1); });
#elif defined(__WIIU__)
SohGui::RegisterPopup("Outdated ROM Archives",
"You've launched the Ship with an old a ROM O2R file.\n\n"
"Please generate a ROM O2R and relaunch.\n\n"
"Press and hold the Power button to shutdown...",
"OK", "", [&]() { exit(1); });
OSFatal();
#endif
if (!std::filesystem::exists(installPath + "/assets")) {
SohGui::RegisterPopup("Extractor assets not found",
"No O2R files found. Missing 'assets/' folder needed to generate OTR file.\nPlease "
"re-extract them from the download or.\n\nExiting...",
"OK", "", [&]() { exit(1); });
} else if (shouldRegen) {
SohGui::RegisterPopup("Outdated ROM Archives",
"Your oot.o2r or oot-mq.o2r were created with incompatible versions of SoH.\nYou will "
"now be redirected to re-extract them.");
std::filesystem::remove("oot.o2r");
std::filesystem::remove("oot-mq.o2r");
}
std::shared_ptr<BS::thread_pool> threadPool = std::make_shared<BS::thread_pool>(1);
std::optional<std::future<void>> extractionTask;
#if not defined(__SWITCH__) && not defined(__WIIU__)
CheckAndCreateModFolder();
#endif
while (!extractDone) {
if (SohGui::PopupsQueued() > 0 || extractionTask.has_value()) {
goto render;
}
switch (extractStep) {
case ES_PORT_ARCHIVE: {
if (sohArchiveVersionMatch) {
#ifdef _WIN32
extractStep = ES_WINDOWS;
#elif (defined(__WIIU__) || defined(__SWITCH__))
extractStep = ES_VERIFY;
#else
extractStep = ES_EXTRACT;
#endif
} else {
std::string msg;
#if defined(__SWITCH__)
msg = "\x1b[4;2HPlease re-extract it from the download.\n"
"\x1b[6;2HPress the Home button to exit...";
#elif defined(__WIIU__)
msg = "Please extract the soh.o2r from the Ship of Harkinian download\nto your folder.\n\nPress "
"and hold the power\n"
"button to shutdown...";
#else
msg =
"Please extract the soh.o2r from the Ship of Harkinian download to your folder.\n\nExiting...";
#endif
std::string title =
!std::filesystem::exists(portArchivePath) ? "Missing soh.o2r" : "soh.o2r is outdated";
SohGui::RegisterPopup(title, msg, "OK", "", [&]() { exit(1); });
}
continue;
}
case ES_WINDOWS: {
switch (windowsStep) {
case WS_TEMP: {
#ifdef _WIN32
char* tempVar = getenv("TEMP");
std::filesystem::path tempPath;
try {
tempPath = std::filesystem::canonical(tempVar);
} catch (std::filesystem::filesystem_error const& ex) {
std::string userPath = getenv("USERPROFILE");
userPath.append("\\AppData\\Local\\Temp");
tempPath = std::filesystem::canonical(userPath);
}
wchar_t buffer[MAX_PATH];
GetModuleFileName(NULL, buffer, _countof(buffer));
ownPath = std::filesystem::canonical(buffer).parent_path();
if (IsSubpath(ownPath, tempPath)) {
SohGui::RegisterPopup("SoH Path Error",
"SoH is running in a temp folder.\nExtract the .zip and run again.",
"OK", "", [&]() { exit(0); });
} else {
windowsStep = WS_PERMS;
}
#endif
continue;
}
case WS_PERMS: {
FILE* tfile = fopen("./text.txt", "w");
std::filesystem::path tfolder = std::filesystem::path("./test/");
bool error = false;
try {
create_directories(tfolder);
} catch (std::filesystem::filesystem_error const& ex) { error = true; }
if (tfile == NULL || error) {
SohGui::RegisterPopup("SoH Permissions Error",
"SoH does not have proper file permissions.\nPlease move it to a "
"folder that does and run again.",
"OK", "", [&]() {
fclose(tfile);
PathTestCleanup(tfile);
exit(0);
});
} else {
fclose(tfile);
if (!PathTestCleanup(tfile)) {
SohGui::RegisterPopup("SoH Permissions Error",
"SoH does not have proper file permissions.\nPlease move it to a "
"folder that does and run again.",
"OK", "", [&]() { exit(0); });
}
windowsStep = WS_ONEDRIVE;
}
continue;
}
case WS_ONEDRIVE: {
if (ownPath.string().find("OneDrive") != std::string::npos) {
SohGui::RegisterPopup("SoH Path Error",
"SoH appears to be in a OneDrive folder, which will cause issues.\n"
"Please move it to a folder outside of OneDrive, like the root of a\n"
"drive (e.g. \"C:\\Games\\SoH\").",
"OK", "", [&]() { exit(0); });
} else {
windowsStep = WS_DONE;
if (args.size() > 0) {
extractStep = ES_EXTRACT_ARGS;
} else {
extractStep = ES_EXTRACT;
}
}
continue;
}
default:
continue;
}
break;
}
case ES_EXTRACT_ARGS: {
#if !defined(__SWITCH__) && !defined(__WIIU__)
if (args.size() == 0) {
SohGui::RegisterPopup(
"Run Ship of Harkinian", "All files have been processed. Run SoH?", "Yes", "No",
[&]() {
if (!std::filesystem::exists(Ship::Context::GetAppDirectoryPath(appShortName) +
"/oot.o2r") &&
!std::filesystem::exists(Ship::Context::GetAppDirectoryPath(appShortName) +
"/oot-mq.o2r")) {
extractStep = ES_EXTRACT;
promptStep = PS_FILE_CHECK;
} else {
extractStep = ES_VERIFY;
}
},
[&]() { exit(0); });
break;
}
file = args.at(0);
args.erase(args.begin());
extract = Extractor();
if (extract.RunFileStandalone(file)) {
bool doExtract = true;
std::string archive = (extract.IsMasterQuest() ? "oot-mq.o2r" : "oot.o2r");
if (std::filesystem::exists(Ship::Context::GetAppDirectoryPath(appShortName) + "/" + archive)) {
std::string msg = "Archive for current ROM, " + archive + ", already exists.\nExtract again?";
SohGui::RegisterPopup("Confirm Re-extract", msg.c_str(), "Yes", "No", [&]() {
extractionTask = threadPool->submit_task([&]() -> void {
extract.CallZapd(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
extractCount = totalExtract = 0;
});
});
} else {
extractionTask = threadPool->submit_task([&]() -> void {
extract.CallZapd(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
extractCount = totalExtract = 0;
});
}
} else {
bool open = true;
std::string msg = "File\n" + std::string(file) + "\nis not a ROM or does not match supported ROMs.";
SohGui::RegisterPopup("SoH ROM Error", msg.c_str());
}
#else
extractStep = ES_VERIFY;
#endif
break;
}
case ES_EXTRACT: {
switch (promptStep) {
case PS_FILE_CHECK: {
const bool ootO2RExists =
std::filesystem::exists(
Ship::Context::LocateFileAcrossAppDirs("oot-mq.o2r", appShortName)) ||
std::filesystem::exists(Ship::Context::LocateFileAcrossAppDirs("oot.o2r", appShortName));
if (!ootO2RExists) {
SohGui::RegisterPopup(
"No O2R Files", "No O2R files found. Generate one now?", "Yes", "No",
[&]() { promptStep = PS_LOCAL; }, [&]() { exit(0); });
} else {
extractStep = ES_VERIFY;
}
continue;
}
case PS_LOCAL: {
extract = Extractor();
extract.SetSearchPath(installPath);
extract.GetRoms(args);
extract.SetSearchPath(dataPath);
extract.GetRoms(args);
if (!args.empty()) {
promptStep = PS_WAIT;
SohGui::RegisterPopup(
"ROMs found", "ROMs found in application directory. Would you like to process them?",
"Yes", "No", [&]() { extractStep = ES_EXTRACT_ARGS; },
[&]() { promptStep = PS_FIRST; });
} else {
promptStep = PS_FIRST;
}
continue;
}
case PS_FIRST: {
if (!extract.ManuallySearchForRomMatchingType(RomSearchMode::Both)) {
promptStep = PS_FILE_CHECK;
continue;
}
extractionTask = threadPool->submit_task([&]() -> void {
extract.CallZapd(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
generatedIsMQ = extract.IsMasterQuest();
promptStep = PS_SECOND;
extractCount = 0;
totalExtract = 0;
});
continue;
}
case PS_SECOND: {
SohGui::RegisterPopup(
"Extraction Complete", "ROM Extracted. Extract another?", "Yes", "No",
[&]() {
if (!extract.ManuallySearchForRomMatchingType(generatedIsMQ ? RomSearchMode::Vanilla
: RomSearchMode::MQ)) {
extractStep = ES_VERIFY;
} else {
extractionTask = threadPool->submit_task([&]() -> void {
extract.CallZapd(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
extractStep = ES_VERIFY;
extractCount = 0;
totalExtract = 0;
});
}
},
[&]() { extractStep = ES_VERIFY; });
continue;
}
default:
break;
}
break;
}
case ES_VERIFY: {
const bool ootO2RExists =
std::filesystem::exists(Ship::Context::LocateFileAcrossAppDirs("oot-mq.o2r", appShortName)) ||
std::filesystem::exists(Ship::Context::LocateFileAcrossAppDirs("oot.o2r", appShortName));
if (!ootO2RExists) {
SohGui::RegisterPopup("No ROM Archives",
"No ROM O2R files detected. Please generate a ROM O2R and relaunch.", "OK",
"", [&]() { exit(0); });
}
extractDone = true;
continue;
}
default:
break;
}
render:
if (!WindowIsRunning()) {
exit(0);
}
// Process window events for resize, mouse, keyboard events
wnd->HandleEvents();
UIWidgets::Colors themeColor =
static_cast<UIWidgets::Colors>(CVarGetInteger(CVAR_SETTING("Menu.Theme"), UIWidgets::Colors::LightBlue));
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIWidgets::ColorValues.at(themeColor));
ImGui::PushStyleColor(ImGuiCol_ModalWindowDimBg, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkGray));
// Skip dropped frames
if (!wnd->IsFrameReady()) {
continue;
}
gui->StartDraw();
sohFast3dWindow->StartFrame();
sohFast3dWindow->RunGuiOnly();
if (extractionTask.has_value()) {
auto status = extractionTask->wait_for(std::chrono::milliseconds(0));
if (status == std::future_status::ready) {
try {
extractionTask->get();
} catch (const std::exception& e) {
SohGui::RegisterPopup("Extraction Crashed", e.what(), "Close", "", []() { exit(1); });
}
extractionTask.reset();
} else {
if (!ImGui::IsPopupOpen("ROM Extraction")) {
ImGui::OpenPopup("ROM Extraction");
}
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f));
auto color = UIWidgets::ColorValues.at(THEME_COLOR);
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 0.6f));
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(color.x, color.y, color.z, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
if (ImGui::BeginPopupModal("ROM Extraction", NULL,
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoSavedSettings)) {
float progress = (totalExtract > 0.0f ? (float)extractCount / (float)totalExtract : 0) * 100.0f;
auto filename = std::filesystem::path(file).filename().string();
ImGui::Text("Extracting %s...%s", filename.c_str(),
roundf(progress) == 100.0f ? " Done. Finishing up." : "");
std::string overlay = extractCount > 0 ? fmt::format("{:.0f}%", progress) : "Starting Up";
ImGui::ProgressBar(progress / 100.0f, ImVec2(600.0f, 50.0f), overlay.c_str());
ImGui::EndPopup();
}
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(2);
}
}
gui->EndDraw();
sohFast3dWindow->EndFrame();
ImGui::PopStyleColor(2);
}
#ifdef __SWITCH__
Ship::Switch::Init(Ship::PreInitPhase);
#elif defined(__WIIU__)
Ship::WiiU::Init(appShortName);
#endif
}
void OTRGlobals::Initialize() {
std::string mqPath = Ship::Context::LocateFileAcrossAppDirs("oot-mq.o2r", appShortName);
if (std::filesystem::exists(mqPath)) {
context->GetResourceManager()->GetArchiveManager()->AddArchive(mqPath);
}
std::string ootPath = Ship::Context::LocateFileAcrossAppDirs("oot.o2r", appShortName);
if (std::filesystem::exists(ootPath)) {
context->GetResourceManager()->GetArchiveManager()->AddArchive(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_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,
};
#if (_DEBUG)
auto defaultLogLevel = spdlog::level::trace;
#else
auto defaultLogLevel = spdlog::level::info;
#endif
context->InitConfiguration();
context->InitConsoleVariables();
auto logLevel =
static_cast<spdlog::level::level_enum>(CVarGetInteger(CVAR_DEVELOPER_TOOLS("LogLevel"), defaultLogLevel));
context->InitLogging(logLevel, logLevel);
Ship::Context::GetInstance()->GetLogger()->set_pattern("[%H:%M:%S.%e] [%s:%#] [%l] %v");
context->InitGfxDebugger();
context->InitFileDropMgr();
// tell LUS to reserve 3 SoH specific threads (Game, Audio, Save)
prevAltAssets = CVarGetInteger(CVAR_SETTING("AltAssets"), 1);
context->GetResourceManager()->SetAltAssetsEnabled(prevAltAssets);
context->InitCrashHandler();
context->GetWindow()->SetAutoCaptureMouse(CVarGetInteger(CVAR_SETTING("EnableMouse"), 0) &&
CVarGetInteger(CVAR_SETTING("AutoCaptureMouse"), 1));
context->GetWindow()->SetForceCursorVisibility(CVarGetInteger(CVAR_SETTING("CursorVisibility"), 0));
context->InitAudio({ .SampleRate = 32000, .SampleLength = 1024, .DesiredBuffered = 1680 });
SPDLOG_INFO("Starting Ship of Harkinian version {} (Branch: {} | Commit: {})", (char*)gBuildVersion,
(char*)gGitBranch, (char*)gGitCommitHash);
auto loader = context->GetResourceManager()->GetResourceLoader();
loader->RegisterResourceFactory(std::make_shared<Fast::ResourceFactoryBinaryTextureV0>(), RESOURCE_FORMAT_BINARY,
"Texture", static_cast<uint32_t>(Fast::ResourceType::Texture), 0);
loader->RegisterResourceFactory(std::make_shared<Fast::ResourceFactoryBinaryTextureV1>(), RESOURCE_FORMAT_BINARY,
"Texture", static_cast<uint32_t>(Fast::ResourceType::Texture), 1);
loader->RegisterResourceFactory(std::make_shared<Fast::ResourceFactoryBinaryVertexV0>(), RESOURCE_FORMAT_BINARY,
"Vertex", static_cast<uint32_t>(Fast::ResourceType::Vertex), 0);
loader->RegisterResourceFactory(std::make_shared<Fast::ResourceFactoryXMLVertexV0>(), RESOURCE_FORMAT_XML, "Vertex",
static_cast<uint32_t>(Fast::ResourceType::Vertex), 0);
loader->RegisterResourceFactory(std::make_shared<Fast::ResourceFactoryBinaryDisplayListV0>(),
RESOURCE_FORMAT_BINARY, "DisplayList",
static_cast<uint32_t>(Fast::ResourceType::DisplayList), 0);
loader->RegisterResourceFactory(std::make_shared<Fast::ResourceFactoryXMLDisplayListV0>(), RESOURCE_FORMAT_XML,
"DisplayList", static_cast<uint32_t>(Fast::ResourceType::DisplayList), 0);
loader->RegisterResourceFactory(std::make_shared<Fast::ResourceFactoryBinaryMatrixV0>(), RESOURCE_FORMAT_BINARY,
"Matrix", static_cast<uint32_t>(Fast::ResourceType::Matrix), 0);
loader->RegisterResourceFactory(std::make_shared<Ship::ResourceFactoryBinaryBlobV0>(), RESOURCE_FORMAT_BINARY,
"Blob", static_cast<uint32_t>(Ship::ResourceType::Blob), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryArrayV0>(), RESOURCE_FORMAT_BINARY,
"Array", static_cast<uint32_t>(SOH::ResourceType::SOH_Array), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryAnimationV0>(), RESOURCE_FORMAT_BINARY,
"Animation", static_cast<uint32_t>(SOH::ResourceType::SOH_Animation), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryPlayerAnimationV0>(),
RESOURCE_FORMAT_BINARY, "PlayerAnimation",
static_cast<uint32_t>(SOH::ResourceType::SOH_PlayerAnimation), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinarySceneV0>(), RESOURCE_FORMAT_BINARY,
"Room", static_cast<uint32_t>(SOH::ResourceType::SOH_Room),
0); // Is room scene? maybe?
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLSceneV0>(), RESOURCE_FORMAT_XML, "Room",
static_cast<uint32_t>(SOH::ResourceType::SOH_Room), 0); // Is room scene? maybe?
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryCollisionHeaderV0>(),
RESOURCE_FORMAT_BINARY, "CollisionHeader",
static_cast<uint32_t>(SOH::ResourceType::SOH_CollisionHeader), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLCollisionHeaderV0>(), RESOURCE_FORMAT_XML,
"CollisionHeader", static_cast<uint32_t>(SOH::ResourceType::SOH_CollisionHeader),
0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinarySkeletonV0>(), RESOURCE_FORMAT_BINARY,
"Skeleton", static_cast<uint32_t>(SOH::ResourceType::SOH_Skeleton), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLSkeletonV0>(), RESOURCE_FORMAT_XML,
"Skeleton", static_cast<uint32_t>(SOH::ResourceType::SOH_Skeleton), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinarySkeletonLimbV0>(),
RESOURCE_FORMAT_BINARY, "SkeletonLimb",
static_cast<uint32_t>(SOH::ResourceType::SOH_SkeletonLimb), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLSkeletonLimbV0>(), RESOURCE_FORMAT_XML,
"SkeletonLimb", static_cast<uint32_t>(SOH::ResourceType::SOH_SkeletonLimb), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryPathV0>(), RESOURCE_FORMAT_BINARY,
"Path", static_cast<uint32_t>(SOH::ResourceType::SOH_Path), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLPathV0>(), RESOURCE_FORMAT_XML, "Path",
static_cast<uint32_t>(SOH::ResourceType::SOH_Path), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryCutsceneV0>(), RESOURCE_FORMAT_BINARY,
"Cutscene", static_cast<uint32_t>(SOH::ResourceType::SOH_Cutscene), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryTextV0>(), RESOURCE_FORMAT_BINARY,
"Text", static_cast<uint32_t>(SOH::ResourceType::SOH_Text), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLTextV0>(), RESOURCE_FORMAT_XML, "Text",
static_cast<uint32_t>(SOH::ResourceType::SOH_Text), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryAudioSampleV2>(), RESOURCE_FORMAT_BINARY,
"AudioSample", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSample), 2);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLAudioSampleV0>(), RESOURCE_FORMAT_XML,
"Sample", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSample), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryAudioSoundFontV2>(),
RESOURCE_FORMAT_BINARY, "AudioSoundFont",
static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSoundFont), 2);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLSoundFontV0>(), RESOURCE_FORMAT_XML,
"SoundFont", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSoundFont), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryAudioSequenceV2>(),
RESOURCE_FORMAT_BINARY, "AudioSequence",
static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSequence), 2);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLAudioSequenceV0>(), RESOURCE_FORMAT_XML,
"Sequence", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSequence), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryBackgroundV0>(), RESOURCE_FORMAT_BINARY,
"Background", static_cast<uint32_t>(SOH::ResourceType::SOH_Background), 0);
gSaveStateMgr = std::make_shared<SaveStateMgr>();
gRandoContext->InitStaticData();
gRandoContext = Rando::Context::CreateInstance();
Rando::Settings::GetInstance()->AssignContext(gRandoContext);
Rando::StaticData::InitItemTable(); // RANDOTODO make this not rely on context's logic so it can be initialised in
// InitStaticData
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 (size_t i = 0; i < sizeof(constCameraStrings) / sizeof(char*); i++) {
// OTRTODO: never deallocated...
cameraStrings[i] = strdup(constCameraStrings[i]);
}
auto versions = context->GetResourceManager()->GetArchiveManager()->GetGameVersions();
for (uint32_t version : versions) {
if (!ValidHashes.contains(version)) {
#if defined(__SWITCH__)
SPDLOG_ERROR("Invalid OTR 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!");
#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;
}
}
}
OTRGlobals::~OTRGlobals() {
}
void OTRGlobals::ScaleImGui() {
int32_t imGuiScaleIndex = CVarGetInteger(CVAR_SETTING("ImGuiScale"), defaultImGuiScale);
if (imGuiScaleIndex == previousImGuiScaleIndex) {
return;
}
float scale = imguiScaleOptionToValue[imGuiScaleIndex];
float newScale = scale / previousImGuiScale;
ImGui::GetStyle().ScaleAllSizes(newScale);
ImGui::GetIO().FontGlobalScale = scale;
previousImGuiScale = scale;
previousImGuiScaleIndex = imGuiScaleIndex;
}
ImFont* OTRGlobals::CreateDefaultFontWithSize(float size) {
auto mImGuiIo = &ImGui::GetIO();
ImFontConfig fontCfg = ImFontConfig();
fontCfg.OversampleH = fontCfg.OversampleV = 1;
fontCfg.PixelSnapH = true;
fontCfg.SizePixels = size;
ImFont* font = mImGuiIo->Fonts->AddFontDefault(&fontCfg);
// FontAwesome fonts need to have their sizes reduced by 2.0f/3.0f in order to align correctly
float iconFontSize = size * 2.0f / 3.0f;
static const ImWchar sIconsRanges[] = { ICON_MIN_FA, ICON_MAX_16_FA, 0 };
ImFontConfig iconsConfig;
iconsConfig.MergeMode = true;
iconsConfig.PixelSnapH = true;
iconsConfig.GlyphMinAdvanceX = iconFontSize;
mImGuiIo->Fonts->AddFontFromMemoryCompressedBase85TTF(fontawesome_compressed_data_base85, iconFontSize,
&iconsConfig, sIconsRanges);
return font;
}
bool OTRGlobals::HasMasterQuest() {
return hasMasterQuest;
}
bool OTRGlobals::HasOriginal() {
return hasOriginal;
}
uint32_t OTRGlobals::GetInterpolationFPS() {
if (CVarGetInteger(CVAR_SETTING("MatchRefreshRate"), 0)) {
return Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate();
} else if (CVarGetInteger(CVAR_VSYNC_ENABLED, 1) ||
!Ship::Context::GetInstance()->GetWindow()->CanDisableVerticalSync()) {
return std::min<uint32_t>(Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(),
CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20));
}
return CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20);
}
extern "C" void OTRMessage_Init();
extern "C" void AudioMgr_CreateNextAudioBuffer(s16* samples, u32 num_samples);
extern "C" void AudioPlayer_Play(const uint8_t* buf, uint32_t len);
extern "C" int AudioPlayer_Buffered(void);
extern "C" int AudioPlayer_GetDesiredBuffered(void);
std::unordered_map<std::string, ExtensionEntry> ExtensionCache;
void OTRAudio_Thread() {
while (audio.running) {
{
std::unique_lock<std::mutex> Lock(audio.mutex);
while (!audio.processing && audio.running) {
audio.cv_to_thread.wait(Lock);
}
if (!audio.running) {
break;
}
}
std::unique_lock<std::mutex> Lock(audio.mutex);
// AudioMgr_ThreadEntry(&gAudioMgr);
// 528 and 544 relate to 60 fps at 32 kHz 32000/60 = 533.333..
// in an ideal world, one third of the calls should use num_samples=544 and two thirds num_samples=528
#define SAMPLES_HIGH 560
#define SAMPLES_LOW 528
#define AUDIO_FRAMES_PER_UPDATE (R_UPDATE_RATE > 0 ? R_UPDATE_RATE : 1)
#define NUM_AUDIO_CHANNELS 2
int samples_left = AudioPlayer_Buffered();
u32 num_audio_samples = samples_left < AudioPlayer_GetDesiredBuffered() ? SAMPLES_HIGH : SAMPLES_LOW;
// 3 is the maximum authentic frame divisor.
s16 audio_buffer[SAMPLES_HIGH * NUM_AUDIO_CHANNELS * 3];
for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) {
AudioMgr_CreateNextAudioBuffer(audio_buffer + i * (num_audio_samples * NUM_AUDIO_CHANNELS),
num_audio_samples);
}
AudioPlayer_Play((u8*)audio_buffer,
num_audio_samples * (sizeof(int16_t) * NUM_AUDIO_CHANNELS * AUDIO_FRAMES_PER_UPDATE));
audio.processing = false;
audio.cv_from_thread.notify_one();
}
}
// C->C++ Bridge
extern "C" void OTRAudio_Init() {
// Precache all our samples, sequences, etc...
ResourceMgr_LoadDirectory("audio");
if (!audio.running) {
audio.running = true;
audio.thread = std::thread(OTRAudio_Thread);
}
}
extern "C" char** sequenceMap;
extern "C" size_t sequenceMapSize;
extern "C" char** fontMap;
extern "C" size_t fontMapSize;
extern "C" void OTRAudio_Exit() {
// Tell the audio thread to stop
{
std::unique_lock<std::mutex> Lock(audio.mutex);
audio.running = false;
}
audio.cv_to_thread.notify_all();
// Wait until the audio thread quit
audio.thread.join();
#if 0
for (size_t i = 0; i < sequenceMapSize; i++) {
free(sequenceMap[i]);
}
free(sequenceMap);
for (size_t i = 0; i < fontMapSize; i++) {
free(fontMap[i]);
}
free(fontMap);
free(gAudioContext.seqLoadStatus);
free(gAudioContext.fontLoadStatus);
#endif
}
extern "C" void VanillaItemTable_Init() {
static GetItemEntry getItemTable[] = {
// clang-format off
GET_ITEM(ITEM_BOMBS_5, OBJECT_GI_BOMB_1, GID_BOMB, 0x32, 0x59, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_BOMBS_5),
GET_ITEM(ITEM_NUTS_5, OBJECT_GI_NUTS, GID_NUTS, 0x34, 0x0C, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_NUTS_5),
GET_ITEM(ITEM_BOMBCHU, OBJECT_GI_BOMB_2, GID_BOMBCHU, 0x33, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BOMBCHUS_10),
GET_ITEM(ITEM_BOW, OBJECT_GI_BOW, GID_BOW, 0x31, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BOW),
GET_ITEM(ITEM_SLINGSHOT, OBJECT_GI_PACHINKO, GID_SLINGSHOT, 0x30, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_SLINGSHOT),
GET_ITEM(ITEM_BOOMERANG, OBJECT_GI_BOOMERANG, GID_BOOMERANG, 0x35, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BOOMERANG),
GET_ITEM(ITEM_STICK, OBJECT_GI_STICK, GID_STICK, 0x37, 0x0D, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_STICKS_1),
GET_ITEM(ITEM_HOOKSHOT, OBJECT_GI_HOOKSHOT, GID_HOOKSHOT, 0x36, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_HOOKSHOT),
GET_ITEM(ITEM_LONGSHOT, OBJECT_GI_HOOKSHOT, GID_LONGSHOT, 0x4F, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_LONGSHOT),
GET_ITEM(ITEM_LENS, OBJECT_GI_GLASSES, GID_LENS, 0x39, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_LENS),
GET_ITEM(ITEM_LETTER_ZELDA, OBJECT_GI_LETTER, GID_LETTER_ZELDA, 0x69, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_LETTER_ZELDA),
GET_ITEM(ITEM_OCARINA_TIME, OBJECT_GI_OCARINA, GID_OCARINA_TIME, 0x3A, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_OCARINA_OOT),
GET_ITEM(ITEM_HAMMER, OBJECT_GI_HAMMER, GID_HAMMER, 0x38, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_HAMMER),
GET_ITEM(ITEM_COJIRO, OBJECT_GI_NIWATORI, GID_COJIRO, 0x02, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_COJIRO),
GET_ITEM(ITEM_BOTTLE, OBJECT_GI_BOTTLE, GID_BOTTLE, 0x42, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BOTTLE),
GET_ITEM(ITEM_POTION_RED, OBJECT_GI_LIQUID, GID_POTION_RED, 0x43, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_POTION_RED),
GET_ITEM(ITEM_POTION_GREEN, OBJECT_GI_LIQUID, GID_POTION_GREEN, 0x44, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_POTION_GREEN),
GET_ITEM(ITEM_POTION_BLUE, OBJECT_GI_LIQUID, GID_POTION_BLUE, 0x45, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_POTION_BLUE),
GET_ITEM(ITEM_FAIRY, OBJECT_GI_BOTTLE, GID_BOTTLE, 0x46, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_FAIRY),
GET_ITEM(ITEM_MILK_BOTTLE, OBJECT_GI_MILK, GID_MILK, 0x98, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_MILK_BOTTLE),
GET_ITEM(ITEM_LETTER_RUTO, OBJECT_GI_BOTTLE_LETTER, GID_LETTER_RUTO, 0x99, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_LETTER_RUTO),
GET_ITEM(ITEM_BEAN, OBJECT_GI_BEAN, GID_BEAN, 0x48, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BEAN),
GET_ITEM(ITEM_MASK_SKULL, OBJECT_GI_SKJ_MASK, GID_MASK_SKULL, 0x10, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_MASK_SKULL),
GET_ITEM(ITEM_MASK_SPOOKY, OBJECT_GI_REDEAD_MASK, GID_MASK_SPOOKY, 0x11, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_MASK_SPOOKY),
GET_ITEM(ITEM_CHICKEN, OBJECT_GI_NIWATORI, GID_CHICKEN, 0x48, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_CHICKEN),
GET_ITEM(ITEM_MASK_KEATON, OBJECT_GI_KI_TAN_MASK, GID_MASK_KEATON, 0x12, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_MASK_KEATON),
GET_ITEM(ITEM_MASK_BUNNY, OBJECT_GI_RABIT_MASK, GID_MASK_BUNNY, 0x13, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_MASK_BUNNY),
GET_ITEM(ITEM_MASK_TRUTH, OBJECT_GI_TRUTH_MASK, GID_MASK_TRUTH, 0x17, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_MASK_TRUTH),
GET_ITEM(ITEM_POCKET_EGG, OBJECT_GI_EGG, GID_EGG, 0x01, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_POCKET_EGG),
GET_ITEM(ITEM_POCKET_CUCCO, OBJECT_GI_NIWATORI, GID_CHICKEN, 0x48, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_POCKET_CUCCO),
GET_ITEM(ITEM_ODD_MUSHROOM, OBJECT_GI_MUSHROOM, GID_ODD_MUSHROOM, 0x03, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_ODD_MUSHROOM),
GET_ITEM(ITEM_ODD_POTION, OBJECT_GI_POWDER, GID_ODD_POTION, 0x04, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_ODD_POTION),
GET_ITEM(ITEM_SAW, OBJECT_GI_SAW, GID_SAW, 0x05, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_SAW),
GET_ITEM(ITEM_SWORD_BROKEN, OBJECT_GI_BROKENSWORD, GID_SWORD_BROKEN, 0x08, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_SWORD_BROKEN),
GET_ITEM(ITEM_PRESCRIPTION, OBJECT_GI_PRESCRIPTION, GID_PRESCRIPTION, 0x09, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_PRESCRIPTION),
GET_ITEM(ITEM_FROG, OBJECT_GI_FROG, GID_FROG, 0x0D, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_FROG),
GET_ITEM(ITEM_EYEDROPS, OBJECT_GI_EYE_LOTION, GID_EYEDROPS, 0x0E, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_EYEDROPS),
GET_ITEM(ITEM_CLAIM_CHECK, OBJECT_GI_TICKETSTONE, GID_CLAIM_CHECK, 0x0A, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_CLAIM_CHECK),
GET_ITEM(ITEM_SWORD_KOKIRI, OBJECT_GI_SWORD_1, GID_SWORD_KOKIRI, 0xA4, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_SWORD_KOKIRI),
GET_ITEM(ITEM_SWORD_BGS, OBJECT_GI_LONGSWORD, GID_SWORD_BGS, 0x4B, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_SWORD_KNIFE),
GET_ITEM(ITEM_SHIELD_DEKU, OBJECT_GI_SHIELD_1, GID_SHIELD_DEKU, 0x4C, 0xA0, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_SHIELD_DEKU),
GET_ITEM(ITEM_SHIELD_HYLIAN, OBJECT_GI_SHIELD_2, GID_SHIELD_HYLIAN, 0x4D, 0xA0, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_SHIELD_HYLIAN),
GET_ITEM(ITEM_SHIELD_MIRROR, OBJECT_GI_SHIELD_3, GID_SHIELD_MIRROR, 0x4E, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_SHIELD_MIRROR),
GET_ITEM(ITEM_TUNIC_GORON, OBJECT_GI_CLOTHES, GID_TUNIC_GORON, 0x50, 0xA0, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_TUNIC_GORON),
GET_ITEM(ITEM_TUNIC_ZORA, OBJECT_GI_CLOTHES, GID_TUNIC_ZORA, 0x51, 0xA0, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_TUNIC_ZORA),
GET_ITEM(ITEM_BOOTS_IRON, OBJECT_GI_BOOTS_2, GID_BOOTS_IRON, 0x53, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BOOTS_IRON),
GET_ITEM(ITEM_BOOTS_HOVER, OBJECT_GI_HOVERBOOTS, GID_BOOTS_HOVER, 0x54, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BOOTS_HOVER),
GET_ITEM(ITEM_QUIVER_40, OBJECT_GI_ARROWCASE, GID_QUIVER_40, 0x56, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_QUIVER_40),
GET_ITEM(ITEM_QUIVER_50, OBJECT_GI_ARROWCASE, GID_QUIVER_50, 0x57, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_QUIVER_50),
GET_ITEM(ITEM_BOMB_BAG_20, OBJECT_GI_BOMBPOUCH, GID_BOMB_BAG_20, 0x58, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BOMB_BAG_20),
GET_ITEM(ITEM_BOMB_BAG_30, OBJECT_GI_BOMBPOUCH, GID_BOMB_BAG_30, 0x59, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_BOMB_BAG_30),
GET_ITEM(ITEM_BOMB_BAG_40, OBJECT_GI_BOMBPOUCH, GID_BOMB_BAG_40, 0x5A, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_BOMB_BAG_40),
GET_ITEM(ITEM_GAUNTLETS_SILVER, OBJECT_GI_GLOVES, GID_GAUNTLETS_SILVER, 0x5B, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_GAUNTLETS_SILVER),
GET_ITEM(ITEM_GAUNTLETS_GOLD, OBJECT_GI_GLOVES, GID_GAUNTLETS_GOLD, 0x5C, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_GAUNTLETS_GOLD),
GET_ITEM(ITEM_SCALE_SILVER, OBJECT_GI_SCALE, GID_SCALE_SILVER, 0xCD, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_SCALE_SILVER),
GET_ITEM(ITEM_SCALE_GOLDEN, OBJECT_GI_SCALE, GID_SCALE_GOLDEN, 0xCE, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_SCALE_GOLDEN),
GET_ITEM(ITEM_STONE_OF_AGONY, OBJECT_GI_MAP, GID_STONE_OF_AGONY, 0x68, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_STONE_OF_AGONY),
GET_ITEM(ITEM_GERUDO_CARD, OBJECT_GI_GERUDO, GID_GERUDO_CARD, 0x7B, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_GERUDO_CARD),
GET_ITEM(ITEM_OCARINA_FAIRY, OBJECT_GI_OCARINA_0, GID_OCARINA_FAIRY, 0x4A, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_OCARINA_FAIRY),
GET_ITEM(ITEM_SEEDS, OBJECT_GI_SEED, GID_SEEDS, 0xDC, 0x50, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_SEEDS_5),
GET_ITEM(ITEM_HEART_CONTAINER, OBJECT_GI_HEARTS, GID_HEART_CONTAINER, 0xC6, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_HEALTH, MOD_NONE, GI_HEART_CONTAINER),
GET_ITEM(ITEM_HEART_PIECE_2, OBJECT_GI_HEARTS, GID_HEART_PIECE, 0xC2, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_HEALTH, MOD_NONE, GI_HEART_PIECE),
GET_ITEM(ITEM_KEY_BOSS, OBJECT_GI_BOSSKEY, GID_KEY_BOSS, 0xC7, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_BOSS_KEY, MOD_NONE, GI_KEY_BOSS),
GET_ITEM(ITEM_COMPASS, OBJECT_GI_COMPASS, GID_COMPASS, 0x67, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_COMPASS),
GET_ITEM(ITEM_DUNGEON_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, 0x66, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_MAP),
GET_ITEM(ITEM_KEY_SMALL, OBJECT_GI_KEY, GID_KEY_SMALL, 0x60, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_SMALL_KEY, MOD_NONE, GI_KEY_SMALL),
GET_ITEM(ITEM_MAGIC_SMALL, OBJECT_GI_MAGICPOT, GID_MAGIC_SMALL, 0x52, 0x6F, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_MAGIC_SMALL),
GET_ITEM(ITEM_MAGIC_LARGE, OBJECT_GI_MAGICPOT, GID_MAGIC_LARGE, 0x52, 0x6E, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_MAGIC_LARGE),
GET_ITEM(ITEM_WALLET_ADULT, OBJECT_GI_PURSE, GID_WALLET_ADULT, 0x5E, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_WALLET_ADULT),
GET_ITEM(ITEM_WALLET_GIANT, OBJECT_GI_PURSE, GID_WALLET_GIANT, 0x5F, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_WALLET_GIANT),
GET_ITEM(ITEM_WEIRD_EGG, OBJECT_GI_EGG, GID_EGG, 0x9A, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_WEIRD_EGG),
GET_ITEM(ITEM_HEART, OBJECT_GI_HEART, GID_HEART, 0x55, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_HEART),
GET_ITEM(ITEM_ARROWS_SMALL, OBJECT_GI_ARROW, GID_ARROWS_SMALL, 0xE6, 0x48, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_ARROWS_SMALL),
GET_ITEM(ITEM_ARROWS_MEDIUM, OBJECT_GI_ARROW, GID_ARROWS_MEDIUM, 0xE6, 0x49, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_ARROWS_MEDIUM),
GET_ITEM(ITEM_ARROWS_LARGE, OBJECT_GI_ARROW, GID_ARROWS_LARGE, 0xE6, 0x4A, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_ARROWS_LARGE),
GET_ITEM(ITEM_RUPEE_GREEN, OBJECT_GI_RUPY, GID_RUPEE_GREEN, 0x6F, 0x00, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_RUPEE_GREEN),
GET_ITEM(ITEM_RUPEE_BLUE, OBJECT_GI_RUPY, GID_RUPEE_BLUE, 0xCC, 0x01, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_RUPEE_BLUE),
GET_ITEM(ITEM_RUPEE_RED, OBJECT_GI_RUPY, GID_RUPEE_RED, 0xF0, 0x02, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_RUPEE_RED),
GET_ITEM(ITEM_HEART_CONTAINER, OBJECT_GI_HEARTS, GID_HEART_CONTAINER, 0xC6, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_HEALTH, MOD_NONE, GI_HEART_CONTAINER_2),
GET_ITEM(ITEM_MILK, OBJECT_GI_MILK, GID_MILK, 0x98, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_MILK),
GET_ITEM(ITEM_MASK_GORON, OBJECT_GI_GOLONMASK, GID_MASK_GORON, 0x14, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_MASK_GORON),
GET_ITEM(ITEM_MASK_ZORA, OBJECT_GI_ZORAMASK, GID_MASK_ZORA, 0x15, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_MASK_ZORA),
GET_ITEM(ITEM_MASK_GERUDO, OBJECT_GI_GERUDOMASK, GID_MASK_GERUDO, 0x16, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_MASK_GERUDO),
GET_ITEM(ITEM_BRACELET, OBJECT_GI_BRACELET, GID_BRACELET, 0x79, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BRACELET),
GET_ITEM(ITEM_RUPEE_PURPLE, OBJECT_GI_RUPY, GID_RUPEE_PURPLE, 0xF1, 0x14, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_RUPEE_PURPLE),
GET_ITEM(ITEM_RUPEE_GOLD, OBJECT_GI_RUPY, GID_RUPEE_GOLD, 0xF2, 0x13, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_RUPEE_GOLD),
GET_ITEM(ITEM_SWORD_BGS, OBJECT_GI_LONGSWORD, GID_SWORD_BGS, 0x0C, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_SWORD_BGS),
GET_ITEM(ITEM_ARROW_FIRE, OBJECT_GI_M_ARROW, GID_ARROW_FIRE, 0x70, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_ARROW_FIRE),
GET_ITEM(ITEM_ARROW_ICE, OBJECT_GI_M_ARROW, GID_ARROW_ICE, 0x71, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_ARROW_ICE),
GET_ITEM(ITEM_ARROW_LIGHT, OBJECT_GI_M_ARROW, GID_ARROW_LIGHT, 0x72, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_ARROW_LIGHT),
GET_ITEM(ITEM_SKULL_TOKEN, OBJECT_GI_SUTARU, GID_SKULL_TOKEN, 0xB4, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_SKULLTULA_TOKEN, MOD_NONE, GI_SKULL_TOKEN),
GET_ITEM(ITEM_DINS_FIRE, OBJECT_GI_GODDESS, GID_DINS_FIRE, 0xAD, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_DINS_FIRE),
GET_ITEM(ITEM_FARORES_WIND, OBJECT_GI_GODDESS, GID_FARORES_WIND, 0xAE, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_FARORES_WIND),
GET_ITEM(ITEM_NAYRUS_LOVE, OBJECT_GI_GODDESS, GID_NAYRUS_LOVE, 0xAF, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_NAYRUS_LOVE),
GET_ITEM(ITEM_BULLET_BAG_30, OBJECT_GI_DEKUPOUCH, GID_BULLET_BAG, 0x07, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_BULLET_BAG_30),
GET_ITEM(ITEM_BULLET_BAG_40, OBJECT_GI_DEKUPOUCH, GID_BULLET_BAG, 0x07, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_BULLET_BAG_40),
GET_ITEM(ITEM_STICKS_5, OBJECT_GI_STICK, GID_STICK, 0x37, 0x0D, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_STICKS_5),
GET_ITEM(ITEM_STICKS_10, OBJECT_GI_STICK, GID_STICK, 0x37, 0x0D, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_STICKS_10),
GET_ITEM(ITEM_NUTS_5, OBJECT_GI_NUTS, GID_NUTS, 0x34, 0x0C, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_NUTS_5_2),
GET_ITEM(ITEM_NUTS_10, OBJECT_GI_NUTS, GID_NUTS, 0x34, 0x0C, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_NUTS_10),
GET_ITEM(ITEM_BOMB, OBJECT_GI_BOMB_1, GID_BOMB, 0x32, 0x59, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_BOMBS_1),
GET_ITEM(ITEM_BOMBS_10, OBJECT_GI_BOMB_1, GID_BOMB, 0x32, 0x59, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_BOMBS_10),
GET_ITEM(ITEM_BOMBS_20, OBJECT_GI_BOMB_1, GID_BOMB, 0x32, 0x59, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_BOMBS_20),
GET_ITEM(ITEM_BOMBS_30, OBJECT_GI_BOMB_1, GID_BOMB, 0x32, 0x59, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_BOMBS_30),
GET_ITEM(ITEM_SEEDS_30, OBJECT_GI_SEED, GID_SEEDS, 0xDC, 0x50, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_SEEDS_30),
GET_ITEM(ITEM_BOMBCHUS_5, OBJECT_GI_BOMB_2, GID_BOMBCHU, 0x33, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BOMBCHUS_5),
GET_ITEM(ITEM_BOMBCHUS_20, OBJECT_GI_BOMB_2, GID_BOMBCHU, 0x33, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_MAJOR, MOD_NONE, GI_BOMBCHUS_20),
GET_ITEM(ITEM_FISH, OBJECT_GI_FISH, GID_FISH, 0x47, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_FISH),
GET_ITEM(ITEM_BUG, OBJECT_GI_INSECT, GID_BUG, 0x7A, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_BUGS),
GET_ITEM(ITEM_BLUE_FIRE, OBJECT_GI_FIRE, GID_BLUE_FIRE, 0x5D, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_BLUE_FIRE),
GET_ITEM(ITEM_POE, OBJECT_GI_GHOST, GID_POE, 0x97, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_POE),
GET_ITEM(ITEM_BIG_POE, OBJECT_GI_GHOST, GID_BIG_POE, 0xF9, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_BIG_POE),
GET_ITEM(ITEM_KEY_SMALL, OBJECT_GI_KEY, GID_KEY_SMALL, 0xF3, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_SMALL_KEY, MOD_NONE, GI_DOOR_KEY),
GET_ITEM(ITEM_RUPEE_GREEN, OBJECT_GI_RUPY, GID_RUPEE_GREEN, 0xF4, 0x00, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_RUPEE_GREEN_LOSE),
GET_ITEM(ITEM_RUPEE_BLUE, OBJECT_GI_RUPY, GID_RUPEE_BLUE, 0xF5, 0x01, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_RUPEE_BLUE_LOSE),
GET_ITEM(ITEM_RUPEE_RED, OBJECT_GI_RUPY, GID_RUPEE_RED, 0xF6, 0x02, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_RUPEE_RED_LOSE),
GET_ITEM(ITEM_RUPEE_PURPLE, OBJECT_GI_RUPY, GID_RUPEE_PURPLE, 0xF7, 0x14, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_RUPEE_PURPLE_LOSE),
GET_ITEM(ITEM_HEART_PIECE_2, OBJECT_GI_HEARTS, GID_HEART_PIECE, 0xFA, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_HEALTH, MOD_NONE, GI_HEART_PIECE_WIN),
GET_ITEM(ITEM_STICK_UPGRADE_20, OBJECT_GI_STICK, GID_STICK, 0x90, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_STICK_UPGRADE_20),
GET_ITEM(ITEM_STICK_UPGRADE_30, OBJECT_GI_STICK, GID_STICK, 0x91, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_STICK_UPGRADE_30),
GET_ITEM(ITEM_NUT_UPGRADE_30, OBJECT_GI_NUTS, GID_NUTS, 0xA7, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_NUT_UPGRADE_30),
GET_ITEM(ITEM_NUT_UPGRADE_40, OBJECT_GI_NUTS, GID_NUTS, 0xA8, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_NUT_UPGRADE_40),
GET_ITEM(ITEM_BULLET_BAG_50, OBJECT_GI_DEKUPOUCH, GID_BULLET_BAG_50, 0x6C, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_BULLET_BAG_50),
GET_ITEM_NONE,
GET_ITEM_NONE,
GET_ITEM_NONE // GI_MAX - if you need to add to this table insert it before this entry.
// clang-format on
};
ItemTableManager::Instance->AddItemTable(MOD_NONE);
for (uint8_t i = 0; i < ARRAY_COUNT(getItemTable); i++) {
// The vanilla item table array started with ITEM_BOMBS_5,
// but the GetItemID enum started with GI_NONE. Then everywhere
// that table was accessed used `GetItemID - 1`. This allows the
// "first" item of the new map to start at 1, syncing it up with
// the GetItemID values and removing the need for the `- 1`
ItemTableManager::Instance->AddItemEntry(MOD_NONE, i + 1, getItemTable[i]);
}
}
std::unordered_map<ItemID, GetItemID> ItemIDtoGetItemIDMap{
{ ITEM_ARROWS_LARGE, GI_ARROWS_LARGE },
{ ITEM_ARROWS_MEDIUM, GI_ARROWS_MEDIUM },
{ ITEM_ARROWS_SMALL, GI_ARROWS_SMALL },
{ ITEM_ARROW_FIRE, GI_ARROW_FIRE },
{ ITEM_ARROW_ICE, GI_ARROW_ICE },
{ ITEM_ARROW_LIGHT, GI_ARROW_LIGHT },
{ ITEM_BEAN, GI_BEAN },
{ ITEM_BIG_POE, GI_BIG_POE },
{ ITEM_BLUE_FIRE, GI_BLUE_FIRE },
{ ITEM_BOMB, GI_BOMBS_1 },
{ ITEM_BOMBCHU, GI_BOMBCHUS_10 },
{ ITEM_BOMBCHUS_20, GI_BOMBCHUS_20 },
{ ITEM_BOMBCHUS_5, GI_BOMBCHUS_5 },
{ ITEM_BOMBS_10, GI_BOMBS_10 },
{ ITEM_BOMBS_20, GI_BOMBS_20 },
{ ITEM_BOMBS_30, GI_BOMBS_30 },
{ ITEM_BOMBS_5, GI_BOMBS_5 },
{ ITEM_BOMB_BAG_20, GI_BOMB_BAG_20 },
{ ITEM_BOMB_BAG_30, GI_BOMB_BAG_30 },
{ ITEM_BOMB_BAG_40, GI_BOMB_BAG_40 },
{ ITEM_BOOMERANG, GI_BOOMERANG },
{ ITEM_BOOTS_HOVER, GI_BOOTS_HOVER },
{ ITEM_BOOTS_IRON, GI_BOOTS_IRON },
{ ITEM_BOTTLE, GI_BOTTLE },
{ ITEM_BOW, GI_BOW },
{ ITEM_BRACELET, GI_BRACELET },
{ ITEM_BUG, GI_BUGS },
{ ITEM_BULLET_BAG_30, GI_BULLET_BAG_30 },
{ ITEM_BULLET_BAG_40, GI_BULLET_BAG_40 },
{ ITEM_BULLET_BAG_50, GI_BULLET_BAG_50 },
{ ITEM_CHICKEN, GI_CHICKEN },
{ ITEM_CLAIM_CHECK, GI_CLAIM_CHECK },
{ ITEM_COJIRO, GI_COJIRO },
{ ITEM_COMPASS, GI_COMPASS },
{ ITEM_DINS_FIRE, GI_DINS_FIRE },
{ ITEM_DUNGEON_MAP, GI_MAP },
{ ITEM_EYEDROPS, GI_EYEDROPS },
{ ITEM_FAIRY, GI_FAIRY },
{ ITEM_FARORES_WIND, GI_FARORES_WIND },
{ ITEM_FISH, GI_FISH },
{ ITEM_FROG, GI_FROG },
{ ITEM_GAUNTLETS_GOLD, GI_GAUNTLETS_GOLD },
{ ITEM_GAUNTLETS_SILVER, GI_GAUNTLETS_SILVER },
{ ITEM_GERUDO_CARD, GI_GERUDO_CARD },
{ ITEM_HAMMER, GI_HAMMER },
{ ITEM_HEART, GI_HEART },
{ ITEM_HEART_CONTAINER, GI_HEART_CONTAINER },
{ ITEM_HEART_CONTAINER, GI_HEART_CONTAINER_2 },
{ ITEM_HEART_PIECE_2, GI_HEART_PIECE },
{ ITEM_HEART_PIECE_2, GI_HEART_PIECE_WIN },
{ ITEM_HOOKSHOT, GI_HOOKSHOT },
{ ITEM_KEY_BOSS, GI_KEY_BOSS },
{ ITEM_KEY_SMALL, GI_DOOR_KEY },
{ ITEM_KEY_SMALL, GI_KEY_SMALL },
{ ITEM_LENS, GI_LENS },
{ ITEM_LETTER_RUTO, GI_LETTER_RUTO },
{ ITEM_LETTER_ZELDA, GI_LETTER_ZELDA },
{ ITEM_LONGSHOT, GI_LONGSHOT },
{ ITEM_MAGIC_LARGE, GI_MAGIC_LARGE },
{ ITEM_MAGIC_SMALL, GI_MAGIC_SMALL },
{ ITEM_MASK_BUNNY, GI_MASK_BUNNY },
{ ITEM_MASK_GERUDO, GI_MASK_GERUDO },
{ ITEM_MASK_GORON, GI_MASK_GORON },
{ ITEM_MASK_KEATON, GI_MASK_KEATON },
{ ITEM_MASK_SKULL, GI_MASK_SKULL },
{ ITEM_MASK_SPOOKY, GI_MASK_SPOOKY },
{ ITEM_MASK_TRUTH, GI_MASK_TRUTH },
{ ITEM_MASK_ZORA, GI_MASK_ZORA },
{ ITEM_MILK, GI_MILK },
{ ITEM_MILK_BOTTLE, GI_MILK_BOTTLE },
{ ITEM_NAYRUS_LOVE, GI_NAYRUS_LOVE },
{ ITEM_NUT, GI_NUTS_5 },
{ ITEM_NUTS_10, GI_NUTS_10 },
{ ITEM_NUTS_5, GI_NUTS_5 },
{ ITEM_NUTS_5, GI_NUTS_5_2 },
{ ITEM_NUT_UPGRADE_30, GI_NUT_UPGRADE_30 },
{ ITEM_NUT_UPGRADE_40, GI_NUT_UPGRADE_40 },
{ ITEM_OCARINA_FAIRY, GI_OCARINA_FAIRY },
{ ITEM_OCARINA_TIME, GI_OCARINA_OOT },
{ ITEM_ODD_MUSHROOM, GI_ODD_MUSHROOM },
{ ITEM_ODD_POTION, GI_ODD_POTION },
{ ITEM_POCKET_CUCCO, GI_POCKET_CUCCO },
{ ITEM_POCKET_EGG, GI_POCKET_EGG },
{ ITEM_POE, GI_POE },
{ ITEM_POTION_BLUE, GI_POTION_BLUE },
{ ITEM_POTION_GREEN, GI_POTION_GREEN },
{ ITEM_POTION_RED, GI_POTION_RED },
{ ITEM_PRESCRIPTION, GI_PRESCRIPTION },
{ ITEM_QUIVER_40, GI_QUIVER_40 },
{ ITEM_QUIVER_50, GI_QUIVER_50 },
{ ITEM_RUPEE_BLUE, GI_RUPEE_BLUE },
{ ITEM_RUPEE_BLUE, GI_RUPEE_BLUE_LOSE },
{ ITEM_RUPEE_GOLD, GI_RUPEE_GOLD },
{ ITEM_RUPEE_GREEN, GI_RUPEE_GREEN },
{ ITEM_RUPEE_GREEN, GI_RUPEE_GREEN_LOSE },
{ ITEM_RUPEE_PURPLE, GI_RUPEE_PURPLE },
{ ITEM_RUPEE_PURPLE, GI_RUPEE_PURPLE_LOSE },
{ ITEM_RUPEE_RED, GI_RUPEE_RED },
{ ITEM_RUPEE_RED, GI_RUPEE_RED_LOSE },
{ ITEM_SAW, GI_SAW },
{ ITEM_SCALE_GOLDEN, GI_SCALE_GOLDEN },
{ ITEM_SCALE_SILVER, GI_SCALE_SILVER },
{ ITEM_SEEDS, GI_SEEDS_5 },
{ ITEM_SEEDS_30, GI_SEEDS_30 },
{ ITEM_SHIELD_DEKU, GI_SHIELD_DEKU },
{ ITEM_SHIELD_HYLIAN, GI_SHIELD_HYLIAN },
{ ITEM_SHIELD_MIRROR, GI_SHIELD_MIRROR },
{ ITEM_SKULL_TOKEN, GI_SKULL_TOKEN },
{ ITEM_SLINGSHOT, GI_SLINGSHOT },
{ ITEM_STICK, GI_STICKS_1 },
{ ITEM_STICKS_10, GI_STICKS_10 },
{ ITEM_STICKS_5, GI_STICKS_5 },
{ ITEM_STICK_UPGRADE_20, GI_STICK_UPGRADE_20 },
{ ITEM_STICK_UPGRADE_30, GI_STICK_UPGRADE_30 },
{ ITEM_STONE_OF_AGONY, GI_STONE_OF_AGONY },
{ ITEM_SWORD_BGS, GI_SWORD_BGS },
{ ITEM_SWORD_BGS, GI_SWORD_KNIFE },
{ ITEM_SWORD_BROKEN, GI_SWORD_BROKEN },
{ ITEM_SWORD_KOKIRI, GI_SWORD_KOKIRI },
{ ITEM_TUNIC_GORON, GI_TUNIC_GORON },
{ ITEM_TUNIC_ZORA, GI_TUNIC_ZORA },
{ ITEM_WALLET_ADULT, GI_WALLET_ADULT },
{ ITEM_WALLET_GIANT, GI_WALLET_GIANT },
{ ITEM_WEIRD_EGG, GI_WEIRD_EGG },
};
extern "C" GetItemID RetrieveGetItemIDFromItemID(ItemID itemID) {
if (ItemIDtoGetItemIDMap.contains(itemID)) {
return ItemIDtoGetItemIDMap.at(itemID);
}
return GI_MAX;
}
std::unordered_map<ItemID, RandomizerGet> ItemIDtoRandomizerGetMap{
{ ITEM_SONG_MINUET, RG_MINUET_OF_FOREST },
{ ITEM_SONG_BOLERO, RG_BOLERO_OF_FIRE },
{ ITEM_SONG_SERENADE, RG_SERENADE_OF_WATER },
{ ITEM_SONG_REQUIEM, RG_REQUIEM_OF_SPIRIT },
{ ITEM_SONG_NOCTURNE, RG_NOCTURNE_OF_SHADOW },
{ ITEM_SONG_PRELUDE, RG_PRELUDE_OF_LIGHT },
{ ITEM_SONG_LULLABY, RG_ZELDAS_LULLABY },
{ ITEM_SONG_EPONA, RG_EPONAS_SONG },
{ ITEM_SONG_SARIA, RG_SARIAS_SONG },
{ ITEM_SONG_SUN, RG_SUNS_SONG },
{ ITEM_SONG_TIME, RG_SONG_OF_TIME },
{ ITEM_SONG_STORMS, RG_SONG_OF_STORMS },
{ ITEM_MEDALLION_FOREST, RG_FOREST_MEDALLION },
{ ITEM_MEDALLION_FIRE, RG_FIRE_MEDALLION },
{ ITEM_MEDALLION_WATER, RG_WATER_MEDALLION },
{ ITEM_MEDALLION_SPIRIT, RG_SPIRIT_MEDALLION },
{ ITEM_MEDALLION_SHADOW, RG_SHADOW_MEDALLION },
{ ITEM_MEDALLION_LIGHT, RG_LIGHT_MEDALLION },
{ ITEM_KOKIRI_EMERALD, RG_KOKIRI_EMERALD },
{ ITEM_GORON_RUBY, RG_GORON_RUBY },
{ ITEM_ZORA_SAPPHIRE, RG_ZORA_SAPPHIRE },
{ ITEM_SWORD_MASTER, RG_MASTER_SWORD },
};
extern "C" RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID) {
if (ItemIDtoRandomizerGetMap.contains(itemID)) {
return ItemIDtoRandomizerGetMap.at(itemID);
}
return RG_MAX;
}
extern "C" void OTRExtScanner() {
auto lst = *Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->ListFiles().get();
for (auto& rPath : lst) {
std::vector<std::string> raw = StringHelper::Split(rPath, ".");
std::string ext = raw[raw.size() - 1];
std::string nPath = rPath.substr(0, rPath.size() - (ext.size() + 1));
replace(nPath.begin(), nPath.end(), '\\', '/');
ExtensionCache[nPath] = { rPath, ext };
}
}
// Read the port version from an OTR file
OTRVersion ReadPortVersionFromOTR(std::string otrPath) {
OTRVersion version = {};
// Use a temporary archive instance to load the otr and read the version file
auto archive = std::make_shared<Ship::O2rArchive>(otrPath);
if (archive->Open()) {
auto t = archive->LoadFile("portVersion");
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();
}
}
return version;
}
// Checks the program version stored in the otr and compares the major value to soh
// For Windows/Mac/Linux if the version doesn't match, offer to
OTRVersion DetectOTRVersion(std::string fileName, bool isMQ) {
bool isOtrOld = false;
std::string otrPath = Ship::Context::LocateFileAcrossAppDirs(fileName, appShortName);
// Doesn't exist so nothing to do here
if (!std::filesystem::exists(otrPath)) {
return { INT16_MAX, INT16_MAX, INT16_MAX };
}
return ReadPortVersionFromOTR(otrPath);
}
extern "C" void Messagebox_ShowErrorBox(char* title, char* body) {
Extractor::ShowErrorBox(title, body);
}
bool VerifyArchiveVersion(OTRVersion version) {
return version.major != INT16_MAX && version.major != gBuildVersionMajor;
}
extern "C" void InitOTR(int argc, char* argv[]) {
OTRGlobals::Instance = new OTRGlobals();
OTRGlobals::Instance->RunExtract(argc, argv);
OTRGlobals::Instance->Initialize();
CustomMessageManager::Instance = new CustomMessageManager();
ItemTableManager::Instance = new ItemTableManager();
GameInteractor::Instance = new GameInteractor();
SaveManager::Instance = new SaveManager();
std::shared_ptr<Ship::Config> conf = OTRGlobals::Instance->context->GetConfig();
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion1Updater>());
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion2Updater>());
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion3Updater>());
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion4Updater>());
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion5Updater>());
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion6Updater>());
conf->RunVersionUpdates();
SohGui::SetupGuiElements();
SohGui::SetupMenuElements();
AudioCollection::Instance = new AudioCollection();
ActorDB::Instance = new ActorDB();
#ifdef __APPLE__
SpeechSynthesizer::Instance = new DarwinSpeechSynthesizer();
#elif defined(_WIN32)
SpeechSynthesizer::Instance = new SAPISpeechSynthesizer();
#elif ESPEAK
SpeechSynthesizer::Instance = new ESpeakSpeechSynthesizer();
#else
SpeechSynthesizer::Instance = new SpeechLogger();
#endif
SpeechSynthesizer::Instance->Init();
CrowdControl::Instance = new CrowdControl();
Sail::Instance = new Sail();
Anchor::Instance = new Anchor();
OTRMessage_Init();
OTRAudio_Init();
OTRExtScanner();
VanillaItemTable_Init();
DebugConsole_Init();
InitMods();
ActorDB::AddBuiltInCustomActors();
// #region SOH [Randomizer] TODO: Remove these and refactor spoiler file handling for randomizer
CVarClear(CVAR_GENERAL("RandomizerNewFileDropped"));
CVarClear(CVAR_GENERAL("RandomizerDroppedFile"));
// #endregion
Ship::Context::GetInstance()->GetFileDropMgr()->RegisterDropHandler(SoH_HandleConfigDrop);
RegisterImGuiItemIcons();
time_t now = time(NULL);
tm* tm_now = localtime(&now);
if (tm_now->tm_mon == 11 && tm_now->tm_mday >= 24 && tm_now->tm_mday <= 25) {
CVarRegisterInteger(CVAR_GENERAL("LetItSnow"), 1);
} else {
CVarClear(CVAR_GENERAL("LetItSnow"));
}
srand(now);
#ifdef ENABLE_REMOTE_CONTROL
SDLNet_Init();
#endif
if (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0)) {
CrowdControl::Instance->Enable();
}
if (CVarGetInteger(CVAR_REMOTE_SAIL("Enabled"), 0)) {
Sail::Instance->Enable();
}
if (CVarGetInteger(CVAR_REMOTE_ANCHOR("Enabled"), 0)) {
Anchor::Instance->Enable();
}
ShipInit::InitAll();
Rando::StaticData::InitHashMaps();
OTRGlobals::Instance->gRandoContext->AddExcludedOptions();
}
extern "C" void SaveManager_ThreadPoolWait() {
SaveManager::Instance->ThreadPoolWait();
}
extern "C" void DeinitOTR() {
SaveManager_ThreadPoolWait();
OTRAudio_Exit();
if (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0)) {
CrowdControl::Instance->Disable();
}
if (CVarGetInteger(CVAR_REMOTE_SAIL("Enabled"), 0)) {
Sail::Instance->Disable();
}
if (CVarGetInteger(CVAR_REMOTE_ANCHOR("Enabled"), 0)) {
Anchor::Instance->Disable();
}
#ifdef ENABLE_REMOTE_CONTROL
SDLNet_Quit();
#endif
// Destroying gui here because we have shared ptrs to LUS objects which output to SPDLOG which is destroyed before
// these shared ptrs.
SohGui::Destroy();
sohFast3dWindow = nullptr;
OTRGlobals::Instance->context = nullptr;
}
#ifdef _WIN32
extern "C" uint64_t GetFrequency() {
LARGE_INTEGER nFreq;
QueryPerformanceFrequency(&nFreq);
return nFreq.QuadPart;
}
extern "C" uint64_t GetPerfCounter() {
LARGE_INTEGER ticks;
QueryPerformanceCounter(&ticks);
return ticks.QuadPart;
}
#else
extern "C" uint64_t GetFrequency() {
return 1000; // sec -> ms
}
extern "C" uint64_t GetPerfCounter() {
struct timespec monotime;
clock_gettime(CLOCK_MONOTONIC, &monotime);
uint64_t remainingMs = (monotime.tv_nsec / 1000000);
// in milliseconds
return monotime.tv_sec * 1000 + remainingMs;
}
#endif
extern "C" uint64_t GetUnixTimestamp() {
auto time = std::chrono::system_clock::now();
auto since_epoch = time.time_since_epoch();
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(since_epoch);
return (uint64_t)millis.count();
}
extern "C" void Graph_StartFrame() {
#ifndef __WIIU__
using Ship::KbScancode;
int32_t dwScancode = OTRGlobals::Instance->context->GetWindow()->GetLastScancode();
OTRGlobals::Instance->context->GetWindow()->SetLastScancode(-1);
switch (dwScancode) {
case KbScancode::LUS_KB_F1: {
std::shared_ptr<SohModalWindow> modal = static_pointer_cast<SohModalWindow>(
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Modal Window"));
if (modal->IsPopupOpen("Menu Moved")) {
modal->DismissPopup();
} else {
modal->RegisterPopup("Menu Moved",
"The menubar, accessed by hitting F1, no longer exists.\nThe new menu can be "
"accessed by hitting the Esc button instead.",
"OK");
}
break;
}
case KbScancode::LUS_KB_F5: {
if (CVarGetInteger(CVAR_CHEAT("SaveStatesEnabled"), 0) == 0) {
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->TextDrawNotification(
6.0f, true, "Save states not enabled. Check Cheats Menu.");
return;
}
const unsigned int slot = OTRGlobals::Instance->gSaveStateMgr->GetCurrentSlot();
const SaveStateReturn stateReturn =
OTRGlobals::Instance->gSaveStateMgr->AddRequest({ slot, RequestType::SAVE });
switch (stateReturn) {
case SaveStateReturn::SUCCESS:
SPDLOG_INFO("[SOH] Saved state to slot {}", slot);
break;
case SaveStateReturn::FAIL_WRONG_GAMESTATE:
SPDLOG_ERROR("[SOH] Can not save a state outside of \"GamePlay\"");
break;
[[unlikely]] default : break;
}
break;
}
case KbScancode::LUS_KB_F6: {
if (CVarGetInteger(CVAR_CHEAT("SaveStatesEnabled"), 0) == 0) {
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->TextDrawNotification(
6.0f, true, "Save states not enabled. Check Cheats Menu.");
return;
}
unsigned int slot = OTRGlobals::Instance->gSaveStateMgr->GetCurrentSlot();
slot++;
if (slot > 5) {
slot = 0;
}
OTRGlobals::Instance->gSaveStateMgr->SetCurrentSlot(slot);
SPDLOG_INFO("Set SaveState slot to {}.", slot);
break;
}
case KbScancode::LUS_KB_F7: {
if (CVarGetInteger(CVAR_CHEAT("SaveStatesEnabled"), 0) == 0) {
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->TextDrawNotification(
6.0f, true, "Save states not enabled. Check Cheats Menu.");
return;
}
const unsigned int slot = OTRGlobals::Instance->gSaveStateMgr->GetCurrentSlot();
const SaveStateReturn stateReturn =
OTRGlobals::Instance->gSaveStateMgr->AddRequest({ slot, RequestType::LOAD });
switch (stateReturn) {
case SaveStateReturn::SUCCESS:
SPDLOG_INFO("[SOH] Loaded state from slot {}", slot);
break;
case SaveStateReturn::FAIL_INVALID_SLOT:
SPDLOG_ERROR("[SOH] Invalid State Slot Number {}", slot);
break;
case SaveStateReturn::FAIL_STATE_EMPTY:
SPDLOG_ERROR("[SOH] State Slot {} is empty", slot);
break;
case SaveStateReturn::FAIL_WRONG_GAMESTATE:
SPDLOG_ERROR("[SOH] Can not load a state outside of \"GamePlay\"");
break;
[[unlikely]] default : break;
}
break;
}
#if defined(_WIN32) || defined(__APPLE__)
case KbScancode::LUS_KB_F9: {
// Toggle TTS
CVarSetInteger(CVAR_SETTING("A11yTTS"), !CVarGetInteger(CVAR_SETTING("A11yTTS"), 0));
break;
}
#endif
case KbScancode::LUS_KB_TAB: {
if (CVarGetInteger(CVAR_SETTING("Mods.AlternateAssetsHotkey"), 1)) {
CVarSetInteger(CVAR_SETTING("AltAssets"), !CVarGetInteger(CVAR_SETTING("AltAssets"), 1));
}
break;
}
}
#endif
}
void RunCommands(Gfx* Commands, const std::vector<std::unordered_map<Mtx*, MtxF>>& mtx_replacements) {
auto wnd = std::dynamic_pointer_cast<Fast::Fast3dWindow>(OTRGlobals::Instance->context->GetWindow());
if (wnd == nullptr) {
return;
}
// Process window events for resize, mouse, keyboard events
wnd->HandleEvents();
auto intp = wnd->GetInterpreterWeak().lock().get();
intp->mInterpolationIndex = 0;
UIWidgets::Colors themeColor =
static_cast<UIWidgets::Colors>(CVarGetInteger(CVAR_SETTING("Menu.Theme"), UIWidgets::Colors::LightBlue));
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIWidgets::ColorValues.at(themeColor));
for (const auto& m : mtx_replacements) {
wnd->DrawAndRunGraphicsCommands(Commands, m);
intp->mInterpolationIndex++;
}
ImGui::PopStyleColor();
}
// C->C++ Bridge
extern "C" void Graph_ProcessGfxCommands(Gfx* commands) {
{
std::unique_lock<std::mutex> Lock(audio.mutex);
audio.processing = true;
}
audio.cv_to_thread.notify_one();
std::vector<std::unordered_map<Mtx*, MtxF>> mtx_replacements;
int target_fps = OTRGlobals::Instance->GetInterpolationFPS();
static int last_fps;
static int last_update_rate;
static int time;
int fps = target_fps;
int original_fps = 60 / R_UPDATE_RATE;
auto wnd = std::dynamic_pointer_cast<Fast::Fast3dWindow>(Ship::Context::GetInstance()->GetWindow());
if (target_fps == 20 || original_fps > target_fps) {
fps = original_fps;
}
if (last_fps != fps || last_update_rate != R_UPDATE_RATE) {
time = 0;
}
// time_base = fps * original_fps (one second)
int next_original_frame = fps;
while (time + original_fps <= next_original_frame) {
time += original_fps;
if (time != next_original_frame) {
mtx_replacements.push_back(FrameInterpolation_Interpolate((float)time / next_original_frame));
} else {
mtx_replacements.emplace_back();
}
}
time -= fps;
if (wnd != nullptr) {
wnd->SetTargetFps(fps);
}
// When the gfx debugger is active, only run with the final mtx
if (GfxDebuggerIsDebugging()) {
mtx_replacements.clear();
mtx_replacements.emplace_back();
}
RunCommands(commands, mtx_replacements);
last_fps = fps;
last_update_rate = R_UPDATE_RATE;
{
std::unique_lock<std::mutex> Lock(audio.mutex);
while (audio.processing) {
audio.cv_from_thread.wait(Lock);
}
}
bool curAltAssets = CVarGetInteger(CVAR_SETTING("AltAssets"), 1);
if (prevAltAssets != curAltAssets) {
prevAltAssets = curAltAssets;
Ship::Context::GetInstance()->GetResourceManager()->SetAltAssetsEnabled(curAltAssets);
gfx_texture_cache_clear();
SOH::SkeletonPatcher::UpdateSkeletons();
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnAssetAltChange>();
}
// OTRTODO: FIGURE OUT END FRAME POINT
/* if (OTRGlobals::Instance->context->lastScancode != -1)
OTRGlobals::Instance->context->lastScancode = -1;*/
}
float divisor_num = 0.0f;
extern "C" void OTRGetPixelDepthPrepare(float x, float y) {
auto wnd = std::dynamic_pointer_cast<Fast::Fast3dWindow>(Ship::Context::GetInstance()->GetWindow());
if (wnd == nullptr) {
return;
}
wnd->GetPixelDepthPrepare(x, y);
}
extern "C" uint16_t OTRGetPixelDepth(float x, float y) {
auto wnd = std::dynamic_pointer_cast<Fast::Fast3dWindow>(Ship::Context::GetInstance()->GetWindow());
if (wnd == nullptr) {
return 0;
}
return wnd->GetPixelDepth(x, y);
}
extern "C" Sprite* GetSeedTexture(uint8_t index) {
return OTRGlobals::Instance->gRandoContext->GetSeedTexture(index);
}
extern "C" uint8_t GetSeedIconIndex(uint8_t index) {
return OTRGlobals::Instance->gRandoContext->hashIconIndexes[index];
}
std::map<std::string, SoundFontSample*> cachedCustomSFs;
extern "C" SoundFontSample* ReadCustomSample(const char* path) {
return nullptr;
/*
if (!ExtensionCache.contains(path))
return nullptr;
ExtensionEntry entry = ExtensionCache[path];
auto sampleRaw = Ship::Context::GetInstance()->GetResourceManager()->LoadFile(entry.path);
uint32_t* strem = (uint32_t*)sampleRaw->Buffer.get();
uint8_t* strem2 = (uint8_t*)strem;
SoundFontSample* sampleC = new SoundFontSample;
if (entry.ext == "wav") {
drwav_uint32 channels;
drwav_uint32 sampleRate;
drwav_uint64 totalPcm;
drmp3_int16* pcmData =
drwav_open_memory_and_read_pcm_frames_s16(strem2, sampleRaw->BufferSize, &channels, &sampleRate, &totalPcm,
NULL); sampleC->size = totalPcm; sampleC->sampleAddr = (uint8_t*)pcmData; sampleC->codec = CODEC_S16;
sampleC->loop = new AdpcmLoop;
sampleC->loop->start = 0;
sampleC->loop->end = sampleC->size - 1;
sampleC->loop->count = 0;
sampleC->sampleRateMagicValue = 'RIFF';
sampleC->sampleRate = sampleRate;
cachedCustomSFs[path] = sampleC;
return sampleC;
} else if (entry.ext == "mp3") {
drmp3_config mp3Info;
drmp3_uint64 totalPcm;
drmp3_int16* pcmData =
drmp3_open_memory_and_read_pcm_frames_s16(strem2, sampleRaw->BufferSize, &mp3Info, &totalPcm, NULL);
sampleC->size = totalPcm * mp3Info.channels * sizeof(short);
sampleC->sampleAddr = (uint8_t*)pcmData;
sampleC->codec = CODEC_S16;
sampleC->loop = new AdpcmLoop;
sampleC->loop->start = 0;
sampleC->loop->end = sampleC->size;
sampleC->loop->count = 0;
sampleC->sampleRateMagicValue = 'RIFF';
sampleC->sampleRate = mp3Info.sampleRate;
cachedCustomSFs[path] = sampleC;
return sampleC;
}
return nullptr;
*/
}
ImFont* OTRGlobals::CreateFontWithSize(float size, std::string fontPath, bool isJapaneseFont) {
auto mImGuiIo = &ImGui::GetIO();
ImFont* font;
if (fontPath == "") {
ImFontConfig fontCfg = ImFontConfig();
fontCfg.OversampleH = fontCfg.OversampleV = 1;
fontCfg.PixelSnapH = true;
fontCfg.SizePixels = size;
font = mImGuiIo->Fonts->AddFontDefault(&fontCfg);
} else {
auto initData = std::make_shared<Ship::ResourceInitData>();
initData->Format = RESOURCE_FORMAT_BINARY;
initData->Type = static_cast<uint32_t>(RESOURCE_TYPE_FONT);
initData->ResourceVersion = 0;
initData->Path = fontPath;
std::shared_ptr<Ship::Font> fontData = std::static_pointer_cast<Ship::Font>(
Ship::Context::GetInstance()->GetResourceManager()->LoadResource(fontPath, false, initData));
ImFontConfig fontConf;
fontConf.FontDataOwnedByAtlas = false;
const ImWchar* glyph_ranges = isJapaneseFont ? mImGuiIo->Fonts->GetGlyphRangesJapanese() : nullptr;
font = mImGuiIo->Fonts->AddFontFromMemoryTTF(fontData->Data, fontData->DataSize, size, &fontConf, glyph_ranges);
}
// FontAwesome fonts need to have their sizes reduced by 2.0f/3.0f in order to align correctly
float iconFontSize = size * 2.0f / 3.0f;
static const ImWchar sIconsRanges[] = { ICON_MIN_FA, ICON_MAX_16_FA, 0 };
ImFontConfig iconsConfig;
iconsConfig.MergeMode = true;
iconsConfig.PixelSnapH = true;
iconsConfig.GlyphMinAdvanceX = iconFontSize;
mImGuiIo->Fonts->AddFontFromMemoryCompressedBase85TTF(fontawesome_compressed_data_base85, iconFontSize,
&iconsConfig, sIconsRanges);
return font;
}
std::filesystem::path GetSaveFile(std::shared_ptr<Ship::Config> Conf) {
const std::string fileName =
Conf->GetString("Game.SaveName", Ship::Context::GetPathRelativeToAppDirectory("oot_save.sav"));
std::filesystem::path saveFile = std::filesystem::absolute(fileName);
if (!exists(saveFile.parent_path())) {
create_directories(saveFile.parent_path());
}
return saveFile;
}
std::filesystem::path GetSaveFile() {
const std::shared_ptr<Ship::Config> pConf = OTRGlobals::Instance->context->GetConfig();
return GetSaveFile(pConf);
}
void OTRGlobals::CheckSaveFile(size_t sramSize) const {
const std::shared_ptr<Ship::Config> pConf = Instance->context->GetConfig();
std::filesystem::path savePath = GetSaveFile(pConf);
std::fstream saveFile(savePath, std::fstream::in | std::fstream::out | std::fstream::binary);
if (saveFile.fail()) {
saveFile.open(savePath, std::fstream::in | std::fstream::out | std::fstream::binary | std::fstream::app);
for (size_t i = 0; i < sramSize; i++) {
saveFile.write("\0", 1);
}
}
saveFile.close();
}
extern "C" void Ctx_ReadSaveFile(uintptr_t addr, void* dramAddr, size_t size) {
SaveManager::ReadSaveFile(GetSaveFile(), addr, dramAddr, size);
}
extern "C" void Ctx_WriteSaveFile(uintptr_t addr, void* dramAddr, size_t size) {
SaveManager::WriteSaveFile(GetSaveFile(), addr, dramAddr, size);
}
std::wstring StringToU16(const std::string& s) {
std::vector<unsigned long> result;
size_t i = 0;
while (i < s.size()) {
unsigned long uni;
size_t nbytes = 0;
bool error = false;
unsigned char c = s[i++];
if (c < 0x80) { // ascii
uni = c;
nbytes = 0;
} else if (c == GFXP_HIRAGANA_CHAR) { // Start Hiragana Mode
uni = c;
nbytes = 0;
} else if (c == GFXP_KATAKANA_CHAR) { // Start Katakana Mode
uni = c;
nbytes = 0;
} else if (c <= 0xBF) { // Invalid Characters (Skipped)
nbytes = 0;
uni = '\1';
} else if (c <= 0xDF) {
uni = c & 0x1F;
nbytes = 1;
} else if (c <= 0xEF) {
uni = c & 0x0F;
nbytes = 2;
} else if (c <= 0xF7) {
uni = c & 0x07;
nbytes = 3;
}
for (size_t j = 0; j < nbytes; ++j) {
unsigned char c = s[i++];
uni <<= 6;
uni += c & 0x3F;
}
if (uni != '\1')
result.push_back(uni);
}
std::wstring utf16;
for (size_t i = 0; i < result.size(); ++i) {
unsigned long uni = result[i];
if (uni <= 0xFFFF) {
utf16 += (wchar_t)uni;
} else {
uni -= 0x10000;
utf16 += (wchar_t)((uni >> 10) + 0xD800);
utf16 += (wchar_t)((uni & 0x3FF) + 0xDC00);
}
}
return utf16;
}
extern "C" void OTRGfxPrint(const char* str, void* printer, void (*printImpl)(void*, char)) {
const std::vector<uint32_t> hira1 = {
u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'-', u'', u'',
u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'',
};
const std::vector<uint32_t> hira2 = {
u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'',
u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'',
};
const std::vector<uint32_t> kata1 = {
u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'',
};
const std::vector<uint32_t> kata2 = {
u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'',
u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'',
u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'', u'',
};
std::wstring wstr = StringToU16(str);
bool hiraganaMode = false;
for (const auto& c : wstr) {
if (c < 0x80) {
printImpl(printer, c);
} else if (c == GFXP_HIRAGANA_CHAR) {
hiraganaMode = true;
} else if (c == GFXP_KATAKANA_CHAR) {
hiraganaMode = false;
} else if (c >= u'' && c <= u'') { // katakana (hankaku)
if (hiraganaMode && c >= u'' && c <= u'ソ') {
printImpl(printer, c - 0xFEC0 - 0x20); // Hiragana Mode, Block 1
} else if (hiraganaMode && c >= u'' && c <= u'') {
printImpl(printer, c - 0xFEC0 + 0x20); // Hiragana Mode, Block 2
} else {
printImpl(printer, c - 0xFEC0);
}
} else if (c == u' ') { // zenkaku space
printImpl(printer, u' ');
} else {
auto it = std::find(hira1.begin(), hira1.end(), c);
if (it != hira1.end()) { // hiragana block 1
printImpl(printer, 0x86 + std::distance(hira1.begin(), it));
}
auto it2 = std::find(hira2.begin(), hira2.end(), c);
if (it2 != hira2.end()) { // hiragana block 2
printImpl(printer, 0xe0 + std::distance(hira2.begin(), it2));
}
auto it3 = std::find(kata1.begin(), kata1.end(), c);
if (it3 != kata1.end()) { // katakana zenkaku block 1
printImpl(printer, 0xa6 + std::distance(kata1.begin(), it3));
}
auto it4 = std::find(kata2.begin(), kata2.end(), c);
if (it4 != kata2.end()) { // katakana zenkaku block 2
printImpl(printer, 0xb1 + std::distance(kata2.begin(), it4));
}
}
}
}
extern "C" uint32_t OTRGetCurrentWidth() {
return OTRGlobals::Instance->context->GetWindow()->GetWidth();
}
extern "C" uint32_t OTRGetCurrentHeight() {
return OTRGlobals::Instance->context->GetWindow()->GetHeight();
}
Color_RGB8 GetColorForControllerLED() {
auto brightness = CVarGetFloat(CVAR_SETTING("LEDBrightness"), 1.0f) / 1.0f;
Color_RGB8 color = { 0, 0, 0 };
if (brightness > 0.0f) {
LEDColorSource source =
static_cast<LEDColorSource>(CVarGetInteger(CVAR_SETTING("LEDColorSource"), LED_SOURCE_TUNIC_ORIGINAL));
bool criticalOverride = CVarGetInteger(CVAR_SETTING("LEDCriticalOverride"), 1);
if (gPlayState && (source == LED_SOURCE_TUNIC_ORIGINAL || source == LED_SOURCE_TUNIC_COSMETICS)) {
switch (CUR_EQUIP_VALUE(EQUIP_TYPE_TUNIC)) {
case EQUIP_VALUE_TUNIC_KOKIRI:
color = source == LED_SOURCE_TUNIC_COSMETICS
? CVarGetColor24(CVAR_COSMETIC("Link.KokiriTunic.Value"), kokiriColor)
: kokiriColor;
break;
case EQUIP_VALUE_TUNIC_GORON:
color = source == LED_SOURCE_TUNIC_COSMETICS
? CVarGetColor24(CVAR_COSMETIC("Link.GoronTunic.Value"), goronColor)
: goronColor;
break;
case EQUIP_VALUE_TUNIC_ZORA:
color = source == LED_SOURCE_TUNIC_COSMETICS
? CVarGetColor24(CVAR_COSMETIC("Link.ZoraTunic.Value"), zoraColor)
: zoraColor;
break;
}
}
if (gPlayState && (source == LED_SOURCE_NAVI_ORIGINAL || source == LED_SOURCE_NAVI_COSMETICS)) {
Actor* arrowPointedActor = gPlayState->actorCtx.targetCtx.arrowPointedActor;
if (arrowPointedActor) {
ActorCategory category = (ActorCategory)arrowPointedActor->category;
switch (category) {
case ACTORCAT_PLAYER:
if (source == LED_SOURCE_NAVI_COSMETICS &&
CVarGetInteger(CVAR_COSMETIC("Navi.IdlePrimary.Changed"), 0)) {
color = CVarGetColor24(CVAR_COSMETIC("Navi.IdlePrimary.Value"), defaultIdleColor.inner);
break;
}
color = LEDColorDefaultNaviColorList[category].inner;
break;
case ACTORCAT_NPC:
if (source == LED_SOURCE_NAVI_COSMETICS &&
CVarGetInteger(CVAR_COSMETIC("Navi.NPCPrimary.Changed"), 0)) {
color = CVarGetColor24(CVAR_COSMETIC("Navi.NPCPrimary.Value"), defaultNPCColor.inner);
break;
}
color = LEDColorDefaultNaviColorList[category].inner;
break;
case ACTORCAT_ENEMY:
case ACTORCAT_BOSS:
if (source == LED_SOURCE_NAVI_COSMETICS &&
CVarGetInteger(CVAR_COSMETIC("Navi.EnemyPrimary.Changed"), 0)) {
color = CVarGetColor24(CVAR_COSMETIC("Navi.EnemyPrimary.Value"), defaultEnemyColor.inner);
break;
}
color = LEDColorDefaultNaviColorList[category].inner;
break;
default:
if (source == LED_SOURCE_NAVI_COSMETICS &&
CVarGetInteger(CVAR_COSMETIC("Navi.PropsPrimary.Changed"), 0)) {
color = CVarGetColor24(CVAR_COSMETIC("Navi.PropsPrimary.Value"), defaultPropsColor.inner);
break;
}
color = LEDColorDefaultNaviColorList[category].inner;
}
} else { // No target actor.
if (source == LED_SOURCE_NAVI_COSMETICS &&
CVarGetInteger(CVAR_COSMETIC("Navi.IdlePrimary.Changed"), 0)) {
color = CVarGetColor24(CVAR_COSMETIC("Navi.IdlePrimary.Value"), defaultIdleColor.inner);
} else {
color = LEDColorDefaultNaviColorList[ACTORCAT_PLAYER].inner;
}
}
}
if (source == LED_SOURCE_CUSTOM) {
color = CVarGetColor24(CVAR_SETTING("LEDPort1Color"), { 255, 255, 255 });
}
if (gPlayState && (criticalOverride || source == LED_SOURCE_HEALTH)) {
if (HealthMeter_IsCritical()) {
color = { 0xFF, 0, 0 };
} else if (gSaveContext.healthCapacity != 0 && source == LED_SOURCE_HEALTH) {
if (gSaveContext.health / (float)gSaveContext.healthCapacity <= 0.4f) {
color = { 0xFF, 0xFF, 0 };
} else {
color = { 0, 0xFF, 0 };
}
}
}
color.r = color.r * brightness;
color.g = color.g * brightness;
color.b = color.b * brightness;
}
return color;
}
extern "C" void OTRControllerCallback(uint8_t rumble) {
// We call this every tick, SDL accounts for this use and prevents driver spam
// https://github.com/libsdl-org/SDL/blob/f17058b562c8a1090c0c996b42982721ace90903/src/joystick/SDL_joystick.c#L1114-L1144
Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(0)->GetLED()->SetLEDColor(
GetColorForControllerLED());
static std::shared_ptr<SohInputEditorWindow> controllerConfigWindow = nullptr;
if (controllerConfigWindow == nullptr) {
controllerConfigWindow = std::dynamic_pointer_cast<SohInputEditorWindow>(
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Controller Configuration"));
} else if (controllerConfigWindow->TestingRumble()) {
return;
}
if (rumble) {
Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(0)->GetRumble()->StartRumble();
} else {
Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(0)->GetRumble()->StopRumble();
}
}
extern "C" float OTRGetAspectRatio() {
return Ship::Context::GetInstance()->GetWindow()->GetAspectRatio();
}
extern "C" float OTRGetDimensionFromLeftEdge(float v) {
return (SCREEN_WIDTH / 2 - SCREEN_HEIGHT / 2 * OTRGetAspectRatio() + (v));
}
extern "C" float OTRGetDimensionFromRightEdge(float v) {
return (SCREEN_WIDTH / 2 + SCREEN_HEIGHT / 2 * OTRGetAspectRatio() - (SCREEN_WIDTH - v));
}
// Gets the width of the current render target area
extern "C" uint32_t OTRGetGameRenderWidth() {
auto fastWnd = dynamic_pointer_cast<Fast::Fast3dWindow>(Ship::Context::GetInstance()->GetWindow());
auto intP = fastWnd->GetInterpreterWeak().lock();
if (!intP) {
assert(false && "Lost reference to Fast::Interpreter");
return 320;
}
uint32_t height, width;
intP->GetCurDimensions(&width, &height);
return width;
}
// Gets the height of the current render target area
extern "C" uint32_t OTRGetGameRenderHeight() {
auto fastWnd = dynamic_pointer_cast<Fast::Fast3dWindow>(Ship::Context::GetInstance()->GetWindow());
auto intP = fastWnd->GetInterpreterWeak().lock();
if (!intP) {
assert(false && "Lost reference to Fast::Interpreter");
return 240;
}
uint32_t height, width;
intP->GetCurDimensions(&width, &height);
return height;
}
f32 floorf(f32 x); // RANDOTODO False positive error "allowing all exceptions is incompatible with previous function"
f32 ceilf(f32 x); // This gets annoying
extern "C" int16_t OTRGetRectDimensionFromLeftEdge(float v) {
return ((int)floorf(OTRGetDimensionFromLeftEdge(v)));
}
extern "C" int16_t OTRGetRectDimensionFromRightEdge(float v) {
return ((int)ceilf(OTRGetDimensionFromRightEdge(v)));
}
extern "C" int AudioPlayer_Buffered(void) {
return AudioPlayerBuffered();
}
extern "C" int AudioPlayer_GetDesiredBuffered(void) {
return AudioPlayerGetDesiredBuffered();
}
extern "C" void AudioPlayer_Play(const uint8_t* buf, uint32_t len) {
AudioPlayerPlayFrame(buf, len);
}
extern "C" int Controller_ShouldRumble(size_t slot) {
// don't rumble if we don't have rumble mappings
if (Ship::Context::GetInstance()
->GetControlDeck()
->GetControllerByPort(static_cast<uint8_t>(slot))
->GetRumble()
->GetAllRumbleMappings()
.empty()) {
return 0;
}
// don't rumble if we don't have connected gamepads
if (Ship::Context::GetInstance()
->GetControlDeck()
->GetConnectedPhysicalDeviceManager()
->GetConnectedSDLGamepadsForPort(slot)
.empty()) {
return 0;
}
// rumble
return 1;
}
extern "C" size_t GetEquipNowMessage(char* buffer, char* src, const size_t maxBufferSize) {
CustomMessage customMessage("\x04\x1A\x08"
"Would you like to equip it now?"
"\x09&&"
"\x1B%g"
"Yes"
"&"
"No"
"%w\x02",
"\x04\x1A\x08"
"M"
"\x9A"
"chtest Du es jetzt ausr\x9Esten?"
"\x09&&"
"\x1B%g"
"Ja!"
"&"
"Nein!"
"%w\x02",
"\x04\x1A\x08"
"D\x96sirez-vous l'\x96quiper maintenant?"
"\x09&&"
"\x1B%g"
"Oui"
"&"
"Non"
"%w\x02");
customMessage.Format();
std::string postfix = customMessage.GetForCurrentLanguage();
std::string str;
std::string FixedBaseStr(src);
size_t RemoveControlChar = FixedBaseStr.find_first_of("\x02");
if (RemoveControlChar != std::string::npos) {
FixedBaseStr = FixedBaseStr.substr(0, RemoveControlChar);
}
str = FixedBaseStr + postfix;
if (!str.empty()) {
memset(buffer, 0, maxBufferSize);
const size_t copiedCharLen = std::min<size_t>(maxBufferSize - 1, str.length());
memcpy(buffer, str.c_str(), copiedCharLen);
return copiedCharLen;
}
return 0;
}
extern "C" void Randomizer_ParseSpoiler(const char* fileLoc) {
OTRGlobals::Instance->gRandoContext->ParseSpoiler(fileLoc);
}
extern "C" bool Randomizer_IsTrialRequired(s32 trialFlag) {
return OTRGlobals::Instance->gRandomizer->IsTrialRequired(trialFlag);
}
extern "C" u32 SpoilerFileExists(const char* spoilerFileName) {
return OTRGlobals::Instance->gRandomizer->SpoilerFileExists(spoilerFileName);
}
extern "C" u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey) {
return OTRGlobals::Instance->gRandoContext->GetOption(randoSettingKey).Get();
}
extern "C" RandomizerCheck Randomizer_GetCheckFromActor(s16 actorId, s16 sceneNum, s16 actorParams) {
return OTRGlobals::Instance->gRandomizer->GetCheckFromActor(actorId, sceneNum, actorParams);
}
extern "C" ShopItemIdentity Randomizer_IdentifyShopItem(s32 sceneNum, u8 slotIndex) {
return OTRGlobals::Instance->gRandomizer->IdentifyShopItem(sceneNum, slotIndex);
}
extern "C" GetItemEntry ItemTable_Retrieve(int16_t getItemID) {
GetItemEntry giEntry = ItemTableManager::Instance->RetrieveItemEntry(MOD_NONE, getItemID);
return giEntry;
}
extern "C" GetItemEntry ItemTable_RetrieveEntry(s16 tableID, s16 getItemID) {
if (tableID == MOD_RANDOMIZER) {
return Rando::StaticData::RetrieveItem(static_cast<RandomizerGet>(getItemID)).GetGIEntry_Copy();
}
return ItemTableManager::Instance->RetrieveItemEntry(tableID, getItemID);
}
extern "C" GetItemEntry Randomizer_GetItemFromActor(s16 actorId, s16 sceneNum, s16 actorParams, GetItemID ogId) {
return OTRGlobals::Instance->gRandomizer->GetItemFromActor(actorId, sceneNum, actorParams, ogId);
}
extern "C" GetItemEntry Randomizer_GetItemFromActorWithoutObtainabilityCheck(s16 actorId, s16 sceneNum, s16 actorParams,
GetItemID ogId) {
return OTRGlobals::Instance->gRandomizer->GetItemFromActor(actorId, sceneNum, actorParams, ogId, false);
}
extern "C" GetItemEntry Randomizer_GetItemFromKnownCheck(RandomizerCheck randomizerCheck, GetItemID ogId) {
return OTRGlobals::Instance->gRandomizer->GetItemFromKnownCheck(randomizerCheck, ogId);
}
extern "C" GetItemEntry Randomizer_GetItemFromKnownCheckWithoutObtainabilityCheck(RandomizerCheck randomizerCheck,
GetItemID ogId) {
return OTRGlobals::Instance->gRandomizer->GetItemFromKnownCheck(randomizerCheck, ogId, false);
}
extern "C" RandomizerInf Randomizer_GetRandomizerInfFromCheck(RandomizerCheck randomizerCheck) {
return OTRGlobals::Instance->gRandomizer->GetRandomizerInfFromCheck(randomizerCheck);
}
extern "C" ItemObtainability Randomizer_GetItemObtainabilityFromRandomizerCheck(RandomizerCheck randomizerCheck) {
return OTRGlobals::Instance->gRandomizer->GetItemObtainabilityFromRandomizerCheck(randomizerCheck);
}
extern "C" bool Randomizer_IsCheckShuffled(RandomizerCheck rc) {
return CheckTracker::IsCheckShuffled(rc);
}
extern "C" GetItemEntry GetItemMystery() {
return GET_ITEM_MYSTERY;
}
extern "C" uint8_t Randomizer_IsSeedGenerated() {
return OTRGlobals::Instance->gRandoContext->IsSeedGenerated() ? 1 : 0;
}
extern "C" void Randomizer_SetSeedGenerated(bool seedGenerated) {
OTRGlobals::Instance->gRandoContext->SetSeedGenerated(seedGenerated);
}
extern "C" uint8_t Randomizer_IsSpoilerLoaded() {
return OTRGlobals::Instance->gRandoContext->IsSpoilerLoaded() ? 1 : 0;
}
extern "C" void Randomizer_SetSpoilerLoaded(bool spoilerLoaded) {
OTRGlobals::Instance->gRandoContext->SetSpoilerLoaded(spoilerLoaded);
}
extern "C" uint8_t Randomizer_GenerateRandomizer() {
return GenerateRandomizer() ? 1 : 0;
}
extern "C" void Randomizer_ShowRandomizerMenu() {
SohGui::ShowRandomizerSettingsMenu();
}
extern "C" void EntranceTracker_SetCurrentGrottoID(s16 entranceIndex) {
EntranceTracker::SetCurrentGrottoIDForTracker(entranceIndex);
}
extern "C" void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex) {
EntranceTracker::SetLastEntranceOverrideForTracker(entranceIndex);
}
extern "C" void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement) {
if (auto intP = dynamic_pointer_cast<Fast::Fast3dWindow>(Ship::Context::GetInstance()->GetWindow())
->GetInterpreterWeak()
.lock()) {
intP->RegisterBlendedTexture(name, mask, replacement);
} else {
assert(false && "Lost reference to Fast::Interpreter");
}
}
extern "C" void Gfx_UnregisterBlendedTexture(const char* name) {
if (auto intP = dynamic_pointer_cast<Fast::Fast3dWindow>(Ship::Context::GetInstance()->GetWindow())
->GetInterpreterWeak()
.lock()) {
intP->UnregisterBlendedTexture(name);
} else {
assert(false && "Lost reference to Fast::Interpreter");
}
}
extern "C" void Gfx_TextureCacheDelete(const uint8_t* texAddr) {
char* imgName = (char*)texAddr;
if (texAddr == nullptr) {
return;
}
if (ResourceMgr_OTRSigCheck(imgName)) {
texAddr = (const uint8_t*)ResourceMgr_GetResourceDataByNameHandlingMQ(imgName);
}
if (auto intP = dynamic_pointer_cast<Fast::Fast3dWindow>(Ship::Context::GetInstance()->GetWindow())
->GetInterpreterWeak()
.lock()) {
intP->TextureCacheDelete(texAddr);
} else {
assert(false && "Lost reference to Fast::Interpreter");
}
}
bool SoH_HandleConfigDrop(char* filePath) {
if (SohUtils::IsStringEmpty(filePath)) {
return false;
}
try {
std::ifstream configStream(filePath);
if (!configStream) {
return false;
}
nlohmann::json configJson;
configStream >> configJson;
if (!configJson.contains("CVars")) {
return false;
}
CVarClearBlock(CVAR_PREFIX_ENHANCEMENT);
CVarClearBlock(CVAR_PREFIX_CHEAT);
CVarClearBlock(CVAR_PREFIX_RANDOMIZER_SETTING);
CVarClearBlock(CVAR_PREFIX_RANDOMIZER_ENHANCEMENT);
CVarClearBlock(CVAR_PREFIX_DEVELOPER_TOOLS);
// Flatten everything under CVars into a single array
auto cvars = configJson["CVars"].flatten();
for (auto& [key, value] : cvars.items()) {
// Replace slashes with dots in key, and remove leading dot
std::string path = key;
std::replace(path.begin(), path.end(), '/', '.');
if (path[0] == '.') {
path.erase(0, 1);
}
if (value.is_string()) {
CVarSetString(path.c_str(), value.get<std::string>().c_str());
} else if (value.is_number_integer()) {
CVarSetInteger(path.c_str(), value.get<int>());
} else if (value.is_number_float()) {
CVarSetFloat(path.c_str(), value.get<float>());
}
}
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
gui->GetGuiWindow("Console")->Hide();
gui->GetGuiWindow("Actor Viewer")->Hide();
gui->GetGuiWindow("Collision Viewer")->Hide();
gui->GetGuiWindow("Save Editor")->Hide();
gui->GetGuiWindow("Display List Viewer")->Hide();
gui->GetGuiWindow("Stats")->Hide();
std::dynamic_pointer_cast<Ship::ConsoleWindow>(
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))
->ClearBindings();
Rando::Settings::GetInstance()->UpdateAllOptions();
gui->SaveConsoleVariablesNextFrame();
ShipInit::Init("*");
uint32_t finalHash = SohUtils::Hash(configJson.dump());
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Configuration Loaded. Hash: %d", finalHash);
return true;
} catch (std::exception& e) {
SPDLOG_ERROR("Failed to load config file: {}", e.what());
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file");
return false;
} catch (...) {
SPDLOG_ERROR("Failed to load config file");
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file");
return false;
}
return false;
}
extern "C" void CheckTracker_RecalculateAvailableChecks() {
CheckTracker::RecalculateAvailableChecks();
}
extern "C" uint32_t Ship_GetInterpolationFPS() {
return OTRGlobals::Instance->GetInterpolationFPS();
}
// Number of interpolated frames
extern "C" uint32_t Ship_GetInterpolationFrameCount() {
return ceil((float)Ship_GetInterpolationFPS() / 20.0f);
}