Files
riojoy/src/RioJoy.Core/Profiles/IniFile.cs
T
CydandClaude Fable 5 1ff0b16015 Phase 8A (2/2): RioJoy.Core multi-targets net48 + net40 (Windows XP flavor)
- TargetFrameworks net48;net40. net48 keeps x64 + ViGEm + System.IO.Ports
  package; net40 adds Microsoft.Bcl.Async + System.ValueTuple and uses the
  in-box SerialPort.
- Compat/TaskCompat bridges Task.Run/Delay/WhenAny/WhenAll (TaskEx on
  net40) and SemaphoreSlim.WaitAsync (net40 blocks briefly - trivial at
  9600 baud).
- IReadOnlyList/IReadOnlyDictionary -> IList/IDictionary throughout
  (net40 predates the IReadOnly* interfaces and the Bcl backport cannot
  make arrays implement them).
- HashCode.Combine replaced with a manual combine (Bcl.HashCode has no
  net40 build); Marshal.SizeOf<T> -> typeof form; ViGEmJoystickSink
  gated #if !NET40. HidFeederJoystickSink stays on both flavors - it
  will drive RioGamepadXP.sys on XP via the same contract.

Both TFMs build; 275 tests green on net48.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 20:41:58 -05:00

83 lines
2.8 KiB
C#

namespace RioJoy.Core.Profiles;
/// <summary>
/// Minimal INI reader (sections, <c>key = value</c>, <c>;</c>/<c>#</c> comments)
/// sufficient to parse the legacy <c>RIO.ini</c>. Keys are case-insensitive.
/// </summary>
public sealed class IniFile
{
private readonly Dictionary<string, Dictionary<string, string>> _sections =
new(StringComparer.OrdinalIgnoreCase);
public static IniFile Parse(string text)
{
if (text is null) throw new ArgumentNullException(nameof(text));
var ini = new IniFile();
var current = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
ini._sections[string.Empty] = current;
foreach (string rawLine in text.Split('\n'))
{
string line = rawLine.Trim();
if (line.Length == 0 || line[0] is ';' or '#')
continue;
if (line[0] == '[' && line[^1] == ']')
{
string section = line[1..^1].Trim();
if (!ini._sections.TryGetValue(section, out current!))
ini._sections[section] = current = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
continue;
}
int eq = line.IndexOf('=');
if (eq < 0)
continue;
string key = line[..eq].Trim();
string value = line[(eq + 1)..].Trim();
if (key.Length > 0)
current[key] = value;
}
return ini;
}
/// <summary>All key/value pairs in a section (empty if the section is absent).</summary>
public IDictionary<string, string> Section(string name) =>
_sections.TryGetValue(name, out Dictionary<string, string>? s)
? s
: new Dictionary<string, string>();
public string? GetString(string section, string key) =>
Section(section).TryGetValue(key, out string? v) ? v : null;
public bool GetBool(string section, string key, bool fallback)
{
string? v = GetString(section, key);
if (v is null)
return fallback;
return v.Trim().ToLowerInvariant() switch
{
"1" or "true" or "yes" or "on" => true,
"0" or "false" or "no" or "off" => false,
_ => fallback,
};
}
/// <summary>Parse an integer value that may be hex (<c>0x…</c>) or decimal.</summary>
public static int ParseInt(string value)
{
if (value is null) throw new ArgumentNullException(nameof(value));
string v = value.Trim();
bool neg = v.StartsWith("-", StringComparison.Ordinal);
if (neg) v = v[1..];
int magnitude = v.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? Convert.ToInt32(v[2..], 16)
: int.Parse(v);
return neg ? -magnitude : magnitude;
}
}