Skip to content

Commit

Permalink
net 9, some other updates
Browse files Browse the repository at this point in the history
  • Loading branch information
diogotr7 committed Nov 23, 2024
1 parent 99bf7dc commit cbf73e1
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 118 deletions.
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<IsAotCompatible>true</IsAotCompatible>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
Expand Down
10 changes: 1 addition & 9 deletions src/StarBreaker.FileSystem/StarBreaker.FileSystem.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk" />
168 changes: 93 additions & 75 deletions src/StarBreaker.P4k/P4kFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,37 @@
using System.IO.Enumeration;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Channels;
using StarBreaker.Common;
using ZstdSharp;

namespace StarBreaker.P4k;

public sealed class P4kFile
{
private static readonly byte[] _key =
[
0x5E, 0x7A, 0x20, 0x02,
0x30, 0x2E, 0xEB, 0x1A,
0x3B, 0xB6, 0x17, 0xC3,
0x0F, 0xDE, 0x1E, 0x47
];

private readonly Aes _aes;

public Dictionary<string, ZipEntry> Entries { get; }
public ZipEntry[] Entries { get; }
public string P4KPath { get; }
public ZipNode Root { get; }

private P4kFile(string path, Dictionary<string, ZipEntry> entries, ZipNode root)
private P4kFile(string path, ZipEntry[] entries, ZipNode root)
{
P4KPath = path;
Root = root;
Entries = entries;

_aes = Aes.Create();
_aes.Key = _key;
_aes.IV = new byte[16];
_aes.Mode = CipherMode.CBC;
_aes.Padding = PaddingMode.Zeros;
_aes.IV = new byte[16];
_aes.Key =
[
0x5E, 0x7A, 0x20, 0x02,
0x30, 0x2E, 0xEB, 0x1A,
0x3B, 0xB6, 0x17, 0xC3,
0x0F, 0xDE, 0x1E, 0x47
];
}

public static P4kFile FromFile(string filePath, IProgress<double>? progress = null)
Expand Down Expand Up @@ -66,102 +65,121 @@ public static P4kFile FromFile(string filePath, IProgress<double>? progress = nu
var reportInterval = (int)Math.Max(eocd64.TotalEntries / 50, 1);
reader.BaseStream.Seek((long)eocd64.CentralDirectoryOffset, SeekOrigin.Begin);

var dict = new Dictionary<string, ZipEntry>(eocd.TotalEntries);
var root = new ZipNode("_root_");
var entries = new ZipEntry[eocd64.TotalEntries];

var channel = Channel.CreateUnbounded<ZipEntry>();

var channelInsertTask = Task.Run(async () =>
{
var fileSystem = new ZipNode("_root_");

await foreach (var entry in channel.Reader.ReadAllAsync())
{
fileSystem.Insert(entry);
}

return fileSystem;
});

for (var i = 0; i < (int)eocd64.TotalEntries; i++)
{
var entry = ReadEntry();
dict[entry.Name] = entry;
root.Insert(entry);
var entry = ReadEntry(reader);
entries[i] = entry;
channel.Writer.TryWrite(entry);

if (i % reportInterval == 0)
progress?.Report(i / (double)eocd64.TotalEntries);
}

channel.Writer.Complete();

channelInsertTask.Wait();
var fileSystem = channelInsertTask.Result;

progress?.Report(1);
return new P4kFile(filePath, dict, root);
return new P4kFile(filePath, entries, fileSystem);
}

ZipEntry ReadEntry()
private static ZipEntry ReadEntry(BinaryReader reader)
{
var header = reader.Read<CentralDirectoryFileHeader>();
var length = header.FileNameLength + header.ExtraFieldLength + header.FileCommentLength;
var rent = ArrayPool<byte>.Shared.Rent(length);
try
{
var header = reader.Read<CentralDirectoryFileHeader>();
var length = header.FileNameLength + header.ExtraFieldLength + header.FileCommentLength;
var rent = ArrayPool<byte>.Shared.Rent(length);
try
{
var bytes = rent.AsSpan(0, length);
var bytes = rent.AsSpan(0, length);

reader.BaseStream.ReadExactly(bytes);
reader.BaseStream.ReadExactly(bytes);

var reader2 = new SpanReader(bytes);
var reader2 = new SpanReader(bytes);

var fileName = reader2.ReadStringOfLength(header.FileNameLength);
ulong compressedSize = header.CompressedSize;
ulong uncompressedSize = header.UncompressedSize;
ulong localHeaderOffset = header.LocalFileHeaderOffset;
ulong diskNumberStart = header.DiskNumberStart;
var fileName = reader2.ReadStringOfLength(header.FileNameLength);
ulong compressedSize = header.CompressedSize;
ulong uncompressedSize = header.UncompressedSize;
ulong localHeaderOffset = header.LocalFileHeaderOffset;
ulong diskNumberStart = header.DiskNumberStart;

if (reader2.ReadUInt16() != 1)
throw new Exception("Invalid version needed to extract");
if (reader2.ReadUInt16() != 1)
throw new Exception("Invalid version needed to extract");

var zip64HeaderSize = reader2.ReadUInt16();
var zip64HeaderSize = reader2.ReadUInt16();

if (uncompressedSize == uint.MaxValue)
uncompressedSize = reader2.ReadUInt64();
if (uncompressedSize == uint.MaxValue)
uncompressedSize = reader2.ReadUInt64();

if (compressedSize == uint.MaxValue)
compressedSize = reader2.ReadUInt64();
if (compressedSize == uint.MaxValue)
compressedSize = reader2.ReadUInt64();

if (localHeaderOffset == uint.MaxValue)
localHeaderOffset = reader2.ReadUInt64();
if (localHeaderOffset == uint.MaxValue)
localHeaderOffset = reader2.ReadUInt64();

if (diskNumberStart == ushort.MaxValue)
diskNumberStart = reader2.ReadUInt32();
if (diskNumberStart == ushort.MaxValue)
diskNumberStart = reader2.ReadUInt32();

if (reader2.ReadUInt16() != 0x5000)
throw new Exception("Invalid extra field id");
if (reader2.ReadUInt16() != 0x5000)
throw new Exception("Invalid extra field id");

var extra0x5000Size = reader2.ReadUInt16();
reader2.Advance(extra0x5000Size - 4);
var extra0x5000Size = reader2.ReadUInt16();
reader2.Advance(extra0x5000Size - 4);

if (reader2.ReadUInt16() != 0x5002)
throw new Exception("Invalid extra field id");
if (reader2.ReadUInt16() != 6)
throw new Exception("Invalid extra field size");
if (reader2.ReadUInt16() != 0x5002)
throw new Exception("Invalid extra field id");
if (reader2.ReadUInt16() != 6)
throw new Exception("Invalid extra field size");

var isCrypted = reader2.ReadUInt16() == 1;
var isCrypted = reader2.ReadUInt16() == 1;

if (reader2.ReadUInt16() != 0x5003)
throw new Exception("Invalid extra field id");
if (reader2.ReadUInt16() != 0x5003)
throw new Exception("Invalid extra field id");

var extra0x5003Size = reader2.ReadUInt16();
reader2.Advance(extra0x5003Size - 4);
var extra0x5003Size = reader2.ReadUInt16();
reader2.Advance(extra0x5003Size - 4);

if (header.FileCommentLength != 0)
throw new Exception("File comment not supported");
if (header.FileCommentLength != 0)
throw new Exception("File comment not supported");

return new ZipEntry(
fileName,
compressedSize,
uncompressedSize,
header.CompressionMethod,
isCrypted,
localHeaderOffset,
header.LastModifiedTime,
header.LastModifiedDate
);
}
finally
{
ArrayPool<byte>.Shared.Return(rent);
}
return new ZipEntry(
fileName,
compressedSize,
uncompressedSize,
header.CompressionMethod,
isCrypted,
localHeaderOffset,
header.LastModifiedTime,
header.LastModifiedDate
);
}
finally
{
ArrayPool<byte>.Shared.Return(rent);
}
}

public void Extract(string outputDir, string? filter = null, IProgress<double>? progress = null)
{
var filteredEntries = (filter is null
? Entries.Values
: Entries.Values.Where(entry => FileSystemName.MatchesSimpleExpression(filter, entry.Name))).ToArray();
? Entries
: Entries.Where(entry => FileSystemName.MatchesSimpleExpression(filter, entry.Name))).ToArray();

var numberOfEntries = filteredEntries.Length;
var fivePercent = numberOfEntries / 20;
Expand Down
3 changes: 2 additions & 1 deletion src/StarBreaker.P4k/StarBreaker.P4k.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="ZstdSharp.Port" Version="0.8.1" />
<PackageReference Include="System.Threading.Channels" Version="9.0.0" />
<PackageReference Include="ZstdSharp.Port" Version="0.8.3" />
</ItemGroup>

<ItemGroup>
Expand Down
74 changes: 60 additions & 14 deletions src/StarBreaker.P4k/ZipNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,31 @@ public ZipNode(string name)
_zipEntry = null;
_children = [];
}
public ZipNode(ZipEntry file)

public ZipNode(ZipEntry file, string name)
{
_name = file.Name;
_name = name;
_zipEntry = file;
_children = _empty;
}

public void Insert(ZipEntry zipEntry)
{
Span<Range> ranges = stackalloc Range[20];

var current = this;
var name = zipEntry.Name.AsSpan();
var rangeCount = name.Split(ranges, '\\');

for (var index = 0; index < rangeCount; index++)

foreach (var range in name.Split('\\'))
{
var part = name[ranges[index]];
var part = name[range];
var partHashCode = string.GetHashCode(part);
if (index == rangeCount - 1)

if (range.End.Value == name.Length)
{
// If this is the last part, we're at the file
current._children[partHashCode] = new ZipNode(zipEntry);
current._children[partHashCode] = new ZipNode(zipEntry, part.ToString());
return;
}

if (!current._children.TryGetValue(partHashCode, out var value))
{
value = new ZipNode(part.ToString());
Expand All @@ -57,4 +54,53 @@ public void Insert(ZipEntry zipEntry)
current = value;
}
}
}

public IEnumerable<string> GetDirectories(string path)
{
Span<Range> ranges = stackalloc Range[20];
var span = path.AsSpan();
var partsCount = span.Split(ranges, '\\');
var current = this;

for (var index = 0; index < partsCount; index++)
{
var part = ranges[index];
if (!current._children.TryGetValue(string.GetHashCode(span[part]), out var value))
{
yield break;
}

current = value;
}

foreach (var child in current._children.Values.Where(x => x.ZipEntry == null))
{
yield return child.Name;
}
}

public IEnumerable<string> GetFiles(string path)
{
Span<Range> ranges = stackalloc Range[20];
var span = path.AsSpan();
var partsCount = span.Split(ranges, '\\');

var current = this;

for (var index = 0; index < partsCount; index++)
{
var part = ranges[index];
if (!current._children.TryGetValue(string.GetHashCode(span[part]), out var value))
{
yield break;
}

current = value;
}

foreach (var child in current._children.Values.Where(x => x.ZipEntry != null))
{
yield return child.Name;
}
}
}
4 changes: 0 additions & 4 deletions src/StarBreaker.Sandbox/StarBreaker.Sandbox.csproj
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
</PropertyGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/StarBreaker.Sandbox/TimeP4kExtract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ public static void Run()
{
var sw = Stopwatch.StartNew();
var p4kFile = P4kFile.FromFile(p4k);
Console.WriteLine($"Took {sw.ElapsedMilliseconds}ms to load {p4kFile.Entries.Count} entries");
Console.WriteLine($"Took {sw.ElapsedMilliseconds}ms to load {p4kFile.Entries.Length} entries");
}
}
6 changes: 0 additions & 6 deletions src/StarBreaker.Tests/StarBreaker.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="TUnit" Version="0.1.1099" />
</ItemGroup>
Expand Down
Loading

0 comments on commit cbf73e1

Please sign in to comment.