using System.Text.Json; using System.Text.Json.Nodes; namespace CVRManager.Services; public static class CacheGuardService { const string AssemblyName = "CVRCacheGuard"; const string AssemblyFile = AssemblyName + ".dll"; const int UserAssemblyType = 16; static string Source => Path.Combine(AppContext.BaseDirectory, "CacheGuard"); static string Managed(string gameDir) => Path.Combine(gameDir, "ChilloutVR_Data", "Managed"); public static void Deploy(string gameDir, Action log, string? source = null) { var managed = Managed(gameDir); var scripting = Path.Combine(gameDir, "ChilloutVR_Data", "ScriptingAssemblies.json"); var init = Path.Combine(gameDir, "ChilloutVR_Data", "RuntimeInitializeOnLoads.json"); source ??= Source; if (!Directory.Exists(source) || !Directory.Exists(managed) || !File.Exists(scripting) || !File.Exists(init)) return; var changed = false; var deps = Path.Combine(managed, AssemblyName); Directory.CreateDirectory(deps); foreach (var file in Directory.EnumerateFiles(source, "*.dll")) { var name = Path.GetFileName(file); var target = name == AssemblyFile ? Path.Combine(managed, name) : Path.Combine(deps, name); changed |= CopyIfDifferent(file, target); } var scriptingJson = JsonNode.Parse(File.ReadAllText(scripting))!.AsObject(); var names = scriptingJson["names"]!.AsArray(); var types = scriptingJson["types"]!.AsArray(); if (!names.Any(n => n?.GetValue() == AssemblyFile)) { names.Add(AssemblyFile); types.Add(UserAssemblyType); WriteJson(scripting, scriptingJson); changed = true; } var initJson = JsonNode.Parse(File.ReadAllText(init))!.AsObject(); var root = initJson["root"]!.AsArray(); if (!root.Any(e => e?["assemblyName"]?.GetValue() == AssemblyName)) { root.Add(new JsonObject { ["assemblyName"] = AssemblyName, ["nameSpace"] = AssemblyName, ["className"] = "Entry", ["methodName"] = "Init", ["loadTypes"] = 2, ["isUnityClass"] = false, }); WriteJson(init, initJson); changed = true; } if (changed) log("Multi-instance cache guard added to this install."); } public static void Remove(string gameDir, Action log) { var data = Path.Combine(gameDir, "ChilloutVR_Data"); var scripting = Path.Combine(data, "ScriptingAssemblies.json"); var init = Path.Combine(data, "RuntimeInitializeOnLoads.json"); var changed = false; if (File.Exists(scripting)) { var json = JsonNode.Parse(File.ReadAllText(scripting))!.AsObject(); var names = json["names"]!.AsArray(); var types = json["types"]!.AsArray(); for (var i = names.Count - 1; i >= 0; i--) if (names[i]?.GetValue() == AssemblyFile) { names.RemoveAt(i); if (i < types.Count) types.RemoveAt(i); changed = true; } if (changed) WriteJson(scripting, json); } if (File.Exists(init)) { var json = JsonNode.Parse(File.ReadAllText(init))!.AsObject(); var root = json["root"]!.AsArray(); var before = root.Count; for (var i = root.Count - 1; i >= 0; i--) if (root[i]?["assemblyName"]?.GetValue() == AssemblyName) root.RemoveAt(i); if (root.Count != before) { WriteJson(init, json); changed = true; } } var managed = Managed(gameDir); try { File.Delete(Path.Combine(managed, AssemblyFile)); var deps = Path.Combine(managed, AssemblyName); if (Directory.Exists(deps)) Directory.Delete(deps, true); } catch (Exception e) when (e is IOException or UnauthorizedAccessException) { } if (changed) log("Multi-instance cache guard removed from this install."); } static bool CopyIfDifferent(string source, string target) { var s = new FileInfo(source); var t = new FileInfo(target); if (t.Exists && t.Length == s.Length && t.LastWriteTimeUtc == s.LastWriteTimeUtc) return false; try { File.Copy(source, target, true); File.SetLastWriteTimeUtc(target, s.LastWriteTimeUtc); return true; } catch (IOException) when (t.Exists) { return false; } } static void WriteJson(string path, JsonNode node) => File.WriteAllText(path, node.ToJsonString(new JsonSerializerOptions { WriteIndented = false }), new System.Text.UTF8Encoding(false)); }