Compare commits
14 Commits
steam-conn
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f479f63662 | |||
| 8804ac08d9 | |||
| 287cd26bcb | |||
| 3a961fffbb | |||
| 7cad137f39 | |||
| 82afefd3e7 | |||
| 858bd580b6 | |||
| dfda870eac | |||
| f25b366a4d | |||
| 5b2b0abfa3 | |||
| 2abadc4071 | |||
| be6e4a496b | |||
| 62d3ba6492 | |||
| 7854a576a7 |
@ -1,6 +1,6 @@
|
||||
namespace SrcMod.Shell;
|
||||
|
||||
public class Game
|
||||
public class Game : IEquatable<Game>
|
||||
{
|
||||
public static readonly Game Portal2 = new()
|
||||
{
|
||||
@ -8,12 +8,48 @@ public class Game
|
||||
NameId = "portal2",
|
||||
SteamId = 620
|
||||
};
|
||||
public static readonly Game Unknown = new()
|
||||
{
|
||||
Name = "Unknown Game",
|
||||
NameId = "unknown",
|
||||
SteamId = -1,
|
||||
IsUnknown = true
|
||||
};
|
||||
|
||||
public required string Name { get; init; }
|
||||
public required string NameId { get; init; }
|
||||
public required int SteamId { get; init; }
|
||||
public string Name { get; private set; }
|
||||
public string NameId { get; private set; }
|
||||
public int SteamId { get; private set; }
|
||||
|
||||
private Game() { }
|
||||
public bool IsUnknown { get; private set; }
|
||||
|
||||
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 static bool operator ==(Game a, Game b) => a.Equals(b);
|
||||
public static bool operator !=(Game a, Game b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ global using SrcMod.Shell.Interop;
|
||||
global using SrcMod.Shell.Modules;
|
||||
global using SrcMod.Shell.Modules.ObjectModels;
|
||||
global using SrcMod.Shell.ObjectModels;
|
||||
global using SrcMod.Shell.ObjectModels.Source;
|
||||
global using SrcMod.Shell.ObjectModels.Steam;
|
||||
global using System;
|
||||
global using System.Collections;
|
||||
|
||||
@ -2,24 +2,183 @@
|
||||
|
||||
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? 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()
|
||||
{
|
||||
BaseGame = Game.Unknown;
|
||||
SearchPaths = new();
|
||||
HiddenMaps = Array.Empty<string>();
|
||||
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)
|
||||
{
|
||||
if (!File.Exists(dir + "\\GameInfo.txt")) return null;
|
||||
dir = dir.Trim().Replace('/', '\\');
|
||||
string check = dir;
|
||||
|
||||
Mod mod = new()
|
||||
while (!string.IsNullOrEmpty(check))
|
||||
{
|
||||
Name = dir.Split("\\").Last()
|
||||
};
|
||||
string gameInfoPath = Path.Combine(check, "GameInfo.txt");
|
||||
if (File.Exists(gameInfoPath))
|
||||
{
|
||||
// Root mod directory found, go from here.
|
||||
|
||||
return mod;
|
||||
FileStream fs = new(gameInfoPath, FileMode.Open);
|
||||
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;
|
||||
|
||||
[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
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,38 +2,73 @@
|
||||
|
||||
public static class TypeParsers
|
||||
{
|
||||
public static bool CanParse(object? obj) => obj is not null && obj is sbyte or byte or short or ushort or int
|
||||
or uint or long or ulong or Int128 or UInt128 or nint or nuint or Half or float or double or decimal
|
||||
public static bool CanParse(object? obj) => obj is not null && obj is bool or sbyte or byte or short or ushort
|
||||
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 char or DateOnly or DateTime or DateTimeOffset or Guid or TimeOnly or TimeSpan;
|
||||
public static object ParseAll(string msg)
|
||||
{
|
||||
if (TryParse(msg, out sbyte int8)) return int8;
|
||||
if (TryParse(msg, out byte uInt8)) return uInt8;
|
||||
if (TryParse(msg, out short int16)) return int16;
|
||||
if (TryParse(msg, out ushort uInt16)) return uInt16;
|
||||
if (TryParse(msg, out int int32)) return int32;
|
||||
if (TryParse(msg, out uint uInt32)) return uInt32;
|
||||
if (TryParse(msg, out long int64)) return int64;
|
||||
if (TryParse(msg, out ulong uInt64)) return uInt64;
|
||||
if (TryParse(msg, out Int128 int128)) return int128;
|
||||
if (TryParse(msg, out UInt128 uInt128)) return uInt128;
|
||||
if (TryParse(msg, out nint intPtr)) return intPtr;
|
||||
if (TryParse(msg, out nuint uIntPtr)) return uIntPtr;
|
||||
if (TryParse(msg, out Half float16)) return float16;
|
||||
if (TryParse(msg, out float float32)) return float32;
|
||||
if (TryParse(msg, out double float64)) return float64;
|
||||
if (TryParse(msg, out decimal float128)) return float128;
|
||||
if (TryParse(msg, out char resChar)) return resChar;
|
||||
if (TryParse(msg, out DateOnly dateOnly)) return dateOnly;
|
||||
if (TryParse(msg, out DateTime dateTime)) return dateTime;
|
||||
if (TryParse(msg, out DateTimeOffset dateTimeOffset)) return dateTimeOffset;
|
||||
if (TryParse(msg, out Guid guid)) return guid;
|
||||
if (TryParse(msg, out TimeOnly timeOnly)) return timeOnly;
|
||||
if (TryParse(msg, out TimeSpan timeSpan)) return timeSpan;
|
||||
|
||||
return msg;
|
||||
if (TryParseBool(msg, out bool resBool)) return resBool;
|
||||
else if (TryParse(msg, out sbyte int8)) return int8;
|
||||
else if (TryParse(msg, out byte uInt8)) return uInt8;
|
||||
else if (TryParse(msg, out short int16)) return int16;
|
||||
else if (TryParse(msg, out ushort uInt16)) return uInt16;
|
||||
else if (TryParse(msg, out int int32)) return int32;
|
||||
else if (TryParse(msg, out uint uInt32)) return uInt32;
|
||||
else if (TryParse(msg, out long int64)) return int64;
|
||||
else if (TryParse(msg, out ulong uInt64)) return uInt64;
|
||||
else if (TryParse(msg, out Int128 int128)) return int128;
|
||||
else if (TryParse(msg, out UInt128 uInt128)) return uInt128;
|
||||
else if (TryParse(msg, out nint intPtr)) return intPtr;
|
||||
else if (TryParse(msg, out nuint uIntPtr)) return uIntPtr;
|
||||
else if (TryParse(msg, out Half float16)) return float16;
|
||||
else if (TryParse(msg, out float float32)) return float32;
|
||||
else if (TryParse(msg, out double float64)) return float64;
|
||||
else if (TryParse(msg, out decimal float128)) return float128;
|
||||
else if (TryParse(msg, out char resChar)) return resChar;
|
||||
else if (TryParse(msg, out DateOnly dateOnly)) return dateOnly;
|
||||
else if (TryParse(msg, out DateTime dateTime)) return dateTime;
|
||||
else if (TryParse(msg, out DateTimeOffset dateTimeOffset)) return dateTimeOffset;
|
||||
else if (TryParse(msg, out Guid guid)) return guid;
|
||||
else if (TryParse(msg, out TimeOnly timeOnly)) return timeOnly;
|
||||
else if (TryParse(msg, out TimeSpan timeSpan)) return timeSpan;
|
||||
else 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>
|
||||
=> T.TryParse(msg, null, out result);
|
||||
}
|
||||
|
||||
@ -4,6 +4,9 @@ public class Config
|
||||
{
|
||||
public const string FilePath = "config.json";
|
||||
|
||||
public static bool HasDisplayableError => false;
|
||||
public static bool HasDisplayableWarning => p_printedLastSteamWarning;
|
||||
|
||||
public static Config Defaults => new();
|
||||
|
||||
private static readonly FieldInfo[] p_configSharedFields;
|
||||
@ -22,8 +25,10 @@ public class Config
|
||||
private static Config p_applied;
|
||||
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.
|
||||
private string p_steamLocation;
|
||||
private readonly string p_steamLocation;
|
||||
|
||||
static Config()
|
||||
{
|
||||
@ -66,6 +71,7 @@ public class Config
|
||||
|
||||
public string[] GameDirectories;
|
||||
public AskMode RunUnsafeCommands;
|
||||
public bool UseLocalModDirectories;
|
||||
|
||||
internal Config()
|
||||
{
|
||||
@ -92,26 +98,40 @@ public class Config
|
||||
|
||||
string gameDirDataPath = Path.Combine(p_steamLocation, @"steamapps\libraryfolders.vdf");
|
||||
|
||||
VkvSerializer serializer = new(new()
|
||||
{
|
||||
useEscapeCodes = true,
|
||||
useQuotes = true
|
||||
});
|
||||
FileStream gameDirData = new(gameDirDataPath, FileMode.Open);
|
||||
|
||||
LibraryFolder[]? folders = serializer.Deserialize<LibraryFolder[]>(gameDirData);
|
||||
try
|
||||
{
|
||||
LibraryFolder[]? folders = SerializeVkv.Deserialize<LibraryFolder[]>(gameDirData);
|
||||
if (folders is null)
|
||||
{
|
||||
if (!p_printedLastSteamWarning)
|
||||
Write("[WARNING] Error parsing Steam game directories.", ConsoleColor.DarkYellow);
|
||||
GameDirectories = Array.Empty<string>();
|
||||
p_printedLastSteamWarning = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
GameDirectories = new string[folders.Length];
|
||||
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;
|
||||
UseLocalModDirectories = true;
|
||||
}
|
||||
|
||||
public Config ApplyChanges(Changes changes)
|
||||
@ -177,7 +197,7 @@ public class Config
|
||||
}
|
||||
StreamReader reader = new(fullPath);
|
||||
JsonTextReader jsonReader = new(reader);
|
||||
p_changes = Serializer.Deserialize<Changes?>(jsonReader);
|
||||
p_changes = Tools.SerializerJson.Deserialize<Changes?>(jsonReader);
|
||||
jsonReader.Close();
|
||||
reader.Close();
|
||||
|
||||
@ -198,7 +218,7 @@ public class Config
|
||||
{
|
||||
Indentation = 4
|
||||
};
|
||||
Serializer.Serialize(jsonWriter, p_changes);
|
||||
Tools.SerializerJson.Serialize(jsonWriter, p_changes);
|
||||
jsonWriter.Close();
|
||||
writer.Close();
|
||||
}
|
||||
@ -212,6 +232,7 @@ public class Config
|
||||
{
|
||||
public string[]? GameDirectories;
|
||||
public AskMode? RunUnsafeCommands;
|
||||
public bool? UseLocalModDirectories;
|
||||
|
||||
public bool Any() => typeof(Changes).GetFields().Any(x => x.GetValue(this) is not null);
|
||||
}
|
||||
|
||||
59
SrcMod/Shell/ObjectModels/Source/GameInfo.cs
Normal file
59
SrcMod/Shell/ObjectModels/Source/GameInfo.cs
Normal file
@ -0,0 +1,59 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,12 @@ public class Shell
|
||||
public const string Name = "SrcMod";
|
||||
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 List<CommandInfo> LoadedCommands;
|
||||
@ -19,6 +25,8 @@ public class Shell
|
||||
private bool p_lastCancel;
|
||||
private bool p_printedCancel;
|
||||
|
||||
private bool p_printedLastReloadError;
|
||||
|
||||
private BackgroundWorker? p_activeCommand;
|
||||
|
||||
public Shell()
|
||||
@ -139,9 +147,21 @@ public class Shell
|
||||
|
||||
public string ReadLine()
|
||||
{
|
||||
Write($"\n{WorkingDirectory}", ConsoleColor.DarkGreen, false);
|
||||
if (ActiveGame is not null) Write($" {ActiveGame}", ConsoleColor.DarkYellow, false);
|
||||
Write("\n", newLine: false);
|
||||
if (HasAnyDisplayableError) Write($"(Error) ", ConsoleColor.DarkRed, false);
|
||||
else if (HasAnyDisplayableWarning) Write($"(Warning) ", ConsoleColor.DarkYellow, 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($" {Name}", ConsoleColor.DarkCyan, false);
|
||||
@ -299,13 +319,34 @@ public class Shell
|
||||
}
|
||||
|
||||
public void ReloadDirectoryInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
ActiveMod = Mod.ReadDirectory(WorkingDirectory);
|
||||
ActiveGame = ActiveMod?.BaseGame;
|
||||
|
||||
// Update title.
|
||||
string title = "SrcMod";
|
||||
if (ActiveMod is not null) title += $" - {ActiveMod.Name}";
|
||||
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)
|
||||
|
||||
@ -2,15 +2,27 @@
|
||||
|
||||
public static class Tools
|
||||
{
|
||||
public static JsonSerializer Serializer { get; private set; }
|
||||
public static JsonSerializer SerializerJson { get; private set; }
|
||||
public static VkvSerializer SerializeVkv { get; private set; }
|
||||
|
||||
static Tools()
|
||||
{
|
||||
Serializer = JsonSerializer.Create(new()
|
||||
SerializerJson = JsonSerializer.Create(new()
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
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)
|
||||
|
||||
@ -2,45 +2,73 @@
|
||||
|
||||
public static class TypeParsers
|
||||
{
|
||||
public static bool CanParse(object? obj) => obj is not null && obj is sbyte or byte or short or ushort or int
|
||||
or uint or long or ulong or Int128 or UInt128 or nint or nuint or Half or float or double or decimal
|
||||
public static bool CanParse(object? obj) => obj is not null && obj is bool or sbyte or byte or short or ushort
|
||||
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 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)
|
||||
{
|
||||
if (TryParse(msg, out sbyte int8)) return int8;
|
||||
if (TryParse(msg, out byte uInt8)) return uInt8;
|
||||
if (TryParse(msg, out short int16)) return int16;
|
||||
if (TryParse(msg, out ushort uInt16)) return uInt16;
|
||||
if (TryParse(msg, out int int32)) return int32;
|
||||
if (TryParse(msg, out uint uInt32)) return uInt32;
|
||||
if (TryParse(msg, out long int64)) return int64;
|
||||
if (TryParse(msg, out ulong uInt64)) return uInt64;
|
||||
if (TryParse(msg, out Int128 int128)) return int128;
|
||||
if (TryParse(msg, out UInt128 uInt128)) return uInt128;
|
||||
if (TryParse(msg, out nint intPtr)) return intPtr;
|
||||
if (TryParse(msg, out nuint uIntPtr)) return uIntPtr;
|
||||
if (TryParse(msg, out Half float16)) return float16;
|
||||
if (TryParse(msg, out float float32)) return float32;
|
||||
if (TryParse(msg, out double float64)) return float64;
|
||||
if (TryParse(msg, out decimal float128)) return float128;
|
||||
if (TryParse(msg, out char resChar)) return resChar;
|
||||
if (TryParse(msg, out DateOnly dateOnly)) return dateOnly;
|
||||
if (TryParse(msg, out DateTime dateTime)) return dateTime;
|
||||
if (TryParse(msg, out DateTimeOffset dateTimeOffset)) return dateTimeOffset;
|
||||
if (TryParse(msg, out Guid guid)) return guid;
|
||||
if (TryParse(msg, out TimeOnly timeOnly)) return timeOnly;
|
||||
if (TryParse(msg, out TimeSpan timeSpan)) return timeSpan;
|
||||
|
||||
return msg;
|
||||
if (TryParseBool(msg, out bool resBool)) return resBool;
|
||||
else if (TryParse(msg, out sbyte int8)) return int8;
|
||||
else if (TryParse(msg, out byte uInt8)) return uInt8;
|
||||
else if (TryParse(msg, out short int16)) return int16;
|
||||
else if (TryParse(msg, out ushort uInt16)) return uInt16;
|
||||
else if (TryParse(msg, out int int32)) return int32;
|
||||
else if (TryParse(msg, out uint uInt32)) return uInt32;
|
||||
else if (TryParse(msg, out long int64)) return int64;
|
||||
else if (TryParse(msg, out ulong uInt64)) return uInt64;
|
||||
else if (TryParse(msg, out Int128 int128)) return int128;
|
||||
else if (TryParse(msg, out UInt128 uInt128)) return uInt128;
|
||||
else if (TryParse(msg, out nint intPtr)) return intPtr;
|
||||
else if (TryParse(msg, out nuint uIntPtr)) return uIntPtr;
|
||||
else if (TryParse(msg, out Half float16)) return float16;
|
||||
else if (TryParse(msg, out float float32)) return float32;
|
||||
else if (TryParse(msg, out double float64)) return float64;
|
||||
else if (TryParse(msg, out decimal float128)) return float128;
|
||||
else if (TryParse(msg, out char resChar)) return resChar;
|
||||
else if (TryParse(msg, out DateOnly dateOnly)) return dateOnly;
|
||||
else if (TryParse(msg, out DateTime dateTime)) return dateTime;
|
||||
else if (TryParse(msg, out DateTimeOffset dateTimeOffset)) return dateTimeOffset;
|
||||
else if (TryParse(msg, out Guid guid)) return guid;
|
||||
else if (TryParse(msg, out TimeOnly timeOnly)) return timeOnly;
|
||||
else if (TryParse(msg, out TimeSpan timeSpan)) return timeSpan;
|
||||
else 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>
|
||||
=> T.TryParse(msg, null, out result);
|
||||
}
|
||||
|
||||
9
SrcMod/Valve.NET/Vkv/ObjectModels/VkvKeyNameAttribute.cs
Normal file
9
SrcMod/Valve.NET/Vkv/ObjectModels/VkvKeyNameAttribute.cs
Normal file
@ -0,0 +1,9 @@
|
||||
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;
|
||||
}
|
||||
@ -1,6 +1,4 @@
|
||||
using Valve.Vkv.ObjectModels;
|
||||
|
||||
namespace Valve.Vkv;
|
||||
namespace Valve.Vkv;
|
||||
|
||||
public static class VkvConvert
|
||||
{
|
||||
@ -21,9 +19,19 @@ public static class VkvConvert
|
||||
|
||||
#region DeserializeNode
|
||||
public static VkvNode? DeserializeNode(StreamReader reader) =>
|
||||
DeserializeNode(reader, VkvOptions.Default, out _, null);
|
||||
public static VkvNode? DeserializeNode(StreamReader reader, VkvOptions options) =>
|
||||
DeserializeNode(reader, options, out _, null);
|
||||
DeserializeNode(reader, VkvOptions.Default);
|
||||
public static VkvNode? DeserializeNode(StreamReader reader, VkvOptions options)
|
||||
{
|
||||
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,
|
||||
string? first)
|
||||
@ -57,7 +65,6 @@ public static class VkvConvert
|
||||
{
|
||||
if (current == "}") break;
|
||||
VkvNode? output = DeserializeNode(reader, options, out string subName, current);
|
||||
if (output is null) throw new VkvSerializationException("Error deserializing sub-node.");
|
||||
tree[subName] = output;
|
||||
}
|
||||
if (current is null) throw new VkvSerializationException("Reached end-of-file while deserializing group.");
|
||||
@ -114,6 +121,8 @@ public static class VkvConvert
|
||||
#region FromNodeTree
|
||||
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)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (node is null) return null;
|
||||
|
||||
@ -121,6 +130,12 @@ public static class VkvConvert
|
||||
else if (node is VkvTreeNode tree) return FromTreeNode(outputType, tree, options);
|
||||
else throw new VkvSerializationException("Unknown VKV node type.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!options.noExceptions) throw;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static object? FromSingleNode(Type outputType, VkvSingleNode node)
|
||||
{
|
||||
@ -177,9 +192,9 @@ public static class VkvConvert
|
||||
|
||||
foreach (FieldInfo field in validFields)
|
||||
{
|
||||
string name = field.Name;
|
||||
VkvKeyNameAttribute? namingAttribute = field.GetCustomAttribute<VkvKeyNameAttribute>();
|
||||
|
||||
VkvNode? subNode = node[name];
|
||||
VkvNode? subNode = node[namingAttribute?.name ?? field.Name];
|
||||
if (subNode is null) continue;
|
||||
|
||||
object? result = FromNodeTree(field.FieldType, subNode, options);
|
||||
@ -188,9 +203,9 @@ public static class VkvConvert
|
||||
}
|
||||
foreach (PropertyInfo prop in validProperties)
|
||||
{
|
||||
string name = prop.Name;
|
||||
VkvKeyNameAttribute? namingAttribute = prop.GetCustomAttribute<VkvKeyNameAttribute>();
|
||||
|
||||
VkvNode? subNode = node[name];
|
||||
VkvNode? subNode = node[namingAttribute?.name ?? prop.Name];
|
||||
if (subNode is null) continue;
|
||||
|
||||
object? result = FromNodeTree(prop.PropertyType, subNode, options);
|
||||
@ -268,9 +283,19 @@ public static class VkvConvert
|
||||
|
||||
#region SerializeNode
|
||||
public static void SerializeNode(StreamWriter writer, VkvNode? node, string name,
|
||||
VkvOptions options) => SerializeNode(writer, node, name, options, 0);
|
||||
VkvOptions options)
|
||||
{
|
||||
try
|
||||
{
|
||||
SerializeNode(writer, node, name, options, 0);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!options.noExceptions) throw;
|
||||
}
|
||||
}
|
||||
public static void SerializeNode(StreamWriter writer, VkvNode? node, string name) =>
|
||||
SerializeNode(writer, node, name, VkvOptions.Default, 0);
|
||||
SerializeNode(writer, node, name, VkvOptions.Default);
|
||||
|
||||
private static void SerializeNode(StreamWriter writer, VkvNode? node, string name,
|
||||
VkvOptions options, int indentLevel)
|
||||
@ -343,6 +368,8 @@ public static class VkvConvert
|
||||
#region ToNodeTree
|
||||
public static VkvNode? ToNodeTree(object? obj) => ToNodeTree(obj, VkvOptions.Default);
|
||||
public static VkvNode? ToNodeTree(object? obj, VkvOptions options)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (obj is null) return null;
|
||||
Type type = obj.GetType();
|
||||
@ -401,14 +428,22 @@ public static class VkvConvert
|
||||
|
||||
foreach (FieldInfo field in validFields)
|
||||
{
|
||||
tree[field.Name] = ToNodeTree(field.GetValue(obj), options);
|
||||
VkvKeyNameAttribute? namingAttribute = field.GetCustomAttribute<VkvKeyNameAttribute>();
|
||||
tree[namingAttribute?.name ?? field.Name] = ToNodeTree(field.GetValue(obj), options);
|
||||
}
|
||||
foreach (PropertyInfo prop in validProperties)
|
||||
{
|
||||
tree[prop.Name] = ToNodeTree(prop.GetValue(obj), options);
|
||||
VkvKeyNameAttribute? namingAttribute = prop.GetCustomAttribute<VkvKeyNameAttribute>();
|
||||
tree[namingAttribute?.name ?? prop.Name] = ToNodeTree(prop.GetValue(obj), options);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!options.noExceptions) throw;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ public record class VkvOptions
|
||||
|
||||
public bool closeWhenFinished;
|
||||
public int indentSize;
|
||||
public bool noExceptions;
|
||||
public bool resetStreamPosition;
|
||||
public bool serializeProperties;
|
||||
public SpacingMode spacing;
|
||||
@ -16,6 +17,7 @@ public record class VkvOptions
|
||||
{
|
||||
closeWhenFinished = true;
|
||||
indentSize = 4;
|
||||
noExceptions = false;
|
||||
resetStreamPosition = false;
|
||||
serializeProperties = true;
|
||||
spacing = SpacingMode.DoubleTab;
|
||||
|
||||
@ -16,12 +16,19 @@ public class VkvSerializer
|
||||
{
|
||||
long pos = stream.Position;
|
||||
StreamReader reader = new(stream, leaveOpen: !p_options.closeWhenFinished);
|
||||
try
|
||||
{
|
||||
VkvNode? result = VkvConvert.DeserializeNode(reader, p_options);
|
||||
reader.Close();
|
||||
|
||||
if (!p_options.closeWhenFinished && p_options.resetStreamPosition) stream.Seek(pos, SeekOrigin.Begin);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.Close();
|
||||
if (!p_options.closeWhenFinished && p_options.resetStreamPosition) stream.Seek(pos, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
public T? Deserialize<T>(Stream stream)
|
||||
{
|
||||
VkvNode? result = Deserialize(stream);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user