diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..49bc779 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,43 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG REPORT] " +labels: bug +assignees: AmeliaCute, EltyDev + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +``` + + + +``` + + +**Expected behavior** +``` + + + +``` + +**Screenshots / Videos** +If applicable, add screenshots to help explain your problem. + +FLiAPI Version: `1.10` + +Mod list: +``` +- +- + +``` + + +**Additional context** +Add any other context about the problem here. diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b73f02..772243e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.16) project(FantasyLifeI-ModLoader LANGUAGES CXX C) set(VERSION 1.00) -set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") option(MLDEBUG "Enable ModLoader debug code" ON) add_subdirectory(DllProxy) @@ -26,8 +25,8 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() -target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_link_libraries(${PROJECT_NAME} PRIVATE user32 kernel32) file(GLOB_RECURSE PROJECT_SOURCE "src/*.cpp") -target_sources(${PROJECT_NAME} PUBLIC src/Lib/miniz.c ${PROJECT_SOURCE}) \ No newline at end of file +target_sources(${PROJECT_NAME} PRIVATE src/Lib/miniz.c ${PROJECT_SOURCE}) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..de38604 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,2 @@ +AmeliaCute +EltyDev diff --git a/include b/include index 9183e5a..d662472 160000 --- a/include +++ b/include @@ -1 +1 @@ -Subproject commit 9183e5ac82d7e1ecf46e69cdac4337c9449484bb +Subproject commit d662472a920a977557fc258744ac520ee73097b4 diff --git a/src/API/Skill/SkillData.cpp b/src/API/Skill/SkillData.cpp index b325204..b8691db 100644 --- a/src/API/Skill/SkillData.cpp +++ b/src/API/Skill/SkillData.cpp @@ -1,5 +1,4 @@ #include "API/Skill/SkillData.hpp" -#include "API/Engine/FName.hpp" #include "API/Common/Common.hpp" void SkillData::SetName(LANG lang, FString string) diff --git a/src/CommonData.cpp b/src/CommonData.cpp new file mode 100644 index 0000000..de5b6ac --- /dev/null +++ b/src/CommonData.cpp @@ -0,0 +1,6 @@ +#include "CommonData.hpp" + +#ifdef _WIN32 +int64_t CommonData::sizeImage = 0; +uintptr_t CommonData::baseAddress = 0; +#endif \ No newline at end of file diff --git a/src/GameData.cpp b/src/GameData.cpp index 0a7f7de..052d8d2 100644 --- a/src/GameData.cpp +++ b/src/GameData.cpp @@ -3,26 +3,25 @@ #include "API/Entities/Player/Player.hpp" #include "ModLoader.hpp" #include "Offset.h" +#include "CommonData.hpp" #ifdef _WIN32 #include #else #endif -GameData::GameData(uintptr_t baseAddress, uint32_t imageSize) : +GameData::GameData() : _staticDataManager(nullptr), _dynamicDataManager(nullptr) { ModLoader::logger->verbose("Initialize GameData"); - this->_baseAddress = baseAddress; - this->_imageSize = imageSize; } void GameData::init() { - this->_gObjects = reinterpret_cast(this->_baseAddress + GOBJECTS_OFFSET); - this->_gWorld = reinterpret_cast(this->_baseAddress + GWORLD_OFFSET); - this->_gNames = reinterpret_cast(this->_baseAddress + GNAMES_OFFSET); + this->_gObjects = reinterpret_cast(CommonData::GetBaseAddress() + GOBJECTS_OFFSET); + this->_gWorld = reinterpret_cast(CommonData::GetBaseAddress() + GWORLD_OFFSET); + this->_gNames = reinterpret_cast(CommonData::GetBaseAddress() + GNAMES_OFFSET); this->waitObject(&this->_gObjects); this->waitObject(&this->_staticDataManager, "StaticDataManager", 1); ModLoader::logger->verbose("Found StaticDataManager => ", std::hex, this->_staticDataManager); @@ -66,15 +65,6 @@ Player *GameData::getPlayer() { return _player.get(); } -uintptr_t GameData::getBaseAddress() { - return _baseAddress; -} - -uint32_t GameData::getImageSize() -{ - return _imageSize; -} - FUObjectArray *GameData::getGObjects() { return _gObjects; } diff --git a/src/GameRegistries.cpp b/src/GameRegistries.cpp new file mode 100644 index 0000000..12b8774 --- /dev/null +++ b/src/GameRegistries.cpp @@ -0,0 +1,132 @@ +#include "GameRegistries.hpp" +#include "GameData.hpp" +#include "ModLoader.hpp" +#include "SDK/DP1Project.h" + +// Initialize merged registries +std::shared_ptr> GameRegistries::BATTLE_COMMAND_NOUN = nullptr; +std::shared_ptr> GameRegistries::PLANT_DUNGEON_NOUN = nullptr; +std::shared_ptr> GameRegistries::ITEM_NOUN = nullptr; +std::shared_ptr> GameRegistries::LIFE_NOUN = nullptr; +std::shared_ptr> GameRegistries::SKILL_NOUN = nullptr; +std::shared_ptr> GameRegistries::QUEST_REQUEST_NOUN = nullptr; +std::shared_ptr> GameRegistries::QUEST_TITLE_NOUN = nullptr; +std::shared_ptr> GameRegistries::MAP_NOUN = nullptr; +std::shared_ptr> GameRegistries::MENU_NOUN = nullptr; +std::shared_ptr> GameRegistries::CHARA_NOUN = nullptr; +std::shared_ptr> GameRegistries::SYSTEM_NOUN = nullptr; +std::shared_ptr> GameRegistries::ITEM_MATERIAL = nullptr; + +// Game source IDs +uint16_t GameRegistries::_battleCommandNounSourceId = 0; +uint16_t GameRegistries::_plantDungeonNounSourceId = 0; +uint16_t GameRegistries::_itemNounSourceId = 0; +uint16_t GameRegistries::_lifeNounSourceId = 0; +uint16_t GameRegistries::_skillNounSourceId = 0; +uint16_t GameRegistries::_questRequestNounSourceId = 0; +uint16_t GameRegistries::_questTitleNounSourceId = 0; +uint16_t GameRegistries::_mapNounSourceId = 0; +uint16_t GameRegistries::_menuNounSourceId = 0; +uint16_t GameRegistries::_charaNounSourceId = 0; +uint16_t GameRegistries::_systemNounSourceId = 0; +uint16_t GameRegistries::_itemMaterialSourceId = 0; + +void GameRegistries::init(GameData* gameData) +{ + ModLoader::logger->verbose("Initialize Game Registries"); + auto* sdm = gameData->getStaticDataManager(); + + bindNounPRE(sdm); + bindItem(sdm); +} + +void GameRegistries::bindNounPRE(UStaticDataManager* sdm) +{ + ModLoader::gameData->waitObject(&sdm->m_BattleCommandNameNoun); + BATTLE_COMMAND_NOUN = std::make_shared>(); + auto battleCommandSource = std::make_shared>("BattleCommandNoun", 0, &sdm->m_BattleCommandNameNoun->m_dataMap); + battleCommandSource->BuildIndex(); + _battleCommandNounSourceId = BATTLE_COMMAND_NOUN->AddSource(battleCommandSource); + ModLoader::logger->verbose("Binded Battle Command Noun Registry!"); + + ModLoader::gameData->waitObject(&sdm->m_PlantDungeonText_Noun); + PLANT_DUNGEON_NOUN = std::make_shared>(); + auto plantDungeonSource = std::make_shared>("PlantDungeonNoun", 0, &sdm->m_PlantDungeonText_Noun->m_dataMap); + plantDungeonSource->BuildIndex(); + _plantDungeonNounSourceId = PLANT_DUNGEON_NOUN->AddSource(plantDungeonSource); + ModLoader::logger->verbose("Binded Plant Dungeon Noun Registry!"); + + ModLoader::gameData->waitObject(&sdm->m_ItemText_Noun); + ITEM_NOUN = std::make_shared>(); + auto itemNounSource = std::make_shared>("ItemNoun", 0, &sdm->m_ItemText_Noun->m_dataMap); + itemNounSource->BuildIndex(); + _itemNounSourceId = ITEM_NOUN->AddSource(itemNounSource); + ModLoader::logger->verbose("Binded Item Noun Registry!"); + + ModLoader::gameData->waitObject(&sdm->m_LifeText_Noun); + LIFE_NOUN = std::make_shared>(); + auto lifeNounSource = std::make_shared>("LifeNoun", 0, &sdm->m_LifeText_Noun->m_dataMap); + lifeNounSource->BuildIndex(); + _lifeNounSourceId = LIFE_NOUN->AddSource(lifeNounSource); + ModLoader::logger->verbose("Binded Life Noun Registry!"); + + ModLoader::gameData->waitObject(&sdm->m_SkillText); + SKILL_NOUN = std::make_shared>(); + auto skillNounSource = std::make_shared>("SkillNoun", 0, &sdm->m_SkillText->m_dataMap); + skillNounSource->BuildIndex(); + _skillNounSourceId = SKILL_NOUN->AddSource(skillNounSource); + ModLoader::logger->verbose("Binded Skill Noun Registry!"); + + ModLoader::gameData->waitObject(&sdm->m_QuestRequestMapText_Noun); + QUEST_REQUEST_NOUN = std::make_shared>(); + auto questRequestSource = std::make_shared>("QuestRequestNoun", 0, &sdm->m_QuestRequestMapText_Noun->m_dataMap); + questRequestSource->BuildIndex(); + _questRequestNounSourceId = QUEST_REQUEST_NOUN->AddSource(questRequestSource); + ModLoader::logger->verbose("Binded Quest Request Noun Registry!"); + + ModLoader::gameData->waitObject(&sdm->m_QuestTitleText); + QUEST_TITLE_NOUN = std::make_shared>(); + auto questTitleSource = std::make_shared>("QuestTitleNoun", 0, &sdm->m_QuestTitleText->m_dataMap); + questTitleSource->BuildIndex(); + _questTitleNounSourceId = QUEST_TITLE_NOUN->AddSource(questTitleSource); + ModLoader::logger->verbose("Binded Quest Title Noun Registry!"); + + ModLoader::gameData->waitObject(&sdm->m_MapText_Noun); + MAP_NOUN = std::make_shared>(); + auto mapNounSource = std::make_shared>("MapNoun", 0, &sdm->m_MapText_Noun->m_dataMap); + mapNounSource->BuildIndex(); + _mapNounSourceId = MAP_NOUN->AddSource(mapNounSource); + ModLoader::logger->verbose("Binded Map Noun Registry!"); + + ModLoader::gameData->waitObject(&sdm->m_MenuText_Noun); + MENU_NOUN = std::make_shared>(); + auto menuNounSource = std::make_shared>("MenuNoun", 0, &sdm->m_MenuText_Noun->m_dataMap); + menuNounSource->BuildIndex(); + _menuNounSourceId = MENU_NOUN->AddSource(menuNounSource); + ModLoader::logger->verbose("Binded Menu Noun Registry!"); + + ModLoader::gameData->waitObject(&sdm->m_CharaText_Noun); + CHARA_NOUN = std::make_shared>(); + auto charaNounSource = std::make_shared>("CharaNoun", 0, &sdm->m_CharaText_Noun->m_dataMap); + charaNounSource->BuildIndex(); + _charaNounSourceId = CHARA_NOUN->AddSource(charaNounSource); + ModLoader::logger->verbose("Binded Chara Noun Registry!"); + + ModLoader::gameData->waitObject(&sdm->m_SystemText_Noun); + SYSTEM_NOUN = std::make_shared>(); + auto systemNounSource = std::make_shared>("SystemNoun", 0, &sdm->m_SystemText_Noun->m_dataMap); + systemNounSource->BuildIndex(); + _systemNounSourceId = SYSTEM_NOUN->AddSource(systemNounSource); + ModLoader::logger->verbose("Binded System Noun Registry!"); +} + +void GameRegistries::bindItem(UStaticDataManager* sdm) +{ + ModLoader::gameData->waitObject(&sdm->m_ItemMaterialData); + ITEM_MATERIAL = std::make_shared>(); + auto itemMaterialSource = std::make_shared>("ItemMaterial", 0, &sdm->m_ItemMaterialData->m_dataMap); + + itemMaterialSource->BuildIndex(); + _itemMaterialSourceId = ITEM_MATERIAL->AddSource(itemMaterialSource); + ModLoader::logger->verbose("Binded Material Item Registry!"); +} \ No newline at end of file diff --git a/src/Hook/Pattern.cpp b/src/Hook/Pattern.cpp index 7d5dc9f..30d8147 100644 --- a/src/Hook/Pattern.cpp +++ b/src/Hook/Pattern.cpp @@ -1,19 +1,23 @@ #include "Hook/Pattern.hpp" +#include "CommonData.hpp" #include "GameData.hpp" #include "Hook/MemoryHelper.hpp" #include "ModLoader.hpp" Pattern::Pattern(uint8_t *pattern, const char *mask) : _pattern(pattern), _mask(mask) {} -size_t Pattern::getSize() { +size_t Pattern::getSize() +{ return strlen(_mask); } -uint8_t *Pattern::getPattern() { +uint8_t *Pattern::getPattern() +{ return _pattern; } -const char *Pattern::getMask() { +const char *Pattern::getMask() +{ return _mask; } @@ -24,5 +28,5 @@ uintptr_t Pattern::find(uintptr_t baseAddress, uint32_t range) uintptr_t Pattern::find(uintptr_t startOffset) { - return MemoryHelper::findPattern(ModLoader::gameData->getBaseAddress() + startOffset, ModLoader::gameData->getImageSize(), this->_pattern, this->_mask); + return MemoryHelper::findPattern(CommonData::GetBaseAddress() + startOffset, CommonData::GetSizeImage(), this->_pattern, this->_mask); } \ No newline at end of file diff --git a/src/Mod/ModEnvironnement.cpp b/src/Mod/ModEnvironnement.cpp index 1c09398..20c2da9 100644 --- a/src/Mod/ModEnvironnement.cpp +++ b/src/Mod/ModEnvironnement.cpp @@ -106,46 +106,26 @@ void ModEnvironnement::resolveOrder(std::vector mods) _modsList = std::move(sorted); } -bool ModEnvironnement::findModJsonInArchive(const std::filesystem::path& archivePath, std::string& foundPath) +bool ModEnvironnement::findFileInArchive(const std::filesystem::path& archivePath, const std::string& suffix, std::string& foundPath) { mz_zip_archive archive; memset(&archive, 0, sizeof(archive)); - if(!mz_zip_reader_init_file(&archive, archivePath.string().c_str(), 0)) return false; + if(!mz_zip_reader_init_file(&archive, archivePath.string().c_str(), 0)) + return false; mz_uint num = mz_zip_reader_get_num_files(&archive); + const size_t suffixLen = suffix.size(); + for(mz_uint i = 0; i < num; ++i) { mz_zip_archive_file_stat stat; if(!mz_zip_reader_file_stat(&archive, i, &stat)) continue; - std::string fname = stat.m_filename; - if(fname.size() >= 8 && fname.substr(fname.size() - 8) == "Mod.json") - { - foundPath = fname; - mz_zip_reader_end(&archive); - return true; - } - } - - mz_zip_reader_end(&archive); - return false; -} - -bool ModEnvironnement::findModLibInArchive(const std::filesystem::path& archivePath, const std::string& libName, std::string& foundPath) -{ - mz_zip_archive archive; - memset(&archive, 0, sizeof(archive)); - - if(!mz_zip_reader_init_file(&archive, archivePath.string().c_str(), 0)) return false; - mz_uint num = mz_zip_reader_get_num_files(&archive); - for (mz_uint i = 0; i < num; ++i) - { - mz_zip_archive_file_stat st; - if (!mz_zip_reader_file_stat(&archive, i, &st)) continue; - std::string fname = st.m_filename; - - if (fname.size() >= libName.size() && fname.compare(fname.size() - libName.size(), libName.size(), libName) == 0) + const char* fname = stat.m_filename; + size_t fnameLen = strlen(fname); + + if(fnameLen >= suffixLen && strncmp(fname + fnameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { foundPath = fname; mz_zip_reader_end(&archive); @@ -174,8 +154,6 @@ bool ModEnvironnement::readContentFromArchive(const std::filesystem::path& archi { mz_zip_archive_file_stat stat; if(!mz_zip_reader_file_stat(&archive, i, &stat)) continue; - //if(stat.m_filename == "") break; - std::string fname = stat.m_filename; if(fname.size() >= internalName.size() && fname.compare(fname.size() - internalName.size(), internalName.size(), internalName) == 0) { @@ -242,7 +220,7 @@ void ModEnvironnement::SetupEnvironnnement(std::string modDirs) if(entryPath.extension() == ".fliarchive") { std::string modJsonInternal; - if(!findModJsonInArchive(entryPath, modJsonInternal)) + if(!findFileInArchive(entryPath, "Mod.json", modJsonInternal)) { ModLoader::logger->warn("Invalid mod (no metadata) ", entryPath); continue; @@ -265,7 +243,7 @@ void ModEnvironnement::SetupEnvironnnement(std::string modDirs) std::string internalModPath; std::string modLibIdentifier = "CompiledBin.bin"; - if(!findModLibInArchive(entryPath, modLibIdentifier, internalModPath)) + if(!findFileInArchive(entryPath, modLibIdentifier, internalModPath)) { ModLoader::logger->warn("Invalid mod (no content) ", entryPath); continue; diff --git a/src/ModLoader.cpp b/src/ModLoader.cpp index 49ab4f7..8c75030 100644 --- a/src/ModLoader.cpp +++ b/src/ModLoader.cpp @@ -1,10 +1,12 @@ #include "ModLoader.hpp" #include "GameCache.hpp" #include "GameData.hpp" +#include "GameRegistries.hpp" #include "Hook/EventHandler.hpp" #include "Patcher/Patcher.hpp" #include "Patcher/Patches/EventHook.hpp" #include +#include "CommonData.hpp" GameData *ModLoader::gameData = nullptr; GameCache *ModLoader::gameCache = nullptr; @@ -17,18 +19,23 @@ void WINAPI ModLoader::init(MODULEINFO* moduleInfo) logger = new Logger("ModLoader"); logger->info("Mod loader has been started"); - // TODO: Remove this hacky sleep, but it's needed, maybe check if the memory is ready next time? - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + Patcher patcher; - logger->verbose("Dll module is loaded"); - uintptr_t baseAddress = (uintptr_t) GetModuleHandle(nullptr); - patcher.add(new EventHook(EventType::ClickEvent, 0x657DC32)); - patcher.applyPatches(baseAddress); - gameData = new GameData(baseAddress, moduleInfo->SizeOfImage); + CommonData::init(moduleInfo->SizeOfImage, (uintptr_t) GetModuleHandle(nullptr)); + // Patcher is crashing the game for unknown reasons - disabling for now, waiting @EltyDev to investigate + // patcher.add(new EventHook(EventType::ClickEvent, 0x657DC32)); + // patcher.applyPatches(baseAddress); + + gameData = new GameData(); gameData->init(); gameCache = new GameCache(); + + GameRegistries* gameReg = new GameRegistries(); + gameReg->init(gameData); + configManager = new ConfigManager("../../Content/Settings"); modEnvironnement = new ModEnvironnement("../../Content/Mods"); modEnvironnement->PreLoad(); @@ -38,6 +45,11 @@ void WINAPI ModLoader::init(MODULEINFO* moduleInfo) modEnvironnement->PostLoad(); logger->verbose("Mod loader initialization complete"); + + for(auto text : GameRegistries::LIFE_NOUN->GetAll()) + { + logger->verbose("Life Noun: ", text->ID.ToString()); + } } BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)