376 lines
No EOL
16 KiB
C++
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);
|
|
} |