diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0566270 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# IDE0305: Simplify collection initialization +dotnet_style_prefer_collection_expression = never diff --git a/DataInterfaceConsole/Actions/Settings/ManageSettings.cs b/DataInterfaceConsole/Actions/BaseManageSettingsMenu.cs similarity index 58% rename from DataInterfaceConsole/Actions/Settings/ManageSettings.cs rename to DataInterfaceConsole/Actions/BaseManageSettingsMenu.cs index 1fdaebb..f2dc3ab 100644 --- a/DataInterfaceConsole/Actions/Settings/ManageSettings.cs +++ b/DataInterfaceConsole/Actions/BaseManageSettingsMenu.cs @@ -1,18 +1,24 @@ -using DataInterfaceConsole.Actions.Settings; +using DataInterfaceConsole.Actions.EphemeralSettings; +using DataInterfaceConsole.Actions.Settings; using DataInterfaceConsole.Types; using System; using System.Linq; namespace DataInterfaceConsole.Actions; - -internal class ManageSettings : BaseAction { - public override string Name => "Manage Persistent Settings"; +internal abstract class BaseManageSettingsMenu : BaseAction { + public abstract ISettingsContainer SettingsHandler { get; } protected override void Run() { - WriteLineIndented("Select one of the settings that can be changed:"); - var settings = Program.instance.sh.GetSettings(); + WriteLineIndented("Select one of the following settings that you would like to change:"); + var settings = SettingsHandler.GetSettings(); var width = (int)Math.Log10(settings.Length) + 1; - WriteLineIndented(settings.SelectMany((s, i) => $"[{(i + 1).ToString().PadLeft(width)}] {s.Name}: {s.Description}\n\tCurrent value: {s.GetValueAsString()}".Split("\n"))); + WriteLineIndented(settings.SelectMany((s, i) => { + var rv = $"[{(i + 1).ToString().PadLeft(width)}] {s.Name}: {s.Description}"; + if (!s.HideOutputValue && s is not SettingsValuePrimitive) { + rv += $"\n\tCurrent value: {s.GetValueAsString()}"; + } + return rv.Split("\n"); + })); if (int.TryParse(ConsoleNonBlocking.ReadLineBlocking(), out int input) && input > 0 && input <= settings.Length) { var chosenSetting = settings[input - 1]; @@ -24,6 +30,7 @@ protected override void Run() { WriteLineIndented(allowedValues.Select((s, i) => $"[{(i + 1).ToString().PadLeft(width2)}] {s}"), 2); if (int.TryParse(ConsoleNonBlocking.ReadLineBlocking(), out int input2) && input2 > 0 && input2 <= allowedValues.Length) { sv.SetValue(allowedValues[input2 - 1]); + sv.OnSettingChanged(); } else { WriteLineIndented("Invalid input. Setting was left unchanged.", 2); return; @@ -33,16 +40,25 @@ protected override void Run() { var str = ConsoleNonBlocking.ReadLineBlocking(); if (int.TryParse(str, out int input2)) { sv2.SetPrimitive(input2); - } else if (str.Replace("\"", null).Replace("'", null).ToLowerInvariant() == "reset") { + sv2.OnSettingChanged(); + } else if (str.Replace("\"", null).Replace("'", null).Equals("reset", StringComparison.InvariantCultureIgnoreCase)) { sv2.SetPrimitive(null); + sv2.OnSettingChanged(); } else { WriteLineIndented("Invalid input format. Setting was left unchanged.", 2); return; } + } else if (chosenSetting is SettingsValuePrimitive sv3) { + WriteLineIndented($"Please enter a string to be set as the new value, or enter 'reset' to reset the setting:"); + var str = ConsoleNonBlocking.ReadLineBlocking(); + sv3.SetPrimitive(str); + sv3.OnSettingChanged(); + } else if (chosenSetting is SettingsValuePrimitive sv4) { + sv4.Value.Run(); } else { throw new NotImplementedException("This setting type has not been implemented yet!"); } - Program.instance.sh.Save(); + SettingsHandler.OnSettingsChanged(); } else { WriteLineIndented("Invalid setting chosen. Aborting."); } diff --git a/DataInterfaceConsole/Actions/EphemeralSettings/EphemeralSettingsContainer.cs b/DataInterfaceConsole/Actions/EphemeralSettings/EphemeralSettingsContainer.cs new file mode 100644 index 0000000..97562e5 --- /dev/null +++ b/DataInterfaceConsole/Actions/EphemeralSettings/EphemeralSettingsContainer.cs @@ -0,0 +1,51 @@ +using DataInterfaceConsole.Actions.Settings; +using FiveDChessDataInterface; +using FiveDChessDataInterface.Types; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace DataInterfaceConsole.Actions.EphemeralSettings; +internal class EphemeralSettingsContainer : ISettingsContainer { + public ISettingsValue[] GetSettings() => this.OptionsStore.Values.ToArray(); + + private readonly Dictionary OptionsStore = new Dictionary(); // string : OptionsValue + private void AddOption(ISettingsValue s) { + this.OptionsStore.Add(s.Id, s); + } + + private DataInterface Di => Program.instance.di; + + public EphemeralSettingsContainer() { + AddOption(new SettingsValueWhitelisted("UndoSubmittedMoves", "Allow Undoing Submitted Moves", "Allows Undoing submitted moves in Singleplayer Games (Local/CPU)", + ["on", "off"], "off", @this => Di.MemLocUndoMoveReducedByValue.SetValue((byte)(@this.GetValueAsString() == "off" ? 0xFF : 0x00)))); + AddOption(new SettingsValuePrimitive("PasteLobbyCode", "Set private match code", "Sets the ingame private match code used for joining someone else's game", "", + @this => { + string[] pieceMap = [ + ":pawn_white:", + ":knight_white:", + ":bishop_white:", + ":rook_white:", + ":queen_white:", + ":king_white:", + ":pawn_black:", + ":knight_black:", + ":bishop_black:", + ":rook_black:", + ":queen_black:", + ":king_black:" + ]; + + // this last integer (6) is the amount of already entered digits + int[] value = @this.GetValueAsString().Trim().Replace("\\", "").Replace("\"", "").Split(" ").Select(x => Array.IndexOf(pieceMap, x)).Append(6).ToArray(); + Di.MemLocJoiningRoomCodeArray.SetValue(new InlineMemoryArray(value)); + @this.SetPrimitive(""); + }) { HideOutputValue = true }); + AddOption(new SettingsValuePrimitive("ResumeGame", "Resume finished Game", "Restores the game to a state where an already finished game can be continued as if it never ended", + new Trigger(() => { + foreach (var curItem in new[] { Di.MemLocShowFinishGameButton, Di.MemLocShowEndOfGameDesc, Di.MemLocBackgroundColorChange, Di.MemLocPropertyAtEndOfGame }) { + curItem.SetValue(0); + } + }))); + } +} diff --git a/DataInterfaceConsole/Actions/EphemeralSettings/ManageEphemeralSettings.cs b/DataInterfaceConsole/Actions/EphemeralSettings/ManageEphemeralSettings.cs new file mode 100644 index 0000000..063e329 --- /dev/null +++ b/DataInterfaceConsole/Actions/EphemeralSettings/ManageEphemeralSettings.cs @@ -0,0 +1,9 @@ +using DataInterfaceConsole.Actions.Settings; + +namespace DataInterfaceConsole.Actions.EphemeralSettings; + +internal class ManageEphemeralSettings : BaseManageSettingsMenu { + public override string Name => "Ephemeral Settings and Triggers"; + + public override ISettingsContainer SettingsHandler => Program.instance.eh; +} diff --git a/DataInterfaceConsole/Actions/EphemeralSettings/Trigger.cs b/DataInterfaceConsole/Actions/EphemeralSettings/Trigger.cs new file mode 100644 index 0000000..a0ed283 --- /dev/null +++ b/DataInterfaceConsole/Actions/EphemeralSettings/Trigger.cs @@ -0,0 +1,16 @@ +using System; + +namespace DataInterfaceConsole.Actions.EphemeralSettings; +public class Trigger { + private readonly Action action; + + public override string ToString() { + return "Trigger"; + } + + public Trigger(Action action) { + this.action = action; + } + + public void Run() => action.Invoke(); +} diff --git a/DataInterfaceConsole/Actions/Settings/ISettingsContainer.cs b/DataInterfaceConsole/Actions/Settings/ISettingsContainer.cs new file mode 100644 index 0000000..01dc4e2 --- /dev/null +++ b/DataInterfaceConsole/Actions/Settings/ISettingsContainer.cs @@ -0,0 +1,5 @@ +namespace DataInterfaceConsole.Actions.Settings; +internal interface ISettingsContainer { + ISettingsValue[] GetSettings(); + void OnSettingsChanged() { } +} diff --git a/DataInterfaceConsole/Actions/Settings/ISettingsValue.cs b/DataInterfaceConsole/Actions/Settings/ISettingsValue.cs index 77fa502..f33e54a 100644 --- a/DataInterfaceConsole/Actions/Settings/ISettingsValue.cs +++ b/DataInterfaceConsole/Actions/Settings/ISettingsValue.cs @@ -9,4 +9,5 @@ public interface ISettingsValue { void SetValueDirect(JToken newValue); object GetValue(); string GetValueAsString(); + bool HideOutputValue { get; } } diff --git a/DataInterfaceConsole/Actions/Settings/ManagePersistentSettings.cs b/DataInterfaceConsole/Actions/Settings/ManagePersistentSettings.cs new file mode 100644 index 0000000..913cbfe --- /dev/null +++ b/DataInterfaceConsole/Actions/Settings/ManagePersistentSettings.cs @@ -0,0 +1,12 @@ +using DataInterfaceConsole.Actions.Settings; +using DataInterfaceConsole.Types; +using System; +using System.Linq; + +namespace DataInterfaceConsole.Actions; + +internal class ManagePersistentSettings : BaseManageSettingsMenu { + public override string Name => "Manage Persistent Settings"; + + public override ISettingsContainer SettingsHandler => Program.instance.sh; +} diff --git a/DataInterfaceConsole/Actions/Settings/SettingsHandler.cs b/DataInterfaceConsole/Actions/Settings/PersistentSettingsContainer.cs similarity index 93% rename from DataInterfaceConsole/Actions/Settings/SettingsHandler.cs rename to DataInterfaceConsole/Actions/Settings/PersistentSettingsContainer.cs index 53ae976..3fab74f 100644 --- a/DataInterfaceConsole/Actions/Settings/SettingsHandler.cs +++ b/DataInterfaceConsole/Actions/Settings/PersistentSettingsContainer.cs @@ -7,7 +7,7 @@ namespace DataInterfaceConsole.Actions.Settings; -internal class SettingsHandler { +internal class PersistentSettingsContainer : ISettingsContainer { private const string settingsFilePath = "settings.json"; public ISettingsValue[] GetSettings() => this.settingsStore.Values.ToArray(); @@ -20,9 +20,9 @@ private void AddSetting(ISettingsValue s) { private ISettingsValue GetSetting(string Id) => this.settingsStore[Id]; - public SettingsHandler() { + public PersistentSettingsContainer() { AddSetting(new SettingsValueWhitelisted("ForceTimetravelAnimationValue", "Force timetravel animation value", "Whether or not to force the timetravel button to a certain state", - new[] { "ignore", "always_on", "always_off" }, "ignore")); + ["ignore", "always_on", "always_off"], "ignore")); AddSetting(new SettingsValuePrimitive("Clock1BaseTime", "Short Timer Base Time", "The base time of the first clock in total seconds", null)); AddSetting(new SettingsValuePrimitive("Clock1Increment", "Short Timer Increment", "The increment of the first clock in seconds", null)); @@ -33,8 +33,8 @@ public SettingsHandler() { } - public static SettingsHandler LoadOrCreateNew() { - var sh = new SettingsHandler(); + public static PersistentSettingsContainer LoadOrCreateNew() { + var sh = new PersistentSettingsContainer(); if (File.Exists(settingsFilePath)) { var str = File.ReadAllText(settingsFilePath); @@ -49,6 +49,7 @@ public static SettingsHandler LoadOrCreateNew() { return sh; } + void ISettingsContainer.OnSettingsChanged() => Save(); public void Save() { var str = JsonConvert.SerializeObject(this.settingsStore.ToDictionary(x => x.Key, x => x.Value.GetValue()), Formatting.Indented); File.WriteAllText(settingsFilePath, str); diff --git a/DataInterfaceConsole/Actions/Settings/SettingsValue.cs b/DataInterfaceConsole/Actions/Settings/SettingsValue.cs index eae5f16..2909a7c 100644 --- a/DataInterfaceConsole/Actions/Settings/SettingsValue.cs +++ b/DataInterfaceConsole/Actions/Settings/SettingsValue.cs @@ -16,6 +16,7 @@ internal sealed class SettingsValue : ISettingsValue { public string Description { get; } public T Value { get; private set; } + public bool HideOutputValue { get; init; } = false; private SettingsValue(string id, string name, string description) { Id = id; diff --git a/DataInterfaceConsole/Actions/Settings/SettingsValuePrimitive.cs b/DataInterfaceConsole/Actions/Settings/SettingsValuePrimitive.cs index 8da5678..2b620f9 100644 --- a/DataInterfaceConsole/Actions/Settings/SettingsValuePrimitive.cs +++ b/DataInterfaceConsole/Actions/Settings/SettingsValuePrimitive.cs @@ -1,19 +1,24 @@ using Newtonsoft.Json.Linq; +using System; namespace DataInterfaceConsole.Actions.Settings; internal class SettingsValuePrimitive : ISettingsValue { + private readonly Action> onSettingChanged; + public string Id { get; } public string Name { get; } public string Description { get; } public T Value { get; private set; } + public bool HideOutputValue { get; init; } = false; - public SettingsValuePrimitive(string id, string name, string description, T defaultValue) { + public SettingsValuePrimitive(string id, string name, string description, T defaultValue, Action> onSettingChanged = null) { Id = id; Name = name; Description = description; Value = defaultValue; + this.onSettingChanged = onSettingChanged; } public object GetValue() { @@ -29,4 +34,6 @@ public string GetValueAsString() { public void SetValueDirect(JToken newValue) { Value = newValue.Value(); } + + public void OnSettingChanged() => this.onSettingChanged?.Invoke(this); } diff --git a/DataInterfaceConsole/Actions/Settings/SettingsValueWhitelisted.cs b/DataInterfaceConsole/Actions/Settings/SettingsValueWhitelisted.cs index 7a094ef..3487d21 100644 --- a/DataInterfaceConsole/Actions/Settings/SettingsValueWhitelisted.cs +++ b/DataInterfaceConsole/Actions/Settings/SettingsValueWhitelisted.cs @@ -5,6 +5,8 @@ namespace DataInterfaceConsole.Actions.Settings; internal class SettingsValueWhitelisted : ISettingsValue { + private readonly Action> onSettingChanged; + public string Id { get; } public string Name { get; } public string Description { get; } @@ -12,6 +14,7 @@ internal class SettingsValueWhitelisted : ISettingsValue { public T[] AllowedValues { get; private set; } public T Value { get; private set; } + public bool HideOutputValue { get; init; } = false; public object GetValue() { return Value; @@ -33,11 +36,14 @@ public string GetValueAsString() { return Value.ToString(); } - public SettingsValueWhitelisted(string id, string name, string description, T[] allowedValues, T defaultValue) { + public SettingsValueWhitelisted(string id, string name, string description, T[] allowedValues, T defaultValue, Action> onSettingChanged = null) { Id = id; Name = name; Description = description; AllowedValues = allowedValues; + this.onSettingChanged = onSettingChanged; SetValue(defaultValue); } + + public void OnSettingChanged() => this.onSettingChanged?.Invoke(this); } diff --git a/DataInterfaceConsole/Program.cs b/DataInterfaceConsole/Program.cs index 5df9d64..15cb1bc 100644 --- a/DataInterfaceConsole/Program.cs +++ b/DataInterfaceConsole/Program.cs @@ -1,4 +1,5 @@ using DataInterfaceConsole.Actions; +using DataInterfaceConsole.Actions.EphemeralSettings; using DataInterfaceConsole.Actions.Settings; using DataInterfaceConsole.Types; using DataInterfaceConsole.Types.Exceptions; @@ -14,7 +15,8 @@ internal class Program { internal static Program instance = new Program(); private Thread backgroundThread; public DataInterface di; - public SettingsHandler sh; + public PersistentSettingsContainer sh; + public EphemeralSettingsContainer eh; private static void Main() { ConsoleNonBlocking.Init(); @@ -24,7 +26,8 @@ private static void Main() { private void Run() { Console.WriteLine("Some output will occasionally be provided via the console title."); - this.sh = SettingsHandler.LoadOrCreateNew(); + this.sh = PersistentSettingsContainer.LoadOrCreateNew(); + this.eh = new EphemeralSettingsContainer(); this.backgroundThread = new Thread(BackgroundThreadRun) { Name = "BackgroundThread" diff --git a/FiveDChessDataInterface.sln b/FiveDChessDataInterface.sln index 668c17a..b2f4d39 100644 --- a/FiveDChessDataInterface.sln +++ b/FiveDChessDataInterface.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30503.244 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36518.9 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FiveDChessDataInterface", "FiveDChessDataInterface\FiveDChessDataInterface.csproj", "{EEEE52A7-F66A-4BFC-B6C5-E36F0A852774}" EndProject @@ -18,6 +18,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataInterfaceConsole", "Dat {EEEE52A7-F66A-4BFC-B6C5-E36F0A852774} = {EEEE52A7-F66A-4BFC-B6C5-E36F0A852774} EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/FiveDChessDataInterface/DataInterface.cs b/FiveDChessDataInterface/DataInterface.cs index 2def96a..64c1123 100644 --- a/FiveDChessDataInterface/DataInterface.cs +++ b/FiveDChessDataInterface/DataInterface.cs @@ -55,6 +55,19 @@ public class DataInterface { public MemoryLocation MemLocBlackIncrement { get; private set; } public MemoryLocation MemLocTimeTravelAnimationEnabled { get; private set; } + + public MemoryLocation MemLocUndoMoveReducedByValue { get; private set; } // setting this to 0 allows pressing Undo an arbitrary amount of times + + /// + /// Sequential, inline (no derefs) storage of the 6 piece values that make up the code, followed by an int32 specifying the numberof digits that were already entered + /// + public MemoryLocation> MemLocJoiningRoomCodeArray { get; private set; } + public MemoryLocation MemLocShowEndOfGameDesc { get; private set; } // Whether to show the text that states "game finished, black/white won", or "puzzle incomplete/complete" + public MemoryLocation MemLocShowFinishGameButton { get; private set; } + public MemoryLocation MemLocBackgroundColorChange { get; private set; } // This changes the Background Color sometimes when end of game is reached + public MemoryLocation MemLocPropertyAtEndOfGame { get; private set; } // Some property that also changes when the game ends, though currently unknown what it actually does + + [RequiredForSave("CosmeticTurnOffset")] public MemoryLocation MemLocCosmeticTurnOffset { get; private set; } @@ -222,7 +235,12 @@ private void CalculatePointers() { MemLocWhiteIncrement = new MemoryLocation(GetGameHandle(), chessboardPointerLocation, 0x1B0); MemLocBlackIncrement = new MemoryLocation(GetGameHandle(), chessboardPointerLocation, 0x1B4); MemLocTimeTravelAnimationEnabled = new MemoryLocation(GetGameHandle(), chessboardPointerLocation, 0x3E8); - + MemLocUndoMoveReducedByValue = new MemoryLocation(GetGameHandle(), chessboardPointerLocation - 0xE8A23); + MemLocJoiningRoomCodeArray = new MemoryLocation>(GetGameHandle(), chessboardPointerLocation - 0xF8); + MemLocShowFinishGameButton = new MemoryLocation(GetGameHandle(), chessboardPointerLocation, 0xC8); + MemLocShowEndOfGameDesc = new MemoryLocation(GetGameHandle(), chessboardPointerLocation, 0xD0); + MemLocBackgroundColorChange = new MemoryLocation(GetGameHandle(), chessboardPointerLocation, 0x3DC); + MemLocPropertyAtEndOfGame = new MemoryLocation(GetGameHandle(), chessboardPointerLocation, 0xD4); // set to -1 for even starting timeline cnt, and to 0 for odd starting timeline cnt MemLocTimelineValueOffset = new MemoryLocation(GetGameHandle(), chessboardPointerLocation, -0x34); MemLocWhiteTimelineCountInternal = new MemoryLocation(GetGameHandle(), chessboardPointerLocation, -0x30); diff --git a/FiveDChessDataInterface/MemoryHelpers/MemoryUtil.cs b/FiveDChessDataInterface/MemoryHelpers/MemoryUtil.cs index e355043..157ab1f 100644 --- a/FiveDChessDataInterface/MemoryHelpers/MemoryUtil.cs +++ b/FiveDChessDataInterface/MemoryHelpers/MemoryUtil.cs @@ -1,4 +1,5 @@ -using System; +using FiveDChessDataInterface.Types; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -87,6 +88,9 @@ public static void WriteValue(IntPtr handle, IntPtr location, T newValue) { var t = typeof(T); byte[] bytesToWrite = null; switch (Type.GetTypeCode(t)) { + case TypeCode.Byte: + bytesToWrite = new byte[1] { (byte)(object)newValue }; ; + break; case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: @@ -95,21 +99,23 @@ public static void WriteValue(IntPtr handle, IntPtr location, T newValue) { case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: - bytesToWrite = BitConverter.GetBytes((dynamic)newValue); break; - + bytesToWrite = BitConverter.GetBytes((dynamic)newValue); + break; case TypeCode.Object: - switch (t.FullName) { - case "System.IntPtr": - bytesToWrite = BitConverter.GetBytes(((IntPtr)(object)newValue).ToInt64()); break; + switch (newValue) { + case IntPtr v: + bytesToWrite = BitConverter.GetBytes(v.ToInt64()); + break; + case InlineMemoryArray v: + bytesToWrite = v.backing.SelectMany(x => BitConverter.GetBytes(x)).ToArray(); + break; default: throw new NotImplementedException("Invalid obj type"); } break; - default: throw new NotImplementedException("Invalid type"); } - KernelMethods.WriteMemory(handle, location, bytesToWrite); } diff --git a/FiveDChessDataInterface/Types/InlineMemoryArray.cs b/FiveDChessDataInterface/Types/InlineMemoryArray.cs new file mode 100644 index 0000000..4ff9a2d --- /dev/null +++ b/FiveDChessDataInterface/Types/InlineMemoryArray.cs @@ -0,0 +1,15 @@ +using System; + +namespace FiveDChessDataInterface.Types { + /// + /// Used by , + /// Represents a series of elements of type T, stored sequentially, located DIRECTLY at the position and not representing a by-ref-array. + /// + public class InlineMemoryArray { + internal readonly T[] backing; + + public InlineMemoryArray(T[] backing) { + this.backing = backing; + } + } +}