mirror of https://github.com/zeldaret/botw.git
547 lines
15 KiB
C++
547 lines
15 KiB
C++
#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} {
|
|
resetFlags();
|
|
}
|
|
|
|
Manager::~Manager() {
|
|
if (auto* gdm = ksys::gdt::Manager::instance())
|
|
gdm->removeReinitCallback(mGdtReinitSlot);
|
|
|
|
mAocMainFieldPackPrefix.deregister();
|
|
mAocPackPrefix.deregister();
|
|
mVersionFileDevPrefix.deregister();
|
|
|
|
if (mFileDevice) {
|
|
sead::FileDeviceMgr::instance()->unmount("aoc");
|
|
ksys::util::safeDelete(mFileDevice);
|
|
}
|
|
|
|
#ifdef NNSDK
|
|
if (mAocFsCache) {
|
|
nn::fs::Unmount("aoc");
|
|
ksys::util::safeDelete(mAocFsCache);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Manager::resetFlags() {
|
|
mFlagAocVerAtLastPlay = ksys::gdt::InvalidHandle;
|
|
mFlagLatestAocVerPlayed = ksys::gdt::InvalidHandle;
|
|
mFlagHasAocVer1 = ksys::gdt::InvalidHandle;
|
|
mFlagHasAocVer2 = ksys::gdt::InvalidHandle;
|
|
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::setGameDataFlags() const {
|
|
ksys::gdt::Manager::instance()->setS32(mVersion, mFlagAocVerAtLastPlay);
|
|
|
|
s32 latest_ver;
|
|
if (ksys::gdt::Manager::instance()->getS32(mFlagLatestAocVerPlayed, &latest_ver)) {
|
|
if (u32(latest_ver) < mVersion)
|
|
ksys::gdt::Manager::instance()->setS32(mVersion, mFlagLatestAocVerPlayed);
|
|
}
|
|
|
|
const auto ver = mVersion;
|
|
ksys::gdt::Manager::instance()->setBool(ver >= 0x100, mFlagHasAocVer1);
|
|
ksys::gdt::Manager::instance()->setBool(ver >= 0x200, mFlagHasAocVer2);
|
|
ksys::gdt::Manager::instance()->setBool(ver >= 0x300, mFlagHasAocVer3);
|
|
}
|
|
|
|
void Manager::onGdtReinit(ksys::gdt::Manager::ReinitEvent* event) {
|
|
reinitFlags();
|
|
}
|
|
|
|
} // namespace uking::aoc
|