Skip to content

Instantly share code, notes, and snippets.

@anatawa12
Created April 12, 2024 07:53
Show Gist options
  • Save anatawa12/6fe2a6802922a50193cc2ebadf02cfb1 to your computer and use it in GitHub Desktop.
Save anatawa12/6fe2a6802922a50193cc2ebadf02cfb1 to your computer and use it in GitHub Desktop.
#if UNITY_EDITOR && (!ANATAWA12_GISTS_VPM_PACKAGE || GIST_a4bb4e2e5d75b4fa5ba42e236aae564d)
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using HarmonyLib;
using JetBrains.Annotations;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace anatawa12.gists
{
internal static class VRCSDKApiChecker
{
[MenuItem("Tools/anatawa12 gists/Export VRCSDK API List")]
private static void CheckVRCSDKAPI()
{
var assemblies = new List<string>();
foreach (var sdkPath in new[]
{
"Packages/com.vrchat.base",
"Packages/com.vrchat.worlds",
"Packages/com.vrchat.avatars",
})
{
try
{
foreach (var file in Directory.GetFiles(sdkPath, "*.asmdef", SearchOption.AllDirectories))
{
var asmdef = AssetDatabase.LoadAssetAtPath<AssemblyDefinitionAsset>(file);
assemblies.Add(asmdef.name);
}
foreach (var file in Directory.GetFiles(sdkPath, "*.dll", SearchOption.AllDirectories))
{
var pluginImporter = (PluginImporter)AssetImporter.GetAtPath(file);
if (!pluginImporter.isNativePlugin && pluginImporter.GetCompatibleWithEditor())
{
assemblies.Add(Path.GetFileNameWithoutExtension(file));
}
}
}
catch (DirectoryNotFoundException)
{
// ignored
}
}
Debug.Log("VRCSDK Assembly" + string.Join(", ", assemblies));
Directory.CreateDirectory("VRCSDKApiChecker");
foreach (var assemblyName in assemblies)
{
var assembly = Assembly.Load(assemblyName);
Directory.CreateDirectory($"VRCSDKApiChecker/{assemblyName}");
foreach (var type in assembly.GetTypes())
{
var apiText = CreateTypeApis(type);
if (apiText == null) continue; // it's not public
File.WriteAllText($"VRCSDKApiChecker/{assemblyName}/{type.FullName}.txt", apiText);
}
}
}
[CanBeNull]
private static string CreateTypeApis(Type type)
{
if (!type.IsPubliclyVisible()) return null;
var apiText = new StringBuilder();
apiText.AppendLine($"Type: {type.FullName}");
apiText.AppendLine($"Extends: {type.BaseType}");
foreach (var @interface in type.GetInterfaces())
apiText.AppendLine($"Implements: {@interface}");
apiText.AppendLine($"Access: {type.Attributes}");
const BindingFlags allMembers = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
foreach (var method in type.GetMethods(allMembers))
if (method.IsPubliclyVisible())
apiText.AppendLine($"Method: {method.Attributes} {method.ReturnType} {method.Name}({method.GetParameters().Join()})");
foreach (var field in type.GetFields(allMembers))
if (field.IsPubliclyVisible())
apiText.AppendLine($"Field: {field.Attributes} {field.FieldType} {field.Name}");
foreach (var property in type.GetProperties(allMembers))
{
if (property.GetMethod != null && property.GetMethod.IsPubliclyVisible())
apiText.AppendLine($"Property: {property.PropertyType} {property.Name} {{ get; }}");
if (property.SetMethod != null && property.SetMethod.IsPubliclyVisible())
apiText.AppendLine($"Property: {property.PropertyType} {property.Name} {{ set; }}");
}
return apiText.ToString();
}
private static bool IsPubliclyVisible(this MethodInfo method) => method.IsPublic || method.IsFamilyOrAssembly || method.IsFamily;
private static bool IsPubliclyVisible(this FieldInfo field) => field.IsPublic || field.IsFamilyOrAssembly || field.IsFamily;
private static bool IsPubliclyVisible(this Type type)
{
switch (type.Attributes & TypeAttributes.VisibilityMask)
{
case TypeAttributes.Public:
return true;
case TypeAttributes.NestedPublic:
case TypeAttributes.NestedFamORAssem:
case TypeAttributes.NestedFamily:
return type.DeclaringType.IsPubliclyVisible();
default:
return false;
}
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment