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


namespace RMDEC
{
    public class VXAProject
    {
        public VXAProject() { }

        //Private Variables
        private byte[] encryptionKey = new byte[0x4];

        private string gameTitle;
        private string exeName;
        private string filePath;

        private Stream rgss3aStream;
        private struct ArchiveFile
        {
            public UInt32 Offset;
            public UInt32 Size;
            public Byte[] Key;
            public String Name;
        }

        private List<ArchiveFile> archiveFileList = new List<ArchiveFile>();

        //Public Variables 

        public List<String> FileList = new List<string>();

        public String GameTitle
        {
            get
            {
                return gameTitle;
            }
        }

        public String ExeName
        {
            get
            {
                return exeName;
            }
        }


        public String FilePath
        {
            get
            {
                return filePath;
            }
        }

        public byte[] EncryptionKey
        { 
            get
            {
                return encryptionKey;
            }
        }

        //Private Functions
        private uint readUInt32()
        {
            byte[] intBytes = new byte[0x4];
            rgss3aStream.Read(intBytes, 0x00, 0x4);
            return BitConverter.ToUInt32(intBytes, 0x00);
        }
        private byte[] decryptData(uint size)
        {
            byte[] data = new byte[size];
            rgss3aStream.Read(data, 0x00, data.Length);
            return RMProject.Xor(data, encryptionKey);
        }
        private uint decryptUint32()
        {
            byte[] intBytes = decryptData(0x4);
            return BitConverter.ToUInt32(intBytes, 0x00);

        }
        private string decryptString(uint length)
        {
            byte[] strBytes = decryptData(length);
            return Encoding.UTF8.GetString(strBytes);
        }

        

        private static string read_ini_value(string iniFile, string column,string key,string defaultValue="")
        {
            string[] lines = File.ReadAllLines(iniFile);
            string value = defaultValue;
            
            bool inColumn = false;
            foreach(string line in lines)
            {
                if (line == ("[" + column + "]")) 
                {
                    inColumn = true;
                    continue;
                }
                
                if(!inColumn)
                {
                    continue;
                }

                string[] iniKeyValue = line.Split('=');

                if (iniKeyValue[0] == key)
                {
                    value = iniKeyValue[1];
                    break;
                }

                if(line.StartsWith("["))
                {
                    break;
                }
                
            }
            return value;
        }



        private byte[] decryptFileData(byte[] input, byte[] keydata)
        {
            long size = input.LongLength;
            byte[] output = new byte[size];

            uint keyInt = BitConverter.ToUInt32(keydata, 0x00);
            byte[] derivedKeyData = new byte[keydata.Length];
            Array.Copy(keydata, derivedKeyData, 0x4);

            for (int i = 0; i < size; i++)
            {
                if (i != 0 && i % 4 == 0)
                {
                    // Derive new key
                    keyInt = ((keyInt * 7) + 3);
                    derivedKeyData = BitConverter.GetBytes(keyInt);
                }

                output[i] = (byte)(input[i] ^ derivedKeyData[i % derivedKeyData.Length]);
            }

            //Final Derive (incase of >MAX_SIZE)
            keyInt = ((keyInt * 7) + 3);
            derivedKeyData = BitConverter.GetBytes(keyInt);
            Array.Copy(derivedKeyData, keydata, 0x4);

            return output;
        }
        
        //Public Functions
        public static VXAProject ParseRgss3a(string file)
        {
            VXAProject vxp = new VXAProject();
            FileStream rgss3a = File.OpenRead(file);
            
            byte[] magic = new byte[0x06];
            rgss3a.Read(magic, 0x00, 0x06);
            string magicStr = Encoding.UTF8.GetString(magic);

            if (magicStr != "RGSSAD")
            {
                throw new InvalidDataException("Not a valid rgss3a!");
            }
            string workDir = Path.GetDirectoryName(file);
            vxp.exeName = Path.GetFileNameWithoutExtension(file);
            string iniFilePath = Path.Combine(workDir, vxp.exeName + ".ini");

            vxp.rgss3aStream = rgss3a;
            try
            {
                vxp.gameTitle = read_ini_value(iniFilePath, "Game", "Title", vxp.exeName);
            }
            catch (Exception)
            {
                vxp.gameTitle = vxp.exeName;
            }

            vxp.filePath = workDir;

            rgss3a.Seek(0x2, SeekOrigin.Current);
           
            vxp.encryptionKey = BitConverter.GetBytes((((vxp.readUInt32()) * 9) + 3));
        

            return vxp;
        }


        public void DecryptFile(int fileIndex, Stream outStream)
        {
            int MAX_SIZE = 0x20000000; //512MB (MUST BE DIVISIBLE BY 4)

            outStream.SetLength(0);
            ArchiveFile fileData = archiveFileList[fileIndex];
            byte[] keyData = fileData.Key;

            rgss3aStream.Seek(fileData.Offset, SeekOrigin.Begin);
            uint size = fileData.Size;
            

            for (int i = 0; i < size; i += MAX_SIZE)
            {
                
                if (size > MAX_SIZE)
                {
                    byte[] gameData = new byte[MAX_SIZE];
                    rgss3aStream.Read(gameData, 0x00, MAX_SIZE);
                    gameData = decryptFileData(gameData, keyData);
                    outStream.Write(gameData, 0x00, gameData.Length);
                }
                else
                {
                    byte[] gameData = new byte[size];
                    rgss3aStream.Read(gameData, 0x00, gameData.Length);
                    gameData = decryptFileData(gameData, keyData);
                    outStream.Write(gameData, 0x00, gameData.Length);
                }
            }

        }

        public void PopulateFileList()
        {
            archiveFileList.Clear();
            FileList.Clear();

            while (true)
            {

                uint offset = decryptUint32();
                if (offset == 0x00)
                {
                    break;
                }

                ArchiveFile file;

                file.Offset = offset;
                file.Size = decryptUint32();
                file.Key = decryptData(0x4);
                uint nameLen = decryptUint32();
                file.Name = decryptString(nameLen);

                archiveFileList.Add(file);
                FileList.Add(file.Name);
            }
        }
        public void Close()
        {
            rgss3aStream.Close();
            rgss3aStream.Dispose();
            FileList.Clear();
            archiveFileList.Clear();
        }
    }
    
}
