Some fastfile stuff.
This commit is contained in:
parent
5bdc37b89c
commit
b58085810a
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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"; };
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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
124
src/Utils/CSV.cpp
Normal 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
20
src/Utils/CSV.hpp
Normal 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
249
src/Utils/Stream.cpp
Normal 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
114
src/Utils/Stream.hpp
Normal 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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user