mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-22 03:07:04 +02:00
feat: rename cli
This commit is contained in:
1 parent
157fa0c1dd
commit
74a8df6842
18 files changed
+1419
-33
No files matched your search
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../submodules/CUE4Parse/CUE4Parse/CUE4Parse.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,26 @@
|
||||
using BlitzKit.CLI.Models;
|
||||
|
||||
namespace BlitzKit.CLI.Functions
|
||||
{
|
||||
public static class Mangler
|
||||
{
|
||||
public static void Mangle()
|
||||
{
|
||||
BlitzProvider provider = new();
|
||||
|
||||
foreach (var nation in provider.RootDirectory.GetDirectory("Blitz/Content/Tanks").Directories)
|
||||
{
|
||||
if (nation.Key == "TankStub")
|
||||
continue;
|
||||
|
||||
foreach (var tank in nation.Value.Directories)
|
||||
{
|
||||
if (!tank.Value.HasFile($"DA_{tank.Key}_Attributes.uasset"))
|
||||
{
|
||||
Console.WriteLine($"{nation.Key}/{tank.Key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using System.Text.Json;
|
||||
using BlitzKit.CLI.Models;
|
||||
using BlitzKit.CLI.Utils;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.UE4.Pak;
|
||||
using CUE4Parse.UE4.Readers;
|
||||
using DotNet.Globbing;
|
||||
|
||||
namespace BlitzKit.CLI.Functions
|
||||
{
|
||||
public static class Unpacker
|
||||
{
|
||||
private const string CONTENT_GROUP = "p14y73c7_p5e_b10gg3RS_big_play";
|
||||
private const string VFS_PATH = "../../temp/vfs/";
|
||||
|
||||
// this may be different from the one provided in args so check this and not args[1]
|
||||
// we don't wanna delete my local installation of the game when debugging haha!
|
||||
private const string TEMP_DEPOT_DIR = "../../temp/depot/";
|
||||
private const string WG_DLC_DOMAIN = "http://dl-wotblitz-gc.wargaming.net";
|
||||
private const string BUILD_MANIFEST_OS = "Windows";
|
||||
|
||||
public static async Task Unpack(string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
throw new ArgumentException("Missing required initial paks directory");
|
||||
}
|
||||
|
||||
UnpackDepot(args[1]);
|
||||
await UnpackDLC();
|
||||
}
|
||||
|
||||
private static async Task UnpackDLC()
|
||||
{
|
||||
string defaultDlcPath = Path.Combine(VFS_PATH, "Blitz/Config/DefaultDlc.ini");
|
||||
IniParser iniParser = new(defaultDlcPath);
|
||||
string? contentBuildId = iniParser.GetString("DefaultContentBuildId");
|
||||
|
||||
if (string.IsNullOrEmpty(contentBuildId))
|
||||
{
|
||||
throw new Exception("Failed to read content build id from DefaultDlc.ini");
|
||||
}
|
||||
|
||||
Console.WriteLine($"Exploring DLC \"{contentBuildId}\" within group \"{CONTENT_GROUP}\"");
|
||||
|
||||
DlcManifest manifest = await FetchManifest(CONTENT_GROUP, contentBuildId);
|
||||
|
||||
List<PakFile> nonHdPaks = [];
|
||||
List<PakFile> hdPaks = [];
|
||||
|
||||
Console.WriteLine("Filtering HD and non-HD containers");
|
||||
|
||||
foreach (var pakFile in manifest.PakFiles)
|
||||
{
|
||||
long chunkId = pakFile.ChunkId;
|
||||
manifest.ChunkTags.TryGetValue(chunkId.ToString(), out var tags);
|
||||
|
||||
if (tags == null)
|
||||
{
|
||||
throw new Exception($"Failed to find tags for chunk {chunkId}");
|
||||
}
|
||||
|
||||
bool isHd = tags.GameplayTags.Any(tag => tag.TagName == "Dlc.HdTextures");
|
||||
|
||||
if (isHd)
|
||||
{
|
||||
hdPaks.Add(pakFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
nonHdPaks.Add(pakFile);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$"Found {nonHdPaks.Count} non-HD containers and {hdPaks.Count} HD containers"
|
||||
);
|
||||
|
||||
Console.WriteLine($"Exergizing non-HD containers");
|
||||
|
||||
foreach (var pak in nonHdPaks)
|
||||
{
|
||||
await ExergizeManifestPakFile(pak);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Exergizing HD containers");
|
||||
|
||||
foreach (var pak in hdPaks)
|
||||
{
|
||||
await ExergizeManifestPakFile(pak);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ExergizeManifestPakFile(PakFile pak)
|
||||
{
|
||||
Console.WriteLine($"Exergizing DLC container \"{pak.FileName}\"");
|
||||
|
||||
string url = $"{WG_DLC_DOMAIN}/dlc/{CONTENT_GROUP}/dlc/{pak.RelativeUrl}";
|
||||
|
||||
using HttpClient httpClient = new();
|
||||
Stream httpStream = await httpClient.GetStreamAsync(url);
|
||||
MemoryStream memoryStream = new();
|
||||
|
||||
await httpStream.CopyToAsync(memoryStream);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
FStreamArchive archive = new(url, memoryStream);
|
||||
PakFileReader reader = new(archive);
|
||||
|
||||
Exergize(reader);
|
||||
|
||||
memoryStream.Dispose();
|
||||
httpStream.Dispose();
|
||||
archive.Dispose();
|
||||
}
|
||||
|
||||
public static async Task<DlcManifest> FetchManifest(string group, string build)
|
||||
{
|
||||
string url =
|
||||
$"{WG_DLC_DOMAIN}/dlc/{group}/dlc/BuildManifest-{BUILD_MANIFEST_OS}-{build}.json";
|
||||
using HttpClient client = new();
|
||||
string jsonString = await client.GetStringAsync(url);
|
||||
DlcManifest manifest =
|
||||
JsonSerializer.Deserialize<DlcManifest>(jsonString)
|
||||
?? throw new Exception("Failed to deserialize manifest");
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private static void UnpackDepot(string workingDirectory)
|
||||
{
|
||||
string paksDirectory = Path.Combine(workingDirectory, "Blitz/Content/Paks");
|
||||
string[] containers = Directory.GetFiles(paksDirectory, "*.pak");
|
||||
|
||||
foreach (string container in containers)
|
||||
{
|
||||
Console.WriteLine($"Exergizing disk container \"{container}\"");
|
||||
|
||||
PakFileReader reader = new(container);
|
||||
Exergize(reader);
|
||||
}
|
||||
|
||||
if (Directory.Exists(TEMP_DEPOT_DIR))
|
||||
{
|
||||
Console.WriteLine("Deleting temp depots directory");
|
||||
Directory.Delete(TEMP_DEPOT_DIR, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Exergize(PakFileReader reader)
|
||||
{
|
||||
reader.Mount();
|
||||
|
||||
foreach (var file in reader.Files)
|
||||
{
|
||||
GameFile gameFile = file.Value;
|
||||
bool exergizable = ShouldExergize(gameFile.Path);
|
||||
|
||||
if (!exergizable)
|
||||
{
|
||||
Console.WriteLine($"Exergize 🔴 {gameFile.Path}");
|
||||
continue;
|
||||
}
|
||||
|
||||
string writePath = Path.Combine(VFS_PATH, gameFile.Path);
|
||||
|
||||
if (File.Exists(writePath))
|
||||
{
|
||||
Console.WriteLine($"Exergize 🟡 {gameFile.Path}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Exergize 🟢 {gameFile.Path}");
|
||||
}
|
||||
|
||||
string? directory = Path.GetDirectoryName(writePath);
|
||||
|
||||
if (directory != null && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
File.WriteAllBytes(writePath, gameFile.Read());
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string[] ExergizableGlobsRaw =
|
||||
[
|
||||
"Blitz/Config/DefaultDlc.ini",
|
||||
"Blitz/Content/Tanks/**",
|
||||
"Blitz/Content/TankSets/*",
|
||||
];
|
||||
private static readonly Glob[] ExergizableGlobs = [.. ExergizableGlobsRaw.Select(Glob.Parse)];
|
||||
|
||||
private static bool ShouldExergize(string path)
|
||||
{
|
||||
// return true;
|
||||
return ExergizableGlobs.Any(glob => glob.IsMatch(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using CUE4Parse.FileProvider;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
|
||||
namespace BlitzKit.CLI.Models
|
||||
{
|
||||
public class BlitzProvider : DefaultFileProvider
|
||||
{
|
||||
private static readonly string LOCAL_USER = "coola";
|
||||
private static readonly string LOCAL_DLC_CONTAINERS_PATH =
|
||||
$"C:/Users/{LOCAL_USER}/AppData/Local/Blitz/Saved/PersistentDownloadDir/Dlc/PakCache";
|
||||
private static readonly string LOCAL_BUNDLED_CONTAINERS_PATH =
|
||||
"C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz Playtest/Blitz/Content/Paks";
|
||||
|
||||
public readonly VfsDirectory RootDirectory = new();
|
||||
|
||||
public BlitzProvider()
|
||||
: base(
|
||||
directory: new DirectoryInfo(LOCAL_BUNDLED_CONTAINERS_PATH),
|
||||
extraDirectories: [new(LOCAL_DLC_CONTAINERS_PATH)],
|
||||
searchOption: SearchOption.TopDirectoryOnly
|
||||
)
|
||||
{
|
||||
Initialize();
|
||||
Mount();
|
||||
|
||||
foreach (var file in Files)
|
||||
{
|
||||
var gameFile = file.Value;
|
||||
var path = gameFile.Path.Split('/');
|
||||
var lastPathSegment = path.Last();
|
||||
var directory = RootDirectory;
|
||||
|
||||
foreach (var segment in path)
|
||||
{
|
||||
bool isLastSegment = lastPathSegment == segment;
|
||||
|
||||
if (isLastSegment)
|
||||
{
|
||||
directory.AddFile(segment, gameFile);
|
||||
}
|
||||
else if (directory.HasDirectory(segment))
|
||||
{
|
||||
directory = directory.GetDirectory(segment);
|
||||
}
|
||||
else
|
||||
{
|
||||
VfsDirectory newDirectory = new();
|
||||
directory.AddDirectory(segment, newDirectory);
|
||||
directory = newDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class VfsDirectory
|
||||
{
|
||||
public Dictionary<string, VfsDirectory> Directories = [];
|
||||
public Dictionary<string, GameFile> Files = [];
|
||||
|
||||
public void AddFile(string name, GameFile file) => Files.TryAdd(name, file);
|
||||
|
||||
public bool HasFile(string name) => Files.ContainsKey(name);
|
||||
|
||||
public GameFile GetFile(string name) => Files[name];
|
||||
|
||||
public bool HasDirectory(string name) => Directories.ContainsKey(name);
|
||||
|
||||
public VfsDirectory GetDirectory(string name)
|
||||
{
|
||||
var segments = name.Split('/');
|
||||
var directory = this;
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
directory = directory.GetImmediateDirectory(segment);
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private VfsDirectory GetImmediateDirectory(string name) => Directories[name];
|
||||
|
||||
public void AddDirectory(string name, VfsDirectory directory) =>
|
||||
Directories.Add(name, directory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BlitzKit.CLI.Models
|
||||
{
|
||||
public partial class DlcManifest
|
||||
{
|
||||
[JsonPropertyName("contentBuildId")]
|
||||
public string ContentBuildId { get; set; }
|
||||
|
||||
[JsonPropertyName("pakFiles")]
|
||||
public List<PakFile> PakFiles { get; set; }
|
||||
|
||||
[JsonPropertyName("chunkTags")]
|
||||
public Dictionary<string, ChunkTag> ChunkTags { get; set; }
|
||||
|
||||
[JsonPropertyName("chunkDependencies")]
|
||||
public List<ChunkDependency> ChunkDependencies { get; set; }
|
||||
}
|
||||
|
||||
public partial class ChunkDependency
|
||||
{
|
||||
[JsonPropertyName("chunkId")]
|
||||
public long ChunkId { get; set; }
|
||||
|
||||
[JsonPropertyName("parentChunkId")]
|
||||
public long ParentChunkId { get; set; }
|
||||
}
|
||||
|
||||
public partial class ChunkTag
|
||||
{
|
||||
[JsonPropertyName("gameplayTags")]
|
||||
public List<GameplayTag> GameplayTags { get; set; }
|
||||
}
|
||||
|
||||
public partial class GameplayTag
|
||||
{
|
||||
[JsonPropertyName("tagName")]
|
||||
public string TagName { get; set; }
|
||||
}
|
||||
|
||||
public partial class PakFile
|
||||
{
|
||||
[JsonPropertyName("fileName")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[JsonPropertyName("fileSize")]
|
||||
public long FileSize { get; set; }
|
||||
|
||||
[JsonPropertyName("fileVersion")]
|
||||
public string FileVersion { get; set; }
|
||||
|
||||
[JsonPropertyName("chunkId")]
|
||||
public long ChunkId { get; set; }
|
||||
|
||||
[JsonPropertyName("relativeUrl")]
|
||||
public string RelativeUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("availabilityMask")]
|
||||
public long AvailabilityMask { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace BlitzKit.CLI.Models
|
||||
{
|
||||
public class LocalManifestPakFile
|
||||
{
|
||||
public long FileSize { get; set; }
|
||||
public required string FileHash { get; set; }
|
||||
}
|
||||
|
||||
public class LocalManifest
|
||||
{
|
||||
public required Dictionary<string, LocalManifestPakFile> PakFiles { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using BlitzKit.CLI.Functions;
|
||||
using BlitzKit.CLI.Utils;
|
||||
|
||||
namespace BlitzKit.CLI
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
await AgnosticHelpers.Initialize();
|
||||
|
||||
switch (args[0])
|
||||
{
|
||||
case "unpack":
|
||||
{
|
||||
await Unpacker.Unpack(args);
|
||||
break;
|
||||
}
|
||||
|
||||
case "mangle":
|
||||
{
|
||||
Mangler.Mangle();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new ArgumentException("Invalid command");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using CUE4Parse.Compression;
|
||||
|
||||
namespace BlitzKit.CLI.Utils
|
||||
{
|
||||
public static class AgnosticHelpers
|
||||
{
|
||||
public const string BINARY_DIR = "../../temp/";
|
||||
public const string ZLIB_BINARY_URL_PREFIX =
|
||||
"https://github.com/NotOfficer/Zlib-ng.NET/releases/download/1.0.0";
|
||||
public const string OODLE_BINARY_URL_PREFIX =
|
||||
"https://github.com/WorkingRobot/OodleUE/releases/download/2024-11-01-726";
|
||||
|
||||
public static async Task Initialize()
|
||||
{
|
||||
if (!Directory.Exists(BINARY_DIR))
|
||||
{
|
||||
Directory.CreateDirectory(BINARY_DIR);
|
||||
}
|
||||
|
||||
InitializeZlib();
|
||||
await InitializeOodle();
|
||||
}
|
||||
|
||||
public static async Task InitializeOodle()
|
||||
{
|
||||
string url;
|
||||
string extension;
|
||||
string entry;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
url = $"{OODLE_BINARY_URL_PREFIX}/msvc.zip";
|
||||
entry = "bin/Release/oodle-data-shared.dll";
|
||||
extension = "dll";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
url = $"{OODLE_BINARY_URL_PREFIX}/gcc.zip";
|
||||
entry = "lib/Release/liboodle-data-shared.so";
|
||||
extension = "so";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
string fileName = $"oodle.{extension}";
|
||||
string filePath = Path.Combine(BINARY_DIR, fileName);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
Console.WriteLine($"Oodle found at {filePath}; using that");
|
||||
OodleHelper.Initialize(filePath);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$"Oodle not found; downloading to {filePath} from {url} and extracting {entry}"
|
||||
);
|
||||
|
||||
HttpClient client = new();
|
||||
using var response = await client.GetAsync(url).ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
await using var responseStream = await response
|
||||
.Content.ReadAsStreamAsync()
|
||||
.ConfigureAwait(false);
|
||||
using var zip = new ZipArchive(responseStream, ZipArchiveMode.Read);
|
||||
var zipEntry = zip.GetEntry(entry);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(zipEntry, "oodle entry in zip not found");
|
||||
|
||||
await using var entryStream = zipEntry.Open();
|
||||
await using var fs = File.Create(filePath);
|
||||
await entryStream.CopyToAsync(fs).ConfigureAwait(false);
|
||||
|
||||
OodleHelper.Initialize(filePath);
|
||||
}
|
||||
|
||||
public static void InitializeZlib()
|
||||
{
|
||||
string url;
|
||||
string extension;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
url = $"{ZLIB_BINARY_URL_PREFIX}/zlib-ng2.dll";
|
||||
extension = "dll";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
url = $"{ZLIB_BINARY_URL_PREFIX}/libz-ng.so";
|
||||
extension = "so";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
string fileName = $"zlib.{extension}";
|
||||
string filePath = Path.Combine(BINARY_DIR, fileName);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
Console.WriteLine($"Zlib found at {filePath}; using that");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Zlib not found; downloading to {filePath} from {url}");
|
||||
ZlibHelper.DownloadDll(filePath, url);
|
||||
}
|
||||
|
||||
ZlibHelper.Initialize(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
namespace BlitzKit.CLI.Utils
|
||||
{
|
||||
public class ExplicitlySizedStream(Stream stream, long length) : Stream
|
||||
{
|
||||
private readonly Stream _stream = stream;
|
||||
private readonly long _length = length;
|
||||
private long _position = 0;
|
||||
|
||||
public override bool CanRead => _stream.CanRead;
|
||||
public override bool CanSeek => _stream.CanSeek;
|
||||
public override bool CanWrite => _stream.CanWrite;
|
||||
|
||||
public override long Length => _length;
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set => _position = value;
|
||||
}
|
||||
|
||||
public override void Flush() => _stream.Flush();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_position >= _length)
|
||||
return 0; // End of stream
|
||||
var bytesRead = _stream.Read(buffer, offset, count);
|
||||
_position += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
long targetPosition = origin switch
|
||||
{
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.Current => _position + offset,
|
||||
SeekOrigin.End => _length + offset,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(origin), "Invalid SeekOrigin value."),
|
||||
};
|
||||
|
||||
if (targetPosition < 0 || targetPosition > _length)
|
||||
throw new ArgumentOutOfRangeException(nameof(offset), "Seek position is out of range.");
|
||||
|
||||
// Simulate seek by discarding data up to the target position
|
||||
if (targetPosition != _position)
|
||||
{
|
||||
if (targetPosition < _position)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Backward seeking is not supported for non-seekable streams."
|
||||
);
|
||||
}
|
||||
|
||||
var toSkip = targetPosition - _position;
|
||||
Skip(toSkip);
|
||||
_position = targetPosition;
|
||||
}
|
||||
|
||||
return _position;
|
||||
}
|
||||
|
||||
private void Skip(long bytes)
|
||||
{
|
||||
byte[] buffer = new byte[8192];
|
||||
while (bytes > 0)
|
||||
{
|
||||
int toRead = (int)Math.Min(buffer.Length, bytes);
|
||||
int read = _stream.Read(buffer, 0, toRead);
|
||||
if (read == 0)
|
||||
throw new EndOfStreamException();
|
||||
bytes -= read;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_stream.Write(buffer, offset, count);
|
||||
_position += count;
|
||||
}
|
||||
|
||||
// public override void Flush() => _stream.Flush();
|
||||
|
||||
// public override int Read(byte[] buffer, int offset, int count) =>
|
||||
// _stream.Read(buffer, offset, count);
|
||||
|
||||
// public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin);
|
||||
|
||||
// public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
// public override void Write(byte[] buffer, int offset, int count) =>
|
||||
// _stream.Write(buffer, offset, count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
namespace BlitzKit.CLI.Utils
|
||||
{
|
||||
public class IniParser
|
||||
{
|
||||
private readonly Dictionary<string, string> strings = [];
|
||||
private readonly Dictionary<string, List<string>> arrays = [];
|
||||
|
||||
public IniParser(string file)
|
||||
{
|
||||
string content = File.ReadAllText(file);
|
||||
|
||||
foreach (string line in content.Split('\n'))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
string[] parts = line.Split('=');
|
||||
if (parts.Length < 2)
|
||||
continue;
|
||||
|
||||
string key = parts[0].Trim();
|
||||
string value = string.Join("=", parts, 1, parts.Length - 1).Trim();
|
||||
|
||||
if (key.StartsWith('+'))
|
||||
{
|
||||
string keySuffix = key[1..];
|
||||
|
||||
if (arrays.TryGetValue(keySuffix, out List<string>? listValue))
|
||||
{
|
||||
listValue.Add(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
arrays[keySuffix] = [value];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Overwrite existing key instead of throwing an exception
|
||||
strings[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string? GetString(string key) =>
|
||||
strings.TryGetValue(key, out string? value) ? value : null;
|
||||
|
||||
public List<string>? GetArray(string key) =>
|
||||
arrays.TryGetValue(key, out List<string>? value) ? value : null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user