diff --git a/SurrealEngine/Engine.cpp b/SurrealEngine/Engine.cpp index 158f136a..359c18c6 100644 --- a/SurrealEngine/Engine.cpp +++ b/SurrealEngine/Engine.cpp @@ -32,7 +32,8 @@ Engine::Engine(GameLaunchInfo launchinfo) : LaunchInfo(launchinfo) { engine = this; - packages = std::make_unique(LaunchInfo.folder, LaunchInfo.engineVersion, LaunchInfo.gameName); + //packages = std::make_unique(LaunchInfo.folder, LaunchInfo.engineVersion, LaunchInfo.gameName); + packages = std::make_unique(LaunchInfo); } Engine::~Engine() diff --git a/SurrealEngine/File.cpp b/SurrealEngine/File.cpp index 0560a14c..0795bbcb 100644 --- a/SurrealEngine/File.cpp +++ b/SurrealEngine/File.cpp @@ -373,6 +373,40 @@ std::string FilePath::remove_extension(const std::string &filename) return filename.substr(0, filename.length() - file.length() + pos); } +std::string FilePath::first_component(const std::string& path) +{ +#ifdef WIN32 + auto first_slash = path.find_first_of("/\\"); + if (first_slash != std::string::npos) + return path.substr(0, first_slash); + else + return path; +#else + auto first_slash = path.find_first_of('/'); + if (first_slash != std::string::npos) + return path.substr(0, first_slash); + else + return path; +#endif +} + +std::string FilePath::remove_first_component(const std::string& path) +{ +#ifdef WIN32 + auto first_slash = path.find_first_of("/\\"); + if (first_slash != std::string::npos) + return path.substr(first_slash + 1); + else + return std::string(); +#else + auto first_slash = path.find_first_of('/'); + if (first_slash != std::string::npos) + return path.substr(first_slash + 1); + else + return std::string(); +#endif +} + std::string FilePath::last_component(const std::string &path) { #ifdef WIN32 @@ -409,27 +443,50 @@ std::string FilePath::remove_last_component(const std::string &path) std::string FilePath::combine(const std::string &path1, const std::string &path2) { + auto path1_conv = convert_path_delimiters(path1); + auto path2_conv = convert_path_delimiters(path2); #ifdef WIN32 - if (path1.empty()) - return path2; - else if (path2.empty()) - return path1; - else if (path2.front() == '/' || path2.front() == '\\') - return path2; - else if (path1.back() != '/' && path1.back() != '\\') - return path1 + "\\" + path2; + if (path1_conv.empty()) + return path2_conv; + else if (path2_conv.empty()) + return path1_conv; + else if (path2_conv.front() == '/' || path2_conv.front() == '\\') + return path2_conv; + else if (path1_conv.back() != '/' && path1_conv.back() != '\\') + return path1_conv + "\\" + path2_conv; else - return path1 + path2; + return path1_conv + path2_conv; #else - if (path1.empty()) - return path2; - else if (path2.empty()) - return path1; - else if (path2.front() == '/') - return path2; - else if (path1.back() != '/') - return path1 + "/" + path2; + if (path1_conv.empty()) + return path2_conv; + else if (path2_conv.empty()) + return path1_conv; + else if (path2_conv.front() == '/') + return path2_conv; + else if (path1_conv.back() != '/') + return path1_conv + "/" + path2_conv; else - return path1 + path2; + return path1_conv + path2_conv; +#endif +} + +std::string FilePath::convert_path_delimiters(const std::string &path) +{ + std::string result = path; +#ifdef WIN32 + auto pos = result.find("/"); + while (pos != std::string::npos) + { + result.replace(result.find("/"), 1, "\\"); + pos = result.find("/"); + } +#else + auto pos = result.find("\\"); + while (pos != std::string::npos) + { + result.replace(result.find("\\"), 1, "/"); + pos = result.find("\\"); + } #endif + return result; } diff --git a/SurrealEngine/File.h b/SurrealEngine/File.h index 68d50d31..49b19690 100644 --- a/SurrealEngine/File.h +++ b/SurrealEngine/File.h @@ -70,7 +70,10 @@ class FilePath static bool has_extension(const std::string &filename, const char *extension); static std::string extension(const std::string &filename); static std::string remove_extension(const std::string &filename); + static std::string first_component(const std::string& path); + static std::string remove_first_component(const std::string& path); static std::string last_component(const std::string &path); static std::string remove_last_component(const std::string &path); static std::string combine(const std::string &path1, const std::string &path2); + static std::string convert_path_delimiters(const std::string &path); }; diff --git a/SurrealEngine/Package/IniFile.cpp b/SurrealEngine/Package/IniFile.cpp index 7d109e08..766eb21b 100644 --- a/SurrealEngine/Package/IniFile.cpp +++ b/SurrealEngine/Package/IniFile.cpp @@ -73,6 +73,24 @@ bool IniFile::ReadLine(const std::string& text, size_t& pos, std::string& line) return true; } +std::vector IniFile::GetKeys(NameString sectionName) const +{ + std::vector result; + + auto itSection = sections.find(sectionName); + if (itSection == sections.end()) + return {}; + + const auto& values = itSection->second; + + for (auto& key : values) + { + result.push_back(key.first); + } + + return result; +} + std::string IniFile::GetValue(NameString sectionName, NameString keyName) const { auto itSection = sections.find(sectionName); diff --git a/SurrealEngine/Package/IniFile.h b/SurrealEngine/Package/IniFile.h index f5f5b59a..96a6bd0a 100644 --- a/SurrealEngine/Package/IniFile.h +++ b/SurrealEngine/Package/IniFile.h @@ -8,6 +8,7 @@ class IniFile IniFile() = default; IniFile(const std::string& filename); + std::vector GetKeys(NameString sectionName) const; std::string GetValue(NameString sectionName, NameString keyName) const; std::vector GetValues(NameString sectionName, NameString keyName) const; diff --git a/SurrealEngine/Package/PackageManager.cpp b/SurrealEngine/Package/PackageManager.cpp index ed983300..76289929 100644 --- a/SurrealEngine/Package/PackageManager.cpp +++ b/SurrealEngine/Package/PackageManager.cpp @@ -36,7 +36,7 @@ #include "Native/NScriptedPawn.h" #include "Native/NPlayerPawnExt.h" -PackageManager::PackageManager(const std::string& basepath, int engineVersion, const std::string& gameName) : basepath(basepath), engineVersion(engineVersion), gameName(gameName) +PackageManager::PackageManager(const GameLaunchInfo& launchInfo) : launchInfo(launchInfo) { NActor::RegisterFunctions(); NCanvas::RegisterFunctions(); @@ -57,7 +57,7 @@ PackageManager::PackageManager(const std::string& basepath, int engineVersion, c NInternetLink::RegisterFunctions(); NTcpLink::RegisterFunctions(); NUdpLink::RegisterFunctions(); - if (gameName == "DeusEx") + if (IsDeusEx()) { NDebugInfo::RegisterFunctions(); NDeusExDecoration::RegisterFunctions(); @@ -71,17 +71,9 @@ PackageManager::PackageManager(const std::string& basepath, int engineVersion, c } LoadIntFiles(); + LoadPackageRemaps(); ScanForMaps(); - - // TODO: parse game ini for this info - ScanFolder("Maps", "*.unr"); - ScanFolder("Maps", "*.dx"); // Deus Ex - if (IsUnreal1()) - ScanFolder("Maps/UPak", "*.unr"); - ScanFolder("Music", "*.umx"); - ScanFolder("Sounds", "*.uax"); - ScanFolder("System", "*.u"); - ScanFolder("Textures", "*.utx"); + ScanPaths(); InitPropertyOffsets(this); @@ -95,15 +87,23 @@ Package* PackageManager::GetPackage(const NameString& name) if (package) return package.get(); - auto it = packageFilenames.find(name); + // TODO: Is this how PackageRemap really works? + // There is no documentation of it anywhere it seems + NameString remapped_name = name; + + auto remap_it = packageRemaps.find(name.ToString()); + if (remap_it != packageRemaps.end()) + { + remapped_name = NameString(remap_it->second); + } + + auto it = packageFilenames.find(remapped_name); if (it != packageFilenames.end()) { - package = std::make_unique(this, name, it->second); + package = std::make_unique(this, remapped_name, it->second); } else { - if (name == "UnrealI") - return GetPackage("UnrealShare"); throw std::runtime_error("Could not find package " + name.ToString()); } @@ -129,19 +129,58 @@ void PackageManager::UnloadPackage(const NameString& name) void PackageManager::ScanForMaps() { - std::string packagedir = FilePath::combine(basepath, "Maps"); + std::string packagedir = FilePath::combine(launchInfo.folder, "Maps"); for (std::string filename : Directory::files(FilePath::combine(packagedir, "*.unr"))) { maps.push_back(filename); } } -void PackageManager::ScanFolder(const std::string& name, const std::string& search) +void PackageManager::ScanFolder(const std::string& packagedir, const std::string& search) { - std::string packagedir = FilePath::combine(basepath, name); for (std::string filename : Directory::files(FilePath::combine(packagedir, search))) { - packageFilenames[NameString(FilePath::remove_extension(filename))] = FilePath::combine(packagedir, filename); + // Do not add the package again if it exists + // This is useful for example when you have HD textures installed in a different folder + // And you wish to load them instead of the original ones + auto it = packageFilenames.find(NameString(FilePath::remove_extension(filename))); + if (it == packageFilenames.end()) + packageFilenames[NameString(FilePath::remove_extension(filename))] = FilePath::combine(packagedir, filename); + } +} + +void PackageManager::ScanPaths() +{ + auto paths = GetIniValues("system", "Core.System", "Paths"); + + for (auto& current_path : paths) + { + // Get the filename + std::string filename = FilePath::last_component(current_path); + current_path = FilePath::remove_last_component(current_path); + + // Paths are relative from the System folder the executable (and the ini file) is in + // So we should start from there + auto resulting_root_path = FilePath::combine(launchInfo.folder, "System"); + + auto first_component = FilePath::first_component(current_path); + + while (first_component == ".." || first_component == ".") + { + if (first_component == "..") + { + // "Go one directory up" as many as the amount of ".."s in the current_path + resulting_root_path = FilePath::remove_last_component(resulting_root_path); + } + + current_path = FilePath::remove_first_component(current_path); + first_component = FilePath::first_component(current_path); + } + + // Combine everything + auto final_path = FilePath::combine(resulting_root_path, current_path); + + ScanFolder(final_path, filename); } } @@ -231,25 +270,67 @@ UClass* PackageManager::FindClass(const NameString& name) } } +std::vector PackageManager::GetIniKeysFromSection(NameString iniName, const NameString& sectionName) +{ + if (iniName == "system" || iniName == "System") + iniName = launchInfo.gameName; + else if (iniName == "user") + iniName = "User"; + + auto& ini = iniFiles[iniName]; + if (!ini) + { + ini = std::make_unique(FilePath::combine(launchInfo.folder, "System/" + iniName.ToString() + ".ini")); + } + + return ini->GetKeys(sectionName); +} + std::string PackageManager::GetIniValue(NameString iniName, const NameString& sectionName, const NameString& keyName) { if (iniName == "system" || iniName == "System") - iniName = gameName; + iniName = launchInfo.gameName; else if (iniName == "user") iniName = "User"; auto& ini = iniFiles[iniName]; if (!ini) { - ini = std::make_unique(FilePath::combine(basepath, "System/" + iniName.ToString() + ".ini")); + ini = std::make_unique(FilePath::combine(launchInfo.folder, "System/" + iniName.ToString() + ".ini")); } return ini->GetValue(sectionName, keyName); } +std::vector PackageManager::GetIniValues(NameString iniName, const NameString& sectionName, const NameString& keyName) +{ + if (iniName == "system" || iniName == "System") + iniName = launchInfo.gameName; + else if (iniName == "user") + iniName = "User"; + + auto& ini = iniFiles[iniName]; + if (!ini) + { + ini = std::make_unique(FilePath::combine(launchInfo.folder, "System/" + iniName.ToString() + ".ini")); + } + + return ini->GetValues(sectionName, keyName); +} + +void PackageManager::LoadPackageRemaps() +{ + auto remap_keys = GetIniKeysFromSection("system", "PackageRemap"); + + for (auto& key : remap_keys) + { + packageRemaps[key.ToString()] = GetIniValue("system", "PackageRemap", key); + } +} + void PackageManager::LoadIntFiles() { - std::string systemdir = FilePath::combine(basepath, "System"); + std::string systemdir = FilePath::combine(launchInfo.folder, "System"); for (std::string filename : Directory::files(FilePath::combine(systemdir, "*.int"))) { try @@ -316,7 +397,7 @@ std::string PackageManager::Localize(NameString packageName, const NameString& s { try { - intFile = std::make_unique(FilePath::combine(basepath, "System/" + packageName.ToString() + ".int")); + intFile = std::make_unique(FilePath::combine(launchInfo.folder, "System/" + packageName.ToString() + ".int")); } catch (...) { diff --git a/SurrealEngine/Package/PackageManager.h b/SurrealEngine/Package/PackageManager.h index cd8855c1..a8a28299 100644 --- a/SurrealEngine/Package/PackageManager.h +++ b/SurrealEngine/Package/PackageManager.h @@ -2,6 +2,7 @@ #include "Package.h" #include "IniFile.h" +#include "GameFolder.h" #include class PackageStream; @@ -19,10 +20,17 @@ struct IntObject class PackageManager { public: - PackageManager(const std::string& basepath, int engineVersion, const std::string& gameName); + PackageManager(const GameLaunchInfo& launchInfo); - bool IsUnreal1() const { return engineVersion < 300; } - int GetEngineVersion() const { return engineVersion; } + bool IsUnreal1() const { return launchInfo.gameName == "Unreal"; } + bool IsUnreal1_226() const { return IsUnreal1() && launchInfo.engineVersion == 226; } + bool IsUnreal1_227() const { return IsUnreal1() && launchInfo.engineVersion == 227; } + bool IsUnrealTournament() const { return launchInfo.gameName == "UnrealTournament"; } + bool IsUnrealTournament_469() const { return IsUnrealTournament() && launchInfo.engineVersion == 469; } + bool IsDeusEx() const { return launchInfo.gameName == "DeusEx"; } + + int GetEngineVersion() const { return launchInfo.engineVersion; } + int GetEngineSubVersion() const { return launchInfo.engineSubVersion; } Package *GetPackage(const NameString& name); std::vector GetPackageNames() const; @@ -36,7 +44,9 @@ class PackageManager UClass* FindClass(const NameString& name); + std::vector GetIniKeysFromSection(NameString iniName, const NameString& sectionName); std::string GetIniValue(NameString iniName, const NameString& sectionName, const NameString& keyName); + std::vector GetIniValues(NameString iniName, const NameString& sectionName, const NameString& keyName); std::string Localize(NameString packageName, const NameString& sectionName, const NameString& keyName); std::vector& GetIntObjects(const NameString& metaclass); @@ -44,22 +54,24 @@ class PackageManager private: void LoadIntFiles(); + void LoadPackageRemaps(); std::map ParseIntPublicValue(const std::string& value); void ScanForMaps(); - void ScanFolder(const std::string& name, const std::string& search); + void ScanFolder(const std::string& packagedir, const std::string& search); + void ScanPaths(); void DelayLoadNow(); std::vector delayLoads; int delayLoadActive = 0; - std::string basepath; std::map packageFilenames; std::map> packages; std::map> iniFiles; std::map> intFiles; + std::map packageRemaps; std::map> IntObjects; @@ -73,8 +85,7 @@ class PackageManager std::list openStreams; - std::string gameName; - int engineVersion = 436; + GameLaunchInfo launchInfo; friend class Package; friend struct SetDelayLoadActive; diff --git a/SurrealEngine/UObject/PropertyOffsets.cpp b/SurrealEngine/UObject/PropertyOffsets.cpp index e4a12bab..051a6c45 100644 --- a/SurrealEngine/UObject/PropertyOffsets.cpp +++ b/SurrealEngine/UObject/PropertyOffsets.cpp @@ -2428,10 +2428,14 @@ static void InitPropertyOffsets_TcpLink(PackageManager* packages) memset(&PropOffsets_TcpLink, 0xff, sizeof(PropOffsets_TcpLink)); return; } - PropOffsets_TcpLink.AcceptClass = cls->GetProperty("AcceptClass")->DataOffset; + + // AcceptClass and SendFIFO don't seem to exist in Unreal Gold 226 for some reason? + if (!packages->IsUnreal1_226()) + PropOffsets_TcpLink.AcceptClass = cls->GetProperty("AcceptClass")->DataOffset; PropOffsets_TcpLink.LinkState = cls->GetProperty("LinkState")->DataOffset; PropOffsets_TcpLink.RemoteAddr = cls->GetProperty("RemoteAddr")->DataOffset; - PropOffsets_TcpLink.SendFIFO = cls->GetProperty("SendFIFO")->DataOffset; + if (!packages->IsUnreal1_226()) + PropOffsets_TcpLink.SendFIFO = cls->GetProperty("SendFIFO")->DataOffset; } void InitPropertyOffsets(PackageManager* packages)