810 lines
37 KiB
C++
810 lines
37 KiB
C++
#include "MasterLocal.h"
|
|
#include "Local.h"
|
|
#include "Il2cppUtils.hpp"
|
|
#include "config/Config.hpp"
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
#include <regex>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
namespace GakumasLocal::MasterLocal {
|
|
using Il2cppString = UnityResolve::UnityType::String;
|
|
|
|
static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldSetCache;
|
|
static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldGetCache;
|
|
|
|
enum class JsonValueType {
|
|
JVT_String,
|
|
JVT_Int,
|
|
JVT_Object,
|
|
JVT_ArrayObject,
|
|
JVT_ArrayString,
|
|
JVT_Unsupported,
|
|
JVT_NeedMore_EmptyArray
|
|
};
|
|
|
|
struct ItemRule {
|
|
std::vector<std::string> mainPrimaryKey;
|
|
std::map<std::string, std::vector<std::string>> subPrimaryKey;
|
|
|
|
std::vector<std::string> mainLocalKey;
|
|
std::map<std::string, std::vector<std::string>> subLocalKey;
|
|
};
|
|
|
|
struct TableLocalData {
|
|
ItemRule itemRule;
|
|
|
|
std::unordered_map<std::string, JsonValueType> mainKeyType;
|
|
std::unordered_map<std::string, std::unordered_map<std::string, JsonValueType>> subKeyType;
|
|
|
|
std::unordered_map<std::string, std::string> transData;
|
|
std::unordered_map<std::string, std::vector<std::string>> transStrListData;
|
|
|
|
[[nodiscard]] JsonValueType GetMainKeyType(const std::string& mainKey) const {
|
|
if (auto it = mainKeyType.find(mainKey); it != mainKeyType.end()) {
|
|
return it->second;
|
|
}
|
|
return JsonValueType::JVT_Unsupported;
|
|
}
|
|
|
|
[[nodiscard]] JsonValueType GetSubKeyType(const std::string& parentKey, const std::string& subKey) const {
|
|
if (auto it = subKeyType.find(parentKey); it != subKeyType.end()) {
|
|
if (auto subIt = it->second.find(subKey); subIt != it->second.end()) {
|
|
return subIt->second;
|
|
}
|
|
}
|
|
return JsonValueType::JVT_Unsupported;
|
|
}
|
|
};
|
|
|
|
static std::unordered_map<std::string, TableLocalData> masterLocalData;
|
|
|
|
class FieldController {
|
|
void* self;
|
|
std::string self_klass_name;
|
|
|
|
static std::string capitalizeFirstLetter(const std::string& input) {
|
|
if (input.empty()) return input;
|
|
std::string result = input;
|
|
result[0] = static_cast<char>(std::toupper(result[0]));
|
|
return result;
|
|
}
|
|
|
|
Il2cppUtils::MethodInfo* GetGetSetMethodFromCache(const std::string& fieldName, int argsCount,
|
|
std::unordered_map<std::string, Il2cppUtils::MethodInfo*>& fromCache, const std::string& prefix = "set_") {
|
|
const std::string methodName = prefix + capitalizeFirstLetter(fieldName);
|
|
const std::string searchName = self_klass_name + "." + methodName;
|
|
|
|
if (auto it = fromCache.find(searchName); it != fromCache.end()) {
|
|
return it->second;
|
|
}
|
|
auto set_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
|
|
self_klass,
|
|
methodName.c_str(),
|
|
argsCount
|
|
);
|
|
fromCache.emplace(searchName, set_mtd);
|
|
return set_mtd;
|
|
}
|
|
|
|
public:
|
|
Il2cppUtils::Il2CppClassHead* self_klass;
|
|
|
|
explicit FieldController(void* from) {
|
|
if (!from) {
|
|
self = nullptr;
|
|
return;
|
|
}
|
|
self = from;
|
|
self_klass = Il2cppUtils::get_class_from_instance(self);
|
|
if (self_klass) {
|
|
self_klass_name = self_klass->name;
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
T ReadField(const std::string& fieldName) {
|
|
if (!self) return T();
|
|
auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_");
|
|
if (get_mtd) {
|
|
return reinterpret_cast<T (*)(void*, void*)>(get_mtd->methodPointer)(self, get_mtd);
|
|
}
|
|
|
|
auto field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>(
|
|
"il2cpp_class_get_field_from_name",
|
|
self_klass,
|
|
(fieldName + '_').c_str()
|
|
);
|
|
if (!field) {
|
|
return T();
|
|
}
|
|
return Il2cppUtils::ClassGetFieldValue<T>(self, field);
|
|
}
|
|
|
|
template<typename T>
|
|
void SetField(const std::string& fieldName, T value) {
|
|
if (!self) return;
|
|
auto set_mtd = GetGetSetMethodFromCache(fieldName, 1, fieldSetCache, "set_");
|
|
if (set_mtd) {
|
|
reinterpret_cast<void (*)(void*, T, void*)>(
|
|
set_mtd->methodPointer
|
|
)(self, value, set_mtd);
|
|
return;
|
|
}
|
|
auto field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>(
|
|
"il2cpp_class_get_field_from_name",
|
|
self_klass,
|
|
(fieldName + '_').c_str()
|
|
);
|
|
if (!field) return;
|
|
Il2cppUtils::ClassSetFieldValue(self, field, value);
|
|
}
|
|
|
|
int ReadIntField(const std::string& fieldName) {
|
|
return ReadField<int>(fieldName);
|
|
}
|
|
|
|
Il2cppString* ReadStringField(const std::string& fieldName) {
|
|
if (!self) return nullptr;
|
|
auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_");
|
|
if (!get_mtd) {
|
|
return ReadField<Il2cppString*>(fieldName);
|
|
}
|
|
auto returnClass = UnityResolve::Invoke<Il2cppUtils::Il2CppClassHead*>(
|
|
"il2cpp_class_from_type",
|
|
UnityResolve::Invoke<void*>("il2cpp_method_get_return_type", get_mtd)
|
|
);
|
|
if (!returnClass) {
|
|
return reinterpret_cast<Il2cppString* (*)(void*, void*)>(
|
|
get_mtd->methodPointer
|
|
)(self, get_mtd);
|
|
}
|
|
auto isEnum = UnityResolve::Invoke<bool>("il2cpp_class_is_enum", returnClass);
|
|
if (!isEnum) {
|
|
return reinterpret_cast<Il2cppString* (*)(void*, void*)>(
|
|
get_mtd->methodPointer
|
|
)(self, get_mtd);
|
|
}
|
|
auto enumMap = Il2cppUtils::EnumToValueMap(returnClass, true);
|
|
auto enumValue = reinterpret_cast<int (*)(void*, void*)>(
|
|
get_mtd->methodPointer
|
|
)(self, get_mtd);
|
|
if (auto it = enumMap.find(enumValue); it != enumMap.end()) {
|
|
return Il2cppString::New(it->second);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void SetStringField(const std::string& fieldName, const std::string& value) {
|
|
if (!self) return;
|
|
auto newString = Il2cppString::New(value);
|
|
SetField(fieldName, newString);
|
|
}
|
|
|
|
void SetStringListField(const std::string& fieldName, const std::vector<std::string>& data) {
|
|
if (!self) return;
|
|
static auto List_String_klass = Il2cppUtils::get_system_class_from_reflection_type_str(
|
|
"System.Collections.Generic.List`1[System.String]"
|
|
);
|
|
static auto List_String_ctor_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
|
|
List_String_klass, ".ctor", 0
|
|
);
|
|
static auto List_String_ctor = reinterpret_cast<void (*)(void*, void*)>(
|
|
List_String_ctor_mtd->methodPointer
|
|
);
|
|
|
|
auto newList = UnityResolve::Invoke<void*>("il2cpp_object_new", List_String_klass);
|
|
List_String_ctor(newList, List_String_ctor_mtd);
|
|
|
|
Il2cppUtils::Tools::CSListEditor<Il2cppString*> newListEditor(newList);
|
|
for (auto& s : data) {
|
|
newListEditor.Add(Il2cppString::New(s));
|
|
}
|
|
SetField(fieldName, newList);
|
|
}
|
|
|
|
void* ReadObjectField(const std::string& fieldName) {
|
|
if (!self) return nullptr;
|
|
return ReadField<void*>(fieldName);
|
|
}
|
|
|
|
void* ReadObjectListField(const std::string& fieldName) {
|
|
if (!self) return nullptr;
|
|
return ReadField<void*>(fieldName);
|
|
}
|
|
|
|
static FieldController CreateSubFieldController(void* subObj) {
|
|
return FieldController(subObj);
|
|
}
|
|
|
|
FieldController CreateSubFieldController(const std::string& subObjName) {
|
|
auto field = ReadObjectField(subObjName);
|
|
return FieldController(field);
|
|
}
|
|
};
|
|
|
|
|
|
JsonValueType checkJsonValueType(const nlohmann::json& j) {
|
|
if (j.is_string()) return JsonValueType::JVT_String;
|
|
if (j.is_number_integer()) return JsonValueType::JVT_Int;
|
|
if (j.is_object()) return JsonValueType::JVT_Object;
|
|
if (j.is_array()) {
|
|
if (!j.empty()) {
|
|
if (j.begin()->is_object()) {
|
|
return JsonValueType::JVT_ArrayObject;
|
|
}
|
|
else if (j.begin()->is_string()) {
|
|
return JsonValueType::JVT_ArrayString;
|
|
}
|
|
}
|
|
else {
|
|
return JsonValueType::JVT_NeedMore_EmptyArray;
|
|
}
|
|
}
|
|
return JsonValueType::JVT_Unsupported;
|
|
}
|
|
|
|
|
|
std::string ReadFileToString(const std::filesystem::path& path) {
|
|
std::ifstream ifs(path, std::ios::binary);
|
|
if (!ifs) return {};
|
|
std::stringstream buffer;
|
|
buffer << ifs.rdbuf();
|
|
return buffer.str();
|
|
}
|
|
|
|
namespace Load {
|
|
std::vector<std::string> ArrayStrJsonToVec(nlohmann::json& data) {
|
|
return data;
|
|
}
|
|
|
|
bool BuildObjectItemLocalRule(nlohmann::json& transData, ItemRule& itemRule) {
|
|
// transData: data[]
|
|
bool hasSuccess = false;
|
|
for (auto& data : transData) {
|
|
// data: {"id": "xxx", "produceDescriptions": [{"k", "v"}], "descriptions": {"k2", "v2"}}
|
|
if (!data.is_object()) continue;
|
|
for (auto& [key, value] : data.items()) {
|
|
// key: "id", value: "xxx"
|
|
// key: "produceDescriptions", value: [{"k", "v"}]
|
|
const auto valueType = checkJsonValueType(value);
|
|
switch (valueType) {
|
|
case JsonValueType::JVT_String:
|
|
// case JsonValueType::JVT_Int:
|
|
case JsonValueType::JVT_ArrayString: {
|
|
if (std::find(itemRule.mainPrimaryKey.begin(), itemRule.mainPrimaryKey.end(), key) != itemRule.mainPrimaryKey.end()) {
|
|
continue;
|
|
}
|
|
if (auto it = std::find(itemRule.mainLocalKey.begin(), itemRule.mainLocalKey.end(), key); it == itemRule.mainLocalKey.end()) {
|
|
itemRule.mainLocalKey.emplace_back(key);
|
|
}
|
|
hasSuccess = true;
|
|
} break;
|
|
|
|
case JsonValueType::JVT_Object: {
|
|
ItemRule currRule{ .mainPrimaryKey = itemRule.subPrimaryKey[key] };
|
|
|
|
auto vJson = nlohmann::json::array();
|
|
vJson.push_back(value);
|
|
|
|
if (BuildObjectItemLocalRule(vJson, currRule)) {
|
|
itemRule.subLocalKey.emplace(key, currRule.mainLocalKey);
|
|
hasSuccess = true;
|
|
}
|
|
} break;
|
|
|
|
case JsonValueType::JVT_ArrayObject: {
|
|
for (auto& obj : value) {
|
|
// obj: {"k", "v"}
|
|
ItemRule currRule{ .mainPrimaryKey = itemRule.subPrimaryKey[key] };
|
|
if (BuildObjectItemLocalRule(value, currRule)) {
|
|
itemRule.subLocalKey.emplace(key, currRule.mainLocalKey);
|
|
hasSuccess = true;
|
|
break;
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case JsonValueType::JVT_Unsupported:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (hasSuccess) break;
|
|
}
|
|
return hasSuccess;
|
|
}
|
|
|
|
bool GetItemRule(nlohmann::json& fullData, ItemRule& itemRule) {
|
|
auto& primaryKeys = fullData["rules"]["primaryKeys"];
|
|
auto& transData = fullData["data"];
|
|
if (!primaryKeys.is_array()) return false;
|
|
if (!transData.is_array()) return false;
|
|
|
|
// 首先构造 mainPrimaryKey 规则
|
|
for (auto& pkItem : primaryKeys) {
|
|
if (!pkItem.is_string()) {
|
|
return false;
|
|
}
|
|
std::string pk = pkItem;
|
|
auto dotCount = std::ranges::count(pk, '.');
|
|
if (dotCount == 0) {
|
|
itemRule.mainPrimaryKey.emplace_back(pk);
|
|
}
|
|
else if (dotCount == 1) {
|
|
auto [parentKey, subKey] = Misc::StringFormat::split_once(pk, ".");
|
|
if (itemRule.subPrimaryKey.contains(parentKey)) {
|
|
itemRule.subPrimaryKey[parentKey].emplace_back(subKey);
|
|
}
|
|
else {
|
|
itemRule.subPrimaryKey.emplace(parentKey, std::vector<std::string>{subKey});
|
|
}
|
|
}
|
|
else {
|
|
Log::ErrorFmt("Unsupported depth: %d", dotCount);
|
|
continue;
|
|
}
|
|
}
|
|
return BuildObjectItemLocalRule(transData, itemRule);
|
|
}
|
|
|
|
std::string BuildBaseMainUniqueKey(nlohmann::json& data, TableLocalData& tableLocalData) {
|
|
try {
|
|
std::string mainBaseUniqueKey;
|
|
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainPrimaryKey) {
|
|
if (!data.contains(mainPrimaryKey)) {
|
|
return "";
|
|
}
|
|
auto& value = data[mainPrimaryKey];
|
|
if (value.is_number_integer()) {
|
|
mainBaseUniqueKey.append(std::to_string(value.get<int>()));
|
|
}
|
|
else {
|
|
mainBaseUniqueKey.append(value);
|
|
}
|
|
mainBaseUniqueKey.push_back('|');
|
|
}
|
|
return mainBaseUniqueKey;
|
|
}
|
|
catch (std::exception& e) {
|
|
Log::ErrorFmt("LoadData - BuildBaseMainUniqueKey failed: %s", e.what());
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
void BuildBaseObjectSubUniqueKey(nlohmann::json& value, JsonValueType valueType, std::string& currLocalKey) {
|
|
switch (valueType) {
|
|
case JsonValueType::JVT_String:
|
|
currLocalKey.append(value.get<std::string>()); // p_card-00-acc-0_002|0|produceDescriptions|ProduceDescriptionType_Exam|
|
|
currLocalKey.push_back('|');
|
|
break;
|
|
case JsonValueType::JVT_Int:
|
|
currLocalKey.append(std::to_string(value.get<int>()));
|
|
currLocalKey.push_back('|');
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool BuildUniqueKeyValue(nlohmann::json& data, TableLocalData& tableLocalData) {
|
|
// 首先处理 main 部分
|
|
const std::string mainBaseUniqueKey = BuildBaseMainUniqueKey(data, tableLocalData); // p_card-00-acc-0_002|0|
|
|
if (mainBaseUniqueKey.empty()) return false;
|
|
for (auto& mainLocalKey : tableLocalData.itemRule.mainLocalKey) {
|
|
if (!data.contains(mainLocalKey)) continue;
|
|
auto& currLocalValue = data[mainLocalKey];
|
|
auto currUniqueKey = mainBaseUniqueKey + mainLocalKey; // p_card-00-acc-0_002|0|name
|
|
if (tableLocalData.GetMainKeyType(mainLocalKey) == JsonValueType::JVT_ArrayString) {
|
|
tableLocalData.transStrListData.emplace(currUniqueKey, ArrayStrJsonToVec(currLocalValue));
|
|
}
|
|
else {
|
|
tableLocalData.transData.emplace(currUniqueKey, currLocalValue);
|
|
}
|
|
}
|
|
// 然后处理 sub 部分
|
|
/*
|
|
for (const auto& [subPrimaryParentKey, subPrimarySubKeys] : tableLocalData.itemRule.subPrimaryKey) {
|
|
if (!data.contains(subPrimaryParentKey)) continue;
|
|
|
|
const std::string subBaseUniqueKey = mainBaseUniqueKey + subPrimaryParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
|
|
|
|
auto subValueType = checkJsonValueType(data[subPrimaryParentKey]);
|
|
std::string currLocalKey = subBaseUniqueKey; // p_card-00-acc-0_002|0|produceDescriptions|
|
|
switch (subValueType) {
|
|
case JsonValueType::JVT_Object: {
|
|
for (auto& subPrimarySubKey : subPrimarySubKeys) {
|
|
if (!data[subPrimaryParentKey].contains(subPrimarySubKey)) continue;
|
|
auto& value = data[subPrimaryParentKey][subPrimarySubKey];
|
|
auto valueType = tableLocalData.GetSubKeyType(subPrimaryParentKey, subPrimarySubKey);
|
|
BuildBaseObjectSubUniqueKey(value, valueType, currLocalKey); // p_card-00-acc-0_002|0|produceDescriptions|ProduceDescriptionType_Exam|
|
|
}
|
|
} break;
|
|
case JsonValueType::JVT_ArrayObject: {
|
|
int currIndex = 0;
|
|
for (auto& obj : data[subPrimaryParentKey]) {
|
|
for (auto& subPrimarySubKey : subPrimarySubKeys) {
|
|
|
|
}
|
|
currIndex++;
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}*/
|
|
|
|
for (const auto& [subLocalParentKey, subLocalSubKeys] : tableLocalData.itemRule.subLocalKey) {
|
|
if (!data.contains(subLocalParentKey)) continue;
|
|
|
|
const std::string subBaseUniqueKey = mainBaseUniqueKey + subLocalParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
|
|
auto subValueType = checkJsonValueType(data[subLocalParentKey]);
|
|
if (subValueType != JsonValueType::JVT_NeedMore_EmptyArray) {
|
|
tableLocalData.mainKeyType.emplace(subLocalParentKey, subValueType); // 在这里插入 subParent 的类型
|
|
}
|
|
switch (subValueType) {
|
|
case JsonValueType::JVT_Object: {
|
|
for (auto& localSubKey : subLocalSubKeys) {
|
|
const std::string currLocalUniqueKey = subBaseUniqueKey + localSubKey; // p_card-00-acc-0_002|0|produceDescriptions|text
|
|
if (tableLocalData.GetSubKeyType(subLocalParentKey, localSubKey) == JsonValueType::JVT_ArrayString) {
|
|
tableLocalData.transStrListData.emplace(currLocalUniqueKey, ArrayStrJsonToVec(data[subLocalParentKey][localSubKey]));
|
|
}
|
|
else {
|
|
tableLocalData.transData.emplace(currLocalUniqueKey, data[subLocalParentKey][localSubKey]);
|
|
}
|
|
}
|
|
} break;
|
|
case JsonValueType::JVT_ArrayObject: {
|
|
int currIndex = 0;
|
|
for (auto& obj : data[subLocalParentKey]) {
|
|
for (auto& localSubKey : subLocalSubKeys) {
|
|
std::string currLocalUniqueKey = subBaseUniqueKey; // p_card-00-acc-0_002|0|produceDescriptions|
|
|
currLocalUniqueKey.push_back('[');
|
|
currLocalUniqueKey.append(std::to_string(currIndex));
|
|
currLocalUniqueKey.append("]|");
|
|
currLocalUniqueKey.append(localSubKey); // p_card-00-acc-0_002|0|produceDescriptions|[0]|text
|
|
|
|
if (tableLocalData.GetSubKeyType(subLocalParentKey, localSubKey) == JsonValueType::JVT_ArrayString) {
|
|
// if (obj[localSubKey].is_array()) {
|
|
tableLocalData.transStrListData.emplace(currLocalUniqueKey, ArrayStrJsonToVec(obj[localSubKey]));
|
|
}
|
|
else if (obj[localSubKey].is_string()) {
|
|
tableLocalData.transData.emplace(currLocalUniqueKey, obj[localSubKey]);
|
|
}
|
|
}
|
|
currIndex++;
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define MainKeyTypeProcess() if (!data.contains(mainPrimaryKey)) { Log::ErrorFmt("mainPrimaryKey: %s not found", mainPrimaryKey.c_str()); isFailed = true; break; } \
|
|
auto currType = checkJsonValueType(data[mainPrimaryKey]); \
|
|
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
|
|
tableLocalData.mainKeyType[mainPrimaryKey] = currType
|
|
#define SubKeyTypeProcess() if (!data.contains(subKeyParent)) { Log::ErrorFmt("subKeyParent: %s not found", subKeyParent.c_str()); isFailed = true; break; } \
|
|
for (auto& subKey : subKeys) { \
|
|
auto& subKeyValue = data[subKeyParent]; \
|
|
if (subKeyValue.is_object()) { \
|
|
if (!subKeyValue.contains(subKey)) { \
|
|
Log::ErrorFmt("subKey: %s not in subKeyParent: %s", subKey.c_str(), subKeyParent.c_str()); isFailed = true; break; \
|
|
} \
|
|
auto currType = checkJsonValueType(subKeyValue[subKey]); \
|
|
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
|
|
tableLocalData.subKeyType[subKeyParent].emplace(subKey, currType); \
|
|
} \
|
|
else if (subKeyValue.is_array()) { \
|
|
if (subKeyValue.empty()) goto NextLoop; \
|
|
for (auto& i : subKeyValue) { \
|
|
if (!i.is_object()) continue; \
|
|
if (!i.contains(subKey)) continue; \
|
|
auto currType = checkJsonValueType(i[subKey]); \
|
|
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
|
|
tableLocalData.subKeyType[subKeyParent].emplace(subKey, currType); \
|
|
break; \
|
|
} \
|
|
} \
|
|
else { \
|
|
goto NextLoop;\
|
|
} \
|
|
}
|
|
|
|
bool GetTableLocalData(nlohmann::json& fullData, TableLocalData& tableLocalData) {
|
|
bool isFailed = false;
|
|
|
|
// 首先 Build mainKeyType 和 subKeyType
|
|
for (auto& data : fullData["data"]) {
|
|
if (!data.is_object()) continue;
|
|
|
|
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainPrimaryKey) {
|
|
MainKeyTypeProcess();
|
|
}
|
|
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainLocalKey) {
|
|
MainKeyTypeProcess();
|
|
}
|
|
|
|
for (const auto& [subKeyParent, subKeys] : tableLocalData.itemRule.subPrimaryKey) {
|
|
SubKeyTypeProcess()
|
|
|
|
if (isFailed) break;
|
|
}
|
|
for (const auto& [subKeyParent, subKeys] : tableLocalData.itemRule.subLocalKey) {
|
|
SubKeyTypeProcess()
|
|
if (isFailed) break;
|
|
}
|
|
if (!isFailed) break;
|
|
NextLoop:
|
|
;
|
|
}
|
|
|
|
if (isFailed) return false;
|
|
|
|
bool hasSuccess = false;
|
|
// 然后构造 transData
|
|
for (auto& data : fullData["data"]) {
|
|
if (!data.is_object()) continue;
|
|
if (BuildUniqueKeyValue(data, tableLocalData)) {
|
|
hasSuccess = true;
|
|
}
|
|
}
|
|
if (!hasSuccess) {
|
|
Log::ErrorFmt("BuildUniqueKeyValue failed.");
|
|
}
|
|
return hasSuccess;
|
|
}
|
|
|
|
void LoadData() {
|
|
masterLocalData.clear();
|
|
static auto masterDir = Local::GetBasePath() / "local-files" / "masterTrans";
|
|
if (!std::filesystem::is_directory(masterDir)) {
|
|
Log::ErrorFmt("LoadData: not found: %s", masterDir.string().c_str());
|
|
return;
|
|
}
|
|
|
|
bool isFirstIteration = true;
|
|
for (auto& p : std::filesystem::directory_iterator(masterDir)) {
|
|
if (isFirstIteration) {
|
|
auto totalFileCount = std::distance(
|
|
std::filesystem::directory_iterator(masterDir),
|
|
std::filesystem::directory_iterator{}
|
|
);
|
|
UnityResolveProgress::classProgress.total = totalFileCount <= 0 ? 1 : totalFileCount;
|
|
isFirstIteration = false;
|
|
}
|
|
UnityResolveProgress::classProgress.current++;
|
|
|
|
if (!p.is_regular_file()) continue;
|
|
const auto& path = p.path();
|
|
if (path.extension() != ".json") continue;
|
|
|
|
std::string tableName = path.stem().string();
|
|
auto fileContent = ReadFileToString(path);
|
|
if (fileContent.empty()) continue;
|
|
|
|
try {
|
|
auto j = nlohmann::json::parse(fileContent);
|
|
if (!j.contains("rules") || !j["rules"].contains("primaryKeys")) {
|
|
continue;
|
|
}
|
|
ItemRule currRule;
|
|
if (!GetItemRule(j, currRule)) {
|
|
Log::ErrorFmt("GetItemRule failed: %s", path.string().c_str());
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
if (tableName == "ProduceStepEventDetail") {
|
|
for (auto& i : currRule.mainLocalKey) {
|
|
Log::DebugFmt("currRule.mainLocalKey: %s", i.c_str());
|
|
}
|
|
for (auto& i : currRule.mainPrimaryKey) {
|
|
Log::DebugFmt("currRule.mainPrimaryKey: %s", i.c_str());
|
|
}
|
|
for (auto& i : currRule.subLocalKey) {
|
|
for (auto& m : i.second) {
|
|
Log::DebugFmt("currRule.subLocalKey: %s - %s", i.first.c_str(), m.c_str());
|
|
}
|
|
}
|
|
for (auto& i : currRule.subPrimaryKey) {
|
|
for (auto& m : i.second) {
|
|
Log::DebugFmt("currRule.subPrimaryKey: %s - %s", i.first.c_str(), m.c_str());
|
|
}
|
|
}
|
|
}*/
|
|
|
|
TableLocalData tableLocalData{ .itemRule = currRule };
|
|
if (GetTableLocalData(j, tableLocalData)) {
|
|
for (auto& i : tableLocalData.transData) {
|
|
// Log::DebugFmt("%s: %s -> %s", tableName.c_str(), i.first.c_str(), i.second.c_str());
|
|
Local::translatedText.emplace(i.second);
|
|
}
|
|
for (auto& i : tableLocalData.transStrListData) {
|
|
for (auto& str : i.second) {
|
|
// Log::DebugFmt("%s[]: %s -> %s", tableName.c_str(), i.first.c_str(), str.c_str());
|
|
Local::translatedText.emplace(str);
|
|
}
|
|
}
|
|
|
|
/*
|
|
if (tableName == "ProduceStepEventDetail") {
|
|
for (auto& i : tableLocalData.mainKeyType) {
|
|
Log::DebugFmt("mainKeyType: %s -> %d", i.first.c_str(), i.second);
|
|
}
|
|
for (auto& i : tableLocalData.subKeyType) {
|
|
for (auto& m : i.second) {
|
|
Log::DebugFmt("subKeyType: %s - %s -> %d", i.first.c_str(), m.first.c_str(), m.second);
|
|
}
|
|
}
|
|
}*/
|
|
// JVT_ArrayString in HelpCategory, ProduceStory, Tutorial
|
|
|
|
masterLocalData.emplace(tableName, std::move(tableLocalData));
|
|
}
|
|
else {
|
|
Log::ErrorFmt("GetTableLocalData failed: %s", path.string().c_str());
|
|
}
|
|
} catch (std::exception& e) {
|
|
Log::ErrorFmt("MasterLocal::LoadData: parse error in '%s': %s",
|
|
path.string().c_str(), e.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LoadData() {
|
|
return Load::LoadData();
|
|
}
|
|
|
|
std::string GetTransString(const std::string& key, const TableLocalData& localData) {
|
|
if (auto it = localData.transData.find(key); it != localData.transData.end()) {
|
|
return it->second;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::vector<std::string> GetTransArrayString(const std::string& key, const TableLocalData& localData) {
|
|
if (auto it = localData.transStrListData.find(key); it != localData.transStrListData.end()) {
|
|
return it->second;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void LocalizeMasterItem(FieldController& fc, const std::string& tableName) {
|
|
auto it = masterLocalData.find(tableName);
|
|
if (it == masterLocalData.end()) return;
|
|
const auto& localData = it->second;
|
|
|
|
// 首先拼 BasePrimaryKey
|
|
std::string baseDataKey; // p_card-00-acc-0_002|0|
|
|
for (auto& mainPk : localData.itemRule.mainPrimaryKey) {
|
|
auto mainPkType = localData.GetMainKeyType(mainPk);
|
|
switch (mainPkType) {
|
|
case JsonValueType::JVT_Int: {
|
|
auto readValue = std::to_string(fc.ReadIntField(mainPk));
|
|
baseDataKey.append(readValue);
|
|
baseDataKey.push_back('|');
|
|
} break;
|
|
case JsonValueType::JVT_String: {
|
|
auto readValue = fc.ReadStringField(mainPk);
|
|
if (!readValue) return;
|
|
baseDataKey.append(readValue->ToString());
|
|
baseDataKey.push_back('|');
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 然后本地化 mainLocal
|
|
for (auto& mainLocal : localData.itemRule.mainLocalKey) {
|
|
std::string currSearchKey = baseDataKey;
|
|
currSearchKey.append(mainLocal); // p_card-00-acc-0_002|0|name
|
|
auto localVType = localData.GetMainKeyType(mainLocal);
|
|
switch (localVType) {
|
|
case JsonValueType::JVT_String: {
|
|
auto localValue = GetTransString(currSearchKey, localData);
|
|
if (!localValue.empty()) {
|
|
fc.SetStringField(mainLocal, localValue);
|
|
}
|
|
} break;
|
|
case JsonValueType::JVT_ArrayString: {
|
|
auto localValue = GetTransArrayString(currSearchKey, localData);
|
|
if (!localValue.empty()) {
|
|
fc.SetStringListField(mainLocal, localValue);
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 处理 sub
|
|
for (const auto& [subParentKey, subLocalKeys] : localData.itemRule.subLocalKey) {
|
|
const auto subBaseSearchKey = baseDataKey + subParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
|
|
|
|
const auto subParentType = localData.GetMainKeyType(subParentKey);
|
|
switch (subParentType) {
|
|
case JsonValueType::JVT_Object: {
|
|
auto subParentField = fc.CreateSubFieldController(subParentKey);
|
|
for (const auto& subLocalKey : subLocalKeys) {
|
|
const auto currSearchKey = subBaseSearchKey + subLocalKey; // p_card-00-acc-0_002|0|produceDescriptions|text
|
|
auto localKeyType = localData.GetSubKeyType(subParentKey, subLocalKey);
|
|
if (localKeyType == JsonValueType::JVT_String) {
|
|
auto setData = GetTransString(currSearchKey, localData);
|
|
if (!setData.empty()) {
|
|
subParentField.SetStringField(subLocalKey, setData);
|
|
}
|
|
}
|
|
else if (localKeyType == JsonValueType::JVT_ArrayString) {
|
|
auto setData = GetTransArrayString(currSearchKey, localData);
|
|
if (!setData.empty()) {
|
|
subParentField.SetStringListField(subLocalKey, setData);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
case JsonValueType::JVT_ArrayObject: {
|
|
auto subArrField = fc.ReadObjectListField(subParentKey);
|
|
if (!subArrField) continue;
|
|
Il2cppUtils::Tools::CSListEditor<void*> subListEdit(subArrField);
|
|
auto count = subListEdit.get_Count();
|
|
for (int idx = 0; idx < count; idx++) {
|
|
auto currItem = subListEdit.get_Item(idx);
|
|
if (!currItem) continue;
|
|
auto currFc = FieldController::CreateSubFieldController(currItem);
|
|
|
|
std::string currSearchBaseKey = subBaseSearchKey; // p_card-00-acc-0_002|0|produceDescriptions|
|
|
currSearchBaseKey.push_back('[');
|
|
currSearchBaseKey.append(std::to_string(idx));
|
|
currSearchBaseKey.append("]|"); // p_card-00-acc-0_002|0|produceDescriptions|[0]|
|
|
|
|
for (const auto& subLocalKey : subLocalKeys) {
|
|
std::string currSearchKey = currSearchBaseKey + subLocalKey; // p_card-00-acc-0_002|0|produceDescriptions|[0]|text
|
|
|
|
auto localKeyType = localData.GetSubKeyType(subParentKey, subLocalKey);
|
|
|
|
/*
|
|
if (tableName == "ProduceStepEventDetail") {
|
|
Log::DebugFmt("localKeyType: %d currSearchKey: %s", localKeyType, currSearchKey.c_str());
|
|
}*/
|
|
|
|
if (localKeyType == JsonValueType::JVT_String) {
|
|
auto setData = GetTransString(currSearchKey, localData);
|
|
if (!setData.empty()) {
|
|
currFc.SetStringField(subLocalKey, setData);
|
|
}
|
|
}
|
|
else if (localKeyType == JsonValueType::JVT_ArrayString) {
|
|
auto setData = GetTransArrayString(currSearchKey, localData);
|
|
if (!setData.empty()) {
|
|
currFc.SetStringListField(subLocalKey, setData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void LocalizeMasterItem(void* item, const std::string& tableName) {
|
|
if (!Config::useMasterTrans) return;
|
|
// Log::DebugFmt("LocalizeMasterItem: %s", tableName.c_str());
|
|
FieldController fc(item);
|
|
LocalizeMasterItem(fc, tableName);
|
|
}
|
|
|
|
} // namespace GakumasLocal::MasterLocal
|