8.0.2 Update and Controller Reconnect Fix

This commit is contained in:
Waterdish
2023-11-19 21:17:55 -08:00
parent ef1407664f
commit 3584fec653
30 changed files with 265 additions and 162 deletions
+2 -2
View File
@@ -16,8 +16,8 @@ android {
}
minSdkVersion 18
targetSdkVersion 31
versionCode 2
versionName "1.1.0"
versionCode 3
versionName "1.1.1"
externalNativeBuild {
//ndkBuild {
// arguments "APP_PLATFORM=android-23"
+2 -2
View File
@@ -5,8 +5,8 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 ")
project(Game VERSION 8.0.0 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Alfa" CACHE STRING "")
project(Game VERSION 8.0.2 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Charlie" CACHE STRING "")
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
add_subdirectory(src)
+2 -2
View File
@@ -5,8 +5,8 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use")
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
project(Ship VERSION 8.0.0 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Alfa" CACHE STRING "")
project(Ship VERSION 8.0.2 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Charlie" CACHE STRING "")
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh)
-4
View File
@@ -333,10 +333,6 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(CMAKE_FIND_FRAMEWORK LAST)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(CMAKE_FIND_FRAMEWORK LAST)
endif()
if (NOT CMAKE_SYSTEM_NAME STREQUAL "Android")
find_package(PNG REQUIRED)
endif()
+6 -4
View File
@@ -1,7 +1,7 @@
#include "CrashHandler.h"
#include "Utils/StringHelper.h"
#if __has_include(<unistd.h>)
#if __has_include(<unistd.h>) && !defined(__ANDROID__)
#define HAS_POSIX 1
#else
#define HAS_POSIX 0
@@ -25,6 +25,8 @@
#include <inttypes.h>
#pragma comment(lib, "Dbghelp.lib")
#else
#include "WarningHandler.h"
#endif
// Feel free to add more crash messages.
@@ -50,7 +52,7 @@ static std::array<const char* const, 14> crashEasterEgg = {
"\tOh! MY GOD!!",
};
#if HAS_POSIX == 1 && !defined(__ANDROID__)
#if HAS_POSIX == 1
void ErrorHandler(int sig)
{
std::array<void*, 4096> arr;
@@ -193,12 +195,12 @@ LONG seh_filter(_EXCEPTION_POINTERS* ex)
void CrashHandler_Init()
{
#if HAS_POSIX == 1 && !defined(__ANDROID__)
#if HAS_POSIX == 1
signal(SIGSEGV, ErrorHandler);
signal(SIGABRT, ErrorHandler);
#elif defined(_MSC_VER)
SetUnhandledExceptionFilter(seh_filter);
#elif !defined(__ANDROID__)
#else
HANDLE_WARNING(WarningType::Always,
"tried to set error handler, but this ZAPD build lacks support for one", "");
#endif
+5 -5
View File
@@ -1,9 +1,9 @@
#define VER_FILEVERSION 8, 0, 0, 0
#define VER_FILEVERSION_STR "8.0.0\0"
#define VER_FILEVERSION 8, 0, 2, 0
#define VER_FILEVERSION_STR "8.0.2\0"
#define VER_PRODUCTVERSION 8, 0, 0, 0
#define VER_PRODUCTVERSION_str "8.0.0\0"
#define VER_PRODUCTVERSION 8, 0, 2, 0
#define VER_PRODUCTVERSION_str "8.0.2\0"
#define VER_COMPANYNAME_STR "github.com/harbourmasters\0"
#define VER_PRODUCTNAME_STR "Ship of Harkinian\0"
@@ -11,4 +11,4 @@
#define VER_INTERNALNAME_STR "soh\0"
#define VER_ORIGINALFILENAME_STR "soh.exe\0"
#define VER_FILEDESCRIPTION_STR "Ship of Harkinian - MacReady Alfa\0"
#define VER_FILEDESCRIPTION_STR "Ship of Harkinian - MacReady Charlie\0"
+1 -1
View File
@@ -439,7 +439,7 @@ static std::unordered_map<u16, const char*> actorDescriptions = {
{ ACTOR_EN_DAIKU_KAKARIKO, "Carpenters (Kakariko)" },
{ ACTOR_BG_BOWL_WALL, "Bombchu Bowling Alley Wall" },
{ ACTOR_EN_WALL_TUBO, "Bombchu Bowling Alley Bullseyes" },
{ ACTOR_EN_PO_DESERT, "Poe Guide (Desert Wasteland)" },
{ ACTOR_EN_PO_DESERT, "Poe Guide (Haunted Wasteland)" },
{ ACTOR_EN_CROW, "Guay" },
{ ACTOR_DOOR_KILLER, "Fake Door" },
{ ACTOR_BG_SPOT11_OASIS, "Oasis (Desert Colossus)" },
@@ -400,8 +400,9 @@ void AudioCollection::InitializeShufflePool() {
if (shufflePoolInitialized) return;
for (auto& [seqId, seqInfo] : sequenceMap) {
if (!seqInfo.canBeUsedAsReplacement) continue;
const std::string cvarKey = "gAudioEditor.Excluded." + seqInfo.sfxKey;
if (CVarGetInteger(cvarKey.c_str(), 0) && !seqInfo.canBeUsedAsReplacement) {
if (CVarGetInteger(cvarKey.c_str(), 0)) {
excludedSequences.insert(&seqInfo);
} else {
includedSequences.insert(&seqInfo);
@@ -1767,6 +1767,10 @@ void CosmeticsEditorWindow::DrawElement() {
ImGui::SameLine();
UIWidgets::EnhancementCombobox("gCosmetics.DefaultColorScheme", colorSchemes, COLORSCHEME_N64);
UIWidgets::EnhancementCheckbox("Advanced Mode", "gCosmetics.AdvancedMode");
UIWidgets::InsertHelpHoverText(
"Some cosmetic options may not apply if you have any mods that provide custom models for the cosmetic option.\n\n"
"For example, if you have custom Link model, then the Link's Hair color option will most likely not apply."
);
if (CVarGetInteger("gCosmetics.AdvancedMode", 0)) {
if (ImGui::Button("Lock All Advanced", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) {
for (auto& [id, cosmeticOption] : cosmeticOptions) {
@@ -380,13 +380,13 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
effect->category = kEffectCatBoots;
effect->timeRemaining = 30000;
effect->giEffect = new GameInteractionEffect::ForceEquipBoots();
effect->giEffect->parameters[0] = PLAYER_BOOTS_IRON;
effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_IRON;
break;
case kEffectForceHoverBoots:
effect->category = kEffectCatBoots;
effect->timeRemaining = 30000;
effect->giEffect = new GameInteractionEffect::ForceEquipBoots();
effect->giEffect->parameters[0] = PLAYER_BOOTS_HOVER;
effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_HOVER;
break;
case kEffectSlipperyFloor:
effect->category = kEffectCatSlipperyFloor;
@@ -207,6 +207,7 @@ static bool ResetHandler(std::shared_ptr<LUS::Console> Console, std::vector<std:
return 1;
}
gPlayState->gameplayFrames = 0;
SET_NEXT_GAMESTATE(&gPlayState->state, TitleSetup_Init, GameState);
gPlayState->state.running = false;
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnExitGame>(gSaveContext.fileNum);
+3 -23
View File
@@ -35,8 +35,6 @@ extern PlayState* gPlayState;
extern void Overlay_DisplayText(float duration, const char* text);
uint32_t ResourceMgr_IsSceneMasterQuest(s16 sceneNum);
}
bool performDelayedSave = false;
bool performSave = false;
// TODO: When there's more uses of something like this, create a new GI::RawAction?
void ReloadSceneTogglingLinkAge() {
@@ -258,14 +256,12 @@ void RegisterOcarinaTimeTravel() {
void AutoSave(GetItemEntry itemEntry) {
u8 item = itemEntry.itemId;
bool performSave = false;
// Don't autosave immediately after buying items from shops to prevent getting them for free!
// Don't autosave in the Chamber of Sages since resuming from that map breaks the game
// Don't autosave during the Ganon fight when picking up the Master Sword
// Don't autosave in the fishing pond to prevent getting rod on B outside of the pond
// Don't autosave in the bombchu bowling alley to prevent having chus on B outside of the minigame
// Don't autosave in grottos since resuming from grottos breaks the game.
if ((CVarGetInteger("gAutosave", AUTOSAVE_OFF) != AUTOSAVE_OFF) && (gPlayState != NULL) && (gSaveContext.pendingSale == ITEM_NONE) &&
(gPlayState->gameplayFrames > 60 && gSaveContext.cutsceneIndex < 0xFFF0) && (gPlayState->sceneNum != SCENE_GANON_BOSS)) {
(gPlayState->gameplayFrames > 60 && gSaveContext.cutsceneIndex < 0xFFF0) && (gPlayState->sceneNum != SCENE_GANON_BOSS) && (gPlayState->sceneNum != SCENE_CHAMBER_OF_THE_SAGES)) {
if (((CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_ALL_ITEMS) || (CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_ALL_ITEMS)) && (item != ITEM_NONE)) {
// Autosave for all items
performSave = true;
@@ -326,25 +322,9 @@ void AutoSave(GetItemEntry itemEntry) {
CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION) {
performSave = true;
}
if (gPlayState->sceneNum == SCENE_FAIRYS_FOUNTAIN || gPlayState->sceneNum == SCENE_GROTTOS ||
gPlayState->sceneNum == SCENE_CHAMBER_OF_THE_SAGES || gPlayState->sceneNum == SCENE_FISHING_POND ||
gPlayState->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY) {
if (CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_MAJOR_ITEMS ||
CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_ALL_ITEMS ||
CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION) {
performSave = false;
return;
}
if (performSave) {
performSave = false;
performDelayedSave = true;
}
return;
}
if (performSave || performDelayedSave) {
if (performSave) {
Play_PerformSave(gPlayState);
performSave = false;
performDelayedSave = false;
}
}
}
@@ -783,7 +783,7 @@ std::map<RandomizerCheckArea, std::string> rcAreaNames = {
{ RCAREA_LAKE_HYLIA, "Lake Hylia"},
{ RCAREA_GERUDO_VALLEY, "Gerudo Valley"},
{ RCAREA_GERUDO_FORTRESS, "Gerudo Fortress"},
{ RCAREA_WASTELAND, "Desert Wasteland"},
{ RCAREA_WASTELAND, "Haunted Wasteland"},
{ RCAREA_DESERT_COLOSSUS, "Desert Colossus"},
{ RCAREA_MARKET, "Hyrule Market"},
{ RCAREA_HYRULE_CASTLE, "Hyrule Castle"},
@@ -452,6 +452,63 @@ bool HasItemBeenCollected(RandomizerCheck rc) {
return false;
}
void CheckTrackerLoadGame(int32_t fileNum) {
LoadSettings();
TrySetAreas();
for (auto [rc, rcObj] : RandomizerCheckObjects::GetAllRCObjects()) {
RandomizerCheckTrackerData rcTrackerData = gSaveContext.checkTrackerData[rc];
if (rc == RC_UNKNOWN_CHECK || rc == RC_MAX || rc == RC_LINKS_POCKET ||
!RandomizerCheckObjects::GetAllRCObjects().contains(rc))
continue;
RandomizerCheckObject realRcObj;
if (rc == RC_GIFT_FROM_SAGES && !IS_RANDO) {
realRcObj = RCO_RAORU;
} else {
realRcObj = rcObj;
}
if (!IsVisibleInCheckTracker(realRcObj)) continue;
checksByArea.find(realRcObj.rcArea)->second.push_back(realRcObj);
if (rcTrackerData.status == RCSHOW_SAVED || rcTrackerData.skipped) {
areaChecksGotten[realRcObj.rcArea]++;
}
if (areaChecksGotten[realRcObj.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(realRcObj.rcArea)) {
areasSpoiled |= (1 << realRcObj.rcArea);
}
}
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING && IS_RANDO) {
s8 startingAge = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_STARTING_AGE);
RandomizerCheckArea startingArea;
switch (startingAge) {
case RO_AGE_CHILD:
startingArea = RCAREA_KOKIRI_FOREST;
break;
case RO_AGE_ADULT:
startingArea = RCAREA_MARKET;
break;
default:
startingArea = RCAREA_KOKIRI_FOREST;
break;
}
RandomizerCheckObject linksPocket = { RC_LINKS_POCKET, RCVORMQ_BOTH, RCTYPE_LINKS_POCKET, startingArea, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, GI_NONE, false, "Link's Pocket", "Link's Pocket" };
checksByArea.find(startingArea)->second.push_back(linksPocket);
areaChecksGotten[startingArea]++;
}
showVOrMQ = (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_RANDOM_NUMBER ||
(OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SET_NUMBER &&
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) < 12));
LinksPocket();
SongFromImpa();
GiftFromSages();
initialized = true;
UpdateAllOrdering();
UpdateInventoryChecks();
}
void CheckTrackerDialogClosed() {
if (messageCloseCheck) {
messageCloseCheck = false;
@@ -679,9 +736,6 @@ void SaveFile(SaveContext* saveContext, int sectionID, bool fullSave) {
}
void LoadFile() {
Teardown();
LoadSettings();
TrySetAreas();
SaveManager::Instance->LoadArray("checks", RC_MAX, [](size_t i) {
SaveManager::Instance->LoadStruct("", [&]() {
SaveManager::Instance->LoadData("status", gSaveContext.checkTrackerData[i].status);
@@ -689,58 +743,7 @@ void LoadFile() {
SaveManager::Instance->LoadData("price", gSaveContext.checkTrackerData[i].price);
SaveManager::Instance->LoadData("hintItem", gSaveContext.checkTrackerData[i].hintItem);
});
RandomizerCheckTrackerData entry = gSaveContext.checkTrackerData[i];
RandomizerCheck rc = static_cast<RandomizerCheck>(i);
if (rc == RC_UNKNOWN_CHECK || rc == RC_MAX ||
!RandomizerCheckObjects::GetAllRCObjects().contains(rc))
return;
RandomizerCheckObject entry2;
if (rc == RC_GIFT_FROM_SAGES && !IS_RANDO) {
entry2 = RCO_RAORU;
} else {
entry2 = RandomizerCheckObjects::GetAllRCObjects().find(rc)->second;
}
if (!IsVisibleInCheckTracker(entry2)) return;
checksByArea.find(entry2.rcArea)->second.push_back(entry2);
if (entry.status == RCSHOW_SAVED || entry.skipped) {
areaChecksGotten[entry2.rcArea]++;
}
if (areaChecksGotten[entry2.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(entry2.rcArea)) {
areasSpoiled |= (1 << entry2.rcArea);
}
});
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING && IS_RANDO) {
s8 startingAge = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_STARTING_AGE);
RandomizerCheckArea startingArea;
switch (startingAge) {
case RO_AGE_CHILD:
startingArea = RCAREA_KOKIRI_FOREST;
break;
case RO_AGE_ADULT:
startingArea = RCAREA_MARKET;
break;
default:
startingArea = RCAREA_KOKIRI_FOREST;
break;
}
RandomizerCheckObject linksPocket = { RC_LINKS_POCKET, RCVORMQ_BOTH, RCTYPE_LINKS_POCKET, startingArea, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, GI_NONE, false, "Link's Pocket", "Link's Pocket" };
checksByArea.find(startingArea)->second.push_back(linksPocket);
areaChecksGotten[startingArea]++;
}
showVOrMQ = (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_RANDOM_NUMBER ||
(OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SET_NUMBER &&
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) < 12));
LinksPocket();
SongFromImpa();
GiftFromSages();
initialized = true;
UpdateAllOrdering();
UpdateInventoryChecks();
}
void Teardown() {
@@ -1533,6 +1536,7 @@ void CheckTrackerWindow::InitElement() {
SaveManager::Instance->AddInitFunction(InitTrackerData);
sectionId = SaveManager::Instance->AddSaveFunction("trackerData", 1, SaveFile, true, -1);
SaveManager::Instance->AddLoadFunction("trackerData", 1, LoadFile);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>(CheckTrackerLoadGame);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([](uint32_t fileNum) {
Teardown();
});
@@ -219,7 +219,7 @@ std::unordered_map<RandomizerTrickArea, std::string> rtAreaNames = {
{ RTAREA_LAKE_HYLIA, "Lake Hylia"},
{ RTAREA_GERUDO_VALLEY, "Gerudo Valley"},
{ RTAREA_GERUDO_FORTRESS, "Gerudo Fortress"},
{ RTAREA_WASTELAND, "Desert Wasteland"},
{ RTAREA_WASTELAND, "Haunted Wasteland"},
{ RTAREA_DESERT_COLOSSUS, "Desert Colossus"},
{ RTAREA_MARKET, "Hyrule Market"},
{ RTAREA_HYRULE_CASTLE, "Hyrule Castle"},
+15
View File
@@ -1569,6 +1569,11 @@ extern "C" Gfx* ResourceMgr_LoadGfxByName(const char* path)
return (Gfx*)&res->Instructions[0];
}
extern "C" uint8_t ResourceMgr_FileIsCustomByName(const char* path) {
auto res = std::static_pointer_cast<LUS::DisplayList>(GetResourceByNameHandlingMQ(path));
return res->GetInitData()->IsCustom;
}
typedef struct {
int index;
Gfx instruction;
@@ -1600,6 +1605,11 @@ extern "C" void ResourceMgr_PatchGfxByName(const char* path, const char* patchNa
// index /= 2;
// }
// Do not patch custom assets as they most likely do not have the same instructions as authentic assets
if (res->GetInitData()->IsCustom) {
return;
}
Gfx* gfx = (Gfx*)&res->Instructions[index];
if (!originalGfx.contains(path) || !originalGfx[path].contains(patchName)) {
@@ -1616,6 +1626,11 @@ extern "C" void ResourceMgr_PatchGfxCopyCommandByName(const char* path, const ch
auto res = std::static_pointer_cast<LUS::DisplayList>(
LUS::Context::GetInstance()->GetResourceManager()->LoadResource(path));
// Do not patch custom assets as they most likely do not have the same instructions as authentic assets
if (res->GetInitData()->IsCustom) {
return;
}
Gfx* destinationGfx = (Gfx*)&res->Instructions[destinationIndex];
Gfx sourceGfx = res->Instructions[sourceIndex];
+1
View File
@@ -103,6 +103,7 @@ AnimationHeaderCommon* ResourceMgr_LoadAnimByName(const char* path);
char* ResourceMgr_GetNameByCRC(uint64_t crc, char* alloc);
Gfx* ResourceMgr_LoadGfxByCRC(uint64_t crc);
Gfx* ResourceMgr_LoadGfxByName(const char* path);
uint8_t ResourceMgr_FileIsCustomByName(const char* path);
void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction);
void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName);
char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path);
+63 -30
View File
@@ -47,6 +47,11 @@ std::filesystem::path SaveManager::GetFileName(int fileNum) {
return sSavePath / ("file" + std::to_string(fileNum + 1) + ".sav");
}
std::filesystem::path SaveManager::GetFileTempName(int fileNum) {
const std::filesystem::path sSavePath(LUS::Context::GetPathRelativeToAppDirectory("Save"));
return sSavePath / ("file" + std::to_string(fileNum + 1) + ".temp");
}
SaveManager::SaveManager() {
coreSectionIDsByName["base"] = SECTION_ID_BASE;
coreSectionIDsByName["randomizer"] = SECTION_ID_RANDOMIZER;
@@ -65,6 +70,10 @@ SaveManager::SaveManager() {
AddInitFunction(InitFileImpl);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([this](uint32_t fileNum) { ThreadPoolWait(); });
smThreadPool = std::make_shared<BS::thread_pool>(1);
for (SaveFileMetaInfo& info : fileMetaInfo) {
info.valid = false;
info.deaths = 0;
@@ -357,12 +366,14 @@ void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool f
});
}
// Init() here is an extension of InitSram, and thus not truly an initializer for SaveManager itself. don't put any class initialization stuff here
void SaveManager::Init() {
// Wait on saves that snuck through the Wait in OnExitGame
ThreadPoolWait();
const std::filesystem::path sSavePath(LUS::Context::GetPathRelativeToAppDirectory("Save"));
const std::filesystem::path sGlobalPath = sSavePath / std::string("global.sav");
auto sOldSavePath = LUS::Context::GetPathRelativeToAppDirectory("oot_save.sav");
auto sOldBackupSavePath = LUS::Context::GetPathRelativeToAppDirectory("oot_save.bak");
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([this](uint32_t fileNum) { ThreadPoolWait(); });
// If the save directory does not exist, create it
if (!std::filesystem::exists(sSavePath)) {
@@ -403,7 +414,6 @@ void SaveManager::Init() {
} else {
CreateDefaultGlobal();
}
smThreadPool = std::make_shared<BS::thread_pool>(1);
// Load files to initialize metadata
for (int fileNum = 0; fileNum < MaxFiles; fileNum++) {
@@ -869,6 +879,32 @@ void SaveManager::InitFileMaxed() {
gSaveContext.sceneFlags[5].swch = 0x40000000;
}
#if defined(__WIIU__) || defined(__SWITCH__)
// std::filesystem::copy_file doesn't work properly with the Wii U's toolchain atm
int copy_file(const char* src, const char* dst) {
alignas(0x40) uint8_t buf[4096];
FILE* r = fopen(src, "r");
if (!r) {
return -1;
}
FILE* w = fopen(dst, "w");
if (!w) {
return -2;
}
size_t res;
while ((res = fread(buf, 1, sizeof(buf), r)) > 0) {
if (fwrite(buf, 1, res, w) != res) {
break;
}
}
fclose(r);
fclose(w);
return res >= 0 ? 0 : res;
}
#endif
// Threaded SaveFile takes copy of gSaveContext for local unmodified storage
void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID) {
@@ -910,19 +946,42 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int se
svi.func(saveContext, sectionID, false);
}
std::filesystem::path fileName = GetFileName(fileNum);
std::filesystem::path tempFile = GetFileTempName(fileNum);
if (std::filesystem::exists(tempFile)) {
std::filesystem::remove(tempFile);
}
#if defined(__SWITCH__) || defined(__WIIU__)
FILE* w = fopen(GetFileName(fileNum).c_str(), "w");
FILE* w = fopen(tempFile.c_str(), "w");
std::string json_string = saveBlock.dump(4);
fwrite(json_string.c_str(), sizeof(char), json_string.length(), w);
fclose(w);
#else
std::ofstream output(GetFileName(fileNum));
std::ofstream output(tempFile);
output << std::setw(4) << saveBlock << std::endl;
output.close();
#endif
if (std::filesystem::exists(fileName)) {
std::filesystem::remove(fileName);
}
#if defined(__SWITCH__) || defined(__WIIU__)
copy_file(tempFile.c_str(), fileName.c_str());
#else
std::filesystem::copy_file(tempFile, fileName);
#endif
if (std::filesystem::exists(tempFile)) {
std::filesystem::remove(tempFile);
}
delete saveContext;
InitMeta(fileNum);
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveFile>(fileNum);
SPDLOG_INFO("Save File Finish - fileNum: {}", fileNum);
}
// SaveSection creates a copy of gSaveContext to prevent mid-save data modification, and passes its reference to SaveFileThreaded
@@ -2105,32 +2164,6 @@ void SaveManager::LoadStruct(const std::string& name, LoadStructFunc func) {
}
}
#if defined(__WIIU__) || defined(__SWITCH__)
// std::filesystem::copy_file doesn't work properly with the Wii U's toolchain atm
int copy_file(const char* src, const char* dst) {
alignas(0x40) uint8_t buf[4096];
FILE* r = fopen(src, "r");
if (!r) {
return -1;
}
FILE* w = fopen(dst, "w");
if (!w) {
return -2;
}
size_t res;
while ((res = fread(buf, 1, sizeof(buf), r)) > 0) {
if (fwrite(buf, 1, res, w) != res) {
break;
}
}
fclose(r);
fclose(w);
return res >= 0 ? 0 : res;
}
#endif
void SaveManager::CopyZeldaFile(int from, int to) {
assert(std::filesystem::exists(GetFileName(from)));
DeleteZeldaFile(to);
+1
View File
@@ -142,6 +142,7 @@ class SaveManager {
private:
std::filesystem::path GetFileName(int fileNum);
std::filesystem::path GetFileTempName(int fileNum);
nlohmann::json saveBlock;
void ConvertFromUnversioned();

Some files were not shown because too many files have changed in this diff Show More