├── .gitignore ├── Readme.md └── apidecomp ├── ApiWriter.cs ├── Program.cs └── apidecomp.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | .vscode 4 | archive 5 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Unity API Changes 2 | 3 | This repository contains the decompiled public API of Unity 3D to allow comparing the API between Unity versions. 4 | 5 | **This project is experimental.** The API decompilation is still work in progress and there might be incorrect or missing information. For now, the project only contains major releases and the main UnityEngine and UnityEditor assemblies. Issue reports and feature requests welcome. 6 | 7 | Use the table below to compare the API of two Unity versions. 8 | 9 | ## Versions 10 | 11 | From→To | 2022.1 | 2021.2 | 2021.1 | 2020.3 | 2019.4 12 | --- | --- | --- | --- | --- | --- 13 | 2021.2 | [2021.2→2022.1] | - | - | - | - 14 | 2021.1 | [2021.1→2022.1] | [2021.1→2021.2] | - | - | - 15 | 2020.3 | [2020.3→2022.1] | [2020.3→2021.2] | [2020.3→2021.1] | - | - 16 | 2019.4 | [2019.4→2022.1] | [2019.4→2021.2] | [2019.4→2021.1] | [2019.4→2020.3] | - 17 | 2018.4 | [2018.4→2022.1] | [2018.4→2021.2] | [2018.4→2021.1] | [2018.4→2020.3] | [2018.4→2019.4] 18 | 19 | [2021.2→2022.1]: https://github.com/sttz/unity-api-diff/compare/unity/2021.2..unity/2022.1 20 | 21 | [2021.1→2022.1]: https://github.com/sttz/unity-api-diff/compare/unity/2021.1..unity/2022.1 22 | [2021.1→2021.2]: https://github.com/sttz/unity-api-diff/compare/unity/2021.1..unity/2021.2 23 | 24 | [2020.3→2022.1]: https://github.com/sttz/unity-api-diff/compare/unity/2020.3..unity/2022.1 25 | [2020.3→2021.2]: https://github.com/sttz/unity-api-diff/compare/unity/2020.3..unity/2021.2 26 | [2020.3→2021.1]: https://github.com/sttz/unity-api-diff/compare/unity/2020.3..unity/2021.1 27 | 28 | [2019.4→2022.1]: https://github.com/sttz/unity-api-diff/compare/unity/2019.4..unity/2022.1 29 | [2019.4→2021.2]: https://github.com/sttz/unity-api-diff/compare/unity/2019.4..unity/2021.2 30 | [2019.4→2021.1]: https://github.com/sttz/unity-api-diff/compare/unity/2019.4..unity/2021.1 31 | [2019.4→2020.3]: https://github.com/sttz/unity-api-diff/compare/unity/2019.4..unity/2020.3 32 | 33 | [2018.4→2022.1]: https://github.com/sttz/unity-api-diff/compare/unity/2018.4..unity/2022.1 34 | [2018.4→2021.2]: https://github.com/sttz/unity-api-diff/compare/unity/2018.4..unity/2021.2 35 | [2018.4→2021.1]: https://github.com/sttz/unity-api-diff/compare/unity/2018.4..unity/2021.1 36 | [2018.4→2020.3]: https://github.com/sttz/unity-api-diff/compare/unity/2018.4..unity/2020.3 37 | [2018.4→2019.4]: https://github.com/sttz/unity-api-diff/compare/unity/2018.4..unity/2019.4 38 | -------------------------------------------------------------------------------- /apidecomp/ApiWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using System.Linq; 6 | using Mono.Cecil; 7 | 8 | namespace apidiff 9 | { 10 | 11 | /// 12 | /// Extract the public API of a C# assembly, to make changes 13 | /// to the API easily diffable. 14 | /// 15 | public static class ApiWriter 16 | { 17 | public static void WriteApi(string assemblyPath, string outputPath) 18 | { 19 | var resolver = new DefaultAssemblyResolver(); 20 | resolver.AddSearchDirectory(Path.GetDirectoryName(assemblyPath)); 21 | 22 | var config = new ReaderParameters(); 23 | config.AssemblyResolver = resolver; 24 | 25 | var module = ModuleDefinition.ReadModule(assemblyPath, config); 26 | Console.WriteLine($"Opened module {module.Name}"); 27 | 28 | var types = module.Types 29 | .Where(t => t.IsPublic) 30 | .OrderBy(t => t.Name) 31 | .ToList(); 32 | 33 | var count = types.Count; 34 | Console.WriteLine($"Found {types.Count} public root types"); 35 | if (count == 0) return; 36 | 37 | var moduleOutputPath = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(module.Name)); 38 | if (!Directory.Exists(moduleOutputPath)) { 39 | Directory.CreateDirectory(moduleOutputPath); 40 | } 41 | 42 | foreach (var type in types) { 43 | WriteType(type, 0, moduleOutputPath); 44 | } 45 | } 46 | 47 | // -------- Main Types -------- 48 | 49 | static void WriteType(TypeDefinition type, int indent, string basePath) 50 | { 51 | var namespacePath = Path.Combine(basePath, type.Namespace.Replace(".", "/")); 52 | if (!Directory.Exists(namespacePath)) { 53 | Directory.CreateDirectory(namespacePath); 54 | } 55 | 56 | var filename = RemoveGenericSuffix(type.Name); 57 | if (type.HasGenericParameters) { 58 | filename += "<" + string.Join(", ", type.GenericParameters.Select(p => FormatTypeName(p))) + ">"; 59 | } 60 | filename += ".cs"; 61 | 62 | var filePath = Path.Combine(namespacePath, filename); 63 | if (File.Exists(filePath)) { 64 | Console.WriteLine($"WARNING File already exists: {filePath}"); 65 | } 66 | 67 | using (var stream = File.Open(filePath, FileMode.Create, FileAccess.Write)) { 68 | using (var output = new StreamWriter(stream)) { 69 | foreach (var imported in ImportedNamespaces) { 70 | WriteIndent(indent, output); 71 | output.WriteLine($"using {imported};"); 72 | } 73 | 74 | output.WriteLine(); 75 | 76 | WriteIndent(indent, output); 77 | output.Write("namespace "); 78 | output.WriteLine(type.Namespace); 79 | 80 | WriteIndent(indent, output); 81 | output.WriteLine("{"); 82 | 83 | output.WriteLine(); 84 | 85 | WriteType(type, indent, output); 86 | 87 | output.WriteLine(); 88 | 89 | WriteIndent(indent, output); 90 | output.WriteLine("}"); 91 | } 92 | } 93 | } 94 | 95 | static void WriteType(TypeDefinition type, int indent, TextWriter output) 96 | { 97 | if (type.IsEnum) { 98 | WriteEnum(type, indent, output); 99 | } else if (type.BaseType != null && type.BaseType.FullName == "System.MulticastDelegate") { 100 | WriteDelegate(type, indent, output); 101 | } else if (type.IsClass || type.IsValueType || type.IsInterface) { 102 | WriteClassStructOrInterface(type, indent, output); 103 | } 104 | } 105 | 106 | static void WriteClassStructOrInterface(TypeDefinition type, int indent, TextWriter output) 107 | { 108 | WriteIndent(indent, output); 109 | 110 | output.Write(FormatTypeAccess(type)); 111 | 112 | if (type.IsSealed && type.IsAbstract) output.Write(" static"); 113 | else if (type.IsSealed && !type.IsValueType) output.Write(" sealed"); 114 | else if (type.IsAbstract && !type.IsInterface) output.Write(" abstract"); 115 | 116 | if (type.IsValueType) output.Write(" struct"); 117 | else if (type.IsClass) output.Write(" class"); 118 | else if (type.IsInterface) output.Write(" interface"); 119 | else throw new Exception("Type not a class, struct or interface."); 120 | 121 | output.Write(" " + FormatTypeName(type, forceNoNamespace: true)); 122 | 123 | var hasBaseType = type.BaseType != null && type.BaseType.FullName != "System.Object" && type.BaseType.FullName != "System.ValueType"; 124 | if (hasBaseType || type.HasInterfaces) { 125 | var extends = type.Interfaces.Select(i => i.InterfaceType); 126 | if (hasBaseType) extends = extends.Prepend(type.BaseType); 127 | output.Write(" : " + string.Join(", ", extends.Select(t => FormatTypeName(t)))); 128 | } 129 | 130 | output.WriteLine(); 131 | 132 | WriteIndent(indent, output); 133 | output.WriteLine("{"); 134 | 135 | var fields = type.Fields 136 | .Where(f => (f.IsPublic || f.IsFamily) && !f.IsSpecialName) 137 | .OrderBy(f => f.Name); 138 | var props = type.Properties 139 | .Where(p => { var a = GetMoreAccessibleAccessor(p); return a.IsPublic || a.IsFamily; }) 140 | .OrderBy(p => p.Name); 141 | var events = type.Events 142 | .Where(e => e.AddMethod.IsPublic || e.AddMethod.IsFamily) 143 | .OrderBy(e => e.Name); 144 | var methods = type.Methods 145 | .Where(m => (m.IsPublic || m.IsFamily) && (!m.IsSpecialName || m.IsConstructor) && m.Name != "Finalize") 146 | .OrderBy(m => m, MethodComparer.Instance); 147 | 148 | WriteMembers( 149 | fields.Where(m => m.IsStatic), 150 | WriteField, indent, output 151 | ); 152 | WriteMembers( 153 | props.Where(m => GetMoreAccessibleAccessor(m).IsStatic), 154 | WriteProperty, indent, output 155 | ); 156 | WriteMembers( 157 | events.Where(m => m.AddMethod.IsStatic), 158 | WriteEvent, indent, output 159 | ); 160 | WriteMembers( 161 | methods.Where(m => m.IsStatic), 162 | WriteMethod, indent, output 163 | ); 164 | 165 | WriteMembers( 166 | fields.Where(m => !m.IsStatic), 167 | WriteField, indent, output 168 | ); 169 | WriteMembers( 170 | props.Where(m => !GetMoreAccessibleAccessor(m).IsStatic), 171 | WriteProperty, indent, output 172 | ); 173 | WriteMembers( 174 | events.Where(m => !m.AddMethod.IsStatic), 175 | WriteEvent, indent, output 176 | ); 177 | 178 | WriteMembers( 179 | methods.Where(m => !m.IsStatic && m.IsConstructor), 180 | WriteMethod, indent, output 181 | ); 182 | WriteMembers( 183 | methods.Where(m => !m.IsStatic && !m.IsConstructor), 184 | WriteMethod, indent, output 185 | ); 186 | 187 | foreach (var nested in type.NestedTypes.OrderBy(t => t.Name)) { 188 | if (!nested.IsNestedPublic && !nested.IsNestedFamily) continue; 189 | WriteType(nested, indent + 1, output); 190 | output.WriteLine(); 191 | } 192 | 193 | WriteIndent(indent, output); 194 | output.WriteLine("}"); 195 | } 196 | 197 | static void WriteMembers(IEnumerable members, Action writer, int indent, TextWriter output) 198 | where T : IMemberDefinition 199 | { 200 | if (members.Any()) { 201 | foreach (var member in members) { 202 | WriteIndent(indent + 1, output); 203 | writer(member, output); 204 | } 205 | output.WriteLine(); 206 | } 207 | } 208 | 209 | static void WriteEnum(TypeDefinition type, int indent, TextWriter output) 210 | { 211 | WriteIndent(indent, output); 212 | 213 | output.Write(FormatTypeAccess(type)); 214 | output.Write(" enum"); 215 | output.Write(" " + type.Name); 216 | 217 | var valueField = type.Fields.FirstOrDefault(f => !f.IsStatic); 218 | if (valueField != null && valueField.FieldType.FullName != "System.Int32") { 219 | output.Write(" : " + FormatTypeName(valueField.FieldType)); 220 | } 221 | 222 | output.WriteLine(); 223 | 224 | WriteIndent(indent, output); 225 | output.WriteLine("{"); 226 | 227 | foreach (var field in type.Fields.OrderBy(f => f.Constant)) { 228 | if (!field.IsStatic) continue; 229 | WriteIndent(indent + 1, output); 230 | output.WriteLine(field.Name + " = " + FormatConstant(field.Constant) + ","); 231 | } 232 | 233 | WriteIndent(indent, output); 234 | output.WriteLine("}"); 235 | } 236 | 237 | static void WriteDelegate(TypeDefinition type, int indent, TextWriter output) 238 | { 239 | WriteIndent(indent, output); 240 | 241 | output.Write(FormatTypeAccess(type)); 242 | output.Write(" delegate"); 243 | 244 | var invoke = type.Methods.FirstOrDefault(m => m.Name == "Invoke"); 245 | output.Write(" " + FormatTypeName(invoke.ReturnType)); 246 | 247 | output.Write(" " + type.Name); 248 | 249 | output.Write("(" + string.Join(", ", invoke.Parameters.Select(FormatParameter)) + ")"); 250 | 251 | output.WriteLine(";"); 252 | } 253 | 254 | // -------- Members -------- 255 | 256 | static void WriteField(FieldDefinition field, TextWriter output) 257 | { 258 | if (field.IsStatic) 259 | output.Write("static "); 260 | 261 | if (field.IsPublic) { 262 | output.Write("public "); 263 | } else if (field.IsAssembly) { 264 | output.Write("internal "); 265 | } else if (field.IsFamily) { 266 | output.Write("protected "); 267 | } else if (field.IsFamilyOrAssembly) { 268 | output.Write("protected internal "); 269 | } 270 | 271 | output.Write(FormatTypeName(field.FieldType) + " "); 272 | output.Write(field.Name); 273 | 274 | if (field.HasConstant) { 275 | output.Write(" = " + FormatConstant(field.Constant)); 276 | } 277 | 278 | output.WriteLine(";"); 279 | } 280 | 281 | static void WriteProperty(PropertyDefinition property, TextWriter output) 282 | { 283 | var eitherMethod = property.GetMethod ?? property.SetMethod; 284 | var otherMethod = eitherMethod == property.SetMethod ? null : property.SetMethod; 285 | 286 | if (eitherMethod.IsStatic) 287 | output.Write("static "); 288 | 289 | string mainAccess; 290 | if (otherMethod == null) { 291 | mainAccess = FormatMethodAccess(eitherMethod); 292 | } else { 293 | mainAccess = FormatMethodAccess(GetMoreAccessibleAccessor(property)); 294 | } 295 | output.Write(mainAccess + " "); 296 | 297 | output.Write(FormatTypeName(property.PropertyType) + " "); 298 | 299 | if (property.Name == "Item" && property.HasParameters) { 300 | output.Write("this[" + string.Join(", ", property.Parameters.Select(FormatParameter)) + "]"); 301 | } else { 302 | output.Write(property.Name); 303 | } 304 | 305 | output.Write(" { "); 306 | 307 | if (property.GetMethod != null) { 308 | var access = FormatMethodAccess(property.GetMethod); 309 | if (access != mainAccess) output.Write(access + " "); 310 | output.Write("get; "); 311 | } 312 | if (property.SetMethod != null) { 313 | var access = FormatMethodAccess(property.SetMethod); 314 | if (access != mainAccess) output.Write(access + " "); 315 | output.Write("set; "); 316 | } 317 | 318 | output.Write("}"); 319 | 320 | if (property.HasConstant) { 321 | output.Write(" = " + FormatConstant(property.Constant)); 322 | } 323 | 324 | output.WriteLine(); 325 | } 326 | 327 | static void WriteEvent(EventDefinition ev, TextWriter output) 328 | { 329 | var method = ev.AddMethod; 330 | 331 | if (method.IsStatic) 332 | output.Write("static "); 333 | 334 | if (method.IsPublic) { 335 | output.Write("public "); 336 | } else if (method.IsAssembly) { 337 | output.Write("internal "); 338 | } else if (method.IsFamily) { 339 | output.Write("protected "); 340 | } else if (method.IsFamilyOrAssembly) { 341 | output.Write("protected internal "); 342 | } else if (method.IsFamilyAndAssembly) { 343 | output.Write("private protected "); 344 | } else { 345 | output.Write("private "); 346 | } 347 | 348 | output.Write("event "); 349 | 350 | output.Write(FormatTypeName(ev.EventType) + " "); 351 | 352 | output.WriteLine(ev.Name + ";"); 353 | } 354 | 355 | static void WriteMethod(MethodDefinition method, TextWriter output) 356 | { 357 | if (method.IsStatic) 358 | output.Write("static "); 359 | 360 | output.Write(FormatMethodAccess(method) + " "); 361 | 362 | if (method.Name == ".ctor") { 363 | output.Write(RemoveGenericSuffix(method.DeclaringType.Name)); 364 | } else { 365 | output.Write(FormatTypeName(method.ReturnType) + " "); 366 | output.Write(method.Name); 367 | if (method.HasGenericParameters) { 368 | output.Write("<" + string.Join(", ", method.GenericParameters.Select(p => FormatTypeName(p))) + ">"); 369 | } 370 | } 371 | 372 | output.WriteLine("(" + string.Join(", ", method.Parameters.Select(FormatParameter)) + ");"); 373 | } 374 | 375 | public class MethodComparer : IComparer 376 | { 377 | public static readonly MethodComparer Instance = new MethodComparer(); 378 | 379 | public int Compare(MethodDefinition x, MethodDefinition y) 380 | { 381 | if (x == y) return 0; 382 | 383 | // First sort by method name 384 | var value = string.Compare(x.Name, y.Name, true); 385 | if (value != 0) return value; 386 | 387 | // Then sort by generic parameter count 388 | value = x.GenericParameters.Count.CompareTo(y.GenericParameters.Count); 389 | if (value != 0) return value; 390 | 391 | // Then by generic parameter names 392 | for (int i = 0; i < x.GenericParameters.Count; i++) { 393 | value = string.Compare(x.GenericParameters[i].Name, y.GenericParameters[i].Name, true); 394 | if (value != 0) return value; 395 | } 396 | 397 | // Then sort by parameter count 398 | value = x.Parameters.Count.CompareTo(y.Parameters.Count); 399 | if (value != 0) return value; 400 | 401 | // Then by parameter names 402 | for (int i = 0; i < x.Parameters.Count; i++) { 403 | value = string.Compare(x.Parameters[i].Name, y.Parameters[i].Name, true); 404 | if (value != 0) return value; 405 | } 406 | 407 | // Finally by parameter types 408 | for (int i = 0; i < x.Parameters.Count; i++) { 409 | value = string.Compare(x.Parameters[i].ParameterType.FullName, y.Parameters[i].ParameterType.FullName, true); 410 | if (value != 0) return value; 411 | } 412 | 413 | //var writer = new StringWriter(); 414 | //writer.Write("- "); 415 | //WriteMethod(x, writer); 416 | //writer.Write("- "); 417 | //WriteMethod(y, writer); 418 | //Console.WriteLine($"WARNING Method names and parameter types/names match:\n{writer}"); 419 | 420 | // This can happen if there are methods that just differ in case, 421 | // e.g. when the case has been corrected but the old method remains 422 | // for backwards compatibility 423 | return 0; 424 | } 425 | } 426 | 427 | // -------- Helpers -------- 428 | 429 | const string Indent = " "; 430 | 431 | static readonly string[] ImportedNamespaces = new string[] { 432 | "System", 433 | "System.Collections", 434 | "System.Collections.Generic", 435 | "UnityEngine", 436 | }; 437 | 438 | static void WriteIndent(int indent, TextWriter output) 439 | { 440 | for (int i = 0; i < indent; i++) { 441 | output.Write(Indent); 442 | } 443 | } 444 | 445 | static string FormatTypeAccess(TypeDefinition type) 446 | { 447 | if (type.IsPublic || type.IsNestedPublic) { 448 | return "public"; 449 | } else if (type.IsNestedAssembly) { 450 | return "internal"; 451 | } else if (type.IsNestedFamily) { 452 | return "protected"; 453 | } else if (type.IsNestedFamilyOrAssembly) { 454 | return "protected internal"; 455 | } else if (type.IsNestedFamilyAndAssembly) { 456 | return "private protected"; 457 | } else { 458 | return "private"; 459 | } 460 | } 461 | 462 | static string RemoveGenericSuffix(string name) 463 | { 464 | var apo = name.LastIndexOf('`'); 465 | if (apo >= 0) { 466 | return name.Substring(0, apo); 467 | } else { 468 | return name; 469 | } 470 | } 471 | 472 | static string FormatTypeName(TypeReference type, bool forceNoNamespace = false) 473 | { 474 | var resolved = type.Resolve() ?? type; 475 | 476 | string name; 477 | if (type.IsArray) { 478 | name = type.Name; 479 | } else { 480 | name = resolved.Name; 481 | } 482 | 483 | if (resolved.Namespace == "System") { 484 | switch (resolved.Name) { 485 | case "Void": 486 | name = name.Replace("Void", "void"); 487 | break; 488 | case "Object": 489 | name = name.Replace("Object", "object"); 490 | break; 491 | case "Boolean": 492 | name = name.Replace("Boolean", "bool"); 493 | break; 494 | case "Int16": 495 | name = name.Replace("Int16", "short"); 496 | break; 497 | case "UInt16": 498 | name = name.Replace("UInt16", "ushort"); 499 | break; 500 | case "Int32": 501 | name = name.Replace("Int32", "int"); 502 | break; 503 | case "UInt32": 504 | name = name.Replace("UInt32", "uint"); 505 | break; 506 | case "Int64": 507 | name = name.Replace("Int64", "long"); 508 | break; 509 | case "UInt64": 510 | name = name.Replace("UInt64", "ulong"); 511 | break; 512 | case "Single": 513 | name = name.Replace("Single", "float"); 514 | break; 515 | case "Double": 516 | name = name.Replace("Double", "double"); 517 | break; 518 | case "Decimal": 519 | name = name.Replace("Decimal", "decimal"); 520 | break; 521 | case "Byte": 522 | name = name.Replace("Byte", "byte"); 523 | break; 524 | case "SByte": 525 | name = name.Replace("SByte", "sbyte"); 526 | break; 527 | case "String": 528 | name = name.Replace("String", "string"); 529 | break; 530 | case "Char": 531 | name = name.Replace("Char", "char"); 532 | break; 533 | } 534 | } 535 | 536 | if (name.EndsWith("&")) { 537 | name = name.Substring(0, name.Length - 1); 538 | } 539 | 540 | if (!forceNoNamespace 541 | && resolved.Namespace != "" 542 | && !ImportedNamespaces.Contains(resolved.Namespace)) { 543 | if (resolved.Namespace.StartsWith("UnityEngine.")) { 544 | name = resolved.Namespace.Substring(12) + "." + name; 545 | } else { 546 | name = resolved.Namespace + "." + name; 547 | } 548 | } 549 | 550 | if (resolved.GenericParameters.Count > 0) { 551 | name = RemoveGenericSuffix(name); 552 | if (type is GenericInstanceType instanceType) { 553 | name += "<" + string.Join(", ", instanceType.GenericArguments.Select(p => FormatTypeName(p))) + ">"; 554 | } else { 555 | name += "<" + string.Join(", ", resolved.GenericParameters.Select(p => FormatTypeName(p))) + ">"; 556 | } 557 | } 558 | 559 | return name; 560 | } 561 | 562 | static string FormatConstant(object constant) 563 | { 564 | if (constant == null) { 565 | return "null"; 566 | } else if (constant is string) { 567 | return $"\"{constant}\""; 568 | } else if (constant is bool) { 569 | return ((bool)constant ? "true" : "false"); 570 | } else { 571 | return constant.ToString(); 572 | } 573 | } 574 | 575 | static MethodDefinition GetMoreAccessibleAccessor(PropertyDefinition property) 576 | { 577 | if (property.GetMethod == null) { 578 | return property.SetMethod; 579 | } else if (property.SetMethod == null) { 580 | return property.GetMethod; 581 | } 582 | 583 | var getAccess = GetAccessRank(property.GetMethod); 584 | var setAccess = GetAccessRank(property.SetMethod); 585 | return getAccess < setAccess ? property.GetMethod : property.SetMethod; 586 | } 587 | 588 | static int GetAccessRank(MethodDefinition method) 589 | { 590 | if (method.IsPublic) { 591 | return 1; 592 | } else if (method.IsAssembly) { 593 | return 2; 594 | } else if (method.IsFamily) { 595 | return 2; 596 | } else if (method.IsFamilyOrAssembly) { 597 | return 2; 598 | } else if (method.IsFamilyAndAssembly) { 599 | return 3; 600 | } else { 601 | return 3; 602 | } 603 | } 604 | 605 | static string FormatMethodAccess(MethodDefinition method) 606 | { 607 | if (method.IsPublic) { 608 | return "public"; 609 | } else if (method.IsAssembly) { 610 | return "internal"; 611 | } else if (method.IsFamily) { 612 | return "protected"; 613 | } else if (method.IsFamilyOrAssembly) { 614 | return "protected internal"; 615 | } else if (method.IsFamilyAndAssembly) { 616 | return "private protected"; 617 | } else { 618 | return "private"; 619 | } 620 | } 621 | 622 | static string FormatParameter(ParameterDefinition param) 623 | { 624 | var name = ""; 625 | 626 | if (param.IsIn && param.IsOut) name += "ref "; 627 | else if (param.IsIn) name += "in "; 628 | else if (param.IsOut) name += "out "; 629 | 630 | name += FormatTypeName(param.ParameterType) + " " + param.Name; 631 | 632 | if (param.HasConstant) { 633 | name += " = " + FormatConstant(param.Constant); 634 | } 635 | 636 | return name; 637 | } 638 | } 639 | 640 | } 641 | -------------------------------------------------------------------------------- /apidecomp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace apidiff 5 | { 6 | 7 | class Program 8 | { 9 | // -------- CLI -------- 10 | 11 | static void Main(string[] args) 12 | { 13 | if (args.Length != 2) { 14 | PrintHelp(); 15 | Environment.Exit(1); 16 | } 17 | 18 | var path = args[0]; 19 | var outputPath = args[1]; 20 | 21 | if (!Directory.Exists(path)) { 22 | Console.WriteLine($"Path is not a directory: {path}"); 23 | PrintHelp(); 24 | Environment.Exit(2); 25 | } 26 | 27 | var editorPaths = Directory.GetFiles(path, "UnityEditor*.dll"); 28 | if (editorPaths.Length == 0) { 29 | Console.WriteLine($"Could not find UnityEditor assemblies at path: {path}"); 30 | PrintHelp(); 31 | Environment.Exit(3); 32 | } 33 | 34 | var enginePaths = Directory.GetFiles(path, "UnityEngine*.dll"); 35 | if (enginePaths.Length == 0) { 36 | Console.WriteLine($"Could not find UnityEngine assemblies at path: {path}"); 37 | PrintHelp(); 38 | Environment.Exit(3); 39 | } 40 | 41 | if (Directory.Exists(outputPath)) { 42 | var contents = Directory.GetDirectories(outputPath); 43 | foreach (var dir in contents) { 44 | if (Path.GetFileName(dir).StartsWith(".")) continue; 45 | Directory.Delete(dir, true); 46 | } 47 | } 48 | 49 | Directory.CreateDirectory(outputPath); 50 | 51 | foreach (var assembly in editorPaths) { 52 | Console.WriteLine($""); 53 | ApiWriter.WriteApi(assembly, outputPath); 54 | } 55 | 56 | foreach (var assembly in enginePaths) { 57 | Console.WriteLine($""); 58 | ApiWriter.WriteApi(assembly, outputPath); 59 | } 60 | } 61 | 62 | static void PrintHelp() 63 | { 64 | Console.WriteLine("Public API Printer"); 65 | Console.WriteLine("usage: apidecomp PATH_TO_ASSEMBLIES OUTPUT_PATH"); 66 | } 67 | 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /apidecomp/apidecomp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | $(DefaultItemExcludes);Output\**\* 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------