mirror of
https://github.com/MathiasLui/CSGO-Projects.git
synced 2025-06-23 14:51:19 +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>
|
||||
/// Gets or sets a list of concommands or convars in this CFG-file.
|
||||
/// </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
|
||||
{
|
||||
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.
|
||||
/// </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
|
||||
{
|
||||
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<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
|
||||
test_command2; test_command3 // another comment
|
||||
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>
|
||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="SourceConfig\test_source.cfg">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Loading…
Add table
Reference in a new issue