Files
AIGame/AIClient/AgentScripts/VerifyYooAssetStripping.cs

122 lines
5.7 KiB
C#

using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using AOT.Editor.WeChat;
using Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
public static class VerifyYooAssetStripping
{
static readonly string[] TypeNames = {
"YooAsset.WebNetworkFileSystem", "YooAsset.BuiltinFileSystem", "YooAsset.SandboxFileSystem"
};
static string Project => Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
static string Output => Path.GetFullPath(Path.Combine(Project, "../Tools/output/yooasset_stripping"));
public static string Before()
{
return Save("before", new {
full = Inspect(Path.Combine(Project, "Library/ScriptAssemblies/YooAsset.dll")),
stripped = Inspect(Path.Combine(Project, "Library/Bee/artifacts/WebGL/ManagedStripped/YooAsset.dll"))
});
}
public static string BuildAndVerify()
{
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.WebGL)
throw new Exception("Select WebGL before running this verification");
string gamePath = Path.Combine(Project, "Build/WeChat/Base/minigame/game.js");
string oldDataPath = StartupDataPath(gamePath);
string oldDataHash = Hash(oldDataPath);
string[] business = SnapshotBusinessBundles();
DateTime started = DateTime.UtcNow;
string result = ExportWeChatBasePackage.Export();
if (!result.StartsWith("ok minigame=", StringComparison.Ordinal)) throw new Exception(result);
string strippedPath = Path.Combine(Project, "Library/Bee/artifacts/WebGL/ManagedStripped/YooAsset.dll");
var stripped = Inspect(strippedPath);
if (File.GetLastWriteTimeUtc(strippedPath) < started)
throw new Exception("Stripped YooAsset DLL is stale");
if (stripped.Any(t => !t.defaultConstructor || !t.constructorBody))
throw new Exception("A reflected file system constructor was stripped");
string cpp = Path.Combine(Project, "Library/Bee/artifacts/WebGL/il2cppOutput/cpp");
bool nativeConstructor = Directory.GetFiles(cpp, "YooAsset*.cpp")
.Any(p => File.ReadAllText(p).Contains("WebNetworkFileSystem__ctor"));
if (!nativeConstructor) throw new Exception("Native WebNetworkFileSystem constructor was not emitted");
if (!business.SequenceEqual(SnapshotBusinessBundles()))
throw new Exception("Business bundles changed unexpectedly");
string newDataPath = StartupDataPath(gamePath);
return Save("after", new {
passed = true, export = result, startedUtc = started, completedUtc = DateTime.UtcNow,
stripped, nativeConstructor,
oldStartupData = oldDataPath, startupData = newDataPath,
oldStartupHash = oldDataHash, startupHash = Hash(newDataPath),
startupDataUnchanged = oldDataHash == Hash(newDataPath),
businessBundlesUnchanged = true,
wechatRuntimeRetested = false
});
}
public sealed class TypeCheck
{
public string type;
public bool typePresent;
public bool defaultConstructor;
public bool constructorBody;
}
static TypeCheck[] Inspect(string path)
{
// Burst embeds a second Cecil copy; select Unity's loaded Mono.Cecil explicitly.
var cecil = AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "Mono.Cecil");
var definition = cecil.GetType("Mono.Cecil.AssemblyDefinition");
object assembly = definition.GetMethod("ReadAssembly", new[] { typeof(string) }).Invoke(null, new object[] { path });
using ((IDisposable)assembly)
{
object module = Property(assembly, "MainModule");
return TypeNames.Select(name => {
object type = module.GetType().GetMethod("GetType", new[] { typeof(string) }).Invoke(module, new object[] { name });
var check = new TypeCheck { type = name, typePresent = type != null };
if (type != null)
foreach (object method in (IEnumerable)Property(type, "Methods"))
if ((string)Property(method, "Name") == ".ctor" && !(bool)Property(method, "HasParameters"))
{
check.defaultConstructor = true;
check.constructorBody = (bool)Property(method, "HasBody");
}
return check;
}).ToArray();
}
}
static object Property(object instance, string name) => instance.GetType().GetProperty(name).GetValue(instance);
static string StartupDataPath(string gamePath)
{
var match = Regex.Match(File.ReadAllText(gamePath), @"DATA_FILE_MD5:\s*'([0-9a-f]+)'");
if (!match.Success) throw new Exception("Cannot read DATA_FILE_MD5");
return Path.Combine(Project, "Build/WeChat/Base/webgl", match.Groups[1].Value + ".webgl.data.unityweb.bin.txt");
}
static string Hash(string path)
{
using (var sha = SHA256.Create())
using (var stream = File.OpenRead(path))
return BitConverter.ToString(sha.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
}
static string[] SnapshotBusinessBundles() => Directory.GetFiles(Path.Combine(Application.dataPath, "StreamingAssets/yoo"), "*.bundle", SearchOption.AllDirectories)
.OrderBy(p => p, StringComparer.Ordinal).Select(p => p + ":" + Hash(p)).ToArray();
static string Save(string name, object report)
{
Directory.CreateDirectory(Output);
string json = JsonConvert.SerializeObject(report, Formatting.Indented);
File.WriteAllText(Path.Combine(Output, name + ".json"), json);
return json;
}
}