- 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>
83 lines
2.8 KiB
C#
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;
|
|
}
|
|
}
|