Skip to content

Commit

Permalink
поддержка пакетов в приложении командной строки
Browse files Browse the repository at this point in the history
  • Loading branch information
miegir committed Mar 6, 2024
1 parent 966daa5 commit edbd1a8
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 77 deletions.
31 changes: 23 additions & 8 deletions ShibuyaTools.Core/FileSource.cs
Expand Up @@ -24,24 +24,39 @@ public FileTarget CreateTarget(ProgressCallback<long> callback)

if (sourceDir != backupDir)
{
using var target = new FileTarget(backupPath);
using var source = File.OpenRead(sourcePath);
source.CopyTo(target.Stream, callback);
target.CopyFileInfo(sourcePath);
target.Commit();

Copy(sourcePath, backupPath, callback);
createBackupIfNotExists = false; // already backed up
}
}

return new(sourcePath, createBackupIfNotExists);
}

public void Unroll()
public void Unroll(ProgressCallback<long> callback)
{
if (File.Exists(backupPath))
{
File.Move(backupPath, sourcePath, overwrite: true);
var sourceDir = Path.GetDirectoryName(sourcePath);
var backupDir = Path.GetDirectoryName(backupPath);

// Only copy backup if the directory is different
if (sourceDir == backupDir)
{
File.Move(backupPath, sourcePath, overwrite: true);
}
else
{
Copy(backupPath, sourcePath, callback);
}
}
}

private static void Copy(string sourcePath, string targetPath, ProgressCallback<long> callback)
{
using var target = new FileTarget(targetPath);
using var source = File.OpenRead(sourcePath);
source.CopyTo(target.Stream, callback);
target.CopyFileInfo(sourcePath);
target.Commit();
}
}
35 changes: 35 additions & 0 deletions ShibuyaTools.Core/ProgressReporter.cs
@@ -0,0 +1,35 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;

namespace ShibuyaTools.Core;

public class ProgressReporter(ILogger logger)
{
private readonly Stopwatch stopwatch = Stopwatch.StartNew();

public void Restart() => stopwatch.Restart();

public void ReportProgress(ProgressPayload<long> progress)
{
if (stopwatch.Elapsed.TotalSeconds >= 1)
{
logger.LogDebug(
"written {count} of {total} ({progress:0.00}%)",
FormatLength(progress.Position),
FormatLength(progress.Total),
progress.Position * 100.0 / progress.Total);

stopwatch.Restart();
}
}

private static string FormatLength(float length)
{
if (length < 1024) return $"{length:0.00}B";
length /= 1024;
if (length < 1024) return $"{length:0.00}KB";
length /= 1024;
if (length < 1024) return $"{length:0.00}MB";
return $"{length:0.00}GB";
}
}
38 changes: 5 additions & 33 deletions ShibuyaTools.Resources.Wad/WadArchive.cs
Expand Up @@ -92,9 +92,9 @@ public void Save(ILogger logger)
Close();

logger.LogInformation("creating target...");
var stopwatch = Stopwatch.StartNew();
var scope = logger.BeginScope("creating target");
using var target = source.CreateTarget(ReportProgress);
var progressReporter = new ProgressReporter(logger);
using var target = source.CreateTarget(progressReporter.ReportProgress);
scope?.Dispose();
logger.LogInformation("writing header...");

Expand All @@ -106,7 +106,7 @@ public void Save(ILogger logger)

logger.LogInformation("writing content...");
scope = logger.BeginScope("writing content");
stopwatch.Restart();
progressReporter.Restart();

foreach (var file in targetFileSourceList)
{
Expand All @@ -130,44 +130,16 @@ public void Save(ILogger logger)
break;
}

if (stopwatch.Elapsed.TotalSeconds > 1)
{
ReportProgress(new ProgressPayload<long>(
progressReporter.ReportProgress(
progress: new ProgressPayload<long>(
Total: totalLength,
Position: target.Stream.Position));

stopwatch.Restart();
}
}

scope?.Dispose();
logger.LogDebug("writing wad done.");
Close();
target.Commit();

void ReportProgress(ProgressPayload<long> progress)
{
if (stopwatch.Elapsed.TotalSeconds > 1)
{
logger.LogDebug(
"written {count} of {total} ({progress:0.00}%)",
FormatLength(progress.Position),
FormatLength(progress.Total),
progress.Position * 100.0 / progress.Total);

stopwatch.Restart();
}
}
}

private static string FormatLength(float length)
{
if (length < 1024) return $"{length:0.00}B";
length /= 1024;
if (length < 1024) return $"{length:0.00}KB";
length /= 1024;
if (length < 1024) return $"{length:0.00}MB";
return $"{length:0.00}GB";
}

private Stream EnsureStream() => stream ??= source.OpenRead();
Expand Down
6 changes: 5 additions & 1 deletion ShibuyaTools.Resources.Wad/WadResource.cs
Expand Up @@ -116,7 +116,11 @@ public IEnumerable<Action> BeginUnroll()
yield return () =>
{
logger.LogInformation("unrolling {name}...", name);
source.Unroll();
using (logger.BeginScope("unrolling {name}", name))
{
var progressReporter = new ProgressReporter(logger);
source.Unroll(progressReporter.ReportProgress);
}
};
}
}
Expand Down
126 changes: 126 additions & 0 deletions ShibuyaTools/BundleHelper.cs
@@ -0,0 +1,126 @@
using System.Diagnostics;
using System.IO.Compression;
using System.IO.MemoryMappedFiles;
using System.Reflection;
using System.Runtime.CompilerServices;
using DotNext.IO.MemoryMappedFiles;

namespace ShibuyaTools;

// see https://github.com/dotnet/runtime/tree/main/src/installer/managed/Microsoft.NET.HostModel
internal static class BundleHelper
{
private static readonly byte[] BundleSignature =
{
// 32 bytes represent the bundle signature: SHA-256 for ".net core bundle"
0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae,
};

public static Stream? OpenBundle(string bundlePath, string filePath, string manifestResourceName)
{
using var memoryMappedFile = MemoryMappedFile.CreateFromFile(bundlePath, FileMode.Open, null, 0, MemoryMappedFileAccess.Read);

long headerOffset;
using (var accessor = memoryMappedFile.CreateMemoryAccessor(0, 0, MemoryMappedFileAccess.Read))
{
var index = accessor.Bytes.IndexOf(BundleSignature) - sizeof(long);
if (index < 0) return null;
headerOffset = Unsafe.ReadUnaligned<long>(ref accessor[index]);
}

using var stream = memoryMappedFile.CreateViewStream(0, 0, MemoryMappedFileAccess.Read);
using var reader = new BinaryReader(stream);

stream.Position = headerOffset;

var major = reader.ReadInt32();

if (major > 6)
{
throw new NotSupportedException($"Bundle version not supported: {major}.");
}

var minor = reader.ReadInt32();
var count = reader.ReadInt32();

_ = reader.ReadString();

if (major >= 2)
{
_ = reader.ReadInt64();
_ = reader.ReadInt64();
_ = reader.ReadInt64();
_ = reader.ReadInt64();
_ = reader.ReadInt64();
}

for (var i = 0; i < count; i++)
{
var offset = reader.ReadInt64();
var size = reader.ReadInt64();

var compressedSize = major >= 6
? reader.ReadInt64()
: 0;

var fileType = reader.ReadByte();
if (fileType != 1) // Assembly
{
continue;
}

var relativePath = reader.ReadString();

if (relativePath.Equals(filePath, StringComparison.OrdinalIgnoreCase))
{
return LoadAssembly(offset, size, compressedSize);
}
}

return null;

Stream? LoadAssembly(long offset, long size, long compressedSize)
{
stream.Position = offset;

var comressed = compressedSize > 0;

if (!comressed)
{
compressedSize = size;
}

if (compressedSize > int.MaxValue)
{
throw new NotSupportedException("Assembly too large.");
}

var bytes = reader.ReadBytes((int)compressedSize);

if (comressed)
{
using var assemblyStream = new MemoryStream(bytes);
using var compressedStream = new DeflateStream(assemblyStream, CompressionMode.Decompress);
using var uncompressedStream = new MemoryStream();

compressedStream.CopyTo(uncompressedStream);

bytes = uncompressedStream.ToArray();
}

var assembly = Assembly.Load(bytes);

var source = assembly.GetManifestResourceStream(manifestResourceName)
?? throw new FileNotFoundException($"Manifest resource '{manifestResourceName}' not found in the assembly '{filePath}'.");

var target = new MemoryStream();
source.CopyTo(target);
target.Position = 0;

return target;
}
}
}
18 changes: 9 additions & 9 deletions ShibuyaTools/CreateCommand.cs
Expand Up @@ -6,40 +6,40 @@

namespace ShibuyaTools;

[Command("create")]
[Command("create", Description = "Creates asset .zip bundle.")]
internal class CreateCommand(ILogger<CreateCommand> logger)
{
#nullable disable
[Required]
[FileExists]
[Option("-g|--game-path")]
[Option("-g|--game-path", Description = "Game executable path.")]
public string GamePath { get; }

[Required]
[DirectoryExists]
[Option("-s|--source-directory")]
[Option("-s|--source-directory", Description = "Directory containing source assets.")]
public string SourceDirectory { get; }

[Required]
[LegalFilePath]
[Option("-j|--object-directory")]
[Option("-j|--object-directory", Description = "Intermediate output directory.")]
public string ObjectDirectory { get; }

[Option("-b|--backup-directory")]
[Option("-b|--backup-directory", Description = "Game backup directory. Defaults to the game directory.")]
public string BackupDirectory { get; }

[Required]
[LegalFilePath]
[Option("-a|--archive-path")]
[Option("-a|--archive-path", Description = "Path to the created asset bundle.")]
public string ArchivePath { get; }

[Option("-f|--force")]
[Option("-f|--force", Description = "Overwrite unchanged files.")]
public bool Force { get; }

[Option("--force-objects")]
[Option("--force-objects", Description = "Overwrite unchanged intermediate files.")]
public bool ForceObjects { get; }

[Option("--force-pack")]
[Option("--force-pack", Description = "Overwrite unchanged bundle.")]
public bool ForcePack { get; }
#nullable restore

Expand Down
12 changes: 6 additions & 6 deletions ShibuyaTools/ExportCommand.cs
Expand Up @@ -6,27 +6,27 @@

namespace ShibuyaTools;

[Command("export")]
[Command("export", Description = "Exports game assets.")]
internal class ExportCommand(ILogger<ExportCommand> logger)
{
#nullable disable
[Required]
[FileExists]
[Option("-g|--game-path")]
[Option("-g|--game-path", Description = "Game executable path.")]
public string GamePath { get; }

[Required]
[LegalFilePath]
[Option("-e|--export-directory")]
[Option("-e|--export-directory", Description = "Directory to place exported assets into.")]
public string ExportDirectory { get; }

[Option("-b|--backup-directory")]
[Option("-b|--backup-directory", Description = "Game backup directory. Defaults to the game directory.")]
public string BackupDirectory { get; }

[Option("-f|--force")]
[Option("-f|--force", Description = "Overwrite existing files.")]
public bool Force { get; }

[Option("--force-export")]
[Option("--force-export", Description = "Overwrite exported assets.")]
public bool ForceExport { get; }
#nullable restore

Expand Down

0 comments on commit edbd1a8

Please sign in to comment.