From b58085810aa06b5f74857abaee390e3a5a2319b2 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 6 Jan 2016 01:23:43 +0100 Subject: [PATCH] Some fastfile stuff. --- src/Components/Modules/AssetHandler.cpp | 8 +- src/Components/Modules/AssetHandler.hpp | 2 +- src/Components/Modules/Console.cpp | 2 +- src/Components/Modules/Dedicated.cpp | 35 +- src/Components/Modules/FastFiles.hpp | 30 -- src/Components/Modules/ZoneBuilder.cpp | 553 +++++++++++++++++++----- src/Components/Modules/ZoneBuilder.hpp | 79 ++-- src/Game/Functions.cpp | 23 + src/Game/Functions.hpp | 9 + src/Game/Structs.hpp | 88 +++- src/STDInclude.hpp | 4 + src/Utils/CSV.cpp | 124 ++++++ src/Utils/CSV.hpp | 20 + src/Utils/Stream.cpp | 249 +++++++++++ src/Utils/Stream.hpp | 114 +++++ src/Utils/Utils.cpp | 49 +++ src/Utils/Utils.hpp | 4 + 17 files changed, 1198 insertions(+), 195 deletions(-) create mode 100644 src/Utils/CSV.cpp create mode 100644 src/Utils/CSV.hpp create mode 100644 src/Utils/Stream.cpp create mode 100644 src/Utils/Stream.hpp diff --git a/src/Components/Modules/AssetHandler.cpp b/src/Components/Modules/AssetHandler.cpp index 51d79e86..1f6fc20d 100644 --- a/src/Components/Modules/AssetHandler.cpp +++ b/src/Components/Modules/AssetHandler.cpp @@ -129,13 +129,13 @@ namespace Components } } - void AssetHandler::OffsetToAlias(FastFiles::Offset* offset) + void AssetHandler::OffsetToAlias(Utils::Stream::Offset* offset) { - offset->fullPointer = *reinterpret_cast((*Game::g_streamBlocks)[offset->GetDecrementedStream()].data + offset->GetDecrementedPointer()); + offset->pointer = *reinterpret_cast((*Game::g_streamBlocks)[offset->GetUnpackedBlock()].data + offset->GetUnpackedOffset()); - if (AssetHandler::Relocations.find(offset->fullPointer) != AssetHandler::Relocations.end()) + if (AssetHandler::Relocations.find(offset->pointer) != AssetHandler::Relocations.end()) { - offset->fullPointer = AssetHandler::Relocations[offset->fullPointer]; + offset->pointer = AssetHandler::Relocations[offset->pointer]; } } diff --git a/src/Components/Modules/AssetHandler.hpp b/src/Components/Modules/AssetHandler.hpp index 42c71772..cdb82675 100644 --- a/src/Components/Modules/AssetHandler.hpp +++ b/src/Components/Modules/AssetHandler.hpp @@ -23,7 +23,7 @@ namespace Components static void FindAssetStub(); static void AddAssetStub(); - static void OffsetToAlias(FastFiles::Offset* offset); + static void OffsetToAlias(Utils::Stream::Offset* offset); static std::map TypeCallbacks; static std::vector RestrictCallbacks; diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index 2b3d7fc0..8092f675 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -24,7 +24,7 @@ namespace Components Console::Console() { // External console - if (Flags::HasFlag("console") || Dedicated::IsDedicated()) + if (Flags::HasFlag("console") || Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) { Utils::Hook::Nop(0x60BB58, 11); } diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index 28032782..373f565a 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -166,7 +166,7 @@ namespace Components // Map rotation Utils::Hook::Set(0x4152E8, Dedicated::MapRotate); - if (Dedicated::IsDedicated()) + if (Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) // Run zonebuilder as dedi :P { Dvar::Register("sv_lanOnly", false, Game::dvar_flag::DVAR_FLAG_NONE, "Don't register at the master server"); @@ -201,12 +201,6 @@ namespace Components Utils::Hook::Nop(0x64CF77, 5); // function detecting video card, causes Direct3DCreate9 to be called Utils::Hook::Nop(0x60BC52, 0x15); // recommended settings check - // Dedicated frame handler - Utils::Hook(0x4B0F81, Dedicated::FrameStub, HOOK_CALL).Install()->Quick(); - - // Post initialization point - Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).Install()->Quick(); - // isHost script call return 0 Utils::Hook::Set(0x5DEC04, 0); @@ -226,17 +220,26 @@ namespace Components // stop saving a config_mp.cfg Utils::Hook::Set(0x60B240, 0xC3); - // Heartbeats - Dedicated::OnFrame([] () - { - static int LastHeartbeat = 0; + // Dedicated frame handler + Utils::Hook(0x4B0F81, Dedicated::FrameStub, HOOK_CALL).Install()->Quick(); - if (Dvar::Var("sv_maxclients").Get() > 0 && !LastHeartbeat || (Game::Com_Milliseconds() - LastHeartbeat) > 120 * 1000) + if (!ZoneBuilder::IsEnabled()) + { + // Post initialization point + Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).Install()->Quick(); + + // Heartbeats + Dedicated::OnFrame([] () { - LastHeartbeat = Game::Com_Milliseconds(); - Dedicated::Heartbeat(); - } - }); + static int LastHeartbeat = 0; + + if (Dvar::Var("sv_maxclients").Get() > 0 && !LastHeartbeat || (Game::Com_Milliseconds() - LastHeartbeat) > 120 * 1000) + { + LastHeartbeat = Game::Com_Milliseconds(); + Dedicated::Heartbeat(); + } + }); + } } } diff --git a/src/Components/Modules/FastFiles.hpp b/src/Components/Modules/FastFiles.hpp index e9b77408..4cca26ab 100644 --- a/src/Components/Modules/FastFiles.hpp +++ b/src/Components/Modules/FastFiles.hpp @@ -3,36 +3,6 @@ namespace Components class FastFiles : public Component { public: - - class Offset - { - public: - union - { - struct - { - uint32_t pointer : 28; - int stream : 4; - }; - uint32_t fullValue; - void* fullPointer; - }; - - uint32_t GetDecrementedPointer() - { - Offset offset = *this; - offset.fullValue--; - return offset.pointer; - }; - - int GetDecrementedStream() - { - Offset offset = *this; - offset.fullValue--; - return offset.stream; - }; - }; - FastFiles(); ~FastFiles(); const char* GetName() { return "FastFiles"; }; diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index c1b4cee8..1563c36f 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -2,112 +2,443 @@ namespace Components { - std::string ZoneBuilder::Zone::Build() - { - static_assert(sizeof(XFileHeader) == 21, "Invalid XFileHeader structure!"); - static_assert(sizeof(XFile) == 40, "Invalid XFile structure!"); - std::string buffer; + ZoneBuilder::Zone::Zone(std::string name) : DataMap("zone_source/" + name + ".csv"), ZoneName(name), IndexStart(0), - XFileHeader header = { XFILE_MAGIC_UNSIGNED, XFILE_VERSION, 0, 0, 0 }; - - FILETIME fileTime; - GetSystemTimeAsFileTime(&fileTime); - - header.lowDateTime = fileTime.dwLowDateTime; - header.highDateTime = fileTime.dwHighDateTime; - - buffer.append((char*)&header, sizeof(header)); - - std::string zoneBuffer; - - XFile file; - ZeroMemory(&file, sizeof(file)); - zoneBuffer.append((char*)&file, sizeof(file)); - - // Fill zone - XAssetList list; - list.assetCount = 0; - list.assets = 0; - list.stringList.count = 0; - list.stringList.strings = 0; - - list.assetCount = 2; - - list.assets = (Game::XAsset*)0xFFFFFFFF; - - zoneBuffer.append((char*)&list, sizeof(list)); - - // Crappy assetlist entry - DWORD type = Game::XAssetType::ASSET_TYPE_LOCALIZE; - zoneBuffer.append((char*)&type, sizeof(type)); - zoneBuffer.append((char*)&list.assets, sizeof(list.assets)); // Hue - - type = Game::XAssetType::ASSET_TYPE_RAWFILE; - zoneBuffer.append((char*)&type, sizeof(type)); - zoneBuffer.append((char*)&list.assets, sizeof(list.assets)); // Hue - - // Localized entry - zoneBuffer.append((char*)&list.assets, sizeof(list.assets)); // Hue - zoneBuffer.append((char*)&list.assets, sizeof(list.assets)); // Hue - zoneBuffer.append("MENU_PENIS"); - zoneBuffer.append("\0", 1); - - zoneBuffer.append("PENIS"); - zoneBuffer.append("\0", 1); - - // Obligatory rawfile - struct Rawfile - { - const char* name; - int sizeCompressed; - int sizeUnCompressed; - char * compressedData; - }; - - char* _data = "map mp_rust"; - - Rawfile data; - data.name = (char*)0xFFFFFFFF; - data.compressedData = (char*)0xFFFFFFFF; - data.sizeUnCompressed = 0; - data.sizeCompressed = strlen(_data) + 1; - - zoneBuffer.append((char*)&data, sizeof(data)); - - zoneBuffer.append("zob.cfg"); - zoneBuffer.append("\0", 1); - - zoneBuffer.append(_data); - zoneBuffer.append("\0", 1); - - XFile* zone = (XFile*)zoneBuffer.data(); - ZeroMemory(zone, sizeof(XFile)); - - zone->size = zoneBuffer.size() - 40; - - zone->blockSize[3] = zoneBuffer.size() * 2; - zone->blockSize[0] = zoneBuffer.size() * 2; - -// for (int i = 0; i < 8; i++) -// { -// zone->blockSize[i] = zoneBuffer.size() * 2; -// } - - auto compressedData = Utils::Compression::ZLib::Compress(zoneBuffer); - buffer.append(compressedData); - - return buffer; - } - - ZoneBuilder::Zone::Zone(std::string zoneName) : ZoneName(zoneName) - { - - } + // Reserve 100MB by default. + // That's totally fine, as the dedi doesn't load images and therefore doesn't need much memory. + // That way we can be sure it won't need to reallocate memory. + // Side note: if you need a fastfile larger than 100MB, you're doing it wrong- + // Well, decompressed maps can get way larger than 100MB, so let's increase that. + Buffer(0xC800000) + {} ZoneBuilder::Zone::~Zone() { + //Assets::Patches::DeleteTemporaryLocalizeEntries(); + ZoneBuilder::Zone::Assets.clear(); + ZoneBuilder::Zone::ScriptStrings.clear(); + ZoneBuilder::Zone::ScriptStringMap.clear(); + } + + Utils::Stream* ZoneBuilder::Zone::GetBuffer() + { + return &Buffer; + } + + void ZoneBuilder::Zone::Zone::Build() + { + ZoneBuilder::Zone::LoadFastFiles(); + + Logger::Print("link..."); + if (!ZoneBuilder::Zone::LoadAssets()) return; + + Game::RawFile* rawFile = new Game::RawFile; + rawFile->name = "zob.cfg"; + rawFile->compressedData = "map mp_rust"; + rawFile->sizeUnCompressed = strlen(rawFile->compressedData) + 1; + rawFile->sizeCompressed = 0; + + Assets.push_back({ Game::XAssetType::ASSET_TYPE_RAWFILE, rawFile }); + + ZoneBuilder::Zone::AddBranding(); + + Logger::Print("save..."); + ZoneBuilder::Zone::SaveData(); + + Logger::Print("compress..."); + ZoneBuilder::Zone::WriteZone(); + } + + void ZoneBuilder::Zone::LoadFastFiles() + { + Logger::Print("Loading required FastFiles...\n"); + for (int i = 0; i < DataMap.GetRows(); i++) + { + if (DataMap.GetElementAt(i, 0) == "require") + { + std::string fastfile = DataMap.GetElementAt(i, 1); + + Logger::Print("Loading '%s'...\n", fastfile.c_str()); + + //if (!DB_IsZoneLoaded(fastfile.c_str())) + { + Game::XZoneInfo info; + info.name = fastfile.data(); + info.allocFlags = 0x01000000; + info.freeFlags = 0; + + Game::DB_LoadXAssets(&info, 1, true); + + //LoadFastFile(fastfile.c_str(), true); + } +// else +// { +// Logger::Print("Zone '%s' already loaded\n", fastfile.c_str()); +// } + } + } + } + + bool ZoneBuilder::Zone::LoadAssets() + { + for (int i = 0; i < DataMap.GetRows(); i++) + { + if (DataMap.GetElementAt(i, 0) != "require") + { + if (DataMap.GetColumns(i) > 2) + { +// if (DataMap.GetElementAt(i, 0) == "localize") +// { +// Assets::Patches::AddTemporaryLocalizeEntry(DataMap.GetElementAt(i, 1), DataMap.GetElementAt(i, 2)); +// } +// else + { + ZoneBuilder::Zone::RenameAsset(Game::DB_GetXAssetNameType(DataMap.GetElementAt(i, 0).data()), DataMap.GetElementAt(i, 1), DataMap.GetElementAt(i, 2)); + } + } + + if (!ZoneBuilder::Zone::LoadAsset(DataMap.GetElementAt(i, 0), DataMap.GetElementAt(i, 1))) + { + return false; + } + } + } + + return true; + } + + bool ZoneBuilder::Zone::LoadAsset(Game::XAssetType type, std::string name) + { + return ZoneBuilder::Zone::LoadAsset(Game::DB_GetXAssetTypeName(type), name); + } + + bool ZoneBuilder::Zone::LoadAsset(std::string typeName, std::string name) + { + Game::XAssetType type = Game::DB_GetXAssetNameType(typeName.data()); + + if (ZoneBuilder::Zone::FindAsset(type, name.c_str()) != -1) return true; + + if (type == Game::XAssetType::ASSET_TYPE_INVALID || type >= Game::XAssetType::ASSET_TYPE_COUNT) + { + Logger::Print("Error: Invalid asset type '%s'\n", typeName.data()); + return false; + } + + Game::XAssetHeader assetHeader = Game::DB_FindXAssetHeader(type, name.data()); + + if (!assetHeader.data) + { + Logger::Print("Error: Missing asset '%s' of type '%s'\n", name.data(), Game::DB_GetXAssetTypeName(type)); + return false; + } + + Game::XAsset asset; + asset.type = type; + asset.header = assetHeader; + + // Handle script strings and referenced assets + //Assets::Mark(&asset, this); + + ZoneBuilder::Zone::Assets.push_back(asset); + return true; + } + + int ZoneBuilder::Zone::FindAsset(Game::XAssetType type, const char* name) + { + for (unsigned int i = 0; i < ZoneBuilder::Zone::Assets.size(); i++) + { + Game::XAsset* asset = &ZoneBuilder::Zone::Assets[i]; + + if (asset->type != type) continue; + + const char* _name = DB_GetXAssetName(asset); + + if (_name && !strcmp(name, _name)) // Match case! + { + return i; + } + } + + return -1; + } + + Game::XAsset* ZoneBuilder::Zone::GetAsset(int index) + { + if ((uint32_t)index < ZoneBuilder::Zone::Assets.size()) + { + return &ZoneBuilder::Zone::Assets[index]; + } + + return nullptr; + } + + uint32_t ZoneBuilder::Zone::GetAssetOffset(int index) + { + Utils::Stream::Offset offset; + offset.block = Game::XFILE_BLOCK_VIRTUAL; + offset.offset = (ZoneBuilder::Zone::IndexStart + (index * sizeof(Game::XAsset)) + 4); + return offset.GetPackedOffset(); + } + + Game::XAssetHeader ZoneBuilder::Zone::RequireAsset(Game::XAssetType type, const char* name) + { + Game::XAssetHeader header; + header.data = (void*)-1; + + int assetIndex = ZoneBuilder::Zone::FindAsset(type, name); + + if (assetIndex != -1) + { + header.data = (void*)ZoneBuilder::Zone::GetAssetOffset(assetIndex); + } + else + { + Logger::Error("Missing required asset '%s' (%s). Export failed!", name, Game::DB_GetXAssetTypeName(type)); + } + + return header; + } + + void ZoneBuilder::Zone::WriteZone() + { + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + + Game::XFileHeader header = { XFILE_MAGIC_UNSIGNED, XFILE_VERSION, Game::XFileLanguage::XLANG_NONE, fileTime.dwHighDateTime, fileTime.dwLowDateTime }; + + std::string outBuffer; + outBuffer.append(reinterpret_cast(&header), sizeof(header)); + + std::string zoneBuffer = ZoneBuilder::Zone::Buffer.ToBuffer(); + zoneBuffer = Utils::Compression::ZLib::Compress(zoneBuffer); + outBuffer.append(zoneBuffer); + + std::string outFile = "zone/" + ZoneBuilder::Zone::ZoneName + ".ff"; + Utils::WriteFile(outFile, outBuffer); + + Logger::Print("done.\n"); + Logger::Print("Zone '%s' written with %d assets\n", outFile.c_str(), ZoneBuilder::Zone::Assets.size()); + } + + void ZoneBuilder::Zone::SaveData() + { + // Add header + Game::ZoneHeader zoneHeader = { 0 }; + zoneHeader.assetList.assetCount = Assets.size(); + zoneHeader.assetList.assets = (Game::XAsset *)-1; + + // Increment ScriptStrings count (for empty script string) if available + if (ZoneBuilder::Zone::ScriptStrings.size()) + { + zoneHeader.assetList.stringList.count = ZoneBuilder::Zone::ScriptStrings.size() + 1; + zoneHeader.assetList.stringList.strings = (const char**)-1; + } + + // Write header + ZoneBuilder::Zone::Buffer.Save(&zoneHeader, sizeof(Game::ZoneHeader)); + ZoneBuilder::Zone::Buffer.PushBlock(Game::XFILE_BLOCK_VIRTUAL); // Push main stream onto the stream stack + + // Write ScriptStrings, if available + if (ZoneBuilder::Zone::ScriptStrings.size()) + { + ZoneBuilder::Zone::Buffer.SaveNull(4); // Empty script string? + // This actually represents a NULL string, but as scriptString. + // So scriptString loading for NULL scriptStrings from fastfile results in a NULL scriptString. + // That's the reason why the count is incremented by 1, if scriptStrings are available. + + // Write ScriptString pointer table + for (size_t i = 0; i < ZoneBuilder::Zone::ScriptStrings.size(); i++) + { + ZoneBuilder::Zone::Buffer.SaveMax(4); + } + + ZoneBuilder::Zone::Buffer.Align(Utils::Stream::ALIGN_4); + + // Write ScriptStrings + for (auto ScriptString : ZoneBuilder::Zone::ScriptStrings) + { + ZoneBuilder::Zone::Buffer.SaveString(ScriptString.c_str()); + } + } + + // Align buffer (4 bytes) to get correct offsets for pointers + ZoneBuilder::Zone::Buffer.Align(Utils::Stream::ALIGN_4); + ZoneBuilder::Zone::IndexStart = ZoneBuilder::Zone::Buffer.GetBlockSize(Game::XFILE_BLOCK_VIRTUAL); // Mark AssetTable offset + // AssetTable + for (auto asset : Assets) + { + Game::XAsset entry; + entry.type = asset.type; + entry.header.data = (void*)-1; + + ZoneBuilder::Zone::Buffer.Save(&entry, sizeof(Game::XAsset)); + } + + // Assets + for (auto asset : Assets) + { + ZoneBuilder::Zone::Buffer.PushBlock(Game::XFILE_BLOCK_TEMP); + + if (asset.type == Game::XAssetType::ASSET_TYPE_RAWFILE) + { + Game::RawFile* _asset = asset.header.rawfile; + Game::RawFile* dest = (Game::RawFile*)ZoneBuilder::Zone::Buffer.At(); + ZoneBuilder::Zone::Buffer.Save(_asset, sizeof(Game::RawFile)); + + ZoneBuilder::Zone::Buffer.PushBlock(Game::XFILE_BLOCK_VIRTUAL); + + if (_asset->name) + { + ZoneBuilder::Zone::Buffer.SaveString(_asset->name); + dest->name = (char *)-1; + } + + if (_asset->compressedData) + { + if (_asset->sizeCompressed) + { + ZoneBuilder::Zone::Buffer.SaveString(_asset->compressedData, _asset->sizeCompressed); + } + else + { + ZoneBuilder::Zone::Buffer.SaveString(_asset->compressedData, _asset->sizeUnCompressed); + } + + dest->compressedData = (char*)-1; + } + + ZoneBuilder::Zone::Buffer.PopBlock(); + } + else + { + Logger::Error("Wat?"); + } + + //Assets::Save(asset, this); + + ZoneBuilder::Zone::Buffer.PopBlock(); + } + + // Adapt header + ZoneBuilder::Zone::Buffer.EnterCriticalSection(); + Game::XFile* header = (Game::XFile*)ZoneBuilder::Zone::Buffer.Data(); + header->size = ZoneBuilder::Zone::Buffer.Length() - sizeof(Game::XFile); // Write correct data size + header->externalSize = 0; // ? + + // Write stream sizes + for (int i = 0; i < Game::MAX_XFILE_COUNT; i++) + { + header->blockSize[i] = ZoneBuilder::Zone::Buffer.GetBlockSize((Game::XFILE_BLOCK_TYPES)i); + } + + ZoneBuilder::Zone::Buffer.LeaveCriticalSection(); + ZoneBuilder::Zone::Buffer.PopBlock(); + } + + // Add branding asset + // TODO: Check if a RawFile with the same name has already been added, to prevent conflicts. + void ZoneBuilder::Zone::AddBranding() + { + char* data = "FastFile built using iw4x IW4 ZoneTool!"; + branding = { ZoneBuilder::Zone::ZoneName.data(), (int)strlen(data), 0, data }; + + Game::XAssetHeader header = { &branding }; + Game::XAsset brandingAsset = { Game::XAssetType::ASSET_TYPE_RAWFILE, header }; + Assets.push_back(brandingAsset); + } + + // Check if the given pointer has already been mapped + bool ZoneBuilder::Zone::HasPointer(const void* pointer) + { + return (ZoneBuilder::Zone::PointerMap.find(pointer) != ZoneBuilder::Zone::PointerMap.end()); + } + + // Get stored offset for given file pointer + uint32_t ZoneBuilder::Zone::SafeGetPointer(const void* pointer) + { + if (ZoneBuilder::Zone::HasPointer(pointer)) + { + return ZoneBuilder::Zone::PointerMap[pointer]; + } + + return NULL; + } + + void ZoneBuilder::Zone::StorePointer(const void* pointer) + { + ZoneBuilder::Zone::PointerMap[pointer] = ZoneBuilder::Zone::Buffer.GetPackedOffset(); + } + + // Mark a scriptString for writing and map it. + int ZoneBuilder::Zone::AddScriptString(unsigned short gameIndex) + { + // Handle NULL scriptStrings + // Might optimize that later + if (!gameIndex) + { + if (!ZoneBuilder::Zone::ScriptStrings.size()) + { + ZoneBuilder::Zone::ScriptStrings.push_back(""); + } + + return 0; + } + + std::string str = Game::SL_ConvertToString(gameIndex); + int prev = FindScriptString(str); + + if (prev > 0) + { + ZoneBuilder::Zone::ScriptStringMap[gameIndex] = prev; + return prev; + } + + ZoneBuilder::Zone::ScriptStrings.push_back(str); + ZoneBuilder::Zone::ScriptStringMap[gameIndex] = ScriptStrings.size(); + return ZoneBuilder::Zone::ScriptStrings.size(); + } + + // Find a local scriptString + int ZoneBuilder::Zone::FindScriptString(std::string str) + { + int loc = 0; + for (auto it : ScriptStrings) + { + ++loc; + if (!it.compare(str)) return loc; + } + return -1; + } + + // Remap a scriptString to it's corresponding value in the local scriptString table. + void ZoneBuilder::Zone::MapScriptString(unsigned short* gameIndex) + { + *gameIndex = 0xFFFF & ZoneBuilder::Zone::ScriptStringMap[*gameIndex]; + } + + // Store a new name for a given asset + void ZoneBuilder::Zone::RenameAsset(Game::XAssetType type, std::string asset, std::string newName) + { + if (type < Game::XAssetType::ASSET_TYPE_COUNT) + { + ZoneBuilder::Zone::RenameMap[type][asset] = newName; + } + } + + // Return the new name for a given asset + std::string ZoneBuilder::Zone::GetAssetName(Game::XAssetType type, std::string asset) + { + if (type < Game::XAssetType::ASSET_TYPE_COUNT) + { + if (ZoneBuilder::Zone::RenameMap[type].find(asset) != ZoneBuilder::Zone::RenameMap[type].end()) + { + return ZoneBuilder::Zone::RenameMap[type][asset]; + } + } + + return asset; } bool ZoneBuilder::IsEnabled() @@ -115,6 +446,7 @@ namespace Components return Flags::HasFlag("zonebuilder"); } + // TODO: Remove and replace with loadzone command void TestZoneLoading(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) { std::vector data; @@ -127,20 +459,21 @@ namespace Components ZoneBuilder::ZoneBuilder() { + static_assert(sizeof(Game::XFileHeader) == 21, "Invalid XFileHeader structure!"); + static_assert(sizeof(Game::XFile) == 40, "Invalid XFile structure!"); + static_assert(Game::MAX_XFILE_COUNT == 8, "XFile block enum is invalid!"); + if (ZoneBuilder::IsEnabled()) { - auto data = Zone("penis").Build(); - - FILE* fp; - fopen_s(&fp, "zone/patch/penis.ff", "wb"); - - if (fp) + Command::Add("build", [] (Command::Params params) { - fwrite(data.data(), 1, data.size(), fp); - fclose(fp); - } + if (params.Length() < 2) return; - ExitProcess(0); + std::string zoneName = params[1]; + Logger::Print("Building zone '%s'...", zoneName.data()); + + Zone(zoneName).Build(); + }); } //Utils::Hook(0x60B4AC, TestZoneLoading, HOOK_CALL).Install()->Quick(); diff --git a/src/Components/Modules/ZoneBuilder.hpp b/src/Components/Modules/ZoneBuilder.hpp index 472fdf65..58e383c8 100644 --- a/src/Components/Modules/ZoneBuilder.hpp +++ b/src/Components/Modules/ZoneBuilder.hpp @@ -1,37 +1,6 @@ #define XFILE_MAGIC_UNSIGNED 0x3030317566665749 #define XFILE_VERSION 276 -#pragma pack(push, 1) -struct XFileHeader -{ - uint64_t magic; - uint32_t version; - uint8_t flag; - DWORD highDateTime; - DWORD lowDateTime; -}; -#pragma pack(pop) - -struct XFile -{ - unsigned int size; - unsigned int externalSize; - unsigned int blockSize[8]; -}; - -struct ScriptStringList -{ - int count; - const char **strings; -}; - -struct XAssetList -{ - ScriptStringList stringList; - int assetCount; - Game::XAsset *assets; -}; - namespace Components { class ZoneBuilder : public Component @@ -43,10 +12,56 @@ namespace Components Zone(std::string zoneName); ~Zone(); - std::string Build(); + void Build(); + + Utils::Stream* GetBuffer(); + + bool HasPointer(const void* pointer); + void StorePointer(const void* pointer); + + template + T* GetPointer(const T* pointer) { return reinterpret_cast(SafeGetPointer(pointer)); } + + int FindAsset(Game::XAssetType type, const char* name); + Game::XAsset* GetAsset(int index); + uint32_t GetAssetOffset(int index); + Game::XAssetHeader RequireAsset(Game::XAssetType type, const char* name); + bool LoadAsset(Game::XAssetType type, std::string name); + + int AddScriptString(unsigned short gameIndex); + int FindScriptString(std::string str); + + void MapScriptString(unsigned short* gameIndex); + + void RenameAsset(Game::XAssetType type, std::string asset, std::string newName); + std::string GetAssetName(Game::XAssetType type, std::string asset); private: + void LoadFastFiles(); + + bool LoadAssets(); + bool LoadAsset(std::string type, std::string name); + + void SaveData(); + void WriteZone(); + + void AddBranding(); + + uint32_t SafeGetPointer(const void* pointer); + + int IndexStart; + Utils::Stream Buffer; + std::string ZoneName; + Utils::CSV DataMap; + + std::vector Assets; + std::vector ScriptStrings; + std::map ScriptStringMap; + std::map RenameMap[Game::XAssetType::ASSET_TYPE_COUNT]; + std::map PointerMap; + + Game::RawFile branding; }; ZoneBuilder(); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index e8af7d03..c927d725 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -20,6 +20,7 @@ namespace Game DB_FindXAssetHeader_t DB_FindXAssetHeader = (DB_FindXAssetHeader_t)0x407930; DB_GetXAssetNameHandler_t* DB_GetXAssetNameHandlers = (DB_GetXAssetNameHandler_t*)0x799328; DB_GetXAssetSizeHandler_t* DB_GetXAssetSizeHandlers = (DB_GetXAssetSizeHandler_t*)0x799488; + DB_GetXAssetTypeName_t DB_GetXAssetTypeName = (DB_GetXAssetTypeName_t)0x4CFCF0; DB_LoadXAssets_t DB_LoadXAssets = (DB_LoadXAssets_t)0x4E5930; Dvar_RegisterBool_t Dvar_RegisterBool = (Dvar_RegisterBool_t)0x4CE1A0; @@ -93,6 +94,8 @@ namespace Game SetConsole_t SetConsole = (SetConsole_t)0x44F060; + SL_ConvertToString_t SL_ConvertToString = (SL_ConvertToString_t)0x4EC1D0; + Steam_JoinLobby_t Steam_JoinLobby = (Steam_JoinLobby_t)0x49CF70; Sys_IsMainThread_t Sys_IsMainThread = (Sys_IsMainThread_t)0x4C37D0; @@ -213,4 +216,24 @@ namespace Game return gameType; } + + const char *DB_GetXAssetName(XAsset *asset) + { + if (!asset) return ""; + return DB_GetXAssetNameHandlers[asset->type](&asset->header); + } + + XAssetType DB_GetXAssetNameType(const char* name) + { + for (int i = 0; i < ASSET_TYPE_COUNT; i++) + { + XAssetType type = (XAssetType)i; + if (!_stricmp(DB_GetXAssetTypeName(type), name)) + { + return type; + } + } + + return ASSET_TYPE_INVALID; + } } \ No newline at end of file diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index ec8eb737..6e156280 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -42,6 +42,9 @@ namespace Game typedef int(__cdecl * DB_GetXAssetSizeHandler_t)(); extern DB_GetXAssetSizeHandler_t* DB_GetXAssetSizeHandlers; + typedef const char *(__cdecl * DB_GetXAssetTypeName_t)(XAssetType type); + extern DB_GetXAssetTypeName_t DB_GetXAssetTypeName; + typedef void(*DB_LoadXAssets_t)(XZoneInfo *zoneInfo, unsigned int zoneCount, int sync); extern DB_LoadXAssets_t DB_LoadXAssets; @@ -208,6 +211,9 @@ namespace Game typedef void(__cdecl * SetConsole_t)(const char* cvar, const char* value); extern SetConsole_t SetConsole; + typedef char* (__cdecl * SL_ConvertToString_t)(unsigned short); + extern SL_ConvertToString_t SL_ConvertToString; + typedef void(__cdecl * Steam_JoinLobby_t)(SteamID, char); extern Steam_JoinLobby_t Steam_JoinLobby; @@ -260,4 +266,7 @@ namespace Game const char* TabeLookup(StringTable* stringtable, int row, int column); const char* UI_LocalizeMapName(const char* mapName); const char* UI_LocalizeGameType(const char* gameType); + + const char *DB_GetXAssetName(XAsset *asset); + XAssetType DB_GetXAssetNameType(const char* name); } diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 469b56e3..912c9f49 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -47,7 +47,9 @@ namespace Game ASSET_TYPE_TRACER = 40, ASSET_TYPE_VEHICLE = 41, ASSET_TYPE_ADDON_MAP_ENTS = 42, - ASSET_TYPE_MAX = 43 + + ASSET_TYPE_COUNT, + ASSET_TYPE_INVALID = -1, } XAssetType; typedef enum @@ -892,6 +894,14 @@ namespace Game StringTableCell *values; }; + struct RawFile + { + const char* name; + int sizeCompressed; + int sizeUnCompressed; + char * compressedData; + }; + union XAssetHeader { void *data; @@ -902,6 +912,7 @@ namespace Game localizedEntry_s *localize; StringTable *stringTable; MapEnts* mapEnts; + RawFile* rawfile; }; struct XAsset @@ -926,6 +937,81 @@ namespace Game unsigned __int16 usageFrame; }; + enum XFileLanguage : uint8_t + { + XLANG_NONE = 0x00, + XLANG_ENGLISH = 0x01, + XLANG_FRENCH = 0x02, + XLANG_GERMAN = 0x03, + XLANG_ITALIAN = 0x04, + XLANG_SPANISH = 0x05, + XLANG_BRITISH = 0x06, + XLANG_RUSSIAN = 0x07, + XLANG_POLISH = 0x08, + XLANG_KOREAN = 0x09, + XLANG_TAIWANESE = 0x0A, + XLANG_JAPANESE = 0x0B, + XLANG_CHINESE = 0x0C, + XLANG_THAI = 0x0D, + XLANG_LEET = 0x0E, // Wat? + XLANG_CZECH = 0x0F, + }; + +#pragma pack(push, 1) + struct XFileHeader + { + uint64_t magic; + uint32_t version; + XFileLanguage language; + DWORD highDateTime; + DWORD lowDateTime; + }; +#pragma pack(pop) + + enum XFILE_BLOCK_TYPES + { + XFILE_BLOCK_TEMP = 0x0, + XFILE_BLOCK_PHYSICAL = 0x1, + XFILE_BLOCK_RUNTIME = 0x2, + XFILE_BLOCK_VIRTUAL = 0x3, + XFILE_BLOCK_LARGE = 0x4, + + // Those are probably incorrect + XFILE_BLOCK_CALLBACK, + XFILE_BLOCK_VERTEX, + XFILE_BLOCK_INDEX, + + MAX_XFILE_COUNT, + + XFILE_BLOCK_INVALID = -1 + }; + + struct XFile + { + unsigned int size; + unsigned int externalSize; + unsigned int blockSize[MAX_XFILE_COUNT]; + }; + + struct ScriptStringList + { + int count; + const char **strings; + }; + + struct XAssetList + { + ScriptStringList stringList; + int assetCount; + Game::XAsset *assets; + }; + + struct ZoneHeader + { + XFile xFile; + XAssetList assetList; + }; + struct XNKID { char ab[8]; diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 4bbbe225..f69d7de2 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include +#include "Utils\CSV.hpp" #include "Utils\Utils.hpp" #include "Utils\WebIO.hpp" #include "Utils\Hooking.hpp" @@ -41,6 +43,8 @@ #include "Game\Structs.hpp" #include "Game\Functions.hpp" +#include "Utils\Stream.hpp" + #include "Components\Loader.hpp" // Libraries diff --git a/src/Utils/CSV.cpp b/src/Utils/CSV.cpp new file mode 100644 index 00000000..01d49b29 --- /dev/null +++ b/src/Utils/CSV.cpp @@ -0,0 +1,124 @@ +#include "STDInclude.hpp" + +namespace Utils +{ + CSV::CSV(std::string file) + { + CSV::Parse(file); + } + + CSV::~CSV() + { + for (auto row : CSV::DataMap) + { + for (auto entry : row) + { + entry.clear(); + } + + row.clear(); + } + + CSV::DataMap.clear(); + } + + int CSV::GetRows() + { + return CSV::DataMap.size(); + } + + int CSV::GetColumns(size_t row) + { + if (CSV::DataMap.size() > row) + { + return CSV::DataMap[row].size(); + } + + return 0; + } + + std::string CSV::GetElementAt(size_t row, size_t column) + { + if (CSV::DataMap.size() > row) + { + auto _row = CSV::DataMap[row]; + + if (_row.size() > column) + { + return _row[column]; + } + } + + return ""; + } + + void CSV::Parse(std::string file) + { + if (!Utils::FileExists(file)) return; + + std::string buffer = Utils::ReadFile(file); + + if (buffer.size()) + { + auto rows = Utils::Explode(buffer, '\n'); + + for (auto row : rows) + { + CSV::ParseRow(row); + } + } + } + + void CSV::ParseRow(std::string row) + { + bool isString = false; + std::string element; + std::vector _row; + char tempStr[2] = { 0, 0 }; + + for (unsigned int i = 0; i < row.size(); i++) + { + if (row[i] == ',' && !isString) // FLush entry + { + _row.push_back(element); + element.clear(); + continue; + } + else if (row[i] == '"') // Start/Terminate string + { + isString = !isString; + continue; + } + else if (i < (row.size() - 1) && row[i] == '\\' &&row[i + 1] == '"' && isString) // Handle quotes in strings as \" + { + tempStr[0] = '"'; + ++i; + } + else if (!isString && (row[i] == '\n' || row[i] == '\x0D' || row[i] == '\x0A' || row[i] == '\t')) + { + //++i; + continue; + } + else if (!isString && row[i] == '#') // Skip comments. I know CSVs usually don't have comments, but in this case it's useful + { + return; + } + else + { + tempStr[0] = row[i]; + } + + element.append(tempStr); + } + + // Push last element + _row.push_back(element); + + if (_row.size() == 0 || (_row.size() == 1 && !_row[0].size())) // Skip empty rows + { + return; + } + + DataMap.push_back(_row); + } +} diff --git a/src/Utils/CSV.hpp b/src/Utils/CSV.hpp new file mode 100644 index 00000000..76151d12 --- /dev/null +++ b/src/Utils/CSV.hpp @@ -0,0 +1,20 @@ +namespace Utils +{ + class CSV + { + public: + CSV(std::string file); + ~CSV(); + + int GetRows(); + int GetColumns(size_t row); + + std::string GetElementAt(size_t row, size_t column); + + private: + + void Parse(std::string file); + void ParseRow(std::string row); + std::vector> DataMap; + }; +} diff --git a/src/Utils/Stream.cpp b/src/Utils/Stream.cpp new file mode 100644 index 00000000..7edb1b27 --- /dev/null +++ b/src/Utils/Stream.cpp @@ -0,0 +1,249 @@ +#include "STDInclude.hpp" + +namespace Utils +{ + Stream::Stream() : CriticalSectionState(0) + { + memset(Stream::BlockSize, 0, sizeof(Stream::BlockSize)); + } + + Stream::Stream(size_t size) : Stream() + { + Stream::Buffer.reserve(size); + } + + Stream::~Stream() + { + Stream::Buffer.clear(); + + if (Stream::CriticalSectionState != 0) + { + MessageBoxA(0, Utils::VA("Invalid critical section state '%i' for stream destruction!", Stream::CriticalSectionState), "WARNING", MB_ICONEXCLAMATION); + } + }; + + size_t Stream::Length() + { + return Stream::Buffer.length(); + } + + size_t Stream::Capacity() + { + return Stream::Buffer.capacity(); + } + + char* Stream::Save(const void * _str, size_t size, size_t count) + { + return Stream::Save(Stream::GetCurrentBlock(), _str, size, count); + } + + char* Stream::Save(Game::XFILE_BLOCK_TYPES stream, const void * _str, size_t size, size_t count) + { + //if (stream == XFILE_BLOCK_TEMP || stream == XFILE_BLOCK_VIRTUAL || stream == XFILE_BLOCK_PHYSICAL) // Only those seem to actually write data. + // As I'm not sure though, I'll still write the data + // Use IncreaseStreamSize to fill virtual streams + auto data = Stream::Data(); + + if (Stream::IsCriticalSection() && Stream::Length() + (size * count) > Stream::Capacity()) + { + MessageBoxA(0, Utils::VA("Potential stream reallocation during critical operation detected! Writing data of the length 0x%X exceeds the allocated stream size of 0x%X\n", (size * count), Stream::Capacity()), "ERROR", MB_ICONERROR); + __debugbreak(); + } + + Stream::Buffer.append((char*)_str, size * count); + + if (Stream::Data() != data && Stream::IsCriticalSection()) + { + MessageBoxA(0, "Stream reallocation during critical operations not permitted!\nPlease increase the initial memory size or reallocate memory during non-critical sections!", "ERROR", MB_ICONERROR); + __debugbreak(); + } + + Stream::IncreaseBlockSize(stream, size * count); // stay up to date on those streams + + return Stream::At() - (size * count); + } + + char* Stream::Save(Game::XFILE_BLOCK_TYPES stream, int value, size_t count) + { + auto ret = Stream::Length(); + + for (size_t i = 0; i < count; i++) + { + Stream::Save(stream, &value, 4, 1); + } + + return Stream::Data() + ret; + } + + char* Stream::SaveString(std::string string) + { + return Stream::SaveString(string.data()/*, string.size()*/); + } + + char* Stream::SaveString(const char* string) + { + return Stream::SaveString(string, strlen(string)); + } + + char* Stream::SaveString(const char* string, size_t len) + { + auto ret = Stream::Length(); + + if (string) + { + Stream::Save(string, len); + } + + Stream::SaveNull(); + + return Stream::Data() + ret; + } + + char* Stream::SaveText(std::string string) + { + return Stream::Save(string.data(), string.length()); + } + + char* Stream::SaveByte(unsigned char byte, size_t count) + { + auto ret = Stream::Length(); + + for (size_t i = 0; i < count; i++) + { + Stream::Save(&byte, 1); + } + + return Stream::Data() + ret; + } + + char* Stream::SaveNull(size_t count) + { + return Stream::SaveByte(0, count); + } + + char* Stream::SaveMax(size_t count) + { + return Stream::SaveByte(-1, count); + } + + void Stream::Align(Stream::Alignment align) + { + uint32_t size = 2 << align; + + // Not power of 2! + if (!size || (size & (size - 1))) return; + --size; + + Game::XFILE_BLOCK_TYPES stream = Stream::GetCurrentBlock(); + Stream::BlockSize[stream] = ~size & (Stream::GetBlockSize(stream) + size); + } + + bool Stream::PushBlock(Game::XFILE_BLOCK_TYPES stream) + { + Stream::StreamStack.push_back(stream); + return Stream::IsValidBlock(stream); + } + + bool Stream::PopBlock() + { + if (Stream::StreamStack.size()) + { + Stream::StreamStack.pop_back(); + return true; + } + + return false; + } + + bool Stream::IsValidBlock(Game::XFILE_BLOCK_TYPES stream) + { + return (stream < Game::MAX_XFILE_COUNT && stream >= Game::XFILE_BLOCK_TEMP); + } + + void Stream::IncreaseBlockSize(Game::XFILE_BLOCK_TYPES stream, unsigned int size) + { + if (Stream::IsValidBlock(stream)) + { + Stream::BlockSize[stream] += size; + } + } + + void Stream::IncreaseBlockSize(unsigned int size) + { + return IncreaseBlockSize(Stream::GetCurrentBlock(), size); + } + + Game::XFILE_BLOCK_TYPES Stream::GetCurrentBlock() + { + if (Stream::StreamStack.size()) + { + return Stream::StreamStack[Stream::StreamStack.size() - 1]; + } + + return Game::XFILE_BLOCK_INVALID; + } + + char* Stream::At() + { + return (char*)(Stream::Data() + Stream::Length()); + } + + char* Stream::Data() + { + return (char*)Stream::Buffer.data(); + } + + unsigned int Stream::GetBlockSize(Game::XFILE_BLOCK_TYPES stream) + { + if (Stream::IsValidBlock(stream)) + { + return Stream::BlockSize[stream]; + } + + return 0; + } + + DWORD Stream::GetPackedOffset() + { + Game::XFILE_BLOCK_TYPES block = Stream::GetCurrentBlock(); + + Stream::Offset offset; + offset.block = block; + offset.offset = Stream::GetBlockSize(block); + return offset.GetPackedOffset(); + } + + void Stream::ToBuffer(std::string& outBuffer) + { + outBuffer.clear(); + outBuffer.append(Stream::Data(), Stream::Length()); + } + + std::string Stream::ToBuffer() + { + std::string buffer; + Stream::ToBuffer(buffer); + return buffer; + } + + void Stream::EnterCriticalSection() + { + ++Stream::CriticalSectionState; + } + + void Stream::LeaveCriticalSection() + { + --Stream::CriticalSectionState; + } + + bool Stream::IsCriticalSection() + { + if (Stream::CriticalSectionState < 0) + { + MessageBoxA(0, "CriticalSectionState in stream has been overrun!", "ERROR", MB_ICONERROR); + __debugbreak(); + } + + return (Stream::CriticalSectionState != 0); + } +} diff --git a/src/Utils/Stream.hpp b/src/Utils/Stream.hpp new file mode 100644 index 00000000..27788901 --- /dev/null +++ b/src/Utils/Stream.hpp @@ -0,0 +1,114 @@ +namespace Utils +{ + class Stream + { + private: + int CriticalSectionState; + unsigned int BlockSize[Game::MAX_XFILE_COUNT]; + std::vector StreamStack; + std::string Buffer; + + public: + enum Alignment + { + ALIGN_2, + ALIGN_4, + ALIGN_8, + ALIGN_16, + ALIGN_32, + ALIGN_64, + ALIGN_128, + ALIGN_256, + ALIGN_512, + ALIGN_1024, + ALIGN_2048, + }; + + Stream(); + Stream(size_t size); + ~Stream(); + + size_t Length(); + size_t Capacity(); + + char* Save(const void * _str, size_t size, size_t count = 1); + char* Save(Game::XFILE_BLOCK_TYPES stream, const void * _str, size_t size, size_t count); + char* Save(Game::XFILE_BLOCK_TYPES stream, int value, size_t count); + + char* SaveString(std::string string); + char* SaveString(const char* string); + char* SaveString(const char* string, size_t len); + char* SaveByte(unsigned char byte, size_t count = 1); + char* SaveNull(size_t count = 1); + char* SaveMax(size_t count = 1); + + char* SaveText(std::string string); + + void Align(Alignment align); + bool PushBlock(Game::XFILE_BLOCK_TYPES stream); + bool PopBlock(); + bool IsValidBlock(Game::XFILE_BLOCK_TYPES stream); + void IncreaseBlockSize(Game::XFILE_BLOCK_TYPES stream, unsigned int size); + void IncreaseBlockSize(unsigned int size); + Game::XFILE_BLOCK_TYPES GetCurrentBlock(); + unsigned int GetBlockSize(Game::XFILE_BLOCK_TYPES stream); + + DWORD GetPackedOffset(); + + char* At(); + char* Data(); + + void ToBuffer(std::string& outBuffer); + std::string ToBuffer(); + + // Enter/Leave critical sections in which reallocations are not allowed. + // If buffer reallocation is detected, the operation has to be terminated + // and more memory has to be allocated next time. This will have to be done + // by editing the code though. + void EnterCriticalSection(); + void LeaveCriticalSection(); + bool IsCriticalSection(); + + + // This represents packed offset in streams: + // - lowest 28 bits store the value/offset + // - highest 4 bits store the stream block + class Offset + { + public: + union + { + struct + { + uint32_t offset : 28; + Game::XFILE_BLOCK_TYPES block : 4; + }; + uint32_t packed; + void* pointer; + }; + + Offset() : packed(0) {}; + Offset(Game::XFILE_BLOCK_TYPES _block, uint32_t _offset) : offset(_offset), block(_block) {}; + + // The game needs it to be incremented + uint32_t GetPackedOffset() + { + return this->packed + 1; + }; + + uint32_t GetUnpackedOffset() + { + Offset offset = *this; + offset.packed--; + return offset.offset; + }; + + int GetUnpackedBlock() + { + Offset offset = *this; + offset.packed--; + return offset.block; + }; + }; + }; +} diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index 88f72358..1a879805 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -104,6 +104,55 @@ namespace Utils return data.substr(0, data.find_first_of("\n")).data(); } + // TODO: Use modern file reading methods + bool FileExists(std::string file) + { + FILE* fp; + fopen_s(&fp, file.data(), "r"); + + if (fp) + { + fclose(fp); + return true; + } + + return false; + } + + void WriteFile(std::string file, std::string data) + { + std::ofstream stream(file, std::ios::binary); + stream.write(data.data(), data.size()); + stream.close(); + } + + std::string ReadFile(std::string file) + { + std::string buffer; + + if (FileExists(file)) + { + std::ifstream stream(file, std::ios::binary); + std::streamsize size = 0; + + stream.seekg(0, std::ios::end); + size = stream.tellg(); + stream.seekg(0, std::ios::beg); + + if (size > -1) + { + buffer.clear(); + buffer.resize((uint32_t)size); + + stream.read((char *)buffer.data(), size); + } + + stream.close(); + } + + return buffer; + } + // Infostring class void InfoString::Set(std::string key, std::string value) { diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index 9a49a956..60d748ac 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -12,6 +12,10 @@ namespace Utils std::string ParseChallenge(std::string data); + bool FileExists(std::string file); + void WriteFile(std::string file, std::string data); + std::string ReadFile(std::string file); + class InfoString { public: