diff --git a/Runtime/CGameOptions.hpp b/Runtime/CGameOptions.hpp index ddb6b8456..20e665631 100644 --- a/Runtime/CGameOptions.hpp +++ b/Runtime/CGameOptions.hpp @@ -9,6 +9,7 @@ namespace urde /** Options tracked persistently between game sessions */ class CPersistentOptions { + friend class CGameState; bool x0_[98] = {}; bool x68_[64] = {}; std::vector> xac_cinematicStates; /* (MLVL, Cinematic) */ @@ -40,11 +41,16 @@ public: CPersistentOptions(CBitStreamReader& stream); void SetCinematicState(ResId mlvlId, TEditorId cineId, bool state); - bool PlayerHasHardMode() const { return xd0_25_hasHardMode; } - bool PlayerBeatHardMode() const { return xd0_26_hardModeBeat; } - bool PlayerHasFusion() const { return xd0_28_hasFusion; } - bool AllItemsCollected() const { return xd0_29_allItemsCollected; } + bool GetPlayerHasHardMode() const { return xd0_25_hasHardMode; } + void SetPlayerHasHardMode(bool v) { xd0_25_hasHardMode = v; } + bool GetPlayerBeatHardMode() const { return xd0_26_hardModeBeat; } + void SetPlayerBeatHardMode(bool v) { xd0_26_hardModeBeat = v; } + bool GetPlayerHasFusion() const { return xd0_28_hasFusion; } + void SetPlayerHasFusion(bool v) { xd0_28_hasFusion = v; } + bool GetAllItemsCollected() const { return xd0_29_allItemsCollected; } + void SetAllItemsCollected(bool v) { xd0_29_allItemsCollected = v; } u32 GetLogScanCount() const { return xcc_logScanCount; } + void SetLogScanCount(u32 v) { xcc_logScanCount = v; } }; /** Options tracked per game session */ diff --git a/Runtime/CGameState.cpp b/Runtime/CGameState.cpp index ca0f0953c..61e9a194e 100644 --- a/Runtime/CGameState.cpp +++ b/Runtime/CGameState.cpp @@ -88,6 +88,52 @@ void CWorldState::PutTo(CBitStreamWriter& writer, const CSaveWorld& savw) const x14_layerState->PutTo(writer); } +CGameState::GameFileStateInfo CGameState::LoadGameFileState(const u8* data) +{ + CBitStreamReader stream(data, 4096); + GameFileStateInfo ret; + + for (u32 i = 0; i < 128; i++) + stream.ReadEncoded(8); + ret.x14_timestamp = stream.ReadEncoded(32); + + ret.x20_hardMode = stream.ReadEncoded(1); + stream.ReadEncoded(1); + ret.x8_mlvlId = stream.ReadEncoded(32); + + union BitsToDouble + { + struct + { + u32 low; + u32 high; + }; + double doub; + } conv; + conv.low = stream.ReadEncoded(32); + conv.high = stream.ReadEncoded(32); + ret.x0_playTime = conv.doub; + + CPlayerState playerState(stream); + ret.x10_energyTanks = playerState.GetItemCapacity(CPlayerState::EItemType::EnergyTanks); + + u32 itemPercent; + if (ret.x8_mlvlId == 0x158EFE17) + itemPercent = 0; + else + itemPercent = playerState.CalculateItemCollectionRate() * 100 / playerState.GetPickupTotal(); + ret.x18_itemPercent = itemPercent; + + float somePercent; + if (playerState.GetTotalLogScans() == 0) + somePercent = 0.f; + else + somePercent = 100.f * playerState.GetLogScans() / float(playerState.GetTotalLogScans()); + ret.x1c_scanPercent = somePercent; + + return ret; +} + CGameState::CGameState() { x98_playerState.reset(new CPlayerState()); @@ -137,6 +183,19 @@ CGameState::CGameState(CBitStreamReader& stream) } } +void CGameState::MergePersistentOptions(const CPersistentOptions& opts) +{ + if (opts.xd0_24_) + xa8_systemOptions.xd0_24_ = true; + if (opts.xd0_27_) + xa8_systemOptions.xd0_27_ = true; + if (&opts != &xa8_systemOptions) + memcpy(xa8_systemOptions.x0_, opts.x0_, 98); + xa8_systemOptions.SetLogScanCount(opts.GetLogScanCount()); + xa8_systemOptions.SetAllItemsCollected(opts.GetAllItemsCollected()); + xa8_systemOptions.SetPlayerHasHardMode(opts.GetPlayerHasHardMode()); + xa8_systemOptions.SetPlayerBeatHardMode(opts.GetPlayerBeatHardMode()); +} void CGameState::PutTo(CBitStreamWriter& writer) const { diff --git a/Runtime/CGameState.hpp b/Runtime/CGameState.hpp index c3e99fe9e..eef71b7e1 100644 --- a/Runtime/CGameState.hpp +++ b/Runtime/CGameState.hpp @@ -108,7 +108,21 @@ public: CWorldState& CurrentWorldState() { return StateForWorld(x84_mlvlId); } ResId CurrentWorldAssetId() const { return x84_mlvlId; } void SetHardMode(bool v) { x228_24_hardMode = v; } + void MergePersistentOptions(const CPersistentOptions& opts); void PutTo(CBitStreamWriter& writer) const; + + struct GameFileStateInfo + { + double x0_playTime; + u32 x8_mlvlId; + float xc_health; + u32 x10_energyTanks; + u32 x14_timestamp; + u32 x18_itemPercent; + float x1c_scanPercent; + bool x20_hardMode; + }; + static GameFileStateInfo LoadGameFileState(const u8* data); }; } diff --git a/Runtime/CMemoryCardSys.cpp b/Runtime/CMemoryCardSys.cpp index d6c5dcd56..33f5f7b81 100644 --- a/Runtime/CMemoryCardSys.cpp +++ b/Runtime/CMemoryCardSys.cpp @@ -117,7 +117,7 @@ bool CMemoryCardSys::InitializePump() x20_scanStates.reserve(x20_scanStates.size() + savw.GetScans().size()); for (const CSaveWorld::SScanState& scan : savw.GetScans()) - x20_scanStates[scan.x0_id] = scan.x4_category; + x20_scanStates.emplace_back(scan.x0_id, scan.x4_category); wldMemOut.x3c_saveWorld = std::move(world.x34_saveWorld); wldMemOut.x2c_worldName = g_SimplePool->GetObj(SObjectTag{FOURCC('STRG'), wldMemOut.x0_strgId}); @@ -157,4 +157,24 @@ CMemoryCardSys::ECardResult CMemoryCardSys::GetSerialNo(EMemoryCardPort port, u6 return ECardResult::CARD_RESULT_READY; } +CMemoryCardSys::ECardResult CMemoryCardSys::GetResultCode(EMemoryCardPort port) +{ + return ECardResult::CARD_RESULT_READY; +} + +CMemoryCardSys::ECardResult CMemoryCardSys::GetStatus(EMemoryCardPort port, int fileNo, CARDStat& statOut) +{ + return ECardResult::CARD_RESULT_READY; +} + +CMemoryCardSys::ECardResult CMemoryCardSys::DeleteFile(EMemoryCardPort port, const char* name) +{ + return ECardResult::CARD_RESULT_READY; +} + +CMemoryCardSys::ECardResult CMemoryCardSys::FastDeleteFile(EMemoryCardPort port, int fileNo) +{ + return ECardResult::CARD_RESULT_READY; +} + } diff --git a/Runtime/CMemoryCardSys.hpp b/Runtime/CMemoryCardSys.hpp index c15dde5b9..3734504b6 100644 --- a/Runtime/CMemoryCardSys.hpp +++ b/Runtime/CMemoryCardSys.hpp @@ -7,6 +7,11 @@ #include "CSaveWorld.hpp" #include +// longest file name string excluding terminating zero +#define CARD_FILENAME_MAX 32 + +#define CARD_ICON_MAX 8 + namespace urde { class CDummyWorld; @@ -53,10 +58,11 @@ class CMemoryCardSys TLockedToken x0_hints; std::vector> xc_memoryWorlds; /* MLVL as key */ std::experimental::optional> x1c_worldInter; /* used to be auto_ptr of vector */ - std::unordered_map x20_scanStates; + std::vector> x20_scanStates; public: const std::vector& GetHints() const { return x0_hints->GetHints(); } const std::vector>& GetMemoryWorlds() const { return xc_memoryWorlds; } + const std::vector>& GetScanStates() const { return x20_scanStates; } CMemoryCardSys(); bool InitializePump(); @@ -68,6 +74,7 @@ public: enum class ECardResult { + CARD_RESULT_CRC_MISMATCH = -1003, CARD_RESULT_FATAL_ERROR = -128, CARD_RESULT_ENCODING = -13, CARD_RESULT_BROKEN = -6, @@ -85,11 +92,59 @@ public: u32 x8_sectorSize; // in bytes }; + struct CARDStat + { + // read-only (Set by CARDGetStatus) + char x0_fileName[CARD_FILENAME_MAX]; + u32 x20_length; + u32 x24_time; // seconds since 01/01/2000 midnight + u8 x28_gameName[4]; + u8 x2c_company[2]; + + // read/write (Set by CARDGetStatus/CARDSetStatus) + u8 x2e_bannerFormat; + u8 x2f___padding; + u32 x30_iconAddr; // offset to the banner, bannerTlut, icon, iconTlut data set. + u16 x34_iconFormat; + u16 x36_iconSpeed; + u32 x38_commentAddr; // offset to the pair of 32 byte character strings. + + // read-only (Set by CARDGetStatus) + u32 x3c_offsetBanner; + u32 x40_offsetBannerTlut; + u32 x44_offsetIcon[CARD_ICON_MAX]; + u32 x64_offsetIconTlut; + u32 x68_offsetData; + + u32 GetTime() const { return x24_time; } + u32 GetBannerFormat() const { return x2e_bannerFormat & 0x3; } + void SetBannerFormat(u32 fmt) { x2e_bannerFormat = (x2e_bannerFormat & ~0x3) | fmt; } + u32 GetIconFormat(int idx) const { return (x34_iconFormat >> (idx * 2)) & 0x3; } + void SetIconFormat(u32 fmt, int idx) + { + x34_iconFormat &= ~(0x3 << (idx * 2)); + x34_iconFormat |= fmt << (idx * 2); + } + void SetIconSpeed(u32 sp, int idx) + { + x36_iconSpeed &= ~(0x3 << (idx * 2)); + x36_iconSpeed |= sp << (idx * 2); + } + u32 GetIconAddr() const { return x30_iconAddr; } + void SetIconAddr(u32 addr) { x30_iconAddr = addr; } + u32 GetCommentAddr() const { return x38_commentAddr; } + void SetCommentAddr(u32 addr) { x38_commentAddr = addr; } + }; + static CardProbeResults CardProbe(EMemoryCardPort port); static ECardResult MountCard(EMemoryCardPort port); static ECardResult CheckCard(EMemoryCardPort port); static ECardResult GetNumFreeBytes(EMemoryCardPort port, s32& freeBytes, s32& freeFiles); static ECardResult GetSerialNo(EMemoryCardPort port, u64& serialOut); + static ECardResult GetResultCode(EMemoryCardPort port); + static ECardResult GetStatus(EMemoryCardPort port, int fileNo, CARDStat& statOut); + static ECardResult DeleteFile(EMemoryCardPort port, const char* name); + static ECardResult FastDeleteFile(EMemoryCardPort port, int fileNo); }; } diff --git a/Runtime/CPlayerState.cpp b/Runtime/CPlayerState.cpp index a3486308b..13e7b1762 100644 --- a/Runtime/CPlayerState.cpp +++ b/Runtime/CPlayerState.cpp @@ -86,8 +86,8 @@ CPlayerState::CPlayerState(CBitStreamReader& stream) x170_scanTimes[i].second = 0.f; } - x180_ = stream.ReadEncoded(CBitStreamReader::GetBitCount(0x100)); - x184_ = stream.ReadEncoded(CBitStreamReader::GetBitCount(0x100)); + x180_logScans = stream.ReadEncoded(CBitStreamReader::GetBitCount(0x100)); + x184_totalLogScans = stream.ReadEncoded(CBitStreamReader::GetBitCount(0x100)); } void CPlayerState::PutTo(CBitStreamWriter &stream) @@ -112,8 +112,8 @@ void CPlayerState::PutTo(CBitStreamWriter &stream) stream.WriteEncoded(false, 1); } - stream.WriteEncoded(x180_, CBitStreamWriter::GetBitCount(0x100)); - stream.WriteEncoded(x184_, CBitStreamWriter::GetBitCount(0x100)); + stream.WriteEncoded(x180_logScans, CBitStreamWriter::GetBitCount(0x100)); + stream.WriteEncoded(x184_totalLogScans, CBitStreamWriter::GetBitCount(0x100)); } static const float unk[] diff --git a/Runtime/CPlayerState.hpp b/Runtime/CPlayerState.hpp index 50a9a38f2..16f294f2c 100644 --- a/Runtime/CPlayerState.hpp +++ b/Runtime/CPlayerState.hpp @@ -124,8 +124,8 @@ private: EPlayerSuit x20_currentSuit = EPlayerSuit::Power; rstl::reserved_vector x24_powerups; rstl::reserved_vector, 846> x170_scanTimes; - u32 x180_ = 0; - u32 x184_ = 0; + u32 x180_logScans = 0; + u32 x184_totalLogScans = 0; CStaticInterference x188_staticIntf; public: @@ -166,6 +166,9 @@ public: float CalculateHealth(u32 health); void ReInitalizePowerUp(EItemType type, u32 capacity); void InitializePowerUp(EItemType type, u32 capacity); + u32 GetLogScans() const { return x180_logScans; } + u32 GetTotalLogScans() const { return x184_totalLogScans; } + const rstl::reserved_vector, 846>& GetScanTimes() const { return x170_scanTimes; } CPlayerState() : x188_staticIntf(5) { x0_24_ = true; } CPlayerState(CBitStreamReader& stream); void PutTo(CBitStreamWriter& stream); diff --git a/Runtime/CStateManager.cpp b/Runtime/CStateManager.cpp index 2e12f40df..47af41201 100644 --- a/Runtime/CStateManager.cpp +++ b/Runtime/CStateManager.cpp @@ -22,6 +22,7 @@ #include "World/CScriptSpawnPoint.hpp" #include "AutoMapper/CMapWorldInfo.hpp" #include "Particle/CGenDescription.hpp" +#include "CMemoryCardSys.hpp" #include @@ -808,4 +809,23 @@ TUniqueId CStateManager::AllocateUniqueId() return 0; } +std::pair CStateManager::CalculateScanCompletionRate() const +{ + u32 num = 0; + u32 denom = 0; + int idx = 0; + for (const std::pair& scan : x8b8_playerState->GetScanTimes()) + { + CSaveWorld::EScanCategory category = g_MemoryCardSys->GetScanStates()[idx++].second; + if (category != CSaveWorld::EScanCategory::None && + category != CSaveWorld::EScanCategory::Research) + { + ++denom; + if (scan.second == 1.f) + ++num; + } + } + return {num, denom}; +} + } diff --git a/Runtime/CStateManager.hpp b/Runtime/CStateManager.hpp index e94ed1184..fb456466a 100644 --- a/Runtime/CStateManager.hpp +++ b/Runtime/CStateManager.hpp @@ -303,6 +303,8 @@ public: CListeningAiList& GetListeningAiObjectList() const { return *x834_listenAiObjs; } CAiWaypointList& GetAiWaypointObjectList() const { return *x83c_aiWaypointObjs; } CPlatformAndDoorList& GetPlatformAndDoorObjectList() const { return *x844_platformAndDoorObjs; } + + std::pair CalculateScanCompletionRate() const; }; } diff --git a/Runtime/MP1/CFrontEndUI.cpp b/Runtime/MP1/CFrontEndUI.cpp index bb8935df6..4d32e67e1 100644 --- a/Runtime/MP1/CFrontEndUI.cpp +++ b/Runtime/MP1/CFrontEndUI.cpp @@ -277,7 +277,7 @@ void CFrontEndUI::SNewFileSelectFrame::ActivateNewGamePopup() PlayAdvanceSfx(); - if (g_GameState->SystemOptions().PlayerHasHardMode()) + if (g_GameState->SystemOptions().GetPlayerHasHardMode()) { x48_textpane_popupadvance.SetPairText(g_MainStringTable->GetString(102)); x50_textpane_popupcancel.SetPairText(g_MainStringTable->GetString(94)); @@ -414,7 +414,7 @@ void CFrontEndUI::SNewFileSelectFrame::DoPopupAdvance(CGuiTableGroup* caller) } else { - if (g_GameState->SystemOptions().PlayerHasHardMode()) + if (g_GameState->SystemOptions().GetPlayerHasHardMode()) { if (x40_tablegroup_popup->GetUserSelection() == 1) { @@ -502,7 +502,7 @@ void CFrontEndUI::SGBASupportFrame::FinishedLoading() x2c_tablegroup_fusionsuit->SetIsActive(false); x2c_tablegroup_fusionsuit->SetIsVisible(false); x2c_tablegroup_fusionsuit->SetD1(false); - x2c_tablegroup_fusionsuit->SetUserSelection(g_GameState->SystemOptions().PlayerHasFusion()); + x2c_tablegroup_fusionsuit->SetUserSelection(g_GameState->SystemOptions().GetPlayerHasFusion()); SetTableColors(x28_tablegroup_options); SetTableColors(x2c_tablegroup_fusionsuit); diff --git a/Runtime/MP1/CMemoryCardDriver.cpp b/Runtime/MP1/CMemoryCardDriver.cpp index 5d99526b7..4f921b0cd 100644 --- a/Runtime/MP1/CMemoryCardDriver.cpp +++ b/Runtime/MP1/CMemoryCardDriver.cpp @@ -1,24 +1,130 @@ #include "CMemoryCardDriver.hpp" #include "MP1.hpp" +#include "CCRC32.hpp" namespace urde { namespace MP1 { -CMemoryCardDriver::SMemoryCardSlotInfo::SMemoryCardSlotInfo(CMemoryCardSys::EMemoryCardPort parentIdx, - const std::string& name) -: x0_cardPort(parentIdx), x14_name(name) -{} +using ECardResult = CMemoryCardSys::ECardResult; +using EMemoryCardPort = CMemoryCardSys::EMemoryCardPort; -CMemoryCardDriver::CMemoryCardDriver(CMemoryCardSys::EMemoryCardPort cardPort, ResId saveBanner, - ResId saveIcon0, ResId saveIcon1, bool flag) -: x0_cardPort(cardPort), x4_saveBanner(saveBanner), - x8_saveIcon0(saveIcon0), xc_saveIcon1(saveIcon1), x19d_flag(flag) +ECardResult CMemoryCardDriver::SFileInfo::Close() { - x100_mcSlotInfos.reserve(2); - x100_mcSlotInfos.emplace_back(0, SMemoryCardSlotInfo(x0_cardPort, "MetroidPrime A")); - x100_mcSlotInfos.emplace_back(0, SMemoryCardSlotInfo(x0_cardPort, "MetroidPrime B")); + auto backup = GetFileCardPort(); + ECardResult result = ECardResult::CARD_RESULT_READY; + //result = CARDClose(backup); + x0_fileInfo.x0_cardPort = backup; + return result; +} + +ECardResult CMemoryCardDriver::SFileInfo::TryFileRead() +{ + ECardResult res = CMemoryCardSys::GetResultCode(GetFileCardPort()); + if (res == ECardResult::CARD_RESULT_READY) + res = FileRead(); + return res; +} + +ECardResult CMemoryCardDriver::SFileInfo::FileRead() +{ + x34_saveData.clear(); + u32 existingCrc = hecl::SBig(*reinterpret_cast(x24_saveFileData.data())); + u32 newCrc = CCRC32::Calculate(x24_saveFileData.data() + 4, x24_saveFileData.size() - 4); + if (existingCrc == newCrc) + { + u32 saveDataOff; + ECardResult result = GetSaveDataOffset(saveDataOff); + if (result != ECardResult::CARD_RESULT_READY) + { + x24_saveFileData.clear(); + return result; + } + + u32 saveSize = x24_saveFileData.size() - saveDataOff; + x34_saveData.resize(saveSize); + memmove(x34_saveData.data(), x24_saveFileData.data() + saveDataOff, saveSize); + x24_saveFileData.clear(); + return ECardResult::CARD_RESULT_READY; + } + else + { + x24_saveFileData.clear(); + return ECardResult::CARD_RESULT_CRC_MISMATCH; + } +} + +ECardResult CMemoryCardDriver::SFileInfo::GetSaveDataOffset(u32& offOut) +{ + CMemoryCardSys::CARDStat stat = {}; + ECardResult result = CMemoryCardSys::GetStatus(GetFileCardPort(), GetFileNo(), stat); + if (result != ECardResult::CARD_RESULT_READY) + { + offOut = -1; + return result; + } + + offOut = 4; + offOut += 64; + switch (stat.GetBannerFormat()) + { + case 1: + offOut += 3584; + break; + case 2: + offOut += 6144; + break; + default: break; + } + + int idx = 0; + bool paletteIcon = false; + while (u32 fmt = stat.GetIconFormat(idx)) + { + if (fmt == 1) + { + paletteIcon = true; + offOut += 1024; + } + else + offOut += 2048; + ++idx; + } + + if (paletteIcon) + offOut += 512; + + return ECardResult::CARD_RESULT_READY; +} + +CMemoryCardDriver::SGameFileSlot::SGameFileSlot() +{ + InitializeFromGameState(); +} + +void CMemoryCardDriver::SGameFileSlot::InitializeFromGameState() +{ + CBitStreamWriter w(x0_saveBuffer, 940); + g_GameState->PutTo(w); + x944_fileInfo = CGameState::LoadGameFileState(x0_saveBuffer); +} + +CMemoryCardDriver::SFileInfo::SFileInfo(EMemoryCardPort port, + const std::string& name) +: x14_name(name) +{ + x0_fileInfo.x0_cardPort = port; +} + +CMemoryCardDriver::CMemoryCardDriver(EMemoryCardPort cardPort, ResId saveBanner, + ResId saveIcon0, ResId saveIcon1, bool mergePersistent) +: x0_cardPort(cardPort), x4_saveBanner(saveBanner), + x8_saveIcon0(saveIcon0), xc_saveIcon1(saveIcon1), x19d_doMergePersistent(mergePersistent) +{ + x100_mcFileInfos.reserve(2); + x100_mcFileInfos.emplace_back(0, SFileInfo(x0_cardPort, "MetroidPrime A")); + x100_mcFileInfos.emplace_back(0, SFileInfo(x0_cardPort, "MetroidPrime B")); } void CMemoryCardDriver::FinishedLoading() @@ -33,7 +139,7 @@ void CMemoryCardDriver::FinishedLoading2() auto result = CMemoryCardSys::CardProbe(x0_cardPort); switch (result.x0_error) { - case CMemoryCardSys::ECardResult::CARD_RESULT_READY: + case ECardResult::CARD_RESULT_READY: if (result.x8_sectorSize != 0x2000) { x10_state = EState::Twelve; @@ -43,9 +149,9 @@ void CMemoryCardDriver::FinishedLoading2() x10_state = EState::Five; MountCard(); break; - case CMemoryCardSys::ECardResult::CARD_RESULT_BUSY: + case ECardResult::CARD_RESULT_BUSY: break; - case CMemoryCardSys::ECardResult::CARD_RESULT_WRONGDEVICE: + case ECardResult::CARD_RESULT_WRONGDEVICE: x10_state = EState::Twelve; x14_error = EError::Four; break; @@ -65,20 +171,97 @@ void CMemoryCardDriver::MountCard() { x10_state = EState::TwentySix; x14_error = EError::Zero; - CMemoryCardSys::ECardResult result = CMemoryCardSys::MountCard(x0_cardPort); - if (result != CMemoryCardSys::ECardResult::CARD_RESULT_READY) - MountCardFailed(result); + ECardResult result = CMemoryCardSys::MountCard(x0_cardPort); + if (result != ECardResult::CARD_RESULT_READY) + Case26(result); } -void CMemoryCardDriver::MountCardFailed(CMemoryCardSys::ECardResult result) +void CMemoryCardDriver::CheckCard() +{ + ECardResult result = CMemoryCardSys::CheckCard(x0_cardPort); + if (result != ECardResult::CARD_RESULT_READY) + Case27(result); +} + +CGameState::GameFileStateInfo* CMemoryCardDriver::GetGameFileStateInfo(int idx) +{ + SGameFileSlot* slot = xe4_fileSlots[idx].get(); + if (!slot) + return nullptr; + return &slot->x944_fileInfo; +} + +CMemoryCardDriver::SSaveHeader CMemoryCardDriver::LoadSaveHeader(CMemoryInStream& in) +{ + SSaveHeader ret; + ret.x0_ = in.readUint32Big(); + for (int i=0 ; i<3 ; ++i) + ret.x4_[i] = in.readBool(); + return ret; +} + +std::unique_ptr CMemoryCardDriver::LoadSaveFile(CMemoryInStream& in) +{ + auto ret = std::make_unique(); + in.readBytesToBuf(ret->x0_saveBuffer, 940); + ret->x944_fileInfo = CGameState::LoadGameFileState(ret->x0_saveBuffer); + return ret; +} + +void CMemoryCardDriver::ReadFinished() +{ + CMemoryCardSys::CARDStat stat = {}; + SFileInfo& fileInfo = x100_mcFileInfos[x194_fileIdx].second; + if (CMemoryCardSys::GetStatus(x0_cardPort, fileInfo.GetFileNo(), stat) != ECardResult::CARD_RESULT_READY) + { + NoCardFound(); + return; + } + + x20_fileTime = stat.GetTime(); + CMemoryInStream r(fileInfo.x34_saveData.data(), 3004); + SSaveHeader header = LoadSaveHeader(r); + r.readBytesToBuf(x30_systemData, 174); + + for (int i=0 ; i<3 ; ++i) + xe4_fileSlots[i] = LoadSaveFile(r); + + if (x19d_doMergePersistent) + MergePersistentOptions(); +} + +void CMemoryCardDriver::MergePersistentOptions() +{ + CBitStreamReader r(x30_systemData, 174); + CPersistentOptions opts(r); + g_GameState->MergePersistentOptions(opts); +} + +void CMemoryCardDriver::DeleteFile() +{ + x14_error = EError::Zero; + x10_state = EState::Thirty; + SFileInfo& fileInfo = x100_mcFileInfos[bool(x194_fileIdx)].second; + ECardResult result = CMemoryCardSys::FastDeleteFile(x0_cardPort, fileInfo.GetFileNo()); + if (result != ECardResult::CARD_RESULT_READY) + Case30(result); +} + +void CMemoryCardDriver::CheckCardCapacity() +{ + if (x18_cardFreeBytes < 0x2000 || !x1c_cardFreeFiles) + x14_error = EError::Six; +} + +void CMemoryCardDriver::Case26(ECardResult result) { switch (result) { - case CMemoryCardSys::ECardResult::CARD_RESULT_READY: + case ECardResult::CARD_RESULT_READY: x10_state = EState::Six; CheckCard(); break; - case CMemoryCardSys::ECardResult::CARD_RESULT_BROKEN: + case ECardResult::CARD_RESULT_BROKEN: x10_state = EState::Six; x14_error = EError::One; CheckCard(); @@ -89,27 +272,19 @@ void CMemoryCardDriver::MountCardFailed(CMemoryCardSys::ECardResult result) } } -void CMemoryCardDriver::CheckCard() -{ - CMemoryCardSys::ECardResult result = CMemoryCardSys::CheckCard(x0_cardPort); - if (result != CMemoryCardSys::ECardResult::CARD_RESULT_READY) - CheckCardFailed(result); -} - -void CMemoryCardDriver::CheckCardFailed(CMemoryCardSys::ECardResult result) +void CMemoryCardDriver::Case27(ECardResult result) { switch (result) { - case CMemoryCardSys::ECardResult::CARD_RESULT_READY: + case ECardResult::CARD_RESULT_READY: x10_state = EState::Seven; if (!GetCardFreeBytes()) return; - if (CMemoryCardSys::GetSerialNo(x0_cardPort, x28_cardSerial) == - CMemoryCardSys::ECardResult::CARD_RESULT_READY) + if (CMemoryCardSys::GetSerialNo(x0_cardPort, x28_cardSerial) == ECardResult::CARD_RESULT_READY) return; NoCardFound(); break; - case CMemoryCardSys::ECardResult::CARD_RESULT_BROKEN: + case ECardResult::CARD_RESULT_BROKEN: x10_state = EState::Fourteen; x14_error = EError::One; break; @@ -118,32 +293,148 @@ void CMemoryCardDriver::CheckCardFailed(CMemoryCardSys::ECardResult result) } } +void CMemoryCardDriver::Case28(ECardResult result) +{ + if (result == ECardResult::CARD_RESULT_READY) + { + x100_mcFileInfos[x194_fileIdx].first = 1; + if (x100_mcFileInfos[bool(x194_fileIdx)].first == 3) + { + x10_state = EState::Seventeen; + GoTo28(); + } + else + { + x10_state = EState::Seven; + if (!GetCardFreeBytes()) + return; + GoTo17(); + } + } + else + HandleCardError(result, EState::Fifteen); +} + +void CMemoryCardDriver::Case29(ECardResult result) +{ + if (result == ECardResult::CARD_RESULT_READY) + { + auto& fileInfo = x100_mcFileInfos[x194_fileIdx]; + ECardResult readRes = fileInfo.second.TryFileRead(); + if (fileInfo.second.Close() != ECardResult::CARD_RESULT_READY) + { + NoCardFound(); + return; + } + + u32 fileIdx = bool(x194_fileIdx); + if (readRes == ECardResult::CARD_RESULT_READY) + { + x10_state = EState::One; + ReadFinished(); + u32 fileId = x100_mcFileInfos[fileIdx].first; + if (fileId == 1) + CheckCardCapacity(); + else + DeleteFile(); + return; + } + + if (readRes == ECardResult::CARD_RESULT_CRC_MISMATCH) + { + x100_mcFileInfos[x194_fileIdx].first = 3; + if (x100_mcFileInfos[fileIdx].first == 2) + { + x10_state = EState::Seven; + GoTo17(); + } + else + { + x10_state = EState::Seventeen; + x14_error = EError::Nine; + } + } + } + else + HandleCardError(result, EState::Seventeen); +} + +void CMemoryCardDriver::Case30(ECardResult result) +{ + +} + +void CMemoryCardDriver::Case31(ECardResult result) +{ + +} + +void CMemoryCardDriver::Case32(ECardResult result) +{ + +} + +void CMemoryCardDriver::Case33(ECardResult result) +{ + +} + +void CMemoryCardDriver::Case34(ECardResult result) +{ + +} + +void CMemoryCardDriver::Case35(ECardResult result) +{ + +} + +void CMemoryCardDriver::Case36(ECardResult result) +{ + +} + +void CMemoryCardDriver::Case37(ECardResult result) +{ + +} + +void CMemoryCardDriver::GoTo17() +{ + +} + +void CMemoryCardDriver::GoTo28() +{ + +} + bool CMemoryCardDriver::GetCardFreeBytes() { - CMemoryCardSys::ECardResult result = CMemoryCardSys::GetNumFreeBytes(x0_cardPort, - x18_cardFreeBytes, - x1c_cardFreeFiles); - if (result == CMemoryCardSys::ECardResult::CARD_RESULT_READY) + ECardResult result = CMemoryCardSys::GetNumFreeBytes(x0_cardPort, + x18_cardFreeBytes, + x1c_cardFreeFiles); + if (result == ECardResult::CARD_RESULT_READY) return true; NoCardFound(); return false; } -void CMemoryCardDriver::HandleCardError(CMemoryCardSys::ECardResult result, EState state) +void CMemoryCardDriver::HandleCardError(ECardResult result, EState state) { switch (result) { - case CMemoryCardSys::ECardResult::CARD_RESULT_WRONGDEVICE: + case ECardResult::CARD_RESULT_WRONGDEVICE: x10_state = state; x14_error = EError::Four; break; - case CMemoryCardSys::ECardResult::CARD_RESULT_NOCARD: + case ECardResult::CARD_RESULT_NOCARD: NoCardFound(); break; - case CMemoryCardSys::ECardResult::CARD_RESULT_IOERROR: + case ECardResult::CARD_RESULT_IOERROR: x10_state = state; x14_error = EError::Three; - case CMemoryCardSys::ECardResult::CARD_RESULT_ENCODING: + case ECardResult::CARD_RESULT_ENCODING: x10_state = state; x14_error = EError::Two; break; @@ -153,9 +444,9 @@ void CMemoryCardDriver::HandleCardError(CMemoryCardSys::ECardResult result, ESta void CMemoryCardDriver::Update() { - auto result = CMemoryCardSys::CardProbe(x0_cardPort); + CMemoryCardSys::CardProbeResults result = CMemoryCardSys::CardProbe(x0_cardPort); - if (result.x0_error == CMemoryCardSys::ECardResult::CARD_RESULT_NOCARD) + if (result.x0_error == ECardResult::CARD_RESULT_NOCARD) { if (x10_state != EState::Two) NoCardFound(); @@ -163,22 +454,64 @@ void CMemoryCardDriver::Update() return; } - switch (x10_state) + if (x10_state == EState::TwentyFive) { - case EState::TwentySix: - case EState::TwentySeven: - case EState::TwentyEight: - case EState::TwentyNine: - case EState::Thirty: - case EState::ThirtyOne: - case EState::ThirtyTwo: - case EState::ThirtyThree: - case EState::ThirtyFour: - case EState::ThirtyFive: - case EState::ThirtySix: - case EState::ThirtySeven: - default: break; + FinishedLoading2(); + static_cast(g_Main)->SetCardInserted(false); + return; } + + + ECardResult resultCode = g_MemoryCardSys->GetResultCode(x0_cardPort); + bool cardInserted = false; + + if (InCardInsertedRange(x10_state)) + { + cardInserted = true; + + switch (x10_state) + { + case EState::TwentySix: + Case26(resultCode); + break; + case EState::TwentySeven: + Case27(resultCode); + break; + case EState::TwentyEight: + Case28(resultCode); + break; + case EState::TwentyNine: + Case29(resultCode); + break; + case EState::Thirty: + Case30(resultCode); + break; + case EState::ThirtyOne: + Case31(resultCode); + break; + case EState::ThirtyTwo: + Case32(resultCode); + break; + case EState::ThirtyThree: + Case33(resultCode); + break; + case EState::ThirtyFour: + Case34(resultCode); + break; + case EState::ThirtyFive: + Case35(resultCode); + break; + case EState::ThirtySix: + Case36(resultCode); + break; + case EState::ThirtySeven: + Case37(resultCode); + break; + default: break; + } + } + + static_cast(g_Main)->SetCardInserted(cardInserted); } } diff --git a/Runtime/MP1/CMemoryCardDriver.hpp b/Runtime/MP1/CMemoryCardDriver.hpp index 7bca56235..f4f619778 100644 --- a/Runtime/MP1/CMemoryCardDriver.hpp +++ b/Runtime/MP1/CMemoryCardDriver.hpp @@ -2,6 +2,7 @@ #define __URDE_CMEMORYCARDDRIVER_HPP__ #include "CMemoryCardSys.hpp" +#include "CGameState.hpp" namespace urde { @@ -23,6 +24,8 @@ public: Twelve = 12, Thirteen = 13, Fourteen = 14, + Fifteen = 15, + Seventeen = 17, TwentyFive = 26, TwentySix = 26, TwentySeven = 27, @@ -53,14 +56,42 @@ public: }; private: - struct SMemoryCardSlotInfo + struct CARDFileInfo { CMemoryCardSys::EMemoryCardPort x0_cardPort; - u32 x4_ = -1; + int x4_fileNo = -1; + int x8_offset; + int xc_length; + u16 iBlock; + }; + + struct SFileInfo + { + CARDFileInfo x0_fileInfo; std::string x14_name; - std::vector x24_; - std::vector x34_; - SMemoryCardSlotInfo(CMemoryCardSys::EMemoryCardPort cardPort, const std::string& name); + std::vector x24_saveFileData; + std::vector x34_saveData; + SFileInfo(CMemoryCardSys::EMemoryCardPort cardPort, const std::string& name); + CMemoryCardSys::ECardResult Close(); + CMemoryCardSys::EMemoryCardPort GetFileCardPort() const { return x0_fileInfo.x0_cardPort; } + int GetFileNo() const { return x0_fileInfo.x4_fileNo; } + CMemoryCardSys::ECardResult TryFileRead(); + CMemoryCardSys::ECardResult FileRead(); + CMemoryCardSys::ECardResult GetSaveDataOffset(u32& offOut); + }; + + struct SSaveHeader + { + u32 x0_; + u32 x4_[3]; + }; + + struct SGameFileSlot + { + u8 x0_saveBuffer[940] = {}; + CGameState::GameFileStateInfo x944_fileInfo; + SGameFileSlot(); + void InitializeFromGameState(); }; CMemoryCardSys::EMemoryCardPort x0_cardPort; @@ -71,16 +102,16 @@ private: EError x14_error = EError::Zero; s32 x18_cardFreeBytes = 0; s32 x1c_cardFreeFiles = 0; - u32 x20_ = 0; + u32 x20_fileTime = 0; u32 x24_ = 0; u64 x28_cardSerial = 0; - u8 x30_[174]; - std::unique_ptr xe4_[3]; - std::vector> x100_mcSlotInfos; - u32 x194_ = -1; + u8 x30_systemData[174] = {}; + std::unique_ptr xe4_fileSlots[3]; + std::vector> x100_mcFileInfos; + u32 x194_fileIdx = -1; u32 x198_ = 0; bool x19c_ = false; - bool x19d_flag; + bool x19d_doMergePersistent; public: CMemoryCardDriver(CMemoryCardSys::EMemoryCardPort cardPort, ResId saveBanner, @@ -89,12 +120,51 @@ public: void FinishedLoading2(); void NoCardFound(); void MountCard(); - void MountCardFailed(CMemoryCardSys::ECardResult result); void CheckCard(); - void CheckCardFailed(CMemoryCardSys::ECardResult result); + + CGameState::GameFileStateInfo* GetGameFileStateInfo(int idx); + static SSaveHeader LoadSaveHeader(CMemoryInStream& in); + static std::unique_ptr LoadSaveFile(CMemoryInStream& in); + void ReadFinished(); + void MergePersistentOptions(); + void DeleteFile(); + void CheckCardCapacity(); + + void Case26(CMemoryCardSys::ECardResult result); + void Case27(CMemoryCardSys::ECardResult result); + void Case28(CMemoryCardSys::ECardResult result); + void Case29(CMemoryCardSys::ECardResult result); + void Case30(CMemoryCardSys::ECardResult result); + void Case31(CMemoryCardSys::ECardResult result); + void Case32(CMemoryCardSys::ECardResult result); + void Case33(CMemoryCardSys::ECardResult result); + void Case34(CMemoryCardSys::ECardResult result); + void Case35(CMemoryCardSys::ECardResult result); + void Case36(CMemoryCardSys::ECardResult result); + void Case37(CMemoryCardSys::ECardResult result); + + void GoTo17(); + void GoTo28(); + bool GetCardFreeBytes(); void HandleCardError(CMemoryCardSys::ECardResult result, EState state); void Update(); + + static bool InCardInsertedRange(EState v) + { + return v >= EState::TwentySix && v <= EState::ThirtySeven; + } + + static bool InRange2(EState v) + { + if (v < EState::TwentyFive) + return false; + if (v == EState::TwentySeven) + return false; + if (v == EState::TwentyNine) + return false; + return true; + } }; } diff --git a/Runtime/MP1/CSaveUI.cpp b/Runtime/MP1/CSaveUI.cpp index 3f012615c..5d9fa9933 100644 --- a/Runtime/MP1/CSaveUI.cpp +++ b/Runtime/MP1/CSaveUI.cpp @@ -57,22 +57,6 @@ bool CSaveUI::PumpLoad() return true; } -static bool InRange1(EState v) -{ - return v >= EState::TwentySix && v <= EState::ThirtySeven; -} - -static bool InRange2(EState v) -{ - if (v < EState::TwentyFive) - return false; - if (v == EState::TwentySeven) - return false; - if (v == EState::TwentyNine) - return false; - return true; -} - CSaveUI::UIType CSaveUI::SelectUIType() const { if (x6c_cardDriver->x10_state == EState::Two) @@ -87,9 +71,9 @@ CSaveUI::UIType CSaveUI::SelectUIType() const default: break; } - if (InRange1(x6c_cardDriver->x10_state)) + if (CMemoryCardDriver::InCardInsertedRange(x6c_cardDriver->x10_state)) { - if (!InRange2(x6c_cardDriver->x10_state)) + if (!CMemoryCardDriver::InRange2(x6c_cardDriver->x10_state)) return UIType::Two; return UIType::One; } @@ -156,7 +140,6 @@ void CSaveUI::ProcessUserInput(const CFinalInput& input) void CSaveUI::StartGame(int idx) { - } void CSaveUI::EraseGame(int idx) @@ -178,7 +161,7 @@ CSaveUI::CSaveUI(u32 instIdx, u32 a, u32 b) x38_strgMemoryCard = g_SimplePool->GetObj("STRG_MemoryCard"); x44_frmeGenericMenu = g_SimplePool->GetObj("FRME_GenericMenu"); - x6c_cardDriver = ConstructCardDriver(x0_instIdx / 32); + x6c_cardDriver = ConstructCardDriver(x0_instIdx); if (instIdx == 1) { diff --git a/Runtime/MP1/CSlideShow.cpp b/Runtime/MP1/CSlideShow.cpp index e7f86ea43..97f33a4da 100644 --- a/Runtime/MP1/CSlideShow.cpp +++ b/Runtime/MP1/CSlideShow.cpp @@ -174,9 +174,9 @@ u32 CSlideShow::SlideShowGalleryFlags() ret |= 1; if (g_GameState->SystemOptions().GetLogScanCount() == 100) ret |= 2; - if (g_GameState->SystemOptions().PlayerBeatHardMode()) + if (g_GameState->SystemOptions().GetPlayerBeatHardMode()) ret |= 4; - if (g_GameState->SystemOptions().AllItemsCollected()) + if (g_GameState->SystemOptions().GetAllItemsCollected()) ret |= 8; return ret; }