diff --git a/Winch/Components/WinchTerminal.cs b/Winch/Components/WinchTerminal.cs new file mode 100644 index 00000000..34f4ac20 --- /dev/null +++ b/Winch/Components/WinchTerminal.cs @@ -0,0 +1,24 @@ +using CommandTerminal; +using UnityEngine; +using Winch.Config; + +namespace Winch.Components +{ + [RequireComponent(typeof(Terminal))] + internal class WinchTerminal : MonoBehaviour + { + public bool Enabled => WinchConfig.GetProperty("EnableDeveloperConsole", false); + + private Terminal terminal; + + public void Start() + { + terminal = GetComponent(); + } + + public void Update() + { + terminal.enabled = Enabled; + } + } +} diff --git a/Winch/Core/WinchCore.cs b/Winch/Core/WinchCore.cs index 24fb916b..415f0785 100644 --- a/Winch/Core/WinchCore.cs +++ b/Winch/Core/WinchCore.cs @@ -21,24 +21,32 @@ public static class WinchCore public static string WinchInstallLocation => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + internal static string Name => WinchCore.WinchModConfig["Name"].ToString(); + + internal static string Author => WinchCore.WinchModConfig["Author"].ToString(); + + internal static string GUID => WinchCore.WinchModConfig["ModGUID"].ToString(); + + internal static string Version => WinchCore.WinchModConfig["Version"].ToString(); + public static void Main() { - try - { - string metaPath = Path.Combine(WinchInstallLocation, "mod_meta.json"); - if (!File.Exists(metaPath)) - { - throw new FileNotFoundException($"Missing mod_meta.json file for Winch at {metaPath}. Reinstall the mod."); - } + try + { + string metaPath = Path.Combine(WinchInstallLocation, "mod_meta.json"); + if (!File.Exists(metaPath)) + { + throw new FileNotFoundException($"Missing mod_meta.json file for Winch at {metaPath}. Reinstall the mod."); + } - string metaText = File.ReadAllText(metaPath); - WinchModConfig = JsonConvert.DeserializeObject>(metaText) - ?? throw new InvalidOperationException($"Unable to parse mod_meta.json file at {metaPath}. Reinstall the mod."); - } - catch (Exception e) - { - Log.Error(e); - } + string metaText = File.ReadAllText(metaPath); + WinchModConfig = JsonConvert.DeserializeObject>(metaText) + ?? throw new InvalidOperationException($"Unable to parse mod_meta.json file at {metaPath}. Reinstall the mod."); + } + catch (Exception e) + { + Log.Error(e); + } string version = VersionUtil.GetVersion(); Log.Info($"Winch {version} booting up..."); diff --git a/Winch/Logging/Logger.cs b/Winch/Logging/Logger.cs index 489b706f..90f63c13 100644 --- a/Winch/Logging/Logger.cs +++ b/Winch/Logging/Logger.cs @@ -11,59 +11,62 @@ namespace Winch.Logging { - public class Logger + public class Logger { private LogFile? _log; private LogFile? _latestLog; - private bool _writeLogsToFile; - private bool _writeLogsToConsole; - private LogLevel? _minLogLevel; + private bool _writeLogsToFile = true; + private bool _writeLogsToConsole = false; - private LogSocket? _logSocket; + private LogSocket? _logSocket; - public string LogConsoleExe => Path.Combine(WinchCore.WinchInstallLocation, "WinchConsole.exe"); + public LogLevel minLogLevel => EnumUtil.Parse(WinchConfig.GetProperty("LogLevel", "DEBUG"), true, LogLevel.DEBUG); + public string LogConsoleExe => Path.Combine(WinchCore.WinchInstallLocation, "WinchConsole.exe"); public Logger() { _writeLogsToFile = WinchConfig.GetProperty("WriteLogsToFile", true); if (_writeLogsToFile) - { - _minLogLevel = EnumUtil.Parse(WinchConfig.GetProperty("LogLevel", "DEBUG"), true, LogLevel.DEBUG); - _log = new LogFile(); - _latestLog = new LogFile("latest.log"); - CleanupLogs(); - } - - _writeLogsToConsole = WinchConfig.GetProperty("WriteLogsToConsole", true); - - if (_writeLogsToConsole) - { - // Find an avialable port for the logs - var listener = new TcpListener(IPAddress.Loopback, 0); - listener.Start(); - var port = ((IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - - // Console exe will get the port from the WinchConfig file - WinchConfig.SetProperty("LogPort", $"{port}"); - - Info($"Writing logs to port {port}"); - - try - { - Info($"Starting console at path {LogConsoleExe}"); - Process.Start(LogConsoleExe); - - _logSocket = new LogSocket(this, port); - } - catch (Exception e) - { - Error($"Could not start console : {e}"); - } - } - - Info($"Writing logs to file: {_writeLogsToFile}. Writing logs to console: {_writeLogsToConsole}."); + { + _log = new LogFile(); + _latestLog = new LogFile("latest.log"); + CleanupLogs(); + } + + _writeLogsToConsole = WinchConfig.GetProperty("WriteLogsToConsole", true); + + if (_writeLogsToConsole) + { + // Find an avialable port for the logs + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + + // Console exe will get the port from the WinchConfig file + WinchConfig.SetProperty("LogPort", $"{port}"); + + Info($"Writing logs to port {port}"); + + try + { + Info($"Starting console at path {LogConsoleExe}"); + Process.Start(LogConsoleExe); + + _logSocket = new LogSocket(this, port); + } + catch (Exception e) + { + Error($"Could not start console : {e}"); + } + } + + Info($"Writing logs to file: {_writeLogsToFile}. Writing logs to console: {_writeLogsToConsole}."); + } + + private static void SetMinLogLevel(LogLevel level) + { } private static void CleanupLogs() @@ -96,25 +99,25 @@ private void Log(LogLevel level, string message) private void Log(LogLevel level, string message, string source) { - if (level < _minLogLevel) - return; - - if (_writeLogsToConsole) - { - _logSocket?.WriteToSocket(new LogMessage() - { - Level = level, - Message = message, - Source = source - }); - } + if (level < minLogLevel) + return; + + if (_writeLogsToConsole) + { + _logSocket?.WriteToSocket(new LogMessage() + { + Level = level, + Message = message, + Source = source + }); + } if (_writeLogsToFile) - { - string logMessage = $"[{GetLogTimestamp()}] [{source}] [{level}] : {message}"; - _log?.Write(logMessage); - _latestLog?.Write(logMessage); - } + { + string logMessage = $"[{GetLogTimestamp()}] [{source}] [{level}] : {message}"; + _log?.Write(logMessage); + _latestLog?.Write(logMessage); + } } private string GetLogTimestamp() diff --git a/Winch/Patches/SKUPatcher.cs b/Winch/Patches/SKUPatcher.cs index 0fc50899..47b66865 100644 --- a/Winch/Patches/SKUPatcher.cs +++ b/Winch/Patches/SKUPatcher.cs @@ -5,6 +5,7 @@ using CommandTerminal; using UnityEngine; using Winch.Config; +using Winch.Components; namespace Winch.Patches { @@ -17,13 +18,14 @@ public static void Prefix(this SKUSpecificDisabler __instance) __instance.destroyIfUnavailable = false; // Disable destroying // Enable terminal - if (__instance.TryGetComponent(out Terminal terminal) && WinchConfig.GetProperty("EnableDeveloperConsole", false)) + if (__instance.TryGetComponent(out Terminal terminal)) { terminal.ConsoleFont = Font.CreateDynamicFontFromOSFont("Courier New", 16); __instance.supportedBuilds = BuildEnvironment.ALL; __instance.supportedPlatforms = Platform.ALL; __instance.unsupportedOnSteamDeck = false; __instance.allowInConventionBuilds = true; + terminal.gameObject.AddComponent(); } } } diff --git a/Winch/Serialization/DredgeTypeConverter.cs b/Winch/Serialization/DredgeTypeConverter.cs index b55f3808..b4ff7f89 100644 --- a/Winch/Serialization/DredgeTypeConverter.cs +++ b/Winch/Serialization/DredgeTypeConverter.cs @@ -7,6 +7,7 @@ using UnityEngine; using UnityEngine.Localization; using Winch.Core; +using Winch.Util; // ReSharper disable HeapView.BoxingAllocation // ReSharper disable HeapView.PossibleBoxingAllocation @@ -113,7 +114,7 @@ protected static LocalizedString CreateLocalizedString(string key, string value) { return cached; } - var localizedString = new LocalizedString(key, value); + var localizedString = LocalizationUtil.CreateReference(key, value); StringDefinitionCache.Add(keyValueTuple, localizedString); return localizedString; } diff --git a/Winch/Util/LocalizationUtil.cs b/Winch/Util/LocalizationUtil.cs index 0ec7c8a9..5e6d5c27 100644 --- a/Winch/Util/LocalizationUtil.cs +++ b/Winch/Util/LocalizationUtil.cs @@ -31,12 +31,12 @@ public static class LocalizationUtil private static Dictionary> StringDatabase = new Dictionary>(); private static Dictionary> StringTableDict = new Dictionary>(); - public static LocalizedString Empty => new LocalizedString(string.Empty, string.Empty); - public static LocalizedString Unknown => new LocalizedString("Strings", "label.unknown"); - public static LocalizedString CreateReference(string table, string entry) => new LocalizedString(table, entry); - internal static List AddedLocales = new List(); + public static LocalizedString CreateReference(string table, string entry) => new LocalizedString(table, entry); + public static LocalizedString Empty => LocalizationUtil.CreateReference(string.Empty, string.Empty); + public static LocalizedString Unknown => LocalizationUtil.CreateReference("Strings", "label.unknown"); + public static void InstallLocale(SystemLanguage language) => InstallLocale(Locale.CreateLocale(language)); public static void InstallLocale(Locale locale) diff --git a/Winch/Util/VersionUtil.cs b/Winch/Util/VersionUtil.cs index fe545751..e3d0e37c 100644 --- a/Winch/Util/VersionUtil.cs +++ b/Winch/Util/VersionUtil.cs @@ -12,7 +12,7 @@ internal class VersionUtil internal static string GetVersion() { - return WinchCore.WinchModConfig["Version"].ToString(); + return WinchCore.Version; } internal static bool ValidateVersion(string version) diff --git a/WinchCommon/Config/JSONConfig.cs b/WinchCommon/Config/JSONConfig.cs index 16ab1afe..45affdbb 100644 --- a/WinchCommon/Config/JSONConfig.cs +++ b/WinchCommon/Config/JSONConfig.cs @@ -3,27 +3,163 @@ using System.Collections.Generic; using System.IO; using Newtonsoft.Json.Linq; +using System.Text; // ReSharper disable HeapView.PossibleBoxingAllocation namespace Winch.Config { public class JSONConfig { - private readonly Dictionary _config; + private static JsonSerializer jsonSerializer = new JsonSerializer + { + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + Formatting = Newtonsoft.Json.Formatting.Indented, + }; + + private static StringBuilder stringBuilder = new StringBuilder(); + + private Dictionary _config; private readonly Dictionary _defaultConfig; private readonly string _configPath; + private readonly string _defaultConfigString; + private readonly string _defaultConfigPath; + + public static Dictionary ParseConfig(string value) + { + return JsonConvert.DeserializeObject>(value); + } - public JSONConfig(string path, string defaultConfig) { + public static string ToSerializedJson(object? value) + { + string json = "{}"; + using (StringWriter stringWriter = new StringWriter(stringBuilder)) + { + using (JsonTextWriter jsonTextWriter = new JsonTextWriter(stringWriter) + { + Formatting = Newtonsoft.Json.Formatting.Indented, + IndentChar = '\t', + Indentation = 1 + }) + { + jsonSerializer.Serialize(jsonTextWriter, value); + json = stringBuilder.ToString(); + stringBuilder.Clear(); + } + } + return json; + } + + public static void WriteConfig(string path, object value) + { + WriteConfig(path, ToSerializedJson(value)); + } + + public static void WriteConfig(string path, string value) + { + File.WriteAllText(path, value); + } + + public JSONConfig(string path, string defaultConfig) + { _configPath = path; - if (!File.Exists(_configPath)) + if (!string.IsNullOrWhiteSpace(defaultConfig)) { - File.WriteAllText(_configPath, defaultConfig); - _defaultConfig = JsonConvert.DeserializeObject>(defaultConfig) ?? throw new InvalidOperationException("Unable to parse default config."); + if (defaultConfig.Contains("{")) + { + _defaultConfigString = defaultConfig; + _defaultConfig = ParseConfig(defaultConfig); + } + else + { + _defaultConfigPath = defaultConfig; + string dconfText = File.ReadAllText(_defaultConfigPath); + _defaultConfigString = dconfText; + _defaultConfig = ParseConfig(dconfText); + } + + if (!File.Exists(_configPath)) + { + WriteConfig(_configPath, _defaultConfigString); + } + else + { + _config = ParseConfig(_defaultConfigString); + string pconfText = File.ReadAllText(_configPath); + var parsedConfig = ParseConfig(pconfText) ?? throw new InvalidOperationException("Unable to parse config file."); + foreach (var kvp in parsedConfig) + { + var value = kvp.Value is JObject objectValue ? objectValue["value"] : kvp.Value; + if (_config.TryGetValue(kvp.Key, out var defaultValue)) + { + if (defaultValue is JObject setting) + { + setting["value"] = JToken.FromObject(value); + } + else + { + _config[kvp.Key] = value; + } + } + else + _config[kvp.Key] = kvp.Value; + } + WriteConfig(_configPath, _config); + return; + } } string confText = File.ReadAllText(_configPath); - _config = JsonConvert.DeserializeObject>(confText) ?? throw new InvalidOperationException("Unable to parse config file."); + _config = ParseConfig(confText) ?? throw new InvalidOperationException("Unable to parse config file."); + } + + internal void ResetToDefaultConfig() + { + _config = ParseConfig(_defaultConfigString); + WriteConfig(_configPath, _defaultConfigString); + } + + internal void ResetPropertyToDefault(string key) + { + var defaultValue = GetProperty(_defaultConfig, key); + SetProperty(_config, key, defaultValue); + } + + internal static object? GetProperty(Dictionary config, string key, Dictionary defaultConfig = null) + { + if (!config.TryGetValue(key, out var setting)) + { + if (defaultConfig != null && defaultConfig.TryGetValue(key, out var defaultValue)) + { + SetProperty(config, key, defaultValue); + setting = defaultValue; + } + else + { + throw new InvalidOperationException($"No default config value found for {key}."); + } + } + return setting is JObject objectValue ? objectValue["value"] : setting; + } + + internal static T? GetProperty(Dictionary config, string key, Dictionary defaultConfig = null) + { + var type = typeof(T); + var value = GetProperty(config, key, defaultConfig); + return type.IsEnum ? ConvertToEnum(value) : (T)Convert.ChangeType(value, type); + } + + internal static void SetProperty(Dictionary config, string key, T? value) + { + if (config[key] is JObject setting) + { + setting["value"] = JToken.FromObject(value); + } + else + { + config[key] = value; + } } internal Dictionary GetDefaultProperties() @@ -33,7 +169,7 @@ internal Dictionary GetDefaultProperties() public T? GetDefaultProperty(string key) { - return _defaultConfig.ContainsKey(key) ? (T?)_defaultConfig[key] : default; + return GetProperty(_defaultConfig, key); } internal Dictionary GetProperties() @@ -41,28 +177,52 @@ internal Dictionary GetProperties() return _config; } - public T? GetProperty(string key, T? defaultValue) + public T? GetProperty(string key) { - if (!_config.ContainsKey(key)) - { - SetProperty(key, defaultValue); - return defaultValue; - } - return (T?)_config[key]; + return GetProperty(_config, key, _defaultConfig); } + [Obsolete] + public T? GetProperty(string key, T? defaultValue) => GetProperty(key); + public void SetProperty(string key, T? value) { - _config[key] = value; + SetProperty(_config, key, value); SaveSettings(); } private void SaveSettings() { - string confText = JsonConvert.SerializeObject(_config, Formatting.Indented); - File.WriteAllText(_configPath, confText); + WriteConfig(_configPath, _config); } public override string ToString() => _configPath; + + private static T ConvertToEnum(object value) + { + if (value == null) return default(T); + + if (value is float || value is double) + { + var floatValue = Convert.ToDouble(value); + return (T)Enum.ToObject(typeof(T), (long)Math.Round(floatValue)); + } + + if (value is int || value is long || value is short || value is uint || value is ulong || value is ushort || value is byte || value is sbyte) + { + return (T)Enum.ToObject(typeof(T), value); + } + + var valueString = Convert.ToString(value); + + try + { + return (T)Enum.Parse(typeof(T), valueString, true); + } + catch (ArgumentException ex) + { + throw new InvalidOperationException($"Can't convert {valueString} to enum {typeof(T)}", ex); + } + } } } diff --git a/WinchCommon/Config/ModConfig.cs b/WinchCommon/Config/ModConfig.cs index 734e288b..c4310e18 100644 --- a/WinchCommon/Config/ModConfig.cs +++ b/WinchCommon/Config/ModConfig.cs @@ -7,28 +7,36 @@ public class ModConfig : JSONConfig private static Dictionary DefaultConfigs = new Dictionary(); private static Dictionary Instances = new Dictionary(); - private ModConfig(string modName) : base(GetConfigPath(modName), GetDefaultConfigPath(modName)) + private ModConfig(string modName) : base(GetConfigPath(modName), GetDefaultConfig(modName)) { } - internal static string GetDefaultConfigPath(string modName) + internal static string GetDefaultConfig(string modName) { if(!DefaultConfigs.ContainsKey(modName)) { - //WinchCore.Log.Error($"No 'DefaultConfig' attribute found in mod_meta.json for {modName}!"); - throw new KeyNotFoundException($"No 'DefaultConfig' attribute found in mod_meta.json for {modName}!"); + var path = GetDefaultConfigPath(modName); + if (File.Exists(path)) + return path; + else + { + //WinchCore.Log.Error($"No 'DefaultConfig' attribute found in mod_meta.json for {modName}!"); + throw new KeyNotFoundException($"No 'DefaultConfig' attribute found in mod_meta.json for {modName}!"); + } } return DefaultConfigs[modName]; } + private static string GetDefaultConfigPath(string modName) + { + return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Mods", modName, "DefaultConfig.json"); + } + private static string GetConfigPath(string modName) { return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Mods", modName, "Config.json"); } - - - internal static ModConfig GetConfig(string modName) { if (!Instances.ContainsKey(modName)) @@ -55,6 +63,12 @@ internal static Dictionary GetProperties(string modName) return GetConfig(modName).GetProperties(); } + public static T? GetProperty(string modName, string key) + { + return GetConfig(modName).GetProperty(key); + } + + [Obsolete] public static T? GetProperty(string modName, string key, T? defaultValue) { return GetConfig(modName).GetProperty(key, defaultValue); diff --git a/WinchCommon/Config/WinchConfig.cs b/WinchCommon/Config/WinchConfig.cs index 59652579..593967c6 100644 --- a/WinchCommon/Config/WinchConfig.cs +++ b/WinchCommon/Config/WinchConfig.cs @@ -20,13 +20,31 @@ public static WinchConfig Instance } } + internal static new void ResetToDefaultConfig() + { + ((JSONConfig)Instance).ResetToDefaultConfig(); + } + + public static new T? GetDefaultProperty(string key) + { + return ((JSONConfig)Instance).GetDefaultProperty(key); + } + + internal static new Dictionary GetProperties() + { + return ((JSONConfig)Instance).GetProperties(); + } + + public static new T? GetProperty(string key) + { + return ((JSONConfig)Instance).GetProperty(key); + } + public static new T GetProperty(string key, T defaultValue) { try { - var property = ((JSONConfig)Instance).GetProperty(key, defaultValue); - if (property == null) return defaultValue; - return property; + return GetProperty(key) ?? defaultValue; } catch { @@ -34,9 +52,14 @@ public static WinchConfig Instance } } - public static new void SetProperty(string key, T value) - { - ((JSONConfig)Instance).SetProperty(key, value); - } + public static new void SetProperty(string key, T value) + { + ((JSONConfig)Instance).SetProperty(key, value); + } + + internal static new void ResetPropertyToDefault(string key) + { + ((JSONConfig)Instance).ResetPropertyToDefault(key); + } } }