122 lines
5.7 KiB
C#
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;
|
|
}
|
|
}
|