void ProcessMessage(TypeDefinition type) { var typeName = type.PackageName(); var result = new MessageNode(typeName); var defaults = new Dictionary <string, string>(); var deserializeWalker = new MethodWalker(type.Methods.First(m => m.Name == "Deserialize" && m.Parameters.Count == 3)); deserializeWalker.OnCall = info => { if (info.Conditions.Count == 0 && info.Method.Name.StartsWith("set_")) { var fieldName = info.Method.Name.Substring(4).ToLowerUnder(); var val = info.Arguments[1].ToString(); if (val.EndsWith("String::Empty")) { val = "\"\""; } else if (info.Arguments[1].GetType() == typeof(string)) { val = "\"" + val.Replace("\"", "\\\"") + "\""; } if (info.Method.Parameters.First().ParameterType.Name == "Boolean") { val = val == "0" ? "false" : "true"; } defaults[fieldName] = val; } }; deserializeWalker.Walk(); var written = new List <byte>(); var serializeWalker = new MethodWalker(type.Methods.First(m => m.Name == "Serialize" && m.Parameters.Count == 2)); serializeWalker.OnCall = info => { if (info.Method.Name == "WriteByte") { written.Add((byte)(int)info.Arguments[1]); return; } if (info.Arguments.Any(x => x.ToString().Contains("GetSerializedSize()"))) { return; } if (!info.Method.Name.StartsWith("Write") && info.Method.Name != "Serialize") { return; } // !!! packed vs not packed: // bnet.protocol.channel_invitation.IncrementChannelCountResponse/reservation_tokens: *not* packed // PegasusGame.ChooseEntities/entities: *packed* // not packed = {{tag, data}, {tag, data}, ...} // packed = {tag, size, data} // repeated fixed fields are packed by default. // // not packed: // call: ProtocolParser.WriteUInt64(arg0, V_0) // conditions: arg1.get_ReservationTokens().get_Count() > 0, &V_1.MoveNext() == true // // packed: // call: ProtocolParser.WriteUInt32(arg0, V_0) // size // conditions: arg1.get_Entities().get_Count() > 0, &V_2.MoveNext() == false // call: ProtocolParser.WriteUInt64(arg0, V_3) // datum // conditions: arg1.get_Entities().get_Count() > 0, &V_2.MoveNext() == false, &V_4.MoveNext() == true var iterConds = info.Conditions.Where(x => x.Lhs.Contains("MoveNext")); var listConds = info.Conditions.Where(x => x.Lhs.Contains("().get_Count()")); if (listConds.Any() && !iterConds.Any( x => x.Cmp == MethodWalker.Comparison.IsTrue)) { // Skip packed size writes: return; } var packed = iterConds.Any(x => x.Cmp == MethodWalker.Comparison.IsFalse); var label = FieldLabel.Invalid; if (iterConds.Any()) { label = FieldLabel.Repeated; } else if (info.Conditions.Any(x => x.Lhs.Contains(".Has"))) { label = FieldLabel.Optional; } else { label = FieldLabel.Required; } // Get name: var name = ""; if (label == FieldLabel.Repeated) { name = info.Conditions.First(x => x.Lhs.Contains("get_Count()")).Lhs; name = name.Substring(name.IndexOf(".get_") + 5); name = name.Substring(0, name.Length - 14); } else { name = info.Arguments[1].ToString(); if (name.StartsWith("Encoding.get_UTF8()")) { name = name.Substring(31, name.Length - 32); } name = name.Substring(name.IndexOf(".get_") + 5); name = name.Substring(0, name.Length - 2); } var prop = type.Properties.First(x => x.Name == name); name = name.ToLowerUnder(); // Pop tag: var tag = 0; var i = 0; while (true) { var b = written[i]; tag |= (b & 0x7f) << (7 * i); i += 1; if (0 == (b & 0x80)) { break; } } if (i != written.Count) { throw new InvalidProgramException( "bad tag bytes, not gonna recover from this state"); } written.Clear(); tag >>= 3; // Parse field type: var fieldType = FieldType.Invalid; var subType = default(TypeName); if (prop.PropertyType.Resolve().IsEnum) { fieldType = FieldType.Enum; var enumType = prop.PropertyType; enumTypes.Add(enumType.Resolve()); subType = enumType.PackageName(); fieldType = FieldType.Enum; if (defaults.ContainsKey(name)) { var intVal = Int32.Parse(defaults[name]); defaults[name] = enumType.Resolve().Fields .First(x => x.HasConstant && intVal == (int)x.Constant) .Name; } } else if (info.Method.Name == "Serialize") { var messageType = info.Method.DeclaringType; subType = messageType.PackageName(); fieldType = FieldType.Message; } else if (info.Method.DeclaringType.Name == "ProtocolParser") { var innerType = prop.PropertyType; if (innerType.IsGenericInstance) { innerType = (innerType as GenericInstanceType).GenericArguments.First(); } switch (innerType.Name) { // Int32, Int64, // UInt32, UInt64, // Bool, String, Bytes case "Int32": fieldType = FieldType.Int32; break; case "Int64": fieldType = FieldType.Int64; break; case "UInt32": fieldType = FieldType.UInt32; break; case "UInt64": fieldType = FieldType.UInt64; break; case "Boolean": fieldType = FieldType.Bool; break; case "String": fieldType = FieldType.String; break; case "Byte[]": fieldType = FieldType.Bytes; break; default: Console.WriteLine("unresolved type"); break; } } else if (info.Method.DeclaringType.Name == "BinaryWriter") { // Double, Float, // Fixed32, Fixed64, // SFixed32, SFixed64, switch (info.Method.Parameters.First().ParameterType.Name) { case "Double": fieldType = FieldType.Double; break; case "Single": fieldType = FieldType.Float; break; case "UInt32": fieldType = FieldType.Fixed32; break; case "UInt64": fieldType = FieldType.Fixed64; break; default: Console.WriteLine("unresolved type"); break; } } if (fieldType == FieldType.Invalid) { Console.WriteLine("unresolved type"); } var field = new FieldNode(name, label, fieldType, tag); field.TypeName = subType; field.Packed = packed; if (defaults.ContainsKey(name)) { field.DefaultValue = defaults[name]; } result.Fields.Add(field); }; serializeWalker.Walk(); AddPackageNode(typeName.Package, result); }
void ProcessService(TypeDefinition type) { ServiceNode service = null; var retType = default(TypeName); var constructorWalker = new MethodWalker(type.Methods.First(m => m.IsConstructor && !m.HasParameters)); constructorWalker.OnCall = info => { var methodDef = info.Method.Resolve(); if (methodDef.IsConstructor) { var declType = methodDef.DeclaringType; if (declType == type.BaseType) { // Base class constructor invocation. var fullName = info.Arguments[1] as string; var i = fullName.LastIndexOf('.'); var serviceType = new TypeName(fullName.Substring(0, i), fullName.Substring(i + 1)); service = new ServiceNode(serviceType); } else if (declType.FullName == "MethodDescriptor/ParseMethod") { var funcPtr = info.Arguments[2] as string; var start = funcPtr.IndexOf('<') + 1; var end = funcPtr.IndexOf('>'); var fullName = funcPtr.Substring(start, end - start); if (!retType.Equals(default(TypeName))) { Console.WriteLine("Discarding RPC return type: " + retType.Name); } var i = fullName.LastIndexOf('.'); retType = new TypeName(fullName.Substring(0, i), fullName.Substring(i + 1)); } else if (declType.FullName == "MethodDescriptor") { if (retType.Equals(default(TypeName))) { Console.WriteLine("Missing RPC return type"); } else { var fullMethodName = info.Arguments[1] as string; var methodName = fullMethodName.Substring(fullMethodName.LastIndexOf('.') + 1); var argType = new TypeName("unknown", "Unknown"); var rpc = new RPCNode(methodName, argType, retType); rpc.Options.Add("(method_id)", info.Arguments[2].ToString()); if (service != null) { service.Methods.Add(rpc); } retType = default(TypeName); } } } }; constructorWalker.Walk(); // TODO: Extract argument types. // This will require analysis of senders, handlers and/or handler registration. if (service == null) { Console.WriteLine("Failed to extract protobuf name for service class: " + type.FullName); } else { AddPackageNode(service.Name.Package, service); } }