Skip to content

Commit

Permalink
Fix RLE compression bug and add metafile option in compress command
Browse files Browse the repository at this point in the history
  • Loading branch information
jianmingyong committed Aug 15, 2023
1 parent 9a24750 commit 90393f5
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,41 +83,6 @@ protected override void Decompress(BinaryReader reader, BinaryWriter writer, Str

protected override void Compress(BinaryReader reader, BinaryWriter writer, MemoryStream inputStream, MemoryStream outputStream)
{
int GetNextRepeatCount(Span<byte> buffer)
{
if (inputStream.Position >= inputStream.Length) return 0;

var bytesWritten = 0;
var dataToCheck = reader.ReadByte();

buffer[bytesWritten++] = dataToCheck;

while (bytesWritten < MaxCompressDataLength && inputStream.Position < inputStream.Length)
{
if (dataToCheck != reader.ReadByte())
{
inputStream.Seek(-1, SeekOrigin.Current);
break;
}

buffer[bytesWritten++] = dataToCheck;
}

return bytesWritten;
}

void WriteCompressed(byte data, int count)
{
writer.Write((byte) (CompressFlag | (count - MinCompressDataLength)));
writer.Write(data);
}

void WriteUncompressed(ReadOnlySpan<byte> buffer)
{
writer.Write((byte) (buffer.Length - MinUncompressDataLength));
writer.Write(buffer);
}

writer.Write((uint) (CompressionHeader | (inputStream.Length << 8)));

var tempBuffer = ArrayPool<byte>.Shared.Rent(MaxCompressDataLength);
Expand Down Expand Up @@ -152,11 +117,22 @@ void WriteUncompressed(ReadOnlySpan<byte> buffer)
tempBuffer.AsSpan(0, tempBufferLength).CopyTo(rawDataBuffer.AsSpan(rawDataLength));
rawDataLength += tempBufferLength;
}
else
else if (tempBufferLength <= rawDataSpaceRemaining)
{
tempBuffer.AsSpan(0, tempBufferLength).CopyTo(rawDataBuffer.AsSpan(rawDataLength));
rawDataLength += tempBufferLength;
}
else
{
tempBuffer.AsSpan(0, rawDataSpaceRemaining).CopyTo(rawDataBuffer.AsSpan(rawDataLength));
rawDataLength += rawDataSpaceRemaining;

WriteUncompressed(rawDataBuffer.AsSpan(0, rawDataLength));
rawDataLength = 0;

tempBuffer.AsSpan(rawDataSpaceRemaining, tempBufferLength - rawDataSpaceRemaining).CopyTo(rawDataBuffer.AsSpan(rawDataLength));
rawDataLength += tempBufferLength - rawDataSpaceRemaining;
}
}
}

Expand All @@ -175,5 +151,42 @@ void WriteUncompressed(ReadOnlySpan<byte> buffer)
ArrayPool<byte>.Shared.Return(tempBuffer);
ArrayPool<byte>.Shared.Return(rawDataBuffer);
}

return;

void WriteCompressed(byte data, int count)
{
writer.Write((byte) (CompressFlag | (count - MinCompressDataLength)));
writer.Write(data);
}

void WriteUncompressed(ReadOnlySpan<byte> buffer)
{
writer.Write((byte) (buffer.Length - MinUncompressDataLength));
writer.Write(buffer);
}

int GetNextRepeatCount(Span<byte> buffer)
{
if (inputStream.Position >= inputStream.Length) return 0;

var bytesWritten = 0;
var dataToCheck = reader.ReadByte();

buffer[bytesWritten++] = dataToCheck;

while (bytesWritten < MaxCompressDataLength && inputStream.Position < inputStream.Length)
{
if (dataToCheck != reader.ReadByte())
{
inputStream.Seek(-1, SeekOrigin.Current);
break;
}

buffer[bytesWritten++] = dataToCheck;
}

return bytesWritten;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<IsPublishable>false</IsPublishable>

<Version>1.7.0</Version>
<Version>1.8.0</Version>
<Title>Fossil Fighters Tool API</Title>
<Authors>Yong Jian Ming</Authors>
<Description>This library is used to decompress and compress MAR archives used in Fossil Fighters game.</Description>
Expand Down
31 changes: 21 additions & 10 deletions TheDialgaTeam.FossilFighters.Tool.Cli/Command/CompressCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,50 +28,61 @@ public CompressCommand() : base("compress", Localization.CompressCommandDescript
var inputArgument = new Argument<string>("input", "Target folder to compress.") { Arity = ArgumentArity.ExactlyOne };
inputArgument.LegalFilePathsOnly();

var outputOption = new Option<string>(new[] { "--output", "-o" }, "Output file after compression.") { Arity = ArgumentArity.ExactlyOne, IsRequired = true };
var outputOption = new Option<string>(new[] { "--output", "-o" }, "Output file after compression.") { Arity = ArgumentArity.ExactlyOne, IsRequired = true, ArgumentHelpName = "file" };

var includeOption = new Option<string[]>(new[] { "--include", "-i" }, () => new[] { "*.bin" }, "Include files to be compressed. You can use wildcard (*) to specify one or more files.") { Arity = ArgumentArity.OneOrMore, IsRequired = false };
var includeOption = new Option<string[]>(new[] { "--include", "-i" }, () => new[] { "*.bin" }, "Include files to be compressed. You can use wildcard (*) to specify one or more files. E.g \"-i *.bin -i *.hex\"") { Arity = ArgumentArity.OneOrMore, IsRequired = false, ArgumentHelpName = "fileTypes" };

var compressionTypeOption = new Option<McmFileCompressionType[]>(new[] { "--compress-type", "-c" }, () => new[] { McmFileCompressionType.None }, "Type of compression to be used. (Maximum 2) E.g \"-c Huffman -c Lzss\" Compression is done in reverse order. Make sure to put huffman first for better compression ratio.") { Arity = new ArgumentArity(1, 2), IsRequired = false };
compressionTypeOption.AddCompletions(Enum.GetNames<McmFileCompressionType>());

var maxSizePerChunkOption = new Option<uint>(new[] { "--max-size-per-chunk", "-m" }, () => 0x2000, "Split each file into chunks of <size> bytes when compressing.") { Arity = ArgumentArity.ExactlyOne, IsRequired = false, ArgumentHelpName = "size" };

var metaFileOption = new Option<string>(new[] { "--meta-file", "-mf" }, () => "meta.json", "Meta definition file to define the compression type and the chunk size.") { Arity = ArgumentArity.ExactlyOne, IsRequired = false, ArgumentHelpName = "file" };

AddArgument(inputArgument);
AddOption(outputOption);
AddOption(includeOption);
AddOption(compressionTypeOption);
AddOption(maxSizePerChunkOption);
AddOption(metaFileOption);

this.SetHandler(Invoke, inputArgument, outputOption, includeOption, compressionTypeOption, maxSizePerChunkOption);
this.SetHandler(Invoke, inputArgument, outputOption, includeOption, compressionTypeOption, maxSizePerChunkOption, metaFileOption);
}

private void Invoke(string input, string output, string[] includes, McmFileCompressionType[] compressionTypes, uint maxSizePerChunk)
private void Invoke(string input, string output, string[] includes, McmFileCompressionType[] compressionTypes, uint maxSizePerChunk, string metaFile)
{
if (Directory.Exists(input))
{
Compress(input, output, includes, compressionTypes, maxSizePerChunk);
Compress(input, output, includes, compressionTypes, maxSizePerChunk, metaFile);
}
else
{
Console.WriteLine(Localization.InputDoesNotExists, input);
}
}

private void Compress(string folder, string output, string[] includes, McmFileCompressionType[] compressionTypes, uint maxSizePerChunk)
private void Compress(string folder, string output, string[] includes, McmFileCompressionType[] compressionTypes, uint maxSizePerChunk, string metaFile)
{
using var outputFile = new FileStream(output, FileMode.Create, FileAccess.Write);
using var marArchive = new MarArchive(outputFile, MarArchiveMode.Create);

var matcher = new Matcher();
matcher.AddIncludePatterns(includes);

var mcmMetaFilePath = Path.GetFullPath(Path.Combine(folder, "meta.json"));
Dictionary<int, McmFileMetadata>? mcmMetadata = null;
if (File.Exists(mcmMetaFilePath))

if (File.Exists(metaFile))
{
mcmMetadata = JsonSerializer.Deserialize(File.OpenRead(mcmMetaFilePath), McmFileMetadataContext.Default.DictionaryInt32McmFileMetadata);
mcmMetadata = JsonSerializer.Deserialize(File.OpenRead(metaFile), McmFileMetadataContext.Default.DictionaryInt32McmFileMetadata);
}
else
{
var mcmMetaFilePath = Path.GetFullPath(Path.Combine(folder, metaFile));

if (File.Exists(mcmMetaFilePath))
{
mcmMetadata = JsonSerializer.Deserialize(File.OpenRead(mcmMetaFilePath), McmFileMetadataContext.Default.DictionaryInt32McmFileMetadata);
}
}

foreach (var file in matcher.GetResultsInFullPath(folder).OrderBy(s => int.Parse(Path.GetFileNameWithoutExtension(s))))
Expand Down
1 change: 0 additions & 1 deletion TheDialgaTeam.FossilFighters.Tool.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public static async Task<int> Main(string[] args)
var rootCommand = new RootCommand(Localization.FossilFightersToolDescription);
rootCommand.AddCommand(new DecompressCommand());
rootCommand.AddCommand(new CompressCommand());
rootCommand.AddCommand(new ConvertCommand());

if (args.Length > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>

<Version>1.7.0</Version>
<Version>1.8.0</Version>
<Title>Fossil Fighters Tool</Title>
<Authors>Yong Jian Ming</Authors>
<Description>This program is used to decompress and compress MAR archives used in Fossil Fighters game.</Description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<TrimMode>copyused</TrimMode>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>

<IsPublishable>false</IsPublishable>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>false</PublishTrimmed>

Expand Down

0 comments on commit 90393f5

Please sign in to comment.