Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of G3A3 Config Editor v1.1.0
G3A3ConfigEditor.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace G3A3ConfigEditor { [BepInPlugin("g3a3.configeditor", "G3A3 Config Editor", "1.1.0")] public class EditorPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(Menu), "Start")] private static class PauseMenuPatch { private static void Postfix(Menu __instance) { try { AddButton(__instance.m_settingsButton); } catch (Exception ex) { EditorPlugin instance = _instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)("Pause menu button: " + ex.Message)); } } } } [HarmonyPatch(typeof(FejdStartup), "Start")] private static class MainMenuPatch { private static readonly FieldInfo MenuButtons = AccessTools.Field(typeof(FejdStartup), "m_menuButtons"); private static void Postfix(FejdStartup __instance) { try { object obj = (MenuButtons?.GetValue(__instance) as Button[])?.FirstOrDefault((Func<Button, bool>)IsSettings); if (obj == null) { GameObject menuList = __instance.m_menuList; obj = ((menuList != null) ? ((IEnumerable<Button>)menuList.GetComponentsInChildren<Button>(true)).FirstOrDefault((Func<Button, bool>)IsSettings) : null); } AddButton((Button)obj); } catch (Exception ex) { EditorPlugin instance = _instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)("Main menu button: " + ex.Message)); } } } private static bool IsSettings(Button button) { if ((Object)(object)button == (Object)null) { return false; } for (int i = 0; i < ((UnityEventBase)button.onClick).GetPersistentEventCount(); i++) { if (((UnityEventBase)button.onClick).GetPersistentMethodName(i) == "OnButtonSettings") { return true; } } return false; } } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static UnityAction <>9__21_0; internal void <AddButton>b__21_0() { _instance.Open(null); } } public const string Guid = "g3a3.configeditor"; public const string Name = "G3A3 Config Editor"; public const string Version = "1.1.0"; private const string ButtonName = "G3A3ConfigEditorButton"; private static EditorPlugin _instance; private readonly ConcurrentQueue<Action> _mainThread = new ConcurrentQueue<Action>(); private ConfigEntry<KeyboardShortcut> _hotkey; private ConfigEntry<bool> _menuButtons; private ConfigEntry<int> _port; private EditorServer _server; private void Awake() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) _instance = this; _hotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "Hotkey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "EN: Key that opens the config editor in your browser. RU: Клавиша, которая открывает редактор конфигов в браузере."); _menuButtons = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "MenuButtons", true, "EN: When true, the main menu and the pause menu (Esc) get a Configs button. RU: Если true, в главном меню и в меню паузы (Esc) появляется кнопка «Конфиги»."); _port = ((BaseUnityPlugin)this).Config.Bind<int>("General", "Port", 47900, new ConfigDescription("EN: First port the editor tries on this computer (127.0.0.1); the next free one up to +20 is used. RU: Первый порт, который редактор пробует на этом компьютере (127.0.0.1); берётся следующий свободный, до +20.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1024, 65000), Array.Empty<object>())); if (Application.isBatchMode) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Dedicated server: the editor stays off."); return; } new Harmony("g3a3.configeditor").PatchAll(typeof(EditorPlugin).Assembly); new ConsoleCommand("configeditor", "[G3A3] opens the config editor in your browser", (ConsoleEvent)delegate(ConsoleEventArgs args) { Open(args.Context); }, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private void Update() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) Action result; while (_mainThread.TryDequeue(out result)) { try { result(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)ex); } } if (_hotkey == null || Application.isBatchMode) { return; } KeyboardShortcut value = _hotkey.Value; if ((int)((KeyboardShortcut)(ref value)).MainKey != 0) { value = _hotkey.Value; if (((KeyboardShortcut)(ref value)).IsDown() && !Typing()) { Open(null); } } } private void OnDestroy() { _server?.Stop(); } private static bool Typing() { if (!Console.IsVisible() && !TextInput.IsVisible()) { if ((Object)(object)Chat.instance != (Object)null) { return Chat.instance.HasFocus(); } return false; } return true; } internal void Open(Terminal output) { try { if (_server == null) { string backupRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "G3A3 Config Editor", "backups"); _server = new EditorServer(Paths.ConfigPath, backupRoot, LoadPage(), Hint(), ApplyInGame, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }); } if (!_server.Running && !_server.Start(_port.Value)) { ((BaseUnityPlugin)this).Logger.LogError((object)("No free port from " + _port.Value + " to " + (_port.Value + 20) + ".")); if (output != null) { output.AddString("Config editor: no free port from " + _port.Value); } } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Config editor: " + _server.Url)); if (output != null) { output.AddString("Config editor: " + _server.Url); } Application.OpenURL(_server.Url); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Config editor could not start: " + ex)); if (output != null) { output.AddString("Config editor could not start: " + ex.Message); } } } private static string LoadPage() { using Stream stream = typeof(EditorPlugin).Assembly.GetManifestResourceStream("G3A3ConfigEditor.ConfigEditor.html"); using StreamReader streamReader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); return streamReader.ReadToEnd(); } private unsafe string Hint() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = _hotkey.Value; string text; if ((int)((KeyboardShortcut)(ref value)).MainKey == 0) { text = string.Empty; } else { value = _hotkey.Value; text = " или клавиша " + ((object)(*(KeyboardShortcut*)(&value))/*cast due to .constrained prefix*/).ToString(); } string text2 = text; return (_menuButtons.Value ? "кнопка «Конфиги» в меню (Esc)" : "команда configeditor в консоли") + text2; } private bool ApplyInGame(string path) { bool applied = false; ManualResetEvent done = new ManualResetEvent(initialState: false); _mainThread.Enqueue(delegate { try { applied = Reload(path); } finally { done.Set(); } }); return done.WaitOne(3000) && applied; } private static bool Reload(string path) { bool result = false; foreach (ConfigFile item in OpenConfigs()) { if (string.Equals(Path.GetFullPath(item.ConfigFilePath), path, StringComparison.OrdinalIgnoreCase)) { item.Reload(); result = true; } } return result; } private static IEnumerable<ConfigFile> OpenConfigs() { HashSet<ConfigFile> seen = new HashSet<ConfigFile>(); object? obj = AccessTools.Property(typeof(ConfigFile), "CoreConfig")?.GetValue(null, null); ConfigFile val = (ConfigFile)((obj is ConfigFile) ? obj : null); if (val != null && seen.Add(val)) { yield return val; } foreach (PluginInfo item in Chainloader.PluginInfos.Values.ToList()) { ConfigFile val2 = (((Object)(object)item.Instance != (Object)null) ? item.Instance.Config : null); if (val2 != null && seen.Add(val2)) { yield return val2; } } } private static string Label() { if (!(((Localization.instance != null) ? Localization.instance.GetSelectedLanguage() : string.Empty) == "Russian")) { return "Configs"; } return "Конфиги"; } private static void AddButton(Button template) { //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Expected O, but got Unknown //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Expected O, but got Unknown if ((Object)(object)template == (Object)null || (Object)(object)_instance == (Object)null || !_instance._menuButtons.Value) { return; } Transform parent = ((Component)template).transform.parent; if ((Object)(object)parent == (Object)null || (Object)(object)parent.Find("G3A3ConfigEditorButton") != (Object)null) { return; } if ((Object)(object)((Component)parent).GetComponent<LayoutGroup>() == (Object)null) { ((BaseUnityPlugin)_instance).Logger.LogInfo((object)("No layout around the " + ((Object)template).name + " button: no Configs button there, use the hotkey.")); return; } GameObject val = Object.Instantiate<GameObject>(((Component)template).gameObject, parent); ((Object)val).name = "G3A3ConfigEditorButton"; val.transform.SetSiblingIndex(((Component)template).transform.GetSiblingIndex() + 1); Localize[] componentsInChildren = val.GetComponentsInChildren<Localize>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren[i]); } UIGamePad[] componentsInChildren2 = val.GetComponentsInChildren<UIGamePad>(true); for (int i = 0; i < componentsInChildren2.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren2[i]); } TMP_Text[] componentsInChildren3 = val.GetComponentsInChildren<TMP_Text>(true); for (int i = 0; i < componentsInChildren3.Length; i++) { componentsInChildren3[i].text = Label(); } Button component = val.GetComponent<Button>(); component.onClick = new ButtonClickedEvent(); ButtonClickedEvent onClick = component.onClick; object obj = <>c.<>9__21_0; if (obj == null) { UnityAction val2 = delegate { _instance.Open(null); }; <>c.<>9__21_0 = val2; obj = (object)val2; } ((UnityEvent)onClick).AddListener((UnityAction)obj); val.SetActive(true); } } internal sealed class EditorServer { private sealed class Request { public string Method; public string Path; public Dictionary<string, string> Query; public Dictionary<string, string> Headers; public byte[] Body; public string Arg(string name) { if (!Query.TryGetValue(name, out var value)) { return null; } return value; } public string Header(string name) { if (!Headers.TryGetValue(name, out var value)) { return null; } return value; } } private sealed class Response { public int Status; public string Type; public byte[] Body; public bool StopAfter; } private const int MaxHeader = 32768; private const int MaxBody = 16777216; private const int KeepBackups = 15; private const int MaxDepth = 4; private static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private static readonly byte[] HeaderEnd = new byte[4] { 13, 10, 13, 10 }; private static readonly Regex PluginLine = new Regex("^## Settings file was created by plugin (.+?)(?: v(\\S+))?\\s*$"); private readonly string _configDir; private readonly string _backupRoot; private readonly string _page; private readonly string _hint; private readonly Func<string, bool> _apply; private readonly Action<string> _log; private readonly string _token = Guid.NewGuid().ToString("N"); private TcpListener _listener; private volatile bool _running; public int Port { get; private set; } public bool Running => _running; public string Url => "http://127.0.0.1:" + Port + "/"; public EditorServer(string configDir, string backupRoot, string page, string hint, Func<string, bool> apply, Action<string> log) { _configDir = Path.GetFullPath(configDir).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); _backupRoot = backupRoot; _page = page; _hint = hint; _apply = apply; _log = log ?? ((Action<string>)delegate { }); } public bool Start(int firstPort) { if (_running) { return true; } for (int i = firstPort; i <= firstPort + 20 && i <= 65535; i++) { TcpListener tcpListener = new TcpListener(IPAddress.Loopback, i) { ExclusiveAddressUse = true }; try { tcpListener.Start(); } catch (SocketException) { continue; } _listener = tcpListener; Port = i; _running = true; Thread thread = new Thread(Loop); thread.IsBackground = true; thread.Name = "G3A3 Config Editor"; thread.Start(); return true; } return false; } public void Stop() { _running = false; try { _listener?.Stop(); } catch (Exception) { } } private void Loop() { while (_running) { TcpClient client; try { client = _listener.AcceptTcpClient(); } catch (Exception) { if (_running) { Thread.Sleep(200); } continue; } ThreadPool.QueueUserWorkItem(delegate { Serve(client); }); } } private void Serve(TcpClient client) { bool flag = false; using (client) { try { client.ReceiveTimeout = 10000; client.SendTimeout = 10000; NetworkStream stream = client.GetStream(); Request request = ReadRequest(stream); if (request == null) { return; } Response response; try { response = ((request.Header("Transfer-Encoding") != null) ? Text(411, "text/plain; charset=utf-8", "length required") : Handle(request)); } catch (Exception ex) { _log("request failed: " + ex.Message); response = Json(500, new Dictionary<string, object> { ["error"] = ex.Message }); } flag = response.StopAfter; Write(stream, response); Linger(client, stream); } catch (Exception ex2) { _log("connection failed: " + ex2.Message); } } if (flag) { Stop(); } } private static Request ReadRequest(Stream stream) { MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[8192]; int num = -1; while (num < 0) { int num2 = stream.Read(array, 0, array.Length); if (num2 <= 0) { return null; } memoryStream.Write(array, 0, num2); num = IndexOf(memoryStream.GetBuffer(), (int)memoryStream.Length, HeaderEnd); if (num < 0 && memoryStream.Length > 32768) { return null; } } byte[] array2 = memoryStream.ToArray(); string[] array3 = Encoding.ASCII.GetString(array2, 0, num).Split(new string[1] { "\r\n" }, StringSplitOptions.None); string[] array4 = array3[0].Split(new char[1] { ' ' }); if (array4.Length < 3) { return null; } Request request = new Request { Method = array4[0], Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) }; for (int i = 1; i < array3.Length; i++) { int num3 = array3[i].IndexOf(':'); if (num3 > 0) { request.Headers[array3[i].Substring(0, num3).Trim()] = array3[i].Substring(num3 + 1).Trim(); } } string text = array4[1]; int num4 = text.IndexOf('?'); request.Path = Uri.UnescapeDataString((num4 < 0) ? text : text.Substring(0, num4)); request.Query = ParseQuery((num4 < 0) ? string.Empty : text.Substring(num4 + 1)); int result = 0; string text2 = request.Header("Content-Length"); if (text2 != null && (!int.TryParse(text2, out result) || result < 0 || result > 16777216)) { return null; } byte[] array5 = new byte[result]; int j = Math.Min(result, array2.Length - (num + 4)); Array.Copy(array2, num + 4, array5, 0, j); int num5; for (; j < result; j += num5) { num5 = stream.Read(array5, j, result - j); if (num5 <= 0) { return null; } } request.Body = array5; return request; } private static Dictionary<string, string> ParseQuery(string query) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); string[] array = query.Split(new char[1] { '&' }); foreach (string text in array) { if (text.Length != 0) { int num = text.IndexOf('='); string text2 = ((num < 0) ? text : text.Substring(0, num)); string text3 = ((num < 0) ? string.Empty : text.Substring(num + 1)); dictionary[Uri.UnescapeDataString(text2.Replace('+', ' '))] = Uri.UnescapeDataString(text3.Replace('+', ' ')); } } return dictionary; } private static int IndexOf(byte[] data, int length, byte[] pattern) { for (int i = 0; i + pattern.Length <= length; i++) { int j; for (j = 0; j < pattern.Length && data[i + j] == pattern[j]; j++) { } if (j == pattern.Length) { return i; } } return -1; } private static void Write(Stream stream, Response response) { string s = "HTTP/1.1 " + response.Status + " " + Reason(response.Status) + "\r\nContent-Type: " + response.Type + "\r\nContent-Length: " + response.Body.Length + "\r\nCache-Control: no-store\r\nX-Content-Type-Options: nosniff\r\nConnection: close\r\n\r\n"; byte[] bytes = Encoding.ASCII.GetBytes(s); stream.Write(bytes, 0, bytes.Length); stream.Write(response.Body, 0, response.Body.Length); stream.Flush(); } private static void Linger(TcpClient client, NetworkStream stream) { try { client.Client.Shutdown(SocketShutdown.Send); client.ReceiveTimeout = 500; byte[] array = new byte[8192]; int num; for (int i = 0; i < 16777216; i += num) { num = stream.Read(array, 0, array.Length); if (num <= 0) { break; } } } catch (Exception) { } } private static string Reason(int status) { return status switch { 200 => "OK", 400 => "Bad Request", 403 => "Forbidden", 404 => "Not Found", 409 => "Conflict", 411 => "Length Required", _ => "Internal Server Error", }; } private static Response Text(int status, string type, string text) { return new Response { Status = status, Type = type, Body = Utf8.GetBytes(text) }; } private static Response Json(int status, object data) { return Text(status, "application/json; charset=utf-8", G3A3ConfigEditor.Json.Write(data)); } private static Response Error(int status, string message) { return Json(status, new Dictionary<string, object> { ["error"] = message }); } private Response Handle(Request request) { string text = request.Header("Host"); if (text != "127.0.0.1:" + Port && text != "localhost:" + Port) { return Text(403, "text/plain; charset=utf-8", "forbidden"); } string path = request.Path; if (request.Method == "GET" && (path == "/" || path == "/index.html")) { return Text(200, "text/html; charset=utf-8", _page.Replace("__G3A3_TOKEN__", _token)); } if (!path.StartsWith("/api/", StringComparison.Ordinal)) { return Text(404, "text/plain; charset=utf-8", "not found"); } string text2 = request.Header("Origin"); if (request.Header("X-G3A3-Token") != _token || (text2 != null && text2 != "http://127.0.0.1:" + Port && text2 != "http://localhost:" + Port)) { return Error(403, "forbidden"); } if (path == "/api/files" && request.Method == "GET") { return Files(); } if (path == "/api/status" && request.Method == "GET") { string text3 = Target(request.Arg("name")); return Json(200, new Dictionary<string, object> { ["game"] = false, ["modified"] = ((text3 != null && File.Exists(text3)) ? Stamp(text3) : string.Empty) }); } if (path == "/api/file" && request.Method == "GET") { return ReadFile(request.Arg("name")); } if (path == "/api/file" && request.Method == "POST") { return SaveFile(request); } if (path == "/api/quit" && request.Method == "POST") { Response response = Json(200, new Dictionary<string, object> { ["ok"] = true }); response.StopAfter = true; return response; } return Error(404, "not found"); } private Response Files() { string configDir = _configDir; char directorySeparatorChar = Path.DirectorySeparatorChar; string text = configDir + directorySeparatorChar; List<object> list = new List<object>(); foreach (string item in ConfigFiles()) { string[] array = PluginHead(item); list.Add(new Dictionary<string, object> { ["name"] = item.Substring(text.Length), ["size"] = new FileInfo(item).Length, ["modified"] = Stamp(item), ["plugin"] = array[0], ["version"] = array[1] }); } string[] array2 = PlaceInfo(_configDir); return Json(200, new Dictionary<string, object> { ["dir"] = _configDir, ["title"] = array2[0], ["source"] = array2[1], ["files"] = list, ["game"] = false, ["mode"] = "game", ["hint"] = _hint }); } private Response ReadFile(string name) { string text = Target(name); if (text == null || !File.Exists(text)) { return Error(404, "нет такого файла"); } return Json(200, new Dictionary<string, object> { ["name"] = name, ["text"] = ReadShared(text), ["modified"] = Stamp(text) }); } private Response SaveFile(Request request) { string text = request.Arg("name"); string text2 = Target(text); if (text2 == null || !File.Exists(text2)) { return Error(404, "нет такого файла"); } string text3 = Utf8.GetString(request.Body); if (!text3.Contains("[") || !text3.Contains("=")) { return Error(400, "это не похоже на конфиг BepInEx"); } string text4 = Stamp(text2); string text5 = request.Arg("since"); if (request.Arg("force") != "1" && !string.IsNullOrEmpty(text5) && text5 != text4) { return Json(409, new Dictionary<string, object> { ["error"] = "файл изменился на диске", ["modified"] = text4 }); } string text6 = SaveBackup(text2, text); File.WriteAllText(text2, text3, Utf8); bool flag = false; try { flag = _apply != null && _apply(text2); } catch (Exception ex) { _log("could not apply " + text + " in the game: " + ex.Message); } _log("saved " + text + (flag ? ", applied in the game" : string.Empty) + " (backup: " + text6 + ")"); return Json(200, new Dictionary<string, object> { ["ok"] = true, ["modified"] = Stamp(text2), ["backup"] = text6, ["game"] = false, ["applied"] = flag }); } private List<string> ConfigFiles() { List<string> result = new List<string>(); Collect(_configDir, 0, result); return result; } private static void Collect(string dir, int depth, List<string> result) { try { result.AddRange(from f in Directory.GetFiles(dir, "*.cfg") where string.Equals(Path.GetExtension(f), ".cfg", StringComparison.OrdinalIgnoreCase) select f); if (depth < 4) { string[] directories = Directory.GetDirectories(dir); for (int num = 0; num < directories.Length; num++) { Collect(directories[num], depth + 1, result); } } } catch (Exception) { } } private string Target(string name) { if (string.IsNullOrEmpty(name)) { return null; } char directorySeparatorChar = Path.DirectorySeparatorChar; name = name.Replace('/', directorySeparatorChar).Replace('\\', directorySeparatorChar); if (!name.EndsWith(".cfg", StringComparison.OrdinalIgnoreCase) || Path.IsPathRooted(name) || name.IndexOf(':') >= 0 || name.Split(new char[1] { directorySeparatorChar }).Contains("..") || name.IndexOfAny(Path.GetInvalidPathChars()) >= 0) { return null; } string text = _configDir + directorySeparatorChar; string fullPath; try { fullPath = Path.GetFullPath(text + name); } catch (Exception) { return null; } if (!fullPath.StartsWith(text, StringComparison.OrdinalIgnoreCase)) { return null; } return fullPath; } private static string Stamp(string path) { return File.GetLastWriteTimeUtc(path).Ticks.ToString(); } private static string ReadShared(string path) { using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); using StreamReader streamReader = new StreamReader(stream, Utf8, detectEncodingFromByteOrderMarks: true); return streamReader.ReadToEnd(); } private static string[] PluginHead(string path) { try { using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); using StreamReader streamReader = new StreamReader(stream, Utf8, detectEncodingFromByteOrderMarks: true); Match match = PluginLine.Match(streamReader.ReadLine() ?? string.Empty); if (match.Success) { return new string[2] { match.Groups[1].Value, match.Groups[2].Value }; } } catch (Exception) { } return new string[2] { string.Empty, string.Empty }; } private static string[] PlaceInfo(string dir) { string[] parts = dir.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); int num = parts.Length; if (num >= 3 && Is(num - 1, "config") && Is(num - 2, "BepInEx")) { string text = parts[num - 3]; if (num >= 6 && Is(num - 4, "profiles")) { string text2 = (Is(num - 6, "DataFolder") ? "Thunderstore Mod Manager" : (Is(num - 6, "r2modmanPlus-local") ? "r2modman" : (Is(num - 6, "com.kesomannen.gale") ? "Gale" : "Профили мод-менеджеров"))); return new string[2] { parts[num - 5] + " · " + text, text2 }; } if (num >= 5 && Is(num - 4, "common") && Is(num - 5, "steamapps")) { return new string[2] { text, "Steam" }; } return new string[2] { text, "Папки игр" }; } return new string[2] { parts[num - 1], "Другие папки" }; bool Is(int i, string value) { if (i >= 0) { return string.Equals(parts[i], value, StringComparison.OrdinalIgnoreCase); } return false; } } private string SaveBackup(string path, string name) { byte[] source; using (SHA1 sHA = SHA1.Create()) { source = sHA.ComputeHash(Utf8.GetBytes(_configDir.ToLowerInvariant())); } string text = Regex.Replace(Regex.Replace(PlaceInfo(_configDir)[0], "\\s*·\\s*", "-"), "[\\\\/:*?\"<>|]", "_"); string text2 = Path.Combine(_backupRoot, string.Concat(text, "-", string.Concat(from b in source.Take(4) select b.ToString("x2")))); Directory.CreateDirectory(text2); string text3 = name.Replace('/', '\\').Replace("\\", "__"); string text4 = Path.Combine(text2, text3 + "." + DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".bak"); File.Copy(path, text4, overwrite: true); foreach (string item in Directory.GetFiles(text2, text3 + ".*.bak").OrderByDescending<string, string>((string f) => f, StringComparer.Ordinal).Skip(15)) { File.Delete(item); } string? fileName = Path.GetFileName(text2); char directorySeparatorChar = Path.DirectorySeparatorChar; return fileName + directorySeparatorChar + Path.GetFileName(text4); } } internal static class Json { public static string Write(object value) { StringBuilder stringBuilder = new StringBuilder(); Append(stringBuilder, value); return stringBuilder.ToString(); } private static void Append(StringBuilder sb, object value) { if (value != null) { if (!(value is string text)) { if (!(value is bool flag)) { if (!(value is int) && !(value is long) && !(value is double) && !(value is float)) { if (!(value is IDictionary<string, object> dictionary)) { if (value is IEnumerable enumerable) { sb.Append('['); bool flag2 = true; foreach (object item in enumerable) { if (!flag2) { sb.Append(','); } flag2 = false; Append(sb, item); } sb.Append(']'); } else { AppendString(sb, value.ToString()); } return; } sb.Append('{'); bool flag3 = true; foreach (KeyValuePair<string, object> item2 in dictionary) { if (!flag3) { sb.Append(','); } flag3 = false; AppendString(sb, item2.Key); sb.Append(':'); Append(sb, item2.Value); } sb.Append('}'); } else { sb.Append(((IFormattable)value).ToString(null, CultureInfo.InvariantCulture)); } } else { sb.Append(flag ? "true" : "false"); } } else { AppendString(sb, text); } } else { sb.Append("null"); } } private static void AppendString(StringBuilder sb, string text) { sb.Append('"'); foreach (char c in text) { switch (c) { case '"': sb.Append("\\\""); continue; case '\\': sb.Append("\\\\"); continue; case '\n': sb.Append("\\n"); continue; case '\r': sb.Append("\\r"); continue; case '\t': sb.Append("\\t"); continue; } if (c < ' ' || c == '\u2028' || c == '\u2029') { StringBuilder stringBuilder = sb.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4")); } else { sb.Append(c); } } sb.Append('"'); } } }