Some fastfile stuff.

This commit is contained in:
momo5502 2016-01-06 01:23:43 +01:00
parent 5bdc37b89c
commit b58085810a
17 changed files with 1198 additions and 195 deletions

View File

@ -129,13 +129,13 @@ namespace Components
}
}
void AssetHandler::OffsetToAlias(FastFiles::Offset* offset)
void AssetHandler::OffsetToAlias(Utils::Stream::Offset* offset)
{
offset->fullPointer = *reinterpret_cast<void**>((*Game::g_streamBlocks)[offset->GetDecrementedStream()].data + offset->GetDecrementedPointer());
offset->pointer = *reinterpret_cast<void**>((*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];
}
}

View File

@ -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<Game::XAssetType, Callback> TypeCallbacks;
static std::vector<RestrictCallback> RestrictCallbacks;

View File

@ -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);
}

View File

@ -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<bool>("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<DWORD>(0x5DEC04, 0);
@ -226,6 +220,14 @@ namespace Components
// stop saving a config_mp.cfg
Utils::Hook::Set<BYTE>(0x60B240, 0xC3);
// Dedicated frame handler
Utils::Hook(0x4B0F81, Dedicated::FrameStub, HOOK_CALL).Install()->Quick();
if (!ZoneBuilder::IsEnabled())
{
// Post initialization point
Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).Install()->Quick();
// Heartbeats
Dedicated::OnFrame([] ()
{
@ -239,6 +241,7 @@ namespace Components
});
}
}
}
Dedicated::~Dedicated()
{

View File

@ -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"; };

View File

@ -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<char*>(&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<Game::XZoneInfo> 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();

View File

@ -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<typename T>
T* GetPointer(const T* pointer) { return reinterpret_cast<T*>(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<Game::XAsset> Assets;
std::vector<std::string> ScriptStrings;
std::map<unsigned short, unsigned int> ScriptStringMap;
std::map<std::string, std::string> RenameMap[Game::XAssetType::ASSET_TYPE_COUNT];
std::map<const void*, uint32_t> PointerMap;
Game::RawFile branding;
};
ZoneBuilder();

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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];

View File

@ -16,6 +16,7 @@
#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <utility>
#include <algorithm>
#include <functional>
@ -31,6 +32,7 @@
#include <zlib.h>
#include <json11.hpp>
#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

124
src/Utils/CSV.cpp Normal file
View File

@ -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<std::string> _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);
}
}

20
src/Utils/CSV.hpp Normal file
View File

@ -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<std::vector<std::string>> DataMap;
};
}

249
src/Utils/Stream.cpp Normal file
View File

@ -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);
}
}

114
src/Utils/Stream.hpp Normal file
View File

@ -0,0 +1,114 @@
namespace Utils
{
class Stream
{
private:
int CriticalSectionState;
unsigned int BlockSize[Game::MAX_XFILE_COUNT];
std::vector<Game::XFILE_BLOCK_TYPES> 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;
};
};
};
}

View File

@ -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)
{

View File

@ -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: