mirror of
https://github.com/MathiasLui/CSGO-Projects.git
synced 2025-06-23 23:01:18 +00:00
Add CFG-parser
This commit is contained in:
parent
4e450fc921
commit
e39bbcc61d
6 changed files with 258 additions and 17 deletions
|
@ -12,6 +12,176 @@ namespace SteamShared.SourceConfig
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of concommands or convars in this CFG-file.
|
/// Gets or sets a list of concommands or convars in this CFG-file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<SourceCFGCommand> Commands { get; set; }
|
public List<SourceCFGCommand>? Commands { get; set; }
|
||||||
|
|
||||||
|
public static SourceCFG? FromFile(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = new SourceCFG();
|
||||||
|
var commands = new List<SourceCFGCommand>();
|
||||||
|
|
||||||
|
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<SourceCFGCommandValue>();
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SteamShared.SourceConfig
|
namespace SteamShared.SourceConfig
|
||||||
{
|
{
|
||||||
public class SourceCFGCommand : SourceCFGCommandBase
|
public class SourceCFGCommand
|
||||||
{
|
{
|
||||||
public string? CommandName { get; set; }
|
public string? CommandName { get; set; }
|
||||||
|
|
||||||
|
@ -14,6 +14,19 @@ namespace SteamShared.SourceConfig
|
||||||
/// Gets or sets the values of this command.
|
/// Gets or sets the values of this command.
|
||||||
/// This can be a value or another command, for example when using the 'alias' command.
|
/// This can be a value or another command, for example when using the 'alias' command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<SourceCFGCommandBase> CommandValues { get; set; }
|
public List<SourceCFGCommandValue>? CommandValues { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all values joined with spaces,
|
||||||
|
/// use for example when having an echo command with multiple words that's missing quotes
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string? GetValuesAsOne()
|
||||||
|
{
|
||||||
|
if (this.CommandValues == null || this.CommandValues.Count < 1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return string.Join(' ', this.CommandValues.Select(val => val.Value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,8 +6,68 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SteamShared.SourceConfig
|
namespace SteamShared.SourceConfig
|
||||||
{
|
{
|
||||||
public class SourceCFGCommandValue : SourceCFGCommandBase
|
public class SourceCFGCommandValue
|
||||||
{
|
{
|
||||||
public string? Value { get; set; }
|
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<int?>? GetInts()
|
||||||
|
{
|
||||||
|
if (this.Value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var res = new List<int?>();
|
||||||
|
|
||||||
|
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<float?>? GetFloats()
|
||||||
|
{
|
||||||
|
if (this.Value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var res = new List<float?>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,4 +4,9 @@ test_command
|
||||||
// some comment
|
// some comment
|
||||||
test_command2; test_command3 // another comment
|
test_command2; test_command3 // another comment
|
||||||
test_cvar "test_cvar4 8; test_cvar5 9";test_command
|
test_cvar "test_cvar4 8; test_cvar5 9";test_command
|
||||||
//test comment
|
//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
|
|
@ -13,5 +13,10 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="SourceConfig\test_source.cfg">
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
Loading…
Add table
Reference in a new issue