silas1037_Sys0Decompiler/Sys0Decompiler/AldArchiveFile.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using ZLibNet;

namespace Sys0Decompiler
{
    public class AldArchiveFile : ArchiveFile
    {
        public override string[] SupportedExtensions
        {
            get
            {
                return new string[] { ".ald", ".afa", ".alk", ".dat" };
            }
        }

        public byte[] Footer;
        public byte[] IndexBlock;

        public override bool ReadFile(string fileName)
        {
            this.FileType = AldUtil.CheckFileType(fileName);
            if (this.FileType == ArchiveFileType.Invalid)
            {
                return false;
            }

            this.ArchiveFileName = fileName;

            if (FileType == ArchiveFileType.AldFile || FileType == ArchiveFileType.DatFile || FileType == ArchiveFileType.AlkFile)
            {
                ReadAldFile(fileName);
            }
            else if (FileType == ArchiveFileType.Afa1File || FileType == ArchiveFileType.Afa2File)
            {
                bool isVersion2 = FileType == ArchiveFileType.Afa2File;
                using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    var entries = AldUtil.GetAfaFileEntries(fs, ref isVersion2);
                    int i = 0;
                    foreach (var entry in entries)
                    {
                        entry.Parent = this;
                        entry.Index = i;
                        entry.FileNumber = i;
                        entry.HeaderAddress = entry.FileAddress;
                        FileEntries.Add(entry);
                        i++;
                    }
                }
            }
            else
            {
                return false;
                //throw new InvalidDataException("Archive file is invalid");
            }
            return true;
        }

        void ReadAldFile(string fileName)
        {
            long[] fileAddresses;
            byte[][] fileHeaders;
            string[] fileNames;
            long[] fileLengths;
            int[] fileNumbers;

            if (FileType == ArchiveFileType.AldFile)
            {
                int fileLetter = AldUtil.GetAldFileLetter(fileName);
                this.FileLetter = fileLetter;

                fileHeaders = AldHeadersCache.DefaultInstance.GetAldFileHeaders(fileName);
                using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    fileAddresses = AldUtil.GetAldFileAddresses(fs);
                    if (fileHeaders == null)
                    {
                        fileHeaders = AldUtil.GetAldFileHeaders(fs, fileAddresses);
                        AldHeadersCache.DefaultInstance.SaveAldFileHeaders(fileName, fileHeaders);
                    }
                    fileNames = AldUtil.GetAldFileNames(fileHeaders);
                    fileLengths = AldUtil.GetAldFileLengths(fileHeaders, fileAddresses, fs.Length);
                    IndexBlock = AldUtil.GetAldIndexBlock(fs);
                    fileNumbers = AldUtil.GetAldFileNumbers(fs, AldUtil.GetAldFileLetter(fileName));
                }
                this.Footer = null;

                this.FileEntries.Clear();

                for (int i = 0; i < fileAddresses.Length; i++)
                {
                    //ALD files generated by arc_conv are invalid and do not have the footer
                    if (i == fileAddresses.Length - 1 && fileHeaders[i] != null && fileHeaders[i].Length == 16)
                    {
                        this.Footer = fileHeaders[i];
                    }
                    else
                    {
                        var fileEntry = new ArchiveFileEntry();
                        fileEntry.Parent = this;
                        fileEntry.FileAddress = fileAddresses[i] + ((fileHeaders[i] != null) ? (fileHeaders[i].Length) : (0));
                        fileEntry.FileHeader = fileHeaders[i];
                        fileEntry.FileName = fileNames[i];
                        fileEntry.FileNumber = fileNumbers.GetOrDefault(i, 0);
                        fileEntry.FileSize = fileLengths[i];
                        fileEntry.FileLetter = fileLetter;
                        fileEntry.HeaderAddress = fileAddresses[i];
                        fileEntry.Index = i;
                        FileEntries.Add(fileEntry);
                    }
                }
            }
            else if (FileType == ArchiveFileType.DatFile)
            {
                int fileLetter = AldUtil.GetDatFileLetter(fileName);
                this.FileLetter = fileLetter;

                long fileSize;
                using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    fileSize = fs.Length;
                    fileAddresses = AldUtil.GetDatFileAddresses(fs);
                    IndexBlock = AldUtil.GetDatIndexBlock(fs);
                    fileNumbers = AldUtil.GetDatFileNumbers(fs, AldUtil.GetDatFileLetter(fileName));
                }
                this.Footer = null;

                this.FileEntries.Clear();

                string fileNameBase = Path.GetFileNameWithoutExtension(fileName).ToLowerInvariant();
                string fileNameExtension = ".vsp";
                if (fileNameBase.Length >= 2)
                {
                    fileNameBase = fileNameBase.Substring(1);
                }
                if (fileNameBase.Length < 3)
                {
                    fileNameBase = fileNameBase.PadRight(3, '_');
                }
                else
                {
                    fileNameBase = fileNameBase.Substring(0, 3);
                }
                if (fileNameBase == "dis")
                {
                    fileNameExtension = ".sco";
                }
                else if (fileNameBase == "mus")
                {
                    fileNameExtension = ".mus";
                }
                else if (fileNameBase == "map")
                {
                    fileNameExtension = ".map";
                }

                for (int i = 0; i < fileAddresses.Length - 1; i++)
                {
                    var fileEntry = new ArchiveFileEntry();
                    fileEntry.Parent = this;
                    fileEntry.FileAddress = fileAddresses[i];
                    fileEntry.FileHeader = null;
                    fileEntry.FileNumber = fileNumbers.GetOrDefault(i, 0);

                    fileEntry.FileName = fileNameBase + fileEntry.FileNumber.ToString("0000") + fileNameExtension;

                    long nextFileAddress;

                    if (i + 1 < fileAddresses.Length)
                    {
                        nextFileAddress = fileAddresses[i + 1];
                    }
                    else
                    {
                        nextFileAddress = fileSize;
                    }
                    fileEntry.FileSize = nextFileAddress - fileAddresses[i];
                    fileEntry.FileLetter = fileLetter;
                    fileEntry.HeaderAddress = fileAddresses[i];
                    fileEntry.Index = i;
                    FileEntries.Add(fileEntry);
                }
            }
            else if (FileType == ArchiveFileType.AlkFile)
            {
                this.FileLetter = 0;

                long[] fileSizes;
                using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    BinaryReader br = new BinaryReader(fs);
                    fileAddresses = AldUtil.GetAlkFileAddresses(fs);
                    fileSizes = AldUtil.GetAlkFileSizes(fs);

                    this.Footer = null;
                    this.FileEntries.Clear();
                    for (int i = 0; i < fileAddresses.Length; i++)
                    {
                        var fileEntry = new ArchiveFileEntry();
                        fileEntry.Parent = this;
                        fileEntry.FileAddress = fileAddresses[i];
                        fileEntry.FileSize = fileSizes[i];
                        string extension = ".bin";

                        fs.Position = fileEntry.FileAddress;
                        var filePeek = br.ReadBytes(16);
                        var ms = new MemoryStream(filePeek);
                        var br2 = new BinaryReader(ms);
                        do
                        {
                            ms.Position = 0;
                            if (br2.ReadStringFixedSize(2) == "PM")
                            {
                                extension = ".pms";
                                break;
                            }
                            ms.Position = 0;
                            if (br2.ReadStringFixedSize(3) == "QNT")
                            {
                                extension = ".qnt";
                                break;
                            }
                            ms.Position = 0;
                            if (br2.ReadStringFixedSize(3) == "AJP")
                            {
                                extension = ".ajp";
                                break;
                            }
                            ms.Position = 6;
                            if (br2.ReadStringFixedSize(4) == "JFIF")
                            {
                                extension = ".jpg";
                                break;
                            }
                            ms.Position = 0;
                            if (br2.ReadStringFixedSize(4) == "RIFF")
                            {
                                extension = ".wav";
                                break;
                            }
                            ms.Position = 0;
                            if (br2.ReadStringFixedSize(4) == "OggS")
                            {
                                extension = ".ogg";
                                break;
                            }
                            ms.Position = 0;
                            if (br2.ReadByte() == 0x89 && br2.ReadStringFixedSize(3) == "PNG")
                            {
                                extension = ".png";
                                break;
                            }
                        } while (false);


                        fileEntry.FileName = "FILE" + i.ToString("0000") + extension;
                        if (fileEntry.FileSize == 0)
                        {
                            fileEntry.FileName = "--";
                        }

                        fileEntry.FileNumber = i;
                        fileEntry.FileHeader = null;
                        fileEntry.HeaderAddress = fileAddresses[i];
                        fileEntry.Index = i;
                        FileEntries.Add(fileEntry);
                    }
                }
            }
        }

        public override void SaveToFile(string outputFileName)
        {
            bool keepFileLetter = false;
            byte[] indexBlock = new byte[0];
            bool dontPad = false;
            if (this.FileType == ArchiveFileType.Afa1File || this.FileType == ArchiveFileType.Afa2File || this.FileType == ArchiveFileType.AlkFile)
            {
                dontPad = true;
            }

            if (this.IndexBlock != null)
            {
                indexBlock = this.IndexBlock;
            }
            else
            {
                if (this.FileType == ArchiveFileType.AldFile || this.FileType == ArchiveFileType.DatFile)
                {
                    //???
                    int fileLetter = this.FileLetter;
                    if (!keepFileLetter && fileLetter == 0)
                    {
                        fileLetter = AldUtil.GetAldFileLetter(this.ArchiveFileName);
                        this.FileLetter = fileLetter;
                    }

                    if (this.FileType == ArchiveFileType.AldFile)
                    {
                        indexBlock = CreateAldIndexBlock(fileLetter);
                    }
                    else if (this.FileType == ArchiveFileType.DatFile)
                    {
                        indexBlock = CreateDatIndexBlock(fileLetter);
                    }
                }
            }

            int numberOfFiles = this.FileEntries.Count;
            int sizeOfFilesBlock;
            if (this.FileType == ArchiveFileType.AldFile)
            {
                sizeOfFilesBlock = PadToLength((numberOfFiles + 2) * 3, 256);
            }
            else if (this.FileType == ArchiveFileType.DatFile)
            {
                sizeOfFilesBlock = PadToLength((numberOfFiles + 1) * 2, 256);
            }
            else if (this.FileType == ArchiveFileType.AlkFile)
            {
                sizeOfFilesBlock = (numberOfFiles + 1) * 8;
            }
            else if (this.FileType == ArchiveFileType.Afa1File || this.FileType == ArchiveFileType.Afa2File)
            {
                int estimatedHeaderSize = AldUtil.EstimateAfaHeaderSize(this.FileEntries, this.FileType == ArchiveFileType.Afa2File);
                int headerSize = estimatedHeaderSize;
                headerSize += 64;
                headerSize += 4095;
                headerSize /= 4096;
                headerSize *= 4096;

                headerSize += 8;


                sizeOfFilesBlock = headerSize;
            }
            else
            {
                throw new InvalidOperationException();
            }

            List<long> fileAddresses = new List<long>();

            using (var fs = File.OpenWrite(outputFileName))
            {
                var bw = new BinaryWriter(fs);
                SetStreamLength(fs, sizeOfFilesBlock);
                fileAddresses.Add(fs.Position);
                bw.Write(indexBlock);

                for (int i = 0; i < this.FileEntries.Count; i++)
                {
                    var entry = this.FileEntries[i];
                    entry.Index = i;
                    long headerPosition = fs.Position;

                    if (this.FileType == ArchiveFileType.AldFile)
                    {
                        entry.UpdateFileHeader();
                        fs.WriteZeroes(entry.FileHeader.Length);
                    }

                    long filePosition = fs.Position;
                    entry.WriteDataToStream(fs);
                    long afterFile = fs.Position;
                    entry.FileSize = afterFile - filePosition;

                    //do this AFTER writing the file data
                    entry.Parent = this;

                    if (this.FileType == ArchiveFileType.AldFile)
                    {
                        fs.Position = headerPosition;
                        entry.UpdateFileHeader();
                        bw.Write(entry.FileHeader);
                    }
                    entry.FileAddress = filePosition;
                    entry.HeaderAddress = headerPosition;

                    fileAddresses.Add(headerPosition);

                    if (!dontPad)
                    {
                        long newPosition = PadToLength(afterFile, 256);
                        SetStreamLength(fs, newPosition);
                    }
                }
                fileAddresses.Add(fs.Position);
                if (this.FileType == ArchiveFileType.AldFile)
                {
                    UpdateFooter();
                    bw.Write(Footer);
                }

                fs.Position = 0;
                if (this.FileType == ArchiveFileType.AldFile)
                {
                    for (int i = 0; i < fileAddresses.Count; i++)
                    {
                        uint address = (uint)(fileAddresses[i]);
                        bw.Write((byte)((address >> 8) & 0xFF));
                        bw.Write((byte)((address >> 16) & 0xFF));
                        bw.Write((byte)((address >> 24) & 0xFF));
                    }
                }
                else if (this.FileType == ArchiveFileType.DatFile)
                {
                    for (int i = 0; i < fileAddresses.Count; i++)
                    {
                        uint address = (uint)(fileAddresses[i]);
                        bw.Write((ushort)((address + 256) >> 8));
                    }
                }
                else if (this.FileType == ArchiveFileType.AlkFile)
                {
                    fs.Position = 0;
                    bw.WriteStringFixedSize("ALK0", 4);
                    bw.Write(this.FileEntries.Count);
                    for (int i = 1; i < fileAddresses.Count - 1; i++)
                    {
                        long address = fileAddresses[i];
                        long nextAddress = fileAddresses[i + 1];
                        long length = nextAddress - address;
                        bw.Write((uint)address);
                        bw.Write((uint)length);
                    }
                }
                else if (this.FileType == ArchiveFileType.Afa1File || this.FileType == ArchiveFileType.Afa2File)
                {
                    fs.Position = 0;
                    AldUtil.WriteAfaHeader(fs, this.FileEntries, sizeOfFilesBlock - 8, this.FileType == ArchiveFileType.Afa2File);


                }

                fs.Flush();
                fs.Close();
                fs.Dispose();
            }

        }

        //Only used when none created for master file
        byte[] CreateAldIndexBlock(int fileLetter)
        {
            int highestFileNumber = 1;
            if (this.FileEntries.Count > 0)
            {
                highestFileNumber = this.FileEntries.Max(e => e.FileNumber);
            }
            int size = PadToLength(highestFileNumber * 3, 256);

            byte[] indexBlock = new byte[size];
            for (int i = 0; i < this.FileEntries.Count; i++)
            {
                var entry = this.FileEntries[i];
                int fileNumberIndex = entry.FileNumber - 1;

                if (fileNumberIndex >= 0)
                {
                    indexBlock[fileNumberIndex * 3 + 0] = (byte)(fileLetter);
                    indexBlock[fileNumberIndex * 3 + 1] = (byte)((i + 1) & 0xFF);
                    indexBlock[fileNumberIndex * 3 + 2] = (byte)(((i + 1) >> 8) & 0xFF);
                }
            }

            return indexBlock;
        }

        //Only used when none created for master file
        byte[] CreateDatIndexBlock(int fileLetter)
        {
            int highestFileNumber = 1;
            if (this.FileEntries.Count > 0)
            {
                highestFileNumber = this.FileEntries.Max(e => e.FileNumber);
            }
            int size = PadToLength(highestFileNumber * 2, 256);

            byte[] indexBlock = new byte[size];
            for (int i = 0; i < this.FileEntries.Count; i++)
            {
                var entry = this.FileEntries[i];
                int fileNumberIndex = entry.FileNumber - 1;

                if (fileNumberIndex >= 0)
                {
                    indexBlock[fileNumberIndex * 2 + 0] = (byte)(fileLetter);
                    indexBlock[fileNumberIndex * 2 + 1] = (byte)((i + 1) & 0xFF);
                }
            }

            return indexBlock;
        }

        private void UpdateFooter()
        {
            if (this.Footer == null)
            {
                this.Footer = new byte[16];
                var ms = new MemoryStream(this.Footer);
                var bw = new BinaryWriter(ms);

                bw.Write((byte)'N');
                bw.Write((byte)'L');
                bw.Write((byte)0x01);
                bw.Write((byte)0x00);
                bw.Write((int)0x10);
            }
            {
                var ms = new MemoryStream(this.Footer);
                var bw = new BinaryWriter(ms);
                ms.Position = 8;
                bw.Write((byte)0x01);
                bw.Write((short)this.FileEntries.Count);
            }
        }

        public void BuildIndexBlock(IEnumerable<ArchiveFileEntry> fileEntries)
        {
            if (this.FileType == ArchiveFileType.AldFile)
            {
                this.IndexBlock = CreateAldIndexBlock(fileEntries);
            }
            else if (this.FileType == ArchiveFileType.DatFile)
            {
                this.IndexBlock = CreateDatIndexBlock(fileEntries);
            }
            else if (this.FileType == ArchiveFileType.AlkFile)
            {
                //do nothing
            }
            else if (this.FileType == ArchiveFileType.Afa1File || this.FileType == ArchiveFileType.Afa2File)
            {
                //do nothing
            }
        }

        private static byte[] CreateDatIndexBlock(IEnumerable<ArchiveFileEntry> fileEntries)
        {
            int highestFileNumber = 1;
            if (fileEntries.FirstOrDefault() != null)
            {
                highestFileNumber = fileEntries.Max(e => e.FileNumber);
            }
            int size = PadToLength((highestFileNumber) * 2, 256);

            byte[] indexBlock = new byte[size];
            foreach (var entry in fileEntries)
            {
                int fileLetter = entry.FileLetter;
                int fileNumberIndex = entry.FileNumber - 1;
                int i = entry.Index;

                if (fileNumberIndex >= 0)
                {
                    indexBlock[fileNumberIndex * 2 + 0] = (byte)(fileLetter);
                    indexBlock[fileNumberIndex * 2 + 1] = (byte)((i + 1) & 0xFF);
                }
            }
            return indexBlock;
        }

        private static byte[] CreateAldIndexBlock(IEnumerable<ArchiveFileEntry> fileEntries)
        {
            int highestFileNumber = 1;
            if (fileEntries.FirstOrDefault() != null)
            {
                highestFileNumber = fileEntries.Max(e => e.FileNumber);
            }
            int size = PadToLength(highestFileNumber * 3, 256);

            byte[] indexBlock = new byte[size];
            foreach (var entry in fileEntries)
            {
                int fileLetter = entry.FileLetter;
                int fileNumberIndex = entry.FileNumber - 1;
                int i = entry.Index;

                if (fileNumberIndex >= 0)
                {
                    indexBlock[fileNumberIndex * 3 + 0] = (byte)(fileLetter);
                    indexBlock[fileNumberIndex * 3 + 1] = (byte)((i + 1) & 0xFF);
                    indexBlock[fileNumberIndex * 3 + 2] = (byte)(((i + 1) >> 8) & 0xFF);
                }
            }

            return indexBlock;
        }

        internal static partial class AldUtil
        {
            public static long[] GetAldFileAddresses(Stream fs)
            {
                long oldPosition = fs.Position;
                try
                {
                    long fsLength = fs.Length;
                    if (fsLength > uint.MaxValue || fsLength < 512)
                    {
                        //file size is wrong - over 4GB or under 512 bytes
                        return null;
                    }
                    long fileLength = fsLength;

                    var br = new BinaryReader(fs);

                    var first3Bytes = br.ReadBytes(3);
                    fs.Position = 0;
                    if (first3Bytes[2] > 0 || first3Bytes[1] > 3)
                    {
                        //limit of 65536 files, too many files, invalid ALD file.
                    }
                    int headerSize = (first3Bytes[0] << 8) | (first3Bytes[1] << 16) | (first3Bytes[2] << 24);
                    if (headerSize > 196611 || headerSize < 0)
                    {
                        //limit of 65536 files, too many files, invalid ALD file.
                        return null;
                    }
                    if (headerSize > fileLength)
                    {
                        //invalid ALD file - header length is out of bounds
                        return null;
                    }
                    var header = br.ReadBytes(headerSize);
                    fs.Position = 0;

                    int filesLimit = headerSize / 3;
                    List<long> FileAddresses = new List<long>(filesLimit);
                    long lastAddress = 0;
                    bool sawZero = false;
                    for (int i = 1; i < filesLimit; i++)
                    {
                        long address = ((uint)header[i * 3 + 0] << 8) | ((uint)header[i * 3 + 1] << 16) | ((uint)header[i * 3 + 2] << 24);
                        if (address == 0)
                        {
                            sawZero = true;
                            continue;
                        }
                        if (address < lastAddress)
                        {
                            //invalid ALD file - file addresses are not strictly increasing
                            return null;
                        }
                        if (sawZero)
                        {
                            //invalid ALD file - contains nonzero values after a zero
                            return null;
                        }
                        if (address > fileLength)
                        {
                            //invalid ALD file - file address is out of bounds
                            return null;
                        }
                        lastAddress = address;
                        FileAddresses.Add(address);
                    }

                    if (FileAddresses.Count < 1)
                    {
                        //invalid ALD file - no files found inside
                        return null;
                    }
                    if (FileAddresses[0] - headerSize > 196611)
                    {
                        //invalid ALD file - file index table is too big
                        return null;
                    }

                    return FileAddresses.ToArray();
                }
                finally
                {
                    fs.Position = oldPosition;
                }
            }

            public static int[][] GetAldFileNumbers(string fileName)
            {
                List<int[]> fileNumbersList = new List<int[]>();
                string path = Path.GetDirectoryName(fileName);
                string baseName = Path.GetFileNameWithoutExtension(fileName);
                if (baseName.Length >= 2)
                {
                    baseName = baseName.Substring(0, baseName.Length - 1);
                    for (int i = 0; i < 27; i++)
                    {
                        char c = (i == 0) ? '@' : ((char)('A' + i - i));
                        string aldFileName = Path.Combine(path, baseName + c + ".ald");
                        if (File.Exists(aldFileName))
                        {
                            using (var fs = File.OpenRead(aldFileName))
                            {
                                fileNumbersList.Add(GetAldFileNumbers(fs, i));
                            }
                        }
                        else
                        {
                            fileNumbersList.Add(new int[0]);
                        }
                    }
                }
                return fileNumbersList.ToArray();
            }

            public static byte[] GetAldIndexBlock(Stream fs)
            {
                int tableAddress = 0;
                return GetAldIndexBlock(fs, out tableAddress);
            }

            public static byte[] GetAldIndexBlock(Stream fs, out int tableAddress)
            {
                long oldPosition = fs.Position;
                try
                {
                    var br = new BinaryReader(fs);
                    var first3Bytes = br.ReadBytes(3);
                    var firstFileAddress3Bytes = br.ReadBytes(3);
                    tableAddress = (first3Bytes[0] << 8) | (first3Bytes[1] << 16) | (first3Bytes[2] << 24);
                    int firstFileAddress = (firstFileAddress3Bytes[0] << 8) | (firstFileAddress3Bytes[1] << 16) | (firstFileAddress3Bytes[2] << 24);
                    int tableSize = firstFileAddress - tableAddress;
                    fs.Position = tableAddress;

                    List<int> fileNumbersList = new List<int>();

                    var tableData = br.ReadBytes(tableSize);
                    return tableData;
                }
                finally
                {
                    fs.Position = oldPosition;
                }
            }

            public static int[] GetAldFileNumbers(Stream fs, int fileLetter)
            {
                int tableAddress;
                var tableData = GetAldIndexBlock(fs, out tableAddress);
                int tableSize = tableData.Length;

                //long oldPosition = fs.Position;
                //var br = new BinaryReader(fs);
                //var first3Bytes = br.ReadBytes(3);
                //var firstFileAddress3Bytes = br.ReadBytes(3);
                //int tableAddress = (first3Bytes[0] << 8) | (first3Bytes[1] << 16) | (first3Bytes[2] << 24);
                //int firstFileAddress = (firstFileAddress3Bytes[0] << 8) | (firstFileAddress3Bytes[1] << 16) | (firstFileAddress3Bytes[2] << 24);
                //int tableSize = firstFileAddress - tableAddress;
                //fs.Position = tableAddress;

                List<int> fileNumbersList = new List<int>();

                //var tableData = br.ReadBytes(tableSize);
                int maxFileIndex = tableAddress / 3 - 1;
                int maxFileNumber = tableSize / 3;

                for (int fileNumberIndex = 0; fileNumberIndex < maxFileNumber; fileNumberIndex++)
                {
                    int fileNumber = fileNumberIndex + 1;
                    int entryFileLetter = tableData[fileNumberIndex * 3 + 0];
                    int fileIndex = tableData[fileNumberIndex * 3 + 1] + tableData[fileNumberIndex * 3 + 2] * 256;
                    if (fileIndex != 0)
                    {
                        if (fileIndex < maxFileIndex && entryFileLetter == fileLetter)
                        {
                            fileNumbersList.SetOrAdd(fileIndex - 1, fileNumber);
                        }
                    }
                }

                //fs.Position = oldPosition;
                return fileNumbersList.ToArray();
            }

            public static byte[][] GetAldFileHeaders(Stream fs, long[] fileAddresses)
            {
                long oldPosition = fs.Position;
                var br = new BinaryReader(fs);
                List<byte[]> headers = new List<byte[]>(fileAddresses.Length);
                for (int i = 0; i < fileAddresses.Length; i++)
                {
                    long address = fileAddresses[i];
                    fs.Position = address;
                    long headerLength = br.ReadUInt32();
                    if (headerLength != 0x20)
                    {
                        if (headerLength < 16 || headerLength > 256)
                        {
                            headerLength = -1;
                        }
                        long remainingBytes = fs.Length - address;
                        if (remainingBytes == 16 && headerLength == -1)
                        {
                            headerLength = remainingBytes;
                        }
                        if (headerLength > remainingBytes)
                        {
                            headerLength = remainingBytes;
                        }
                    }
                    fs.Position = address;
                    byte[] header;
                    if (headerLength != -1)
                    {
                        header = br.ReadBytes((int)headerLength);
                    }
                    else
                    {
                        header = null;
                    }
                    headers.Add(header);
                }
                fs.Position = oldPosition;
                return headers.ToArray();
            }

            static Encoding shiftJis = Encoding.GetEncoding("shift_jis");

            public static string[] GetAldFileNames(byte[][] fileHeaders)
            {
                List<string> fileNames = new List<string>(fileHeaders.Length);
                for (int i = 0; i < fileHeaders.Length; i++)
                {
                    byte[] header = fileHeaders[i];
                    string fileName;
                    if (header == null)
                    {
                        fileName = null;
                    }
                    else if (header.Length < 32)
                    {
                        fileName = "";
                    }
                    else
                    {
                        int nameLength = 0;
                        int maxNameLength = header.Length - 16;
                        for (nameLength = 0; nameLength < maxNameLength; nameLength++)
                        {
                            if (header[16 + nameLength] == 0)
                            {
                                break;
                            }
                        }

                        fileName = shiftJis.GetString(header, 16, nameLength);
                        try
                        {
                            string dummy = Path.GetExtension(fileName);
                        }
                        catch (ArgumentException ex)
                        {
                            fileName = null;
                        }
                    }
                    fileNames.Add(fileName);
                }
                return fileNames.ToArray();
            }

            public static long[] GetAldFileLengths(byte[][] fileHeaders, long[] fileAddresses, long totalSize)
            {
                List<long> fileLengths = new List<long>(fileHeaders.Length);
                for (int i = 0; i < fileHeaders.Length; i++)
                {
                    long fileAddress = fileAddresses.GetOrNull(i);
                    long nextAddress = fileAddresses.GetOrNull(i + 1);
                    if (nextAddress == 0)
                    {
                        nextAddress = totalSize;
                    }
                    long physicalSize = nextAddress - fileAddress;
                    if (physicalSize < 0)
                    {

                    }

                    byte[] header = fileHeaders[i];
                    long length = 0;
                    if (header != null && header.Length >= 4)
                    {
                        length = BitConverter.ToUInt32(header, 4);
                        if (length > physicalSize || length < 0)
                        {
                            length = physicalSize - header.Length;
                        }
                    }
                    else
                    {
                        length = physicalSize;
                    }
                    fileLengths.Add(length);
                }
                return fileLengths.ToArray();
            }

            public static int GetDatFileLetter(string fileName)
            {
                if (fileName == null)
                {
                    return 1;
                }
                string baseName = Path.GetFileNameWithoutExtension(fileName).ToUpperInvariant();
                if (baseName.Length > 1)
                {
                    char c = baseName[0];
                    if (c == '@') return 0;
                    if (c >= 'A' && c <= 'Z') return (c - 'A' + 1);
                    return 1;
                }
                else
                {
                    return 1;
                }
            }

            public static bool DatFileHasLetter(string fileName)
            {
                if (fileName == null)
                {
                    return false;
                }
                string baseName = Path.GetFileNameWithoutExtension(fileName).ToUpperInvariant();
                if (baseName.Length > 1)
                {
                    char c = baseName[0];
                    if (c == '@') return true;
                    if (c >= 'A' && c <= 'Z') return true;
                    return false;
                }
                else
                {
                    return false;
                }
            }

            public static int GetAldFileLetter(string fileName)
            {
                if (fileName == null)
                {
                    return 1;
                }
                string baseName = Path.GetFileNameWithoutExtension(fileName).ToUpperInvariant();
                if (baseName.Length > 2)
                {
                    baseName = baseName.Substring(baseName.Length - 2);
                    char c = baseName[1];
                    if (c == '@') return 0;
                    if (c >= 'A' && c <= 'Z') return (c - 'A' + 1);
                    return 1;
                }
                else
                {
                    return 1;
                }
            }

            public static bool AldFileHasLetter(string fileName)
            {
                if (fileName == null)
                {
                    return false;
                }
                string baseName = Path.GetFileNameWithoutExtension(fileName).ToUpperInvariant();
                if (baseName.Length > 2)
                {
                    baseName = baseName.Substring(baseName.Length - 2);
                    char c = baseName[1];
                    if (c == '@') return true;
                    if (c >= 'A' && c <= 'Z') return true;
                    return false;
                }
                else
                {
                    return false;
                }
            }

            public static string GetAldFileName(string fileName, int fileLetter)
            {
                if (String.IsNullOrEmpty(fileName))
                {
                    char c = (fileLetter == 0) ? '@' : ((char)('A' + fileLetter - 1));
                    return c + ".ald";
                }
                string path = Path.GetDirectoryName(fileName);
                string baseName = Path.GetFileNameWithoutExtension(fileName);
                if (baseName.Length >= 2)
                {
                    baseName = baseName.Substring(0, baseName.Length - 1);
                    char c = (fileLetter == 0) ? '@' : ((char)('A' + fileLetter - 1));
                    return Path.Combine(path, baseName + c + ".ald");
                }
                return fileName;
            }

            public static string GetDatFileName(string fileName, int fileLetter)
            {
                string path = Path.GetDirectoryName(fileName);
                string baseName = Path.GetFileNameWithoutExtension(fileName);
                if (baseName.Length >= 2)
                {
                    baseName = baseName.Substring(1);
                    char c = (fileLetter == 0) ? '@' : ((char)('A' + fileLetter - 1));
                    return Path.Combine(path, c + baseName + ".dat");
                }
                return fileName;
            }

            public static IEnumerable<string> GetAldOtherFiles(string fileName)
            {
                string path = Path.GetDirectoryName(fileName);
                string baseName = Path.GetFileNameWithoutExtension(fileName);
                if (baseName.Length >= 2)
                {
                    baseName = baseName.Substring(0, baseName.Length - 1);
                    for (int i = 0; i < 27; i++)
                    {
                        char c = (i == 0) ? '@' : ((char)('A' + i - 1));
                        string aldFileName = Path.Combine(path, baseName + c + ".ald");
                        if (File.Exists(aldFileName))
                        {
                            yield return aldFileName;
                        }
                    }
                }
            }

            public static ArchiveFileType CheckFileType(string fileName)
            {
                using (var fs = File.OpenRead(fileName))
                {
                    var aldFileAddresses = AldUtil.GetAldFileAddresses(fs);
                    if (aldFileAddresses != null)
                    {
                        return ArchiveFileType.AldFile;
                    }

                    var datFileAddresses = AldUtil.GetDatFileAddresses(fs);
                    if (datFileAddresses != null)
                    {
                        return ArchiveFileType.DatFile;
                    }

                    var alkFileAddresses = AldUtil.GetAlkFileAddresses(fs);
                    if (alkFileAddresses != null)
                    {
                        return ArchiveFileType.AlkFile;
                    }

                    bool isVersion2 = false;
                    var afa1FileEntries = AldUtil.GetAfaFileEntries(fs, ref isVersion2);
                    if (afa1FileEntries != null)
                    {
                        if (!isVersion2)
                        {
                            return ArchiveFileType.Afa1File;
                        }
                        else
                        {
                            return ArchiveFileType.Afa2File;
                        }
                    }

                    //var br = new BinaryReader(fs);
                    //var vfsFileEntries = VfsArchiveFile.ListFilesInVFS(br);
                    //if (vfsFileEntries != null)
                    //{
                    //    return ArchiveFileType.SofthouseCharaVfsFile;
                    //}

                    //ArcArchiveFile.
                }
                return ArchiveFileType.Invalid;
            }

            public static ArchiveFileEntry[] GetAfaFileEntries(FileStream fs, ref bool isVersion2)
            {
                isVersion2 = false;

                long oldPosition = fs.Position;
                try
                {
                    byte[] afahSignature;
                    int headerLength;
                    byte[] alicArchSignature;
                    int version, unknown3;
                    long dataBase;

                    byte[] infoTagSignature;
                    int infoTagSize;
                    int tocLengthCompressed, tocLengthDecompressed, entryCount;

                    byte[] dataSignature;
                    long dataLength;

                    long fileLength = fs.Length;

                    if (fileLength < 64 || fileLength > uint.MaxValue)
                    {
                        return null;
                    }

                    var br = new BinaryReader(fs);
                    br.BaseStream.Position = 0;

                    afahSignature = br.ReadBytes(4);
                    headerLength = br.ReadInt32();
                    alicArchSignature = br.ReadBytes(8);

                    if (ASCIIEncoding.ASCII.GetString(afahSignature) != "AFAH")
                    {
                        return null;
                    }
                    if (ASCIIEncoding.ASCII.GetString(alicArchSignature) != "AlicArch")
                    {
                        return null;
                    }
                    if (headerLength != 0x1C)
                    {
                        return null;
                    }
                    version = br.ReadInt32();
                    unknown3 = br.ReadInt32();
                    dataBase = br.ReadUInt32();

                    if (version != 1 || unknown3 != 1)
                    {
                        //these are usually 1, don't really care though
                    }

                    if (dataBase + 8 > fileLength)
                    {
                        return null;
                    }

                    infoTagSignature = br.ReadBytes(4);
                    infoTagSize = br.ReadInt32();
                    tocLengthCompressed = infoTagSize - 16;
                    tocLengthDecompressed = br.ReadInt32();
                    entryCount = br.ReadInt32();

                    if (ASCIIEncoding.ASCII.GetString(infoTagSignature) != "INFO")
                    {
                        return null;
                    }

                    if (tocLengthCompressed < 0)
                    {
                        return null;
                    }
                    if (infoTagSize + headerLength + 4 > dataBase)
                    {
                        return null;
                    }
                    if (tocLengthDecompressed < 0)
                    {
                        return null;
                    }
                    if (entryCount < 0)
                    {
                        return null;
                    }
                    //minimum entry length is 6.5 words long
                    if (tocLengthDecompressed < entryCount * (6 * 4 + 2))
                    {
                        return null;
                    }

                    long pos2 = br.BaseStream.Position;
                    //validate the DATA tag
                    br.BaseStream.Position = dataBase;
                    dataSignature = br.ReadBytes(4);
                    if (ASCIIEncoding.ASCII.GetString(dataSignature) != "DATA")
                    {
                        return null;
                    }

                    dataLength = br.ReadUInt32();
                    if (dataLength + dataBase > fileLength)
                    {
                        return null;
                    }
                    if (dataLength + dataBase != fileLength)
                    {
                        //usually it matches the file size exactly, but if it doesn't?
                    }

                    br.BaseStream.Position = pos2;

                    byte[] compressedToc = br.ReadBytes(tocLengthCompressed);
                    byte[] decompressedToc = Decompress(compressedToc, tocLengthDecompressed);
                    if (decompressedToc == null)
                    {
                        return null;
                    }

                    //try version 1 and version 2 file
                    ArchiveFileEntry[] entries = null;
                    if (version != 2)
                    {
                        //try version 1 first
                        entries = GetAfaFileEntries(decompressedToc, dataBase, entryCount, fileLength, false);
                        if (entries != null)
                        {
                            return entries;
                        }
                        entries = GetAfaFileEntries(decompressedToc, dataBase, entryCount, fileLength, true);
                        if (entries != null)
                        {
                            isVersion2 = true;
                            return entries;
                        }
                    }
                    else
                    {
                        //try version 2 first
                        entries = GetAfaFileEntries(decompressedToc, dataBase, entryCount, fileLength, true);
                        if (entries != null)
                        {
                            isVersion2 = true;
                            return entries;
                        }
                        entries = GetAfaFileEntries(decompressedToc, dataBase, entryCount, fileLength, false);
                        if (entries != null)
                        {
                            return entries;
                        }
                    }
                    return null;
                }
                finally
                {
                    fs.Position = oldPosition;
                }
            }

            static Encoding shiftJisWithThrow = Encoding.GetEncoding("shift_jis", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);

            private static ArchiveFileEntry[] GetAfaFileEntries(byte[] decompressedToc, long dataBase, int entryCount, long fileSize, bool isVersion2)
            {
                try
                {
                    List<ArchiveFileEntry> list = new List<ArchiveFileEntry>();
                    var ms = new MemoryStream(decompressedToc);
                    var br = new BinaryReader(ms);
                    int index = 0;
                    while (ms.Position < ms.Length)
                    {
                        int fileNameLength = br.ReadInt32();
                        int fileNameLengthPadded = br.ReadInt32();
                        if (fileNameLength <= 0 || fileNameLength > 512)
                        {
                            return null;
                        }
                        if (fileNameLengthPadded <= 0 || fileNameLengthPadded > 512)
                        {
                            return null;
                        }
                        if (fileNameLength > fileNameLengthPadded)
                        {
                            return null;
                        }
                        var fileNameBytes = br.ReadBytes(fileNameLengthPadded);
                        //if file name bytes contains any nulls, invalid filename
                        if (fileNameBytes.Take(fileNameLength).AnyEqualTo((byte)0))
                        {
                            return null;
                        }
                        string fileName = shiftJisWithThrow.GetString(fileNameBytes, 0, fileNameLength);
                        int unknown1, unknown2, unknown3 = 0;
                        long offset, length;

                        unknown1 = br.ReadInt32();
                        unknown2 = br.ReadInt32();
                        if (!isVersion2)
                        {
                            unknown3 = br.ReadInt32();
                        }

                        offset = br.ReadUInt32();
                        length = br.ReadUInt32();

                        if (offset + dataBase + length > fileSize)
                        {
                            return null;
                        }

                        ArchiveFileEntry entry = new ArchiveFileEntry();
                        entry.FileAddress = offset + dataBase;
                        entry.FileSize = length;
                        entry.FileName = fileName;
                        entry.Index = index;
                        entry.FileNumber = -1;
                        entry.FileHeader = null;
                        entry.FileLetter = 0;
                        entry.HeaderAddress = -1;
                        entry.ReplacementFileName = null;
                        entry.ReplacementBytes = null;
                        entry.Parent = null;

                        list.Add(entry);
                        index++;
                    }
                    if (list.Count != entryCount)
                    {
                        return null;
                    }
                    return list.ToArray();
                }
                catch
                {
                    return null;
                }
            }

            public static void WriteAfaHeader(FileStream fs, IList<ArchiveFileEntry> archiveFiles, long dataBase, bool isVersion2)
            {
                //this is called after the archiveFiles collection has been updated to the correct data
                BinaryWriter bw = new BinaryWriter(fs);
                bw.WriteStringFixedSize("AFAH", 4);
                bw.Write((int)0x1C);
                bw.WriteStringFixedSize("AlicArch", 8);
                bw.Write((int)(isVersion2 ? 2 : 1)); //unknown2
                bw.Write((int)1); //unknown3
                bw.Write((uint)dataBase);

                bw.WriteStringFixedSize("INFO", 4);
                var tocData = CreateAfaToc(archiveFiles, dataBase, isVersion2);
                var compressedTocData = Compress(tocData);
                //var test1 = Decompress(compressedTocData.Take(compressedTocData.Length - 4).ToArray(), tocData.Length);
                //var test2 = Decompress(compressedTocData.Take(compressedTocData.Length - 5).ToArray(), tocData.Length);
                //var test3 = Decompress(compressedTocData.Take(compressedTocData.Length - 6).ToArray(), tocData.Length);
                //var test4 = Decompress(compressedTocData.Take(compressedTocData.Length - 7).ToArray(), tocData.Length);
                //var test5 = Decompress(compressedTocData.Take(compressedTocData.Length - 8).ToArray(), tocData.Length);
                //var test6 = Decompress(compressedTocData.Take(compressedTocData.Length - 20).ToArray(), tocData.Length);
                //var test7 = Decompress(compressedTocData.Take(compressedTocData.Length - 24).ToArray(), tocData.Length);



                int tocLengthCompressed = compressedTocData.Length;
                int tocLengthDecompressed = tocData.Length;
                int entryCount = archiveFiles.Count;
                int infoTagSize = tocLengthCompressed + 16;
                bw.Write((int)infoTagSize);
                bw.Write((int)tocLengthDecompressed);
                bw.Write((int)entryCount);
                bw.Write(compressedTocData);

                long dummyLength = (dataBase - bw.BaseStream.Position);
                if (dummyLength >= 8)
                {
                    bw.WriteStringFixedSize("DUMM", 4); //arc_conv writes a DUMM tag to pad the header
                    bw.Write((uint)dummyLength);
                    bw.BaseStream.WriteZeroes(dummyLength - 8);
                }
                else if (dummyLength < 8 && dummyLength >= 0)
                {
                    bw.BaseStream.WriteZeroes(dummyLength);
                }
                else
                {
                    //should not happen
                }
                bw.WriteStringFixedSize("DATA", 4);
                long dataSize = bw.BaseStream.Length - dataBase;
                bw.Write((uint)dataSize);
            }

            public static int EstimateAfaHeaderSize(IList<ArchiveFileEntry> archiveFiles, bool isVersion2)
            {
                int headerSize = 0x1C + 0x10;
                int minEntrySize = 6 * 4 + (isVersion2 ? 0 : 4);
                int textLength = archiveFiles.Sum(f => shiftJis.GetByteCount(f.FileName) + 1);
                return headerSize + minEntrySize * archiveFiles.Count + textLength;
            }

            private static byte[] CreateAfaToc(IList<ArchiveFileEntry> archiveFiles, long dataBase, bool isVersion2)
            {
                MemoryStream ms = new MemoryStream();
                BinaryWriter bw = new BinaryWriter(ms);

                foreach (var entry in archiveFiles)
                {
                    byte[] fileNameBytes = shiftJis.GetBytes(entry.FileName);
                    int fileNameLengthPadded = (fileNameBytes.Length) | 3 + 1;

                    bw.Write((int)fileNameBytes.Length);
                    bw.Write((int)(fileNameLengthPadded));
                    bw.Write(fileNameBytes);
                    int paddingByteCount = fileNameLengthPadded - fileNameBytes.Length;
                    for (int i = 0; i < paddingByteCount; i++)
                    {
                        bw.Write((byte)0);
                    }
                    bw.Write((int)0x2d99e180); //unknown1  0x2d99e180
                    bw.Write((int)0x01cf5475); //unknown2  0x01cf5475
                    if (!isVersion2)
                    {
                        bw.Write((int)0); //unknown3
                    }
                    bw.Write((uint)(entry.FileAddress - dataBase));
                    bw.Write((uint)(entry.FileSize));
                }
                return ms.ToArray();
            }

            private static byte[] Decompress(byte[] compressedData, int decompressedLength)
            {
                var ms = new MemoryStream(compressedData);
                var zlibStream = new ZLibStream(ms, CompressionMode.Decompress);
                try
                {
                    var br = new BinaryReader(zlibStream);
                    var bytes = br.ReadBytes(decompressedLength);
                    return bytes;
                }
                catch
                {
                    return null;
                }
            }

            private static byte[] Compress(byte[] uncompressedData)
            {
                var ms = new MemoryStream();
                var zlibStream = new ZLibStream(ms, CompressionMode.Compress, CompressionLevel.Level9);
                try
                {
                    var bw = new BinaryWriter(zlibStream);
                    bw.Write(uncompressedData);
                    zlibStream.Flush();
                    zlibStream.Close();
                    return ms.ToArray();
                }
                catch
                {
                    return null;
                }
            }

            public static long[] GetAlkFileAddresses(Stream fs)
            {
                long oldPosition = fs.Position;
                try
                {
                    long fsLength = fs.Length;
                    long fileLength = fsLength;

                    var br = new BinaryReader(fs);

                    string signature = br.ReadStringFixedSize(4);
                    if (signature != "ALK0") return null;

                    int fileCount = br.ReadInt32();
                    if (fileCount < 0 || fileCount * 8 > fileLength)
                    {
                        return null;
                    }

                    int headerSize = fileCount * 8;
                    if (headerSize > fileLength)
                    {
                        //invalid DAT file - header size is too big for the container file size
                        return null;
                    }
                    var header = br.ReadBytes(headerSize);
                    int minAddress = headerSize + 8;

                    List<long> FileAddresses = new List<long>(fileCount);
                    long lastAddress = 0;
                    for (int i = 0; i < fileCount; i++)
                    {
                        long address = BitConverter.ToUInt32(header, i * 8);
                        long fileSize = BitConverter.ToUInt32(header, i * 8 + 4);
                        if (address < minAddress)
                        {
                            return null;
                        }
                        if (address < lastAddress)
                        {
                            return null;
                        }
                        if (address > fileLength)
                        {
                            return null;
                        }
                        lastAddress = address + fileSize;
                        FileAddresses.Add(address);
                    }

                    if (FileAddresses.Count < 1)
                    {
                        return null;
                    }

                    return FileAddresses.ToArray();
                }
                finally
                {
                    fs.Position = oldPosition;
                }
            }

            public static long[] GetAlkFileSizes(Stream fs)
            {
                long oldPosition = fs.Position;
                try
                {
                    long fsLength = fs.Length;
                    long fileLength = fsLength;

                    var br = new BinaryReader(fs);

                    string signature = br.ReadStringFixedSize(4);
                    if (signature != "ALK0") return null;

                    int fileCount = br.ReadInt32();
                    if (fileCount < 0 || fileCount * 8 > fileLength)
                    {
                        return null;
                    }

                    int headerSize = fileCount * 8;
                    if (headerSize > fileLength)
                    {
                        //invalid DAT file - header size is too big for the container file size
                        return null;
                    }
                    var header = br.ReadBytes(headerSize);
                    int minAddress = headerSize + 8;

                    List<long> FileSizes = new List<long>(fileCount);
                    long lastAddress = 0;
                    for (int i = 0; i < fileCount; i++)
                    {
                        long address = BitConverter.ToUInt32(header, i * 8);
                        long fileSize = BitConverter.ToUInt32(header, i * 8 + 4);
                        if (address < minAddress)
                        {
                            return null;
                        }
                        if (address < lastAddress)
                        {
                            return null;
                        }
                        if (address > fileLength)
                        {
                            return null;
                        }
                        lastAddress = address + fileSize;
                        FileSizes.Add(fileSize);
                    }

                    if (FileSizes.Count < 1)
                    {
                        return null;
                    }

                    return FileSizes.ToArray();
                }
                finally
                {
                    fs.Position = oldPosition;
                }
            }

            public static long[] GetDatFileAddresses(Stream fs)
            {
                long oldPosition = fs.Position;
                try
                {
                    long fsLength = fs.Length;
                    if (fsLength > 16 * 1024 * 1024 || fsLength < 512)
                    {
                        //file is a bad size - over 16MB or under 512 bytes
                        return null;
                    }
                    long fileLength = fsLength;

                    var br = new BinaryReader(fs);

                    int headerSize = (br.ReadUInt16() - 1) * 256;
                    if (headerSize > 512)
                    {
                        //limit of 255 files, too many files, invalid DAT file.
                        return null;
                    }
                    if (headerSize > fileLength)
                    {
                        //invalid DAT file - header size is too big for the container file size
                        return null;
                    }
                    fs.Position = 0;
                    var header = br.ReadBytes(headerSize);

                    int filesLimit = headerSize / 2;
                    List<long> FileAddresses = new List<long>(filesLimit);
                    long lastAddress = 0;
                    bool sawZero = false;
                    for (int i = 1; i < filesLimit; i++)
                    {
                        long address = BitConverter.ToUInt16(header, i * 2);
                        if (address == 0)
                        {
                            sawZero = true;
                            continue;
                        }
                        address = (address - 1) * 256;
                        if (address < lastAddress)
                        {
                            //invalid DAT file - addresses are not strictly increasing
                            return null;
                        }
                        if (sawZero)
                        {
                            //invalid DAT file - nonzero number present after zero
                            return null;
                        }
                        if (address > fileLength)
                        {
                            //invalid DAT file - file address is too big for the container file size
                            return null;
                        }
                        lastAddress = address;
                        FileAddresses.Add(address);
                    }

                    if (FileAddresses.Count < 1)
                    {
                        //invalid DAT file - no files inside
                        return null;
                    }

                    return FileAddresses.ToArray();
                }
                finally
                {
                    fs.Position = oldPosition;
                }
            }

            public static int[][] GetDatFileNumbers(string fileName)
            {
                List<int[]> fileNumbersList = new List<int[]>();
                string path = Path.GetDirectoryName(fileName);
                string baseName = Path.GetFileNameWithoutExtension(fileName);
                if (baseName.Length >= 2)
                {
                    baseName = baseName.Substring(1);
                    for (int i = 0; i < 27; i++)
                    {
                        char c = (i == 0) ? '@' : ((char)('A' + i - i));
                        string datFileName = Path.Combine(path, c + baseName + ".dat");
                        if (File.Exists(datFileName))
                        {
                            using (var fs = File.OpenRead(datFileName))
                            {
                                fileNumbersList.Add(GetDatFileNumbers(fs, i));
                            }
                        }
                        else
                        {
                            fileNumbersList.Add(new int[0]);
                        }
                    }
                }
                return fileNumbersList.ToArray();
            }

            public static int[] GetDatFileNumbers(Stream fs, int fileLetter)
            {
                long oldPosition = fs.Position;
                fs.Position = 0;
                var br = new BinaryReader(fs);
                int tableAddress = (br.ReadUInt16() - 1) * 256;
                int firstFileAddress = (br.ReadUInt16() - 1) * 256;
                int tableSize = firstFileAddress - tableAddress;
                fs.Position = tableAddress;

                List<int> fileNumbersList = new List<int>();

                var tableData = br.ReadBytes(tableSize);
                int maxFileIndex = tableAddress / 2;
                int maxFileNumber = tableSize / 2;

                for (int fileNumberIndex = 0; fileNumberIndex < maxFileNumber; fileNumberIndex++)
                {
                    int fileNumber = fileNumberIndex + 1;
                    int entryFileLetter = tableData[fileNumberIndex * 2 + 0];
                    int fileIndex = tableData[fileNumberIndex * 2 + 1];
                    if (fileIndex != 0)
                    {
                        if (fileIndex < maxFileIndex && entryFileLetter == fileLetter)
                        {
                            fileNumbersList.SetOrAdd(fileIndex - 1, fileNumber);
                        }
                    }
                }

                fs.Position = oldPosition;
                return fileNumbersList.ToArray();
            }

            public static byte[] GetDatIndexBlock(Stream fs)
            {
                long oldPosition = fs.Position;
                fs.Position = 0;
                var br = new BinaryReader(fs);
                int tableAddress = (br.ReadUInt16() - 1) * 256;
                int firstFileAddress = (br.ReadUInt16() - 1) * 256;
                int tableSize = firstFileAddress - tableAddress;
                fs.Position = tableAddress;

                var tableData = br.ReadBytes(tableSize);

                fs.Position = oldPosition;
                return tableData;
            }


            public static IEnumerable<string> GetDatOtherFiles(string fileName)
            {
                string path = Path.GetDirectoryName(fileName);
                string baseName = Path.GetFileNameWithoutExtension(fileName);
                if (baseName.Length >= 2)
                {
                    baseName = baseName.Substring(1);
                    for (int i = 0; i < 27; i++)
                    {
                        char c = (i == 0) ? '@' : ((char)('A' + i - 1));
                        string datFileName = Path.Combine(path, c + baseName + ".dat");
                        if (File.Exists(datFileName))
                        {
                            yield return datFileName;
                        }
                    }
                }
            }
        }

        public static ArchiveFileType GetArchiveFileType(string ext, int version)
        {
            ext = ext.ToLowerInvariant();
            switch (ext)
            {
                case ".ald":
                    return ArchiveFileType.AldFile;
                case ".alk":
                    return ArchiveFileType.AlkFile;
                case ".dat":
                    return ArchiveFileType.DatFile;
                case ".afa":
                    if (version >= 2)
                    {
                        return ArchiveFileType.Afa2File;
                    }
                    else
                    {
                        return ArchiveFileType.Afa1File;
                    }
            }
            return ArchiveFileType.Invalid;
        }
    }
}
