// #include #include #include "ProtoMetadata.h" const std::vector 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 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(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> 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(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(value)); } } std::cout << "Emplacing the item" << std::endl; proto::Enum enumItem = proto::Enum{ enum_name, std::move(variants) }; outPut << enumItem; } else { std::vector> 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 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 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 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 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 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 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(Il2CppApi::FieldGetType(field)); break; } } if (!data_type) { // No se pudo obtener tipo, abortar return std::nullopt; } return FieldDetectionInfo{ data_field->offset, std::optional{ 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(Il2CppApi::ClassGetGenericArgType(klass, 0)); return "repeated " + CsharpTypeToProtobufType(cache, generic_type); } else if (gen_count == 2) { auto generic_type1 = reinterpret_cast(Il2CppApi::ClassGetGenericArgType(klass, 0)); auto generic_type2 = reinterpret_cast(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(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); }