public static void ExtractToFolder(byte[] romBytes, string outputDir) { Console.WriteLine("Parsing filesystem..."); Filesystem filesystem = new Filesystem(romBytes); Directory.CreateDirectory(outputDir); Directory.CreateDirectory(outputDir + RAW_SUBDIR); Directory.CreateDirectory(outputDir + UNPACKED_SUBDIR); //Save off bits before file table File.WriteAllBytes(outputDir + "[0x00000] Data before file table.bin", romBytes.Subsection(0, filesystem.StartLocationInROM)); Console.WriteLine("Section of ROM that's before file table saved to file."); //Save file table File.WriteAllBytes($"{outputDir}[0x{filesystem.FileTable.FormEntryLocation:x6}] File Table.bin", filesystem.FileTable.RawTableBytes); if (filesystem.FileTable.Type == FileTable.FileTableType.FlightGame) { File.WriteAllBytes($"{outputDir}[0x{filesystem.FileTable.FormEntryLocation:x6}] File Table (decompressed).bin", filesystem.FileTable.DecompressedTableBytes); } Console.WriteLine("File table saved to file."); Console.WriteLine("Extracting files..."); Dictionary <string, int> fileTypeCount = new Dictionary <string, int>(); Stopwatch consoleOutputStopwatch = new Stopwatch(); consoleOutputStopwatch.Start(); int fCt = 0; foreach (Filesystem.File file in filesystem.AllFiles) { string outputSubfolder; string fileExtension; if (file.fileTypeFromFileTable == "UVRW") { // special handling for raw data outputSubfolder = RAW_FILETYPE_DIR; if (file.fileTypeFromFileHeader == null) { fileExtension = "_headerless_file"; } else { fileExtension = file.fileTypeFromFileHeader; } } else { outputSubfolder = GetNiceFileType(file.fileTypeFromFileTable, file.fileTypeFromFileHeader); fileExtension = outputSubfolder; } SaveUnmodifiedFile(file, outputDir, outputSubfolder, fileExtension); SaveFileSections(file, outputDir, outputSubfolder, fileExtension); if ((fCt++ % 100 == 99) || consoleOutputStopwatch.ElapsedMilliseconds > 2000) { Console.WriteLine($"{fCt}/{filesystem.FileCount} files extracted..."); consoleOutputStopwatch.Restart(); } } if (filesystem.EndLocationInROM < romBytes.Length) { // Check if there's actually useful data here // In most cases it seems like this is just 0x0 until the next address that's a multiple of 16, and then 0xFF from then on until the end of the ROM. int expectedStartOfFFs = Next16ByteAlignedAddress(filesystem.EndLocationInROM); int curPos = filesystem.EndLocationInROM; bool potentiallyInterestingDataPresent = false; for (; curPos < expectedStartOfFFs; curPos++) { if (romBytes[curPos] != 0x00) { potentiallyInterestingDataPresent = true; break; } } // Some games fill the remaining with 0xFF, some with 0x00 byte testByte = romBytes[curPos]; if (!potentiallyInterestingDataPresent) { for (; curPos < romBytes.Length; curPos++) { if (romBytes[curPos] != testByte) { potentiallyInterestingDataPresent = true; break; } } } if (potentiallyInterestingDataPresent) { File.WriteAllBytes($"{outputDir}[0x{expectedStartOfFFs:x6}] Data after all files.bin", romBytes.Subsection(expectedStartOfFFs, romBytes.Length - expectedStartOfFFs)); } } AsyncWriteHelper.WaitForFilesToFinishWriting(); Console.WriteLine("All files extracted!"); Console.WriteLine(); foreach ((string fileType, int count) in filesystem.AllFiles.GroupBy(file => file.fileTypeFromFileTable).Select(grp => (grp.Key, grp.Count())).OrderBy(tuple => tuple.Key)) { Console.WriteLine($"{fileType}: {count} files"); } }