Compare commits

..

No commits in common. "main" and "steam-connections" have entirely different histories.

13 changed files with 178 additions and 623 deletions

View File

@ -1,6 +1,6 @@
namespace SrcMod.Shell; namespace SrcMod.Shell;
public class Game : IEquatable<Game> public class Game
{ {
public static readonly Game Portal2 = new() public static readonly Game Portal2 = new()
{ {
@ -8,48 +8,12 @@ public class Game : IEquatable<Game>
NameId = "portal2", NameId = "portal2",
SteamId = 620 SteamId = 620
}; };
public static readonly Game Unknown = new()
{
Name = "Unknown Game",
NameId = "unknown",
SteamId = -1,
IsUnknown = true
};
public string Name { get; private set; } public required string Name { get; init; }
public string NameId { get; private set; } public required string NameId { get; init; }
public int SteamId { get; private set; } public required int SteamId { get; init; }
public bool IsUnknown { get; private set; } private Game() { }
private Game()
{
IsUnknown = false;
Name = string.Empty;
NameId = string.Empty;
}
public static Game FromSteamId(int id)
{
if (id == Portal2.SteamId) return Portal2;
else
{
Game game = (Game)Unknown.MemberwiseClone();
game.SteamId = id;
return game;
}
}
public override bool Equals(object? obj)
{
if (obj is Game game) return Equals(game);
return false;
}
public bool Equals(Game? other) => other is not null && SteamId == other.SteamId;
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => Name; public override string ToString() => Name;
public static bool operator ==(Game a, Game b) => a.Equals(b);
public static bool operator !=(Game a, Game b) => !a.Equals(b);
} }

View File

@ -9,7 +9,6 @@ global using SrcMod.Shell.Interop;
global using SrcMod.Shell.Modules; global using SrcMod.Shell.Modules;
global using SrcMod.Shell.Modules.ObjectModels; global using SrcMod.Shell.Modules.ObjectModels;
global using SrcMod.Shell.ObjectModels; global using SrcMod.Shell.ObjectModels;
global using SrcMod.Shell.ObjectModels.Source;
global using SrcMod.Shell.ObjectModels.Steam; global using SrcMod.Shell.ObjectModels.Steam;
global using System; global using System;
global using System.Collections; global using System.Collections;

View File

@ -2,183 +2,24 @@
public class Mod public class Mod
{ {
public Game BaseGame { get; set; }
public string? Developer { get; set; }
public string? DeveloperUrl { get; set; }
public Dictionary<SearchPathType, string> SearchPaths { get; set; }
public string? ManualUrl { get; set; }
public string? FgdDataPath { get; set; }
public string? IconPath { get; set; }
public string? InstancePath { get; set; }
public PlayerType PlayerMode { get; set; }
public CrosshairFlags CrosshairMenuFlags { get; set; }
public bool ShowDifficultyMenu { get; set; }
public bool ShowModelMenu { get; set; }
public bool ShowPortalMenu { get; set; }
public SupportFlags SupportingFlags { get; set; }
public bool HiResModels { get; set; }
public string[] HiddenMaps { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string? Motto { get; set; }
public TitleDisplay TitleDisplayMode { get; set; }
public bool BuildMapNodegraphs { get; set; }
public Dictionary<string, string>? MapbaseLaunchOptions { get; set; }
public string RootDirectory { get; set; }
private Mod() private Mod()
{ {
BaseGame = Game.Unknown;
SearchPaths = new();
HiddenMaps = Array.Empty<string>();
Name = string.Empty; Name = string.Empty;
RootDirectory = string.Empty;
}
public static Mod FromInfo(string root, GameInfo info)
{
Mod curMod = new()
{
BaseGame = Game.FromSteamId(info.FileSystem.SteamAppID),
BuildMapNodegraphs = info.Nodegraph is not null && info.Nodegraph.Value,
CrosshairMenuFlags = CrosshairFlags.None,
Developer = info.Developer,
DeveloperUrl = info.Developer_URL,
FgdDataPath = info.GameData,
HiddenMaps = info.Hidden_Maps is null ? Array.Empty<string>() : info.Hidden_Maps.Keys.ToArray(),
HiResModels = info.NoHIModel is null || !info.NoHIModel.Value,
IconPath = info.Icon is null ? null : info.Icon.Trim().Replace('/', '\\') + ".tga",
InstancePath = info.InstancePath,
MapbaseLaunchOptions = info.CommandLine,
ManualUrl = info.Manual,
Motto = info.Title2,
Name = string.IsNullOrEmpty(info.Title) ? "Default Mod" : info.Title,
PlayerMode = info.Type is null ? PlayerType.Both : info.Type.Trim().ToLower() switch
{
"singleplayer_only" => PlayerType.Singleplayer,
"multiplayer_only" => PlayerType.Multiplayer,
_ => throw new ArgumentException($"Unknown type \"{info.Type}\"")
},
RootDirectory = root,
SearchPaths = new(),
ShowDifficultyMenu = info.NoDifficulty is null || !info.NoDifficulty.Value,
ShowModelMenu = info.NoModels is null || !info.NoModels.Value,
ShowPortalMenu = info.HasPortals is not null && info.HasPortals.Value,
SupportingFlags = SupportFlags.None,
TitleDisplayMode = info.GameLogo is null ? TitleDisplay.Title :
(info.GameLogo.Value ? TitleDisplay.Logo : TitleDisplay.Title)
};
if (curMod.PlayerMode == PlayerType.Multiplayer && info.NoDifficulty is null)
curMod.ShowDifficultyMenu = false;
if (info.NoCrosshair is null || !info.NoCrosshair.Value)
curMod.CrosshairMenuFlags |= CrosshairFlags.ShowMultiplayer;
if (info.AdvCrosshair is not null && info.AdvCrosshair.Value)
curMod.CrosshairMenuFlags |= CrosshairFlags.AdvancedMenu;
if (info.SupportsDX8 is not null && info.SupportsDX8.Value)
curMod.SupportingFlags |= SupportFlags.DirectX8;
if (info.SupportsVR is not null && info.SupportsVR.Value)
curMod.SupportingFlags |= SupportFlags.VirtualReality;
if (info.SupportsXBox360 is not null && info.SupportsXBox360.Value)
curMod.SupportingFlags |= SupportFlags.XBox360;
foreach (KeyValuePair<string, string> pair in info.FileSystem.SearchPaths)
{
SearchPathType type = SearchPathType.Unknown;
string[] parts = pair.Key.Trim().ToLower().Split('+');
foreach (string part in parts) type |= part switch
{
"game" => SearchPathType.Game,
"game_write" => SearchPathType.GameWrite,
"gamebin" => SearchPathType.GameBinaries,
"platform" => SearchPathType.Platform,
"mod" => SearchPathType.Mod,
"mod_write" => SearchPathType.ModWrite,
"default_write_path" => SearchPathType.DefaultWritePath,
"vpk" => SearchPathType.Vpk,
_ => SearchPathType.Unknown
};
}
return curMod;
} }
public static Mod? ReadDirectory(string dir) public static Mod? ReadDirectory(string dir)
{ {
dir = dir.Trim().Replace('/', '\\'); if (!File.Exists(dir + "\\GameInfo.txt")) return null;
string check = dir;
while (!string.IsNullOrEmpty(check)) Mod mod = new()
{ {
string gameInfoPath = Path.Combine(check, "GameInfo.txt"); Name = dir.Split("\\").Last()
if (File.Exists(gameInfoPath)) };
{
// Root mod directory found, go from here.
FileStream fs = new(gameInfoPath, FileMode.Open); return mod;
GameInfo? modInfo = SerializeVkv.Deserialize<GameInfo>(fs);
if (modInfo is null) continue;
return FromInfo(check, modInfo);
}
check = Path.GetDirectoryName(check) ?? string.Empty; // Go to parent folder.
}
return null;
} }
public override string ToString() => Name; public override string ToString() => Name;
[Flags]
public enum CrosshairFlags
{
None = 0,
ShowMultiplayer = 1,
AdvancedMenu = 2
}
[Flags]
public enum SupportFlags
{
None,
DirectX8 = 1,
VirtualReality = 2,
XBox360 = 4
}
[Flags]
public enum SearchPathType
{
Unknown = 0,
Game = 1,
GameWrite = 2,
GameBinaries = 4,
Platform = 8,
Mod = 16,
ModWrite = 32,
DefaultWritePath = 64,
Vpk = 128
}
public enum PlayerType
{
Singleplayer = 1,
Multiplayer = 2,
Both = Singleplayer | Multiplayer
}
public enum TitleDisplay
{
Title,
Logo
}
} }

View File

@ -2,73 +2,38 @@
public static class TypeParsers public static class TypeParsers
{ {
public static bool CanParse(object? obj) => obj is not null && obj is bool or sbyte or byte or short or ushort public static bool CanParse(object? obj) => obj is not null && obj is sbyte or byte or short or ushort or int
or int or uint or long or ulong or Int128 or UInt128 or nint or nuint or Half or float or double or decimal or uint or long or ulong or Int128 or UInt128 or nint or nuint or Half or float or double or decimal
or char or DateOnly or DateTime or DateTimeOffset or Guid or TimeOnly or TimeSpan; or char or DateOnly or DateTime or DateTimeOffset or Guid or TimeOnly or TimeSpan;
public static object ParseAll(string msg) public static object ParseAll(string msg)
{ {
if (TryParseBool(msg, out bool resBool)) return resBool; if (TryParse(msg, out sbyte int8)) return int8;
else if (TryParse(msg, out sbyte int8)) return int8; if (TryParse(msg, out byte uInt8)) return uInt8;
else if (TryParse(msg, out byte uInt8)) return uInt8; if (TryParse(msg, out short int16)) return int16;
else if (TryParse(msg, out short int16)) return int16; if (TryParse(msg, out ushort uInt16)) return uInt16;
else if (TryParse(msg, out ushort uInt16)) return uInt16; if (TryParse(msg, out int int32)) return int32;
else if (TryParse(msg, out int int32)) return int32; if (TryParse(msg, out uint uInt32)) return uInt32;
else if (TryParse(msg, out uint uInt32)) return uInt32; if (TryParse(msg, out long int64)) return int64;
else if (TryParse(msg, out long int64)) return int64; if (TryParse(msg, out ulong uInt64)) return uInt64;
else if (TryParse(msg, out ulong uInt64)) return uInt64; if (TryParse(msg, out Int128 int128)) return int128;
else if (TryParse(msg, out Int128 int128)) return int128; if (TryParse(msg, out UInt128 uInt128)) return uInt128;
else if (TryParse(msg, out UInt128 uInt128)) return uInt128; if (TryParse(msg, out nint intPtr)) return intPtr;
else if (TryParse(msg, out nint intPtr)) return intPtr; if (TryParse(msg, out nuint uIntPtr)) return uIntPtr;
else if (TryParse(msg, out nuint uIntPtr)) return uIntPtr; if (TryParse(msg, out Half float16)) return float16;
else if (TryParse(msg, out Half float16)) return float16; if (TryParse(msg, out float float32)) return float32;
else if (TryParse(msg, out float float32)) return float32; if (TryParse(msg, out double float64)) return float64;
else if (TryParse(msg, out double float64)) return float64; if (TryParse(msg, out decimal float128)) return float128;
else if (TryParse(msg, out decimal float128)) return float128; if (TryParse(msg, out char resChar)) return resChar;
else if (TryParse(msg, out char resChar)) return resChar; if (TryParse(msg, out DateOnly dateOnly)) return dateOnly;
else if (TryParse(msg, out DateOnly dateOnly)) return dateOnly; if (TryParse(msg, out DateTime dateTime)) return dateTime;
else if (TryParse(msg, out DateTime dateTime)) return dateTime; if (TryParse(msg, out DateTimeOffset dateTimeOffset)) return dateTimeOffset;
else if (TryParse(msg, out DateTimeOffset dateTimeOffset)) return dateTimeOffset; if (TryParse(msg, out Guid guid)) return guid;
else if (TryParse(msg, out Guid guid)) return guid; if (TryParse(msg, out TimeOnly timeOnly)) return timeOnly;
else if (TryParse(msg, out TimeOnly timeOnly)) return timeOnly; if (TryParse(msg, out TimeSpan timeSpan)) return timeSpan;
else if (TryParse(msg, out TimeSpan timeSpan)) return timeSpan;
else return msg; return msg;
} }
public static bool TryParseBool(string msg, out bool result)
{
string trimmed = msg.Trim().ToLower();
string[] trues = new string[]
{
"t",
"true",
"1",
"y",
"yes"
},
falses = new string[]
{
"f",
"false",
"0",
"n",
"no"
};
foreach (string t in trues) if (trimmed == t)
{
result = true;
return true;
}
foreach (string f in falses) if (trimmed == f)
{
result = false;
return true;
}
result = false;
return false;
}
public static bool TryParse<T>(string msg, out T? result) where T : IParsable<T> public static bool TryParse<T>(string msg, out T? result) where T : IParsable<T>
=> T.TryParse(msg, null, out result); => T.TryParse(msg, null, out result);
} }

View File

@ -4,9 +4,6 @@ public class Config
{ {
public const string FilePath = "config.json"; public const string FilePath = "config.json";
public static bool HasDisplayableError => false;
public static bool HasDisplayableWarning => p_printedLastSteamWarning;
public static Config Defaults => new(); public static Config Defaults => new();
private static readonly FieldInfo[] p_configSharedFields; private static readonly FieldInfo[] p_configSharedFields;
@ -25,10 +22,8 @@ public class Config
private static Config p_applied; private static Config p_applied;
private static Changes? p_changes; private static Changes? p_changes;
private static bool p_printedLastSteamWarning;
// These variables should only exist in the Config class so they aren't marked as shared. // These variables should only exist in the Config class so they aren't marked as shared.
private readonly string p_steamLocation; private string p_steamLocation;
static Config() static Config()
{ {
@ -71,7 +66,6 @@ public class Config
public string[] GameDirectories; public string[] GameDirectories;
public AskMode RunUnsafeCommands; public AskMode RunUnsafeCommands;
public bool UseLocalModDirectories;
internal Config() internal Config()
{ {
@ -98,40 +92,26 @@ public class Config
string gameDirDataPath = Path.Combine(p_steamLocation, @"steamapps\libraryfolders.vdf"); string gameDirDataPath = Path.Combine(p_steamLocation, @"steamapps\libraryfolders.vdf");
FileStream gameDirData = new(gameDirDataPath, FileMode.Open); VkvSerializer serializer = new(new()
try
{ {
LibraryFolder[]? folders = SerializeVkv.Deserialize<LibraryFolder[]>(gameDirData); useEscapeCodes = true,
useQuotes = true
});
FileStream gameDirData = new(gameDirDataPath, FileMode.Open);
LibraryFolder[]? folders = serializer.Deserialize<LibraryFolder[]>(gameDirData);
if (folders is null) if (folders is null)
{ {
if (!p_printedLastSteamWarning)
Write("[WARNING] Error parsing Steam game directories.", ConsoleColor.DarkYellow); Write("[WARNING] Error parsing Steam game directories.", ConsoleColor.DarkYellow);
GameDirectories = Array.Empty<string>(); GameDirectories = Array.Empty<string>();
p_printedLastSteamWarning = true;
} }
else else
{ {
GameDirectories = new string[folders.Length]; GameDirectories = new string[folders.Length];
for (int i = 0; i < folders.Length; i++) GameDirectories[i] = folders[i].path; for (int i = 0; i < folders.Length; i++) GameDirectories[i] = folders[i].path;
p_printedLastSteamWarning = false;
}
}
catch (Exception ex)
{
if (!p_printedLastSteamWarning)
{
#if RELEASE
Write("[WARNING] Error parsing Steam game directories.", ConsoleColor.DarkYellow);
#else
Write(ex, ConsoleColor.DarkYellow);
#endif
}
GameDirectories = Array.Empty<string>();
p_printedLastSteamWarning = true;
} }
RunUnsafeCommands = AskMode.Ask; RunUnsafeCommands = AskMode.Ask;
UseLocalModDirectories = true;
} }
public Config ApplyChanges(Changes changes) public Config ApplyChanges(Changes changes)
@ -197,7 +177,7 @@ public class Config
} }
StreamReader reader = new(fullPath); StreamReader reader = new(fullPath);
JsonTextReader jsonReader = new(reader); JsonTextReader jsonReader = new(reader);
p_changes = Tools.SerializerJson.Deserialize<Changes?>(jsonReader); p_changes = Serializer.Deserialize<Changes?>(jsonReader);
jsonReader.Close(); jsonReader.Close();
reader.Close(); reader.Close();
@ -218,7 +198,7 @@ public class Config
{ {
Indentation = 4 Indentation = 4
}; };
Tools.SerializerJson.Serialize(jsonWriter, p_changes); Serializer.Serialize(jsonWriter, p_changes);
jsonWriter.Close(); jsonWriter.Close();
writer.Close(); writer.Close();
} }
@ -232,7 +212,6 @@ public class Config
{ {
public string[]? GameDirectories; public string[]? GameDirectories;
public AskMode? RunUnsafeCommands; public AskMode? RunUnsafeCommands;
public bool? UseLocalModDirectories;
public bool Any() => typeof(Changes).GetFields().Any(x => x.GetValue(this) is not null); public bool Any() => typeof(Changes).GetFields().Any(x => x.GetValue(this) is not null);
} }

View File

@ -1,59 +0,0 @@
namespace SrcMod.Shell.ObjectModels.Source;
// Referencing https://developer.valvesoftware.com/wiki/Gameinfo.txt.
public class GameInfo
{
// Name
public string Game;
public string Title;
public string? Title2;
public bool? GameLogo;
// Options
public string? Type;
public bool? NoDifficulty;
public bool? HasPortals;
public bool? NoCrosshair;
public bool? AdvCrosshair;
public bool? NoModels;
public bool? NoHIModel;
public Dictionary<string, int>? Hidden_Maps;
public Dictionary<string, string>? CommandLine;
// Steam games list
public string? Developer;
public string? Developer_URL;
public string? Manual;
public string? Icon;
// Engine and tools
public bool? Nodegraph;
public string? GameData;
public string? InstancePath;
public bool? SupportsDX8;
public bool? SupportsVR;
public bool? SupportsXBox360;
public FileSystemData FileSystem;
public GameInfo()
{
Game = string.Empty;
Title = string.Empty;
FileSystem = new();
}
public class FileSystemData
{
public int SteamAppID;
public int? AdditionalContentId;
public int? ToolsAppId;
public Dictionary<string, string> SearchPaths;
public FileSystemData()
{
SearchPaths = new();
}
}
}

View File

@ -6,12 +6,6 @@ public class Shell
public const string Name = "SrcMod"; public const string Name = "SrcMod";
public const string Version = "Beta 0.5.0"; public const string Version = "Beta 0.5.0";
public bool HasAnyDisplayableError => HasDisplayableError || Config.HasDisplayableError;
public bool HasAnyDisplayableWarning => HasDisplayableWarning || Config.HasDisplayableWarning;
public bool HasDisplayableError => p_printedLastReloadError;
public bool HasDisplayableWarning => false;
public readonly string? ShellDirectory; public readonly string? ShellDirectory;
public List<CommandInfo> LoadedCommands; public List<CommandInfo> LoadedCommands;
@ -25,8 +19,6 @@ public class Shell
private bool p_lastCancel; private bool p_lastCancel;
private bool p_printedCancel; private bool p_printedCancel;
private bool p_printedLastReloadError;
private BackgroundWorker? p_activeCommand; private BackgroundWorker? p_activeCommand;
public Shell() public Shell()
@ -147,21 +139,9 @@ public class Shell
public string ReadLine() public string ReadLine()
{ {
Write("\n", newLine: false); Write($"\n{WorkingDirectory}", ConsoleColor.DarkGreen, false);
if (HasAnyDisplayableError) Write($"(Error) ", ConsoleColor.DarkRed, false); if (ActiveGame is not null) Write($" {ActiveGame}", ConsoleColor.DarkYellow, false);
else if (HasAnyDisplayableWarning) Write($"(Warning) ", ConsoleColor.DarkYellow, false); if (ActiveMod is not null) Write($" {ActiveMod}", ConsoleColor.Magenta, false);
if (ActiveMod is not null) Write($"{ActiveMod} ", ConsoleColor.Magenta, false);
if (ActiveMod is not null && Config.LoadedConfig.UseLocalModDirectories)
{
string directory = Path.GetRelativePath(ActiveMod.RootDirectory, WorkingDirectory);
if (directory == ".") directory = string.Empty;
Write($"~\\{directory}", ConsoleColor.DarkGreen, false);
}
else Write($"{WorkingDirectory}", ConsoleColor.DarkGreen, false);
if (ActiveGame is not null) Write($" ({ActiveGame})", ConsoleColor.Blue, false);
Write(null); Write(null);
Write($" {Name}", ConsoleColor.DarkCyan, false); Write($" {Name}", ConsoleColor.DarkCyan, false);
@ -319,34 +299,13 @@ public class Shell
} }
public void ReloadDirectoryInfo() public void ReloadDirectoryInfo()
{
try
{ {
ActiveMod = Mod.ReadDirectory(WorkingDirectory); ActiveMod = Mod.ReadDirectory(WorkingDirectory);
ActiveGame = ActiveMod?.BaseGame;
// Update title. // Update title.
string title = "SrcMod"; string title = "SrcMod";
if (ActiveMod is not null) title += $" - {ActiveMod.Name}"; if (ActiveMod is not null) title += $" - {ActiveMod.Name}";
Console.Title = title; Console.Title = title;
p_printedLastReloadError = false;
}
catch (Exception ex)
{
if (!p_printedLastReloadError)
{
#if RELEASE
Write("[ERROR] Error reloading directory information. Some data may not update.",
ConsoleColor.Red);
#else
Write(ex, ConsoleColor.Red);
#endif
}
p_printedLastReloadError = true;
Console.Title = "SrcMod (Error)";
}
} }
private void HandleCancel(object? sender, ConsoleCancelEventArgs args) private void HandleCancel(object? sender, ConsoleCancelEventArgs args)

View File

@ -2,27 +2,15 @@
public static class Tools public static class Tools
{ {
public static JsonSerializer SerializerJson { get; private set; } public static JsonSerializer Serializer { get; private set; }
public static VkvSerializer SerializeVkv { get; private set; }
static Tools() static Tools()
{ {
SerializerJson = JsonSerializer.Create(new() Serializer = JsonSerializer.Create(new()
{ {
Formatting = Formatting.Indented, Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore NullValueHandling = NullValueHandling.Ignore
}); });
SerializeVkv = new VkvSerializer(new()
{
closeWhenFinished = true,
indentSize = 4,
resetStreamPosition = false,
serializeProperties = true,
spacing = SpacingMode.DoubleTab,
useEscapeCodes = true,
useQuotes = true
});
} }
public static void DisplayWithPages(IEnumerable<string> lines, ConsoleColor? color = null) public static void DisplayWithPages(IEnumerable<string> lines, ConsoleColor? color = null)

View File

@ -2,73 +2,45 @@
public static class TypeParsers public static class TypeParsers
{ {
public static bool CanParse(object? obj) => obj is not null && obj is bool or sbyte or byte or short or ushort public static bool CanParse(object? obj) => obj is not null && obj is sbyte or byte or short or ushort or int
or int or uint or long or ulong or Int128 or UInt128 or nint or nuint or Half or float or double or decimal or uint or long or ulong or Int128 or UInt128 or nint or nuint or Half or float or double or decimal
or char or DateOnly or DateTime or DateTimeOffset or Guid or TimeOnly or TimeSpan; or char or DateOnly or DateTime or DateTimeOffset or Guid or TimeOnly or TimeSpan;
public static bool CanParse(Type type) => type == typeof(sbyte) || type == typeof(byte) || type == typeof(short)
|| type == typeof(ushort) || type == typeof(int) || type == typeof(uint) || type == typeof(long)
|| type == typeof(ulong) || type == typeof(Int128) || type == typeof(UInt128) || type == typeof(nint)
|| type == typeof(nuint) || type == typeof(Half) || type == typeof(float) || type == typeof(double)
|| type == typeof(decimal) || type == typeof(char) || type == typeof(DateOnly) || type == typeof(DateTime)
|| type == typeof(DateTimeOffset) || type == typeof(Guid) || type == typeof(TimeOnly)
|| type == typeof(TimeSpan);
public static object ParseAll(string msg) public static object ParseAll(string msg)
{ {
if (TryParseBool(msg, out bool resBool)) return resBool; if (TryParse(msg, out sbyte int8)) return int8;
else if (TryParse(msg, out sbyte int8)) return int8; if (TryParse(msg, out byte uInt8)) return uInt8;
else if (TryParse(msg, out byte uInt8)) return uInt8; if (TryParse(msg, out short int16)) return int16;
else if (TryParse(msg, out short int16)) return int16; if (TryParse(msg, out ushort uInt16)) return uInt16;
else if (TryParse(msg, out ushort uInt16)) return uInt16; if (TryParse(msg, out int int32)) return int32;
else if (TryParse(msg, out int int32)) return int32; if (TryParse(msg, out uint uInt32)) return uInt32;
else if (TryParse(msg, out uint uInt32)) return uInt32; if (TryParse(msg, out long int64)) return int64;
else if (TryParse(msg, out long int64)) return int64; if (TryParse(msg, out ulong uInt64)) return uInt64;
else if (TryParse(msg, out ulong uInt64)) return uInt64; if (TryParse(msg, out Int128 int128)) return int128;
else if (TryParse(msg, out Int128 int128)) return int128; if (TryParse(msg, out UInt128 uInt128)) return uInt128;
else if (TryParse(msg, out UInt128 uInt128)) return uInt128; if (TryParse(msg, out nint intPtr)) return intPtr;
else if (TryParse(msg, out nint intPtr)) return intPtr; if (TryParse(msg, out nuint uIntPtr)) return uIntPtr;
else if (TryParse(msg, out nuint uIntPtr)) return uIntPtr; if (TryParse(msg, out Half float16)) return float16;
else if (TryParse(msg, out Half float16)) return float16; if (TryParse(msg, out float float32)) return float32;
else if (TryParse(msg, out float float32)) return float32; if (TryParse(msg, out double float64)) return float64;
else if (TryParse(msg, out double float64)) return float64; if (TryParse(msg, out decimal float128)) return float128;
else if (TryParse(msg, out decimal float128)) return float128; if (TryParse(msg, out char resChar)) return resChar;
else if (TryParse(msg, out char resChar)) return resChar; if (TryParse(msg, out DateOnly dateOnly)) return dateOnly;
else if (TryParse(msg, out DateOnly dateOnly)) return dateOnly; if (TryParse(msg, out DateTime dateTime)) return dateTime;
else if (TryParse(msg, out DateTime dateTime)) return dateTime; if (TryParse(msg, out DateTimeOffset dateTimeOffset)) return dateTimeOffset;
else if (TryParse(msg, out DateTimeOffset dateTimeOffset)) return dateTimeOffset; if (TryParse(msg, out Guid guid)) return guid;
else if (TryParse(msg, out Guid guid)) return guid; if (TryParse(msg, out TimeOnly timeOnly)) return timeOnly;
else if (TryParse(msg, out TimeOnly timeOnly)) return timeOnly; if (TryParse(msg, out TimeSpan timeSpan)) return timeSpan;
else if (TryParse(msg, out TimeSpan timeSpan)) return timeSpan;
else return msg; return msg;
} }
public static bool TryParseBool(string msg, out bool result)
{
string trimmed = msg.Trim().ToLower();
string[] trues = new string[]
{
"t",
"true",
"1",
"y",
"yes"
},
falses = new string[]
{
"f",
"false",
"0",
"n",
"no"
};
foreach (string t in trues) if (trimmed == t)
{
result = true;
return true;
}
foreach (string f in falses) if (trimmed == f)
{
result = false;
return true;
}
result = false;
return false;
}
public static bool TryParse<T>(string msg, out T? result) where T : IParsable<T> public static bool TryParse<T>(string msg, out T? result) where T : IParsable<T>
=> T.TryParse(msg, null, out result); => T.TryParse(msg, null, out result);
} }

View File

@ -1,9 +0,0 @@
namespace Valve.Vkv.ObjectModels;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public class VkvKeyNameAttribute : Attribute
{
public readonly string name;
public VkvKeyNameAttribute(string name) => this.name = name;
}

View File

@ -1,4 +1,6 @@
namespace Valve.Vkv; using Valve.Vkv.ObjectModels;
namespace Valve.Vkv;
public static class VkvConvert public static class VkvConvert
{ {
@ -19,19 +21,9 @@ public static class VkvConvert
#region DeserializeNode #region DeserializeNode
public static VkvNode? DeserializeNode(StreamReader reader) => public static VkvNode? DeserializeNode(StreamReader reader) =>
DeserializeNode(reader, VkvOptions.Default); DeserializeNode(reader, VkvOptions.Default, out _, null);
public static VkvNode? DeserializeNode(StreamReader reader, VkvOptions options) public static VkvNode? DeserializeNode(StreamReader reader, VkvOptions options) =>
{ DeserializeNode(reader, options, out _, null);
try
{
return DeserializeNode(reader, options, out _, null);
}
catch
{
if (!options.noExceptions) throw;
return null;
}
}
private static VkvNode? DeserializeNode(StreamReader reader, VkvOptions options, out string name, private static VkvNode? DeserializeNode(StreamReader reader, VkvOptions options, out string name,
string? first) string? first)
@ -65,6 +57,7 @@ public static class VkvConvert
{ {
if (current == "}") break; if (current == "}") break;
VkvNode? output = DeserializeNode(reader, options, out string subName, current); VkvNode? output = DeserializeNode(reader, options, out string subName, current);
if (output is null) throw new VkvSerializationException("Error deserializing sub-node.");
tree[subName] = output; tree[subName] = output;
} }
if (current is null) throw new VkvSerializationException("Reached end-of-file while deserializing group."); if (current is null) throw new VkvSerializationException("Reached end-of-file while deserializing group.");
@ -121,8 +114,6 @@ public static class VkvConvert
#region FromNodeTree #region FromNodeTree
public static T? FromNodeTree<T>(VkvNode? node, VkvOptions options) => (T?)FromNodeTree(typeof(T), node, options); public static T? FromNodeTree<T>(VkvNode? node, VkvOptions options) => (T?)FromNodeTree(typeof(T), node, options);
public static object? FromNodeTree(Type outputType, VkvNode? node, VkvOptions options) public static object? FromNodeTree(Type outputType, VkvNode? node, VkvOptions options)
{
try
{ {
if (node is null) return null; if (node is null) return null;
@ -130,12 +121,6 @@ public static class VkvConvert
else if (node is VkvTreeNode tree) return FromTreeNode(outputType, tree, options); else if (node is VkvTreeNode tree) return FromTreeNode(outputType, tree, options);
else throw new VkvSerializationException("Unknown VKV node type."); else throw new VkvSerializationException("Unknown VKV node type.");
} }
catch
{
if (!options.noExceptions) throw;
return null;
}
}
private static object? FromSingleNode(Type outputType, VkvSingleNode node) private static object? FromSingleNode(Type outputType, VkvSingleNode node)
{ {
@ -192,9 +177,9 @@ public static class VkvConvert
foreach (FieldInfo field in validFields) foreach (FieldInfo field in validFields)
{ {
VkvKeyNameAttribute? namingAttribute = field.GetCustomAttribute<VkvKeyNameAttribute>(); string name = field.Name;
VkvNode? subNode = node[namingAttribute?.name ?? field.Name]; VkvNode? subNode = node[name];
if (subNode is null) continue; if (subNode is null) continue;
object? result = FromNodeTree(field.FieldType, subNode, options); object? result = FromNodeTree(field.FieldType, subNode, options);
@ -203,9 +188,9 @@ public static class VkvConvert
} }
foreach (PropertyInfo prop in validProperties) foreach (PropertyInfo prop in validProperties)
{ {
VkvKeyNameAttribute? namingAttribute = prop.GetCustomAttribute<VkvKeyNameAttribute>(); string name = prop.Name;
VkvNode? subNode = node[namingAttribute?.name ?? prop.Name]; VkvNode? subNode = node[name];
if (subNode is null) continue; if (subNode is null) continue;
object? result = FromNodeTree(prop.PropertyType, subNode, options); object? result = FromNodeTree(prop.PropertyType, subNode, options);
@ -283,19 +268,9 @@ public static class VkvConvert
#region SerializeNode #region SerializeNode
public static void SerializeNode(StreamWriter writer, VkvNode? node, string name, public static void SerializeNode(StreamWriter writer, VkvNode? node, string name,
VkvOptions options) VkvOptions options) => SerializeNode(writer, node, name, options, 0);
{
try
{
SerializeNode(writer, node, name, options, 0);
}
catch
{
if (!options.noExceptions) throw;
}
}
public static void SerializeNode(StreamWriter writer, VkvNode? node, string name) => public static void SerializeNode(StreamWriter writer, VkvNode? node, string name) =>
SerializeNode(writer, node, name, VkvOptions.Default); SerializeNode(writer, node, name, VkvOptions.Default, 0);
private static void SerializeNode(StreamWriter writer, VkvNode? node, string name, private static void SerializeNode(StreamWriter writer, VkvNode? node, string name,
VkvOptions options, int indentLevel) VkvOptions options, int indentLevel)
@ -368,8 +343,6 @@ public static class VkvConvert
#region ToNodeTree #region ToNodeTree
public static VkvNode? ToNodeTree(object? obj) => ToNodeTree(obj, VkvOptions.Default); public static VkvNode? ToNodeTree(object? obj) => ToNodeTree(obj, VkvOptions.Default);
public static VkvNode? ToNodeTree(object? obj, VkvOptions options) public static VkvNode? ToNodeTree(object? obj, VkvOptions options)
{
try
{ {
if (obj is null) return null; if (obj is null) return null;
Type type = obj.GetType(); Type type = obj.GetType();
@ -428,22 +401,14 @@ public static class VkvConvert
foreach (FieldInfo field in validFields) foreach (FieldInfo field in validFields)
{ {
VkvKeyNameAttribute? namingAttribute = field.GetCustomAttribute<VkvKeyNameAttribute>(); tree[field.Name] = ToNodeTree(field.GetValue(obj), options);
tree[namingAttribute?.name ?? field.Name] = ToNodeTree(field.GetValue(obj), options);
} }
foreach (PropertyInfo prop in validProperties) foreach (PropertyInfo prop in validProperties)
{ {
VkvKeyNameAttribute? namingAttribute = prop.GetCustomAttribute<VkvKeyNameAttribute>(); tree[prop.Name] = ToNodeTree(prop.GetValue(obj), options);
tree[namingAttribute?.name ?? prop.Name] = ToNodeTree(prop.GetValue(obj), options);
} }
return tree; return tree;
} }
catch
{
if (!options.noExceptions) throw;
return null;
}
}
#endregion #endregion
} }

View File

@ -6,7 +6,6 @@ public record class VkvOptions
public bool closeWhenFinished; public bool closeWhenFinished;
public int indentSize; public int indentSize;
public bool noExceptions;
public bool resetStreamPosition; public bool resetStreamPosition;
public bool serializeProperties; public bool serializeProperties;
public SpacingMode spacing; public SpacingMode spacing;
@ -17,7 +16,6 @@ public record class VkvOptions
{ {
closeWhenFinished = true; closeWhenFinished = true;
indentSize = 4; indentSize = 4;
noExceptions = false;
resetStreamPosition = false; resetStreamPosition = false;
serializeProperties = true; serializeProperties = true;
spacing = SpacingMode.DoubleTab; spacing = SpacingMode.DoubleTab;

View File

@ -16,19 +16,12 @@ public class VkvSerializer
{ {
long pos = stream.Position; long pos = stream.Position;
StreamReader reader = new(stream, leaveOpen: !p_options.closeWhenFinished); StreamReader reader = new(stream, leaveOpen: !p_options.closeWhenFinished);
try
{
VkvNode? result = VkvConvert.DeserializeNode(reader, p_options); VkvNode? result = VkvConvert.DeserializeNode(reader, p_options);
reader.Close(); reader.Close();
if (!p_options.closeWhenFinished && p_options.resetStreamPosition) stream.Seek(pos, SeekOrigin.Begin); if (!p_options.closeWhenFinished && p_options.resetStreamPosition) stream.Seek(pos, SeekOrigin.Begin);
return result; return result;
} }
finally
{
reader.Close();
if (!p_options.closeWhenFinished && p_options.resetStreamPosition) stream.Seek(pos, SeekOrigin.Begin);
}
}
public T? Deserialize<T>(Stream stream) public T? Deserialize<T>(Stream stream)
{ {
VkvNode? result = Deserialize(stream); VkvNode? result = Deserialize(stream);