-
-
Save xyx0826/3186510cd3416f798508e4fe8ba7eb4f to your computer and use it in GitHub Desktop.
MSS 10 Bank Slicer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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