dumpcs-dumper/ProtoGen/ProtoMetadata.cpp
2026-01-05 23:50:53 +03:00

376 lines
No EOL
16 KiB
C++

// #include <z3++.h>
#include <chrono>
#include "ProtoMetadata.h"
const std::vector<int> wire_types = {WIRE_TYPE_VAR_INT, WIRE_TYPE_LENGTH_PREFIXED, WIRE_TYPE_I32, WIRE_TYPE_I64};
std::string ProtoMetadata::DumpProto(Il2CppClass* klass) {
void* iter;
std::stringstream outPut;
TypeCache type_cache = TypeCache::init();
std::map<size_t, MessageMinimalInfo> minimal_info_map;
auto get_cmd_id = Il2CppApi::ClassGetMethodFromName(klass, GET_CMD_ID, 0);
if (!get_cmd_id) return {};
Il2CppObject* proto_instance = Il2CppApi::ObjectNew(klass);
Il2CppApi::MethodInvoke(Il2CppApi::ClassGetMethodFromName(klass, ".ctor", 0), proto_instance, nullptr);
Il2CppObject* cmd_id_boxed = Il2CppApi::MethodInvoke(get_cmd_id, proto_instance, nullptr);
auto cmd_id = *reinterpret_cast<const uint16_t*>(Il2CppApi::ObjectUnbox(cmd_id_boxed));
MessageMinimalInfo message_info(cmd_id);
Bruteforcer bruteforcer(type_cache, proto_instance);
for (int field_id = 1; field_id < 4096; ++field_id) {
bool detected = false;
for (int wire_type : wire_types) {
uint32_t tag = pack_wire_tag(field_id, wire_type);
auto input_result = bruteforcer.input(tag);
if (input_result.has_value()) {
const FieldDetectionInfo& info = input_result.value();
message_info.fields.emplace_back(FieldMinimalInfo{
0,
info.offset,
tag,
info.oneof_extra_data
});
detected = true;
break; // salir de wire_types para este field_id
}
}
if (detected) continue;
}
if (Il2CppApi::ClassIsEnum(Il2CppApi::ClassGetParent(klass))) {
std::cout << "Class is enum so we have to do a different thing" << std::endl;
std::string enum_name = Il2CppApi::ClassGetName(klass);
std::vector<std::pair<std::string, int32_t>> variants;
iter = nullptr;
while(auto field = Il2CppApi::ClassGetFields(klass, &iter)) {
std::cout << "Casting field to an IL2CPPField*" << std::endl;
auto value = Il2CppApi::FieldStaticGetValue(field);
std::cout << "Obtaining Field name from field" << std::endl;
auto name = Il2CppApi::FieldGetName(field);
if (!Il2CppApi::FieldIsInstance(field) && value == 0) {
std::cout << "Field with static_get_value == 0" << std::endl;
variants.emplace_back(enum_name + "_" + name, static_cast<int32_t>(value));
}
}
iter = nullptr;
while(auto field = Il2CppApi::ClassGetFields(klass, &iter)) {
std::cout << "Casting field to an IL2CPPField* 2" << std::endl;
auto value = Il2CppApi::FieldStaticGetValue(field);
std::cout << "Obtaining Field name from field 2" << std::endl;
auto name = Il2CppApi::FieldGetName(field);
if (!Il2CppApi::FieldIsInstance(field) && value != 0) {
std::cout << "Field with static_get_value != 0" << std::endl;
variants.emplace_back(enum_name + "_" + name, static_cast<int32_t>(value));
}
}
std::cout << "Emplacing the item" << std::endl;
proto::Enum enumItem = proto::Enum{
enum_name,
std::move(variants)
};
outPut << enumItem;
}
else {
std::vector<std::pair<size_t, proto::Field>> fields;
for (auto field_info : message_info.fields) {
if (field_info.oneof_extra_data.has_value()) continue;
uintptr_t* matching_field = nullptr;
iter = nullptr;
while (auto field = Il2CppApi::ClassGetFields(klass, &iter)) {
auto offset = Il2CppApi::FieldGetOffset(field);
if (Il2CppApi::FieldIsInstance(field) && offset == field_info.offset) {
matching_field = field;
break;
}
}
if (matching_field == nullptr) {
continue;
}
fields.push_back(std::make_pair(
Il2CppApi::FieldGetToken(matching_field),
proto::Field{
ProtoMetadata::CsharpTypeToProtobufType(type_cache, Il2CppApi::FieldGetType(matching_field)),
std::string(Il2CppApi::FieldGetName(matching_field)),
field_info.tag >> 3,
proto::FieldComment{
Il2CppApi::FieldGetOffset(matching_field),
field_info.xor_
}
}
));
}
std::sort(fields.begin(), fields.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
std::vector<proto::Oneof> oneofs;
// Procesar campos que sí tienen oneof_extra_data
for (auto field_info : message_info.fields) {
if (!field_info.oneof_extra_data.has_value()) continue;
auto oneof_data = field_info.oneof_extra_data.value();
uintptr_t* oneof_data_field = nullptr;
iter = nullptr;
while(auto f = Il2CppApi::ClassGetFields(klass, &iter)) {
auto offset = Il2CppApi::FieldGetOffset(f);
if (Il2CppApi::FieldIsInstance(f) && offset == field_info.offset) {
oneof_data_field = f;
break;
}
}
if (!oneof_data_field) continue;
uintptr_t* oneof_enum_field = nullptr;
iter = nullptr;
while(auto f = Il2CppApi::ClassGetFields(klass, &iter)) {
auto offset = Il2CppApi::FieldGetOffset(f);
if (Il2CppApi::FieldIsInstance(f) && offset == oneof_data.oneof_enum_offset) {
oneof_enum_field = f;
break;
}
}
if (!oneof_enum_field) continue;
Il2CppClass* oneof_enum = Il2CppApi::FromIl2CppType(Il2CppApi::FieldGetType(oneof_enum_field));
uintptr_t* oneof_case_enum_field = nullptr;
iter = nullptr;
while(auto f = Il2CppApi::ClassGetFields(oneof_enum, &iter)) {
auto value = Il2CppApi::FieldStaticGetValue(f);
if (!Il2CppApi::FieldIsInstance(f) && value == (field_info.tag >> 3)) {
oneof_case_enum_field = f;
break;
}
}
if (!oneof_case_enum_field) continue;
// Buscar o crear Oneof en vector oneofs
proto::Oneof* oneof_ptr = nullptr;
for (auto& o : oneofs) {
auto fieldName = Il2CppApi::FieldGetName(oneof_data_field);
if (o.name == fieldName) {
oneof_ptr = &o;
break;
}
}
if (!oneof_ptr) {
oneofs.emplace_back(proto::Oneof{std::string(Il2CppApi::FieldGetName(oneof_data_field)), {}});
oneof_ptr = &oneofs.back();
}
// Añadir Field al Oneof
oneof_ptr->fields.emplace_back(proto::Field{
ProtoMetadata::CsharpTypeToProtobufType(type_cache, oneof_data.variant_type),
Il2CppApi::FieldGetName(oneof_case_enum_field),
field_info.tag >> 3,
std::nullopt
});
}
// Construir Message y agregar a proto_file
std::vector<proto::Field> extractedFields;
for (const auto& p : fields) {
extractedFields.push_back(p.second);
}
proto::Message messageItem {
message_info.cmd_id,
Il2CppApi::ClassGetName(klass),
std::move(extractedFields),
std::move(oneofs),
};
outPut << messageItem;
}
return outPut.str();
}
// Constructor bruteforcer con cálculo directo del field_number
Bruteforcer::Bruteforcer(TypeCache type_cache, Il2CppObject* obj) : object(obj) {
Il2CppClass* klass = Il2CppApi::ObjectGetClass(obj);
void* iter = nullptr;
while (auto field = Il2CppApi::ClassGetFields(klass, &iter)) {
size_t offset = Il2CppApi::FieldGetOffset(field);
auto type = Il2CppApi::FieldGetType(field);
Il2CppClass* field_class = Il2CppApi::FromIl2CppType(type);
const char* type_name = Il2CppApi::ClassGetName(field_class);
int wire_type = -1;
if (strcmp(type_name, "Int32") == 0 ||
strcmp(type_name, "UInt32") == 0 ||
strcmp(type_name, "Int64") == 0 ||
strcmp(type_name, "UInt64") == 0 ||
strcmp(type_name, "Boolean") == 0 ||
strcmp(type_name, "Enum") == 0)
{
// En Protobuf: int32, int64, uint32, uint64, bool, enum → varint
wire_type = 0;
}
else if (strcmp(type_name, "String") == 0 ||
strcmp(type_name, "Object") == 0 ||
strcmp(type_name, "Byte[]") == 0)
{
// string, bytes, objetos serializados → length-delimited
wire_type = 2;
}
else if (strcmp(type_name, "Single") == 0 ||
strcmp(type_name, "Fixed32") == 0 ||
strcmp(type_name, "SFixed32") == 0)
{
// float, fixed32, sfixed32 → 32-bit
wire_type = 5;
}
else if (strcmp(type_name, "Double") == 0 ||
strcmp(type_name, "Fixed64") == 0 ||
strcmp(type_name, "SFixed64") == 0)
{
// double, fixed64, sfixed64 → 64-bit
wire_type = 1;
}
bool is_enum = Il2CppApi::ClassIsEnum(field_class);
bool is_object = (strcmp(type_name, "Object") == 0 || strcmp(type_name, "String") == 0);
// Aquí no secuencias field_number, lo calculas con la función dedicada después
cached_fields.push_back(CachedFieldInfo{
-1, // field_number aún no calculado
wire_type,
offset,
is_enum,
is_object
});
}
}
// Método directo que calcula field_number del wire_tag con formula protobuf
std::optional<int> Bruteforcer::build_field_number(uint32_t wire_tag) {
int field_number = wire_tag >> 3;
if (field_number >= 1 && field_number <= 4095) {
return field_number;
}
return std::nullopt;
}
// Función input usa cálculo directo y consulta cached_fields
std::optional<FieldDetectionInfo> Bruteforcer::input(uint32_t wire_tag) {
auto maybe_field_number = build_field_number(wire_tag);
if (!maybe_field_number.has_value()) return std::nullopt;
int field_number = maybe_field_number.value();
int wire_type = wire_tag & 0x07;
for (auto& f : cached_fields) {
if (f.wire_type == wire_type) {
// Ahora consideramos cualquier field_number que coincida con el calculado
// y asociamos el field_number a cached_fields así:
if (f.field_number == -1) {
f.field_number = field_number; // asignar el valor deducido
}
if (f.field_number == field_number) {
bool is_obj = false, is_enum = false;
for (const auto& ff : cached_fields) {
if (ff.offset == f.offset && ff.is_object) is_obj = true;
if (ff.is_enum) is_enum = true;
}
if (is_obj && is_enum) {
return build_field_oneof(wire_tag);
}
return FieldDetectionInfo{
f.offset,
std::nullopt
};
}
}
}
return std::nullopt;
}
std::optional<FieldDetectionInfo> Bruteforcer::build_field_oneof(uint32_t wire_tag) {
auto maybe_field_number = build_field_number(wire_tag);
if (!maybe_field_number.has_value()) return std::nullopt;
int field_number = maybe_field_number.value();
int wire_type = wire_tag & 0x07;
for (auto& f : cached_fields) {
// Considera solo campos con wire_type igual, y que tengan el mismo field_number o no asignado aún (-1)
if (f.wire_type == wire_type && (f.field_number == field_number || f.field_number == -1)) {
// Recolectar campos que compartan offset y sean objeto o enum
std::vector<const CachedFieldInfo*> candidates;
for (const auto& ff : cached_fields) {
if (ff.offset == f.offset && (ff.is_object || ff.is_enum)) {
candidates.push_back(&ff);
}
}
// Un campo oneof típico tiene exactamente dos candidatos: uno objeto, uno enum
if (candidates.size() == 2) {
const CachedFieldInfo* data_field = candidates[0];
const CachedFieldInfo* enum_field = candidates[1];
// Corregir si están invertidos (enum y objeto)
if (enum_field->is_enum && data_field->is_object) {
// Correcto orden
} else if (data_field->is_enum && enum_field->is_object) {
std::swap(data_field, enum_field);
} else {
// No es combinación válida enum-objeto
return std::nullopt;
}
// Usar Il2CppApi para obtener Il2CppType* del campo de datos
// Aquí hay que convertir correctamente el 'field' de la cache a tipo
// Asumo que tienes alguna forma de obtener Il2CppType* desde CachedFieldInfo->offset
// Si no, adapta según tu API
Il2CppClass* klass = Il2CppApi::ObjectGetClass(object);
Il2CppType* data_type = nullptr;
// Buscar campo en klass por offset para obtener tipo
void* iter = nullptr;
while (auto field = Il2CppApi::ClassGetFields(klass, &iter)) {
auto offset = Il2CppApi::FieldGetOffset(field);
if (offset == data_field->offset) {
data_type = const_cast<Il2CppType*>(Il2CppApi::FieldGetType(field));
break;
}
}
if (!data_type) {
// No se pudo obtener tipo, abortar
return std::nullopt;
}
return FieldDetectionInfo{
data_field->offset,
std::optional<OneofVariantInfo>{
OneofVariantInfo{
enum_field->offset,
data_type
}
}
};
}
}
}
return std::nullopt;
}
std::string ProtoMetadata::CsharpTypeToProtobufType(const TypeCache& cache, const Il2CppType* ty) {
if (!ty) return "unknown";
// 🔎 comprobar si es genérico
if (ty->type == IL2CPP_TYPE_GENERICINST) {
Il2CppClass* klass = Il2CppApi::FromIl2CppType(ty);
int gen_count = Il2CppApi::ClassGetGenericArgCount(klass);
if (gen_count == 1) {
auto generic_type = reinterpret_cast<const Il2CppType*>(Il2CppApi::ClassGetGenericArgType(klass, 0));
return "repeated " + CsharpTypeToProtobufType(cache, generic_type);
} else if (gen_count == 2) {
auto generic_type1 = reinterpret_cast<const Il2CppType*>(Il2CppApi::ClassGetGenericArgType(klass, 0));
auto generic_type2 = reinterpret_cast<const Il2CppType*>(Il2CppApi::ClassGetGenericArgType(klass, 1));
return "map<"
+ CsharpTypeToProtobufType(cache, generic_type1) + ", "
+ CsharpTypeToProtobufType(cache, generic_type2) + ">";
}
}
// 🔎 tipos básicos: mirar en type_cache
Il2CppClass* klass = Il2CppApi::FromIl2CppType(ty);
auto ptr = reinterpret_cast<std::size_t>(klass);
auto it = cache.type_map.find(ptr);
if (it != cache.type_map.end()) {
switch (it->second) {
case CachedType::Boolean: return "bool";
case CachedType::Int32: return "int32";
case CachedType::UInt32: return "uint32";
case CachedType::Int64: return "int64";
case CachedType::UInt64: return "uint64";
case CachedType::Single: return "float";
case CachedType::Double: return "double";
case CachedType::String: return "string";
case CachedType::ByteString: return "bytes";
case CachedType::Any: return "google.protobuf.Any";
default: throw std::runtime_error("unreachable CachedType");
}
}
// 🔎 si no es básico ni genérico → es tipo definido por el usuario
return Il2CppApi::ClassGetName(klass);
}