using Microsoft.Win32;
using System;
using System.IO;
using System.Windows.Forms;
using Param_SFO;
using System.Runtime.InteropServices;
using System.Text;
using KeyDerivation;
using PSVIMGTOOLS;
using System.Drawing;
using System.Threading;

namespace CHOVY_TRANSFER
{
    public partial class CHOVYTRANSFER : Form
    {
        [DllImport("CHOVY.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int chovy_gen(string ebootpbp, UInt64 AID, string outscefile);

        public bool IsDexAidSet()
        {
            string isDex = ReadSetting("dexAid");
            if (isDex == "false" || isDex == "")
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        public string GetCmaDir()
        {
            string Dir = "";
            try
            {
                //try qcma
                Dir = Registry.CurrentUser.OpenSubKey(@"Software\codestation\qcma").GetValue("appsPath").ToString();
            }
            catch (Exception)
            {
                try
                {
                    //try sony cma
                    Dir = Registry.CurrentUser.OpenSubKey(@"Software\Sony Corporation\Content Manager Assistant\Settings").GetValue("ApplicationHomePath").ToString();
                }
                catch (Exception)
                {
                    try
                    {
                        //try devkit cma
                        Dir = Registry.CurrentUser.OpenSubKey(@"Software\SCE\PSP2\Services\Content Manager Assistant for PlayStation(R)Vita DevKit\Settings").GetValue("ApplicationHomePath").ToString();
                    }
                    catch (Exception)
                    {
                        try
                        {
                            string DefaultDir = Path.Combine(Environment.GetEnvironmentVariable("HOMEDRIVE"), Environment.GetEnvironmentVariable("HOMEPATH"), "Documents", "PS Vita");
                            if (Directory.Exists(DefaultDir))
                            {
                                Dir = DefaultDir;
                            }
                        }
                        catch (Exception)
                        {
                            //Do nothing
                        }
                    }
                }

            }

            if (ReadSetting("CmaDir") != "")
            {
                Dir = ReadSetting("CmaDir");
            }
            
            return Dir.Replace("/","\\");
        }


        public string ReadSetting(string Setting)
        {
            string Value = "";
            try
            {

                RegistryKey key;
                key = Registry.CurrentUser.CreateSubKey(@"Software\CHOVYProject\Chovy-Trans");
                Value = key.GetValue(Setting).ToString();
                key.Close();
            }
            catch (Exception) { return ""; }
            return Value;
        }

        public void WriteSetting(string Setting, string Value)
        {
            try
            {
                RegistryKey key;
                key = Registry.CurrentUser.CreateSubKey(@"Software\CHOVYProject\Chovy-Trans");
                key.SetValue(Setting, Value);
                key.Close();
            }
            catch (Exception) { }
        }

        public void ChangeCmaDir(string newDir)
        {
            string DriveLetter = Path.GetPathRoot(newDir);
            driveLetterDst.Text = DriveLetter;
            cmaDir.Text = newDir.Remove(0, DriveLetter.Length);
        }

        public void ChangePspDir(string newDir)
        {
            string DriveLetter = Path.GetPathRoot(newDir);
            driveLetterSrc.Text = DriveLetter;
            pspFolder.Text = newDir.Remove(0, DriveLetter.Length);
        }

        public string FindPspDir()
        {
            for(char i = 'A'; i < 'Z'; i++)
            {
                if (Directory.Exists(Path.Combine(i + ":\\", pspFolder.Text)))
                {
                    return Path.Combine(i + ":\\", pspFolder.Text);
                }
            }
            return Path.Combine("D:\\",pspFolder.Text);
        }

        public int ReadInt32(Stream s)
        {
            byte[] IntData = new byte[0x4];
            s.Read(IntData, 0x00, 0x4);
            return BitConverter.ToInt32(IntData, 0x00);
        }

        public void PopulatePspGameList()
        {
            string PspDir = Path.Combine(driveLetterSrc.Text, pspFolder.Text);
            pspGames.Items.Clear();
            try
            {
                string[] games = Directory.GetFileSystemEntries(Path.Combine(PspDir, "GAME"));
               
                foreach (string game in games)
                {
                    try
                    {
                        string EbootPbp = Path.Combine(game, "EBOOT.PBP");
                        

                        string TitleId = Path.GetFileName(game);
                        string Title = GetTitleFromPbp(EbootPbp);
                        string ContentId = GetContentIdFromPbp(EbootPbp);

                        string LicenseFile = Path.Combine(PspDir, "LICENSE", ContentId);

                        if (TitleId.Length == 9 && File.Exists(LicenseFile));
                        {
                            pspGames.Items.Add(TitleId + " - " + Title);
                        } 
                        
                    }
                    catch (Exception) { };

                }
            }
            catch (Exception) { };
            
        }
        public string GetTitleFromPbp(string pbp)
        {
            byte[] SfoData = GetSfo(pbp);

            using (MemoryStream ms = new MemoryStream(SfoData, 0x00, SfoData.Length)) 
            {
                PARAM_SFO sfo = new PARAM_SFO(ms);
                return sfo.Title;
            }
        }

        public byte[] GetSfo(string pbp)
        {
            FileStream pbps = File.OpenRead(pbp);
            pbps.Seek(0x08, SeekOrigin.Begin);
            int sfoOffset = ReadInt32(pbps);
            int sfoSize = ReadInt32(pbps);
            
            

            pbps.Seek(sfoOffset, SeekOrigin.Begin);
            sfoSize -= (int)pbps.Position;

            byte[] SfoData = new byte[sfoSize];
            pbps.Read(SfoData, 0x00, sfoSize);
            pbps.Dispose();
            return SfoData;
        }

        public byte[] GetIcon0(string pbp)
        {
            FileStream pbps = File.OpenRead(pbp);
            pbps.Seek(0x0C, SeekOrigin.Begin);
            int sfoOffset = ReadInt32(pbps);
            int iconSize = ReadInt32(pbps);

            
            pbps.Seek(sfoOffset, SeekOrigin.Begin);
            iconSize -= (int)pbps.Position;

            byte[] IconData = new byte[iconSize];
            pbps.Read(IconData, 0x00, iconSize);
            pbps.Dispose();
            return IconData;
        }

        public string GetContentIdFromPs1Pbp(string pbp)
        {
            FileStream pbps = File.OpenRead(pbp);
            pbps.Seek(0x20, SeekOrigin.Begin);
            Int64 PSPData = ReadInt32(pbps);
            pbps.Seek(PSPData + 0x560, SeekOrigin.Begin);
            byte[] ContentId = new byte[0x24];
            pbps.Read(ContentId, 0x00, 0x24);
            pbps.Close();
            return Encoding.UTF8.GetString(ContentId);
        }

        public bool IsPs1(string pbp)
        {
            FileStream pbps = File.OpenRead(pbp);
            pbps.Seek(0x24, SeekOrigin.Begin);
            Int64 NPUMDIMGOffest = ReadInt32(pbps);
            pbps.Seek(NPUMDIMGOffest, SeekOrigin.Begin);
            byte[] Header = new byte[0x8];
            pbps.Read(Header, 0x00, 0x8);
            pbps.Close();
            string header = Encoding.UTF8.GetString(Header);
            if (header == "PSISOIMG" /*Single Disc PSX*/ || header == "PSTITLEI" /*Multi Disc PSX*/)
            {
                return true;
            }
            else
            {
                return false;
            }

        }
        public string GetContentIdFromPbp(string pbp)
        {
            if(IsPs1(pbp))
            {
                return GetContentIdFromPs1Pbp(pbp);
            }
            else
            {
                return GetContentIdFromPspPbp(pbp);
            }
        }

        public string GetContentIdFromPspPbp(string pbp)
        {
            FileStream pbps = File.OpenRead(pbp);
            pbps.Seek(0x24, SeekOrigin.Begin);
            Int64 NPUMDIMGOffest = ReadInt32(pbps);
            pbps.Seek(NPUMDIMGOffest + 0x10, SeekOrigin.Begin);
            byte[] ContentId = new byte[0x24];
            pbps.Read(ContentId, 0x00, 0x24);
            pbps.Close();
            return Encoding.UTF8.GetString(ContentId);
        }

        public CHOVYTRANSFER()
        {
            InitializeComponent();
        }

        private void CHOVYTRANSFER_Load(object sender, EventArgs e)
        {
            string PspDir = ReadSetting("PspFolder");
            if (PspDir != "")
            {
                pspFolder.Text = PspDir;
            }

            ChangePspDir(FindPspDir());
            ChangeCmaDir(GetCmaDir());
            PopulatePspGameList();
        }

        private void pspFolder_TextChanged(object sender, EventArgs e)
        {
            WriteSetting("PspFolder", pspFolder.Text);
            PopulatePspGameList();
        }

        private void driveLetterSrc_SelectedIndexChanged(object sender, EventArgs e)
        {
            PopulatePspGameList();
        }

        private void cmaDir_TextChanged(object sender, EventArgs e)
        {
            WriteSetting("CmaDir", Path.Combine(driveLetterDst.Text, cmaDir.Text));
        }
        private void driveLetterDst_SelectedIndexChanged(object sender, EventArgs e)
        {
            WriteSetting("CmaDir", Path.Combine(driveLetterDst.Text, cmaDir.Text));
        }
        private void transVita_EnabledChanged(object sender, EventArgs e)
        {
            Color red = Color.FromArgb(192, 0, 0);
            Color black = Color.Black;
            bool enabled = this.transVita.Enabled;
            this.transVita.ForeColor = enabled ? red : black;
            this.transVita.BackColor = enabled ? black : red;
        }

        private void CHOVYTRANSFER_FormClosing(object sender, FormClosingEventArgs e)
        {
            Environment.Exit(0);
        }

        private void transVita_Click(object sender, EventArgs e)
        {

            if(!Directory.Exists(Path.Combine(driveLetterDst.Text, cmaDir.Text)))
            {
                MessageBox.Show("CMA Folder Doesn't Exist", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (!Directory.Exists(Path.Combine(driveLetterSrc.Text,pspFolder.Text)))
            {
                MessageBox.Show("PSP Folder Doesn't Exist", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (pspGames.SelectedIndex < 0)
            {
                MessageBox.Show("No PSP Game Selected", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            transVita.Enabled = false;
            driveLetterDst.Enabled = false;
            driveLetterSrc.Enabled = false;
            pspFolder.ReadOnly = true;
            cmaDir.ReadOnly = true;
            pspGames.Enabled = false;

            string titleId = pspGames.SelectedItem.ToString().Substring(0, 9);
            string gameFolder = Path.Combine(driveLetterSrc.Text, pspFolder.Text, "GAME", titleId);
            string ebootFile = Path.Combine(gameFolder, "EBOOT.PBP");
            string cid = GetContentIdFromPbp(ebootFile);
            string licenseFile = Path.Combine(driveLetterSrc.Text, pspFolder.Text, "LICENSE", cid + ".RIF");
            string sigFile = Path.Combine(gameFolder, "__sce_ebootpbp");
            string backupDir = Path.Combine(driveLetterDst.Text, cmaDir.Text);

            bool isPs1 = IsPs1(ebootFile);

            if (!File.Exists(licenseFile))
            {
                MessageBox.Show("Could not find LICENSE file!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                transVita.Enabled = true;
                driveLetterDst.Enabled = true;
                driveLetterSrc.Enabled = true;
                pspFolder.ReadOnly = false;
                cmaDir.ReadOnly = false;
                pspGames.Enabled = true;
                return;
            }

            FileStream rif = File.OpenRead(licenseFile);
            byte[] bAid = new byte[0x08];
            rif.Seek(0x08, SeekOrigin.Begin);
            rif.Read(bAid, 0x00, 0x08);
            rif.Close();
            string sAid = BitConverter.ToString(bAid).Replace("-", "").ToLower();
            UInt64 uAid = BitConverter.ToUInt64(bAid, 0x00);

            if(File.Exists(sigFile))
            {
                File.Delete(sigFile);
            }

            int ChovyGenRes = 100;
            Thread ChovyGen = new Thread(() =>
            {
                ChovyGenRes = chovy_gen(ebootFile, uAid, sigFile);
            });

            ChovyGen.Start();
            while (ChovyGen.IsAlive)
            {
                Application.DoEvents();
            }

            if (!File.Exists(sigFile) || ChovyGenRes != 0)
            {
                MessageBox.Show("CHOVY-GEN Failed! Please check CHOVY.DLL exists\nand that the Microsoft Visual C++ 2015 Redistributable Update 3 RC is installed", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                transVita.Enabled = true;
                driveLetterDst.Enabled = true;
                driveLetterSrc.Enabled = true;
                pspFolder.ReadOnly = false;
                cmaDir.ReadOnly = false;
                pspGames.Enabled = true;
                return;
            }

            /*
             *  BUILD PSVIMG FILE(s)
             */

            // Pacakge GAME

            byte[] CmaKey;
            if(!IsDexAidSet())
            {
                CmaKey = CmaKeys.GenerateKey(bAid);
            }
            else
            {
                CmaKey = CmaKeys.GenerateKey(new byte[0x8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
            }
            

            string[] entrys = Directory.GetFileSystemEntries(gameFolder, "*", SearchOption.AllDirectories);
            long noEntrys = entrys.LongLength;
            string parentPath = "ux0:pspemu/temp/game/PSP/GAME/" + titleId;
            int noBlocks = 0;
            foreach (string fileName in Directory.GetFiles(gameFolder, "*", SearchOption.AllDirectories))
            {
                noBlocks += Convert.ToInt32(new FileInfo(fileName).Length / PSVIMGConstants.PSVIMG_BLOCK_SIZE);
            }
            progressBar.Maximum = noBlocks;


            string pgameFolder;
            string pgameFolderl;
            string scesys;
            if (!isPs1)
            {
                if(!IsDexAidSet())
                {
                    pgameFolder = Path.Combine(backupDir, "PGAME", sAid, titleId, "game");
                    pgameFolderl = Path.Combine(backupDir, "PGAME", sAid, titleId, "license");
                    scesys = Path.Combine(backupDir, "PGAME", sAid, titleId, "sce_sys");
                }
                else
                {
                    pgameFolder = Path.Combine(backupDir, "PGAME", "0000000000000000", titleId, "game");
                    pgameFolderl = Path.Combine(backupDir, "PGAME", "0000000000000000", titleId, "license");
                    scesys = Path.Combine(backupDir, "PSGAME", "0000000000000000", titleId, "sce_sys");
                }
               
            }
            else
            {
                if(!IsDexAidSet())
                {
                    pgameFolder = Path.Combine(backupDir, "PSGAME", sAid, titleId, "game");
                    pgameFolderl = Path.Combine(backupDir, "PSGAME", sAid, titleId, "license");
                    scesys = Path.Combine(backupDir, "PSGAME", sAid, titleId, "sce_sys");
                }
                else
                {
                    pgameFolder = Path.Combine(backupDir, "PSGAME", "0000000000000000", titleId, "game");
                    pgameFolderl = Path.Combine(backupDir, "PSGAME", "0000000000000000", titleId, "license");
                    scesys = Path.Combine(backupDir, "PSGAME", "0000000000000000", titleId, "sce_sys");
                }
                
            }
           
            Directory.CreateDirectory(pgameFolder);
            Directory.CreateDirectory(pgameFolderl);
            Directory.CreateDirectory(scesys);

            string psvimgFilepathl = Path.Combine(pgameFolderl, "license.psvimg");
            string psvimgFilepath = Path.Combine(pgameFolder, "game.psvimg");

            string psvmdFilepathl = Path.Combine(pgameFolderl, "license.psvmd");
            string psvmdFilepath = Path.Combine(pgameFolder, "game.psvmd");

            FileStream gamePsvimg = File.OpenWrite(psvimgFilepath);
            gamePsvimg.SetLength(0);
            PSVIMGBuilder builder = new PSVIMGBuilder(gamePsvimg, CmaKey);

            foreach (string entry in entrys)
            {
                string relativePath = entry.Remove(0, gameFolder.Length);
                relativePath = relativePath.Replace('\\', '/');

                bool isDir = File.GetAttributes(entry).HasFlag(FileAttributes.Directory);

                if (isDir)
                {
                    builder.AddDir(entry, parentPath, relativePath);
                }
                else
                {
                    builder.AddFileAsync(entry, parentPath, relativePath);
                    while (!builder.HasFinished)
                    {
                        try
                        {
                            int tBlocks = builder.BlocksWritten;
                            progressBar.Value = tBlocks;
                            decimal progress = Math.Floor(((decimal)tBlocks / (decimal)noBlocks) * 100);
                            progressStatus.Text = progress.ToString() + "%";
                        }
                        catch (Exception) { }

                        Application.DoEvents();
                    }
                }
            }

            long ContentSize = builder.Finish();
            gamePsvimg = File.OpenRead(psvimgFilepath);
            FileStream gamePsvmd = File.OpenWrite(psvmdFilepath);
            PSVMDBuilder.CreatePsvmd(gamePsvmd, gamePsvimg, ContentSize, "game", CmaKey);
            gamePsvmd.Close();
            gamePsvimg.Close();


            // Package LICENSE
            FileStream licensePsvimg = File.OpenWrite(psvimgFilepathl);
            licensePsvimg.SetLength(0);
            builder = new PSVIMGBuilder(licensePsvimg, CmaKey);
            builder.AddFile(licenseFile, "ux0:pspemu/temp/game/PSP/LICENSE", "/" + cid + ".rif");
            ContentSize = builder.Finish();

            licensePsvimg = File.OpenRead(psvimgFilepathl);
            FileStream licensePsvmd = File.OpenWrite(psvmdFilepathl);
            PSVMDBuilder.CreatePsvmd(licensePsvmd, licensePsvimg, ContentSize, "license", CmaKey);
            licensePsvmd.Close();
            licensePsvimg.Close();

            // Write PARAM.SFO & ICON0.PNG

            byte[] ParamSfo = GetSfo(ebootFile);
            byte[] Icon0 = GetIcon0(ebootFile);
            File.WriteAllBytes(Path.Combine(scesys, "param.sfo"), ParamSfo);
            File.WriteAllBytes(Path.Combine(scesys, "icon0.png"), Icon0);

            if(!isPs1)
            {
                MessageBox.Show("Success!\nYou can now Restore the game onto your PSVita from\n[Content Mannager] (Copy Content) -> (PC) -> (Applications) -> (PC -> This System) -> (PSP/Others)", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                MessageBox.Show("Success!\nYou can now Restore the game onto your PSVita from\n[Content Mannager] (Copy Content) -> (PC) -> (Applications) -> (PC -> This System) -> (PlayStation)", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            progressBar.Value = 0;
            progressStatus.Text = "0%";

            transVita.Enabled = true;
            driveLetterDst.Enabled = true;
            driveLetterSrc.Enabled = true;
            pspFolder.ReadOnly = false;
            cmaDir.ReadOnly = false;
            pspGames.Enabled = true;
        }

        private void dexToggle_Click(object sender, EventArgs e)
        {
            if(IsDexAidSet())
            {
                WriteSetting("dexAid", "false");
                MessageBox.Show("Cex AID Will be used for CMA.", "CexAid", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                WriteSetting("dexAid", "true");
                MessageBox.Show("Dex AID (0000000000000000) Will be used for CMA.","DexAid",MessageBoxButtons.OK,MessageBoxIcon.Information);
            }
        }
    }
}
