mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2026-04-27 21:01:37 +00:00
Initial mod loader implementation.
This commit is contained in:
parent
281535ad51
commit
1b9d40ac66
8 changed files with 382 additions and 3 deletions
|
|
@ -192,6 +192,10 @@ set(SWA_INSTALL_CXX_SOURCES
|
||||||
set(SWA_USER_CXX_SOURCES
|
set(SWA_USER_CXX_SOURCES
|
||||||
"user/achievement_data.cpp"
|
"user/achievement_data.cpp"
|
||||||
"user/config.cpp"
|
"user/config.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SWA_MOD_CXX_SOURCES
|
||||||
|
"mod/mod_loader.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SWA_THIRDPARTY_SOURCES
|
set(SWA_THIRDPARTY_SOURCES
|
||||||
|
|
@ -251,7 +255,8 @@ set(SWA_CXX_SOURCES
|
||||||
${SWA_PATCHES_CXX_SOURCES}
|
${SWA_PATCHES_CXX_SOURCES}
|
||||||
${SWA_UI_CXX_SOURCES}
|
${SWA_UI_CXX_SOURCES}
|
||||||
${SWA_INSTALL_CXX_SOURCES}
|
${SWA_INSTALL_CXX_SOURCES}
|
||||||
${SWA_USER_CXX_SOURCES}
|
${SWA_USER_CXX_SOURCES}
|
||||||
|
${SWA_MOD_CXX_SOURCES}
|
||||||
${SWA_THIRDPARTY_SOURCES}
|
${SWA_THIRDPARTY_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <kernel/function.h>
|
#include <kernel/function.h>
|
||||||
#include <cpu/guest_thread.h>
|
#include <cpu/guest_thread.h>
|
||||||
#include <os/logger.h>
|
#include <os/logger.h>
|
||||||
|
#include <mod/mod_loader.h>
|
||||||
|
|
||||||
struct FileHandle : KernelObject
|
struct FileHandle : KernelObject
|
||||||
{
|
{
|
||||||
|
|
@ -36,6 +37,15 @@ struct FindHandle : KernelObject
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::filesystem::path TransformOrRedirectPath(const char* path)
|
||||||
|
{
|
||||||
|
std::filesystem::path redirectedPath = ModLoader::RedirectPath(path);
|
||||||
|
if (redirectedPath.empty())
|
||||||
|
redirectedPath = std::u8string_view((const char8_t*)FileSystem::TransformPath(path));
|
||||||
|
|
||||||
|
return redirectedPath;
|
||||||
|
}
|
||||||
|
|
||||||
SWA_API FileHandle* XCreateFileA
|
SWA_API FileHandle* XCreateFileA
|
||||||
(
|
(
|
||||||
const char* lpFileName,
|
const char* lpFileName,
|
||||||
|
|
@ -50,7 +60,7 @@ SWA_API FileHandle* XCreateFileA
|
||||||
assert(((dwShareMode & ~(FILE_SHARE_READ | FILE_SHARE_WRITE)) == 0) && "Unknown share mode bits.");
|
assert(((dwShareMode & ~(FILE_SHARE_READ | FILE_SHARE_WRITE)) == 0) && "Unknown share mode bits.");
|
||||||
assert(((dwCreationDisposition & ~(CREATE_NEW | CREATE_ALWAYS)) == 0) && "Unknown creation disposition bits.");
|
assert(((dwCreationDisposition & ~(CREATE_NEW | CREATE_ALWAYS)) == 0) && "Unknown creation disposition bits.");
|
||||||
|
|
||||||
std::filesystem::path filePath = std::u8string_view((const char8_t*)(FileSystem::TransformPath(lpFileName)));
|
std::filesystem::path filePath = TransformOrRedirectPath(lpFileName);
|
||||||
std::fstream fileStream;
|
std::fstream fileStream;
|
||||||
std::ios::openmode fileOpenMode = std::ios::binary;
|
std::ios::openmode fileOpenMode = std::ios::binary;
|
||||||
if (dwDesiredAccess & (GENERIC_READ | FILE_READ_DATA))
|
if (dwDesiredAccess & (GENERIC_READ | FILE_READ_DATA))
|
||||||
|
|
@ -305,7 +315,7 @@ uint32_t XReadFileEx(FileHandle* hFile, void* lpBuffer, uint32_t nNumberOfBytesT
|
||||||
|
|
||||||
uint32_t XGetFileAttributesA(const char* lpFileName)
|
uint32_t XGetFileAttributesA(const char* lpFileName)
|
||||||
{
|
{
|
||||||
std::filesystem::path filePath(std::u8string_view((const char8_t*)(FileSystem::TransformPath(lpFileName))));
|
std::filesystem::path filePath = TransformOrRedirectPath(lpFileName);
|
||||||
if (std::filesystem::is_directory(filePath))
|
if (std::filesystem::is_directory(filePath))
|
||||||
return FILE_ATTRIBUTE_DIRECTORY;
|
return FILE_ATTRIBUTE_DIRECTORY;
|
||||||
else if (std::filesystem::is_regular_file(filePath))
|
else if (std::filesystem::is_regular_file(filePath))
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
#include <install/installer.h>
|
#include <install/installer.h>
|
||||||
#include <os/logger.h>
|
#include <os/logger.h>
|
||||||
#include <ui/installer_wizard.h>
|
#include <ui/installer_wizard.h>
|
||||||
|
#include <mod/mod_loader.h>
|
||||||
|
|
||||||
#define GAME_XEX_PATH "game:\\default.xex"
|
#define GAME_XEX_PATH "game:\\default.xex"
|
||||||
|
|
||||||
|
|
@ -80,6 +81,8 @@ void KiSystemStartup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModLoader::Init();
|
||||||
|
|
||||||
XAudioInitializeSystem();
|
XAudioInitializeSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
46
UnleashedRecomp/mod/ini_file.h
Normal file
46
UnleashedRecomp/mod/ini_file.h
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <xxHashMap.h>
|
||||||
|
|
||||||
|
class IniFile
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
struct Property
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Section
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
xxHashMap<Property> properties;
|
||||||
|
};
|
||||||
|
|
||||||
|
xxHashMap<Section> m_sections;
|
||||||
|
|
||||||
|
static size_t hashStr(const std::string_view& str);
|
||||||
|
|
||||||
|
static bool isWhitespace(char value);
|
||||||
|
static bool isNewLine(char value);
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool read(const std::filesystem::path& filePath);
|
||||||
|
|
||||||
|
std::string getString(const std::string_view& sectionName, const std::string_view& propertyName, std::string defaultValue) const;
|
||||||
|
|
||||||
|
bool getBool(const std::string_view& sectionName, const std::string_view& propertyName, bool defaultValue) const;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T get(const std::string_view& sectionName, const std::string_view& propertyName, T defaultValue) const;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void enumerate(const T& function) const;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void enumerate(const std::string_view& sectionName, const T& function) const;
|
||||||
|
|
||||||
|
bool contains(const std::string_view& sectionName) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "ini_file.inl"
|
||||||
205
UnleashedRecomp/mod/ini_file.inl
Normal file
205
UnleashedRecomp/mod/ini_file.inl
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
inline size_t IniFile::hashStr(const std::string_view& str)
|
||||||
|
{
|
||||||
|
return XXH3_64bits(str.data(), str.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IniFile::isWhitespace(char value)
|
||||||
|
{
|
||||||
|
return value == ' ' || value == '\t';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IniFile::isNewLine(char value)
|
||||||
|
{
|
||||||
|
return value == '\n' || value == '\r';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IniFile::read(const std::filesystem::path& filePath)
|
||||||
|
{
|
||||||
|
std::ifstream file(filePath, std::ios::binary);
|
||||||
|
if (!file.good())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
|
||||||
|
const size_t dataSize = static_cast<size_t>(file.tellg());
|
||||||
|
const auto data = std::make_unique<char[]>(dataSize + 1);
|
||||||
|
data[dataSize] = '\0';
|
||||||
|
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
file.read(data.get(), dataSize);
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
Section* section = nullptr;
|
||||||
|
const char* dataPtr = data.get();
|
||||||
|
|
||||||
|
while (dataPtr < data.get() + dataSize)
|
||||||
|
{
|
||||||
|
if (*dataPtr == ';')
|
||||||
|
{
|
||||||
|
while (*dataPtr != '\0' && !isNewLine(*dataPtr))
|
||||||
|
++dataPtr;
|
||||||
|
}
|
||||||
|
else if (*dataPtr == '[')
|
||||||
|
{
|
||||||
|
++dataPtr;
|
||||||
|
const char* endPtr = dataPtr;
|
||||||
|
while (*endPtr != '\0' && !isNewLine(*endPtr) && *endPtr != ']')
|
||||||
|
++endPtr;
|
||||||
|
|
||||||
|
if (*endPtr != ']')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string sectionName(dataPtr, endPtr - dataPtr);
|
||||||
|
section = &m_sections[hashStr(sectionName)];
|
||||||
|
section->name = std::move(sectionName);
|
||||||
|
|
||||||
|
dataPtr = endPtr + 1;
|
||||||
|
}
|
||||||
|
else if (!isWhitespace(*dataPtr) && !isNewLine(*dataPtr))
|
||||||
|
{
|
||||||
|
if (section == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const char* endPtr;
|
||||||
|
if (*dataPtr == '"')
|
||||||
|
{
|
||||||
|
++dataPtr;
|
||||||
|
endPtr = dataPtr;
|
||||||
|
|
||||||
|
while (*endPtr != '\0' && !isNewLine(*endPtr) && *endPtr != '"')
|
||||||
|
++endPtr;
|
||||||
|
|
||||||
|
if (*endPtr != '"')
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
endPtr = dataPtr;
|
||||||
|
|
||||||
|
while (*endPtr != '\0' && !isNewLine(*endPtr) && !isWhitespace(*endPtr) && *endPtr != '=')
|
||||||
|
++endPtr;
|
||||||
|
|
||||||
|
if (!isNewLine(*endPtr) && !isWhitespace(*endPtr) && *endPtr != '=')
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string propertyName(dataPtr, endPtr - dataPtr);
|
||||||
|
auto& property = section->properties[hashStr(propertyName)];
|
||||||
|
property.name = std::move(propertyName);
|
||||||
|
|
||||||
|
dataPtr = endPtr;
|
||||||
|
while (*dataPtr != '\0' && !isNewLine(*dataPtr) && *dataPtr != '=')
|
||||||
|
++dataPtr;
|
||||||
|
|
||||||
|
if (*dataPtr == '=')
|
||||||
|
{
|
||||||
|
++dataPtr;
|
||||||
|
|
||||||
|
while (*dataPtr != '\0' && isWhitespace(*dataPtr))
|
||||||
|
++dataPtr;
|
||||||
|
|
||||||
|
if (*dataPtr == '"')
|
||||||
|
{
|
||||||
|
++dataPtr;
|
||||||
|
endPtr = dataPtr;
|
||||||
|
|
||||||
|
while (*endPtr != '\0' && !isNewLine(*endPtr) && *endPtr != '"')
|
||||||
|
++endPtr;
|
||||||
|
|
||||||
|
if (*endPtr != '"')
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
endPtr = dataPtr;
|
||||||
|
|
||||||
|
while (*endPtr != '\0' && !isNewLine(*endPtr) && !isWhitespace(*endPtr))
|
||||||
|
++endPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
property.value = std::string(dataPtr, endPtr - dataPtr);
|
||||||
|
dataPtr = endPtr + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++dataPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string IniFile::getString(const std::string_view& sectionName, const std::string_view& propertyName, std::string defaultValue) const
|
||||||
|
{
|
||||||
|
const auto sectionPair = m_sections.find(hashStr(sectionName));
|
||||||
|
if (sectionPair != m_sections.end())
|
||||||
|
{
|
||||||
|
const auto propertyPair = sectionPair->second.properties.find(hashStr(propertyName));
|
||||||
|
if (propertyPair != sectionPair->second.properties.end())
|
||||||
|
return propertyPair->second.value;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IniFile::getBool(const std::string_view& sectionName, const std::string_view& propertyName, bool defaultValue) const
|
||||||
|
{
|
||||||
|
const auto sectionPair = m_sections.find(hashStr(sectionName));
|
||||||
|
if (sectionPair != m_sections.end())
|
||||||
|
{
|
||||||
|
const auto propertyPair = sectionPair->second.properties.find(hashStr(propertyName));
|
||||||
|
if (propertyPair != sectionPair->second.properties.end() && !propertyPair->second.value.empty())
|
||||||
|
{
|
||||||
|
const char firstChar = propertyPair->second.value[0];
|
||||||
|
return firstChar == 't' || firstChar == 'T' || firstChar == 'y' || firstChar == 'Y' || firstChar == '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IniFile::contains(const std::string_view& sectionName) const
|
||||||
|
{
|
||||||
|
return m_sections.contains(hashStr(sectionName));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T IniFile::get(const std::string_view& sectionName, const std::string_view& propertyName, T defaultValue) const
|
||||||
|
{
|
||||||
|
const auto sectionPair = m_sections.find(hashStr(sectionName));
|
||||||
|
if (sectionPair != m_sections.end())
|
||||||
|
{
|
||||||
|
const auto propertyPair = sectionPair->second.properties.find(hashStr(propertyName));
|
||||||
|
if (propertyPair != sectionPair->second.properties.end())
|
||||||
|
{
|
||||||
|
T value{};
|
||||||
|
const auto result = std::from_chars(propertyPair->second.value.data(),
|
||||||
|
propertyPair->second.value.data() + propertyPair->second.value.size(), value);
|
||||||
|
|
||||||
|
if (result.ec == std::errc{})
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline void IniFile::enumerate(const T& function) const
|
||||||
|
{
|
||||||
|
for (const auto& [_, section] : m_sections)
|
||||||
|
{
|
||||||
|
for (auto& property : section.properties)
|
||||||
|
function(section.name, property.second.name, property.second.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void IniFile::enumerate(const std::string_view& sectionName, const T& function) const
|
||||||
|
{
|
||||||
|
const auto sectionPair = m_sections.find(hashStr(sectionName));
|
||||||
|
if (sectionPair != m_sections.end())
|
||||||
|
{
|
||||||
|
for (const auto& property : sectionPair->second.properties)
|
||||||
|
function(property.second.name, property.second.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
101
UnleashedRecomp/mod/mod_loader.cpp
Normal file
101
UnleashedRecomp/mod/mod_loader.cpp
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
#include "mod_loader.h"
|
||||||
|
#include "ini_file.h"
|
||||||
|
#include "../xxHashMap.h"
|
||||||
|
|
||||||
|
enum class ModType
|
||||||
|
{
|
||||||
|
HMM,
|
||||||
|
UMM
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mod
|
||||||
|
{
|
||||||
|
ModType type{};
|
||||||
|
std::vector<std::filesystem::path> includeDirs;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<Mod> g_mods;
|
||||||
|
|
||||||
|
std::filesystem::path ModLoader::RedirectPath(std::string_view path)
|
||||||
|
{
|
||||||
|
if (g_mods.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
thread_local xxHashMap<std::filesystem::path> pathCache;
|
||||||
|
|
||||||
|
size_t sepIndex = path.find(":\\");
|
||||||
|
if (sepIndex != std::string_view::npos)
|
||||||
|
path.remove_prefix(sepIndex + 2);
|
||||||
|
|
||||||
|
XXH64_hash_t hash = XXH3_64bits(path.data(), path.size());
|
||||||
|
auto findResult = pathCache.find(hash);
|
||||||
|
if (findResult != pathCache.end())
|
||||||
|
return findResult->second;
|
||||||
|
|
||||||
|
for (auto& mod : g_mods)
|
||||||
|
{
|
||||||
|
for (auto& includeDir : mod.includeDirs)
|
||||||
|
{
|
||||||
|
std::filesystem::path modPath = includeDir / path;
|
||||||
|
if (std::filesystem::exists(modPath))
|
||||||
|
return pathCache.emplace(hash, modPath).first->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathCache.emplace(hash, std::filesystem::path{}).first->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModLoader::Init()
|
||||||
|
{
|
||||||
|
IniFile configIni;
|
||||||
|
if (!configIni.read("cpkredir.ini"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string modsDbIniFilePathU8 = configIni.getString("CPKREDIR", "ModsDbIni", "");
|
||||||
|
if (modsDbIniFilePathU8.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
IniFile modsDbIni;
|
||||||
|
if (!modsDbIni.read(std::u8string_view((const char8_t*)modsDbIniFilePathU8.c_str())))
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t activeModCount = modsDbIni.get<size_t>("Main", "ActiveModCount", 0);
|
||||||
|
for (size_t i = 0; i < activeModCount; ++i)
|
||||||
|
{
|
||||||
|
std::string modId = modsDbIni.getString("Main", fmt::format("ActiveMod{}", i), "");
|
||||||
|
if (modId.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string modIniFilePathU8 = modsDbIni.getString("Mods", modId, "");
|
||||||
|
if (modIniFilePathU8.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::filesystem::path modIniFilePath(std::u8string_view((const char8_t*)modIniFilePathU8.c_str()));
|
||||||
|
|
||||||
|
IniFile modIni;
|
||||||
|
if (!modIni.read(modIniFilePath))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto modDirectoryPath = modIniFilePath.parent_path();
|
||||||
|
|
||||||
|
auto& mod = g_mods.emplace_back();
|
||||||
|
|
||||||
|
if (modIni.contains("Details") || modIni.contains("Filesystem")) // UMM
|
||||||
|
{
|
||||||
|
mod.type = ModType::UMM;
|
||||||
|
mod.includeDirs.emplace_back(std::move(modDirectoryPath));
|
||||||
|
}
|
||||||
|
else // HMM
|
||||||
|
{
|
||||||
|
mod.type = ModType::HMM;
|
||||||
|
|
||||||
|
size_t includeDirCount = modIni.get<size_t>("Main", "IncludeDirCount", 0);
|
||||||
|
for (size_t j = 0; j < includeDirCount; j++)
|
||||||
|
{
|
||||||
|
std::string includeDirU8 = modIni.getString("Main", fmt::format("IncludeDir{}", j), "");
|
||||||
|
if (!includeDirU8.empty())
|
||||||
|
mod.includeDirs.emplace_back(modDirectoryPath / std::u8string_view((const char8_t*)includeDirU8.c_str()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
UnleashedRecomp/mod/mod_loader.h
Normal file
8
UnleashedRecomp/mod/mod_loader.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct ModLoader
|
||||||
|
{
|
||||||
|
static std::filesystem::path RedirectPath(std::string_view path);
|
||||||
|
|
||||||
|
static void Init();
|
||||||
|
};
|
||||||
|
|
@ -49,6 +49,7 @@ using Microsoft::WRL::ComPtr;
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <semaphore>
|
#include <semaphore>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
#include <charconv>
|
||||||
|
|
||||||
#include "framework.h"
|
#include "framework.h"
|
||||||
#include "mutex.h"
|
#include "mutex.h"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue