Skip to content

Commit

Permalink
Usability improvements
Browse files Browse the repository at this point in the history
- Added some safety checks and error messages.
- Benchmark auto detects the region of the game.
- Benchmark improvements in the way data is shown in the console.
  • Loading branch information
MaikelChan committed Aug 28, 2020
1 parent 663ab9d commit e05c165
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 67 deletions.
40 changes: 40 additions & 0 deletions MarioPartyCompression/MarioPartyCompression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ public static class MPCompression
/// <param name="outputFilePath">Path where the uncompressed data will be saved.</param>
public static void Decompress(string inputFilePath, string outputFilePath)
{
if (string.IsNullOrEmpty(inputFilePath))
{
throw new ArgumentNullException("inputFilePath");
}

if (string.IsNullOrEmpty(outputFilePath))
{
throw new ArgumentNullException("outputFilePath");
}

if (!File.Exists(inputFilePath))
{
throw new FileNotFoundException($"File \"{inputFilePath}\" does not exist!", inputFilePath);
Expand All @@ -36,6 +46,11 @@ public static void Decompress(string inputFilePath, string outputFilePath)
/// <returns>The uncompressed data buffer.</returns>
public static byte[] Decompress(byte[] compressedBuffer)
{
if (compressedBuffer == null)
{
throw new ArgumentNullException("compressedBuffer");
}

using (MemoryStream compressedStream = new MemoryStream(compressedBuffer))
{
return Decompress(compressedStream);
Expand All @@ -49,6 +64,11 @@ public static byte[] Decompress(byte[] compressedBuffer)
/// <returns>The uncompressed data buffer.</returns>
public static byte[] Decompress(Stream compressedStream)
{
if (compressedStream == null)
{
throw new ArgumentNullException("compressedStream");
}

byte[] uncompressedBuffer;

using (BinaryReader br = new BinaryReader(compressedStream, Encoding.UTF8, true))
Expand Down Expand Up @@ -141,6 +161,16 @@ public static byte[] Decompress(Stream compressedStream)
/// <param name="outputFilePath">Path where the compressed data will be saved.</param>
public static void Compress(string inputFilePath, string outputFilePath)
{
if (string.IsNullOrEmpty(inputFilePath))
{
throw new ArgumentNullException("inputFilePath");
}

if (string.IsNullOrEmpty(outputFilePath))
{
throw new ArgumentNullException("outputFilePath");
}

if (!File.Exists(inputFilePath))
{
throw new FileNotFoundException($"File \"{inputFilePath}\" does not exist!", inputFilePath);
Expand All @@ -160,6 +190,11 @@ public static void Compress(string inputFilePath, string outputFilePath)
/// <returns>The compressed data buffer.</returns>
public static byte[] Compress(byte[] uncompressedBuffer)
{
if (uncompressedBuffer == null)
{
throw new ArgumentNullException("uncompressedBuffer");
}

using (MemoryStream compressedStream = new MemoryStream(uncompressedBuffer))
{
return Compress(compressedStream);
Expand All @@ -173,6 +208,11 @@ public static byte[] Compress(byte[] uncompressedBuffer)
/// <returns>The compressed data buffer.</returns>
public static byte[] Compress(Stream uncompressedStream)
{
if (uncompressedStream == null)
{
throw new ArgumentNullException("uncompressedStream");
}

// Create a buffer big enough to contain the search window (SEARCH_WINDOW_SIZE) and the uncompressed data
byte[] uncompressedBuffer = new byte[uncompressedStream.Length + SEARCH_WINDOW_SIZE];
uint uncompressedBufferPosition = SEARCH_WINDOW_SIZE;
Expand Down
6 changes: 3 additions & 3 deletions MarioPartyCompression/MarioPartyCompression.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<Authors>PacoChan</Authors>
<Product>Mario Party Compression Library</Product>
<Description>A Mario Party compression library.</Description>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.1.0</AssemblyVersion>
<FileVersion>1.0.1.0</FileVersion>
<Version>1.0.1</Version>
<PackageProjectUrl></PackageProjectUrl>
<RepositoryUrl>https://github.com/MaikelChan/MarioPartyCompression</RepositoryUrl>
</PropertyGroup>
Expand Down
120 changes: 88 additions & 32 deletions MarioPartyTools/Benchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,33 @@ namespace MarioPartyTools
{
static class Benchmark
{
public enum RomRegions { NtscU, Pal, NtscJ }
static readonly byte[][] RomCRCs =
{
new byte[] { 0xad, 0xa8, 0x15, 0xbe, 0x60, 0x28, 0x62, 0x2f }, // NTSC-J
new byte[] { 0x28, 0x29, 0x65, 0x7e, 0xa0, 0x62, 0x18, 0x77 }, // NTSC-U
new byte[] { 0x9c, 0x66, 0x30, 0x69, 0x80, 0xf2, 0x4a, 0x80 } // PAL
};

static readonly RomData[] romData =
{
new RomData() { startPosition = 0x31ba80, endPosition = 0xfb47a0 }, // NTSC-J
new RomData() { startPosition = 0x31c7e0, endPosition = 0xfcb860 }, // NTSC-U
new RomData() { startPosition = 0x3373c0, endPosition = 0xff0850 } // PAL
};

struct RomData
{
public uint startPosition;
public uint endPosition;
}

public static void Start(string romFilePath, RomRegions romRegion)
public static void Start(string romFilePath)
{
if (string.IsNullOrEmpty(romFilePath))
{
throw new ArgumentNullException("romFilePath");
}

if (!File.Exists(romFilePath))
{
throw new FileNotFoundException($"File \"{romFilePath}\" does not exist!", romFilePath);
Expand All @@ -19,31 +42,43 @@ public static void Start(string romFilePath, RomRegions romRegion)
float compressionRatio = 0f;
int processedFiles = 0;
Stopwatch sw = new Stopwatch();
sw.Start();

uint dataStartPosition;
uint dataEndPosition;

if (romRegion == RomRegions.NtscU)
{
dataStartPosition = 0x31c7e0;
dataEndPosition = 0xfcb860;
}
else if (romRegion == RomRegions.Pal)
{
dataStartPosition = 0x3373c0;
dataEndPosition = 0xff0850;
}
else
{
dataStartPosition = 0x31ba80;
dataEndPosition = 0xfb47a0;
}

using (FileStream romStream = File.OpenRead(romFilePath))
using (BinaryReader br = new BinaryReader(romStream))
{
romStream.Seek(dataStartPosition, SeekOrigin.Begin);
// Check if it is a valid ROM

romStream.Seek(0x10, SeekOrigin.Begin);
byte[] romCRC = new byte[0x8];
romStream.Read(romCRC, 0, romCRC.Length);
byte[] swappedRomCRC = SwapCRCBytes(romCRC);

int romRegion = -1;

for (int r = 0; r < RomCRCs.Length; r++)
{
if (CompareArrays(RomCRCs[r], swappedRomCRC))
{
throw new FormatException("The Mario Party ROM has swapped data. Please unswap it using another tool first.");
}

if (CompareArrays(RomCRCs[r], romCRC))
{
romRegion = r;
break;
}
}

if (romRegion < 0)
{
throw new FormatException("The file is not a Mario Party ROM for N64. Please make sure you've specified the correct game.");
}

// Start reading the ROM data

sw.Start();

romStream.Seek(romData[romRegion].startPosition, SeekOrigin.Begin);

uint fileCount = SwapU32(br.ReadUInt32());
uint[] filePointers = new uint[fileCount];
Expand All @@ -55,7 +90,7 @@ public static void Start(string romFilePath, RomRegions romRegion)

for (int f = 0; f < fileCount; f++)
{
romStream.Seek(filePointers[f] + dataStartPosition, SeekOrigin.Begin);
romStream.Seek(filePointers[f] + romData[romRegion].startPosition, SeekOrigin.Begin);

uint subFileCount = SwapU32(br.ReadUInt32());
uint[] subFilePointers = new uint[subFileCount];
Expand All @@ -76,12 +111,12 @@ public static void Start(string romFilePath, RomRegions romRegion)
else
{
if (f < fileCount - 1)
subFileLength = (filePointers[f + 1] + dataStartPosition) - (filePointers[f] + subFilePointers[sf] + dataStartPosition);
subFileLength = (filePointers[f + 1] + romData[romRegion].startPosition) - (filePointers[f] + subFilePointers[sf] + romData[romRegion].startPosition);
else
subFileLength = dataEndPosition - (filePointers[f] + subFilePointers[sf] + dataStartPosition);
subFileLength = romData[romRegion].endPosition - (filePointers[f] + subFilePointers[sf] + romData[romRegion].startPosition);
}

uint subFilePosition = filePointers[f] + subFilePointers[sf] + dataStartPosition;
uint subFilePosition = filePointers[f] + subFilePointers[sf] + romData[romRegion].startPosition;
romStream.Seek(subFilePosition, SeekOrigin.Begin);
byte[] data = new byte[subFileLength];
romStream.Read(data, 0, data.Length);
Expand All @@ -101,15 +136,15 @@ public static void Start(string romFilePath, RomRegions romRegion)
compressionRatio /= processedFiles;

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"\nDone! Compression ratio: {compressionRatio}. Total time: {sw.ElapsedMilliseconds}ms.\n");
Console.WriteLine($"\nProcess finished! Compression ratio average: {compressionRatio}. Elapsed time: {sw.ElapsedMilliseconds}ms.\n");
}

static bool ProcessFile(byte[] compressedBuffer, uint offset, out float compressionRatio)
{
compressionRatio = 0f;

Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine($"Processing data at 0x{offset:X8}...");
Console.Write($"Processing data at 0x{offset:X8}... ");

byte[] uncompressedBuffer;

Expand All @@ -120,7 +155,7 @@ static bool ProcessFile(byte[] compressedBuffer, uint offset, out float compress
catch (FormatException ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Error processing data at 0x{offset:X8}: {ex.Message}");
Console.WriteLine(ex.Message);
return false;
}

Expand All @@ -131,11 +166,14 @@ static bool ProcessFile(byte[] compressedBuffer, uint offset, out float compress
if (!areEqual)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Data at 0x{offset:X8} decompression mismatch...");
Console.WriteLine("Decompression mismatch.");
return false;
}

compressionRatio = (float)compressedBuffer.Length / compressedBuffer2.Length;
compressionRatio = (float)compressedBuffer2.Length / compressedBuffer.Length;

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"Done! Compression ratio: {compressionRatio}");

return true;
}
Expand All @@ -152,6 +190,24 @@ static bool CompareArrays(byte[] arr1, byte[] arr2)
return true;
}

static byte[] SwapCRCBytes(byte[] bytes)
{
if (bytes.Length != 8)
{
throw new ArgumentException("The bytes array must have 8 elements.", "bytes");
}

byte[] swappedBytes = new byte[8];

for (int b = 0; b < bytes.Length; b += 2)
{
swappedBytes[b] = bytes[b + 1];
swappedBytes[b + 1] = bytes[b];
}

return swappedBytes;
}

static uint SwapU32(uint value)
{
return (value << 24) | ((value & 0x0000FF00) << 8) | ((value & 0x00FF0000) >> 8) | (value >> 24);
Expand Down
46 changes: 21 additions & 25 deletions MarioPartyTools/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,26 @@ static void Main(string[] args)
{
ShowHeader();

if (args.Length == 3)
if (args.Length == 2)
{
if (args[0] == "-b")
{
try
{
Benchmark.Start(args[1]);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.Message);
}
}
else
{
ShowUsage();
}
}
else if (args.Length == 3)
{
if (args[0] == "-d")
{
Expand Down Expand Up @@ -42,25 +61,6 @@ static void Main(string[] args)
Console.WriteLine(ex.Message);
}
}
else if (args[0] == "-b")
{
if (args[1] == "-rU")
{
Benchmark.Start(args[2], Benchmark.RomRegions.NtscU);
}
else if (args[1] == "-rP")
{
Benchmark.Start(args[2], Benchmark.RomRegions.Pal);
}
else if (args[1] == "-rJ")
{
Benchmark.Start(args[2], Benchmark.RomRegions.NtscJ);
}
else
{
ShowUsage();
}
}
else
{
ShowUsage();
Expand Down Expand Up @@ -106,18 +106,14 @@ static void ShowUsage()

Console.WriteLine(" MarioPartyTools -d <compressed_file> <uncompressed_file> : Decompress a Mario Party file");
Console.WriteLine(" MarioPartyTools -c <uncompressed_file> <compressed_file> : Compress a Mario Party file");
Console.WriteLine(" MarioPartyTools -b -r<rom_region> <rom_file> : Execute some benchmarks and tests\n");
Console.WriteLine(" MarioPartyTools -b <rom_file> : Execute some benchmarks and tests\n");

Console.ForegroundColor = ConsoleColor.Gray;

Console.WriteLine(" The benchmark command will try to decompress and recompress all compressed data");
Console.WriteLine(" in a Mario Party ROM, show the compression ratio compared to the original data,");
Console.WriteLine(" check if there are compression errors, and show how much it took to do all that.");
Console.WriteLine(" Also make sure that the ROM is not swapped, or else the process will fail.\n");

Console.WriteLine(" rom_region: the benchmark is compatible with all versions of Mario Party.");
Console.WriteLine(" Use -rP to specify the PAL version, -rU for the NTSC-U version,");
Console.WriteLine(" and -rJ for the NTSC-J version.\n");
}
}
}
4 changes: 2 additions & 2 deletions MarioPartyTools/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.1.0")]
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@ The program requires the .NET Framework 4.7.2.
```
MarioPartyTools -d <compressed_file> <uncompressed_file> : Decompress a Mario Party file
MarioPartyTools -c <uncompressed_file> <compressed_file> : Compress a Mario Party file
MarioPartyTools -b -r<rom_region> <rom_file> : Execute some benchmarks and tests
MarioPartyTools -b <rom_file> : Execute some benchmarks and tests
The benchmark command will try to decompress and recompress all compressed data
in a Mario Party ROM, show the compression ratio compared to the original data,
check if there are compression errors, and show how much it took to do all that.
Also make sure that the ROM is not swapped, or else the process will fail.
rom_region: the benchmark is compatible with all versions of Mario Party.
Use -rP to specify the PAL version, -rU for the NTSC-U version,
and -rJ for the NTSC-J version.
```

# Changelog
### [1.0.1] - 2020-08-28
- Added some safety checks and error messages.
- Benchmark auto detects the region of the game.
- Benchmark improvements in the way data is shown in the console.

### [1.0.0] - 2020-08-28
- Initial release.

0 comments on commit e05c165

Please sign in to comment.