Advertisement
Brick

Midtown Madness 1 + 2 Archive Extractor

Jul 19th, 2017
131
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 12.95 KB | None | 0 0
  1. using System;
  2. using System.Text;
  3. using System.IO;
  4. using System.Linq;
  5. using System.IO.Compression;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.Threading.Tasks;
  9.  
  10. public class MMArchiveHelper
  11. {
  12.     public static void CheckMagic(uint magic, uint required)
  13.     {
  14.         if (magic != required)
  15.         {
  16.             throw new Exception(string.Format("Invalid Magic {0:08X} (expected {1:08X})", magic, required));
  17.         }
  18.     }
  19.  
  20.     public static string ReadASCII(Stream stream)
  21.     {
  22.         var builder = new StringBuilder();
  23.  
  24.         for (int i; (i = stream.ReadByte()) > 0;)
  25.         {
  26.             builder.Append((char) i);
  27.         }
  28.  
  29.         return builder.ToString();
  30.     }
  31.  
  32.     public class ArchiveEntry
  33.     {
  34.         public readonly string Name;
  35.         public readonly long Size;
  36.         public readonly long RawSize;
  37.         public readonly long Position;
  38.  
  39.         public ArchiveEntry(string name, long size, long rawSize, long fileOffset)
  40.         {
  41.             Name     = name;
  42.             Size     = size;
  43.             RawSize  = rawSize;
  44.             Position = fileOffset;
  45.         }
  46.  
  47.         public byte[] GetContents(Stream input)
  48.         {
  49.             input.Seek(Position, SeekOrigin.Begin);
  50.  
  51.             if (Size != RawSize)
  52.             {
  53.                 input = new DeflateStream(input, CompressionMode.Decompress, true);
  54.             }
  55.  
  56.             var buffer = new byte[Size];
  57.  
  58.             input.Read(buffer, 0, buffer.Length);
  59.  
  60.             return buffer;
  61.         }
  62.  
  63.         public override string ToString()
  64.         {
  65.             return Name;
  66.         }
  67.     }
  68.  
  69.     public class VirtualFileInode
  70.     {
  71.         public readonly string Name;
  72.         public readonly uint DataOffset;
  73.         public readonly uint Size;
  74.         public readonly bool IsDirectory;
  75.  
  76.         public VirtualFileInode(BinaryReader reader, Stream nameStream)
  77.         {
  78.             DataOffset = reader.ReadUInt32();
  79.  
  80.             var field4 = reader.ReadUInt32();
  81.             var field8 = reader.ReadUInt32();
  82.  
  83.             Size        = (field4 & 0x7FFFFF);
  84.             IsDirectory = (field8 & 1) != 0;
  85.  
  86.             var nameOffset      = (field8 >> 14) & 0x3FFFF;
  87.             var extensionOffset = (field4 >> 23) & 0x1FF;
  88.             var nameInteger     = (field8 >>  1) & 0x1FFF;
  89.  
  90.             nameStream.Seek(nameOffset, SeekOrigin.Begin);
  91.  
  92.             Name = ReadASCII(nameStream).Replace("\x01", nameInteger.ToString());
  93.  
  94.             if (extensionOffset != 0)
  95.             {
  96.                 nameStream.Seek(extensionOffset, SeekOrigin.Begin);
  97.  
  98.                 Name += "." + ReadASCII(nameStream);
  99.             }
  100.         }
  101.  
  102.         public ArchiveEntry GetArchiveEntry(string parent = "")
  103.         {
  104.             if (!IsDirectory)
  105.             {
  106.                 return new ArchiveEntry(parent + Name, Size, Size, DataOffset);
  107.             }
  108.  
  109.             return null;
  110.         }
  111.  
  112.         public IEnumerable<VirtualFileInode> GetChildren(VirtualFileInode[] nodes)
  113.         {
  114.             if (IsDirectory)
  115.             {
  116.                 for (var i = DataOffset; i < DataOffset + Size; ++i)
  117.                 {
  118.                     yield return nodes[i];
  119.                 }
  120.             }
  121.         }
  122.  
  123.         public IEnumerable<ArchiveEntry> GetFiles(VirtualFileInode[] nodes, string parent = "")
  124.         {
  125.             if (IsDirectory)
  126.             {
  127.                 var name = parent + Name + "\\";
  128.  
  129.                 foreach (var node in GetChildren(nodes))
  130.                 {
  131.                     if (node.IsDirectory)
  132.                     {
  133.                         foreach (var file in node.GetFiles(nodes, name))
  134.                         {
  135.                             yield return file;
  136.                         }
  137.                     }
  138.                     else
  139.                     {
  140.                         yield return node.GetArchiveEntry(name);
  141.                     }
  142.                 }
  143.             }
  144.         }
  145.  
  146.         public override string ToString()
  147.         {
  148.             return Name;
  149.         }
  150.     }
  151.  
  152.     public static IEnumerable<ArchiveEntry> ReadDaveArchive(BinaryReader reader)
  153.     {
  154.         reader.BaseStream.Seek(0, SeekOrigin.Begin);
  155.  
  156.         CheckMagic(reader.ReadUInt32(), 0x45564144);
  157.  
  158.         var headerCount = reader.ReadUInt32();
  159.         var namesOffset = reader.ReadUInt32();
  160.         var namesSize   = reader.ReadUInt32();
  161.  
  162.         for (var i = 0; i < headerCount; ++i)
  163.         {
  164.             reader.BaseStream.Seek(2048 + (i * 16), SeekOrigin.Begin);
  165.  
  166.             var nameOffset = reader.ReadUInt32();
  167.             var dataOffset = reader.ReadUInt32();
  168.  
  169.             var uncompressedSize = reader.ReadUInt32();
  170.             var compressedSize   = reader.ReadUInt32();
  171.  
  172.             reader.BaseStream.Seek(2048 + namesOffset + nameOffset, SeekOrigin.Begin);
  173.  
  174.             var name = ReadASCII(reader.BaseStream);
  175.  
  176.             yield return new ArchiveEntry(name, uncompressedSize, compressedSize, dataOffset);
  177.         }
  178.     }
  179.  
  180.     public static IEnumerable<ArchiveEntry> ReadPKArchive(BinaryReader reader)
  181.     {
  182.         reader.BaseStream.Seek(-22, SeekOrigin.End);
  183.  
  184.         CheckMagic(reader.ReadUInt32(), 0x06054B50); // ZIPENDLOCATOR
  185.  
  186.         var diskNumber      = reader.ReadUInt16();
  187.         var startDiskNumber = reader.ReadUInt16();
  188.  
  189.         if (diskNumber != startDiskNumber)
  190.         {
  191.             throw new Exception("Incomplete Archive");
  192.         }
  193.  
  194.         var fileCount        = reader.ReadUInt16();
  195.         var filesInDirectory = reader.ReadUInt16();
  196.  
  197.         var directorySize   = reader.ReadUInt32();
  198.         var directoryOffset = reader.ReadUInt32();
  199.  
  200.         var fileCommentLength = reader.ReadUInt16();
  201.  
  202.         var currentOffset = directoryOffset;
  203.  
  204.         while (true)
  205.         {
  206.             reader.BaseStream.Seek(currentOffset, SeekOrigin.Begin);
  207.  
  208.             if (reader.ReadUInt32() != 0x02014B50) // ZIPDIRENTRY
  209.             {
  210.                 break;
  211.             }
  212.  
  213.             var versionMadeBy    = reader.ReadUInt16();
  214.             var versionToExtract = reader.ReadUInt16();
  215.             var flags            = reader.ReadUInt16();
  216.  
  217.             var compressionMethod = reader.ReadUInt16();
  218.  
  219.             if (compressionMethod != 0 && compressionMethod != 8)
  220.             {
  221.                 throw new Exception("Invalid compression method " + compressionMethod);
  222.             }
  223.  
  224.             var fileTime = reader.ReadUInt16();
  225.             var fileDate = reader.ReadUInt16();
  226.             var crc      = reader.ReadUInt32();
  227.  
  228.             var compressedSize   = reader.ReadUInt32();
  229.             var uncompressedSize = reader.ReadUInt32();
  230.  
  231.             var nameLength    = reader.ReadUInt16();
  232.             var extraLength   = reader.ReadUInt16();
  233.             var commentLength = reader.ReadUInt16();
  234.  
  235.             var diskNumberStart = reader.ReadUInt16();
  236.  
  237.             var internalAttributes = reader.ReadUInt16();
  238.             var externalAttributes = reader.ReadUInt32();
  239.  
  240.             var dataOffset = reader.ReadUInt32(); // ZIPFILERECORD
  241.  
  242.             var name = new string(reader.ReadChars(nameLength));
  243.  
  244.             currentOffset = (uint) reader.BaseStream.Position + extraLength + commentLength;
  245.  
  246.             yield return new ArchiveEntry(name, uncompressedSize, compressedSize, dataOffset + nameLength + 30);
  247.         }
  248.     }
  249.  
  250.     public static IEnumerable<ArchiveEntry> ReadARESArchive(BinaryReader reader)
  251.     {
  252.         reader.BaseStream.Seek(0, SeekOrigin.Begin);
  253.  
  254.         CheckMagic(reader.ReadUInt32(), 0x53455241);
  255.  
  256.         var headerCount    = reader.ReadUInt32();
  257.         var directoryCount = reader.ReadUInt32();
  258.         var namesSize      = reader.ReadUInt32();
  259.         var namesOffset    = 16 + headerCount * 12;
  260.  
  261.         reader.BaseStream.Seek(namesOffset, SeekOrigin.Begin);
  262.  
  263.         var namesStream = new MemoryStream(reader.ReadBytes((int) namesSize));
  264.  
  265.         var nodes = new VirtualFileInode[headerCount];
  266.  
  267.         for (var i = 0; i < headerCount; ++i)
  268.         {
  269.             reader.BaseStream.Seek(16 + (i * 12), SeekOrigin.Begin);
  270.  
  271.             nodes[i] = new VirtualFileInode(reader, namesStream);
  272.         }
  273.  
  274.         for (var i = 0; i < directoryCount; ++i)
  275.         {
  276.             foreach (var file in nodes[i].GetFiles(nodes, ""))
  277.             {
  278.                 yield return file;
  279.             }
  280.         }
  281.     }
  282.  
  283.     public static IEnumerable<ArchiveEntry> GetArchiveEntries(Stream archive)
  284.     {
  285.         var binaryReader = new BinaryReader(archive);
  286.  
  287.         binaryReader.BaseStream.Seek(0, SeekOrigin.Begin);
  288.  
  289.         if (binaryReader.ReadUInt32() == 0x45564144) // DAVE
  290.         {
  291.             return ReadDaveArchive(binaryReader);
  292.         }
  293.  
  294.         binaryReader.BaseStream.Seek(0, SeekOrigin.Begin);
  295.  
  296.         if (binaryReader.ReadUInt32() == 0x53455241) // ARES
  297.         {
  298.             return ReadARESArchive(binaryReader);
  299.         }
  300.  
  301.         binaryReader.BaseStream.Seek(-22, SeekOrigin.End);
  302.  
  303.         if (binaryReader.ReadUInt32() == 0x06054B50) // ZIPENDLOCATOR
  304.         {
  305.             return ReadPKArchive(binaryReader);
  306.         }
  307.  
  308.         throw new ArgumentException("Invalid archive stream");
  309.     }
  310. }
  311.  
  312. namespace DaveReader
  313. {
  314.     class MMArchiveReader
  315.     {
  316.         static string ConsoleClearLine()
  317.         {
  318.             return string.Format("\r{0}\r", new string(' ', Console.WindowWidth - 1));
  319.         }
  320.  
  321.         static void OverwriteConsole(string format, params object[ ] args)
  322.         {
  323.             Console.Write(ConsoleClearLine() + format, args);
  324.         }
  325.  
  326.         static void OverwriteConsoleLine(string format, params object[ ] args)
  327.         {
  328.             Console.WriteLine(ConsoleClearLine() + format, args);
  329.         }
  330.  
  331.         static void ExtractArchive(string path, bool merge)
  332.         {
  333.             using (var fileStream = Stream.Synchronized(File.OpenRead(path)))
  334.             {
  335.                 var stopwatch = Stopwatch.StartNew();
  336.  
  337.                 var tasks = MMArchiveHelper.GetArchiveEntries(fileStream).Select((entry) =>
  338.                 {
  339.                     try
  340.                     {
  341.                         return new Task(() =>
  342.                         {
  343.                             var contents = entry.GetContents(fileStream);
  344.  
  345.                             if (contents.Length > 0)
  346.                             {
  347.                                 var outputName = merge
  348.                                     ? Path.Combine(Environment.CurrentDirectory, "Extracted", entry.Name)
  349.                                     : Path.Combine(Environment.CurrentDirectory, "Extracted", Path.GetFileNameWithoutExtension(path), entry.Name);
  350.  
  351.                                 Directory.CreateDirectory(Path.GetDirectoryName(outputName));
  352.  
  353.                                 using (var output = File.OpenWrite(outputName))
  354.                                 {
  355.                                     output.Write(contents, 0, contents.Length);
  356.                                 }
  357.                             }
  358.                         });
  359.                     }
  360.                     catch (Exception e)
  361.                     {
  362.                         Console.WriteLine("Failed to extract {0} - {1}", entry.Name, e);
  363.                     }
  364.  
  365.                     return null;
  366.                 }).ToArray();
  367.  
  368.                 foreach (var task in tasks)
  369.                 {
  370.                     task.Start();
  371.                 }
  372.  
  373.                 var endTask = Task.WhenAll(tasks);
  374.  
  375.                 while (!endTask.Wait(100))
  376.                 {
  377.                     var completed = tasks.Where(x => x.IsCompleted).ToArray();
  378.  
  379.                     OverwriteConsole("{0} : {1} / {2}", path, completed.Length, tasks.Length);
  380.                 }
  381.  
  382.                 OverwriteConsoleLine("{0} : {1} files in {2} ms", path, tasks.Length, stopwatch.ElapsedMilliseconds);
  383.             }
  384.         }
  385.  
  386.         static string GetArgOrReadLine(string[] args, uint index)
  387.         {
  388.             return args.Length > index ? args[index] : Console.ReadLine();
  389.         }
  390.  
  391.         static string GetArgOfDefault(string[] args, uint index, string defaultValue)
  392.         {
  393.             return args.Length > index ? args[index] : defaultValue;
  394.         }
  395.  
  396.         static void Main(string[] args)
  397.         {
  398.             Console.Clear();
  399.             Console.ForegroundColor = ConsoleColor.Green;
  400.  
  401.             var path  = GetArgOrReadLine(args, 0);
  402.             var merge = bool.Parse(GetArgOfDefault(args, 1, "false"));
  403.  
  404.             if (File.Exists(path))
  405.             {
  406.                 ExtractArchive(path, merge);
  407.             }
  408.             else if (Directory.Exists(path))
  409.             {
  410.                 foreach (var file in Directory.GetFiles(path, "*.ar"))
  411.                 {
  412.                     ExtractArchive(file, merge);
  413.                 }
  414.             }
  415.             else
  416.             {
  417.                 Console.WriteLine("Invalid file/path: {0}", path);
  418.             }
  419.  
  420.             Console.WriteLine("Finished");
  421.  
  422.             Console.ReadKey(true);
  423.         }
  424.     }
  425. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement