Brick icon

Midtown Madness 1 + 2 Archive Extractor

Brick | PRO | 07/19/17 03:06:31 PM UTC | 0 ⭐ | 263 👁️ | Never ⏰ | []
C# |

12.95 KB

|

None

|

0 👍

/

0 👎

using System;
using System.Text;
using System.IO;
using System.Linq;
using System.IO.Compression;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
 
public class MMArchiveHelper
{
    public static void CheckMagic(uint magic, uint required)
    {
        if (magic != required)
        {
            throw new Exception(string.Format("Invalid Magic {0:08X} (expected {1:08X})", magic, required));
        }
    }
 
    public static string ReadASCII(Stream stream)
    {
        var builder = new StringBuilder();
 
        for (int i; (i = stream.ReadByte()) > 0;)
        {
            builder.Append((char) i);
        }
 
        return builder.ToString();
    }
 
    public class ArchiveEntry
    {
        public readonly string Name;
        public readonly long Size;
        public readonly long RawSize;
        public readonly long Position;
 
        public ArchiveEntry(string name, long size, long rawSize, long fileOffset)
        {
            Name     = name;
            Size     = size;
            RawSize  = rawSize;
            Position = fileOffset;
        }
 
        public byte[] GetContents(Stream input)
        {
            input.Seek(Position, SeekOrigin.Begin);
 
            if (Size != RawSize)
            {
                input = new DeflateStream(input, CompressionMode.Decompress, true);
            }
 
            var buffer = new byte[Size];
 
            input.Read(buffer, 0, buffer.Length);
 
            return buffer;
        }
 
        public override string ToString()
        {
            return Name;
        }
    }
 
    public class VirtualFileInode
    {
        public readonly string Name;
        public readonly uint DataOffset;
        public readonly uint Size;
        public readonly bool IsDirectory;
 
        public VirtualFileInode(BinaryReader reader, Stream nameStream)
        {
            DataOffset = reader.ReadUInt32();
 
            var field4 = reader.ReadUInt32();
            var field8 = reader.ReadUInt32();
 
            Size        = (field4 & 0x7FFFFF);
            IsDirectory = (field8 & 1) != 0;
 
            var nameOffset      = (field8 >> 14) & 0x3FFFF;
            var extensionOffset = (field4 >> 23) & 0x1FF;
            var nameInteger     = (field8 >>  1) & 0x1FFF;
 
            nameStream.Seek(nameOffset, SeekOrigin.Begin);
 
            Name = ReadASCII(nameStream).Replace("\x01", nameInteger.ToString());
 
            if (extensionOffset != 0)
            {
                nameStream.Seek(extensionOffset, SeekOrigin.Begin);
 
                Name += "." + ReadASCII(nameStream);
            }
        }
 
        public ArchiveEntry GetArchiveEntry(string parent = "")
        {
            if (!IsDirectory)
            {
                return new ArchiveEntry(parent + Name, Size, Size, DataOffset);
            }
 
            return null;
        }
 
        public IEnumerable<VirtualFileInode> GetChildren(VirtualFileInode[] nodes)
        {
            if (IsDirectory)
            {
                for (var i = DataOffset; i < DataOffset + Size; ++i)
                {
                    yield return nodes[i];
                }
            }
        }
 
        public IEnumerable<ArchiveEntry> GetFiles(VirtualFileInode[] nodes, string parent = "")
        {
            if (IsDirectory)
            {
                var name = parent + Name + "\\";
 
                foreach (var node in GetChildren(nodes))
                {
                    if (node.IsDirectory)
                    {
                        foreach (var file in node.GetFiles(nodes, name))
                        {
                            yield return file;
                        }
                    }
                    else
                    {
                        yield return node.GetArchiveEntry(name);
                    }
                }
            }
        }
 
        public override string ToString()
        {
            return Name;
        }
    }
 
    public static IEnumerable<ArchiveEntry> ReadDaveArchive(BinaryReader reader)
    {
        reader.BaseStream.Seek(0, SeekOrigin.Begin);
 
        CheckMagic(reader.ReadUInt32(), 0x45564144);
 
        var headerCount = reader.ReadUInt32();
        var namesOffset = reader.ReadUInt32();
        var namesSize   = reader.ReadUInt32();
 
        for (var i = 0; i < headerCount; ++i)
        {
            reader.BaseStream.Seek(2048 + (i * 16), SeekOrigin.Begin);
 
            var nameOffset = reader.ReadUInt32();
            var dataOffset = reader.ReadUInt32();
 
            var uncompressedSize = reader.ReadUInt32();
            var compressedSize   = reader.ReadUInt32();
 
            reader.BaseStream.Seek(2048 + namesOffset + nameOffset, SeekOrigin.Begin);
 
            var name = ReadASCII(reader.BaseStream);
 
            yield return new ArchiveEntry(name, uncompressedSize, compressedSize, dataOffset);
        }
    }
 
    public static IEnumerable<ArchiveEntry> ReadPKArchive(BinaryReader reader)
    {
        reader.BaseStream.Seek(-22, SeekOrigin.End);
 
        CheckMagic(reader.ReadUInt32(), 0x06054B50); // ZIPENDLOCATOR
 
        var diskNumber      = reader.ReadUInt16();
        var startDiskNumber = reader.ReadUInt16();
 
        if (diskNumber != startDiskNumber)
        {
            throw new Exception("Incomplete Archive");
        }
 
        var fileCount        = reader.ReadUInt16();
        var filesInDirectory = reader.ReadUInt16();
 
        var directorySize   = reader.ReadUInt32();
        var directoryOffset = reader.ReadUInt32();
 
        var fileCommentLength = reader.ReadUInt16();
 
        var currentOffset = directoryOffset;
 
        while (true)
        {
            reader.BaseStream.Seek(currentOffset, SeekOrigin.Begin);
 
            if (reader.ReadUInt32() != 0x02014B50) // ZIPDIRENTRY
            {
                break;
            }
 
            var versionMadeBy    = reader.ReadUInt16();
            var versionToExtract = reader.ReadUInt16();
            var flags            = reader.ReadUInt16();
 
            var compressionMethod = reader.ReadUInt16();
 
            if (compressionMethod != 0 && compressionMethod != 8)
            {
                throw new Exception("Invalid compression method " + compressionMethod);
            }
 
            var fileTime = reader.ReadUInt16();
            var fileDate = reader.ReadUInt16();
            var crc      = reader.ReadUInt32();
 
            var compressedSize   = reader.ReadUInt32();
            var uncompressedSize = reader.ReadUInt32();
 
            var nameLength    = reader.ReadUInt16();
            var extraLength   = reader.ReadUInt16();
            var commentLength = reader.ReadUInt16();
 
            var diskNumberStart = reader.ReadUInt16();
 
            var internalAttributes = reader.ReadUInt16();
            var externalAttributes = reader.ReadUInt32();
 
            var dataOffset = reader.ReadUInt32(); // ZIPFILERECORD
 
            var name = new string(reader.ReadChars(nameLength));
 
            currentOffset = (uint) reader.BaseStream.Position + extraLength + commentLength;
 
            yield return new ArchiveEntry(name, uncompressedSize, compressedSize, dataOffset + nameLength + 30);
        }
    }
 
    public static IEnumerable<ArchiveEntry> ReadARESArchive(BinaryReader reader)
    {
        reader.BaseStream.Seek(0, SeekOrigin.Begin);
 
        CheckMagic(reader.ReadUInt32(), 0x53455241);
 
        var headerCount    = reader.ReadUInt32();
        var directoryCount = reader.ReadUInt32();
        var namesSize      = reader.ReadUInt32();
        var namesOffset    = 16 + headerCount * 12;
 
        reader.BaseStream.Seek(namesOffset, SeekOrigin.Begin);
 
        var namesStream = new MemoryStream(reader.ReadBytes((int) namesSize));
 
        var nodes = new VirtualFileInode[headerCount];
 
        for (var i = 0; i < headerCount; ++i)
        {
            reader.BaseStream.Seek(16 + (i * 12), SeekOrigin.Begin);
 
            nodes[i] = new VirtualFileInode(reader, namesStream);
        }
 
        for (var i = 0; i < directoryCount; ++i)
        {
            foreach (var file in nodes[i].GetFiles(nodes, ""))
            {
                yield return file;
            }
        }
    }
 
    public static IEnumerable<ArchiveEntry> GetArchiveEntries(Stream archive)
    {
        var binaryReader = new BinaryReader(archive);
 
        binaryReader.BaseStream.Seek(0, SeekOrigin.Begin);
 
        if (binaryReader.ReadUInt32() == 0x45564144) // DAVE
        {
            return ReadDaveArchive(binaryReader);
        }
 
        binaryReader.BaseStream.Seek(0, SeekOrigin.Begin);
 
        if (binaryReader.ReadUInt32() == 0x53455241) // ARES
        {
            return ReadARESArchive(binaryReader);
        }
 
        binaryReader.BaseStream.Seek(-22, SeekOrigin.End);
 
        if (binaryReader.ReadUInt32() == 0x06054B50) // ZIPENDLOCATOR
        {
            return ReadPKArchive(binaryReader);
        }
 
        throw new ArgumentException("Invalid archive stream");
    }
}
 
namespace DaveReader
{
    class MMArchiveReader
    {
        static string ConsoleClearLine()
        {
            return string.Format("\r{0}\r", new string(' ', Console.WindowWidth - 1));
        }
 
        static void OverwriteConsole(string format, params object[ ] args)
        {
            Console.Write(ConsoleClearLine() + format, args);
        }
 
        static void OverwriteConsoleLine(string format, params object[ ] args)
        {
            Console.WriteLine(ConsoleClearLine() + format, args);
        }
 
        static void ExtractArchive(string path, bool merge)
        {
            using (var fileStream = Stream.Synchronized(File.OpenRead(path)))
            {
                var stopwatch = Stopwatch.StartNew();
 
                var tasks = MMArchiveHelper.GetArchiveEntries(fileStream).Select((entry) =>
                {
                    try
                    {
                        return new Task(() =>
                        {
                            var contents = entry.GetContents(fileStream);
 
                            if (contents.Length > 0)
                            {
                                var outputName = merge
                                    ? Path.Combine(Environment.CurrentDirectory, "Extracted", entry.Name)
                                    : Path.Combine(Environment.CurrentDirectory, "Extracted", Path.GetFileNameWithoutExtension(path), entry.Name);
 
                                Directory.CreateDirectory(Path.GetDirectoryName(outputName));
 
                                using (var output = File.OpenWrite(outputName))
                                {
                                    output.Write(contents, 0, contents.Length);
                                }
                            }
                        });
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Failed to extract {0} - {1}", entry.Name, e);
                    }
 
                    return null;
                }).ToArray();
 
                foreach (var task in tasks)
                {
                    task.Start();
                }
 
                var endTask = Task.WhenAll(tasks);
 
                while (!endTask.Wait(100))
                {
                    var completed = tasks.Where(x => x.IsCompleted).ToArray();
 
                    OverwriteConsole("{0} : {1} / {2}", path, completed.Length, tasks.Length);
                }
 
                OverwriteConsoleLine("{0} : {1} files in {2} ms", path, tasks.Length, stopwatch.ElapsedMilliseconds);
            }
        }
 
        static string GetArgOrReadLine(string[] args, uint index)
        {
            return args.Length > index ? args[index] : Console.ReadLine();
        }
 
        static string GetArgOfDefault(string[] args, uint index, string defaultValue)
        {
            return args.Length > index ? args[index] : defaultValue;
        }
 
        static void Main(string[] args)
        {
            Console.Clear();
            Console.ForegroundColor = ConsoleColor.Green;
 
            var path  = GetArgOrReadLine(args, 0);
            var merge = bool.Parse(GetArgOfDefault(args, 1, "false"));
 
            if (File.Exists(path))
            {
                ExtractArchive(path, merge);
            }
            else if (Directory.Exists(path))
            {
                foreach (var file in Directory.GetFiles(path, "*.ar"))
                {
                    ExtractArchive(file, merge);
                }
            }
            else
            {
                Console.WriteLine("Invalid file/path: {0}", path);
            }
 
            Console.WriteLine("Finished");
 
            Console.ReadKey(true);
        }
    }
}

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎