2022-02-27 07:53:44 -05:00
|
|
|
#include <STDInclude.hpp>
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
namespace Components
|
|
|
|
{
|
|
|
|
Utils::Memory::Allocator StructuredData::MemAllocator;
|
|
|
|
|
|
|
|
const char* StructuredData::EnumTranslation[ENUM_MAX] =
|
|
|
|
{
|
|
|
|
"features",
|
|
|
|
"weapons",
|
|
|
|
"attachements",
|
|
|
|
"challenges",
|
|
|
|
"camos",
|
|
|
|
"perks",
|
|
|
|
"killstreaks",
|
|
|
|
"accolades",
|
|
|
|
"cardicons",
|
|
|
|
"cardtitles",
|
|
|
|
"cardnameplates",
|
|
|
|
"teams",
|
|
|
|
"gametypes"
|
|
|
|
};
|
|
|
|
|
|
|
|
void StructuredData::PatchPlayerDataEnum(Game::StructuredDataDef* data, StructuredData::PlayerDataType type, std::vector<std::string>& entries)
|
|
|
|
{
|
|
|
|
if (!data || type >= StructuredData::PlayerDataType::ENUM_MAX) return;
|
|
|
|
|
|
|
|
Game::StructuredDataEnum* dataEnum = &data->enums[type];
|
|
|
|
|
|
|
|
// Build index-sorted data vector
|
|
|
|
std::vector<const char*> dataVector;
|
2017-02-20 13:18:07 -05:00
|
|
|
for (int i = 0; i < dataEnum->entryCount; ++i)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
int index = 0;
|
2017-02-20 13:18:07 -05:00
|
|
|
for (; index < dataEnum->entryCount; ++index)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2017-02-20 13:18:07 -05:00
|
|
|
if (dataEnum->entries[index].index == i)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-09 08:33:52 -04:00
|
|
|
dataVector.push_back(dataEnum->entries[index].string);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Rebase or add new entries
|
|
|
|
for (auto entry : entries)
|
|
|
|
{
|
|
|
|
const char* value = nullptr;
|
|
|
|
for (auto i = dataVector.begin(); i != dataVector.end(); ++i)
|
|
|
|
{
|
|
|
|
if (*i == entry)
|
|
|
|
{
|
|
|
|
value = *i;
|
|
|
|
dataVector.erase(i);
|
|
|
|
Logger::Print("Playerdatadef entry '%s' will be rebased!\n", value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!value) value = StructuredData::MemAllocator.duplicateString(entry);
|
|
|
|
dataVector.push_back(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map data back to the game structure
|
|
|
|
Game::StructuredDataEnumEntry* indices = StructuredData::MemAllocator.allocateArray<Game::StructuredDataEnumEntry>(dataVector.size());
|
2017-02-20 13:18:07 -05:00
|
|
|
for (unsigned short i = 0; i < dataVector.size(); ++i)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
indices[i].index = i;
|
2018-05-09 08:33:52 -04:00
|
|
|
indices[i].string = dataVector[i];
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sort alphabetically
|
2018-05-09 08:33:52 -04:00
|
|
|
qsort(indices, dataVector.size(), sizeof(Game::StructuredDataEnumEntry), [](void const* first, void const* second)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
const Game::StructuredDataEnumEntry* entry1 = reinterpret_cast<const Game::StructuredDataEnumEntry*>(first);
|
|
|
|
const Game::StructuredDataEnumEntry* entry2 = reinterpret_cast<const Game::StructuredDataEnumEntry*>(second);
|
|
|
|
|
2018-05-09 08:33:52 -04:00
|
|
|
return std::string(entry1->string).compare(entry2->string);
|
2017-01-19 16:23:59 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
// Apply our patches
|
2017-02-20 13:18:07 -05:00
|
|
|
dataEnum->entryCount = dataVector.size();
|
|
|
|
dataEnum->entries = indices;
|
|
|
|
}
|
|
|
|
|
|
|
|
void StructuredData::PatchCustomClassLimit(Game::StructuredDataDef* data, int count)
|
|
|
|
{
|
|
|
|
const int customClassSize = 64;
|
|
|
|
|
|
|
|
for (int i = 0; i < data->structs[0].propertyCount; ++i)
|
|
|
|
{
|
|
|
|
// 3003 is the offset of the customClasses structure
|
|
|
|
if (data->structs[0].properties[i].offset >= 3643)
|
|
|
|
{
|
|
|
|
// -10 because 10 is the default amount of custom classes.
|
|
|
|
data->structs[0].properties[i].offset += ((count - 10) * customClassSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// update structure size
|
|
|
|
data->size += ((count - 10) * customClassSize);
|
|
|
|
|
|
|
|
// Update amount of custom classes
|
|
|
|
data->indexedArrays[5].arraySize = count;
|
|
|
|
}
|
|
|
|
|
|
|
|
void StructuredData::PatchAdditionalData(Game::StructuredDataDef* data, std::unordered_map<std::string, std::string>& patches)
|
|
|
|
{
|
2017-06-14 06:06:04 -04:00
|
|
|
for (auto& item : patches)
|
2017-02-20 13:18:07 -05:00
|
|
|
{
|
2017-06-14 06:06:04 -04:00
|
|
|
if (item.first == "classes")
|
2017-02-20 13:18:07 -05:00
|
|
|
{
|
|
|
|
StructuredData::PatchCustomClassLimit(data, atoi(item.second.data()));
|
|
|
|
}
|
|
|
|
}
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2017-02-22 14:17:58 -05:00
|
|
|
bool StructuredData::UpdateVersionOffsets(Game::StructuredDataDefSet *set, Game::StructuredDataBuffer *buffer, Game::StructuredDataDef *whatever)
|
|
|
|
{
|
|
|
|
Game::StructuredDataDef* newDef = &set->defs[0];
|
|
|
|
Game::StructuredDataDef* oldDef = &set->defs[0];
|
|
|
|
|
2017-06-14 06:06:04 -04:00
|
|
|
for (unsigned int i = 0; i < set->defCount; ++i)
|
2017-02-22 14:17:58 -05:00
|
|
|
{
|
2017-06-14 06:06:04 -04:00
|
|
|
if (newDef->version < set->defs[i].version)
|
2017-02-22 14:17:58 -05:00
|
|
|
{
|
|
|
|
newDef = &set->defs[i];
|
|
|
|
}
|
|
|
|
|
2017-06-14 06:06:04 -04:00
|
|
|
if (set->defs[i].version == *reinterpret_cast<int*>(buffer->data))
|
2017-02-22 14:17:58 -05:00
|
|
|
{
|
|
|
|
oldDef = &set->defs[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newDef->version >= 159 && oldDef->version <= 158)
|
|
|
|
{
|
|
|
|
// this should move the data 320 bytes infront
|
|
|
|
std::memmove(&buffer->data[3963], &buffer->data[3643], oldDef->size - 3643);
|
|
|
|
}
|
|
|
|
|
|
|
|
// StructuredData_UpdateVersion
|
|
|
|
return Utils::Hook::Call<bool(void*, void*, void*)>(0x456830)(set, buffer, whatever);
|
|
|
|
}
|
|
|
|
|
2017-01-19 16:23:59 -05:00
|
|
|
StructuredData::StructuredData()
|
|
|
|
{
|
2017-06-01 16:23:26 -04:00
|
|
|
if (Dedicated::IsEnabled()) return;
|
|
|
|
|
2017-06-01 12:32:22 -04:00
|
|
|
// Do not execute this when building zones
|
2017-03-18 19:23:47 -04:00
|
|
|
if (!ZoneBuilder::IsEnabled())
|
|
|
|
{
|
2017-06-01 16:23:26 -04:00
|
|
|
// Correctly upgrade stats
|
|
|
|
Utils::Hook(0x42F088, StructuredData::UpdateVersionOffsets, HOOK_CALL).install()->quick();
|
2017-02-22 14:17:58 -05:00
|
|
|
|
2017-06-01 16:23:26 -04:00
|
|
|
// 15 or more custom classes
|
|
|
|
Utils::Hook::Set<BYTE>(0x60A2FE, NUM_CUSTOM_CLASSES);
|
2017-02-20 13:18:07 -05:00
|
|
|
|
2017-03-18 19:23:47 -04:00
|
|
|
// Reset empty names
|
|
|
|
Command::Add("checkClasses", [](Command::Params*)
|
2017-02-25 11:27:34 -05:00
|
|
|
{
|
2017-03-18 19:23:47 -04:00
|
|
|
for (int i = 0; i < NUM_CUSTOM_CLASSES; ++i)
|
|
|
|
{
|
|
|
|
// TODO: Correctly lookup using structured data
|
|
|
|
char* className = (reinterpret_cast<char*>(0x1AD3694) - 4 + 3003 + (64 * i) + 0x29);
|
|
|
|
if (!*className) strcpy_s(className, 24, Game::SEH_StringEd_GetString(Utils::String::VA("CLASS_SLOT%i", i + 1)));
|
|
|
|
}
|
|
|
|
});
|
2017-02-25 11:27:34 -05:00
|
|
|
|
2017-03-18 19:23:47 -04:00
|
|
|
return;
|
|
|
|
}
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2018-12-17 08:29:18 -05:00
|
|
|
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& filename, bool* /*restrict*/)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
// Only intercept playerdatadef loading
|
2018-05-09 06:04:20 -04:00
|
|
|
if (type != Game::XAssetType::ASSET_TYPE_STRUCTURED_DATA_DEF || filename != "mp/playerdata.def") return;
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Store asset
|
2018-05-09 08:33:52 -04:00
|
|
|
Game::StructuredDataDefSet* data = asset.structuredDataDefSet;
|
2017-01-19 16:23:59 -05:00
|
|
|
if (!data) return;
|
|
|
|
|
2017-02-20 13:18:07 -05:00
|
|
|
if (data->defCount != 1)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
Logger::Error("PlayerDataDefSet contains more than 1 definition!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-20 13:18:07 -05:00
|
|
|
if (data->defs[0].version != 155)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
Logger::Error("Initial PlayerDataDef is not version 155, patching not possible!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::map<int, std::vector<std::vector<std::string>>> patchDefinitions;
|
2017-02-20 13:18:07 -05:00
|
|
|
std::map<int, std::unordered_map<std::string, std::string>> otherPatchDefinitions;
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// First check if all versions are present
|
|
|
|
for (int i = 156;; ++i)
|
|
|
|
{
|
|
|
|
FileSystem::File definition(Utils::String::VA("%s/%d.json", filename.data(), i));
|
|
|
|
if (!definition.exists()) break;
|
|
|
|
|
|
|
|
std::vector<std::vector<std::string>> enumContainer;
|
2017-02-20 13:18:07 -05:00
|
|
|
std::unordered_map<std::string, std::string> otherPatches;
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
std::string errors;
|
|
|
|
json11::Json defData = json11::Json::parse(definition.getBuffer(), errors);
|
|
|
|
|
|
|
|
if (!errors.empty())
|
|
|
|
{
|
|
|
|
Logger::Error("Parsing patch file '%s' for PlayerDataDef version %d failed: %s", definition.getName().data(), i, errors.data());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!defData.is_object())
|
|
|
|
{
|
|
|
|
Logger::Error("PlayerDataDef patch for version %d is invalid!", i);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (unsigned int pType = 0; pType < StructuredData::PlayerDataType::ENUM_MAX; ++pType)
|
|
|
|
{
|
|
|
|
auto enumData = defData[StructuredData::EnumTranslation[pType]];
|
|
|
|
|
|
|
|
std::vector<std::string> entryData;
|
|
|
|
|
|
|
|
if (enumData.is_array())
|
|
|
|
{
|
|
|
|
for (auto rawEntry : enumData.array_items())
|
|
|
|
{
|
|
|
|
if (rawEntry.is_string())
|
|
|
|
{
|
|
|
|
entryData.push_back(rawEntry.string_value());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enumContainer.push_back(entryData);
|
|
|
|
}
|
|
|
|
|
2017-02-20 13:18:07 -05:00
|
|
|
auto other = defData["other"];
|
|
|
|
|
2017-06-14 06:06:04 -04:00
|
|
|
if (other.is_object())
|
2017-02-20 13:18:07 -05:00
|
|
|
{
|
2017-06-14 06:06:04 -04:00
|
|
|
for (auto& item : other.object_items())
|
2017-02-20 13:18:07 -05:00
|
|
|
{
|
2017-06-14 06:06:04 -04:00
|
|
|
if (item.second.is_string())
|
2017-02-20 13:18:07 -05:00
|
|
|
{
|
|
|
|
otherPatches[item.first] = item.second.string_value();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-19 16:23:59 -05:00
|
|
|
patchDefinitions[i] = enumContainer;
|
2017-02-20 13:18:07 -05:00
|
|
|
otherPatchDefinitions[i] = otherPatches;
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Nothing to patch
|
|
|
|
if (patchDefinitions.empty()) return;
|
|
|
|
|
|
|
|
// Reallocate the definition
|
2017-02-20 13:18:07 -05:00
|
|
|
Game::StructuredDataDef* newData = StructuredData::MemAllocator.allocateArray<Game::StructuredDataDef>(data->defCount + patchDefinitions.size());
|
|
|
|
std::memcpy(&newData[patchDefinitions.size()], data->defs, sizeof Game::StructuredDataDef * data->defCount);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Prepare the buffers
|
|
|
|
for (unsigned int i = 0; i < patchDefinitions.size(); ++i)
|
|
|
|
{
|
2017-02-20 13:18:07 -05:00
|
|
|
std::memcpy(&newData[i], data->defs, sizeof Game::StructuredDataDef);
|
2017-01-19 16:23:59 -05:00
|
|
|
newData[i].version = (patchDefinitions.size() - i) + 155;
|
|
|
|
|
|
|
|
// Reallocate the enum array
|
2017-02-20 13:18:07 -05:00
|
|
|
Game::StructuredDataEnum* newEnums = StructuredData::MemAllocator.allocateArray<Game::StructuredDataEnum>(data->defs->enumCount);
|
|
|
|
std::memcpy(newEnums, data->defs->enums, sizeof Game::StructuredDataEnum * data->defs->enumCount);
|
2017-01-19 16:23:59 -05:00
|
|
|
newData[i].enums = newEnums;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply new data
|
2017-02-20 13:18:07 -05:00
|
|
|
data->defs = newData;
|
|
|
|
data->defCount += patchDefinitions.size();
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Patch the definition
|
2017-02-20 13:18:07 -05:00
|
|
|
for (unsigned int i = 0; i < data->defCount; ++i)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
// No need to patch version 155
|
|
|
|
if (newData[i].version == 155) continue;
|
|
|
|
|
2017-06-14 06:06:04 -04:00
|
|
|
if (patchDefinitions.find(newData[i].version) != patchDefinitions.end())
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
auto patchData = patchDefinitions[newData[i].version];
|
2017-02-20 13:18:07 -05:00
|
|
|
auto otherData = otherPatchDefinitions[newData[i].version];
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Invalid patch data
|
|
|
|
if (patchData.size() != StructuredData::PlayerDataType::ENUM_MAX)
|
|
|
|
{
|
|
|
|
Logger::Error("PlayerDataDef patch for version %d wasn't parsed correctly!", newData[i].version);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply the patch data
|
|
|
|
for (unsigned int pType = 0; pType < StructuredData::PlayerDataType::ENUM_MAX; ++pType)
|
|
|
|
{
|
|
|
|
if (!patchData[pType].empty())
|
|
|
|
{
|
|
|
|
StructuredData::PatchPlayerDataEnum(&newData[i], static_cast<StructuredData::PlayerDataType>(pType), patchData[pType]);
|
|
|
|
}
|
|
|
|
}
|
2017-02-20 13:18:07 -05:00
|
|
|
|
|
|
|
StructuredData::PatchAdditionalData(&newData[i], otherData);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|