using System; using System.IO; using MelonLoader; using MelonLoader.Utils; using Newtonsoft.Json; namespace ADOFAIRenderer.Core.Data; /// /// Mod-wide settings that are not part of a render preset, stored as JSON in /// <game root>/UserData/ADOFAIRenderer/global_settings.json (see ). /// internal class GlobalConfig { /// /// Output directory chosen in the render configuration window; null or empty selects /// GlobalSettings.DefaultOutputDirectory. Ignored while the directory does not exist (see EffectiveOutputDirectory). /// public string? OutputDirectory { get; set; } /// Name of the preset last selected or saved in the render configuration window; selected again when it opens. public string? LastPresetName { get; set; } /// /// True once PresetManager.EnsureDefaultPresetsCreated has written the built-in preset files. Built-in presets the user /// deleted afterwards are not written again. /// public bool DefaultPresetsInitialized { get; set; } } /// /// Loads and saves . The file is read once by the static constructor and again by /// RenderMod.OnInitializeMelon (Load), and written by Save. Read and write errors are logged as warnings and never /// thrown; a file that cannot be read yields the default settings. Main thread. /// internal static class GlobalSettings { /// MelonLoader's game root directory, or the application base directory when it is empty. private static string GetGameRootDir() { var root = MelonEnvironment.GameRootDirectory; return string.IsNullOrEmpty(root) ? AppDomain.CurrentDomain.BaseDirectory : root; } /// <game root>/UserData/ADOFAIRenderer/global_settings.json. private static string SettingsFilePath => Path.Combine(GetGameRootDir(), "UserData", "ADOFAIRenderer", "global_settings.json"); /// /// Settings in memory. Changes are kept in memory until is called; replaces the /// object. /// public static GlobalConfig Current { get; private set; } = new(); /// Output directory used when none is configured: <game root>/Render. public static string DefaultOutputDirectory => Path.Combine(GetGameRootDir(), "Render"); /// /// Directory where renders without an explicit output path are written: Current.OutputDirectory when it is set and /// exists, otherwise . The default directory is not created here /// (RenderManager.SetupCaptureDirectory creates it). /// public static string EffectiveOutputDirectory { get { if (!string.IsNullOrWhiteSpace(Current.OutputDirectory) && Directory.Exists(Current.OutputDirectory)) { return Current.OutputDirectory; } return DefaultOutputDirectory; } } static GlobalSettings() { Load(); } /// /// Replaces with the content of the settings file. A missing, empty or unreadable file gives the /// default settings (an unreadable file is logged as a warning). /// public static void Load() { try { if (File.Exists(SettingsFilePath)) { var json = File.ReadAllText(SettingsFilePath); Current = JsonConvert.DeserializeObject(json) ?? new GlobalConfig(); return; } } catch (Exception ex) { MelonLogger.Warning($"[GlobalSettings] Failed to load global settings: {ex.Message}"); } Current = new GlobalConfig(); } /// /// Writes as indented JSON to the settings file, creating the directory when needed. /// Errors are logged as warnings. /// public static void Save() { try { var dir = Path.GetDirectoryName(SettingsFilePath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var json = JsonConvert.SerializeObject(Current, Formatting.Indented); File.WriteAllText(SettingsFilePath, json); } catch (Exception ex) { MelonLogger.Warning($"[GlobalSettings] Failed to save global settings: {ex.Message}"); } } }