public static void ExportCPP(DllIniData IniData,
                                     Dictionary <string, bool> itemsToExport, string fileName)
        {
            using (TextWriter writer = File.CreateText(fileName))
            {
                bool            SA2      = IniData.Game == SA_Tools.SplitDLL.Game.SA2B;
                ModelFormat     modelfmt = SA2 ? ModelFormat.Chunk : ModelFormat.BasicDX;
                LandTableFormat landfmt  = SA2 ? LandTableFormat.SA2 : LandTableFormat.SADX;
                writer.WriteLine("// Generated by SA Tools DLL Mod Generator");
                writer.WriteLine();
                if (SA2)
                {
                    writer.WriteLine("#include \"SA2ModLoader.h\"");
                }
                else
                {
                    writer.WriteLine("#include \"SADXModLoader.h\"");
                }
                writer.WriteLine();
                List <string>             labels   = new List <string>();
                Dictionary <string, uint> texlists = new Dictionary <string, uint>();
                foreach (KeyValuePair <string, FileTypeHash> item in
                         IniData.Files.Where(i => itemsToExport[i.Key]))
                {
                    switch (item.Value.Type)
                    {
                    case "landtable":
                        LandTable tbl = LandTable.LoadFromFile(item.Key);
                        texlists.Add(tbl.Name, tbl.TextureList);
                        tbl.ToStructVariables(writer, landfmt, new List <string>());
                        labels.AddRange(tbl.GetLabels());
                        break;

                    case "model":
                        NJS_OBJECT mdl = new ModelFile(item.Key).Model;
                        mdl.ToStructVariables(writer, modelfmt == ModelFormat.BasicDX, new List <string>());
                        labels.AddRange(mdl.GetLabels());
                        break;

                    case "basicmodel":
                    case "chunkmodel":
                    case "gcmodel":
                        mdl = new ModelFile(item.Key).Model;
                        mdl.ToStructVariables(writer, false, new List <string>());
                        labels.AddRange(mdl.GetLabels());
                        break;

                    case "basicdxmodel":
                        mdl = new ModelFile(item.Key).Model;
                        mdl.ToStructVariables(writer, true, new List <string>());
                        labels.AddRange(mdl.GetLabels());
                        break;

                    case "animation":
                        NJS_MOTION ani = NJS_MOTION.Load(item.Key);
                        ani.ToStructVariables(writer);
                        labels.Add(ani.Name);
                        break;
                    }
                    writer.WriteLine();
                }
                foreach (var item in IniData.DataItems.Where(i => itemsToExport[i.Filename]))
                {
                    switch (item.Type)
                    {
                    case "animindexlist":
                    {
                        SortedDictionary <short, NJS_MOTION> anims = new SortedDictionary <short, NJS_MOTION>();
                        foreach (string file in Directory.GetFiles(item.Filename, "*.saanim"))
                        {
                            if (short.TryParse(Path.GetFileNameWithoutExtension(file), NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out short i))
                            {
                                anims.Add(i, NJS_MOTION.Load(file));
                            }
                        }
                        foreach (KeyValuePair <short, NJS_MOTION> obj in anims)
                        {
                            obj.Value.ToStructVariables(writer);
                            writer.WriteLine();
                        }
                        writer.WriteLine("AnimationIndex {0}[] = {{", item.Export);
                        List <string> objs = new List <string>(anims.Count);
                        foreach (KeyValuePair <short, NJS_MOTION> obj in anims)
                        {
                            objs.Add($"{{ {obj.Key}, {obj.Value.ModelParts}, &{obj.Value.Name} }}");
                        }
                        objs.Add("{ -1 }");
                        writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                        writer.WriteLine("};");
                    }
                    break;

                    case "charaobjectdatalist":
                    {
                        foreach (string file in Directory.GetFiles(item.Filename, "*.sa2mdl"))
                        {
                            new ModelFile(file).Model.ToStructVariables(writer, false, new List <string>());
                            writer.WriteLine();
                        }
                        foreach (string file in Directory.GetFiles(item.Filename, "*.saanim"))
                        {
                            NJS_MOTION.Load(file).ToStructVariables(writer);
                            writer.WriteLine();
                        }
                        var data = IniSerializer.Deserialize <CharaObjectData[]>(Path.Combine(item.Filename, "info.ini"));
                        writer.WriteLine("CharaObjectData {0}[] = {{", item.Export);
                        List <string> objs = new List <string>(data.Length);
                        foreach (var obj in data)
                        {
                            objs.Add(obj.ToStruct());
                        }
                        writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                        writer.WriteLine("};");
                    }
                    break;

                    case "kartspecialinfolist":
                    {
                        foreach (string file in Directory.GetFiles(item.Filename, "*.sa2mdl"))
                        {
                            new ModelFile(file).Model.ToStructVariables(writer, false, new List <string>());
                            writer.WriteLine();
                        }
                        var data = IniSerializer.Deserialize <KartSpecialInfo[]>(Path.Combine(item.Filename, "info.ini"));
                        writer.WriteLine("KartSpecialInfo {0}[] = {{", item.Export);
                        List <string> objs = new List <string>(data.Length);
                        for (int i = 0; i < data.Length; i++)
                        {
                            KartSpecialInfo obj = data[i];
                            objs.Add(obj.ToStruct());
                            texlists.Add($"{item.Export}[{i}]", obj.TexList);
                        }
                        writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                        writer.WriteLine("};");
                    }
                    break;

                    case "kartmodelsarray":
                    {
                        foreach (string file in Directory.GetFiles(item.Filename, "*.sa2bmdl"))
                        {
                            new ModelFile(file).Model.ToStructVariables(writer, false, new List <string>());
                            writer.WriteLine();
                        }
                        foreach (string file in Directory.GetFiles(item.Filename, "*.sa1mdl"))
                        {
                            new ModelFile(file).Model.ToStructVariables(writer, false, new List <string>());
                            writer.WriteLine();
                        }
                        var data = IniSerializer.Deserialize <CharaObjectData[]>(Path.Combine(item.Filename, "info.ini"));
                        writer.WriteLine("KartModelsArray {0}[] = {{", item.Export);
                        List <string> objs = new List <string>(data.Length);
                        foreach (var obj in data)
                        {
                            objs.Add(obj.ToStruct());
                        }
                        writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                        writer.WriteLine("};");
                    }
                    break;

                    case "motiontable":
                    {
                        foreach (string file in Directory.GetFiles(item.Filename, "*.saanim"))
                        {
                            NJS_MOTION.Load(file).ToStructVariables(writer);
                            writer.WriteLine();
                        }
                        var data = IniSerializer.Deserialize <MotionTableEntry[]>(Path.Combine(item.Filename, "info.ini"));
                        writer.WriteLine("MotionTableEntry {0}[] = {{", item.Export);
                        List <string> objs = new List <string>(data.Length);
                        foreach (var obj in data)
                        {
                            objs.Add(obj.ToStruct());
                        }
                        writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                        writer.WriteLine("};");
                    }
                    break;
                    }
                }
                writer.WriteLine("extern \"C\" __declspec(dllexport) void __cdecl Init(const char *path, const HelperFunctions &helperFunctions)");
                writer.WriteLine("{");
                writer.WriteLine("\tHMODULE handle = GetModuleHandle(L\"{0}\");", IniData.Name);
                List <string> exports = new List <string>(IniData.Items.Where(item => labels.Contains(item.Label)).Select(item => item.Export).Distinct());
                foreach (KeyValuePair <string, string> item in IniData.Exports.Where(item => exports.Contains(item.Key)))
                {
                    writer.WriteLine("\t{0}{1} = ({0})GetProcAddress(handle, \"{1}\");", typemap[item.Value], item.Key);
                }
                foreach (DllItemInfo item in IniData.Items.Where(item => labels.Contains(item.Label)))
                {
                    if (typemap[IniData.Exports[item.Export]].EndsWith("**"))
                    {
                        writer.WriteLine("\t{0} = &{1};", item.ToString(), item.Label);
                    }
                    else
                    {
                        writer.WriteLine("\t*{0} = {1};", item.ToString(), item.Label);
                    }
                }
                foreach (var item in IniData.DataItems.Where(item => itemsToExport[item.Filename]))
                {
                    switch (item.Type)
                    {
                    case "animindexlist":
                    case "charaobjectdatalist":
                    case "kartspecialinfolist":
                    case "kartmodelsarray":
                        writer.WriteLine("\tHookExport(handle, \"{0}\", {0});", item.Export);
                        break;

                    default:
                        writer.WriteLine("\t{0}{1}_exp = ({0})GetProcAddress(handle, \"{1}\");", typemap[item.Type], item.Export);
                        writer.WriteLine("\t*{0}_exp = {0};", item.Export);
                        break;
                    }
                }
                if (texlists.Count > 0 && IniData.TexLists != null && IniData.TexLists.Items != null)
                {
                    exports = new List <string>(IniData.TexLists.Where(item => texlists.Values.Contains(item.Key)).Select(item => item.Value.Export).Distinct());
                    foreach (KeyValuePair <string, string> item in IniData.Exports.Where(item => exports.Contains(item.Key)))
                    {
                        writer.WriteLine("\t{0}{1} = ({0})GetProcAddress(handle, \"{1}\");", typemap[item.Value], item.Key);
                    }
                    foreach (KeyValuePair <string, uint> item in texlists.Where(item => IniData.TexLists.ContainsKey(item.Value)))
                    {
                        DllTexListInfo tex = IniData.TexLists[item.Value];
                        string         str;
                        if (tex.Index.HasValue)
                        {
                            str = $"{tex.Export}[{tex.Index.Value}]";
                        }
                        else
                        {
                            str = tex.Export;
                        }
                        writer.WriteLine("\t{0}.TexList = {1};", item.Key, str);
                    }
                }
                writer.WriteLine("}");
                writer.WriteLine();
                writer.WriteLine("extern \"C\" __declspec(dllexport) const ModInfo {0}ModInfo = {{ ModLoaderVer }};", SA2 ? "SA2" : "SADX");
            }
        }
Exemple #2
0
        public static int SplitDLLFile(string datafilename, string inifilename, string projectFolderName)
        {
#if !DEBUG
            try
            {
#endif
            byte[] datafile = File.ReadAllBytes(datafilename);
            IniData inifile = IniSerializer.Deserialize <IniData>(inifilename);
            uint imageBase  = HelperFunctions.SetupEXE(ref datafile).Value;
            Dictionary <string, int> exports;
            {
                int      ptr               = BitConverter.ToInt32(datafile, BitConverter.ToInt32(datafile, 0x3c) + 4 + 20 + 96);
                GCHandle handle            = GCHandle.Alloc(datafile, GCHandleType.Pinned);
                IMAGE_EXPORT_DIRECTORY dir = (IMAGE_EXPORT_DIRECTORY)Marshal.PtrToStructure(
                    Marshal.UnsafeAddrOfPinnedArrayElement(datafile, ptr), typeof(IMAGE_EXPORT_DIRECTORY));
                handle.Free();
                exports = new Dictionary <string, int>(dir.NumberOfFunctions);
                int nameaddr = dir.AddressOfNames;
                int ordaddr  = dir.AddressOfNameOrdinals;
                for (int i = 0; i < dir.NumberOfNames; i++)
                {
                    string name = datafile.GetCString(BitConverter.ToInt32(datafile, nameaddr),
                                                      System.Text.Encoding.ASCII);
                    int addr = BitConverter.ToInt32(datafile,
                                                    dir.AddressOfFunctions + (BitConverter.ToInt16(datafile, ordaddr) * 4));
                    exports.Add(name, addr);
                    nameaddr += 4;
                    ordaddr  += 2;
                }
            }
            ModelFormat modelfmt    = 0;
            LandTableFormat landfmt = 0;
            string modelext         = null;
            string landext          = null;
            switch (inifile.Game)
            {
            case Game.SADX:
                modelfmt = ModelFormat.BasicDX;
                landfmt  = LandTableFormat.SADX;
                modelext = ".sa1mdl";
                landext  = ".sa1lvl";
                break;

            case Game.SA2B:
                modelfmt = ModelFormat.Chunk;
                landfmt  = LandTableFormat.SA2;
                modelext = ".sa2mdl";
                landext  = ".sa2lvl";
                break;
            }
            int itemcount                    = 0;
            List <string> labels             = new List <string>();
            ModelAnimationsDictionary models = new ModelAnimationsDictionary();
            DllIniData output                = new DllIniData()
            {
                Name = inifile.ModuleName,
                Game = inifile.Game
            };
            Stopwatch timer = new Stopwatch();
            timer.Start();
            foreach (KeyValuePair <string, FileInfo> item in inifile.Files)
            {
                if (string.IsNullOrEmpty(item.Key))
                {
                    continue;
                }
                FileInfo data = item.Value;
                string   type = data.Type;
                string   name = item.Key;
                output.Exports[name] = type;
                int address = exports[name];

                string fileOutputPath = "";
                if (data.Filename != null)
                {
                    fileOutputPath = string.Concat(projectFolderName, data.Filename);

                    Console.WriteLine(name + " -> " + fileOutputPath);
                    Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath));
                }
                else
                {
                    Console.WriteLine(name);
                }
                switch (type)
                {
                case "landtable":
                {
                    LandTable land = new LandTable(datafile, address, imageBase, landfmt)
                    {
                        Description = name
                    };
                    DllItemInfo info = new DllItemInfo()
                    {
                        Export = name,
                        Label  = land.Name
                    };
                    output.Items.Add(info);
                    if (!labels.Contains(land.Name))
                    {
                        land.SaveToFile(fileOutputPath, landfmt);
                        output.Files[data.Filename] = new FileTypeHash("landtable", HelperFunctions.FileHash(fileOutputPath));
                        labels.AddRange(land.GetLabels());
                    }
                }
                break;

                case "battlelandtable":
                {
                    LandTable land = new LandTable(datafile, address, imageBase, LandTableFormat.SA2B)
                    {
                        Description = name
                    };
                    DllItemInfo info = new DllItemInfo()
                    {
                        Export = name,
                        Label  = land.Name
                    };
                    output.Items.Add(info);
                    if (!labels.Contains(land.Name))
                    {
                        land.SaveToFile(fileOutputPath, LandTableFormat.SA2B);
                        output.Files[data.Filename] = new FileTypeHash("landtable", HelperFunctions.FileHash(fileOutputPath));
                        labels.AddRange(land.GetLabels());
                    }
                }
                break;

                case "landtablearray":
                    for (int i = 0; i < data.Length; i++)
                    {
                        int ptr = BitConverter.ToInt32(datafile, address);
                        if (ptr != 0)
                        {
                            ptr = (int)(ptr - imageBase);
                            string    idx  = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]";
                            LandTable land = new LandTable(datafile, ptr, imageBase, landfmt)
                            {
                                Description = idx
                            };
                            DllItemInfo info = new DllItemInfo()
                            {
                                Export = name,
                                Index  = i,
                                Label  = land.Name
                            };
                            output.Items.Add(info);
                            if (!labels.Contains(land.Name))
                            {
                                string outputFN = Path.Combine(fileOutputPath, i.ToString(NumberFormatInfo.InvariantInfo) + landext);
                                string fileName = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + landext);

                                land.SaveToFile(outputFN, landfmt);
                                output.Files[fileName] = new FileTypeHash("landtable", HelperFunctions.FileHash(outputFN));
                                labels.AddRange(land.GetLabels());
                            }
                        }
                        address += 4;
                    }
                    break;

                case "model":
                {
                    NJS_OBJECT  mdl  = new NJS_OBJECT(datafile, address, imageBase, modelfmt, new Dictionary <int, Attach>());
                    DllItemInfo info = new DllItemInfo()
                    {
                        Export = name,
                        Label  = mdl.Name
                    };
                    output.Items.Add(info);
                    if (!labels.Contains(mdl.Name))
                    {
                        models.Add(new ModelAnimations(data.Filename, name, mdl, modelfmt));
                        labels.AddRange(mdl.GetLabels());
                    }
                }
                break;

                case "morph":
                {
                    BasicAttach dummy = new BasicAttach(datafile, address, imageBase, modelfmt == ModelFormat.BasicDX);
                    NJS_OBJECT  mdl   = new NJS_OBJECT()
                    {
                        Attach = dummy
                    };
                    DllItemInfo info = new DllItemInfo()
                    {
                        Export = name,
                        Label  = dummy.Name
                    };
                    output.Items.Add(info);
                    if (!labels.Contains(dummy.Name))
                    {
                        models.Add(new ModelAnimations(data.Filename, name, mdl, modelfmt));
                        labels.AddRange(mdl.GetLabels());
                    }
                }
                break;

                case "modelarray":
                    for (int i = 0; i < data.Length; i++)
                    {
                        int ptr = BitConverter.ToInt32(datafile, address);
                        if (ptr != 0)
                        {
                            ptr = (int)(ptr - imageBase);
                            NJS_OBJECT  mdl  = new NJS_OBJECT(datafile, ptr, imageBase, modelfmt, new Dictionary <int, Attach>());
                            string      idx  = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]";
                            DllItemInfo info = new DllItemInfo()
                            {
                                Export = name,
                                Index  = i,
                                Label  = mdl.Name
                            };
                            output.Items.Add(info);
                            if (!labels.Contains(mdl.Name))
                            {
                                string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + modelext);
                                models.Add(new ModelAnimations(fn, idx, mdl, modelfmt));
                                labels.AddRange(mdl.GetLabels());
                            }
                        }
                        address += 4;
                    }
                    break;

                case "modelsarray":
                    for (int i = 0; i < data.Length; i++)
                    {
                        int ptr = BitConverter.ToInt32(datafile, address);
                        if (ptr != 0)
                        {
                            ptr = (int)(ptr - imageBase);
                            BasicAttach dummy = new BasicAttach(datafile, ptr, imageBase, modelfmt == ModelFormat.BasicDX);
                            NJS_OBJECT  mdl   = new NJS_OBJECT()
                            {
                                Attach = dummy
                            };
                            string      idx  = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]";
                            DllItemInfo info = new DllItemInfo()
                            {
                                Export = name,
                                Index  = i,
                                Label  = dummy.Name
                            };
                            output.Items.Add(info);
                            if (!labels.Contains(dummy.Name))
                            {
                                string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + modelext);
                                models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.BasicDX));
                                labels.AddRange(mdl.GetLabels());
                            }
                        }
                        address += 4;
                    }
                    break;

                case "basicmodel":
                {
                    NJS_OBJECT  mdl  = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Basic, new Dictionary <int, Attach>());
                    DllItemInfo info = new DllItemInfo()
                    {
                        Export = name,
                        Label  = mdl.Name
                    };
                    output.Items.Add(info);
                    if (!labels.Contains(mdl.Name))
                    {
                        models.Add(new ModelAnimations(data.Filename, name, mdl, ModelFormat.Basic));
                        labels.AddRange(mdl.GetLabels());
                    }
                }
                break;

                case "basicmodelarray":
                    for (int i = 0; i < data.Length; i++)
                    {
                        int ptr = BitConverter.ToInt32(datafile, address);
                        if (ptr != 0)
                        {
                            ptr = (int)(ptr - imageBase);
                            NJS_OBJECT  mdl  = new NJS_OBJECT(datafile, ptr, imageBase, ModelFormat.Basic, new Dictionary <int, Attach>());
                            string      idx  = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]";
                            DllItemInfo info = new DllItemInfo()
                            {
                                Export = name,
                                Index  = i,
                                Label  = mdl.Name
                            };
                            output.Items.Add(info);
                            if (!labels.Contains(mdl.Name))
                            {
                                string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl");
                                models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.Basic));
                                labels.AddRange(mdl.GetLabels());
                            }
                        }
                        address += 4;
                    }
                    break;

                case "basicdxmodel":
                {
                    NJS_OBJECT  mdl  = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.BasicDX, new Dictionary <int, Attach>());
                    DllItemInfo info = new DllItemInfo()
                    {
                        Export = name,
                        Label  = mdl.Name
                    };
                    output.Items.Add(info);
                    if (!labels.Contains(mdl.Name))
                    {
                        models.Add(new ModelAnimations(data.Filename, name, mdl, ModelFormat.BasicDX));
                        labels.AddRange(mdl.GetLabels());
                    }
                }
                break;

                case "basicdxmodelarray":
                    for (int i = 0; i < data.Length; i++)
                    {
                        int ptr = BitConverter.ToInt32(datafile, address);
                        if (ptr != 0)
                        {
                            ptr = (int)(ptr - imageBase);
                            NJS_OBJECT  mdl  = new NJS_OBJECT(datafile, ptr, imageBase, ModelFormat.BasicDX, new Dictionary <int, Attach>());
                            string      idx  = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]";
                            DllItemInfo info = new DllItemInfo()
                            {
                                Export = name,
                                Index  = i,
                                Label  = mdl.Name
                            };
                            output.Items.Add(info);
                            if (!labels.Contains(mdl.Name))
                            {
                                string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl");
                                models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.BasicDX));
                                labels.AddRange(mdl.GetLabels());
                            }
                        }
                        address += 4;
                    }
                    break;

                case "chunkmodel":
                {
                    NJS_OBJECT  mdl  = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>());
                    DllItemInfo info = new DllItemInfo()
                    {
                        Export = name,
                        Label  = mdl.Name
                    };
                    output.Items.Add(info);
                    if (!labels.Contains(mdl.Name))
                    {
                        models.Add(new ModelAnimations(data.Filename, name, mdl, ModelFormat.Chunk));
                        labels.AddRange(mdl.GetLabels());
                    }
                }
                break;

                case "chunkmodelarray":
                    for (int i = 0; i < data.Length; i++)
                    {
                        int ptr = BitConverter.ToInt32(datafile, address);
                        if (ptr != 0)
                        {
                            ptr = (int)(ptr - imageBase);
                            NJS_OBJECT  mdl  = new NJS_OBJECT(datafile, ptr, imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>());
                            string      idx  = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]";
                            DllItemInfo info = new DllItemInfo()
                            {
                                Export = name,
                                Index  = i,
                                Label  = mdl.Name
                            };
                            output.Items.Add(info);
                            if (!labels.Contains(mdl.Name))
                            {
                                string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".sa2mdl");
                                models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.Chunk));
                                labels.AddRange(mdl.GetLabels());
                            }
                        }
                        address += 4;
                    }
                    break;

                case "actionarray":
                    for (int i = 0; i < data.Length; i++)
                    {
                        int ptr = BitConverter.ToInt32(datafile, address);
                        if (ptr != 0)
                        {
                            ptr = (int)(ptr - imageBase);
                            NJS_ACTION ani = new NJS_ACTION(datafile, ptr, imageBase, modelfmt, new Dictionary <int, Attach>());
                            string     idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]";
                            ani.Animation.Name = item.Key + "_" + i;
                            DllItemInfo info = new DllItemInfo()
                            {
                                Export = name,
                                Index  = i,
                                Label  = ani.Animation.Name,
                                Field  = "motion"
                            };
                            output.Items.Add(info);
                            info = new DllItemInfo()
                            {
                                Export = name,
                                Index  = i,
                                Label  = ani.Model.Name,
                                Field  = "object"
                            };
                            output.Items.Add(info);
                            string outputFN = Path.Combine(fileOutputPath, i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim");
                            string fn       = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim");
                            ani.Animation.Save(outputFN);
                            output.Files[fn] = new FileTypeHash("animation", HelperFunctions.FileHash(outputFN));
                            if (models.Contains(ani.Model.Name))
                            {
                                ModelAnimations           mdl = models[ani.Model.Name];
                                System.Text.StringBuilder sb  = new System.Text.StringBuilder(260);
                                PathRelativePathTo(sb, Path.GetFullPath(Path.Combine(projectFolderName, mdl.Filename)), 0, Path.GetFullPath(outputFN), 0);
                                mdl.Animations.Add(sb.ToString());                                                 // this is where the problem is
                            }
                            else
                            {
                                string mfn           = Path.ChangeExtension(fn, modelext);
                                string outputmfn     = Path.Combine(projectFolderName, mfn);
                                string animationName = Path.GetFileName(outputFN);

                                ModelFile.CreateFile(outputmfn, ani.Model, new[] { animationName }, null, idx + "->object",
                                                     null, modelfmt);
                                output.Files[mfn] = new FileTypeHash("model", HelperFunctions.FileHash(outputmfn));
                            }
                        }
                        address += 4;
                    }
                    break;

                case "texlist":
                    if (output.TexLists == null)
                    {
                        output.TexLists = new TexListContainer();
                    }
                    output.TexLists.Add((uint)(address + imageBase), new DllTexListInfo(name, null));
                    break;

                case "texlistarray":
                    if (output.TexLists == null)
                    {
                        output.TexLists = new TexListContainer();
                    }
                    for (int i = 0; i < data.Length; i++)
                    {
                        uint ptr = BitConverter.ToUInt32(datafile, address);
                        if (ptr != 0 && !output.TexLists.ContainsKey(ptr))
                        {
                            output.TexLists.Add(ptr, new DllTexListInfo(name, i));
                        }
                        address += 4;
                    }
                    break;

                case "animindexlist":
                {
                    Directory.CreateDirectory(fileOutputPath);
                    List <string> hashes = new List <string>();
                    int           i      = ByteConverter.ToInt16(datafile, address);
                    while (i != -1)
                    {
                        new NJS_MOTION(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, ByteConverter.ToInt16(datafile, address + 2))
                        .Save(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim");
                        hashes.Add(i.ToString(NumberFormatInfo.InvariantInfo) + ":" + HelperFunctions.FileHash(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"));
                        address += 8;
                        i        = ByteConverter.ToInt16(datafile, address);
                    }
                    output.DataItems.Add(new DllDataItemInfo()
                        {
                            Type = type, Export = name, Filename = data.Filename, MD5Hash = string.Join("|", hashes.ToArray())
                        });
                }
                break;

                case "charaobjectdatalist":
                {
                    Directory.CreateDirectory(fileOutputPath);
                    List <CharaObjectData> result = new List <CharaObjectData>();
                    List <string>          hashes = new List <string>();
                    for (int i = 0; i < data.Length; i++)
                    {
                        string          chnm  = charaobjectnames[i];
                        CharaObjectData chara = new CharaObjectData();
                        NJS_OBJECT      model = new NJS_OBJECT(datafile, (int)(BitConverter.ToInt32(datafile, address) - imageBase), imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>());
                        chara.MainModel = model.Name;
                        NJS_MOTION anim = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 4) - imageBase), imageBase, model.CountAnimated());
                        chara.Animation1 = anim.Name;
                        anim.Save(Path.Combine(fileOutputPath, $"{chnm} Anim 1.saanim"));
                        hashes.Add($"{chnm} Anim 1.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{chnm} Anim 1.saanim")));
                        anim             = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 8) - imageBase), imageBase, model.CountAnimated());
                        chara.Animation2 = anim.Name;
                        anim.Save(Path.Combine(fileOutputPath, $"{chnm} Anim 2.saanim"));
                        hashes.Add($"{chnm} Anim 2.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{chnm} Anim 2.saanim")));
                        anim             = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 12) - imageBase), imageBase, model.CountAnimated());
                        chara.Animation3 = anim.Name;
                        anim.Save(Path.Combine(fileOutputPath, $"{chnm} Anim 3.saanim"));
                        hashes.Add($"{chnm} Anim 3.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{chnm} Anim 3.saanim")));
                        ModelFile.CreateFile(Path.Combine(fileOutputPath, $"{chnm}.sa2mdl"), model, new[] { $"{chnm} Anim 1.saanim", $"{chnm} Anim 2.saanim", $"{chnm} Anim 3.saanim" }, null, null, null, ModelFormat.Chunk);
                        hashes.Add($"{chnm}.sa2mdl:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{chnm}.sa2mdl")));
                        int ptr = BitConverter.ToInt32(datafile, address + 16);
                        if (ptr != 0)
                        {
                            model = new NJS_OBJECT(datafile, (int)(ptr - imageBase), imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>());
                            chara.AccessoryModel      = model.Name;
                            chara.AccessoryAttachNode = "object_" + (BitConverter.ToInt32(datafile, address + 20) - imageBase).ToString("X8");
                            ModelFile.CreateFile(Path.Combine(fileOutputPath, $"{chnm} Accessory.sa2mdl"), model, null, null, null, null, ModelFormat.Chunk);
                            hashes.Add($"{chnm} Accessory.sa2mdl:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{chnm} Accessory.sa2mdl")));
                        }
                        ptr = BitConverter.ToInt32(datafile, address + 24);
                        if (ptr != 0)
                        {
                            model                 = new NJS_OBJECT(datafile, (int)(ptr - imageBase), imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>());
                            chara.SuperModel      = model.Name;
                            anim                  = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 28) - imageBase), imageBase, model.CountAnimated());
                            chara.SuperAnimation1 = anim.Name;
                            anim.Save(Path.Combine(fileOutputPath, $"Super {chnm} Anim 1.saanim"));
                            hashes.Add($"Super {chnm} Anim 1.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"Super {chnm} Anim 1.saanim")));
                            anim = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 32) - imageBase), imageBase, model.CountAnimated());
                            chara.SuperAnimation2 = anim.Name;
                            anim.Save(Path.Combine(fileOutputPath, $"Super {chnm} Anim 2.saanim"));
                            hashes.Add($"Super {chnm} Anim 2.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"Super {chnm} Anim 2.saanim")));
                            anim = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 36) - imageBase), imageBase, model.CountAnimated());
                            chara.SuperAnimation3 = anim.Name;
                            anim.Save(Path.Combine(fileOutputPath, $"Super {chnm} Anim 3.saanim"));
                            hashes.Add($"Super {chnm} Anim 3.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"Super {chnm} Anim 3.saanim")));
                            ModelFile.CreateFile(Path.Combine(fileOutputPath, $"Super {chnm}.sa2mdl"), model, new[] { $"Super {chnm} Anim 1.saanim", $"Super {chnm} Anim 2.saanim", $"Super {chnm} Anim 3.saanim" }, null, null, null, ModelFormat.Chunk);
                            hashes.Add($"Super {chnm}.sa2mdl:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"Super {chnm}.sa2mdl")));
                        }
                        chara.Unknown1        = BitConverter.ToInt32(datafile, address + 40);
                        chara.Rating          = BitConverter.ToInt32(datafile, address + 44);
                        chara.DescriptionID   = BitConverter.ToInt32(datafile, address + 48);
                        chara.TextBackTexture = BitConverter.ToInt32(datafile, address + 52);
                        chara.Unknown5        = BitConverter.ToSingle(datafile, address + 56);
                        result.Add(chara);
                        address += 60;
                    }
                    IniSerializer.Serialize(result, Path.Combine(fileOutputPath, "info.ini"));
                    hashes.Add("info.ini:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, "info.ini")));
                    output.DataItems.Add(new DllDataItemInfo()
                        {
                            Type = type, Export = name, Filename = data.Filename, MD5Hash = string.Join("|", hashes.ToArray())
                        });
                }
                break;

                case "kartspecialinfolist":
                {
                    Directory.CreateDirectory(fileOutputPath);
                    List <KartSpecialInfo> result = new List <KartSpecialInfo>();
                    List <string>          hashes = new List <string>();
                    for (int i = 0; i < data.Length; i++)
                    {
                        KartSpecialInfo kart = new KartSpecialInfo
                        {
                            ID = ByteConverter.ToInt32(datafile, address)
                        };
                        NJS_OBJECT model = new NJS_OBJECT(datafile, (int)(BitConverter.ToInt32(datafile, address + 4) - imageBase), imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>());
                        kart.Model = model.Name;
                        ModelFile.CreateFile(Path.Combine(fileOutputPath, $"{i}.sa2mdl"), model, null, null, null, null, ModelFormat.Chunk);
                        hashes.Add($"{i}.sa2mdl:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{i}.sa2mdl")));
                        int ptr = BitConverter.ToInt32(datafile, address + 8);
                        if (ptr != 0)
                        {
                            model         = new NJS_OBJECT(datafile, (int)(ptr - imageBase), imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>());
                            kart.LowModel = model.Name;
                            ModelFile.CreateFile(Path.Combine(fileOutputPath, $"{i} Low.sa2mdl"), model, null, null, null, null, ModelFormat.Chunk);
                            hashes.Add($"{i} Low.sa2mdl:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{i} Low.sa2mdl")));
                        }
                        kart.TexList  = ByteConverter.ToUInt32(datafile, address + 12);
                        kart.Unknown1 = ByteConverter.ToInt32(datafile, address + 16);
                        kart.Unknown2 = ByteConverter.ToInt32(datafile, address + 20);
                        kart.Unknown3 = ByteConverter.ToInt32(datafile, address + 24);
                        result.Add(kart);
                        address += 0x1C;
                    }
                    IniSerializer.Serialize(result, Path.Combine(fileOutputPath, "info.ini"));
                    hashes.Add("info.ini:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, "info.ini")));
                    output.DataItems.Add(new DllDataItemInfo()
                        {
                            Type = type, Export = name, Filename = data.Filename, MD5Hash = string.Join("|", hashes.ToArray())
                        });
                }
                break;
                }
                itemcount++;
            }
            foreach (ModelAnimations item in models)
            {
                string modelOutputPath = string.Concat(projectFolderName, item.Filename);
                //string modelOutputPath = item.Filename;

                ModelFile.CreateFile(modelOutputPath, item.Model, item.Animations.ToArray(), null, item.Name, null, item.Format);
                string type = "model";
                switch (item.Format)
                {
                case ModelFormat.Basic:
                    type = "basicmodel";
                    break;

                case ModelFormat.BasicDX:
                    type = "basicdxmodel";
                    break;

                case ModelFormat.Chunk:
                    type = "chunkmodel";
                    break;
                }
                output.Files[item.Filename] = new FileTypeHash(type, HelperFunctions.FileHash(modelOutputPath));
            }
            IniSerializer.Serialize(output, Path.Combine(projectFolderName, Path.GetFileNameWithoutExtension(datafilename))
                                    + "_data.ini");
            timer.Stop();
            Console.WriteLine("Split " + itemcount + " items in " + timer.Elapsed.TotalSeconds + " seconds.");
            Console.WriteLine();
#if !DEBUG
        }

        catch (Exception e)
        {
            Console.WriteLine(e.Message);
            Console.WriteLine(e.StackTrace);
            Console.WriteLine("Press any key to exit.");
            Console.ReadLine();
            return((int)SA_Tools.Split.SplitERRORVALUE.UnhandledException);
        }
#endif
            return((int)SA_Tools.Split.SplitERRORVALUE.Success);
        }