first commit
This commit is contained in:
commit
7dd9dd689e
40 changed files with 4100 additions and 0 deletions
74
ProtoGen/ProtoCache.cpp
Normal file
74
ProtoGen/ProtoCache.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#include "ProtoCache.h"
|
||||
#include "ProtoMetadata.h"
|
||||
|
||||
TypeCache TypeCache::init() {
|
||||
TypeCache cache;
|
||||
|
||||
auto corlib = Il2CppApi::AssemblyGetImage(Il2CppApi::DomainAssemblyOpen("mscorlib.dll"));
|
||||
auto assembly = Il2CppApi::AssemblyGetImage(Il2CppApi::DomainAssemblyOpen("Assembly-CSharp.dll"));
|
||||
|
||||
cache.type_map = std::map<std::size_t, CachedType> {
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "Object")),
|
||||
Object,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "Boolean")),
|
||||
Boolean,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "Byte")),
|
||||
Byte,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "SByte")),
|
||||
SByte,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "UInt16")),
|
||||
UInt16,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "Int16")),
|
||||
Int16,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "UInt32")),
|
||||
UInt32,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "Int32")),
|
||||
Int32,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "UInt64")),
|
||||
UInt64,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "Int64")),
|
||||
Int64,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "Single")),
|
||||
Single,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "Double")),
|
||||
Double,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "String")),
|
||||
String,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(corlib, "System", "Enum")),
|
||||
Enum,
|
||||
},
|
||||
{
|
||||
reinterpret_cast<std::size_t>(Il2CppApi::ClassFromName(assembly, "", BYTE_STRING)),
|
||||
ByteString,
|
||||
},
|
||||
};
|
||||
|
||||
return cache;
|
||||
}
|
||||
28
ProtoGen/ProtoCache.h
Normal file
28
ProtoGen/ProtoCache.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "Il2CppApi.h"
|
||||
#include <map>
|
||||
|
||||
enum CachedType{
|
||||
Object,
|
||||
Boolean,
|
||||
Byte,
|
||||
SByte,
|
||||
UInt16,
|
||||
Int16,
|
||||
UInt32,
|
||||
Int32,
|
||||
UInt64,
|
||||
Int64,
|
||||
Single,
|
||||
Double,
|
||||
String,
|
||||
ByteString,
|
||||
Any,
|
||||
Enum,
|
||||
};
|
||||
|
||||
struct TypeCache {
|
||||
std::map<std::size_t, CachedType> type_map;
|
||||
static TypeCache init();
|
||||
};
|
||||
376
ProtoGen/ProtoMetadata.cpp
Normal file
376
ProtoGen/ProtoMetadata.cpp
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
// #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);
|
||||
}
|
||||
80
ProtoGen/ProtoMetadata.h
Normal file
80
ProtoGen/ProtoMetadata.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include "Il2CppHelper.h"
|
||||
|
||||
#include "ProtoUtil.h"
|
||||
#include "ProtoCache.h"
|
||||
#include "ProtoOutput.h"
|
||||
|
||||
// Constantes equivalentes a cadenas estáticas de Rust
|
||||
constexpr const char* CODED_INPUT_STREAM = "CELNCCLEKFP";
|
||||
constexpr const char* MERGE_FROM = "MMANOOLJPGC";
|
||||
constexpr const char* GET_CMD_ID = "PIGAAPNKIGC";
|
||||
constexpr const char* PROTO_CLASS = "KKAGGNDBDHE";
|
||||
constexpr const char* BYTE_STRING = "HIHGFFMNNDN";
|
||||
|
||||
struct CachedFieldInfo {
|
||||
int field_number;
|
||||
int wire_type;
|
||||
size_t offset;
|
||||
bool is_enum;
|
||||
bool is_object;
|
||||
};
|
||||
|
||||
// Equivalente a OneofVariantInfo
|
||||
struct OneofVariantInfo {
|
||||
size_t oneof_enum_offset;
|
||||
const Il2CppType* variant_type;
|
||||
};
|
||||
|
||||
// Equivalente a FieldMinimalInfo
|
||||
struct FieldMinimalInfo {
|
||||
uint32_t xor_;
|
||||
size_t offset;
|
||||
uint32_t tag;
|
||||
std::optional<OneofVariantInfo> oneof_extra_data;
|
||||
};
|
||||
|
||||
// Equivalente a MessageMinimalInfo
|
||||
struct MessageMinimalInfo {
|
||||
uint16_t cmd_id;
|
||||
std::vector<FieldMinimalInfo> fields;
|
||||
|
||||
MessageMinimalInfo(uint16_t cmd_id_) : cmd_id(cmd_id_) {}
|
||||
};
|
||||
|
||||
// Equivalente a FieldDetectionInfo
|
||||
struct FieldDetectionInfo {
|
||||
size_t offset;
|
||||
std::optional<OneofVariantInfo> oneof_extra_data;
|
||||
};
|
||||
|
||||
class Bruteforcer {
|
||||
public:
|
||||
Il2CppObject* object;
|
||||
const uint8_t* merge_from_method;
|
||||
|
||||
std::vector<CachedFieldInfo> cached_fields;
|
||||
|
||||
// Constructor
|
||||
Bruteforcer(TypeCache type_cache, Il2CppObject* obj);
|
||||
// Método input
|
||||
std::optional<FieldDetectionInfo> input(uint32_t wire_tag);
|
||||
private:
|
||||
std::optional<int> build_field_number(uint32_t wire_tag);
|
||||
std::optional<FieldDetectionInfo> build_field_oneof(uint32_t wire_tag);
|
||||
};
|
||||
|
||||
class ProtoMetadata {
|
||||
public:
|
||||
static std::string DumpProto(Il2CppClass* klass);
|
||||
static Il2CppObject* CreateInputStream(const std::vector<uint8_t>& buf);
|
||||
private:
|
||||
static std::string CsharpTypeToProtobufType(const TypeCache& cache, const Il2CppType* ty);
|
||||
};
|
||||
74
ProtoGen/ProtoOutput.cpp
Normal file
74
ProtoGen/ProtoOutput.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#include "ProtoOutput.h"
|
||||
using namespace proto;
|
||||
|
||||
std::ostream& proto::operator<<(std::ostream& os, const FieldComment& c) {
|
||||
os << " // offset: " << c.offset << ", xor const: " << c.xor_const;
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& proto::operator<<(std::ostream& os, const Field& f) {
|
||||
os << " " << f.kind << " " << f.name << " = " << f.number << ";";
|
||||
if (f.comment) {
|
||||
os << *(f.comment);
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& proto::operator<<(std::ostream& os, const Oneof& o) {
|
||||
os << " oneof " << o.name << " {\n";
|
||||
for (const auto& field : o.fields) {
|
||||
os << " " << field << "\n";
|
||||
}
|
||||
os << " }";
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& proto::operator<<(std::ostream& os, const Enum& e) {
|
||||
os << "enum " << e.name << " {\n";
|
||||
for (const auto& [name, value] : e.variants) {
|
||||
os << " " << name << " = " << value << ";\n";
|
||||
}
|
||||
os << "}\n";
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& proto::operator<<(std::ostream& os, const Message& m) {
|
||||
os << "message " << m.name << " {";
|
||||
if (m.cmd_id != 0) {
|
||||
os << " // CmdID: " << m.cmd_id;
|
||||
}
|
||||
os << "\n";
|
||||
|
||||
for (const auto& field : m.fields) {
|
||||
os << field << "\n";
|
||||
}
|
||||
|
||||
for (const auto& oneof : m.oneofs) {
|
||||
os << oneof << "\n";
|
||||
}
|
||||
|
||||
os << "}\n\n";
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& proto::operator<<(std::ostream& os, const ProtoItem& item) {
|
||||
if (std::holds_alternative<Message>(item)) {
|
||||
os << std::get<Message>(item);
|
||||
} else if (std::holds_alternative<Enum>(item)) {
|
||||
os << std::get<Enum>(item);
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& proto::operator<<(std::ostream& os, const ProtoFile& pf) {
|
||||
os << "syntax = \"" << pf.syntax << "\";\n";
|
||||
for (const auto& import : pf.imports) {
|
||||
os << "import \"" << import << "\";\n";
|
||||
}
|
||||
os << "\n";
|
||||
|
||||
for (const auto& item : pf.items) {
|
||||
os << item;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
58
ProtoGen/ProtoOutput.h
Normal file
58
ProtoGen/ProtoOutput.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
// Declaraciones de estructuras
|
||||
|
||||
namespace proto {
|
||||
struct FieldComment {
|
||||
size_t offset;
|
||||
uint32_t xor_const;
|
||||
};
|
||||
|
||||
struct Field {
|
||||
std::string kind;
|
||||
std::string name;
|
||||
uint32_t number;
|
||||
std::optional<FieldComment> comment;
|
||||
};
|
||||
|
||||
struct Oneof {
|
||||
std::string name;
|
||||
std::vector<Field> fields;
|
||||
};
|
||||
|
||||
struct Enum {
|
||||
std::string name;
|
||||
std::vector<std::pair<std::string, int32_t>> variants;
|
||||
};
|
||||
|
||||
struct Message {
|
||||
uint16_t cmd_id;
|
||||
std::string name;
|
||||
std::vector<Field> fields;
|
||||
std::vector<Oneof> oneofs;
|
||||
};
|
||||
|
||||
using ProtoItem = std::variant<Message, Enum>;
|
||||
struct ProtoFile {
|
||||
std::string syntax;
|
||||
std::vector<std::string> imports;
|
||||
std::vector<ProtoItem> items;
|
||||
};
|
||||
|
||||
// Declaración de operadores de salida
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const FieldComment& c);
|
||||
std::ostream& operator<<(std::ostream& os, const Field& f);
|
||||
std::ostream& operator<<(std::ostream& os, const Oneof& o);
|
||||
std::ostream& operator<<(std::ostream& os, const Enum& e);
|
||||
std::ostream& operator<<(std::ostream& os, const Message& m);
|
||||
std::ostream& operator<<(std::ostream& os, const ProtoItem& item);
|
||||
std::ostream& operator<<(std::ostream& os, const ProtoFile& pf);
|
||||
}
|
||||
32
ProtoGen/ProtoUtil.cpp
Normal file
32
ProtoGen/ProtoUtil.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#include "ProtoUtil.h"
|
||||
|
||||
size_t varint_length(uint32_t v) {
|
||||
if (v == 0) return 1;
|
||||
|
||||
size_t logcounter = 0;
|
||||
while (v > 0) {
|
||||
++logcounter;
|
||||
v >>= 7;
|
||||
}
|
||||
return logcounter;
|
||||
}
|
||||
|
||||
size_t encode_varint(std::vector<uint8_t>& dst, uint32_t value) {
|
||||
constexpr uint8_t MSB = 0b10000000;
|
||||
|
||||
uint32_t n = value;
|
||||
size_t i = 0;
|
||||
|
||||
while (n >= 0x80) {
|
||||
dst.push_back(MSB | static_cast<uint8_t>(n & 0x7F));
|
||||
++i;
|
||||
n >>= 7;
|
||||
}
|
||||
|
||||
dst.push_back(static_cast<uint8_t>(n));
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
uint32_t pack_wire_tag(uint32_t field_id, uint8_t wire_type) {
|
||||
return (field_id << 3) | static_cast<uint32_t>(wire_type);
|
||||
}
|
||||
32
ProtoGen/ProtoUtil.h
Normal file
32
ProtoGen/ProtoUtil.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
const uint8_t bytes_i32[4] = {0, 0, 0, 1};
|
||||
const uint8_t bytes_i64[8] = {0, 0, 0, 0, 0, 0, 0, 1};
|
||||
|
||||
const std::vector<uint8_t> VECTOR_ONE = {1};
|
||||
const std::vector<uint8_t> VECTOR_ZERO = {0};
|
||||
const std::vector<uint8_t> VECTOR_COLLECTION = {1, 0};
|
||||
|
||||
const std::vector<std::vector<uint8_t>> LENGTH_PREFIXED_SAMPLES = {
|
||||
{1, 0, 0, 0, 0},
|
||||
{1, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{5, 0x08, 0x01, 0x33, 0x01, 0x00},
|
||||
{5, 0x10, 0x01, 0x33, 0x10, 0x00},
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> LENGTH_I32 = {bytes_i32, bytes_i32 + 4};
|
||||
const std::vector<uint8_t> LENGTH_I64 = {bytes_i64, bytes_i64 + 8};
|
||||
|
||||
// Constantes wire types
|
||||
constexpr uint8_t WIRE_TYPE_VAR_INT = 0;
|
||||
constexpr uint8_t WIRE_TYPE_I64 = 1;
|
||||
constexpr uint8_t WIRE_TYPE_LENGTH_PREFIXED = 2;
|
||||
constexpr uint8_t WIRE_TYPE_I32 = 5;
|
||||
|
||||
// Prototipos de funciones
|
||||
size_t varint_length(uint32_t v);
|
||||
size_t encode_varint(std::vector<uint8_t>& dst, uint32_t value);
|
||||
uint32_t pack_wire_tag(uint32_t field_id, uint8_t wire_type);
|
||||
Loading…
Add table
Add a link
Reference in a new issue