Skip to content

Instantly share code, notes, and snippets.

@xyx0826
Last active November 11, 2020 18:16
Show Gist options
  • Save xyx0826/3186510cd3416f798508e4fe8ba7eb4f to your computer and use it in GitHub Desktop.
Save xyx0826/3186510cd3416f798508e4fe8ba7eb4f to your computer and use it in GitHub Desktop.
MSS 10 Bank Slicer
using System;
using System.Collections.Generic;
using System.IO;
namespace Mileslice
{
internal class Program
{
// BinkA header magic bytes
private static readonly int[] HeaderMagic = { 0x31, 0x46, 0x43, 0x42 };
// File slicing extension
private const string FileExtension = "binka";
// Switches
private static bool _listOnly = false;
private static bool _silent = false;
private static void Main(string[] args)
{
// Help text
PrintDebug("Mileslice - Miles Sound System 10 Bank File Slicer");
PrintDebug("Usage: dotnet Mileslice.dll [inputFile] (options)");
PrintDebug("Options:");
PrintDebug("\t--list-only\tSkip file extraction, only create file list");
PrintDebug("\t--silent\tOnly print minimal debug information");
// Check first argument input file
if (!File.Exists(args[0]))
{
PrintError($"Error: specified input file \"{args[0]}\" does not exist.");
}
// Parse command line switches
foreach (var arg in args)
{
switch (arg)
{
case "--list-only":
_listOnly = true;
break;
case "--silent":
_silent = true;
break;
}
}
// Check switches
if (_listOnly)
{
PrintDebug("List-only mode. Files will not be extracted.");
}
// Start slicing
SliceFile(args[0]);
}
private static void SliceFile(string inputFile)
{
var bankFile = File.OpenRead(inputFile);
var listFile = new StreamWriter("list.csv");
PrintDebug("Searching for headers...");
var headerOffsets = GetAllHeaders(bankFile);
PrintDebug("Calculating file sizes...");
var fileSizes = GetAllFileSizes(bankFile.Length, headerOffsets);
PrintDebug($"Found {headerOffsets.Count} file headers.");
// Export file list and write to console if not silent
listFile.WriteLine("Count,StartOffset,Length");
PrintDebug("Count\tStartOffset\tLength");
for (int i = 0; i < headerOffsets.Count; i++)
{
listFile.WriteLine($"{i},{headerOffsets[i]},{fileSizes[i]}");
if (!_silent)
{
PrintDebug($"{i}\t{headerOffsets[i]}\t{fileSizes[i]}");
}
}
// Save file slices if not list only
if (!_listOnly)
{
PrintDebug("Saving file slices...");
SaveFileSlices(bankFile, headerOffsets, fileSizes);
}
listFile.Close();
PrintError("Search completed. End of file reached.");
}
private static List<ulong> GetAllHeaders(FileStream bankFile)
{
ulong currentOffset = 0;
var bytesRead = 0;
var buffer = new byte[4096];
var headerOffsets = new List<ulong>();
ulong headerOffset = 0;
int headerBytesChecked = 0;
do
{
// Read fresh buffer
bytesRead = bankFile.Read(buffer, 0, 4096);
if (bytesRead == 0)
{
// This can happen when the previous 4096 bytes exactly ends the file
break;
}
// Buffer filled, now search for headers
for (int i = 0; i < bytesRead; i++)
{
if (buffer[i] == HeaderMagic[0])
{
// First byte matched, tentative set header position
headerOffset = currentOffset + (uint)i;
headerBytesChecked = 1;
continue;
}
if (headerBytesChecked > 0)
{
// A header match is in progress
if (buffer[i] == HeaderMagic[headerBytesChecked])
{
// And another byte matches
headerBytesChecked++;
}
else
{
// Byte match failure, reset count
headerBytesChecked = 0;
continue;
}
if (headerBytesChecked == HeaderMagic.Length)
{
// And a header match is found
headerOffsets.Add(headerOffset);
headerBytesChecked = 0;
continue;
}
}
}
currentOffset += (uint)bytesRead;
}
while (bytesRead == 4096);
PrintDebug($"Found a total of {headerOffsets.Count} headers.");
return headerOffsets;
}
private static List<uint> GetAllFileSizes(long bankFileSize, List<ulong> headerOffsets)
{
var fileSizes = new List<uint>();
for (int i = 0; i < headerOffsets.Count; i++)
{
uint fileSize = 0;
if (i < headerOffsets.Count - 1)
{
// Not the last slice
fileSize = (uint)(headerOffsets[i + 1] - headerOffsets[i]);
}
else
{
// The last slice
fileSize = (uint)(bankFileSize - (long)headerOffsets[i]);
PrintDebug($"Last file slice has a length of {fileSize}.");
}
fileSizes.Add(fileSize);
}
return fileSizes;
}
private static void SaveFileSlices(FileStream bankFile, List<ulong> headerOffsets, List<uint> fileSizes)
{
for (int i = 0; i < headerOffsets.Count; i++)
{
var fileSize = (int)fileSizes[i];
var buffer = new byte[fileSize];
using (var output = File.OpenWrite($"{i}.{FileExtension}"))
{
bankFile.Seek((long)headerOffsets[i], SeekOrigin.Begin);
bankFile.Read(buffer, 0, fileSize);
output.Write(buffer, 0, fileSize);
}
}
}
private static void PrintDebug(string message)
{
if (_silent)
{
return;
}
Console.WriteLine(message);
}
private static void PrintError(string message)
{
Console.WriteLine(message);
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
Environment.Exit(0);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment