using System.Diagnostics;
using System.Security.Cryptography;
using System.Text.Json;
namespace XFEWarframe.Client;
public sealed class ClientInstaller
{
public void Recover(string game)
{
game = ValidateGame(game); EnsureStopped(game);
using var operation = Lock(game);
FileTransaction.Recover(game);
}
public const string BridgeFile = "XFEWarframe.Bridge.dll";
public const string ConfigFile = "client.json";
public const string StateFile = "installation.json";
public const string MinimumBridgeVersion = "1.4.2";
public static readonly string[] LegacyProxies = ["dwmapi.dll", "wtsapi32.dll", "version.dll"];
public static string Managed(string game, string name) => SafePath.Within(game, "XFEWarframe/" + name);
public static string FindPayload()
{
foreach (var basePath in new[] { AppContext.BaseDirectory, Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..")) })
{
var path = Path.Combine(basePath, "bridge");
if (File.Exists(Path.Combine(path, "payload.json"))) return path;
}
throw new FileNotFoundException("未找到兼容组件,请使用完整发行包,或指定 --payload 目录。");
}
public CompatibilityReport Install(string game, string server, string payload, IProgress<string>? progress = null, CancellationToken cancel = default)
{
game = ValidateGame(game);
server = ClientProtocol.NormalizeServer(server);
EnsureStopped(game);
using var operation = Lock(game);
FileTransaction.Recover(game);
progress?.Report("检查客户端版本与关键签名…");
var report = Compatibility.Inspect(game);
if (!report.Compatible) throw new InvalidDataException(string.Join(Environment.NewLine, report.Problems));
progress?.Report(report.ExperimentalMultiAbilities ? $"此游戏版本可使用多技能实验绑定(需要兼容组件 {MinimumBridgeVersion} 或更新版本)。" : "此游戏版本不支持多技能实验绑定,保留常规单技能模式。");
cancel.ThrowIfCancellationRequested();
var metadata = JsonSerializer.Deserialize(File.ReadAllText(Path.Combine(payload, "payload.json")), ClientJson.Default.PayloadManifest) ?? throw new InvalidDataException("兼容组件清单为空。");
if (metadata.SchemaVersion != 1) throw new InvalidDataException("兼容组件清单版本不支持。");
RequireCurrentBridge(metadata.Version);
var bridgePath = Path.Combine(payload, BridgeFile);
var bridgeBytes = File.ReadAllBytes(bridgePath);
if (!string.Equals(Convert.ToHexString(SHA256.HashData(bridgeBytes)), metadata.Sha256, StringComparison.OrdinalIgnoreCase)) throw new InvalidDataException("兼容组件 SHA-256 校验失败。");
using (var pe = new BinaryImage(bridgeBytes)) _ = pe.ExportRva("XfeInitialize");
var previous = ReadState(game);
var changes = new Dictionary<string, byte[]?>();
var legacy = previous?.LegacyFiles.ToList() ?? [];
foreach (var file in LegacyProxies)
{
var path = SafePath.Within(game, file);
if (!File.Exists(path)) continue;
if (legacy.Any(x => x.FileName == file)) throw new IOException($"发现新出现的 {file},请先恢复旧安装,避免覆盖备份。");
var backupName = $"backups/legacy/{Guid.NewGuid():N}/{file}";
var bytes = File.ReadAllBytes(path);
var hash = Convert.ToHexString(SHA256.HashData(bytes));
changes.Add("XFEWarframe/" + backupName, bytes);
changes.Add(file, null);
legacy.Add(new(file, backupName, hash));
}
var configuration = new ClientConfiguration { ServerUrl = server, BuildLabel = report.Version, GameSha256 = report.Sha256, Addresses = report.Addresses, Prologues = report.Prologues };
changes.Add("XFEWarframe/" + BridgeFile, bridgeBytes);
changes.Add("XFEWarframe/" + ConfigFile, JsonSerializer.SerializeToUtf8Bytes(configuration, ClientJson.Default.ClientConfiguration));
changes.Add("XFEWarframe/compatibility.json", JsonSerializer.SerializeToUtf8Bytes(report, ClientJson.Default.CompatibilityReport));
var state = new InstallationState { Version = metadata.Version, GameSha256 = report.Sha256, LegacyFiles = legacy, InstalledAt = DateTimeOffset.UtcNow };
foreach (var file in new[] { BridgeFile, ConfigFile, "compatibility.json" })
state.Files[file] = Convert.ToHexString(SHA256.HashData(changes["XFEWarframe/" + file]!));
changes.Add("XFEWarframe/" + StateFile, JsonSerializer.SerializeToUtf8Bytes(state, ClientJson.Default.InstallationState));
progress?.Report("备份旧引导文件并安装兼容组件…");
if (Compatibility.Hash(report.Executable) != report.Sha256) throw new IOException("检测期间游戏已更新,请重新安装。");
FileTransaction.Apply(game, changes, cancel);
progress?.Report("安装完成,游戏 EXE 与缓存文件保持原样。");
return report;
}
public void Uninstall(string game, IProgress<string>? progress = null)
{
game = ValidateGame(game); EnsureStopped(game);
using var operation = Lock(game);
FileTransaction.Recover(game);
var state = ReadState(game) ?? throw new InvalidOperationException("此目录尚未安装 XFE 引导组件。");
var changes = new Dictionary<string, byte[]?>();
foreach (var entry in state.LegacyFiles)
{
if (!LegacyProxies.Contains(entry.FileName)) throw new InvalidDataException("未知旧引导文件。");
var backup = Managed(game, entry.BackupName);
if (Compatibility.Hash(backup) != entry.Sha256) throw new InvalidDataException("旧引导备份损坏。");
if (File.Exists(SafePath.Within(game, entry.FileName))) throw new IOException($"{entry.FileName} 已存在,不覆盖其他程序的文件。");
changes.Add(entry.FileName, File.ReadAllBytes(backup));
}
foreach (var (file, hash) in state.Files)
{
if (!new[] { BridgeFile, ConfigFile, "compatibility.json" }.Contains(file)) throw new InvalidDataException("未知安装文件。");
var path = Managed(game, file);
if (File.Exists(path) && Compatibility.Hash(path) != hash) throw new IOException($"{file} 已被修改,请先备份或修复再卸载。");
changes.Add("XFEWarframe/" + file, null);
}
changes.Add("XFEWarframe/" + StateFile, null);
FileTransaction.Apply(game, changes);
progress?.Report("XFE 引导已卸载,原引导已恢复;备份与诊断日志已保留。");
}
public static InstallationState? ReadState(string game)
{
var path = Managed(game, StateFile);
if (!File.Exists(path)) return null;
var result = JsonSerializer.Deserialize(File.ReadAllText(path), ClientJson.Default.InstallationState);
if (result?.SchemaVersion != 1) throw new InvalidDataException("安装记录版本不支持。");
return result;
}
public static ClientConfiguration VerifyInstalled(string game)
{
if (File.Exists(Managed(game, "transaction.json"))) throw new IOException("安装尚未完成,请先修复。");
var state = ReadState(game) ?? throw new InvalidOperationException("请先安装兼容组件。");
RequireCurrentBridge(state.Version);
if (state.GameSha256 != Compatibility.Hash(Path.Combine(game, "Warframe.x64.exe")))
throw new InvalidOperationException("游戏版本已更新,请重新检测并安装兼容组件。");
foreach (var (file, hash) in state.Files)
if (Compatibility.Hash(Managed(game, file)) != hash) throw new InvalidDataException("安装文件校验失败,请修复:" + file);
if (LegacyProxies.Any(f => File.Exists(SafePath.Within(game, f)))) throw new IOException("检测到冲突的旧引导 DLL,请修复安装。");
return JsonSerializer.Deserialize(File.ReadAllText(Managed(game, ConfigFile)), ClientJson.Default.ClientConfiguration)!;
}
public static string ValidateGame(string path)
{
path = Path.GetFullPath(path); SafePath.CheckNoLinks(path);
if (!File.Exists(Path.Combine(path, "Warframe.x64.exe"))) throw new FileNotFoundException("目录中没有 Warframe.x64.exe。");
return path;
}
private static void RequireCurrentBridge(string? version)
{
if (!Version.TryParse(version, out var installed) || installed < Version.Parse(MinimumBridgeVersion))
throw new InvalidDataException($"兼容组件版本过旧({version ?? "未知"}),当前启动器需要 {MinimumBridgeVersion} 或更新版本。请在“安装与维护”中修复安装。");
}
public static void EnsureStopped(string game)
{
foreach (var process in Process.GetProcessesByName("Warframe.x64"))
{
using (process)
{
try
{
if (string.Equals(Path.GetDirectoryName(process.MainModule?.FileName), game, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("请先关闭这份客户端,再安装或启动。");
}
catch (System.ComponentModel.Win32Exception) { throw new IOException("无法检查正在运行的客户端,请先关闭游戏。"); }
}
}
}
internal static FileStream Lock(string game)
{
var path = Managed(game, "operation.lock"); Directory.CreateDirectory(Path.GetDirectoryName(path)!);
return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
}
}
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text.Json;
namespace XFEWarframe.Client;
public sealed class ClientInstaller
{
public void Recover(string game)
{
game = ValidateGame(game); EnsureStopped(game);
using var operation = Lock(game);
FileTransaction.Recover(game);
}
public const string BridgeFile = "XFEWarframe.Bridge.dll";
public const string ConfigFile = "client.json";
public const string StateFile = "installation.json";
public const string MinimumBridgeVersion = "1.4.2";
public static readonly string[] LegacyProxies = ["dwmapi.dll", "wtsapi32.dll", "version.dll"];
public static string Managed(string game, string name) => SafePath.Within(game, "XFEWarframe/" + name);
public static string FindPayload()
{
foreach (var basePath in new[] { AppContext.BaseDirectory, Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..")) })
{
var path = Path.Combine(basePath, "bridge");
if (File.Exists(Path.Combine(path, "payload.json"))) return path;
}
throw new FileNotFoundException("未找到兼容组件,请使用完整发行包,或指定 --payload 目录。");
}
public CompatibilityReport Install(string game, string server, string payload, IProgress<string>? progress = null, CancellationToken cancel = default)
{
game = ValidateGame(game);
server = ClientProtocol.NormalizeServer(server);
EnsureStopped(game);
using var operation = Lock(game);
FileTransaction.Recover(game);
progress?.Report("检查客户端版本与关键签名…");
var report = Compatibility.Inspect(game);
if (!report.Compatible) throw new InvalidDataException(string.Join(Environment.NewLine, report.Problems));
progress?.Report(report.ExperimentalMultiAbilities ? $"此游戏版本可使用多技能实验绑定(需要兼容组件 {MinimumBridgeVersion} 或更新版本)。" : "此游戏版本不支持多技能实验绑定,保留常规单技能模式。");
cancel.ThrowIfCancellationRequested();
var metadata = JsonSerializer.Deserialize(File.ReadAllText(Path.Combine(payload, "payload.json")), ClientJson.Default.PayloadManifest) ?? throw new InvalidDataException("兼容组件清单为空。");
if (metadata.SchemaVersion != 1) throw new InvalidDataException("兼容组件清单版本不支持。");
RequireCurrentBridge(metadata.Version);
var bridgePath = Path.Combine(payload, BridgeFile);
var bridgeBytes = File.ReadAllBytes(bridgePath);
if (!string.Equals(Convert.ToHexString(SHA256.HashData(bridgeBytes)), metadata.Sha256, StringComparison.OrdinalIgnoreCase)) throw new InvalidDataException("兼容组件 SHA-256 校验失败。");
using (var pe = new BinaryImage(bridgeBytes)) _ = pe.ExportRva("XfeInitialize");
var previous = ReadState(game);
var changes = new Dictionary<string, byte[]?>();
var legacy = previous?.LegacyFiles.ToList() ?? [];
foreach (var file in LegacyProxies)
{
var path = SafePath.Within(game, file);
if (!File.Exists(path)) continue;
if (legacy.Any(x => x.FileName == file)) throw new IOException($"发现新出现的 {file},请先恢复旧安装,避免覆盖备份。");
var backupName = $"backups/legacy/{Guid.NewGuid():N}/{file}";
var bytes = File.ReadAllBytes(path);
var hash = Convert.ToHexString(SHA256.HashData(bytes));
changes.Add("XFEWarframe/" + backupName, bytes);
changes.Add(file, null);
legacy.Add(new(file, backupName, hash));
}
var configuration = new ClientConfiguration { ServerUrl = server, BuildLabel = report.Version, GameSha256 = report.Sha256, Addresses = report.Addresses, Prologues = report.Prologues };
changes.Add("XFEWarframe/" + BridgeFile, bridgeBytes);
changes.Add("XFEWarframe/" + ConfigFile, JsonSerializer.SerializeToUtf8Bytes(configuration, ClientJson.Default.ClientConfiguration));
changes.Add("XFEWarframe/compatibility.json", JsonSerializer.SerializeToUtf8Bytes(report, ClientJson.Default.CompatibilityReport));
var state = new InstallationState { Version = metadata.Version, GameSha256 = report.Sha256, LegacyFiles = legacy, InstalledAt = DateTimeOffset.UtcNow };
foreach (var file in new[] { BridgeFile, ConfigFile, "compatibility.json" })
state.Files[file] = Convert.ToHexString(SHA256.HashData(changes["XFEWarframe/" + file]!));
changes.Add("XFEWarframe/" + StateFile, JsonSerializer.SerializeToUtf8Bytes(state, ClientJson.Default.InstallationState));
progress?.Report("备份旧引导文件并安装兼容组件…");
if (Compatibility.Hash(report.Executable) != report.Sha256) throw new IOException("检测期间游戏已更新,请重新安装。");
FileTransaction.Apply(game, changes, cancel);
progress?.Report("安装完成,游戏 EXE 与缓存文件保持原样。");
return report;
}
public void Uninstall(string game, IProgress<string>? progress = null)
{
game = ValidateGame(game); EnsureStopped(game);
using var operation = Lock(game);
FileTransaction.Recover(game);
var state = ReadState(game) ?? throw new InvalidOperationException("此目录尚未安装 XFE 引导组件。");
var changes = new Dictionary<string, byte[]?>();
foreach (var entry in state.LegacyFiles)
{
if (!LegacyProxies.Contains(entry.FileName)) throw new InvalidDataException("未知旧引导文件。");
var backup = Managed(game, entry.BackupName);
if (Compatibility.Hash(backup) != entry.Sha256) throw new InvalidDataException("旧引导备份损坏。");
if (File.Exists(SafePath.Within(game, entry.FileName))) throw new IOException($"{entry.FileName} 已存在,不覆盖其他程序的文件。");
changes.Add(entry.FileName, File.ReadAllBytes(backup));
}
foreach (var (file, hash) in state.Files)
{
if (!new[] { BridgeFile, ConfigFile, "compatibility.json" }.Contains(file)) throw new InvalidDataException("未知安装文件。");
var path = Managed(game, file);
if (File.Exists(path) && Compatibility.Hash(path) != hash) throw new IOException($"{file} 已被修改,请先备份或修复再卸载。");
changes.Add("XFEWarframe/" + file, null);
}
changes.Add("XFEWarframe/" + StateFile, null);
FileTransaction.Apply(game, changes);
progress?.Report("XFE 引导已卸载,原引导已恢复;备份与诊断日志已保留。");
}
public static InstallationState? ReadState(string game)
{
var path = Managed(game, StateFile);
if (!File.Exists(path)) return null;
var result = JsonSerializer.Deserialize(File.ReadAllText(path), ClientJson.Default.InstallationState);
if (result?.SchemaVersion != 1) throw new InvalidDataException("安装记录版本不支持。");
return result;
}
public static ClientConfiguration VerifyInstalled(string game)
{
if (File.Exists(Managed(game, "transaction.json"))) throw new IOException("安装尚未完成,请先修复。");
var state = ReadState(game) ?? throw new InvalidOperationException("请先安装兼容组件。");
RequireCurrentBridge(state.Version);
if (state.GameSha256 != Compatibility.Hash(Path.Combine(game, "Warframe.x64.exe")))
throw new InvalidOperationException("游戏版本已更新,请重新检测并安装兼容组件。");
foreach (var (file, hash) in state.Files)
if (Compatibility.Hash(Managed(game, file)) != hash) throw new InvalidDataException("安装文件校验失败,请修复:" + file);
if (LegacyProxies.Any(f => File.Exists(SafePath.Within(game, f)))) throw new IOException("检测到冲突的旧引导 DLL,请修复安装。");
return JsonSerializer.Deserialize(File.ReadAllText(Managed(game, ConfigFile)), ClientJson.Default.ClientConfiguration)!;
}
public static string ValidateGame(string path)
{
path = Path.GetFullPath(path); SafePath.CheckNoLinks(path);
if (!File.Exists(Path.Combine(path, "Warframe.x64.exe"))) throw new FileNotFoundException("目录中没有 Warframe.x64.exe。");
return path;
}
private static void RequireCurrentBridge(string? version)
{
if (!Version.TryParse(version, out var installed) || installed < Version.Parse(MinimumBridgeVersion))
throw new InvalidDataException($"兼容组件版本过旧({version ?? "未知"}),当前启动器需要 {MinimumBridgeVersion} 或更新版本。请在“安装与维护”中修复安装。");
}
public static void EnsureStopped(string game)
{
foreach (var process in Process.GetProcessesByName("Warframe.x64"))
{
using (process)
{
try
{
if (string.Equals(Path.GetDirectoryName(process.MainModule?.FileName), game, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("请先关闭这份客户端,再安装或启动。");
}
catch (System.ComponentModel.Win32Exception) { throw new IOException("无法检查正在运行的客户端,请先关闭游戏。"); }
}
}
}
internal static FileStream Lock(string game)
{
var path = Managed(game, "operation.lock"); Directory.CreateDirectory(Path.GetDirectoryName(path)!);
return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
}
}