using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using LibCPK;
namespace CriPakTools
{
    public class SystemEncoding
    {
        public static Encoding Codecs = Encoding.UTF8;
    }
    class Program
    {
        [DllImport("user32.dll", EntryPoint = "MessageBox")]
        public static extern int MsgBox(IntPtr hwnd, string text, string caption, uint type);
        public static void ShowMsgBox(string msg)
        {
            MsgBox(IntPtr.Zero, msg, "CriPakTools", 1);
        }
        

        static void Main(string[] args)
        {
            if (args.Length == 0)
            {

                Console.WriteLine("error: no args\n");
                Console.WriteLine("====================");
                Console.WriteLine("This tool has been redesigned, please use CriPakGUI.exe ");
                Console.WriteLine("====================");
                Console.WriteLine("This tool is based on the codes by Falo , Nanashi3 ,esperknight and uyjulian");
                Console.WriteLine("I forked and added batch reimport and compress code .");
                Console.WriteLine("Thanks for KenTse 's CRILAYLA compression method");
                Console.WriteLine("                                     by: wmltogether@gmail.com");
                Console.WriteLine("CriPakTool Usage:");
                Console.WriteLine(" -e - set encodings (utf8, utf16, cp932)");
                Console.WriteLine(" -l - Displays all contained chunks.");
                Console.WriteLine(" -x - Extracts all files.");
                Console.WriteLine(" -r REPLACE_ME REPLACE_WITH - Replaces REPLACE_ME with REPLACE_WITH.");
                Console.WriteLine(" -o OUT_FILE - Set output file.");
                Console.WriteLine(" -d OUT_DIR - Set output directory.");
                Console.WriteLine(" -i IN_FILE - Set input file.");
                Console.WriteLine(" -c - use CRILAYLA compression");
                Console.WriteLine(" -y - use legacy (c)CRI decompression");
                Console.WriteLine(" -b BATCH_REPLACE_LIST_TXT - Batch Replace file recorded in filelist.txt .");
                Console.WriteLine(" -h HELP");
                Program.ShowMsgBox("Error:  \n  Please run this program from console!");
                
                return;
            }

            bool doExtract = false;
            bool doReplace = false;
            bool doDisplay = false;
            bool bUseCompress = false;
            bool doBatchReplace = false; //添加批量替换功能
            bool bUseLegacyCompress = false; //添加旧式解压参数
            string outDir = ".";
            string inFile = "";
            string outFile = "";
            string replaceMe = "";
            string replaceWith = "";
            string batch_text_name = "";

            for (int i = 0; i < args.Length; i++)
            {
                string option =  args[i];
                if (option[0] == '-')
                {
                    switch (option[1])
                    {
                        case 'e':
                            {
                                var encodingdata = args[i + 1];
                                if (encodingdata.ToLower().Equals("utf8") || encodingdata.ToLower().Equals("utf-8"))
                                {
                                    SystemEncoding.Codecs = Encoding.UTF8;
                                }
                                    
                                else if (encodingdata.ToLower().Equals("cp932") || encodingdata.ToLower().Equals("sjis") || encodingdata.ToLower().Equals("shift-jis"))
                                {
                                    SystemEncoding.Codecs = Encoding.GetEncoding(932);
                                }
                                else if (encodingdata.ToLower().Equals("utf-16") || encodingdata.ToLower().Equals("unicode") || encodingdata.ToLower().Equals("utf16"))
                                {
                                    SystemEncoding.Codecs = Encoding.Unicode;
                                }
                                break;
                            }
                        case 'x': doExtract = true; break;
                        case 'c': bUseCompress = true; break;
                        case 'r': doReplace = true; replaceMe = args[i + 1]; replaceWith = args[i + 2]; break;
                        case 'l': doDisplay = true; break;
                        case 'd': outDir = args[i + 1]; break;
                        case 'i': inFile = args[i + 1]; break;
                        case 'o': outFile = args[i + 1]; break;
                        case 'b': doBatchReplace = true; batch_text_name = args[i + 1]; break;
                        case 'y': bUseLegacyCompress = true; break;
                        case 'h': 
                            Console.WriteLine("CriPakTool Usage:");
                            Console.WriteLine(" -l - Displays all contained chunks.");
                            Console.WriteLine(" -x - Extracts all files.");
                            Console.WriteLine(" -c - use CRILAYLA compression");
                            Console.WriteLine(" -r REPLACE_ME REPLACE_WITH - Replaces REPLACE_ME with REPLACE_WITH.");
                            Console.WriteLine(" -o OUT_FILE - Set output file.");
                            Console.WriteLine(" -d OUT_DIR - Set output directory.");
                            Console.WriteLine(" -i IN_FILE - Set input file.");
                            Console.WriteLine(" -b BATCH_REPLACE_LIST_TXT - Batch Replace file recorded in filelist.txt .");
                            Console.WriteLine(" -h HELP");

                            Console.WriteLine("    [Extract files from cpk]");
                            Console.WriteLine("    CriPakTool.exe -x -i xxx.cpk -d xxx.cpk_unpacked");
                            Console.WriteLine("    [Display files from cpk]");
                            Console.WriteLine("    CriPakTool.exe -l -i xxx.cpk");
                            Console.WriteLine("    [Batch Replace files into cpk]");
                            Console.WriteLine("    CriPakTool.exe -b filelist.txt -c -i xxx.cpk -o xxx_patched.cpk");
                            Console.WriteLine("    //e.g. FILELIST.TXT");
                            Console.WriteLine("    original_file_name(in cpk),patch_file_name(in folder)");
                            Console.WriteLine("    /HD_font_a.ftx,patch/BOOT.cpk_unpacked/HD_font_a.ftx");
                            Console.WriteLine("    OTHER/ICON0.PNG,patch/BOOT.cpk_unpacked/OTHER/ICON0.PNG");
                            Console.WriteLine("...");
                            return;
                        default:
                            Console.WriteLine("CriPakTool Usage:");
                            Console.WriteLine(" -l - Displays all contained chunks.");
                            Console.WriteLine(" -x - Extracts all files.");
                            Console.WriteLine(" -c - use CRILAYLA compression");
                            Console.WriteLine(" -r REPLACE_ME REPLACE_WITH - Replaces REPLACE_ME with REPLACE_WITH.");
                            Console.WriteLine(" -o OUT_FILE - Set output file.");
                            Console.WriteLine(" -d OUT_DIR - Set output directory.");
                            Console.WriteLine(" -i IN_FILE - Set input file.");
                            Console.WriteLine(" -b BATCH_REPLACE_LIST_TXT - Batch Replace file recorded in filelist.txt .");
                            break;

                    }
                }
             }
            if (inFile == "")
            {
                Console.WriteLine("ERROR :You must give -i argv");
                return;
            }

            if (!File.Exists(inFile))
            {
                Console.WriteLine("ERROR :INPUT FILE NOT EXISTS");
                return;
            }

            if (!(doExtract || doReplace || doDisplay || doBatchReplace))
            { //Lazy sanity checking for now
                Console.WriteLine("no? \n");
                return;
            }

            string cpk_name = inFile;

            CPK cpk = new CPK(new Tools());
            cpk.ReadCPK(cpk_name, SystemEncoding.Codecs);

            BinaryReader oldFile = new BinaryReader(File.OpenRead(cpk_name));

            if (doDisplay)
            {
                List<FileEntry> entries = cpk.FileTable.OrderBy(x => x.FileOffset).ToList();
                for (int i = 0; i < entries.Count; i++)
                {
                    Console.WriteLine("FILE ID:{0},File Name:{1},File Type:{5},FileOffset:{2:x8},Extract Size:{3:x8},Chunk Size:{4:x8}", entries[i].ID, 
                                                                (((entries[i].DirName != null) ? entries[i].DirName + "/" : "") + entries[i].FileName) ,
                                                                entries[i].FileOffset,
                                                                entries[i].ExtractSize,
                                                                entries[i].FileSize,
                                                                entries[i].FileType);
                }
            }
            else if (doExtract)
            {
                if (!Directory.Exists(outDir))
                {
                    Directory.CreateDirectory(outDir);
                }

                List<FileEntry> entries = null;

                entries = cpk.FileTable.Where(x => x.FileType == "FILE").ToList();

                if (entries.Count == 0)
                {
                    Console.WriteLine("err while extracting.");
                }

                for (int i = 0; i < entries.Count; i++)
                {
                    if (!String.IsNullOrEmpty((string)entries[i].DirName))
                    {
                        Directory.CreateDirectory(outDir + "/" + entries[i].DirName.ToString());
                    }

                    oldFile.BaseStream.Seek((long)entries[i].FileOffset, SeekOrigin.Begin);

                    string isComp = Encoding.ASCII.GetString(oldFile.ReadBytes(8));
                    oldFile.BaseStream.Seek((long)entries[i].FileOffset, SeekOrigin.Begin);

                    byte[] chunk = oldFile.ReadBytes(Int32.Parse(entries[i].FileSize.ToString()));
                    Console.WriteLine("FileName :{0}\n    FileOffset:{1:x8}    ExtractSize:{2:x8}   ChunkSize:{3:x8}", 
                                                                            entries[i].FileName.ToString(),
                                                                            (long)entries[i].FileOffset, 
                                                                            entries[i].ExtractSize, 
                                                                            entries[i].FileSize);
                    if (isComp == "CRILAYLA")
                    {
                        Console.WriteLine("Got CRILAYLA !");
                        int size = Int32.Parse((entries[i].ExtractSize ?? entries[i].FileSize).ToString());

                        if (size != 0)
                            if (bUseLegacyCompress == false)
                            {
                                chunk = cpk.DecompressCRILAYLA(chunk, size);
                            }
                            else
                            {
                                chunk = cpk.DecompressLegacyCRI(chunk, size);
                            }
                    }

                    string dstpath = outDir + "/" + ((entries[i].DirName != null) ? entries[i].DirName + "/" : "") + entries[i].FileName.ToString();
                    dstpath = Tools.GetSafePath(dstpath);
                    string dstdir = Path.GetDirectoryName(dstpath);
                    if (!Directory.Exists(dstdir))
                    {
                        Directory.CreateDirectory(dstdir);
                    }
                    File.WriteAllBytes(dstpath, chunk);

                }
            }
            else if (doBatchReplace)
            {
                //批量处理功能 ,读取filelist.txt内文件,将相应文件批量导入到cpk

                FileInfo fi = new FileInfo(cpk_name);

                string outputName = outFile;

                BinaryWriter newCPK = new BinaryWriter(File.OpenWrite(outputName));

                List<FileEntry> entries = cpk.FileTable.OrderBy(x => x.FileOffset).ToList();

                Tools tool = new Tools();
                Dictionary<string, string> batch_file_list = tool.ReadBatchScript(batch_text_name);
                for (int i = 0; i < entries.Count; i++)
                {
                    if (entries[i].FileType != "CONTENT")
                    {

                        if (entries[i].FileType == "FILE")
                        {
                            // I'm too lazy to figure out how to update the ContextOffset position so this works :)
                            if ((ulong)newCPK.BaseStream.Position < cpk.ContentOffset)
                            {
                                ulong padLength = cpk.ContentOffset - (ulong)newCPK.BaseStream.Position;
                                for (ulong z = 0; z < padLength; z++)
                                {
                                    newCPK.Write((byte)0);
                                }
                            }
                        }

                        string currentName = ((entries[i].DirName != null) ? entries[i].DirName + "/" : "") + entries[i].FileName;

                        if (!currentName.Contains("/"))
                        {
                            currentName = "/" + currentName;
                        }

                        if (!batch_file_list.Keys.Contains(currentName.ToString()))
                            //如果不在表中,复制原始数据
                        {
                            oldFile.BaseStream.Seek((long)entries[i].FileOffset, SeekOrigin.Begin);

                            entries[i].FileOffset = (ulong)newCPK.BaseStream.Position;

                            if (entries[i].FileName.ToString() == "ETOC_HDR")
                            {
                                
                                cpk.EtocOffset = entries[i].FileOffset;
                                Console.WriteLine("Fix ETOC_OFFSET to {0:x8}", cpk.EtocOffset);

                            }

                            cpk.UpdateFileEntry(entries[i]);

                            byte[] chunk = oldFile.ReadBytes(Int32.Parse(entries[i].FileSize.ToString()));
                            newCPK.Write(chunk);

                            if ((newCPK.BaseStream.Position % 0x800) > 0 && i < entries.Count - 1)
                            {
                                long cur_pos = newCPK.BaseStream.Position;
                                for (int j = 0; j < (0x800 - (cur_pos % 0x800)); j++)
                                {
                                    newCPK.Write((byte)0);
                                }
                            }
                            
                        }
                        else
                        {
                            string replace_with = batch_file_list[currentName.ToString()];
                            //Got patch file name
                            Console.WriteLine("Patching: {0}", currentName.ToString());

                            byte[] newbie = File.ReadAllBytes(replace_with);
                            entries[i].FileOffset = (ulong)newCPK.BaseStream.Position;
                            int o_ext_size = Int32.Parse((entries[i].ExtractSize).ToString());
                            int o_com_size = Int32.Parse((entries[i].FileSize).ToString());
                            if ((o_com_size < o_ext_size) && entries[i].FileType == "FILE" && bUseCompress == true)
                            {
                                // is compressed
                                Console.Write("Compressing data:{0:x8}", newbie.Length);

                                byte[] dest_comp = cpk.CompressCRILAYLA(newbie);
                                
                                entries[i].FileSize = Convert.ChangeType(dest_comp.Length, entries[i].FileSizeType);
                                entries[i].ExtractSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
                                cpk.UpdateFileEntry(entries[i]);
                                newCPK.Write(dest_comp);
                                Console.Write(">> {0:x8}\r\n", dest_comp.Length);
                            }
                            
                            else
                            {
                                Console.Write("Storing data:{0:x8}\r\n", newbie.Length);
                                entries[i].FileSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
                                entries[i].ExtractSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
                                cpk.UpdateFileEntry(entries[i]);
                                newCPK.Write(newbie);
                            }
                            

                            if ((newCPK.BaseStream.Position % 0x800) > 0 && i < entries.Count - 1)
                            {
                                long cur_pos = newCPK.BaseStream.Position;
                                for (int j = 0; j < (0x800 - (cur_pos % 0x800)); j++)
                                {
                                    newCPK.Write((byte)0);
                                }
                            }
                        }

                        
                    }
                    else
                    {
                        // Content is special.... just update the position
                        cpk.UpdateFileEntry(entries[i]);
                    }
                }

                cpk.WriteCPK(newCPK);
                cpk.WriteITOC(newCPK);
                cpk.WriteTOC(newCPK);
                cpk.WriteETOC(newCPK, cpk.EtocOffset);
                cpk.WriteGTOC(newCPK);

                newCPK.Close();
                oldFile.Close();


            }
            else
            {

                string ins_name = replaceMe;
                string replace_with = replaceWith;

                FileInfo fi = new FileInfo(cpk_name);

                string outputName = outFile;

                BinaryWriter newCPK = new BinaryWriter(File.OpenWrite(outputName));

                List<FileEntry> entries = cpk.FileTable.OrderBy(x => x.FileOffset).ToList();

                for (int i = 0; i < entries.Count; i++)
                {
                    if (entries[i].FileType != "CONTENT")
                    {

                        if (entries[i].FileType == "FILE")
                        {
                            // I'm too lazy to figure out how to update the ContextOffset position so this works :)
                            if ((ulong)newCPK.BaseStream.Position < cpk.ContentOffset)
                            {
                                ulong padLength = cpk.ContentOffset - (ulong)newCPK.BaseStream.Position;
                                for (ulong z = 0; z < padLength; z++)
                                {
                                    newCPK.Write((byte)0);
                                }
                            }
                        }
                        

                        if (entries[i].FileName.ToString() != ins_name)
                        {
                            oldFile.BaseStream.Seek((long)entries[i].FileOffset, SeekOrigin.Begin);
                            Console.WriteLine("{0},{1}", entries[i].FileName, entries[i].FileType);
                            entries[i].FileOffset = (ulong)newCPK.BaseStream.Position;
                            cpk.UpdateFileEntry(entries[i]);

                            byte[] chunk = oldFile.ReadBytes(Int32.Parse(entries[i].FileSize.ToString()));
                            newCPK.Write(chunk);
                            if ((newCPK.BaseStream.Position % 0x800) > 0 && i < entries.Count - 1)
                            {
                                long cur_pos = newCPK.BaseStream.Position;
                                for (int j = 0; j < (0x800 - (cur_pos % 0x800)); j++)
                                {
                                    newCPK.Write((byte)0);
                                }
                            }
                        }
                        else
                        {
                            //Got patch file name
                            Console.WriteLine("{0} Patched.", entries[i].FileName.ToString());
                            byte[] newbie = File.ReadAllBytes(replace_with);
                            entries[i].FileOffset = (ulong)newCPK.BaseStream.Position;
                            int o_ext_size = Int32.Parse((entries[i].ExtractSize).ToString());
                            int o_com_size = Int32.Parse((entries[i].FileSize).ToString());
                            if ((o_com_size < o_ext_size) && entries[i].FileType == "FILE" && bUseCompress == true)
                            {
                                // is compressed

                                byte[] dest_comp = cpk.CompressCRILAYLA(newbie);

                                entries[i].FileSize = Convert.ChangeType(dest_comp.Length, entries[i].FileSizeType);
                                entries[i].ExtractSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
                                cpk.UpdateFileEntry(entries[i]);
                                newCPK.Write(dest_comp);
                                Console.WriteLine("Compressing {0:x8} >> {1:x8}", newbie.Length, dest_comp.Length);
                            }

                            else
                            {

                                entries[i].FileSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
                                entries[i].ExtractSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
                                cpk.UpdateFileEntry(entries[i]);
                                newCPK.Write(newbie);
                            }


                            if ((newCPK.BaseStream.Position % 0x800) > 0 && i < entries.Count - 1)
                            {
                                long cur_pos = newCPK.BaseStream.Position;
                                for (int j = 0; j < (0x800 - (cur_pos % 0x800)); j++)
                                {
                                    newCPK.Write((byte)0);
                                }
                            }
                        }

                        
                    }
                    else
                    {
                        Console.WriteLine("{0},{1}", entries[i].FileName, entries[i].FileType);
                        // Content is special.... just update the position
                        cpk.UpdateFileEntry(entries[i]);
                    }
                }

                cpk.WriteCPK(newCPK);
                cpk.WriteITOC(newCPK);
                cpk.WriteTOC(newCPK);
                cpk.WriteETOC(newCPK , cpk.EtocOffset);
                cpk.WriteGTOC(newCPK);

                newCPK.Close();
                oldFile.Close();

            }
        }
    }
}