Initial commit: AIGame Unity client with URP, HybridCLR, YooAsset and tooling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-09-21 14:21:07 +08:00
co-authored by Cursor
commit f3f66ab063
931 changed files with 112366 additions and 0 deletions
@@ -0,0 +1,195 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Unity.Pipeline.CodeReload
{
/// <summary>
/// Shared contract for pushing a compiled in-place code-reload override from the editor to a
/// running player over Unity's PlayerConnection — the same editor↔player message bus the Profiler
/// and Console use (tunnels over USB for mobile, no IP or open port required). Lives in Runtime so
/// both the player (<c>PlayerConnection.Register</c>) and the editor push command
/// (<c>EditorConnection.Send</c>) reference the same GUIDs and wire format.
///
/// The editor compiles the edited method(s) to override-assembly IL bytes (Roslyn, editor-only)
/// and ships {typeName, methodNames, il}; the player feeds them to
/// <c>InterpreterCodeReloadExecutor.Register</c> and runs them through the IlInterpreter interpreter —
/// no <c>Assembly.Load</c>, so it works under IL2CPP.
///
/// This type touches no Unity API, so it compiles unconditionally on both editor and player.
/// </summary>
static class PipelineCodeReloadConnect
{
/// <summary>editor → player: apply a compiled override (typeName + method names + IL).</summary>
public static readonly Guid ApplyReloadMsg = new Guid("b6f1e0d24c7a4e13a9f2c8d5017e3b44");
/// <summary>player → editor: an ack (ok + human-readable summary) for a received push.</summary>
public static readonly Guid ResultMsg = new Guid("c2a7d4e916b84f0db3e5a1c6072f8d99");
/// <summary>editor → player: a save is being compiled for a push (payload = utf8 file name).
/// Compilation happens editor-side, so this notice is the only way an on-device overlay can
/// show "compiling…" progress.</summary>
public static readonly Guid ReloadPendingMsg = new Guid("d4c9f2a1385e46b7a0d1e8c4529b6f77");
/// <summary>editor → player: the compile for a pending reload failed (payload = utf8 summary).
/// Without it the on-device overlay would sit on "compiling…" forever after a syntax error.</summary>
public static readonly Guid ReloadFailedMsg = new Guid("e7b3a8d05c214f9e8b6d2f0a1c435588");
/// <summary>player → editor: session announcement for push signing — protocol version, the
/// key fingerprint the player's baked public key expects, and the random session nonce every
/// signed push must carry. Sent on connect and in answer to <see cref="RequestHandshakeMsg"/>.</summary>
public static readonly Guid HandshakeMsg = new Guid("f19c5b8a2d7e40c6b8a3f0d612c94e55");
/// <summary>editor → player: re-announce your handshake (empty payload). Sent when the editor's
/// nonce table is empty for a target — typically right after an editor domain reload.</summary>
public static readonly Guid RequestHandshakeMsg = new Guid("a8d2c7f4691e4b0f9c5d3a80e17b6c22");
/// <summary>
/// Wire format: [typeNameLen:int32-LE][utf8 typeName][methodCount:int32-LE]
/// (per method: [len:int32-LE][utf8 name]) [il bytes…].
/// </summary>
public static byte[] Encode(string typeName, IReadOnlyList<string> methodNames, byte[] il)
{
var methods = methodNames ?? Array.Empty<string>();
var nameBytes = Encoding.UTF8.GetBytes(typeName ?? "");
var methodBytes = new byte[methods.Count][];
int methodsSize = 4; // count
for (int i = 0; i < methods.Count; i++)
{
methodBytes[i] = Encoding.UTF8.GetBytes(methods[i] ?? "");
methodsSize += 4 + methodBytes[i].Length;
}
int ilLen = il?.Length ?? 0;
var buf = new byte[4 + nameBytes.Length + methodsSize + ilLen];
int o = 0;
o = WriteInt32(buf, o, nameBytes.Length);
o = WriteBytes(buf, o, nameBytes);
o = WriteInt32(buf, o, methods.Count);
for (int i = 0; i < methods.Count; i++)
{
o = WriteInt32(buf, o, methodBytes[i].Length);
o = WriteBytes(buf, o, methodBytes[i]);
}
if (ilLen > 0) Buffer.BlockCopy(il, 0, buf, o, ilLen);
return buf;
}
public static bool TryDecode(byte[] data, out string typeName, out string[] methodNames, out byte[] il)
{
typeName = null; methodNames = null; il = null;
if (data == null || data.Length < 4) return false;
try
{
int o = 0;
int nameLen = ReadInt32(data, ref o);
if (nameLen < 0 || o + nameLen > data.Length) return false;
typeName = Encoding.UTF8.GetString(data, o, nameLen); o += nameLen;
if (o + 4 > data.Length) return false;
int methodCount = ReadInt32(data, ref o);
if (methodCount < 0) return false;
methodNames = new string[methodCount];
for (int i = 0; i < methodCount; i++)
{
if (o + 4 > data.Length) return false;
int len = ReadInt32(data, ref o);
if (len < 0 || o + len > data.Length) return false;
methodNames[i] = Encoding.UTF8.GetString(data, o, len); o += len;
}
int ilLen = data.Length - o;
il = new byte[ilLen];
if (ilLen > 0) Buffer.BlockCopy(data, o, il, 0, ilLen);
return true;
}
catch
{
return false;
}
}
/// <summary>Handshake wire format: [protocolVersion:int32-LE][fpLen:int32-LE][utf8 fingerprint][nonce…].</summary>
public static byte[] EncodeHandshake(int protocolVersion, string keyFingerprint, byte[] sessionNonce)
{
var fp = Encoding.UTF8.GetBytes(keyFingerprint ?? "");
var nonce = sessionNonce ?? Array.Empty<byte>();
var buf = new byte[4 + 4 + fp.Length + nonce.Length];
int o = 0;
o = WriteInt32(buf, o, protocolVersion);
o = WriteInt32(buf, o, fp.Length);
o = WriteBytes(buf, o, fp);
if (nonce.Length > 0) Buffer.BlockCopy(nonce, 0, buf, o, nonce.Length);
return buf;
}
public static bool TryDecodeHandshake(byte[] data, out int protocolVersion, out string keyFingerprint, out byte[] sessionNonce)
{
protocolVersion = 0; keyFingerprint = null; sessionNonce = null;
if (data == null || data.Length < 8) return false;
try
{
int o = 0;
protocolVersion = ReadInt32(data, ref o);
int fpLen = ReadInt32(data, ref o);
if (fpLen < 0 || fpLen > 256 || o + fpLen > data.Length) return false;
keyFingerprint = Encoding.UTF8.GetString(data, o, fpLen); o += fpLen;
sessionNonce = new byte[data.Length - o];
if (sessionNonce.Length > 0) Buffer.BlockCopy(data, o, sessionNonce, 0, sessionNonce.Length);
return true;
}
catch
{
return false;
}
}
/// <summary>Payload for the plain-text notices (<see cref="ReloadPendingMsg"/>, <see cref="ReloadFailedMsg"/>).</summary>
public static byte[] EncodeText(string text) => Encoding.UTF8.GetBytes(text ?? "");
public static string DecodeText(byte[] data) =>
data == null || data.Length == 0 ? "" : Encoding.UTF8.GetString(data);
/// <summary>Result wire format: [ok:1 byte][utf8 message…].</summary>
public static byte[] EncodeResult(bool ok, string message)
{
var msgBytes = Encoding.UTF8.GetBytes(message ?? "");
var buf = new byte[1 + msgBytes.Length];
buf[0] = (byte)(ok ? 1 : 0);
Buffer.BlockCopy(msgBytes, 0, buf, 1, msgBytes.Length);
return buf;
}
public static bool TryDecodeResult(byte[] data, out bool ok, out string message)
{
ok = false; message = null;
if (data == null || data.Length < 1) return false;
ok = data[0] != 0;
message = data.Length > 1 ? Encoding.UTF8.GetString(data, 1, data.Length - 1) : "";
return true;
}
private static int WriteInt32(byte[] buf, int off, int v)
{
buf[off] = (byte)v;
buf[off + 1] = (byte)(v >> 8);
buf[off + 2] = (byte)(v >> 16);
buf[off + 3] = (byte)(v >> 24);
return off + 4;
}
private static int WriteBytes(byte[] buf, int off, byte[] src)
{
if (src.Length > 0) Buffer.BlockCopy(src, 0, buf, off, src.Length);
return off + src.Length;
}
private static int ReadInt32(byte[] buf, ref int off)
{
int v = buf[off] | (buf[off + 1] << 8) | (buf[off + 2] << 16) | (buf[off + 3] << 24);
off += 4;
return v;
}
}
}