mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-10-16 23:49:19 +02:00
The game's key file has its descriptions stripped of them. Where a description is the bundled one without the letters, the bundled one is shown; the file itself is left as it is.
280 lines
8.4 KiB
C#
280 lines
8.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using StarterNG.Infrastructure.Adapters;
|
|
|
|
namespace StarterNG.Classes;
|
|
|
|
public sealed class KeyBinding
|
|
{
|
|
public string Command = string.Empty;
|
|
|
|
public bool Shift;
|
|
|
|
public bool Ctrl;
|
|
|
|
public string Key = "none";
|
|
|
|
public string Description = string.Empty;
|
|
|
|
internal int LineIndex = -1;
|
|
|
|
public bool IsAssigned =>
|
|
!string.IsNullOrEmpty(Key) &&
|
|
!Key.Equals("none", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public sealed class KeyboardConfig
|
|
{
|
|
public static KeyboardConfig Instance { get; } = new();
|
|
|
|
private readonly List<string> _lines = new();
|
|
|
|
public List<KeyBinding> Bindings { get; private set; } = new();
|
|
|
|
public bool Dirty { get; set; }
|
|
|
|
private string _savePath = string.Empty;
|
|
|
|
private static readonly Encoding FileEncoding = ResolveEncoding();
|
|
|
|
private static Encoding ResolveEncoding()
|
|
{
|
|
|
|
try
|
|
{
|
|
return LegacyText.CodePage1250;
|
|
}
|
|
catch
|
|
{
|
|
return Encoding.UTF8;
|
|
}
|
|
}
|
|
|
|
public static string UserConfigPath()
|
|
{
|
|
if (OperatingSystem.IsWindows())
|
|
{
|
|
string? appData = Environment.GetEnvironmentVariable("APPDATA");
|
|
if (!string.IsNullOrEmpty(appData))
|
|
return Path.Combine(appData, "MaSzyna", "eu07_input-keyboard.ini");
|
|
}
|
|
else if (OperatingSystem.IsMacOS())
|
|
{
|
|
string? home = Environment.GetEnvironmentVariable("HOME");
|
|
if (!string.IsNullOrEmpty(home))
|
|
return Path.Combine(home, "Library", "Application Support", "MaSzyna", "eu07_input-keyboard.ini");
|
|
}
|
|
else
|
|
{
|
|
string? home = Environment.GetEnvironmentVariable("HOME");
|
|
if (!string.IsNullOrEmpty(home))
|
|
return Path.Combine(home, ".config", "MaSzyna", "eu07_input-keyboard.ini");
|
|
}
|
|
return Path.Combine(AppContext.BaseDirectory, "eu07_input-keyboard.ini");
|
|
}
|
|
|
|
private static string WorkingDirDefaultPath() =>
|
|
Path.Combine(Directory.GetCurrentDirectory(), "eu07_input-keyboard.ini");
|
|
|
|
private static string BundledDefaultPath() =>
|
|
Path.Combine(AppContext.BaseDirectory, "startercfg", "eu07_input-keyboard.ini");
|
|
|
|
public void Load()
|
|
{
|
|
_savePath = UserConfigPath();
|
|
string source = FirstExisting(_savePath, WorkingDirDefaultPath(), BundledDefaultPath());
|
|
ParseSource(source);
|
|
LoadPolishDescriptions();
|
|
Dirty = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The game ships its key file with the descriptions stripped of Polish letters
|
|
/// ("zwiekszenie nastawnika glownego"); the copy bundled with the starter has
|
|
/// them. Read once, shown in place of the bare ones, never written back.
|
|
/// </summary>
|
|
private readonly Dictionary<string, string> _polishDescriptions = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
private void LoadPolishDescriptions()
|
|
{
|
|
_polishDescriptions.Clear();
|
|
try
|
|
{
|
|
string path = BundledDefaultPath();
|
|
if (!File.Exists(path))
|
|
return;
|
|
|
|
var bundled = new KeyboardConfig();
|
|
bundled.Parse(File.ReadAllText(path, FileEncoding));
|
|
foreach (var b in bundled.Bindings)
|
|
if (!string.IsNullOrEmpty(b.Description))
|
|
_polishDescriptions.TryAdd(b.Command, b.Description);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StarterNG.Infrastructure.Diagnostics.Log("reading the bundled key descriptions", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The description to show: the bundled one with Polish letters when the file's
|
|
/// is the same text without them, the file's own when someone wrote their own.
|
|
/// </summary>
|
|
public string DescriptionOf(KeyBinding binding)
|
|
{
|
|
if (_polishDescriptions.TryGetValue(binding.Command, out string? polish) &&
|
|
(string.IsNullOrEmpty(binding.Description) ||
|
|
string.Equals(StripPolish(polish), binding.Description.Trim(), StringComparison.OrdinalIgnoreCase)))
|
|
return polish;
|
|
|
|
return binding.Description;
|
|
}
|
|
|
|
public static string StripPolish(string text)
|
|
{
|
|
var sb = new StringBuilder(text.Length);
|
|
foreach (char c in text.Trim())
|
|
sb.Append(c switch
|
|
{
|
|
'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z', 'ż' => 'z',
|
|
'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'E', 'Ł' => 'L', 'Ń' => 'N', 'Ó' => 'O', 'Ś' => 'S', 'Ź' => 'Z', 'Ż' => 'Z',
|
|
_ => c
|
|
});
|
|
return sb.ToString();
|
|
}
|
|
|
|
public void LoadDefaults()
|
|
{
|
|
if (string.IsNullOrEmpty(_savePath))
|
|
_savePath = UserConfigPath();
|
|
string source = FirstExisting(WorkingDirDefaultPath(), BundledDefaultPath());
|
|
ParseSource(source);
|
|
}
|
|
|
|
private void ParseSource(string source)
|
|
{
|
|
try
|
|
{
|
|
string text = File.Exists(source) ? File.ReadAllText(source, FileEncoding) : string.Empty;
|
|
Parse(text);
|
|
}
|
|
catch
|
|
{
|
|
_lines.Clear();
|
|
Bindings = new List<KeyBinding>();
|
|
}
|
|
}
|
|
|
|
private static string FirstExisting(params string[] paths)
|
|
{
|
|
foreach (var p in paths)
|
|
if (!string.IsNullOrEmpty(p) && File.Exists(p))
|
|
return p;
|
|
return paths.Length > 0 ? paths[^1] : string.Empty;
|
|
}
|
|
|
|
public void Parse(string text)
|
|
{
|
|
_lines.Clear();
|
|
var bindings = new List<KeyBinding>();
|
|
|
|
text = text.Replace("\r\n", "\n").Replace('\r', '\n');
|
|
foreach (var rawLine in text.Split('\n'))
|
|
{
|
|
int idx = _lines.Count;
|
|
_lines.Add(rawLine);
|
|
|
|
if (TryParseBinding(rawLine, idx, out var binding))
|
|
bindings.Add(binding);
|
|
}
|
|
|
|
Bindings = bindings;
|
|
}
|
|
|
|
private static bool TryParseBinding(string raw, int lineIndex, out KeyBinding binding)
|
|
{
|
|
binding = new KeyBinding { LineIndex = lineIndex };
|
|
|
|
string body = raw;
|
|
string description = string.Empty;
|
|
|
|
int commentAt = body.IndexOf("//", StringComparison.Ordinal);
|
|
if (commentAt >= 0)
|
|
{
|
|
description = body.Substring(commentAt + 2).Trim();
|
|
body = body.Substring(0, commentAt);
|
|
}
|
|
|
|
body = body.Trim();
|
|
if (body.Length == 0)
|
|
return false;
|
|
|
|
var tokens = body.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
|
|
if (tokens.Length == 0)
|
|
return false;
|
|
|
|
binding.Command = tokens[0];
|
|
binding.Description = description;
|
|
|
|
bool shift = false, ctrl = false;
|
|
string key = "none";
|
|
for (int i = 1; i < tokens.Length; i++)
|
|
{
|
|
string t = tokens[i];
|
|
if (t.Equals("shift", StringComparison.OrdinalIgnoreCase)) shift = true;
|
|
else if (t.Equals("ctrl", StringComparison.OrdinalIgnoreCase)) ctrl = true;
|
|
else key = t;
|
|
}
|
|
|
|
binding.Shift = shift;
|
|
binding.Ctrl = ctrl;
|
|
binding.Key = key;
|
|
return true;
|
|
}
|
|
|
|
public string ToText()
|
|
{
|
|
foreach (var b in Bindings)
|
|
{
|
|
if (b.LineIndex < 0 || b.LineIndex >= _lines.Count)
|
|
continue;
|
|
_lines[b.LineIndex] = FormatLine(b);
|
|
}
|
|
return string.Join(Environment.NewLine, _lines);
|
|
}
|
|
|
|
private static string FormatLine(KeyBinding b)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.Append(b.Command);
|
|
sb.Append(' ');
|
|
if (b.Shift) sb.Append("shift ");
|
|
if (b.Ctrl) sb.Append("ctrl ");
|
|
sb.Append(b.IsAssigned ? b.Key : "none");
|
|
if (!string.IsNullOrEmpty(b.Description))
|
|
sb.Append(" // ").Append(b.Description);
|
|
return sb.ToString();
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
if (string.IsNullOrEmpty(_savePath))
|
|
_savePath = UserConfigPath();
|
|
|
|
try
|
|
{
|
|
string? dir = Path.GetDirectoryName(_savePath);
|
|
if (!string.IsNullOrEmpty(dir))
|
|
Directory.CreateDirectory(dir);
|
|
File.WriteAllText(_savePath, ToText(), FileEncoding);
|
|
Dirty = false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StarterNG.Infrastructure.Diagnostics.Log($"saving {_savePath}", ex);
|
|
}
|
|
}
|
|
}
|