diff --git a/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFG.cs b/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFG.cs
index f944d6c..0171732 100644
--- a/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFG.cs
+++ b/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFG.cs
@@ -12,6 +12,176 @@ namespace SteamShared.SourceConfig
///
/// Gets or sets a list of concommands or convars in this CFG-file.
///
- public List Commands { get; set; }
+ public List? Commands { get; set; }
+
+ public static SourceCFG? FromFile(string path)
+ {
+ if (!File.Exists(path))
+ {
+ return null;
+ }
+
+ var config = new SourceCFG();
+ var commands = new List();
+
+ using (var fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ {
+ fs.Position = 0;
+
+ int curByte;
+ int nextByte = 0x20; // Placeholder value
+ char curChar = ' ';
+ char nextChar;
+
+ bool wasInQuoted = false;
+ bool inQuoted = false;
+ bool inComment = false;
+ bool cmdNameParsed = false;
+ bool endOfFile = false;
+
+ while (true)
+ {
+ // Start of the file and every new command or line (after line break or unquoted semicolon)
+ var cmd = new SourceCFGCommand();
+ StringBuilder cmdString = new StringBuilder();
+ StringBuilder valueString = new StringBuilder();
+
+ if (endOfFile)
+ // end of stream
+ break;
+
+ while (true)
+ {
+ // in this loop breaking means going into next line or next command
+ // continuing means going to next character
+ // READ NEXT CHAR
+
+ curByte = nextByte < 0 ? -1 : fs.ReadByte();
+
+ try
+ {
+ curChar = Convert.ToChar(curByte);
+ }
+ catch
+ { }
+
+ nextByte = fs.ReadByte();
+
+ // Since we just sneakily peeked a byte, move it back a notch
+ if (fs.Position > 0)
+ fs.Position--;
+ else
+ {
+ // File is probably empty, because the first ReadByte didn't advance the stream position
+ endOfFile = true;
+ break;
+ }
+
+ try
+ {
+ nextChar = Convert.ToChar(nextByte);
+ }
+ catch
+ {
+ // Probably end of file after this one (AFAIK mostly negative numbers can't be parsed so it should be end of stream)
+ nextChar = ' ';
+ }
+
+ // PARSE NEXT CHAR... man I could've used regexes or some shit but nvm
+
+ if (inComment && !isEndOfLine(curChar, nextChar))
+ // Ends of lines come before this because those are the only ones that cannot be commented out
+ continue;
+
+ if (curByte < 1 || curChar == '\"' || (curChar == ';' || char.IsWhiteSpace(curChar) && !inQuoted) || isEndOfLine(curChar, nextChar))
+ {
+ // Newlines are also whitespace
+ // We've reached end of line, a gap between commands or just padding, a quote or the end of command, always break at newlines or semis
+
+ // We might be at the first unquoted space after a command as well, this would mean... oh yeah! we finally finished fetching that command name!
+ cmdNameParsed = true;
+
+ wasInQuoted = false;
+ if (curChar == '\"')
+ {
+ // Toggle quoted and continue with next character
+ wasInQuoted = inQuoted;
+ inQuoted = !inQuoted;
+ }
+
+ // Create and save current value, if quoted or not empty
+ if (curByte < 1 || wasInQuoted || !string.IsNullOrWhiteSpace(valueString.ToString()))
+ {
+ if (cmd.CommandValues == null)
+ // Only create it if there are values, otherwise it will be null, for example when executing commands that have no values
+ cmd.CommandValues = new List();
+
+ var newVal = new SourceCFGCommandValue();
+ newVal.Value = valueString.ToString();
+ cmd.CommandValues.Add(newVal);
+ valueString.Clear();
+
+ if (curByte < 1)
+ {
+ // end of stream or null byte
+ endOfFile = true;
+ break;
+ }
+ }
+
+ if (curChar == ';' || isEndOfLine(curChar, nextChar))
+ {
+ if (isEndOfLine(curChar, nextChar) && curChar == '\r')
+ // Windows-Style line breaks need an extra character, so skip that one
+ fs.Position++;
+
+ // is line break or unquoted semicolon so go to next line,
+ // if it was just a different whitespace, leave it be
+ break;
+ }
+
+ // oh it was a different white space or a quote... so just skip it ig lmao
+ continue;
+ }
+
+ if (!inQuoted && curChar == '/' && nextChar == '/')
+ {
+ // We've encountered the beginning of a comment (double slashes inside of quoted values are allowed I guess)
+ inComment = true;
+ continue;
+ }
+
+ if (cmdNameParsed)
+ // We already have the command or convar, so just assume everything else is a value
+ valueString.Append(curChar);
+ else
+ // We still at the beginning of the line bro, awesome!
+ cmdString.Append(curChar);
+ }
+
+ // Here is the end of line or command separated by newlines or semicolons (It's late and I've misspelt this thrice)
+ // Just save the command name, the values should've been added already in zhe process of life Bruder
+ cmd.CommandName = cmdString.ToString();
+ cmdString.Clear(); // Prepare for next line, make it tidy and clean like a baby's bottom :)
+
+ if (!string.IsNullOrWhiteSpace(cmd.CommandName))
+ {
+ commands.Add(cmd);
+ }
+
+ inQuoted = false; // In case quotes were forgotten but end of line has been reached, just make sure. In the ideal case this should already be false
+ inComment = false; // We don't want to ignore everything in the file, only everything in that line
+ cmdNameParsed = false; // Next line will probably have another command so prepare dinner here ;)
+ }
+ }
+
+ config.Commands = commands;
+ return config;
+ }
+
+ private static bool isEndOfLine(char curChar, char nextChar)
+ {
+ return curChar == '\n' || (curChar == '\r' && nextChar == '\n');
+ }
}
}
diff --git a/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommand.cs b/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommand.cs
index 54d9993..a5b4b62 100644
--- a/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommand.cs
+++ b/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommand.cs
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace SteamShared.SourceConfig
{
- public class SourceCFGCommand : SourceCFGCommandBase
+ public class SourceCFGCommand
{
public string? CommandName { get; set; }
@@ -14,6 +14,19 @@ namespace SteamShared.SourceConfig
/// Gets or sets the values of this command.
/// This can be a value or another command, for example when using the 'alias' command.
///
- public List CommandValues { get; set; }
+ public List? CommandValues { get; set; }
+
+ ///
+ /// Gets all values joined with spaces,
+ /// use for example when having an echo command with multiple words that's missing quotes
+ ///
+ ///
+ public string? GetValuesAsOne()
+ {
+ if (this.CommandValues == null || this.CommandValues.Count < 1)
+ return null;
+
+ return string.Join(' ', this.CommandValues.Select(val => val.Value));
+ }
}
}
diff --git a/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommandBase.cs b/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommandBase.cs
deleted file mode 100644
index 360a22b..0000000
--- a/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommandBase.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SteamShared.SourceConfig
-{
- public class SourceCFGCommandBase
- {
- }
-}
diff --git a/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommandValue.cs b/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommandValue.cs
index 5cdc730..cd5c525 100644
--- a/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommandValue.cs
+++ b/SteamShared/SteamShared/SteamShared/SourceConfig/SourceCFGCommandValue.cs
@@ -6,8 +6,68 @@ using System.Threading.Tasks;
namespace SteamShared.SourceConfig
{
- public class SourceCFGCommandValue : SourceCFGCommandBase
+ public class SourceCFGCommandValue
{
public string? Value { get; set; }
+
+ public int? GetInt()
+ {
+ if (int.TryParse(this.Value, out int parsed))
+ return parsed;
+ else
+ return null;
+ }
+
+ public float? GetFloat()
+ {
+ if (float.TryParse(this.Value, out float parsed))
+ return parsed;
+ else
+ return null;
+ }
+
+ public List? GetInts()
+ {
+ if (this.Value == null)
+ return null;
+
+ var res = new List();
+
+ string[] values = this.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ for(int i = 0; i < values.Length; i++)
+ {
+ if(int.TryParse(values[i], out int parsed))
+ {
+ res.Add(parsed);
+ }
+ }
+
+ if(res.Count > 0)
+ return res;
+
+ return null;
+ }
+
+ public List? GetFloats()
+ {
+ if (this.Value == null)
+ return null;
+
+ var res = new List();
+
+ string[] values = this.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ for (int i = 0; i < values.Length; i++)
+ {
+ if (float.TryParse(values[i], out float parsed))
+ {
+ res.Add(parsed);
+ }
+ }
+
+ if (res.Count > 0)
+ return res;
+
+ return null;
+ }
}
}
diff --git a/SteamShared/SteamShared/SteamShared/SourceConfig/test_source.cfg b/SteamShared/SteamShared/SteamShared/SourceConfig/test_source.cfg
index c87b542..51428ff 100644
--- a/SteamShared/SteamShared/SteamShared/SourceConfig/test_source.cfg
+++ b/SteamShared/SteamShared/SteamShared/SourceConfig/test_source.cfg
@@ -4,4 +4,9 @@ test_command
// some comment
test_command2; test_command3 // another comment
test_cvar "test_cvar4 8; test_cvar5 9";test_command
-//test comment
\ No newline at end of file
+//test comment
+
+// first, set flags like inQuoted and incomment correctly
+// semicolon OR end line if \n or \r\n and in latter case advance stream position an extra byte
+// if a different whitespace and not inQuoted just skip it
+// if cmdString for command already fetched, and we have non-blank values, or were quoted up until now, add it to a new valuestring, otherwise add it to the cmdString
\ No newline at end of file
diff --git a/SteamShared/SteamShared/SteamShared/SteamShared.csproj b/SteamShared/SteamShared/SteamShared/SteamShared.csproj
index 5624e47..0146f25 100644
--- a/SteamShared/SteamShared/SteamShared/SteamShared.csproj
+++ b/SteamShared/SteamShared/SteamShared/SteamShared.csproj
@@ -13,5 +13,10 @@
+
+
+ Never
+
+