Initial commit: Client Doc docs Server Tools
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using XWorld.Framework;
|
||||
|
||||
namespace XWorld.Server.Host
|
||||
{
|
||||
public sealed class GameModuleLoader
|
||||
{
|
||||
private readonly string _publicKeyPem;
|
||||
|
||||
public GameModuleLoader(string publicKeyPem = null)
|
||||
{
|
||||
_publicKeyPem = publicKeyPem;
|
||||
}
|
||||
|
||||
public LoadedGameModule Load(string gamesRoot, string gameId, int version)
|
||||
{
|
||||
string moduleDir = Path.Combine(gamesRoot, gameId, version.ToString());
|
||||
if (!Directory.Exists(moduleDir))
|
||||
throw new DirectoryNotFoundException($"游戏模块目录不存在: {moduleDir}");
|
||||
|
||||
if (_publicKeyPem != null)
|
||||
VerifySignature(moduleDir);
|
||||
|
||||
var manifest = GameManifest.Load(Path.Combine(moduleDir, "game.json"));
|
||||
if (manifest.GameId != gameId || manifest.Version != version)
|
||||
throw new InvalidDataException(
|
||||
$"game.json 与目录不符: 期望 {gameId} v{version}, 实际 {manifest.GameId} v{manifest.Version}");
|
||||
|
||||
if (manifest.MinFrameworkVersion > XWorld.Framework.FrameworkInfo.Version)
|
||||
throw new InvalidOperationException(
|
||||
$"小游戏要求最小框架版本 {manifest.MinFrameworkVersion},当前框架 {XWorld.Framework.FrameworkInfo.Version},请更新框架后重试");
|
||||
|
||||
string serverDll = Path.Combine(moduleDir, manifest.ServerAssembly);
|
||||
if (!File.Exists(serverDll))
|
||||
throw new FileNotFoundException($"服务端入口 DLL 不存在: {serverDll}");
|
||||
|
||||
var alc = new GameModuleAlc($"{gameId}@{version}", moduleDir);
|
||||
try
|
||||
{
|
||||
Assembly entryAsm = alc.LoadEntry(serverDll);
|
||||
Type entryType = entryAsm.GetType(manifest.ServerEntryType);
|
||||
if (entryType == null)
|
||||
throw new InvalidOperationException(
|
||||
$"在 {serverDll} 中找不到入口类型 {manifest.ServerEntryType}");
|
||||
if (!typeof(IGameServerRoom).IsAssignableFrom(entryType))
|
||||
throw new InvalidOperationException(
|
||||
$"{manifest.ServerEntryType} 未实现 IGameServerRoom");
|
||||
|
||||
Func<IGameServerRoom> factory = () => (IGameServerRoom)Activator.CreateInstance(entryType);
|
||||
return new LoadedGameModule(gameId, version, alc, factory);
|
||||
}
|
||||
catch
|
||||
{
|
||||
alc.Unload(); // 失败路径:立刻卸载,避免遗留锁住 DLL 的 collectible ALC
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// 注意:签名清单仅覆盖 ServerAssembly/CoreDll/game.json(当前小游戏 = Core+Server 两 DLL,完整覆盖)。
|
||||
// 若将来小游戏带私有第三方 DLL,发布工具(PublishLayout)与此处校验须同步把它们纳入清单。
|
||||
private void VerifySignature(string moduleDir)
|
||||
{
|
||||
string filesTxt = Path.Combine(moduleDir, "files.txt");
|
||||
string sigPath = Path.Combine(moduleDir, "files.txt.sig");
|
||||
if (!File.Exists(filesTxt) || !File.Exists(sigPath))
|
||||
throw new InvalidOperationException($"缺少签名清单,拒绝加载: {moduleDir}");
|
||||
var fm = XWorld.PublishTool.FilesManifest.Parse(File.ReadAllText(filesTxt));
|
||||
if (!XWorld.PublishTool.ManifestSigner.Verify(fm.Digest(), File.ReadAllBytes(sigPath), _publicKeyPem))
|
||||
throw new InvalidOperationException($"签名验证失败,拒绝加载: {moduleDir}");
|
||||
foreach (var kv in fm.Entries)
|
||||
{
|
||||
string p = Path.Combine(moduleDir, kv.Key);
|
||||
if (!File.Exists(p) || XWorld.PublishTool.Md5Util.OfFile(p) != kv.Value.Md5)
|
||||
throw new InvalidOperationException($"文件 md5 不符或缺失,拒绝加载: {kv.Key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user