diff options
| author | Léo Lam <leo@leolam.fr> | 2021-03-27 12:35:06 +0100 |
|---|---|---|
| committer | Léo Lam <leo@leolam.fr> | 2021-03-28 21:19:55 +0200 |
| commit | c3528b3911d2a5dc0d80879dc3ab92c31fae2b8b (patch) | |
| tree | c31325f188adff0b80ffdea583cf1901cfca7eed /src/Game/DLC/aocManager.cpp | |
| parent | 96157129f831572fb98ed584ef6f419c8f58f399 (diff) | |
Implement most of aocManager
And fix FileDevicePrefix's userdata param type
Diffstat (limited to 'src/Game/DLC/aocManager.cpp')
| -rw-r--r-- | src/Game/DLC/aocManager.cpp | 489 |
1 files changed, 487 insertions, 2 deletions
diff --git a/src/Game/DLC/aocManager.cpp b/src/Game/DLC/aocManager.cpp index 947bbba4..62e09255 100644 --- a/src/Game/DLC/aocManager.cpp +++ b/src/Game/DLC/aocManager.cpp @@ -1,13 +1,53 @@ #include "Game/DLC/aocManager.h" +#include <container/seadBuffer.h> #include <filedevice/seadFileDeviceMgr.h> +#include <prim/seadStringBuilder.h> +#include <resource/seadSharcArchiveRes.h> +#include "KingSystem/Resource/resLoadRequest.h" +#include "KingSystem/Resource/resResourceMgrTask.h" +#include "KingSystem/Utils/InitTimeInfo.h" #include "KingSystem/Utils/SafeDelete.h" #ifdef NNSDK +#include <filedevice/nin/seadNinAocFileDeviceNin.h> +#include <nn/aoc.h> #include <nn/fs.h> +#include <prim/seadStringUtil.h> #endif namespace uking::aoc { +namespace { + +struct DungeonInfo { + int number; + sead::SafeString flag; +}; + +ksys::util::InitConstants sInitConstants; +DungeonInfo sDungeonInfoData[] = { + {120, "Defeat_OneHitDungeon001"}, + {121, "BalladOfHeroGerudo_AppearDungeon03"}, + {122, "Defeat_OneHitDungeon002"}, + {123, "Defeat_OneHitDungeon003"}, + {124, "BalladOfHeroZora_AppearDungeon01"}, + {125, "BalladOfHeroZora_AppearDungeon02"}, + {126, "BalladOfHeroZora_AppearDungeon03"}, + {127, "BalladOfHeroRito_TargetHittingSuccess"}, + {128, "BalladOfHeroRito_AppearDungeon03"}, + {129, "BalladOfHeroRito_AppearDungeon02"}, + {130, "BalladOfHeroGoron_FirstKillGolemR"}, + {131, "BalladOfHeroGoron_AppearDungeon01"}, + {132, "BalladOfHeroGoron_AppearDungeon03"}, + {133, "BalladOfHeroGerudo_AppearDungeon02"}, + {134, "BalladOfHeroGerudo_AppearDungeon01"}, + {135, "Defeat_OneHitDungeon004"}, +}; +sead::Buffer<DungeonInfo> sDungeonInfo{sDungeonInfoData}; +constexpr int NumDungeons = 16; + +} // namespace + SEAD_SINGLETON_DISPOSER_IMPL(Manager) Manager::Manager() : mGdtReinitSlot{this, &Manager::onGdtReinit} { @@ -18,8 +58,8 @@ Manager::~Manager() { if (auto* gdm = ksys::gdt::Manager::instance()) gdm->removeReinitCallback(mGdtReinitSlot); - mFileDevicePrefix.deregister(); - mFileDevicePrefix2.deregister(); + mAocMainFieldPackPrefix.deregister(); + mAocPackPrefix.deregister(); mVersionFileDevPrefix.deregister(); if (mFileDevice) { @@ -43,4 +83,449 @@ void Manager::resetFlags() { mFlagHasAocVer3 = ksys::gdt::InvalidHandle; } +void Manager::init(sead::Heap* heap) { +#ifdef NNSDK + std::array<int, 2> dlc_info; + if (nn::aoc::ListAddOnContent(dlc_info.data(), 0, dlc_info.size()) == dlc_info.size() && + (dlc_info[0] == 1 || dlc_info[1] == 1)) { + size_t cache_size = 0; + nn::fs::QueryMountAddOnContentCacheSize(&cache_size, 1); + const size_t cache_size_ = cache_size; + mAocFsCache = static_cast<u8*>(heap->tryAlloc(cache_size_, sizeof(void*))); + + nn::fs::MountAddOnContent("aoc", 1, mAocFsCache, cache_size); + + mFileDevice = new (heap) sead::NinAocFileDevice("aoc"); + sead::FileDeviceMgr::instance()->mount(mFileDevice, "aoc"); + loadVersionFile(); + } +#endif +} + +void Manager::loadVersionFile() { + if (!mFileDevice) + return; + + mVersionFileDevPrefix.registerPrefix("Aoc/0010/", mFileDevice, false); + + ksys::res::LoadRequest req; + req.mRequester = "aocManager"; + req._26 = false; + req.mAocFileDevice = mFileDevice; + const sead::SafeString path = "System/AocVersion.txt"; + mVersionFile->file_handle.requestLoad(path, &req); +} + +void Manager::loadAocMainFieldPack(ksys::OverlayArena* arena) { + if (!hasAoc3()) + return; + + auto* device = mFileDevice; + if (!device) + return; + + ksys::res::LoadRequest req; + req.mRequester = "aocManager"; + req._8 = true; + req._26 = false; + req.mAocFileDevice = device; + req.mArena = arena; + mAocMainFieldPack.requestLoad("Pack/AocMainField.pack", &req); +} + +void Manager::registerAocMainFieldPack() { + mAocMainFieldPack.waitForReady(); + mAocMainFieldPack.parseResource(nullptr); + if (mAocMainFieldPack.isSuccess()) + mAocMainFieldPackPrefix.registerPrefix("Aoc/0010/", mAocMainFieldPack.getResource(), false); +} + +void Manager::unloadAocMainFieldPack() { + mAocMainFieldPackPrefix.deregister(); + mAocMainFieldPack.requestUnload(); +} + +sead::FileDevice* Manager::getFileDeviceForMapFile(const sead::SafeString& path) const { + if (!hasAoc2()) + return nullptr; + + auto* device = mFileDevice; + + if (aocPackHasFile(path)) + return nullptr; + + if (path.startsWith("Map/MainField/")) + return device; + + if (hasAoc3() && path.startsWith("Map/MainFieldDungeon/")) + return device; + + if (isAocFile(path, "Map/", "Map/AocField/", "Map/MainFieldDungeon/", "Map/CDungeon/")) + return device; + + return nullptr; +} + +bool Manager::getFileDeviceForMap(sead::FileDevice** p_file_device, ksys::res::Handle** p_handle, + const sead::SafeString& path) { + if (!hasAoc2()) + return false; + + auto* device = mFileDevice; + + if (path.startsWith("Map/MainField/")) { + if (mAocMainFieldPack.isSuccess()) + *p_handle = &mAocMainFieldPack; + else + *p_file_device = device; + return true; + } + + if (aocPackHasFile(path)) + return false; + + if (hasAoc3() && path.startsWith("Map/MainFieldDungeon/")) { + *p_file_device = device; + return true; + } + + if (isAocFile(path, "Map/", "Map/AocField/", "Map/MainFieldDungeon/", "Map/CDungeon/")) { + *p_file_device = device; + return true; + } + + return false; +} + +sead::FileDevice* Manager::getFileDeviceForStaticCompound(const sead::SafeString& path) const { + if (!hasAoc2()) + return nullptr; + + auto* device = mFileDevice; + + if (aocPackHasFile(path)) + return nullptr; + + if (isAocFile(path, "Physics/StaticCompound/", "Physics/StaticCompound/AocField/", + "Physics/StaticCompound/MainFieldDungeon/", "Physics/StaticCompound/CDungeon/")) { + return device; + } + + return nullptr; +} + +sead::FileDevice* Manager::getFileDeviceForTeraMesh(const sead::SafeString& path) const { + if (!hasAoc2()) + return nullptr; + + if (path.startsWith("Physics/TeraMeshRigidBody/AocField/")) + return mFileDevice; + + return nullptr; +} + +sead::FileDevice* Manager::getFileDeviceForNavMesh(const sead::SafeString& path) const { + if (!hasAoc2()) + return nullptr; + + auto* device = mFileDevice; + + if (aocPackHasFile(path)) + return nullptr; + + if (isAocFile(path, "NavMesh/", "NavMesh/AocField/", "NavMesh/MainFieldDungeon/", + "NavMesh/CDungeon/")) { + return device; + } + + return nullptr; +} + +sead::FileDevice* Manager::getFileDeviceForTerrain(const sead::SafeString& path) const { + if (!hasAoc2()) + return nullptr; + + if (path.startsWith("Terrain/A/AocField")) + return mFileDevice; + + return nullptr; +} + +sead::FileDevice* Manager::getFileDeviceForGame(const sead::SafeString& path) const { + if (!hasAoc2()) + return nullptr; + + if (path.startsWith("Game/AocField/")) + return mFileDevice; + + return nullptr; +} + +sead::FileDevice* Manager::getFileDeviceForUI(const sead::SafeString& path) const { + if (!hasAoc3()) + return nullptr; + + if (path.startsWith("UI/StaffRollDLC/")) + return mFileDevice; + + return nullptr; +} + +bool Manager::isAocFile(const sead::SafeString& path, const sead::SafeString& dir, + const sead::SafeString& dir_aoc_field, + const sead::SafeString& dir_main_field_dungeon, + const sead::SafeString& dir_cdungeon) const { + if (!path.startsWith(dir)) + return false; + + if (path.startsWith(dir_aoc_field)) + return true; + + if (path.startsWith(dir_main_field_dungeon)) { + const auto rel_path = path.getPart(dir_main_field_dungeon.calcLength()); + if (rel_path.startsWith("RemainsElectric")) + return false; + if (rel_path.startsWith("RemainsFire")) + return false; + if (rel_path.startsWith("RemainsWater")) + return false; + if (rel_path.startsWith("RemainsWind")) + return false; + return true; + } + + if (path.startsWith(dir_cdungeon)) { + const auto rel_path = path.getPart(dir_cdungeon.calcLength()); + return isAocDungeon(rel_path); + } + + return false; +} + +static bool isAocDungeonNumber(const sead::SafeString& number) { + int num; + sead::FixedSafeString<4> buffer; + buffer.copy(number, 3); + return sead::StringUtil::tryParseS32(&num, buffer, sead::StringUtil::CardinalNumber::Base10) && + num >= 120; +} + +bool Manager::isAocDungeon(const sead::SafeString& map_name) { + if (!map_name.startsWith("Dungeon")) + return true; + const auto number = map_name.getPart(sead::SafeString("Dungeon").calcLength()); + return isAocDungeonNumber(number); +} + +bool Manager::isAocMap(const sead::SafeString& map_type, const sead::SafeString& map_name) { + if (isAocField(map_type)) + return true; + + if (map_type == "MainFieldDungeon") { + if (map_name == "RemainsElectric") + return false; + if (map_name == "RemainsFire") + return false; + if (map_name == "RemainsWater") + return false; + if (map_name == "RemainsWind") + return false; + return true; + } + + if (map_type == "CDungeon") + return isAocDungeon(map_name); + + return false; +} + +bool Manager::isAocField(const sead::SafeString& map_type) { + return map_type == "AocField"; +} + +sead::FileDevice* Manager::getFileDeviceForDungeonPack(const sead::SafeString& path) const { + if (!hasAoc3()) + return nullptr; + + auto* device = mFileDevice; + + const sead::SafeString prefix = "Pack/"; + const auto prefix_len = prefix.calcLength(); + + if (!path.startsWith(prefix)) + return nullptr; + + const auto rel_path = path.getPart(prefix_len); + + if (rel_path.startsWith("Dungeon") && !isAocDungeon(rel_path)) + device = nullptr; + + return device; +} + +void Manager::registerAocPack(ksys::res::Handle* pack) { + mAocPack = pack; + if (pack) { + mAocPackPrefix.registerPrefix("Aoc/0010/", pack->getResource(), true); + } else { + mAocPackPrefix.deregister(); + } +} + +bool Manager::aocPackHasFile(const sead::SafeString& path) const { + if (!mAocPack) + return false; + + const auto* res = sead::DynamicCast<sead::SharcArchiveRes>(mAocPack->getResource()); + if (!res) + return false; + + sead::FixedStringBuilder<0x81> builder; + builder.copy(path.cstr()); + ksys::res::ResourceMgrTask::instance()->addSExtensionPrefix(builder); + return res->isExistFile(builder); +} + +bool Manager::changeMoviePath(sead::BufferedSafeString& path) const { + if (!hasAoc3()) + return false; + + const sead::SafeString prefix = "Movie/"; + const auto prefix_len = prefix.calcLength(); + if (!path.startsWith(prefix)) + return false; + + const auto rel_path = path.getPart(prefix_len); + if (!rel_path.startsWith("Demo6")) + return false; + + path.prepend("aoc://"); + return true; +} + +bool Manager::parseVersion() { + if (!mVersionFile->readVersion()) + return false; + + if (mVersionFile->string.isEmpty()) { + mVersion = 0; + return true; + } + + { + const int dot_index = mVersionFile->string.findIndex("."); + const int minor_index = dot_index + 1; + if (dot_index <= 0 || minor_index >= mVersionFile->string.calcLength()) { + mVersionFile->string.clear(); + mVersion = 0; + return true; + } + + u32 major, minor; + const auto parse = [&] { + sead::FixedSafeString<16> major_str; + major_str.copy(mVersionFile->string, dot_index); + + constexpr auto base = sead::StringUtil::CardinalNumber::Base10; + if (!sead::StringUtil::tryParseU32(&major, major_str, base)) + return false; + + const auto minor_str = mVersionFile->string.getPart(minor_index); + if (!sead::StringUtil::tryParseU32(&minor, minor_str, base)) + return false; + + return true; + }; + + if (!parse()) { + mVersionFile->string.clear(); + mVersion = 0; + return true; + } + + mVersion = (major << 8) + minor; + } + + checkVersion(); + return true; +} + +// NON_MATCHING: stack and duplicated branch -- volatile variables are painful +void Manager::checkVersion() { + if (mVersion == 0) + return; + + const auto fail = [this](VersionError error) { + mVersionFlags.setBit(error); + mVersion = 0; + }; + + if (mVersion < 0x300) { + fail(VersionError::TooOld); + } else if (mVersion >= 0x400) { + fail(VersionError::TooNew); + } + + mVersionFlags.isOnBit(VersionError(VersionError::TooOld)); + mVersionFlags.isOnBit(VersionError(VersionError::TooNew)); +} + +bool Manager::VersionFile::readVersion() { + if (!file_handle.isFlag2Set()) + return true; + + if (file_handle.isSuccess()) { + auto* res = sead::DynamicCast<sead::DirectResource>(file_handle.getResource()); + string.copy(reinterpret_cast<const char*>(res->getRawData()), res->getRawSize()); + } else { + if (!file_handle.checkLoadStatus()) + return false; + string.clear(); + } + + file_handle.requestUnload(); + return true; +} + +void Manager::initGameData() { + reinitFlags(); + ksys::gdt::Manager::instance()->addReinitCallback(mGdtReinitSlot); +} + +static const sead::SafeString& getDungeonFlag(int number) { + for (auto it = sDungeonInfo.begin(); it != sDungeonInfo.end(); ++it) { + if (it->number == number) + return it->flag; + } + return sead::SafeString::cEmptyString; +} + +void Manager::reinitFlags() { + mFlagAocVerAtLastPlay = ksys::gdt::Manager::instance()->getS32Handle("AoCVerAtLastPlay"); + mFlagLatestAocVerPlayed = ksys::gdt::Manager::instance()->getS32Handle("LatestAoCVerPlayed"); + mFlagHasAocVer1 = + ksys::gdt::Manager::instance()->getBoolHandle(GameDataFlag::text(GameDataFlag::HasAoCVer1)); + mFlagHasAocVer2 = + ksys::gdt::Manager::instance()->getBoolHandle(GameDataFlag::text(GameDataFlag::HasAoCVer2)); + mFlagHasAocVer3 = + ksys::gdt::Manager::instance()->getBoolHandle(GameDataFlag::text(GameDataFlag::HasAoCVer3)); + + for (int i = 0; i < mDLCPositions.size(); ++i) { + ksys::gdt::FlagHandle handle = ksys::gdt::InvalidHandle; + + if (i < NumDungeons) { + if (mDLCPositions[i].flag_handle == ksys::gdt::InvalidHandle) + continue; + + handle = ksys::gdt::Manager::instance()->getBoolHandle(getDungeonFlag(i + 120)); + } + + mDLCPositions[i].flag_handle = handle; + } +} + +void Manager::onGdtReinit(ksys::gdt::Manager::ReinitEvent* event) { + reinitFlags(); +} + } // namespace uking::aoc |
