Ready for the first beta release. #105
4
.gitignore
vendored
@ -1,11 +1,13 @@
|
||||
# Visual Studio stuff
|
||||
.vs/
|
||||
SrcMod/.vs/
|
||||
*.sln
|
||||
|
||||
# Compiled Files
|
||||
SrcMod/Compiled
|
||||
SrcMod/Shell/obj/
|
||||
|
||||
SrcMod/Valve.NET/obj
|
||||
|
||||
# Personal Stuff
|
||||
SrcMod/Shell/Modules/TestingModule.cs
|
||||
TODO.md
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
global using Nerd_STF.Mathematics;
|
||||
global using Microsoft.Win32;
|
||||
global using Nerd_STF.Mathematics;
|
||||
global using Newtonsoft.Json;
|
||||
global using SharpCompress.Archives.Rar;
|
||||
global using SharpCompress.Archives.SevenZip;
|
||||
global using SharpCompress.Readers;
|
||||
global using SrcMod.Shell.Extensions;
|
||||
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;
|
||||
global using System.Collections.Generic;
|
||||
@ -20,4 +24,5 @@ global using System.Reflection;
|
||||
global using System.Runtime.InteropServices;
|
||||
global using System.Text;
|
||||
global using System.Threading;
|
||||
global using Valve.Vkv;
|
||||
global using static SrcMod.Shell.Tools;
|
||||
@ -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,35 +2,73 @@
|
||||
|
||||
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
|
||||
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,14 @@ 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 readonly string p_steamLocation;
|
||||
|
||||
static Config()
|
||||
{
|
||||
// Generate shared fields between the config class and its changes equivalent.
|
||||
p_applied = Defaults;
|
||||
|
||||
FieldInfo[] configFields = (from field in typeof(Config).GetFields()
|
||||
@ -62,11 +71,67 @@ public class Config
|
||||
|
||||
public string[] GameDirectories;
|
||||
public AskMode RunUnsafeCommands;
|
||||
public bool UseLocalModDirectories;
|
||||
|
||||
internal Config()
|
||||
{
|
||||
// Locate some steam stuff.
|
||||
const string steamLocationKey = @"Software\Valve\Steam";
|
||||
RegistryKey? key = Registry.CurrentUser.OpenSubKey(steamLocationKey);
|
||||
if (key is null)
|
||||
{
|
||||
Write("[FATAL] Cannot locate Steam installation. Do you have Steam installed?",
|
||||
ConsoleColor.DarkRed);
|
||||
Thread.Sleep(1000);
|
||||
BaseModule.QuitShell(-1);
|
||||
|
||||
// This should never run, and is just here to supress
|
||||
// a couple compiler warnings.
|
||||
p_steamLocation = string.Empty;
|
||||
GameDirectories = Array.Empty<string>();
|
||||
RunUnsafeCommands = AskMode.Ask;
|
||||
return;
|
||||
}
|
||||
p_steamLocation = (string)key.GetValue("SteamPath")!;
|
||||
|
||||
// Assign config variables.
|
||||
|
||||
string gameDirDataPath = Path.Combine(p_steamLocation, @"steamapps\libraryfolders.vdf");
|
||||
|
||||
FileStream gameDirData = new(gameDirDataPath, FileMode.Open);
|
||||
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)
|
||||
@ -132,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();
|
||||
|
||||
@ -153,7 +218,7 @@ public class Config
|
||||
{
|
||||
Indentation = 4
|
||||
};
|
||||
Serializer.Serialize(jsonWriter, p_changes);
|
||||
Tools.SerializerJson.Serialize(jsonWriter, p_changes);
|
||||
jsonWriter.Close();
|
||||
writer.Close();
|
||||
}
|
||||
@ -167,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
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
13
SrcMod/Shell/ObjectModels/Steam/LibraryFolder.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace SrcMod.Shell.ObjectModels.Steam;
|
||||
|
||||
public class LibraryFolder
|
||||
{
|
||||
public string path;
|
||||
public Dictionary<int, ulong> apps;
|
||||
|
||||
public LibraryFolder()
|
||||
{
|
||||
path = string.Empty;
|
||||
apps = new();
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,13 @@ public class Shell
|
||||
{
|
||||
public const string Author = "That_One_Nerd";
|
||||
public const string Name = "SrcMod";
|
||||
public const string Version = "Alpha 0.4.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;
|
||||
|
||||
@ -16,10 +22,12 @@ public class Shell
|
||||
public List<HistoryItem> History;
|
||||
public string WorkingDirectory;
|
||||
|
||||
private bool lastCancel;
|
||||
private bool printedCancel;
|
||||
private bool p_lastCancel;
|
||||
private bool p_printedCancel;
|
||||
|
||||
private BackgroundWorker? activeCommand;
|
||||
private bool p_printedLastReloadError;
|
||||
|
||||
private BackgroundWorker? p_activeCommand;
|
||||
|
||||
public Shell()
|
||||
{
|
||||
@ -88,8 +96,8 @@ public class Shell
|
||||
Write(" by ", ConsoleColor.White, false);
|
||||
Write($"{Author}", ConsoleColor.DarkYellow);
|
||||
|
||||
lastCancel = false;
|
||||
activeCommand = null;
|
||||
p_lastCancel = false;
|
||||
p_activeCommand = null;
|
||||
Console.CancelKeyPress += HandleCancel;
|
||||
|
||||
ActiveGame = null;
|
||||
@ -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);
|
||||
@ -149,7 +169,7 @@ public class Shell
|
||||
|
||||
bool printed = false;
|
||||
|
||||
if (lastCancel && !printedCancel)
|
||||
if (p_lastCancel && !p_printedCancel)
|
||||
{
|
||||
// Print the warning. A little bit of mess because execution must
|
||||
// continue without funny printing errors but it's alright I guess.
|
||||
@ -160,7 +180,7 @@ public class Shell
|
||||
Write("Press ^C again to exit the shell.", ConsoleColor.Red);
|
||||
PlayWarningSound();
|
||||
|
||||
printedCancel = true;
|
||||
p_printedCancel = true;
|
||||
Console.CursorTop += 2;
|
||||
|
||||
Console.CursorLeft = originalLeft;
|
||||
@ -175,8 +195,8 @@ public class Shell
|
||||
|
||||
if (!printed)
|
||||
{
|
||||
lastCancel = false;
|
||||
printedCancel = false;
|
||||
p_lastCancel = false;
|
||||
p_printedCancel = false;
|
||||
}
|
||||
|
||||
return message;
|
||||
@ -262,18 +282,18 @@ public class Shell
|
||||
}
|
||||
}
|
||||
|
||||
activeCommand = new();
|
||||
activeCommand.DoWork += runCommand;
|
||||
activeCommand.RunWorkerAsync();
|
||||
p_activeCommand = new();
|
||||
p_activeCommand.DoWork += runCommand;
|
||||
p_activeCommand.RunWorkerAsync();
|
||||
|
||||
activeCommand.WorkerSupportsCancellation = command.CanBeCancelled;
|
||||
p_activeCommand.WorkerSupportsCancellation = command.CanBeCancelled;
|
||||
|
||||
while (activeCommand is not null && activeCommand.IsBusy) Thread.Yield();
|
||||
while (p_activeCommand is not null && p_activeCommand.IsBusy) Thread.Yield();
|
||||
|
||||
if (activeCommand is not null)
|
||||
if (p_activeCommand is not null)
|
||||
{
|
||||
activeCommand.Dispose();
|
||||
activeCommand = null;
|
||||
p_activeCommand.Dispose();
|
||||
p_activeCommand = null;
|
||||
}
|
||||
|
||||
if (ShellDirectory is null) Write("[WARNING] Could not save config to shell location. Any changes will be ignored.");
|
||||
@ -299,25 +319,46 @@ 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)
|
||||
{
|
||||
if (activeCommand is not null && activeCommand.IsBusy)
|
||||
if (p_activeCommand is not null && p_activeCommand.IsBusy)
|
||||
{
|
||||
if (activeCommand.WorkerSupportsCancellation)
|
||||
if (p_activeCommand.WorkerSupportsCancellation)
|
||||
{
|
||||
// Kill the active command.
|
||||
activeCommand.CancelAsync();
|
||||
activeCommand.Dispose();
|
||||
activeCommand = null;
|
||||
p_activeCommand.CancelAsync();
|
||||
p_activeCommand.Dispose();
|
||||
p_activeCommand = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -326,18 +367,18 @@ public class Shell
|
||||
PlayErrorSound();
|
||||
}
|
||||
|
||||
lastCancel = false;
|
||||
printedCancel = false;
|
||||
p_lastCancel = false;
|
||||
p_printedCancel = false;
|
||||
args.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Due to some funny multithreading issues, we want to make the warning label
|
||||
// single-threaded on the shell.
|
||||
if (!lastCancel)
|
||||
if (!p_lastCancel)
|
||||
{
|
||||
// Enable the warning. The "ReadLine" method will do the rest.
|
||||
lastCancel = true;
|
||||
p_lastCancel = true;
|
||||
args.Cancel = true; // "Cancel" referring to the cancellation of the cancel operation.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>srcmod</AssemblyName>
|
||||
@ -16,16 +16,6 @@
|
||||
<NoWin32Manifest>true</NoWin32Manifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
<WarningLevel>9999</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
<WarningLevel>9999</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Logo.ico" />
|
||||
</ItemGroup>
|
||||
@ -36,4 +26,8 @@
|
||||
<PackageReference Include="SharpCompress" Version="0.33.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Valve.NET\Valve.NET.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -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)
|
||||
|
||||
31
SrcMod/SrcMod.sln
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33213.308
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shell", "Shell\Shell.csproj", "{6EC87235-F2A5-4313-A6DE-A4EE7CB7B341}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Valve.NET", "Valve.NET\Valve.NET.csproj", "{8FC96202-2F7E-4FBE-B08E-FCC38AA62D96}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6EC87235-F2A5-4313-A6DE-A4EE7CB7B341}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6EC87235-F2A5-4313-A6DE-A4EE7CB7B341}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6EC87235-F2A5-4313-A6DE-A4EE7CB7B341}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6EC87235-F2A5-4313-A6DE-A4EE7CB7B341}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8FC96202-2F7E-4FBE-B08E-FCC38AA62D96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8FC96202-2F7E-4FBE-B08E-FCC38AA62D96}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8FC96202-2F7E-4FBE-B08E-FCC38AA62D96}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8FC96202-2F7E-4FBE-B08E-FCC38AA62D96}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {4E25AC28-DD70-4BB6-9083-07D6371DECCF}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
17
SrcMod/Valve.NET/Miscellaneous/GlobalUsings.cs
Normal file
@ -0,0 +1,17 @@
|
||||
global using System;
|
||||
global using System.Collections;
|
||||
global using System.Collections.Generic;
|
||||
global using System.ComponentModel;
|
||||
global using System.Diagnostics;
|
||||
global using System.Formats.Tar;
|
||||
global using System.IO;
|
||||
global using System.IO.Compression;
|
||||
global using System.Linq;
|
||||
global using System.Reflection;
|
||||
global using System.Runtime.InteropServices;
|
||||
global using System.Text;
|
||||
global using System.Threading;
|
||||
global using Valve;
|
||||
global using Valve.Miscellaneous;
|
||||
global using Valve.Vkv;
|
||||
global using Valve.Vkv.ObjectModels;
|
||||
74
SrcMod/Valve.NET/Miscellaneous/TypeParsers.cs
Normal file
@ -0,0 +1,74 @@
|
||||
|
|
||||
namespace Valve.Miscellaneous;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
public static class TypeParsers
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
{
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
public static bool CanParse(object? obj) => obj is not null && obj is bool or sbyte or byte or short or ushort
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
or int or uint or long or ulong or Int128 or UInt128 or nint or nuint or Half or float or double or decimal
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
or char or DateOnly or DateTime or DateTimeOffset or Guid or TimeOnly or TimeSpan;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
public static object ParseAll(string msg)
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
{
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
if (TryParseBool(msg, out bool resBool)) return resBool;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out sbyte int8)) return int8;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out byte uInt8)) return uInt8;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out short int16)) return int16;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out ushort uInt16)) return uInt16;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out int int32)) return int32;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out uint uInt32)) return uInt32;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out long int64)) return int64;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out ulong uInt64)) return uInt64;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out Int128 int128)) return int128;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out UInt128 uInt128)) return uInt128;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out nint intPtr)) return intPtr;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out nuint uIntPtr)) return uIntPtr;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out Half float16)) return float16;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out float float32)) return float32;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out double float64)) return float64;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out decimal float128)) return float128;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out char resChar)) return resChar;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out DateOnly dateOnly)) return dateOnly;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out DateTime dateTime)) return dateTime;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out DateTimeOffset dateTimeOffset)) return dateTimeOffset;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out Guid guid)) return guid;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out TimeOnly timeOnly)) return timeOnly;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else if (TryParse(msg, out TimeSpan timeSpan)) return timeSpan;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
else return msg;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
}
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
public static bool TryParseBool(string msg, out bool result)
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
{
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
string trimmed = msg.Trim().ToLower();
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
string[] trues = new string[]
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
{
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
"t",
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
"true",
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
"1",
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
"y",
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
"yes"
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
},
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
falses = new string[]
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
{
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
"f",
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
"false",
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
"0",
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
"n",
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
"no"
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
};
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
foreach (string t in trues) if (trimmed == t)
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
{
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
result = true;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
return true;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
}
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
foreach (string f in falses) if (trimmed == f)
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
{
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
result = false;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
return true;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
}
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
result = false;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
return false;
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
}
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
public static bool TryParse<T>(string msg, out T? result) where T : IParsable<T>
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
=> T.TryParse(msg, null, out result);
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
}
|
||||
|
Synchronize this file with the other TypeParsers class in the shell. Synchronize this file with the other TypeParsers class in the shell.
This has been completed now. This has been completed now.
|
||||
17
SrcMod/Valve.NET/Valve.NET.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>valve.net</AssemblyName>
|
||||
<RootNamespace>Valve</RootNamespace>
|
||||
<OutputPath>../Compiled/Valve.NET</OutputPath>
|
||||
<Title>Valve.NET</Title>
|
||||
<Authors>That_One_Nerd</Authors>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<NoWin32Manifest>true</NoWin32Manifest>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
6
SrcMod/Valve.NET/Vkv/ObjectModels/IVkvConvertible.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Valve.Vkv.ObjectModels;
|
||||
|
||||
public interface IVkvConvertible
|
||||
{
|
||||
public VkvNode ToNodeTree();
|
||||
}
|
||||
4
SrcMod/Valve.NET/Vkv/ObjectModels/VkvIgnoreAttribute.cs
Normal file
@ -0,0 +1,4 @@
|
||||
namespace Valve.Vkv.ObjectModels;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
|
||||
public class VkvIgnoreAttribute : Attribute { }
|
||||
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;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Valve.Vkv.ObjectModels;
|
||||
|
||||
public class VkvSerializationException : Exception
|
||||
{
|
||||
public VkvSerializationException() : base() { }
|
||||
public VkvSerializationException(string message) : base(message) { }
|
||||
public VkvSerializationException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
8
SrcMod/Valve.NET/Vkv/SpacingMode.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Valve.Vkv;
|
||||
|
||||
public enum SpacingMode
|
||||
{
|
||||
SingleSpace = 0,
|
||||
IndentSizeSpacing,
|
||||
DoubleTab,
|
||||
}
|
||||
449
SrcMod/Valve.NET/Vkv/VkvConvert.cs
Normal file
@ -0,0 +1,449 @@
|
||||
namespace Valve.Vkv;
|
||||
|
||||
public static class VkvConvert
|
||||
{
|
||||
private static readonly Dictionary<string, string> p_escapeCodes = new()
|
||||
{
|
||||
{ "\\", @"\\" }, // This must be first.
|
||||
{ "\'", @"\'" },
|
||||
{ "\"", @"\""" },
|
||||
{ "\0", @"\0" },
|
||||
{ "\a", @"\a" },
|
||||
{ "\b", @"\b" },
|
||||
{ "\f", @"\f" },
|
||||
{ "\n", @"\n" },
|
||||
{ "\r", @"\r" },
|
||||
{ "\t", @"\t" },
|
||||
{ "\v", @"\v" }
|
||||
};
|
||||
|
||||
#region DeserializeNode
|
||||
public static VkvNode? DeserializeNode(StreamReader reader) =>
|
||||
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)
|
||||
{
|
||||
string? header = first ?? (reader.ReadLine()?.Trim());
|
||||
if (header is null || string.IsNullOrEmpty(header))
|
||||
{
|
||||
name = string.Empty;
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] parts = SplitContent(header, options);
|
||||
if (parts.Length > 2) throw new VkvSerializationException("Too many values in node.");
|
||||
|
||||
VkvNode node;
|
||||
|
||||
name = DeserializeString(parts[0], options);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
string value = DeserializeString(parts[1], options);
|
||||
node = new VkvSingleNode(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
string? next = reader.ReadLine()?.Trim();
|
||||
if (next is null) throw new VkvSerializationException("Expected starting '{', found end-of-file.");
|
||||
else if (next != "{") throw new VkvSerializationException($"Expected starting '{{', found \"{next}\".");
|
||||
VkvTreeNode tree = new();
|
||||
string? current;
|
||||
while ((current = reader.ReadLine()?.Trim()) is not null)
|
||||
{
|
||||
if (current == "}") break;
|
||||
VkvNode? output = DeserializeNode(reader, options, out string subName, current);
|
||||
tree[subName] = output;
|
||||
}
|
||||
if (current is null) throw new VkvSerializationException("Reached end-of-file while deserializing group.");
|
||||
node = tree;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static string DeserializeString(string content, VkvOptions options)
|
||||
{
|
||||
if (options.useQuotes)
|
||||
{
|
||||
if (!content.StartsWith('\"') || !content.EndsWith('\"'))
|
||||
throw new VkvSerializationException("No quotes found around content.");
|
||||
content = content[1..^1];
|
||||
}
|
||||
if (options.useEscapeCodes)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> escapeCode in p_escapeCodes.Reverse())
|
||||
content = content.Replace(escapeCode.Value, escapeCode.Key);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private static string[] SplitContent(string content, VkvOptions options)
|
||||
{
|
||||
content = content.Replace('\t', ' ');
|
||||
if (options.useQuotes)
|
||||
{
|
||||
List<string> values = new();
|
||||
string current = string.Empty;
|
||||
bool inQuote = false;
|
||||
for (int i = 0; i < content.Length; i++)
|
||||
{
|
||||
char c = content[i];
|
||||
if (c == '\"' && !(i > 0 && content[i - 1] == '\\')) inQuote = !inQuote;
|
||||
|
||||
if (c == ' ' && !inQuote)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(current)) values.Add(current);
|
||||
current = string.Empty;
|
||||
}
|
||||
else current += c;
|
||||
}
|
||||
if (inQuote) throw new VkvSerializationException("Reached end-of-line while inside quotations.");
|
||||
if (!string.IsNullOrEmpty(current)) values.Add(current);
|
||||
return values.ToArray();
|
||||
}
|
||||
else return content.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#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;
|
||||
|
||||
if (node is VkvSingleNode single) return FromSingleNode(outputType, single);
|
||||
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)
|
||||
{
|
||||
object? value = node.value;
|
||||
if (value is null) return null;
|
||||
else if (value is string str)
|
||||
{
|
||||
value = TypeParsers.ParseAll(str);
|
||||
if (value is string still && outputType.IsEnum)
|
||||
{
|
||||
if (Enum.TryParse(outputType, still, true, out object? res) && res is not null)
|
||||
value = res;
|
||||
}
|
||||
}
|
||||
return Convert.ChangeType(value, outputType);
|
||||
}
|
||||
|
||||
private static object? FromTreeNode(Type outputType, VkvTreeNode node, VkvOptions options)
|
||||
{
|
||||
if (outputType.IsArray)
|
||||
return FromTreeNodeArray(outputType, node, options);
|
||||
|
||||
else if (outputType.GetInterface("IList") is not null)
|
||||
return FromTreeNodeList(outputType, node, options);
|
||||
|
||||
else if (outputType.GetInterface("IDictionary") is not null)
|
||||
return FromTreeNodeDictionary(outputType, node, options);
|
||||
|
||||
object? instance = Activator.CreateInstance(outputType);
|
||||
if (instance is null) return null;
|
||||
|
||||
IEnumerable<FieldInfo> validFields = from field in outputType.GetFields()
|
||||
let isPublic = field.IsPublic
|
||||
let isStatic = field.IsStatic
|
||||
let isIgnored = field.CustomAttributes.Any(x =>
|
||||
x.AttributeType == typeof(VkvIgnoreAttribute))
|
||||
let isConst = field.IsLiteral
|
||||
where isPublic && !isStatic && !isIgnored && !isConst
|
||||
select field;
|
||||
|
||||
IEnumerable<PropertyInfo> validProperties;
|
||||
if (options.serializeProperties)
|
||||
{
|
||||
validProperties = from prop in outputType.GetProperties()
|
||||
let canSet = prop.SetMethod is not null
|
||||
let isPublic = canSet && prop.SetMethod!.IsPublic
|
||||
let isStatic = canSet && prop.SetMethod!.IsStatic
|
||||
let isIgnored = prop.CustomAttributes.Any(x =>
|
||||
x.AttributeType == typeof(VkvIgnoreAttribute))
|
||||
where canSet && isPublic && !isStatic && !isIgnored
|
||||
select prop;
|
||||
}
|
||||
else validProperties = Array.Empty<PropertyInfo>();
|
||||
|
||||
foreach (FieldInfo field in validFields)
|
||||
{
|
||||
VkvKeyNameAttribute? namingAttribute = field.GetCustomAttribute<VkvKeyNameAttribute>();
|
||||
|
||||
VkvNode? subNode = node[namingAttribute?.name ?? field.Name];
|
||||
if (subNode is null) continue;
|
||||
|
||||
object? result = FromNodeTree(field.FieldType, subNode, options);
|
||||
if (result is null) continue;
|
||||
field.SetValue(instance, result);
|
||||
}
|
||||
foreach (PropertyInfo prop in validProperties)
|
||||
{
|
||||
VkvKeyNameAttribute? namingAttribute = prop.GetCustomAttribute<VkvKeyNameAttribute>();
|
||||
|
||||
VkvNode? subNode = node[namingAttribute?.name ?? prop.Name];
|
||||
if (subNode is null) continue;
|
||||
|
||||
object? result = FromNodeTree(prop.PropertyType, subNode, options);
|
||||
if (result is null) continue;
|
||||
prop.SetValue(instance, result);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static object? FromTreeNodeArray(Type outputType, VkvTreeNode node, VkvOptions options)
|
||||
{
|
||||
Type elementType = outputType.GetElementType()!;
|
||||
Array array = Array.CreateInstance(elementType, node.SubNodeCount);
|
||||
|
||||
int index = 0;
|
||||
foreach (KeyValuePair<string, VkvNode?> subNode in node)
|
||||
{
|
||||
string indexStr = index.ToString();
|
||||
if (subNode.Key != indexStr) throw new VkvSerializationException($"Cannot convert node tree to array.");
|
||||
array.SetValue(FromNodeTree(elementType, subNode.Value, options), index);
|
||||
index++;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private static object? FromTreeNodeList(Type outputType, VkvTreeNode node, VkvOptions options)
|
||||
{
|
||||
IList? instance = (IList?)Activator.CreateInstance(outputType);
|
||||
if (instance is null) return null;
|
||||
|
||||
// There is no guarentee that the first type argument corresponds to the element type,
|
||||
// but as far as I know there isn't a better way.
|
||||
Type elementType = outputType.IsGenericType ? outputType.GenericTypeArguments[0] : typeof(object);
|
||||
|
||||
int index = 0;
|
||||
foreach (KeyValuePair<string, VkvNode?> subNode in node)
|
||||
{
|
||||
string indexStr = index.ToString();
|
||||
if (subNode.Key != indexStr) throw new VkvSerializationException($"Cannot convert node tree to array.");
|
||||
instance.Add(FromNodeTree(elementType, subNode.Value, options));
|
||||
index++;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static object? FromTreeNodeDictionary(Type outputType, VkvTreeNode node, VkvOptions options)
|
||||
{
|
||||
IDictionary? instance = (IDictionary?)Activator.CreateInstance(outputType);
|
||||
if (instance is null) return null;
|
||||
|
||||
// There is no guarentee that the first and second type arguments represent the
|
||||
// key and value types, but as far as I know there isn't a better way.
|
||||
bool canUseGenerics = outputType.GenericTypeArguments.Length >= 2;
|
||||
Type keyType = canUseGenerics ? outputType.GenericTypeArguments[0] : typeof(object),
|
||||
valueType = canUseGenerics ? outputType.GenericTypeArguments[1] : typeof(object);
|
||||
|
||||
foreach (KeyValuePair<string, VkvNode?> subNode in node)
|
||||
{
|
||||
object key = TypeParsers.ParseAll(subNode.Key);
|
||||
if (key is string still && keyType.IsEnum)
|
||||
{
|
||||
if (Enum.TryParse(keyType, still, true, out object? res) && res is not null)
|
||||
key = res;
|
||||
}
|
||||
key = Convert.ChangeType(key, keyType);
|
||||
|
||||
object? value = FromNodeTree(valueType, subNode.Value, options);
|
||||
|
||||
instance.Add(key, value);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SerializeNode
|
||||
public static void SerializeNode(StreamWriter writer, VkvNode? node, string name,
|
||||
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);
|
||||
|
||||
private static void SerializeNode(StreamWriter writer, VkvNode? node, string name,
|
||||
VkvOptions options, int indentLevel)
|
||||
{
|
||||
if (node is null) return;
|
||||
else if (node is VkvSingleNode single) SerializeSingleNode(writer, single, name, options, indentLevel);
|
||||
else if (node is VkvTreeNode tree) SerializeTreeNode(writer, tree, name, options, indentLevel);
|
||||
else throw new("Unknown node type.");
|
||||
}
|
||||
|
||||
private static void SerializeSingleNode(StreamWriter writer, VkvSingleNode node, string name,
|
||||
VkvOptions options, int indentLevel)
|
||||
{
|
||||
string? serializedValue = SerializeObject(node.value);
|
||||
if (serializedValue is null) return;
|
||||
|
||||
writer.Write(new string(' ', indentLevel));
|
||||
writer.Write(SerializeString(name, options));
|
||||
|
||||
switch (options.spacing)
|
||||
{
|
||||
case SpacingMode.SingleSpace: writer.Write(' ');
|
||||
break;
|
||||
|
||||
case SpacingMode.IndentSizeSpacing: writer.Write(new string(' ', options.indentSize));
|
||||
break;
|
||||
|
||||
case SpacingMode.DoubleTab: writer.Write("\t\t");
|
||||
break;
|
||||
|
||||
default: throw new VkvSerializationException($"Unknown spacing mode \"{options.spacing}\".");
|
||||
}
|
||||
|
||||
serializedValue = SerializeString(serializedValue, options);
|
||||
writer.WriteLine(serializedValue);
|
||||
}
|
||||
private static void SerializeTreeNode(StreamWriter writer, VkvTreeNode node, string name,
|
||||
VkvOptions options, int indentLevel)
|
||||
{
|
||||
if (node.SubNodeCount <= 0) return;
|
||||
|
||||
writer.Write(new string(' ', indentLevel));
|
||||
writer.WriteLine(SerializeString(name, options));
|
||||
writer.WriteLine(new string(' ', indentLevel) + '{');
|
||||
|
||||
foreach (KeyValuePair<string, VkvNode?> subNode in node)
|
||||
SerializeNode(writer, subNode.Value, subNode.Key, options, indentLevel + options.indentSize);
|
||||
|
||||
writer.WriteLine(new string(' ', indentLevel) + '}');
|
||||
}
|
||||
|
||||
private static string? SerializeObject(object? obj)
|
||||
{
|
||||
if (obj is null) return null;
|
||||
return obj.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
private static string SerializeString(string content, VkvOptions options)
|
||||
{
|
||||
if (options.useEscapeCodes)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> escapeCode in p_escapeCodes)
|
||||
content = content.Replace(escapeCode.Key, escapeCode.Value);
|
||||
}
|
||||
if (options.useQuotes) content = $"\"{content}\"";
|
||||
return content;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#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();
|
||||
|
||||
if (type.IsPrimitive || TypeParsers.CanParse(obj)) return new VkvSingleNode(obj);
|
||||
else if (type.IsPointer) throw new("Cannot serialize a pointer.");
|
||||
|
||||
VkvTreeNode tree = new();
|
||||
|
||||
if (obj is IVkvConvertible vkv) return vkv.ToNodeTree();
|
||||
else if (obj is IDictionary dictionary)
|
||||
{
|
||||
object[] keys = new object[dictionary.Count],
|
||||
values = new object[dictionary.Count];
|
||||
dictionary.Keys.CopyTo(keys, 0);
|
||||
dictionary.Values.CopyTo(values, 0);
|
||||
for (int i = 0; i < dictionary.Count; i++)
|
||||
{
|
||||
tree[SerializeObject(keys.GetValue(i))!] = ToNodeTree(values.GetValue(i), options);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
else if (obj is ICollection enumerable)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (object item in enumerable)
|
||||
{
|
||||
tree[SerializeObject(index)!] = ToNodeTree(item, options);
|
||||
index++;
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
IEnumerable<FieldInfo> validFields = from field in type.GetFields()
|
||||
let isPublic = field.IsPublic
|
||||
let isStatic = field.IsStatic
|
||||
let isIgnored = field.CustomAttributes.Any(x =>
|
||||
x.AttributeType == typeof(VkvIgnoreAttribute))
|
||||
let isConst = field.IsLiteral
|
||||
where isPublic && !isStatic && !isIgnored && !isConst
|
||||
select field;
|
||||
|
||||
IEnumerable<PropertyInfo> validProperties;
|
||||
if (options.serializeProperties)
|
||||
{
|
||||
validProperties = from prop in type.GetProperties()
|
||||
let canGet = prop.GetMethod is not null
|
||||
let isPublic = canGet && prop.GetMethod!.IsPublic
|
||||
let isStatic = canGet && prop.GetMethod!.IsStatic
|
||||
let isIgnored = prop.CustomAttributes.Any(x =>
|
||||
x.AttributeType == typeof(VkvIgnoreAttribute))
|
||||
where canGet && isPublic && !isStatic && !isIgnored
|
||||
select prop;
|
||||
}
|
||||
else validProperties = Array.Empty<PropertyInfo>();
|
||||
|
||||
foreach (FieldInfo field in validFields)
|
||||
{
|
||||
VkvKeyNameAttribute? namingAttribute = field.GetCustomAttribute<VkvKeyNameAttribute>();
|
||||
tree[namingAttribute?.name ?? field.Name] = ToNodeTree(field.GetValue(obj), options);
|
||||
}
|
||||
foreach (PropertyInfo prop in validProperties)
|
||||
{
|
||||
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
|
||||
}
|
||||
3
SrcMod/Valve.NET/Vkv/VkvNode.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Valve.Vkv;
|
||||
|
||||
public abstract class VkvNode { }
|
||||
27
SrcMod/Valve.NET/Vkv/VkvOptions.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace Valve.Vkv;
|
||||
|
||||
public record class VkvOptions
|
||||
{
|
||||
public static VkvOptions Default => new();
|
||||
|
||||
public bool closeWhenFinished;
|
||||
public int indentSize;
|
||||
public bool noExceptions;
|
||||
public bool resetStreamPosition;
|
||||
public bool serializeProperties;
|
||||
public SpacingMode spacing;
|
||||
public bool useEscapeCodes;
|
||||
public bool useQuotes;
|
||||
|
||||
public VkvOptions()
|
||||
{
|
||||
closeWhenFinished = true;
|
||||
indentSize = 4;
|
||||
noExceptions = false;
|
||||
resetStreamPosition = false;
|
||||
serializeProperties = true;
|
||||
spacing = SpacingMode.DoubleTab;
|
||||
useEscapeCodes = false;
|
||||
useQuotes = false;
|
||||
}
|
||||
}
|
||||
57
SrcMod/Valve.NET/Vkv/VkvSerializer.cs
Normal file
@ -0,0 +1,57 @@
|
||||
namespace Valve.Vkv;
|
||||
|
||||
public class VkvSerializer
|
||||
{
|
||||
public VkvOptions Options => p_options;
|
||||
|
||||
private readonly VkvOptions p_options;
|
||||
|
||||
public VkvSerializer() : this(VkvOptions.Default) { }
|
||||
public VkvSerializer(VkvOptions options)
|
||||
{
|
||||
p_options = options;
|
||||
}
|
||||
|
||||
public VkvNode? Deserialize(Stream stream)
|
||||
{
|
||||
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);
|
||||
return VkvConvert.FromNodeTree<T>(result, p_options);
|
||||
}
|
||||
public object? Deserialize(Type outputType, Stream stream)
|
||||
{
|
||||
VkvNode? result = Deserialize(stream);
|
||||
return VkvConvert.FromNodeTree(outputType, result, p_options);
|
||||
}
|
||||
|
||||
public void Serialize(Stream stream, object? value, string parentNodeName)
|
||||
{
|
||||
VkvNode? nodeTree = VkvConvert.ToNodeTree(value, p_options);
|
||||
Serialize(stream, nodeTree, parentNodeName);
|
||||
}
|
||||
public void Serialize(Stream stream, VkvNode? parentNode, string parentNodeName)
|
||||
{
|
||||
long pos = stream.Position;
|
||||
StreamWriter writer = new(stream, leaveOpen: !p_options.closeWhenFinished);
|
||||
VkvConvert.SerializeNode(writer, parentNode, parentNodeName, p_options);
|
||||
writer.Close();
|
||||
|
||||
if (!p_options.closeWhenFinished && p_options.resetStreamPosition) stream.Seek(pos, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
11
SrcMod/Valve.NET/Vkv/VkvSingleNode.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Valve.Vkv;
|
||||
|
||||
public class VkvSingleNode : VkvNode
|
||||
{
|
||||
public object? value;
|
||||
|
||||
public VkvSingleNode(object? value = null) : base()
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
45
SrcMod/Valve.NET/Vkv/VkvTreeNode.cs
Normal file
@ -0,0 +1,45 @@
|
||||
namespace Valve.Vkv;
|
||||
|
||||
public class VkvTreeNode : VkvNode, IEnumerable<KeyValuePair<string, VkvNode?>>
|
||||
{
|
||||
public int SubNodeCount => p_subNodes.Count;
|
||||
|
||||
private readonly Dictionary<string, VkvNode?> p_subNodes;
|
||||
|
||||
public VkvTreeNode(Dictionary<string, VkvNode?>? subNodes = null) : base()
|
||||
{
|
||||
p_subNodes = subNodes ?? new();
|
||||
}
|
||||
|
||||
public VkvNode? this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (p_subNodes.TryGetValue(key, out VkvNode? value)) return value;
|
||||
else return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (p_subNodes.ContainsKey(key)) p_subNodes[key] = value;
|
||||
else p_subNodes.Add(key, value);
|
||||
}
|
||||
}
|
||||
public VkvNode? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (p_subNodes.Count >= index || index < 0) return null;
|
||||
return p_subNodes.Values.ElementAt(index);
|
||||
}
|
||||
set
|
||||
{
|
||||
if (p_subNodes.Count >= index || index < 0) throw new IndexOutOfRangeException();
|
||||
p_subNodes[p_subNodes.Keys.ElementAt(index)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(string key, VkvNode? value) => this[key] = value;
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
public IEnumerator<KeyValuePair<string, VkvNode?>> GetEnumerator() => p_subNodes.GetEnumerator();
|
||||
}
|
||||
Synchronize this file with the other TypeParsers class in the shell.
Synchronize this file with the other TypeParsers class in the shell.
This has been completed now.
This has been completed now.