using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;

namespace isbextract {
    class Program {
        static int extractCount = 0;
        static ArrayList extractedFiles = new ArrayList();

        static void Main(string[] args) {
            Console.WriteLine("Mass Effect .isb extractor.");
            if (args != null && args.Length == 1 && File.Exists(args[0])) {
                extractFiles(args[0]);
            } else {
                Console.WriteLine("usage: isbextract <filename.isb>");
            }
        }

        private static void processList(EasyFileStream fs, long endidx, string dirname) {
            fs.Read(0, 8);
            string currentSection = ASCIIEncoding.ASCII.GetString(fs.buffer);
            fs.ReadDWORD();
            uint currentOffset = BitConverter.ToUInt32(fs.buffer, 0);
            fs.Read(0, (int)currentOffset);
            string exportFilename = ASCIIEncoding.Unicode.GetString(fs.buffer);
            exportFilename = exportFilename.TrimEnd('\0');
           
            if (dirname != null) exportFilename = dirname + "\\" + exportFilename;

            if (currentSection == "fldrtitl") {
                Directory.CreateDirectory(exportFilename);
                while (fs.Position < endidx) {
                    fs.ReadDWORD();
                    currentSection = ASCIIEncoding.ASCII.GetString(fs.buffer);
                    fs.ReadDWORD();
                    currentOffset = BitConverter.ToUInt32(fs.buffer, 0);
                    if (currentSection != "LIST") {
                        fs.Seek(currentOffset, SeekOrigin.Current);
                    } else {
                        processList(fs, fs.Position + currentOffset, exportFilename);
                    }
                }
                
                fs.Seek(endidx, SeekOrigin.Begin);
            } else {
                while (fs.Position < endidx) {
                    fs.ReadDWORD();
                    currentSection = ASCIIEncoding.ASCII.GetString(fs.buffer);
                    fs.ReadDWORD();
                    currentOffset = BitConverter.ToUInt32(fs.buffer, 0);
                    if (currentSection == "data") {

                        string filename = "";
                        string fileext = "";
                        if (exportFilename.IndexOf('.') >= 0) {
                            filename = exportFilename.Substring(0, exportFilename.LastIndexOf('.'));
                            fileext = exportFilename.Substring(exportFilename.LastIndexOf('.'));
                        } else {
                            filename = exportFilename;
                        }

                        long pos = fs.Position;
                        fs.Read(0, 4);
                        string header = ASCIIEncoding.ASCII.GetString(fs.buffer);
                        if (header == "OggS") {
                            fileext = ".ogg";
                        }
                        fs.Seek(pos, SeekOrigin.Begin);                        

                        if (extractedFiles.Contains(exportFilename)) {
                            filename += " (dup)";
                        }

                        exportFilename = filename + fileext;

                        EasyFileStream data = new EasyFileStream(exportFilename, FileMode.Create, FileAccess.Write);

                        int rest = (int)currentOffset % 256;
                        int loops = ((int)currentOffset - rest) / 256;
                        for (int i = 0; i < loops; i++) {
                            fs.ReadNext(256);
                            data.Write(fs.buffer, 0, 256);
                        }

                        fs.ReadNext(rest);
                        data.Write(fs.buffer, 0, rest);

                        data.Close();
                        data.Dispose();
                        data = null;
                        extractCount++;
                        extractedFiles.Add(exportFilename);
                        Console.WriteLine("extracted("+extractCount.ToString()+"): " + exportFilename);
                        fs.Seek(endidx, SeekOrigin.Begin);
                    } else {
                        fs.Seek(currentOffset, SeekOrigin.Current);
                    }
                }                    
            }
        }

        private static void extractFiles(string filename) {
            UInt32 fsize = 0;
            string currentSection = "";
            UInt32 currentOffset = 0;            

            EasyFileStream fs;
            try {
                fs = new EasyFileStream(filename, FileMode.Open, FileAccess.Read);
            } catch (FileNotFoundException e) {
                Console.WriteLine("Couldn't find file '" + e.FileName + "'.");
                return;
            } catch {
                Console.WriteLine("An unknown error occured while trying to access the file.");
                return;
            }
            fs.ReadDWORD();
            if (ASCIIEncoding.ASCII.GetString(fs.buffer, 0, 4) != "RIFF") {
                Console.WriteLine("'" + filename + "' is no isb file.");
                return;
            }

            try {
                fs.ReadDWORD();
                fsize = BitConverter.ToUInt32(fs.buffer, 0);

                // file name
                fs.Read(0, 8);
                currentSection = ASCIIEncoding.ASCII.GetString(fs.buffer);
                fs.ReadDWORD();
                currentOffset = BitConverter.ToUInt32(fs.buffer, 0);
                fs.Read(0, (int)currentOffset);

                while (fs.Position < fsize) {
                    fs.ReadDWORD();
                    currentSection = ASCIIEncoding.ASCII.GetString(fs.buffer);
                    fs.ReadDWORD();
                    currentOffset = BitConverter.ToUInt32(fs.buffer, 0);
                    if (currentSection != "LIST") {
                        fs.Seek(currentOffset, SeekOrigin.Current);
                    } else {
                        processList(fs, fs.Position + currentOffset, null);
                    }
                }
            } catch(Exception ex) {
                Console.WriteLine("Error: " + ex.Message);
            }
        }
    }
}
