private void pasteButton_Click(object sender, EventArgs e)
 {
     if (Clipboard.ContainsData(ClipboardFormat))
     {
         CurrentItem = (SA1LevelAct)Clipboard.GetData(ClipboardFormat);
         ReloadObjectData();
     }
 }
 private void level_ValueChanged(object sender, EventArgs e)
 {
     if (objectList.SelectedIndex == -1)
     {
         return;
     }
     CurrentItem = level.Value;
     objectList.Items[objectList.SelectedIndex] = objectList.SelectedIndex + ": " + level.Value.ToString();
 }
Exemple #3
0
        /// <summary>
        /// Saves changes made to the currently loaded stage.
        /// </summary>
        /// <param name="autoCloseDialog">Defines whether or not the progress dialog should close on completion.</param>
        private void SaveStage(bool autoCloseDialog)
        {
            if (!isStageLoaded)
            {
                return;
            }

            if (isSA2LVL())
            {
                SaveSA2Data(autoCloseDialog);
                return;
            }

            ProgressDialog progress = new ProgressDialog("Saving stage: " + levelName, 6, true, autoCloseDialog);

            progress.Show(this);
            Application.DoEvents();

            IniLevelData level = salvlini.Levels[levelID];

            Directory.CreateDirectory(modSystemFolder);

            SA1LevelAct levelact = new SA1LevelAct(level.LevelID);

            progress.SetTaskAndStep("Saving:", "Geometry...");

            if (LevelData.geo != null)
            {
                LevelData.geo.SaveToFile(level.LevelGeometry, LandTableFormat.SA1);
            }

            progress.StepProgress();

            progress.Step = "Start positions...";
            Application.DoEvents();

            for (int i = 0; i < LevelData.StartPositions.Length; i++)
            {
                if (!File.Exists(salvlini.Characters[LevelData.Characters[i]].StartPositions))
                {
                    log.Add("Error saving start positions for character " + i.ToString());
                    osd.AddMessage("Error saving start positions for character " + i.ToString(), 180);
                    break;
                }

                Dictionary <SA1LevelAct, SA1StartPosInfo> posini =
                    SA1StartPosList.Load(salvlini.Characters[LevelData.Characters[i]].StartPositions);

                if (posini.ContainsKey(levelact))
                {
                    posini.Remove(levelact);
                }

                if (LevelData.StartPositions[i].Position.X != 0 || LevelData.StartPositions[i].Position.Y != 0 ||
                    LevelData.StartPositions[i].Position.Z != 0 || LevelData.StartPositions[i].Rotation.Y != 0)
                {
                    posini.Add(levelact,
                               new SA1StartPosInfo()
                    {
                        Position  = LevelData.StartPositions[i].Position,
                        YRotation = LevelData.StartPositions[i].Rotation.Y
                    });
                }

                posini.Save(salvlini.Characters[LevelData.Characters[i]].StartPositions);
            }

            progress.StepProgress();

            progress.Step = "Death zones...";
            Application.DoEvents();

            if (LevelData.DeathZones != null)
            {
                DeathZoneFlags[] dzini = new DeathZoneFlags[LevelData.DeathZones.Count];
                string           path  = Path.GetDirectoryName(level.DeathZones);
                for (int i = 0; i < LevelData.DeathZones.Count; i++)
                {
                    dzini[i] = LevelData.DeathZones[i].Save(path, i.ToString(System.Globalization.NumberFormatInfo.InvariantInfo) + ".sa1mdl");
                }
                dzini.Save(level.DeathZones);
            }

            progress.StepProgress();

            #region Saving SET Items

            progress.Step = "SET items...";
            Application.DoEvents();

            if (!LevelData.SETItemsIsNull())
            {
                for (int i = 0; i < LevelData.SETChars.Length; i++)
                {
                    string setstr = Path.Combine(modSystemFolder, "SET" + LevelData.SETName + LevelData.SETChars[i] + ".bin");

                    // blank the set file
                    if (File.Exists(setstr) || LevelData.GetSetItemCount(i) == 0)
                    {
                        byte[] emptyBytes = new byte[0x20];
                        File.WriteAllBytes(setstr, emptyBytes);
                    }

                    List <byte> file = new List <byte>(LevelData.GetSetItemCount(i) * 0x20 + 0x20);
                    file.AddRange(BitConverter.GetBytes(LevelData.GetSetItemCount(i)));
                    file.Align(0x20);

                    foreach (SETItem item in LevelData.SETItems(i))
                    {
                        file.AddRange(item.GetBytes());
                    }

                    File.WriteAllBytes(setstr, file.ToArray());
                }
            }

            progress.StepProgress();

            #endregion

            #region Saving CAM Items

            progress.Step = "CAM items...";
            Application.DoEvents();

            if (LevelData.CAMItems != null)
            {
                for (int i = 0; i < LevelData.CAMItems.Length; i++)
                {
                    string camString = Path.Combine(modSystemFolder, "CAM" + LevelData.SETName + LevelData.SETChars[i] + ".bin");

                    /*if (modpath != null)
                     *      camString = Path.Combine(modpath, camString);*/

                    // TODO: Handle this differently. File stream? If the user is using a symbolic link for example, we defeat the purpose by deleting it.
                    if (File.Exists(camString))
                    {
                        File.Delete(camString);
                    }

                    if (LevelData.CAMItems[i].Count == 0)
                    {
                        continue;
                    }

                    List <byte> file = new List <byte>(LevelData.CAMItems[i].Count * 0x40 + 0x40);                   // setting up file size and header
                    file.AddRange(BitConverter.GetBytes(LevelData.CAMItems[i].Count));
                    file.Align(0x40);


                    foreach (CAMItem item in LevelData.CAMItems[i])                     // outputting individual components
                    {
                        file.AddRange(item.GetBytes());
                    }

                    File.WriteAllBytes(camString, file.ToArray());
                }
            }

            #endregion

            #region Saving Mission SET Items

            progress.Step = "Mission SET items...";
            Application.DoEvents();

            if (LevelData.MissionSETItems != null)
            {
                for (int i = 0; i < LevelData.MissionSETItems.Length; i++)
                {
                    string setstr = Path.Combine(modSystemFolder, "SETMI" + level.LevelID + LevelData.SETChars[i] + ".bin");
                    string prmstr = Path.Combine(modSystemFolder, "PRMMI" + level.LevelID + LevelData.SETChars[i] + ".bin");

                    /*if (modpath != nullmodSystemPath
                     * {
                     *      setstr = Path.Combine(modpath, setstr);
                     *      prmstr = Path.Combine(modpath, prmstr);
                     * }*/

                    // TODO: Consider simply blanking the SET file instead of deleting it.
                    // Completely deleting it might be undesirable since Sonic's layout will be loaded
                    // in place of the missing file. And where mods are concerned, you could have conflicts
                    // with other mods if the file is deleted.
                    if (File.Exists(setstr))
                    {
                        File.Delete(setstr);
                    }
                    if (File.Exists(prmstr))
                    {
                        File.Delete(prmstr);
                    }
                    if (LevelData.MissionSETItems[i].Count == 0)
                    {
                        continue;
                    }

                    List <byte> setfile = new List <byte>(LevelData.MissionSETItems[i].Count * 0x20 + 0x20);
                    setfile.AddRange(BitConverter.GetBytes(LevelData.MissionSETItems[i].Count));
                    setfile.Align(0x20);

                    List <byte> prmfile = new List <byte>(LevelData.MissionSETItems[i].Count * 0xC + 0x20);
                    prmfile.AddRange(new byte[] { 0, 0, 0x30, 0x56 });
                    prmfile.Align(0x20);

                    foreach (MissionSETItem item in LevelData.MissionSETItems[i])
                    {
                        setfile.AddRange(item.GetBytes());
                        prmfile.AddRange(item.GetPRMBytes());
                    }

                    File.WriteAllBytes(setstr, setfile.ToArray());
                    File.WriteAllBytes(prmstr, prmfile.ToArray());
                }
            }

            progress.StepProgress();
            #endregion

            #region Saving Splines
            progress.Step = "Splines...";
            Application.DoEvents();
            if (LevelData.LevelSplines != null)
            {
                string splineDirectory = Path.Combine(Path.Combine(modFolder, salvlini.Paths),
                                                      levelact.ToString());
                int s = 0;
                foreach (SplineData spline in LevelData.LevelSplines)
                {
                    string path = Path.Combine(splineDirectory, string.Format("{0}.ini", s));
                    spline.Save(path);
                    s++;
                }
            }
            #endregion

            #region Saving Lights
            progress.Step = "Lights...";
            Application.DoEvents();
            if (stageLightList != null)
            {
                string stageLightPath = Path.Combine(modFolder, "Misc", "Stage Lights.ini");
                IniSerializer.Serialize(stageLightList, stageLightPath);
            }
            if (characterLightList != null)
            {
                string charLightPath = Path.Combine(modFolder, "Misc", "Character Lights.ini");
                IniSerializer.Serialize(characterLightList, charLightPath);
            }
            #endregion

            #region Saving Fog
            progress.Step = "Fog...";
            Application.DoEvents();
            if (stageFogList != null && salvlini.LevelFogFiles.FogEntries != null && salvlini.LevelFogFiles.FogEntries.ContainsKey(levelact.Level))
            {
                string fogFilePath = salvlini.LevelFogFiles.FogEntries[levelact.Level];
                IniSerializer.Serialize(stageFogList, fogFilePath);
            }
            #endregion

            progress.StepProgress();
            progress.SetTaskAndStep("Save complete!");
            unsaved = false;
            Application.DoEvents();
        }
Exemple #4
0
        public static int SplitFile(string datafilename, string inifilename, string projectFolderName)
        {
            try
            {
                byte[]  datafile = File.ReadAllBytes(datafilename);
                IniData inifile  = IniSerializer.Deserialize <IniData>(inifilename);
                if (inifile.MD5 != null && inifile.MD5.Count > 0)
                {
                    string datahash = HelperFunctions.FileHash(datafile);
                    if (!inifile.MD5.Any(h => h.Equals(datahash, StringComparison.OrdinalIgnoreCase)))
                    {
                        Console.WriteLine("The file {0} is not valid for use with the INI {1}.", datafilename, inifilename);
                        return((int)ERRORVALUE.InvalidDataMapping);
                    }
                }
                ByteConverter.BigEndian      = SonicRetro.SAModel.ByteConverter.BigEndian = inifile.BigEndian;
                Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(datafilename));
                if (inifile.Compressed)
                {
                    datafile = FraGag.Compression.Prs.Decompress(datafile);
                }
                uint imageBase = HelperFunctions.SetupEXE(ref datafile) ?? inifile.ImageBase.Value;
                if (Path.GetExtension(datafilename).Equals(".rel", StringComparison.OrdinalIgnoreCase))
                {
                    HelperFunctions.FixRELPointers(datafile);
                }
                bool            SA2      = inifile.Game == Game.SA2 | inifile.Game == Game.SA2B;
                ModelFormat     modelfmt = 0;
                LandTableFormat landfmt  = 0;
                switch (inifile.Game)
                {
                case Game.SA1:
                    modelfmt = ModelFormat.Basic;
                    landfmt  = LandTableFormat.SA1;
                    break;

                case Game.SADX:
                    modelfmt = ModelFormat.BasicDX;
                    landfmt  = LandTableFormat.SADX;
                    break;

                case Game.SA2:
                case Game.SA2B:
                    modelfmt = ModelFormat.Chunk;
                    landfmt  = LandTableFormat.SA2;
                    break;
                }
                int itemcount = 0;
                Dictionary <string, MasterObjectListEntry>     masterobjlist = new Dictionary <string, MasterObjectListEntry>();
                Dictionary <string, Dictionary <string, int> > objnamecounts = new Dictionary <string, Dictionary <string, int> >();
                Stopwatch timer = new Stopwatch();
                timer.Start();
                foreach (KeyValuePair <string, SA_Tools.FileInfo> item in inifile.Files)
                {
                    if (string.IsNullOrEmpty(item.Key))
                    {
                        continue;
                    }
                    string                      filedesc         = item.Key;
                    SA_Tools.FileInfo           data             = item.Value;
                    Dictionary <string, string> customProperties = data.CustomProperties;
                    string                      type             = data.Type;
                    int  address = data.Address;
                    bool nohash  = false;

                    string fileOutputPath = string.Concat(projectFolderName, data.Filename);
                    Console.WriteLine(item.Key + ": " + data.Address.ToString("X") + " → " + fileOutputPath);
                    Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath));
                    switch (type)
                    {
                    case "landtable":
                        new LandTable(datafile, address, imageBase, landfmt)
                        {
                            Description = item.Key
                        }.SaveToFile(fileOutputPath, landfmt);
                        break;

                    case "model":
                    {
                        NJS_OBJECT mdl     = new NJS_OBJECT(datafile, address, imageBase, modelfmt);
                        string[]   mdlanis = new string[0];
                        if (customProperties.ContainsKey("animations"))
                        {
                            mdlanis = customProperties["animations"].Split(',');
                        }
                        string[] mdlmorphs = new string[0];
                        if (customProperties.ContainsKey("morphs"))
                        {
                            mdlmorphs = customProperties["morphs"].Split(',');
                        }
                        ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, modelfmt);
                    }
                    break;

                    case "basicmodel":
                    {
                        NJS_OBJECT mdl     = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Basic);
                        string[]   mdlanis = new string[0];
                        if (customProperties.ContainsKey("animations"))
                        {
                            mdlanis = customProperties["animations"].Split(',');
                        }
                        string[] mdlmorphs = new string[0];
                        if (customProperties.ContainsKey("morphs"))
                        {
                            mdlmorphs = customProperties["morphs"].Split(',');
                        }
                        ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, ModelFormat.Basic);
                    }
                    break;

                    case "basicdxmodel":
                    {
                        NJS_OBJECT mdl     = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.BasicDX);
                        string[]   mdlanis = new string[0];
                        if (customProperties.ContainsKey("animations"))
                        {
                            mdlanis = customProperties["animations"].Split(',');
                        }
                        string[] mdlmorphs = new string[0];
                        if (customProperties.ContainsKey("morphs"))
                        {
                            mdlmorphs = customProperties["morphs"].Split(',');
                        }
                        ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, ModelFormat.BasicDX);
                    }
                    break;

                    case "chunkmodel":
                    {
                        NJS_OBJECT mdl     = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Chunk);
                        string[]   mdlanis = new string[0];
                        if (customProperties.ContainsKey("animations"))
                        {
                            mdlanis = customProperties["animations"].Split(',');
                        }
                        string[] mdlmorphs = new string[0];
                        if (customProperties.ContainsKey("morphs"))
                        {
                            mdlmorphs = customProperties["morphs"].Split(',');
                        }
                        ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, ModelFormat.Chunk);
                    }
                    break;

                    case "action":
                    {
                        NJS_ACTION ani = new NJS_ACTION(datafile, address, imageBase, modelfmt);
                        ani.Animation.Name = filedesc;
                        ani.Animation.Save(fileOutputPath);
                    }
                    break;

                    case "animation":
                        new NJS_MOTION(datafile, address, imageBase, int.Parse(customProperties["numparts"], NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo))
                        {
                            Name = filedesc
                        }
                        .Save(fileOutputPath);
                        break;

                    case "objlist":
                    {
                        ObjectListEntry[] objs = ObjectList.Load(datafile, address, imageBase, SA2);
                        if (inifile.MasterObjectList != null)
                        {
                            foreach (ObjectListEntry obj in objs)
                            {
                                if (!masterobjlist.ContainsKey(obj.CodeString))
                                {
                                    masterobjlist.Add(obj.CodeString, new MasterObjectListEntry(obj));
                                }
                                if (!objnamecounts.ContainsKey(obj.CodeString))
                                {
                                    objnamecounts.Add(obj.CodeString, new Dictionary <string, int>()
                                        {
                                            { obj.Name, 1 }
                                        });
                                }
                                else if (!objnamecounts[obj.CodeString].ContainsKey(obj.Name))
                                {
                                    objnamecounts[obj.CodeString].Add(obj.Name, 1);
                                }
                                else
                                {
                                    objnamecounts[obj.CodeString][obj.Name]++;
                                }
                            }
                        }
                        objs.Save(fileOutputPath);
                    }
                    break;

                    case "startpos":
                        if (SA2)
                        {
                            SA2StartPosList.Load(datafile, address).Save(fileOutputPath);
                        }
                        else
                        {
                            SA1StartPosList.Load(datafile, address).Save(fileOutputPath);
                        }
                        break;

                    case "texlist":
                        TextureList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "leveltexlist":
                        new LevelTextureList(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "triallevellist":
                        TrialLevelList.Save(TrialLevelList.Load(datafile, address, imageBase), fileOutputPath);
                        break;

                    case "bosslevellist":
                        BossLevelList.Save(BossLevelList.Load(datafile, address), fileOutputPath);
                        break;

                    case "fieldstartpos":
                        FieldStartPosList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "soundtestlist":
                        SoundTestList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "musiclist":
                    {
                        int muscnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        MusicList.Load(datafile, address, imageBase, muscnt).Save(fileOutputPath);
                    }
                    break;

                    case "soundlist":
                        SoundList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "stringarray":
                    {
                        int       cnt  = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        Languages lang = Languages.Japanese;
                        if (data.CustomProperties.ContainsKey("language"))
                        {
                            lang = (Languages)Enum.Parse(typeof(Languages), data.CustomProperties["language"], true);
                        }
                        StringArray.Load(datafile, address, imageBase, cnt, lang).Save(fileOutputPath);
                    }
                    break;

                    case "nextlevellist":
                        NextLevelList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "cutscenetext":
                    {
                        int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        new CutsceneText(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[] hashes);
                        data.MD5Hash = string.Join(",", hashes);
                        nohash       = true;
                    }
                    break;

                    case "recapscreen":
                    {
                        int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        RecapScreenList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[][] hashes);
                        string[] hash2 = new string[hashes.Length];
                        for (int i = 0; i < hashes.Length; i++)
                        {
                            hash2[i] = string.Join(",", hashes[i]);
                        }
                        data.MD5Hash = string.Join(":", hash2);
                        nohash       = true;
                    }
                    break;

                    case "npctext":
                    {
                        int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        NPCTextList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[][] hashes);
                        string[] hash2 = new string[hashes.Length];
                        for (int i = 0; i < hashes.Length; i++)
                        {
                            hash2[i] = string.Join(",", hashes[i]);
                        }
                        data.MD5Hash = string.Join(":", hash2);
                        nohash       = true;
                    }
                    break;

                    case "levelclearflags":
                        LevelClearFlagList.Save(LevelClearFlagList.Load(datafile, address), fileOutputPath);
                        break;

                    case "deathzone":
                    {
                        List <DeathZoneFlags> flags = new List <DeathZoneFlags>();
                        string        path          = Path.GetDirectoryName(fileOutputPath);
                        List <string> hashes        = new List <string>();
                        int           num           = 0;
                        while (ByteConverter.ToUInt32(datafile, address + 4) != 0)
                        {
                            flags.Add(new DeathZoneFlags(datafile, address));
                            string file = Path.Combine(path, num++.ToString(NumberFormatInfo.InvariantInfo) + (modelfmt == ModelFormat.Chunk ? ".sa2mdl" : ".sa1mdl"));
                            ModelFile.CreateFile(file, new NJS_OBJECT(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, modelfmt), null, null, null, null, null, modelfmt);
                            hashes.Add(HelperFunctions.FileHash(file));
                            address += 8;
                        }
                        flags.ToArray().Save(fileOutputPath);
                        hashes.Insert(0, HelperFunctions.FileHash(fileOutputPath));
                        data.MD5Hash = string.Join(",", hashes.ToArray());
                        nohash       = true;
                    }
                    break;

                    case "skyboxscale":
                    {
                        int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        SkyboxScaleList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath);
                    }
                    break;

                    case "stageselectlist":
                    {
                        int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        StageSelectLevelList.Load(datafile, address, cnt).Save(fileOutputPath);
                    }
                    break;

                    case "levelrankscores":
                        LevelRankScoresList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "levelranktimes":
                        LevelRankTimesList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "endpos":
                        SA2EndPosList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "animationlist":
                    {
                        int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        SA2AnimationInfoList.Load(datafile, address, cnt).Save(fileOutputPath);
                    }
                    break;

                    case "levelpathlist":
                    {
                        List <string> hashes = new List <string>();
                        ushort        lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address);
                        while (lvlnum != 0xFFFF)
                        {
                            int ptr = ByteConverter.ToInt32(datafile, address + 4);
                            if (ptr != 0)
                            {
                                ptr = (int)((uint)ptr - imageBase);
                                SA1LevelAct level  = new SA1LevelAct(lvlnum);
                                string      lvldir = Path.Combine(fileOutputPath, level.ToString());
                                PathList.Load(datafile, ptr, imageBase).Save(lvldir, out string[] lvlhashes);
                                hashes.Add(level.ToString() + ":" + string.Join(",", lvlhashes));
                            }
                            address += 8;
                            lvlnum   = (ushort)ByteConverter.ToUInt32(datafile, address);
                        }
                        data.MD5Hash = string.Join("|", hashes.ToArray());
                        nohash       = true;
                    }
                    break;

                    case "pathlist":
                    {
                        PathList.Load(datafile, address, imageBase).Save(fileOutputPath, out string[] hashes);
                        data.MD5Hash = string.Join(",", hashes.ToArray());
                        nohash       = true;
                    }
                    break;

                    case "stagelightdatalist":
                        SA1StageLightDataList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "weldlist":
                        WeldList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "bmitemattrlist":
                        BlackMarketItemAttributesList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "creditstextlist":
                        CreditsTextList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        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);
                        }
                        data.MD5Hash = string.Join("|", hashes.ToArray());
                        nohash       = true;
                    }
                    break;

                    case "storysequence":
                        SA2StoryList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    default:                             // raw binary
                    {
                        byte[] bin = new byte[int.Parse(customProperties["size"], NumberStyles.HexNumber)];
                        Array.Copy(datafile, address, bin, 0, bin.Length);
                        File.WriteAllBytes(fileOutputPath, bin);
                    }
                    break;
                    }
                    if (!nohash)
                    {
                        data.MD5Hash = HelperFunctions.FileHash(fileOutputPath);
                    }
                    itemcount++;
                }
                if (inifile.MasterObjectList != null)
                {
                    foreach (KeyValuePair <string, MasterObjectListEntry> obj in masterobjlist)
                    {
                        KeyValuePair <string, int> name = new KeyValuePair <string, int>();
                        foreach (KeyValuePair <string, int> it in objnamecounts[obj.Key])
                        {
                            if (it.Value > name.Value)
                            {
                                name = it;
                            }
                        }
                        obj.Value.Name  = name.Key;
                        obj.Value.Names = objnamecounts[obj.Key].Select((it) => it.Key).ToArray();
                    }

                    string masterObjectListOutputPath = string.Concat(projectFolderName, inifile.MasterObjectList);

                    IniSerializer.Serialize(masterobjlist, masterObjectListOutputPath);
                }
                IniSerializer.Serialize(inifile, Path.Combine(projectFolderName, Path.GetFileNameWithoutExtension(datafilename) + "_data.ini"));
                timer.Stop();
                Console.WriteLine("Split " + itemcount + " items in " + timer.Elapsed.TotalSeconds + " seconds.");
                Console.WriteLine();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine(e.StackTrace);
                Console.WriteLine("Press any key to exit.");
                Console.ReadLine();
                return((int)ERRORVALUE.UnhandledException);
            }

            return((int)ERRORVALUE.Success);
        }
Exemple #5
0
        static void Main(string[] args)
        {
            string[] arguments = Environment.GetCommandLineArgs();
            Game     game;
            string   filename;
            string   dir       = Environment.CurrentDirectory;
            bool     bigendian = false;

            if (args.Length == 0)
            {
                Console.WriteLine("USplit is a tool that lets you extract any data supported by SA Tools from any binary file.");
                Console.WriteLine("Usage: usplit <GAME> <FILENAME> <KEY> <TYPE> <ADDRESS> <PARAMETER1> <PARAMETER2> [-name <NAME>]\n");
                Console.WriteLine("Argument description:");
                Console.WriteLine("<GAME>: SA1, SADX, SA2, SA2B. Add '_b' (e.g. SADX_b) to switch to Big Endian.\n");
                Console.WriteLine("<FILENAME>: The name of the binary file, e.g. sonic.exe.\n");
                Console.WriteLine("<KEY>: Binary key, e.g. 400000 for sonic.exe or C900000 for SA1 STG file.\n");
                Console.WriteLine("<TYPE>: One of the following:\n" +
                                  "binary <length> [hex],\nlandtable, model, basicmodel, basicdxmodel, chunkmodel, gcmodel, action, animation <NJS_OBJECT address> [shortrot],\n" +
                                  "objlist, startpos, texlist, leveltexlist, triallevellist, bosslevellist, fieldstartpos, soundlist, soundtestlist,\nnextlevellist, " +
                                  "levelclearflags, deathzone, levelrankscores, levelranktimes, endpos, levelpathlist, pathlist,\nstagelightdatalist, weldlist" +
                                  "bmitemattrlist, creditstextlist, animindexlist, storysequence, musiclist <count>,\n" +
                                  "stringarray <count> [language], skyboxscale <count>, stageselectlist <count>, animationlist <count>,\n" +
                                  "masterstringlist <count>, cutscenetext <count>, recapscreen <count>, npctext <count>\n");
                Console.WriteLine("<ADDRESS>: The location of data in the file.\n");
                Console.WriteLine("<PARAMETER1>: length, count, secondary address etc. depending on data type\n");
                Console.WriteLine("<PARAMETER2>: 'hex' for binary to read length as hexadecimal, 'shortrot' for animation to read rotation as short\n");
                Console.WriteLine("<NAME>: Output file name (optional)\n");
                Console.WriteLine("Press ENTER to exit");
                Console.ReadLine();
                return;
            }
            //Args list: game, filename, key, type, address, [address2/count], [language], [name]
            switch (args[0])
            {
            case "SA1":
                game = Game.SA1;
                break;

            case "SA1_b":
                game      = Game.SA1;
                bigendian = true;
                break;

            case "SADX":
                game = Game.SADX;
                break;

            case "SADX_b":
                game      = Game.SADX;
                bigendian = true;
                break;

            case "SA2":
                game = Game.SA2;
                break;

            case "SA2_b":
                game      = Game.SA2;
                bigendian = true;
                break;

            case "SA2B":
                game = Game.SA2B;
                break;

            case "SA2B_b":
                game      = Game.SA2B;
                bigendian = true;
                break;

            default:
                return;
            }
            string model_extension     = ".sa1mdl";
            string landtable_extension = ".sa1lvl";

            ByteConverter.BigEndian = SonicRetro.SAModel.ByteConverter.BigEndian = bigendian;
            filename = args[1];
            byte[] datafile = File.ReadAllBytes(filename);
            if (Path.GetExtension(filename).ToLowerInvariant() == ".prs")
            {
                datafile = FraGag.Compression.Prs.Decompress(datafile);
            }
            Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(filename));
            uint            imageBase = uint.Parse(args[2], NumberStyles.AllowHexSpecifier);
            string          type      = args[3];
            int             address   = int.Parse(args[4], NumberStyles.AllowHexSpecifier);
            bool            SA2       = game == Game.SA2 | game == Game.SA2B;
            ModelFormat     modelfmt  = ModelFormat.BasicDX;
            LandTableFormat landfmt   = LandTableFormat.SADX;

            switch (game)
            {
            case Game.SA1:
                modelfmt            = ModelFormat.Basic;
                landfmt             = LandTableFormat.SA1;
                model_extension     = ".sa1mdl";
                landtable_extension = ".sa1lvl";
                break;

            case Game.SADX:
                modelfmt            = ModelFormat.BasicDX;
                landfmt             = LandTableFormat.SADX;
                model_extension     = ".sa1mdl";
                landtable_extension = ".sa1lvl";
                break;

            case Game.SA2:
                modelfmt            = ModelFormat.Chunk;
                landfmt             = LandTableFormat.SA2;
                model_extension     = ".sa2mdl";
                landtable_extension = ".sa2lvl";
                break;

            case Game.SA2B:
                modelfmt            = ModelFormat.Chunk;
                landfmt             = LandTableFormat.SA2B;
                model_extension     = ".sa2mdl";
                landtable_extension = ".sa2blvl";
                break;
            }
            Dictionary <string, MasterObjectListEntry>     masterobjlist = new Dictionary <string, MasterObjectListEntry>();
            Dictionary <string, Dictionary <string, int> > objnamecounts = new Dictionary <string, Dictionary <string, int> >();
            string fileOutputPath = dir + "\\" + address.ToString("X");

            Console.WriteLine("Game: {0}, file: {1}, key: 0x{2}, splitting {3} at 0x{4}", game.ToString(), filename, imageBase.ToString("X"), type, address.ToString("X"));
            if (args[args.Length - 2] == "-name")
            {
                fileOutputPath = dir + "\\" + args[args.Length - 1];
                Console.WriteLine("Name: {0}", args[args.Length - 1]);
            }
            switch (type)
            {
            case "landtable":
                new LandTable(datafile, address, imageBase, landfmt).SaveToFile(fileOutputPath + landtable_extension, landfmt);
                break;

            case "model":
            {
                NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, modelfmt, new Dictionary <int, Attach>());
                ModelFile.CreateFile(fileOutputPath + model_extension, mdl, null, null, null, null, modelfmt);
            }
            break;

            case "basicmodel":
            {
                NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Basic, new Dictionary <int, Attach>());
                ModelFile.CreateFile(fileOutputPath + ".sa1mdl", mdl, null, null, null, null, ModelFormat.Basic);
            }
            break;

            case "basicdxmodel":
            {
                NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.BasicDX, new Dictionary <int, Attach>());
                ModelFile.CreateFile(fileOutputPath + ".sa1mdl", mdl, null, null, null, null, ModelFormat.BasicDX);
            }
            break;

            case "chunkmodel":
            {
                NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>());
                ModelFile.CreateFile(fileOutputPath + ".sa2mdl", mdl, null, null, null, null, ModelFormat.Chunk);
            }
            break;

            case "gcmodel":
            {
                NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.GC, new Dictionary <int, Attach>());
                ModelFile.CreateFile(fileOutputPath + ".sa2mdl", mdl, null, null, null, null, ModelFormat.GC);
            }
            break;

            case "action":
            {
                NJS_ACTION ani = new NJS_ACTION(datafile, address, imageBase, modelfmt, new Dictionary <int, Attach>());
                ani.Animation.Save(fileOutputPath + ".saanim");
                string[]   mdlanis = new string[0];
                NJS_OBJECT mdl     = ani.Model;
                mdlanis = (fileOutputPath + ".saanim").Split(',');
                ModelFile.CreateFile(fileOutputPath + "_model" + model_extension, mdl, mdlanis, null, null, null, modelfmt);
            }
            break;

            case "animation":
            {
                bool shortrot_enabled = false;
                if (args.Length > 6 && args[6] == "shortrot")
                {
                    shortrot_enabled = true;
                }
                NJS_OBJECT mdl = new NJS_OBJECT(datafile, int.Parse(args[5], NumberStyles.AllowHexSpecifier), imageBase, modelfmt, new Dictionary <int, Attach>());
                new NJS_MOTION(datafile, address, imageBase, mdl.CountAnimated(), shortrot: shortrot_enabled).Save(fileOutputPath + ".saanim");
                string[] mdlanis = new string[0];
                mdlanis = (fileOutputPath + ".saanim").Split(',');
                ModelFile.CreateFile(fileOutputPath + "_model" + model_extension, mdl, mdlanis, null, null, null, modelfmt);
            }
            break;

            case "objlist":
            {
                ObjectListEntry[] objs = ObjectList.Load(datafile, address, imageBase, SA2);
                foreach (ObjectListEntry obj in objs)
                {
                    if (!masterobjlist.ContainsKey(obj.CodeString))
                    {
                        masterobjlist.Add(obj.CodeString, new MasterObjectListEntry(obj));
                    }
                    if (!objnamecounts.ContainsKey(obj.CodeString))
                    {
                        objnamecounts.Add(obj.CodeString, new Dictionary <string, int>()
                            {
                                { obj.Name, 1 }
                            });
                    }
                    else if (!objnamecounts[obj.CodeString].ContainsKey(obj.Name))
                    {
                        objnamecounts[obj.CodeString].Add(obj.Name, 1);
                    }
                    else
                    {
                        objnamecounts[obj.CodeString][obj.Name]++;
                    }
                }
                objs.Save(fileOutputPath + ".ini");
            }
            break;

            case "startpos":
                if (SA2)
                {
                    SA2StartPosList.Load(datafile, address).Save(fileOutputPath + ".ini");
                }
                else
                {
                    SA1StartPosList.Load(datafile, address).Save(fileOutputPath + ".ini");
                }
                break;

            case "texlist":
                TextureList.Load(datafile, address, imageBase).Save(fileOutputPath + ".ini");
                break;

            case "leveltexlist":
                new LevelTextureList(datafile, address, imageBase).Save(fileOutputPath + ".ini");
                break;

            case "triallevellist":
                TrialLevelList.Save(TrialLevelList.Load(datafile, address, imageBase), fileOutputPath + ".ini");
                break;

            case "bosslevellist":
                BossLevelList.Save(BossLevelList.Load(datafile, address), fileOutputPath + ".ini");
                break;

            case "fieldstartpos":
                FieldStartPosList.Load(datafile, address).Save(fileOutputPath + ".ini");
                break;

            case "soundtestlist":
                SoundTestList.Load(datafile, address, imageBase).Save(fileOutputPath + ".ini");
                break;

            case "musiclist":
            {
                int muscnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                MusicList.Load(datafile, address, imageBase, muscnt).Save(fileOutputPath + ".ini");
            }
            break;

            case "soundlist":
                SoundList.Load(datafile, address, imageBase).Save(fileOutputPath + ".ini");
                break;

            case "stringarray":
            {
                int       cnt  = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                Languages lang = Languages.Japanese;
                if (args.Length > 6)
                {
                    lang = (Languages)Enum.Parse(typeof(Languages), args[6], true);
                }
                StringArray.Load(datafile, address, imageBase, cnt, lang).Save(fileOutputPath + ".txt");
            }
            break;

            case "nextlevellist":
                NextLevelList.Load(datafile, address).Save(fileOutputPath + ".ini");
                break;

            case "cutscenetext":
            {
                int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                new CutsceneText(datafile, address, imageBase, cnt).Save(fileOutputPath + ".txt", out string[] hashes);
            }
            break;

            case "recapscreen":
            {
                int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                RecapScreenList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath + ".txt", out string[][] hashes);
            }
            break;

            case "npctext":
            {
                int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                NPCTextList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath + ".txt", out string[][] hashes);
            }
            break;

            case "levelclearflags":
                LevelClearFlagList.Save(LevelClearFlagList.Load(datafile, address), fileOutputPath + ".ini");
                break;

            case "deathzone":
            {
                List <DeathZoneFlags> flags = new List <DeathZoneFlags>();
                string        path          = Path.GetDirectoryName(fileOutputPath);
                List <string> hashes        = new List <string>();
                int           num           = 0;
                while (ByteConverter.ToUInt32(datafile, address + 4) != 0)
                {
                    flags.Add(new DeathZoneFlags(datafile, address));
                    string file = Path.Combine(path, num++.ToString(NumberFormatInfo.InvariantInfo) + (modelfmt == ModelFormat.Chunk ? ".sa2mdl" : ".sa1mdl"));
                    ModelFile.CreateFile(file, new NJS_OBJECT(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, modelfmt, new Dictionary <int, Attach>()), null, null, null, null, modelfmt);
                    address += 8;
                }
                flags.ToArray().Save(fileOutputPath + ".ini");
            }
            break;

            case "skyboxscale":
            {
                int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                SkyboxScaleList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath + ".ini");
            }
            break;

            case "stageselectlist":
            {
                int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                StageSelectLevelList.Load(datafile, address, cnt).Save(fileOutputPath + ".ini");
            }
            break;

            case "levelrankscores":
                LevelRankScoresList.Load(datafile, address).Save(fileOutputPath + ".ini");
                break;

            case "levelranktimes":
                LevelRankTimesList.Load(datafile, address).Save(fileOutputPath + ".ini");
                break;

            case "endpos":
                SA2EndPosList.Load(datafile, address).Save(fileOutputPath + ".ini");
                break;

            case "animationlist":
            {
                int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                SA2AnimationInfoList.Load(datafile, address, cnt).Save(fileOutputPath + ".ini");
            }
            break;

            case "levelpathlist":
            {
                ushort lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address);
                while (lvlnum != 0xFFFF)
                {
                    int ptr = ByteConverter.ToInt32(datafile, address + 4);
                    if (ptr != 0)
                    {
                        ptr = (int)((uint)ptr - imageBase);
                        SA1LevelAct level  = new SA1LevelAct(lvlnum);
                        string      lvldir = Path.Combine(fileOutputPath, level.ToString());
                        PathList.Load(datafile, ptr, imageBase).Save(lvldir, out string[] lvlhashes);
                    }
                    address += 8;
                    lvlnum   = (ushort)ByteConverter.ToUInt32(datafile, address);
                }
            }
            break;

            case "pathlist":
            {
                PathList.Load(datafile, address, imageBase).Save(fileOutputPath, out string[] hashes);
            }
            break;

            case "stagelightdatalist":
                SA1StageLightDataList.Load(datafile, address).Save(fileOutputPath);
                break;

            case "weldlist":
                WeldList.Load(datafile, address, imageBase).Save(fileOutputPath);
                break;

            case "bmitemattrlist":
                BlackMarketItemAttributesList.Load(datafile, address, imageBase).Save(fileOutputPath);
                break;

            case "creditstextlist":
                CreditsTextList.Load(datafile, address, imageBase).Save(fileOutputPath);
                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);
                }
            }
            break;

            case "storysequence":
                SA2StoryList.Load(datafile, address).Save(fileOutputPath);
                break;

            case "masterstringlist":
            {
                int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                for (int l = 0; l < 5; l++)
                {
                    Languages            lng = (Languages)l;
                    System.Text.Encoding enc = HelperFunctions.GetEncoding(game, lng);
                    string ld = Path.Combine(fileOutputPath, lng.ToString());
                    Directory.CreateDirectory(ld);
                    int ptr = datafile.GetPointer(address, imageBase);
                    for (int i = 0; i < cnt; i++)
                    {
                        int ptr2 = datafile.GetPointer(ptr, imageBase);
                        if (ptr2 != 0)
                        {
                            string fn = Path.Combine(ld, $"{i}.txt");
                            File.WriteAllText(fn, datafile.GetCString(ptr2, enc).Replace("\n", "\r\n"));
                        }
                        ptr += 4;
                    }
                    address += 4;
                }
            }
            break;

            case "binary":
            {
                int length;
                if (args.Length > 6 && args[6] == "hex")
                {
                    length = int.Parse(args[5], NumberStyles.AllowHexSpecifier);
                }
                else
                {
                    length = int.Parse(args[5]);
                }
                byte[] bin = new byte[length];
                Array.Copy(datafile, address, bin, 0, bin.Length);
                File.WriteAllBytes(fileOutputPath + ".bin", bin);
                Console.WriteLine("Length: {0} (0x{1}) bytes", length.ToString(), length.ToString("X"));
            }
            break;

            default:
                break;
            }
        }
Exemple #6
0
        public static int SplitFile(string datafilename, string inifilename, string projectFolderName, bool nometa = false, bool nolabel = false)
        {
#if !DEBUG
            try
#endif
            {
                byte[]  datafile;
                byte[]  datafile_temp           = File.ReadAllBytes(datafilename);
                IniData inifile                 = IniSerializer.Deserialize <IniData>(inifilename);
                string  listfile                = Path.Combine(Path.GetDirectoryName(inifilename), Path.GetFileNameWithoutExtension(datafilename) + "_labels.txt");
                Dictionary <int, string> labels = new Dictionary <int, string>();
                if (File.Exists(listfile) && !nolabel)
                {
                    labels = IniSerializer.Deserialize <Dictionary <int, string> >(listfile);
                }
                if (inifile.StartOffset != 0)
                {
                    byte[] datafile_new = new byte[inifile.StartOffset + datafile_temp.Length];
                    datafile_temp.CopyTo(datafile_new, inifile.StartOffset);
                    datafile = datafile_new;
                }
                else
                {
                    datafile = datafile_temp;
                }
                if (inifile.MD5 != null && inifile.MD5.Count > 0)
                {
                    string datahash = HelperFunctions.FileHash(datafile);
                    if (!inifile.MD5.Any(h => h.Equals(datahash, StringComparison.OrdinalIgnoreCase)))
                    {
                        Console.WriteLine("The file {0} is not valid for use with the INI {1}.", datafilename, inifilename);
                        return((int)SplitERRORVALUE.InvalidDataMapping);
                    }
                }
                ByteConverter.BigEndian      = SonicRetro.SAModel.ByteConverter.BigEndian = inifile.BigEndian;
                ByteConverter.Reverse        = SonicRetro.SAModel.ByteConverter.Reverse = inifile.Reverse;
                Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(datafilename));
                if (Path.GetExtension(datafilename).ToLowerInvariant() == ".prs" || (inifile.Compressed && Path.GetExtension(datafilename).ToLowerInvariant() != ".bin"))
                {
                    datafile = FraGag.Compression.Prs.Decompress(datafile);
                }
                uint imageBase = HelperFunctions.SetupEXE(ref datafile) ?? inifile.ImageBase.Value;
                if (Path.GetExtension(datafilename).Equals(".rel", StringComparison.OrdinalIgnoreCase))
                {
                    datafile = HelperFunctions.DecompressREL(datafile);
                    HelperFunctions.FixRELPointers(datafile, imageBase);
                }
                bool            SA2          = inifile.Game == Game.SA2 | inifile.Game == Game.SA2B;
                ModelFormat     modelfmt_def = 0;
                LandTableFormat landfmt_def  = 0;
                switch (inifile.Game)
                {
                case Game.SA1:
                    modelfmt_def = ModelFormat.Basic;
                    landfmt_def  = LandTableFormat.SA1;
                    break;

                case Game.SADX:
                    modelfmt_def = ModelFormat.BasicDX;
                    landfmt_def  = LandTableFormat.SADX;
                    break;

                case Game.SA2:
                    modelfmt_def = ModelFormat.Chunk;
                    landfmt_def  = LandTableFormat.SA2;
                    break;

                case Game.SA2B:
                    modelfmt_def = ModelFormat.GC;
                    landfmt_def  = LandTableFormat.SA2B;
                    break;
                }
                int itemcount = 0;
                Dictionary <string, MasterObjectListEntry>     masterobjlist = new Dictionary <string, MasterObjectListEntry>();
                Dictionary <string, Dictionary <string, int> > objnamecounts = new Dictionary <string, Dictionary <string, int> >();
                Stopwatch timer = new Stopwatch();
                timer.Start();
                foreach (KeyValuePair <string, SA_Tools.FileInfo> item in new List <KeyValuePair <string, SA_Tools.FileInfo> >(inifile.Files))
                {
                    if (string.IsNullOrEmpty(item.Key))
                    {
                        continue;
                    }
                    string                      filedesc         = item.Key;
                    SA_Tools.FileInfo           data             = item.Value;
                    Dictionary <string, string> customProperties = data.CustomProperties;
                    string                      type             = data.Type;
                    int  address = data.Address;
                    bool nohash  = false;

                    string fileOutputPath = Path.Combine(projectFolderName, data.Filename);
                    Console.WriteLine(item.Key + ": " + data.Address.ToString("X") + " → " + fileOutputPath);
                    Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath));
                    switch (type)
                    {
                    case "landtable":
                        LandTableFormat format = data.CustomProperties.ContainsKey("format") ? (LandTableFormat)Enum.Parse(typeof(LandTableFormat), data.CustomProperties["format"]) : landfmt_def;
                        new LandTable(datafile, address, imageBase, format, labels)
                        {
                            Description = item.Key
                        }.SaveToFile(fileOutputPath, landfmt_def, nometa);
                        break;

                    case "model":
                    case "basicmodel":
                    case "basicdxmodel":
                    case "chunkmodel":
                    case "gcmodel":
                    {
                        ModelFormat mdlformat;
                        switch (type)
                        {
                        case "basicmodel":
                            mdlformat = ModelFormat.Basic;
                            break;

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

                        case "chunkmodel":
                            mdlformat = ModelFormat.Chunk;
                            break;

                        case "gcmodel":
                            mdlformat = ModelFormat.GC;
                            break;

                        case "model":
                        default:
                            mdlformat = modelfmt_def;
                            break;
                        }
                        if (data.CustomProperties.ContainsKey("format"))
                        {
                            mdlformat = (ModelFormat)Enum.Parse(typeof(ModelFormat), data.CustomProperties["format"]);
                        }
                        NJS_OBJECT mdl     = new NJS_OBJECT(datafile, address, imageBase, mdlformat, labels, new Dictionary <int, Attach>());
                        string[]   mdlanis = new string[0];
                        if (customProperties.ContainsKey("animations"))
                        {
                            mdlanis = customProperties["animations"].Split(',');
                        }
                        string[] mdlmorphs = new string[0];
                        if (customProperties.ContainsKey("morphs"))
                        {
                            mdlmorphs = customProperties["morphs"].Split(',');
                        }
                        ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, null, item.Key, null, mdlformat, nometa);
                    }
                    break;

                    case "action":
                    {
                        ModelFormat modelfmt_act = data.CustomProperties.ContainsKey("format") ? (ModelFormat)Enum.Parse(typeof(ModelFormat), data.CustomProperties["format"]) : modelfmt_def;
                        NJS_ACTION  ani          = new NJS_ACTION(datafile, address, imageBase, modelfmt_act, labels, new Dictionary <int, Attach>());
                        if (!labels.ContainsValue(ani.Name) && !nolabel)
                        {
                            ani.Name = filedesc;
                        }
                        if (customProperties.ContainsKey("numparts"))
                        {
                            ani.Animation.ModelParts = int.Parse(customProperties["numparts"]);
                        }
                        if (ani.Animation.ModelParts == 0)
                        {
                            Console.WriteLine("Action {0} has no model data!", ani.Name);
                            continue;
                        }
                        ani.Animation.Save(fileOutputPath, nometa);
                    }
                    break;

                    case "animation":
                    case "motion":
                        int numparts = 0;
                        if (customProperties.ContainsKey("numparts"))
                        {
                            numparts = int.Parse(customProperties["numparts"], NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo);
                        }
                        else
                        {
                            Console.WriteLine("Number of parts not specified for {0}", filedesc);
                        }
                        if (customProperties.ContainsKey("shortrot"))
                        {
                            NJS_MOTION mot = new NJS_MOTION(datafile, address, imageBase, numparts, labels, bool.Parse(customProperties["shortrot"]));
                            if (!labels.ContainsKey(address) && !nolabel)
                            {
                                mot.Name = filedesc;
                            }
                            mot.Save(fileOutputPath, nometa);
                        }
                        else
                        {
                            NJS_MOTION mot = new NJS_MOTION(datafile, address, imageBase, numparts, labels);
                            if (!labels.ContainsKey(address) && !nolabel)
                            {
                                mot.Name = filedesc;
                            }
                            mot.Save(fileOutputPath, nometa);
                        }
                        break;

                    case "objlist":
                    {
                        ObjectListEntry[] objs = ObjectList.Load(datafile, address, imageBase, SA2);
                        if (inifile.MasterObjectList != null)
                        {
                            foreach (ObjectListEntry obj in objs)
                            {
                                if (!masterobjlist.ContainsKey(obj.CodeString))
                                {
                                    masterobjlist.Add(obj.CodeString, new MasterObjectListEntry(obj));
                                }
                                if (!objnamecounts.ContainsKey(obj.CodeString))
                                {
                                    objnamecounts.Add(obj.CodeString, new Dictionary <string, int>()
                                        {
                                            { obj.Name, 1 }
                                        });
                                }
                                else if (!objnamecounts[obj.CodeString].ContainsKey(obj.Name))
                                {
                                    objnamecounts[obj.CodeString].Add(obj.Name, 1);
                                }
                                else
                                {
                                    objnamecounts[obj.CodeString][obj.Name]++;
                                }
                            }
                        }
                        objs.Save(fileOutputPath);
                    }
                    break;

                    case "startpos":
                        if (SA2)
                        {
                            SA2StartPosList.Load(datafile, address).Save(fileOutputPath);
                        }
                        else
                        {
                            SA1StartPosList.Load(datafile, address).Save(fileOutputPath);
                        }
                        break;

                    case "texlist":
                        TextureList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "texnamearray":
                        TexnameArray texnames = new TexnameArray(datafile, address, imageBase);
                        texnames.Save(fileOutputPath);
                        break;

                    case "texlistarray":
                    {
                        int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        for (int i = 0; i < cnt; i++)
                        {
                            uint ptr = BitConverter.ToUInt32(datafile, address);
                            if (data.Filename != null && ptr != 0)
                            {
                                ptr -= imageBase;
                                TexnameArray texarr = new TexnameArray(datafile, (int)ptr, imageBase);
                                string       fn     = Path.Combine(fileOutputPath, i.ToString("D3", NumberFormatInfo.InvariantInfo) + ".txt");
                                if (data.CustomProperties.ContainsKey("filename" + i.ToString()))
                                {
                                    fn = Path.Combine(fileOutputPath, data.CustomProperties["filename" + i.ToString()] + ".txt");
                                }
                                if (!Directory.Exists(Path.GetDirectoryName(fn)))
                                {
                                    Directory.CreateDirectory(Path.GetDirectoryName(fn));
                                }
                                texarr.Save(fn);
                            }
                            address += 4;
                        }
                        nohash = true;
                    }
                    break;

                    case "leveltexlist":
                        new LevelTextureList(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "triallevellist":
                        TrialLevelList.Save(TrialLevelList.Load(datafile, address, imageBase), fileOutputPath);
                        break;

                    case "bosslevellist":
                        BossLevelList.Save(BossLevelList.Load(datafile, address), fileOutputPath);
                        break;

                    case "fieldstartpos":
                        FieldStartPosList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "soundtestlist":
                        SoundTestList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "musiclist":
                    {
                        int muscnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        MusicList.Load(datafile, address, imageBase, muscnt).Save(fileOutputPath);
                    }
                    break;

                    case "soundlist":
                        SoundList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "stringarray":
                    {
                        int       cnt  = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        Languages lang = Languages.Japanese;
                        if (data.CustomProperties.ContainsKey("language"))
                        {
                            lang = (Languages)Enum.Parse(typeof(Languages), data.CustomProperties["language"], true);
                        }
                        StringArray.Load(datafile, address, imageBase, cnt, lang).Save(fileOutputPath);
                    }
                    break;

                    case "nextlevellist":
                        NextLevelList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "cutscenetext":
                    {
                        int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        new CutsceneText(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[] hashes);
                        data.MD5Hash = string.Join(",", hashes);
                        nohash       = true;
                    }
                    break;

                    case "recapscreen":
                    {
                        int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        RecapScreenList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[][] hashes);
                        string[] hash2 = new string[hashes.Length];
                        for (int i = 0; i < hashes.Length; i++)
                        {
                            hash2[i] = string.Join(",", hashes[i]);
                        }
                        data.MD5Hash = string.Join(":", hash2);
                        nohash       = true;
                    }
                    break;

                    case "npctext":
                    {
                        int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        NPCTextList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[][] hashes);
                        string[] hash2 = new string[hashes.Length];
                        for (int i = 0; i < hashes.Length; i++)
                        {
                            hash2[i] = string.Join(",", hashes[i]);
                        }
                        data.MD5Hash = string.Join(":", hash2);
                        nohash       = true;
                    }
                    break;

                    case "levelclearflags":
                        LevelClearFlagList.Save(LevelClearFlagList.Load(datafile, address), fileOutputPath);
                        break;

                    case "deathzone":
                    {
                        List <DeathZoneFlags> flags = new List <DeathZoneFlags>();
                        string        path          = Path.GetDirectoryName(fileOutputPath);
                        List <string> hashes        = new List <string>();
                        int           num           = 0;
                        while (ByteConverter.ToUInt32(datafile, address + 4) != 0)
                        {
                            string file_tosave;
                            if (customProperties.ContainsKey("filename" + num.ToString()))
                            {
                                file_tosave = customProperties["filename" + num++.ToString()];
                            }
                            else
                            {
                                file_tosave = num++.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl";
                            }
                            string file = Path.Combine(path, file_tosave);
                            flags.Add(new DeathZoneFlags(datafile, address, file_tosave));
                            ModelFormat modelfmt_death = inifile.Game == Game.SADX ? ModelFormat.BasicDX : ModelFormat.Basic;                                             // Death zones in all games except SADXPC use Basic non-DX models
                            ModelFile.CreateFile(file, new NJS_OBJECT(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, modelfmt_death, new Dictionary <int, Attach>()), null, null, null, null, modelfmt_death, nometa);
                            hashes.Add(HelperFunctions.FileHash(file));
                            address += 8;
                        }
                        flags.ToArray().Save(fileOutputPath);
                        hashes.Insert(0, HelperFunctions.FileHash(fileOutputPath));
                        data.MD5Hash = string.Join(",", hashes.ToArray());
                        nohash       = true;
                    }
                    break;

                    case "skyboxscale":
                    {
                        int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        SkyboxScaleList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath);
                    }
                    break;

                    case "stageselectlist":
                    {
                        int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        StageSelectLevelList.Load(datafile, address, cnt).Save(fileOutputPath);
                    }
                    break;

                    case "levelrankscores":
                        LevelRankScoresList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "levelranktimes":
                        LevelRankTimesList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "endpos":
                        SA2EndPosList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "animationlist":
                    {
                        int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        SA2AnimationInfoList.Load(datafile, address, cnt).Save(fileOutputPath);
                    }
                    break;

                    case "enemyanimationlist":
                    {
                        int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        SA2EnemyAnimInfoList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath);
                    }
                    break;

                    case "sa1actionlist":
                    {
                        int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        SA1ActionInfoList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath);
                    }
                    break;

                    case "motiontable":
                    {
                        Directory.CreateDirectory(fileOutputPath);
                        List <MotionTableEntry> result = new List <MotionTableEntry>();
                        List <string>           hashes = new List <string>();
                        bool shortrot = false;
                        if (customProperties.ContainsKey("shortrot"))
                        {
                            shortrot = bool.Parse(customProperties["shortrot"]);
                        }
                        int nodeCount = int.Parse(data.CustomProperties["nodecount"], NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo);
                        int Length    = int.Parse(data.CustomProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        Dictionary <int, string> mtns = new Dictionary <int, string>();
                        for (int i = 0; i < Length; i++)
                        {
                            MotionTableEntry bmte = new MotionTableEntry();
                            int mtnaddr           = (int)(ByteConverter.ToUInt32(datafile, address) - imageBase);
                            if (!mtns.ContainsKey(mtnaddr))
                            {
                                NJS_MOTION motion = new NJS_MOTION(datafile, mtnaddr, imageBase, nodeCount, null, shortrot);
                                bmte.Motion = motion.Name;
                                mtns.Add(mtnaddr, motion.Name);
                                motion.Save(Path.Combine(fileOutputPath, $"{i}.saanim"), nometa);
                                hashes.Add($"{i}.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{i}.saanim")));
                            }
                            else
                            {
                                bmte.Motion = mtns[mtnaddr];
                            }
                            bmte.LoopProperty    = ByteConverter.ToUInt16(datafile, address + 4);
                            bmte.Pose            = ByteConverter.ToUInt16(datafile, address + 6);
                            bmte.NextAnimation   = ByteConverter.ToInt32(datafile, address + 8);
                            bmte.TransitionSpeed = ByteConverter.ToUInt32(datafile, address + 0xC);
                            bmte.StartFrame      = ByteConverter.ToSingle(datafile, address + 0x10);
                            bmte.EndFrame        = ByteConverter.ToSingle(datafile, address + 0x14);
                            bmte.PlaySpeed       = ByteConverter.ToSingle(datafile, address + 0x18);
                            result.Add(bmte);
                            address += 0x1C;
                        }
                        IniSerializer.Serialize(result, Path.Combine(fileOutputPath, "info.ini"));
                        hashes.Add("info.ini:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, "info.ini")));
                        data.MD5Hash = string.Join("|", hashes.ToArray());
                        nohash       = true;
                    }
                    break;

                    case "levelpathlist":
                    {
                        List <string> hashes = new List <string>();
                        ushort        lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address);
                        while (lvlnum != 0xFFFF)
                        {
                            int ptr = ByteConverter.ToInt32(datafile, address + 4);
                            if (ptr != 0)
                            {
                                ptr = (int)((uint)ptr - imageBase);
                                SA1LevelAct level  = new SA1LevelAct(lvlnum);
                                string      lvldir = Path.Combine(fileOutputPath, level.ToString());
                                PathList.Load(datafile, ptr, imageBase).Save(lvldir, out string[] lvlhashes);
                                hashes.Add(level.ToString() + ":" + string.Join(",", lvlhashes));
                            }
                            address += 8;
                            lvlnum   = (ushort)ByteConverter.ToUInt32(datafile, address);
                        }
                        data.MD5Hash = string.Join("|", hashes.ToArray());
                        nohash       = true;
                    }
                    break;

                    case "pathlist":
                    {
                        PathList.Load(datafile, address, imageBase).Save(fileOutputPath, out string[] hashes);
                        data.MD5Hash = string.Join(",", hashes.ToArray());
                        nohash       = true;
                    }
                    break;

                    case "stagelightdatalist":
                        SA1StageLightDataList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "weldlist":
                        WeldList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "bmitemattrlist":
                        BlackMarketItemAttributesList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        break;

                    case "creditstextlist":
                        CreditsTextList.Load(datafile, address, imageBase).Save(fileOutputPath);
                        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", nometa);
                            hashes.Add(i.ToString(NumberFormatInfo.InvariantInfo) + ":" + HelperFunctions.FileHash(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"));
                            address += 8;
                            i        = ByteConverter.ToInt16(datafile, address);
                        }
                        data.MD5Hash = string.Join("|", hashes.ToArray());
                        nohash       = true;
                    }
                    break;

                    case "storysequence":
                        SA2StoryList.Load(datafile, address).Save(fileOutputPath);
                        break;

                    case "masterstringlist":
                    {
                        int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        for (int l = 0; l < 5; l++)
                        {
                            Languages            lng = (Languages)l;
                            System.Text.Encoding enc = HelperFunctions.GetEncoding(inifile.Game, lng);
                            string ld = Path.Combine(fileOutputPath, lng.ToString());
                            Directory.CreateDirectory(ld);
                            int ptr = datafile.GetPointer(address, imageBase);
                            for (int i = 0; i < cnt; i++)
                            {
                                int ptr2 = datafile.GetPointer(ptr, imageBase);
                                if (ptr2 != 0)
                                {
                                    string fn = Path.Combine(ld, $"{i}.txt");
                                    File.WriteAllText(fn, datafile.GetCString(ptr2, enc).Replace("\n", "\r\n"));
                                    inifile.Files.Add($"{filedesc} {lng} {i}", new FileInfo()
                                        {
                                            Type = "string", Filename = fn, PointerList = new int[] { ptr }, MD5Hash = HelperFunctions.FileHash(fn), CustomProperties = new Dictionary <string, string>()
                                            {
                                                { "language", lng.ToString() }
                                            }
                                        });
                                }
                                ptr += 4;
                            }
                            address += 4;
                        }
                        inifile.Files.Remove(filedesc);
                        nohash = true;
                    }
                    break;

                    case "camera":
                        NinjaCamera cam = new NinjaCamera(datafile, address);
                        cam.Save(fileOutputPath);
                        break;

                    case "fogdatatable":
                        int fcnt = 3;
                        if (customProperties.ContainsKey("count"))
                        {
                            fcnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        }
                        FogDataTable fga = new FogDataTable(datafile, address, imageBase, fcnt);
                        fga.Save(fileOutputPath);
                        break;

                    case "palettelightlist":
                        int count = 255;
                        if (customProperties.ContainsKey("count"))
                        {
                            count = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                        }
                        PaletteLightList pllist = new PaletteLightList(datafile, address, count);
                        pllist.Save(fileOutputPath);
                        break;

                    case "physicsdata":
                        PlayerParameter plpm = new PlayerParameter(datafile, address);
                        plpm.Save(fileOutputPath);
                        break;

                    default:                             // raw binary
                    {
                        byte[] bin = new byte[int.Parse(customProperties["size"], NumberStyles.HexNumber)];
                        Array.Copy(datafile, address, bin, 0, bin.Length);
                        File.WriteAllBytes(fileOutputPath, bin);
                    }
                    break;
                    }
                    if (!nohash)
                    {
                        data.MD5Hash = HelperFunctions.FileHash(fileOutputPath);
                    }
                    itemcount++;
                }
                if (inifile.MasterObjectList != null)
                {
                    foreach (KeyValuePair <string, MasterObjectListEntry> obj in masterobjlist)
                    {
                        KeyValuePair <string, int> name = new KeyValuePair <string, int>();
                        foreach (KeyValuePair <string, int> it in objnamecounts[obj.Key])
                        {
                            if (it.Value > name.Value)
                            {
                                name = it;
                            }
                        }
                        obj.Value.Name  = name.Key;
                        obj.Value.Names = objnamecounts[obj.Key].Select((it) => it.Key).ToArray();
                    }

                    string masterObjectListOutputPath = Path.Combine(projectFolderName, inifile.MasterObjectList);

                    IniSerializer.Serialize(masterobjlist, masterObjectListOutputPath);
                }
                IniSerializer.Serialize(inifile, Path.Combine(projectFolderName, Path.GetFileNameWithoutExtension(inifilename) + "_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)SplitERRORVALUE.UnhandledException);
            }
#endif
            return((int)SplitERRORVALUE.Success);
        }
Exemple #7
0
 private void ExportCPP(TextWriter writer, bool SA2)
 {
     Dictionary<uint, string> pointers = new Dictionary<uint, string>();
     uint imagebase = IniData.ImageBase ?? 0x400000;
     ModelFormat modelfmt = 0;
     LandTableFormat landfmt = 0;
     switch (IniData.Game)
     {
         case Game.SA1:
             modelfmt = ModelFormat.Basic;
             landfmt = LandTableFormat.SA1;
             break;
         case Game.SADX:
             modelfmt = ModelFormat.BasicDX;
             landfmt = LandTableFormat.SADX;
             break;
         case Game.SA2:
         case Game.SA2B:
             modelfmt = ModelFormat.Chunk;
             landfmt = LandTableFormat.SA2;
             break;
     }
     writer.WriteLine("// Generated by SA Tools Struct Converter");
     writer.WriteLine();
     if (SA2)
         writer.WriteLine("#include \"SA2ModLoader.h\"");
     else
         writer.WriteLine("#include \"SADXModLoader.h\"");
     writer.WriteLine();
     Dictionary<string, string> models = new Dictionary<string, string>();
     foreach (KeyValuePair<string, SA_Tools.FileInfo> item in IniData.Files.Where((a, i) => listView1.CheckedIndices.Contains(i)))
     {
         string name = item.Key.MakeIdentifier();
         SA_Tools.FileInfo data = item.Value;
         switch (data.Type)
         {
             case "landtable":
                 LandTable tbl = LandTable.LoadFromFile(data.Filename);
                 name = tbl.Name;
                 writer.WriteLine(tbl.ToStructVariables(landfmt, new List<string>()));
                 break;
             case "model":
                 SonicRetro.SAModel.NJS_OBJECT mdl = new ModelFile(data.Filename).Model;
                 name = mdl.Name;
                 writer.WriteLine(mdl.ToStructVariables(modelfmt == ModelFormat.BasicDX, new List<string>()));
                 models.Add(item.Key, mdl.Name);
                 break;
             case "basicmodel":
                 mdl = new SonicRetro.SAModel.ModelFile(data.Filename).Model;
                 name = mdl.Name;
                 writer.WriteLine(mdl.ToStructVariables(false, new List<string>()));
                 models.Add(item.Key, mdl.Name);
                 break;
             case "basicdxmodel":
                 mdl = new SonicRetro.SAModel.ModelFile(data.Filename).Model;
                 name = mdl.Name;
                 writer.WriteLine(mdl.ToStructVariables(true, new List<string>()));
                 models.Add(item.Key, mdl.Name);
                 break;
             case "chunkmodel":
                 mdl = new SonicRetro.SAModel.ModelFile(data.Filename).Model;
                 name = mdl.Name;
                 writer.WriteLine(mdl.ToStructVariables(false, new List<string>()));
                 models.Add(item.Key, mdl.Name);
                 break;
             case "action":
                 Animation ani = Animation.Load(data.Filename);
                 name = "action_" + ani.Name.MakeIdentifier();
                 writer.WriteLine(ani.ToStructVariables());
                 writer.WriteLine("NJS_ACTION {0} = {{ &{1}, &{2} }};", name, models[data.CustomProperties["model"]], ani.Name.MakeIdentifier());
                 break;
             case "animation":
                 ani = Animation.Load(data.Filename);
                 name = ani.Name.MakeIdentifier();
                 writer.WriteLine(ani.ToStructVariables());
                 break;
             case "objlist":
                 {
                     ObjectListEntry[] list = ObjectList.Load(data.Filename, SA2);
                     writer.WriteLine("ObjectListEntry {0}_list[] = {{", name);
                     List<string> objs = new List<string>(list.Length);
                     foreach (ObjectListEntry obj in list)
                         objs.Add(obj.ToStruct() + " " + obj.Name.ToComment());
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                     writer.WriteLine();
                     writer.WriteLine("ObjectList {0} = {{ arraylengthandptr({0}_list) }};", name);
                 }
                 break;
             case "startpos":
                 if (SA2)
                 {
                     Dictionary<SA2LevelIDs, SA2StartPosInfo> list = SA2StartPosList.Load(data.Filename);
                     writer.WriteLine("StartPosition {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Count + 1);
                     foreach (KeyValuePair<SA2LevelIDs, SA2StartPosInfo> obj in list)
                         objs.Add(obj.ToStruct());
                     objs.Add("{ LevelIDs_Invalid }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 else
                 {
                     Dictionary<SA1LevelAct, SA1StartPosInfo> list = SA1StartPosList.Load(data.Filename);
                     writer.WriteLine("StartPosition {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Count + 1);
                     foreach (KeyValuePair<SA1LevelAct, SA1StartPosInfo> obj in list)
                         objs.Add(obj.ToStruct());
                     objs.Add("{ LevelIDs_Invalid }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "texlist":
                 {
                     TextureListEntry[] list = TextureList.Load(data.Filename);
                     writer.WriteLine("PVMEntry {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Length + 1);
                     foreach (TextureListEntry obj in list)
                         objs.Add(obj.ToStruct());
                     objs.Add("{ 0 }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "leveltexlist":
                 {
                     LevelTextureList list = LevelTextureList.Load(data.Filename);
                     writer.WriteLine("PVMEntry {0}_list[] = {{", name);
                     List<string> objs = new List<string>(list.TextureList.Length);
                     foreach (TextureListEntry obj in list.TextureList)
                         objs.Add(obj.ToStruct());
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                     writer.WriteLine();
                     writer.WriteLine("LevelPVMList {0} = {{ {1}, arraylengthandptr({0}_list) }};", name, list.Level.ToC());
                 }
                 break;
             case "triallevellist":
                 {
                     SA1LevelAct[] list = TrialLevelList.Load(data.Filename);
                     writer.WriteLine("TrialLevelListEntry {0}_list[] = {{", name);
                     List<string> objs = new List<string>(list.Length);
                     foreach (SA1LevelAct obj in list)
                         objs.Add(string.Format("{{ {0}, {1} }}", obj.Level.ToC("LevelIDs"), obj.Act));
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                     writer.WriteLine();
                     writer.WriteLine("TrialLevelList {0} = {{ arrayptrandlength({0}_list) }};", name);
                 }
                 break;
             case "bosslevellist":
                 {
                     SA1LevelAct[] list = BossLevelList.Load(data.Filename);
                     writer.WriteLine("__int16 {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Length + 1);
                     foreach (SA1LevelAct obj in list)
                         objs.Add(obj.ToC());
                     objs.Add("levelact(LevelIDs_Invalid, 0)");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "fieldstartpos":
                 {
                     Dictionary<SA1LevelIDs, FieldStartPosInfo> list = FieldStartPosList.Load(data.Filename);
                     writer.WriteLine("FieldStartPosition {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Count + 1);
                     foreach (KeyValuePair<SA1LevelIDs, FieldStartPosInfo> obj in list)
                         objs.Add(obj.ToStruct());
                     objs.Add("{ LevelIDs_Invalid }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "soundtestlist":
                 {
                     SoundTestListEntry[] list = SoundTestList.Load(data.Filename);
                     writer.WriteLine("SoundTestEntry {0}_list[] = {{", name);
                     List<string> objs = new List<string>(list.Length);
                     foreach (SoundTestListEntry obj in list)
                         objs.Add(obj.ToStruct() + " " + obj.Title.ToComment());
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                     writer.WriteLine();
                     writer.WriteLine("SoundTestCategory {0} = {{ arrayptrandlength({0}_list) }};", name);
                 }
                 break;
             case "musiclist":
                 {
                     MusicListEntry[] list = MusicList.Load(data.Filename);
                     writer.WriteLine("MusicInfo {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Length);
                     foreach (MusicListEntry obj in list)
                         objs.Add(obj.ToStruct());
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "soundlist":
                 {
                     SoundListEntry[] list = SoundList.Load(data.Filename);
                     writer.WriteLine("SoundFileInfo {0}_list[] = {{", name);
                     List<string> objs = new List<string>(list.Length);
                     foreach (SoundListEntry obj in list)
                         objs.Add(obj.ToStruct());
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                     writer.WriteLine();
                     writer.WriteLine("SoundList {0} = {{ arraylengthandptr({0}_list) }};", name);
                 }
                 break;
             case "stringarray":
                 {
                     string[] strs = StringArray.Load(data.Filename);
                     Languages lang = Languages.Japanese;
                     if (data.CustomProperties.ContainsKey("language"))
                         lang = (Languages)Enum.Parse(typeof(Languages), data.CustomProperties["language"], true);
                     writer.WriteLine("char *{0}[] = {{", name);
                     List<string> objs = new List<string>(strs.Length);
                     foreach (string obj in strs)
                         objs.Add(obj.ToC(lang) + " " + obj.ToComment());
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "nextlevellist":
                 {
                     NextLevelListEntry[] list = NextLevelList.Load(data.Filename);
                     writer.WriteLine("NextLevelData {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Length + 1);
                     foreach (NextLevelListEntry obj in list)
                         objs.Add(obj.ToStruct());
                     objs.Add("{ 0, -1 }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "cutscenetext":
                 {
                     CutsceneText texts = new CutsceneText(data.Filename);
                     uint addr = (uint)(data.Address + imagebase);
                     for (int j = 0; j < 5; j++)
                     {
                         string[] strs = texts.Text[j];
                         Languages lang = (Languages)j;
                         writer.WriteLine("char *{0}_{1}[] = {{", name, lang);
                         writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", strs.Select((a) => a.ToC(lang) + " " + a.ToComment()).ToArray()));
                         writer.WriteLine("};");
                         writer.WriteLine();
                         pointers.Add(addr, string.Format("{0}_{1}", name, lang));
                         addr += 4;
                     }
                 }
                 break;
             case "recapscreen":
                 {
                     uint addr = (uint)(data.Address + imagebase);
                     RecapScreen[][] texts = RecapScreenList.Load(data.Filename, int.Parse(data.CustomProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo));
                     for (int l = 0; l < 5; l++)
                         for (int j = 0; j < texts.Length; j++)
                         {
                             writer.WriteLine("char *{0}_{1}_{2}_Text[] = {{", name, (Languages)l, j);
                             writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", texts[j][l].Text.Split('\n').Select((a) => a.ToC((Languages)l) + " " + a.ToComment()).ToArray()));
                             writer.WriteLine("};");
                             writer.WriteLine();
                         }
                     for (int l = 0; l < 5; l++)
                     {
                         writer.WriteLine("RecapScreen {0}_{1}[] = {{", name, (Languages)l);
                         List<string> objs = new List<string>(texts.Length);
                         for (int j = 0; j < texts.Length; j++)
                         {
                             RecapScreen scr = texts[j][l];
                             objs.Add(string.Format("{{ {0}, arraylengthandptr({1}_{2}_{3}_Text) }}",
                                 SA_Tools.HelperFunctions.ToC(scr.Speed), name, (Languages)l, j));
                         }
                         writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                         writer.WriteLine("};");
                         writer.WriteLine();
                         pointers.Add(addr, string.Format("{0}_{1}", name, (Languages)l));
                         addr += 4;
                     }
                 }
                 break;
             case "npctext":
                 {
                     NPCText[][] texts = NPCTextList.Load(data.Filename, int.Parse(data.CustomProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo));
                     uint headaddr = (uint)(data.Address + imagebase);
                     for (int l = 0; l < 5; l++)
                     {
                         for (int j = 0; j < texts[l].Length; j++)
                         {
                             if (texts[l][j].Groups.Count == 0)
                                 continue;
                             if (texts[l][j].HasControl)
                             {
                                 writer.WriteLine("__int16 {0}_{1}_{2}_Control[] = {{", name, (Languages)l, j);
                                 bool first = true;
                                 List<string> objs = new List<string>();
                                 foreach (NPCTextGroup group in texts[l][j].Groups)
                                 {
                                     if (!first)
                                         objs.Add(NPCTextControl.NewGroup.ToC());
                                     else
                                         first = false;
                                     foreach (ushort flag in group.EventFlags)
                                     {
                                         objs.Add(NPCTextControl.EventFlag.ToC());
                                         objs.Add(flag.ToCHex());
                                     }
                                     foreach (ushort flag in group.NPCFlags)
                                     {
                                         objs.Add(NPCTextControl.NPCFlag.ToC());
                                         objs.Add(flag.ToCHex());
                                     }
                                     if (group.Character != (SA1CharacterFlags)0xFF)
                                     {
                                         objs.Add(NPCTextControl.Character.ToC());
                                         objs.Add(group.Character.ToC("CharacterFlags"));
                                     }
                                     if (group.Voice.HasValue)
                                     {
                                         objs.Add(NPCTextControl.Voice.ToC());
                                         objs.Add(group.Voice.Value.ToString());
                                     }
                                     if (group.SetEventFlag.HasValue)
                                     {
                                         objs.Add(NPCTextControl.SetEventFlag.ToC());
                                         objs.Add(group.SetEventFlag.Value.ToCHex());
                                     }
                                 }
                                 objs.Add(NPCTextControl.End.ToC());
                                 writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                                 writer.WriteLine("};");
                                 writer.WriteLine();
                             }
                             if (texts[l][j].HasText)
                             {
                                 writer.WriteLine("HintText_Text {0}_{1}_{2}_Text[] = {{", name, (Languages)l, j);
                                 List<string> objs = new List<string>();
                                 foreach (NPCTextGroup group in texts[l][j].Groups)
                                 {
                                     foreach (NPCTextLine line in group.Lines)
                                         objs.Add(line.ToStruct((Languages)l) + " " + line.Line.ToComment());
                                     objs.Add("{ 0 }");
                                 }
                                 writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                                 writer.WriteLine("};");
                                 writer.WriteLine();
                             }
                         }
                     }
                     for (int l = 0; l < 5; l++)
                     {
                         if (l > 0)
                             writer.WriteLine();
                         writer.WriteLine("HintText_Entry {0}_{1}[] = {{", name, (Languages)l);
                         List<string> objs = new List<string>();
                         for (int j = 0; j < texts[l].Length; j++)
                         {
                             if (texts[l][j].Groups.Count == 0)
                             {
                                 objs.Add("{ 0 }");
                                 continue;
                             }
                             StringBuilder line = new StringBuilder("{ ");
                             if (texts[l][j].HasControl)
                                 line.AppendFormat("{0}_{1}_{2}_Control", name, (Languages)l, j);
                             else
                                 line.Append("NULL");
                             line.Append(", ");
                             if (texts[l][j].HasText)
                                 line.AppendFormat("{0}_{1}_{2}_Text", name, (Languages)l, j);
                             else
                                 line.Append("NULL");
                             line.Append(" }");
                             objs.Add(line.ToString());
                         }
                         writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                         writer.WriteLine("};");
                         pointers.Add(headaddr, string.Format("{0}_{1}", name, (Languages)l));
                         headaddr += 4;
                     }
                 }
                 break;
             case "levelclearflags":
                 {
                     LevelClearFlag[] list = LevelClearFlagList.Load(data.Filename);
                     writer.WriteLine("LevelClearFlagData {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Length);
                     foreach (LevelClearFlag obj in list)
                         objs.Add(obj.ToStruct());
                     objs.Add("{ -1 }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "deathzone":
                 {
                     DeathZoneFlags[] list = DeathZoneFlagsList.Load(data.Filename);
                     string path = Path.GetDirectoryName(data.Filename);
                     List<string> mdls = new List<string>(list.Length);
                     List<string> objs = new List<string>();
                     for (int j = 0; j < list.Length; j++)
                     {
                         SonicRetro.SAModel.NJS_OBJECT obj = new ModelFile(Path.Combine(path,
                             j.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl")).Model;
                         writer.WriteLine(obj.ToStructVariables(modelfmt == ModelFormat.BasicDX, objs));
                         mdls.Add(obj.Name);
                         objs.Clear();
                     }
                     writer.WriteLine("DeathZone {0}[] = {{", name);
                     for (int j = 0; j < list.Length; j++)
                         objs.Add(string.Format("{{ {0}, &{1} }}", list[j].Flags.ToC("CharacterFlags"), mdls[j]));
                     objs.Add("{ 0 }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "skyboxscale":
                 {
                     uint headaddr = (uint)(data.Address + imagebase);
                     int cnt = int.Parse(data.CustomProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                     SkyboxScale[] sclini = SkyboxScaleList.Load(data.Filename);
                     for (int j = 0; j < cnt; j++)
                     {
                         writer.WriteLine("SkyboxScale {0}_{1} = {2};", name, j, sclini[j].ToStruct());
                         pointers.Add(headaddr, string.Format("{0}_{1}", name, j));
                         headaddr += 4;
                     }
                 }
                 break;
             case "stageselectlist":
                 {
                     StageSelectLevel[] list = StageSelectLevelList.Load(data.Filename);
                     writer.WriteLine("StageSelectLevel {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Length);
                     foreach (StageSelectLevel obj in list)
                         objs.Add(obj.ToStruct());
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "levelrankscores":
                 {
                     Dictionary<SA2LevelIDs, LevelRankScores> list = LevelRankScoresList.Load(data.Filename);
                     writer.WriteLine("LevelRankScores {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Count);
                     foreach (KeyValuePair<SA2LevelIDs, LevelRankScores> obj in list)
                         objs.Add(obj.ToStruct());
                     objs.Add("{ LevelIDs_Invalid }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "levelranktimes":
                 {
                     Dictionary<SA2LevelIDs, LevelRankTimes> list = LevelRankTimesList.Load(data.Filename);
                     writer.WriteLine("LevelRankTimes {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Count);
                     foreach (KeyValuePair<SA2LevelIDs, LevelRankTimes> obj in list)
                         objs.Add(obj.ToStruct());
                     objs.Add("{ LevelIDs_Invalid }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "endpos":
                 {
                     Dictionary<SA2LevelIDs, SA2EndPosInfo> list = SA2EndPosList.Load(data.Filename);
                     writer.WriteLine("LevelEndPosition {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Count);
                     foreach (KeyValuePair<SA2LevelIDs, SA2EndPosInfo> obj in list)
                         objs.Add(obj.ToStruct());
                     objs.Add("{ LevelIDs_Invalid }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "animationlist":
                 {
                     SA2AnimationInfo[] list = SA2AnimationInfoList.Load(data.Filename);
                     writer.WriteLine("AnimationInfo {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Length);
                     foreach (SA2AnimationInfo obj in list)
                         objs.Add(obj.ToStruct());
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
             case "levelpathlist":
                 {
                     List<SA1LevelAct> levels = new List<SA1LevelAct>();
                     foreach (string dir in Directory.GetDirectories(data.Filename))
                     {
                         SA1LevelAct level;
                         try { level = new SA1LevelAct(new DirectoryInfo(dir).Name); }
                         catch { continue; }
                         levels.Add(level);
                         List<PathData> paths = PathList.Load(dir);
                         for (int i = 0; i < paths.Count; i++)
                         {
                             writer.WriteLine("Loop {0}_{1}_{2}_Entries[] = {{", name, level.ToString().MakeIdentifier(), i);
                             List<string> objs = new List<string>(paths[i].Path.Count);
                             foreach (PathDataEntry entry in paths[i].Path)
                                 objs.Add(entry.ToStruct());
                             writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                             writer.WriteLine("};");
                             writer.WriteLine();
                             writer.WriteLine("LoopHead {0}_{1}_{2} = {{ {3}, LengthOfArray({0}_{1}_{2}_Entries), {4}, {0}_{1}_{2}_Entries, (ObjectFuncPtr){5} }};",
                                 name, level.ToString().MakeIdentifier(), i, paths[i].Unknown,
                                 HelperFunctions.ToC(paths[i].TotalDistance),
                                 HelperFunctions.ToCHex(paths[i].Code));
                             writer.WriteLine();
                         }
                         writer.WriteLine("LoopHead *{0}_{1}[] = {{", name, level.ToString().MakeIdentifier());
                         for (int i = 0; i < paths.Count; i++)
                             writer.WriteLine("\t&{0}_{1}_{2},", name, level.ToString().MakeIdentifier(), i);
                         writer.WriteLine("\tNULL");
                         writer.WriteLine("};");
                         writer.WriteLine();
                     }
                     writer.WriteLine("PathDataPtr {0}[] = {{", name);
                     foreach (SA1LevelAct level in levels)
                         writer.WriteLine("\t{{ {0}, {1}_{2} }},", level.ToC(), name,
                             level.ToString().MakeIdentifier());
                     writer.WriteLine("\t{ 0xFFFF }");
                     writer.WriteLine("};");
                     writer.WriteLine();
                 }
                 break;
             case "stagelightdatalist":
                 {
                     List<SA1StageLightData> list = SA1StageLightDataList.Load(data.Filename);
                     writer.WriteLine("StageLightData {0}[] = {{", name);
                     List<string> objs = new List<string>(list.Count + 1);
                     foreach (SA1StageLightData obj in list)
                         objs.Add(obj.ToStruct());
                     objs.Add("{ 0xFFu }");
                     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray()));
                     writer.WriteLine("};");
                 }
                 break;
         }
         writer.WriteLine();
         if (data.PointerList != null && data.PointerList.Length > 0)
             foreach (int ptr in data.PointerList)
                 pointers.Add((uint)(ptr + imagebase), name);
     }
     writer.WriteLine("PointerInfo pointers[] = {");
     List<string> ptrs = new List<string>(pointers.Count);
     foreach (KeyValuePair<uint, string> ptr in pointers)
         ptrs.Add(string.Format("ptrdecl({0}, &{1})", HelperFunctions.ToCHex(ptr.Key), ptr.Value));
     writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", ptrs.ToArray()));
     writer.WriteLine("};");
     writer.WriteLine();
 }
Exemple #8
0
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            #if !DEBUG
            try
            {
            #endif
                int steps = 10;
                if (d3ddevice == null)
                    ++steps;

                toolStrip1.Enabled = false;

                // HACK: Fixes Twinkle Circuit's geometry lingering if loaded before Sky Chase.
                // I'm sure the real problem is somewhere below, but this is sort of an all around cleanup.
                if (isStageLoaded)
                    LevelData.Clear();

                isStageLoaded = false;

                using (ProgressDialog progress = new ProgressDialog("Loading stage: " + levelName, steps))
                {
                    IniLevelData level = ini.Levels[levelID];

                    string syspath = Path.Combine(Environment.CurrentDirectory, ini.SystemPath);
                    string modpath = ini.ModPath;

                    SA1LevelAct levelact = new SA1LevelAct(level.LevelID);
                    LevelData.leveltexs = null;
                    cam = new EditorCamera(EditorOptions.RenderDrawDistance);

                    Invoke((Action<IWin32Window>)progress.Show, this);

                    if (d3ddevice == null)
                    {
                        progress.SetTask("Initializing Direct3D...");
                        Invoke((Action)InitializeDirect3D);
                        progress.StepProgress();
                    }

                    progress.SetTaskAndStep("Loading level data:", "Geometry");

                    if (string.IsNullOrEmpty(level.LevelGeometry))
                        LevelData.geo = null;
                    else
                    {
                        LevelData.geo = LandTable.LoadFromFile(level.LevelGeometry);
                        LevelData.LevelItems = new List<LevelItem>();
                        for (int i = 0; i < LevelData.geo.COL.Count; i++)
                            LevelData.LevelItems.Add(new LevelItem(LevelData.geo.COL[i], d3ddevice, i, selectedItems));
                    }

                    progress.StepProgress();
                    progress.SetStep("Textures");

                    LevelData.TextureBitmaps = new Dictionary<string, BMPInfo[]>();
                    LevelData.Textures = new Dictionary<string, Texture[]>();
                    if (LevelData.geo != null && !string.IsNullOrEmpty(LevelData.geo.TextureFileName))
                    {
                        BMPInfo[] TexBmps =
                            TextureArchive.GetTextures(Path.Combine(syspath, LevelData.geo.TextureFileName) + ".PVM");
                        Texture[] texs = new Texture[TexBmps.Length];
                        for (int j = 0; j < TexBmps.Length; j++)
                            texs[j] = new Texture(d3ddevice, TexBmps[j].Image, Usage.None, Pool.Managed);
                        if (!LevelData.TextureBitmaps.ContainsKey(LevelData.geo.TextureFileName))
                            LevelData.TextureBitmaps.Add(LevelData.geo.TextureFileName, TexBmps);
                        if (!LevelData.Textures.ContainsKey(LevelData.geo.TextureFileName))
                            LevelData.Textures.Add(LevelData.geo.TextureFileName, texs);
                        LevelData.leveltexs = LevelData.geo.TextureFileName;
                    }

                    progress.StepProgress();

                    #region Start Positions

                    progress.SetTaskAndStep("Setting up start positions...");

                    LevelData.StartPositions = new StartPosItem[LevelData.Characters.Length];
                    for (int i = 0; i < LevelData.StartPositions.Length; i++)
                    {
                        progress.SetStep(string.Format("{0}/{1}", (i + 1), LevelData.StartPositions.Length));

                        IniCharInfo character;
                        if (i == 0 && levelact.Level == SA1LevelIDs.PerfectChaos)
                            character = ini.Characters["SuperSonic"];
                        else
                            character = ini.Characters[LevelData.Characters[i]];

                        Dictionary<SA1LevelAct, SA1StartPosInfo> posini =
                            SA1StartPosList.Load(character.StartPositions);

                        Vertex pos = new Vertex();
                        int rot = 0;

                        if (posini.ContainsKey(levelact))
                        {
                            pos = posini[levelact].Position;
                            rot = posini[levelact].YRotation;
                        }
                        LevelData.StartPositions[i] = new StartPosItem(new ModelFile(character.Model).Model,
                            character.Textures, character.Height, pos, rot, d3ddevice, selectedItems);

                        LoadTextureList(character.TextureList, syspath);
                    }

                    progress.StepProgress();

                    #endregion

                    #region Death Zones

                    progress.SetTaskAndStep("Death Zones:", "Initializing...");

                    if (string.IsNullOrEmpty(level.DeathZones))
                        LevelData.DeathZones = null;
                    else
                    {
                        LevelData.DeathZones = new List<DeathZoneItem>();
                        DeathZoneFlags[] dzini = DeathZoneFlagsList.Load(level.DeathZones);
                        string path = Path.GetDirectoryName(level.DeathZones);
                        for (int i = 0; i < dzini.Length; i++)
                        {
                            progress.SetStep(String.Format("Loading model {0}/{1}", (i + 1), dzini.Length));

                            LevelData.DeathZones.Add(new DeathZoneItem(
                                new ModelFile(Path.Combine(path, i.ToString(System.Globalization.NumberFormatInfo.InvariantInfo) + ".sa1mdl"))
                                    .Model,
                                dzini[i].Flags, d3ddevice, selectedItems));
                        }
                    }

                    progress.StepProgress();

                    #endregion

                    #region Textures and Texture Lists

                    progress.SetTaskAndStep("Loading textures for:");

                    progress.SetStep("Common objects");
                    // Loads common object textures (e.g OBJ_REGULAR)
                    LoadTextureList(ini.ObjectTextureList, syspath);

                    progress.SetTaskAndStep("Loading stage texture lists...");

                    // Loads the textures in the texture list for this stage (e.g BEACH01)
                    foreach (string file in Directory.GetFiles(ini.LevelTextureLists))
                    {
                        LevelTextureList texini = LevelTextureList.Load(file);
                        if (texini.Level != levelact)
                            continue;

                        LoadTextureList(texini.TextureList, syspath);
                    }

                    progress.SetTaskAndStep("Loading textures for:", "Objects");
                    // Object texture list(s)
                    LoadTextureList(level.ObjectTextureList, syspath);

                    progress.SetStep("Stage");
                    // The stage textures... again? "Extra"?
                    if (level.Textures != null && level.Textures.Length > 0)
                        foreach (string tex in level.Textures)
                        {
                            LoadPVM(tex, syspath);

                            if (string.IsNullOrEmpty(LevelData.leveltexs))
                                LevelData.leveltexs = tex;
                        }

                    progress.StepProgress();

                    #endregion

                    #region Object Definitions / SET Layout

                    progress.SetTaskAndStep("Loading Object Definitions:", "Parsing...");

                    LevelData.ObjDefs = new List<ObjectDefinition>();
                    Dictionary<string, ObjectData> objdefini =
                        IniSerializer.Deserialize<Dictionary<string, ObjectData>>(ini.ObjectDefinitions);

                    if (!string.IsNullOrEmpty(level.ObjectList) && File.Exists(level.ObjectList))
                    {
                        List<ObjectData> objectErrors = new List<ObjectData>();
                        ObjectListEntry[] objlstini = ObjectList.Load(level.ObjectList, false);
                        Directory.CreateDirectory("dllcache").Attributes |= FileAttributes.Hidden;

                        for (int ID = 0; ID < objlstini.Length; ID++)
                        {
                            string codeaddr = objlstini[ID].CodeString;

                            if (!objdefini.ContainsKey(codeaddr))
                                codeaddr = "0";

                            ObjectData defgroup = objdefini[codeaddr];
                            ObjectDefinition def;

                            if (!string.IsNullOrEmpty(defgroup.CodeFile))
                            {
                                progress.SetStep("Compiling: " + defgroup.CodeFile);

                                // TODO: Split this out to a function
                                #region Compile object code files

                                string ty = defgroup.CodeType;
                                string dllfile = Path.Combine("dllcache", ty + ".dll");
                                DateTime modDate = DateTime.MinValue;
                                if (File.Exists(dllfile))
                                    modDate = File.GetLastWriteTime(dllfile);
                                string fp = defgroup.CodeFile.Replace('/', Path.DirectorySeparatorChar);
                                if (modDate >= File.GetLastWriteTime(fp) && modDate > File.GetLastWriteTime(Application.ExecutablePath))
                                    def =
                                        (ObjectDefinition)
                                            Activator.CreateInstance(
                                                Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, dllfile))
                                                    .GetType(ty));
                                else
                                {
                                    string ext = Path.GetExtension(fp);
                                    CodeDomProvider pr = null;
                                    switch (ext.ToLowerInvariant())
                                    {
                                        case ".cs":
                                            pr = new Microsoft.CSharp.CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
                                            break;
                                        case ".vb":
                                            pr = new Microsoft.VisualBasic.VBCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
                                            break;
                                    }
                                    if (pr != null)
                                    {
                                        CompilerParameters para =
                                            new CompilerParameters(new string[]
                                            {
                                                "System.dll", "System.Core.dll", "System.Drawing.dll", Assembly.GetAssembly(typeof (Vector3)).Location,
                                                Assembly.GetAssembly(typeof (Texture)).Location, Assembly.GetAssembly(typeof (D3DX)).Location,
                                                Assembly.GetExecutingAssembly().Location, Assembly.GetAssembly(typeof (LandTable)).Location,
                                                Assembly.GetAssembly(typeof (EditorCamera)).Location, Assembly.GetAssembly(typeof (SA1LevelAct)).Location,
                                                Assembly.GetAssembly(typeof (ObjectDefinition)).Location
                                            })
                                            {
                                                GenerateExecutable = false,
                                                GenerateInMemory = false,
                                                IncludeDebugInformation = true,
                                                OutputAssembly = Path.Combine(Environment.CurrentDirectory, dllfile)
                                            };
                                        CompilerResults res = pr.CompileAssemblyFromFile(para, fp);
                                        if (res.Errors.HasErrors)
                                        {
                                            // TODO: Merge with existing object error handler. I add too many ToDos.
                                            string errors = null;
                                            foreach (CompilerError item in res.Errors)
                                                errors += String.Format("\n\n{0}, {1}: {2}", item.Line, item.Column, item.ErrorText);

                                            MessageBox.Show("Failed to compile object code file:\n" + defgroup.CodeFile + errors,
                                                "Object compilation failure", MessageBoxButtons.OK, MessageBoxIcon.Error);

                                            def = new DefaultObjectDefinition();
                                        }
                                        else
                                        {
                                            def = (ObjectDefinition)Activator.CreateInstance(res.CompiledAssembly.GetType(ty));
                                        }
                                    }
                                    else
                                        def = new DefaultObjectDefinition();
                                }

                                #endregion
                            }
                            else
                            {
                                def = new DefaultObjectDefinition();
                            }

                            LevelData.ObjDefs.Add(def);

                            // The only reason .Model is checked for null is for objects that don't yet have any
                            // models defined for them. It would be annoying seeing that error all the time!
                            if (string.IsNullOrEmpty(defgroup.CodeFile) && !string.IsNullOrEmpty(defgroup.Model))
                            {
                                progress.SetStep("Loading: " + defgroup.Model);
                                // Otherwise, if the model file doesn't exist and/or no texture file is defined,
                                // load the "default object" instead ("?").
                                if (!File.Exists(defgroup.Model) || string.IsNullOrEmpty(defgroup.Texture) ||
                                    !LevelData.Textures.ContainsKey(defgroup.Texture))
                                {
                                    ObjectData error = new ObjectData { Name = defgroup.Name, Model = defgroup.Model, Texture = defgroup.Texture };
                                    objectErrors.Add(error);
                                    defgroup.Model = null;
                                }
                            }

                            def.Init(defgroup, objlstini[ID].Name, d3ddevice);
                            def.SetInternalName(objlstini[ID].Name);
                        }

                        // Loading SET Layout
                        progress.SetTaskAndStep("Loading SET items", "Initializing...");

                        if (LevelData.ObjDefs.Count > 0)
                        {
                            LevelData.SETName = level.SETName ?? level.LevelID;
                            string setstr = Path.Combine(syspath, "SET" + LevelData.SETName + "{0}.bin");
                            LevelData.SETItems = new List<SETItem>[LevelData.SETChars.Length];
                            for (int i = 0; i < LevelData.SETChars.Length; i++)
                            {
                                List<SETItem> list = new List<SETItem>();
                                byte[] setfile = null;

                                string formatted = string.Format(setstr, LevelData.SETChars[i]);

                                if (modpath != null && File.Exists(Path.Combine(modpath, formatted)))
                                    setfile = File.ReadAllBytes(Path.Combine(modpath, formatted));
                                else if (File.Exists(formatted))
                                    setfile = File.ReadAllBytes(formatted);

                                if (setfile != null)
                                {
                                    progress.SetTask("SET: " + formatted.Replace(Environment.CurrentDirectory, ""));

                                    int count = BitConverter.ToInt32(setfile, 0);
                                    int address = 0x20;
                                    for (int j = 0; j < count; j++)
                                    {
                                        progress.SetStep(string.Format("{0}/{1}", (j + 1), count));

                                        SETItem ent = new SETItem(setfile, address, selectedItems);
                                        list.Add(ent);
                                        address += 0x20;
                                    }
                                }
                                LevelData.SETItems[i] = list;
                            }
                        }
                        else
                        {
                            LevelData.SETItems = null;
                        }

                        // Checks if there have been any errors added to the error list and does its thing
                        // This thing is a mess. If anyone can think of a cleaner way to do this, be my guest.
                        if (objectErrors.Count > 0)
                        {
                            int count = objectErrors.Count;
                            List<string> errorStrings = new List<string> { "The following objects failed to load:" };

                            foreach (ObjectData o in objectErrors)
                            {
                                bool texEmpty = string.IsNullOrEmpty(o.Texture);
                                bool texExists = (!string.IsNullOrEmpty(o.Texture) && LevelData.Textures.ContainsKey(o.Texture));
                                errorStrings.Add("");
                                errorStrings.Add("Object:\t\t" + o.Name);
                                errorStrings.Add("\tModel:");
                                errorStrings.Add("\t\tName:\t" + o.Model);
                                errorStrings.Add("\t\tExists:\t" + File.Exists(o.Model));
                                errorStrings.Add("\tTexture:");
                                errorStrings.Add("\t\tName:\t" + ((texEmpty) ? "(N/A)" : o.Texture));
                                errorStrings.Add("\t\tExists:\t" + texExists);
                            }

                            // TODO: Proper logging. Who knows where this file may end up
                            File.WriteAllLines("SADXLVL2.log", errorStrings.ToArray());

                            MessageBox.Show(count + ((count == 1) ? " object" : " objects") + " failed to load their model(s).\n"
                                            +
                                            "\nThe level will still display, but the objects in question will not display their proper models." +
                                            "\n\nPlease check the log for details.",
                                "Error loading models", MessageBoxButtons.OK, MessageBoxIcon.Information);
                        }
                    }
                    else
                    {
                        LevelData.SETItems = null;
                    }

                    progress.StepProgress();

                    #endregion

                    #region CAM Layout

                    progress.SetTaskAndStep("Loading CAM items", "Initializing...");

                    string camstr = Path.Combine(syspath, "CAM" + LevelData.SETName + "{0}.bin");

                    LevelData.CAMItems = new List<CAMItem>[LevelData.SETChars.Length];
                    for (int i = 0; i < LevelData.SETChars.Length; i++)
                    {
                        List<CAMItem> list = new List<CAMItem>();
                        byte[] camfile = null;

                        string formatted = string.Format(camstr, LevelData.SETChars[i]);

                        if (modpath != null && File.Exists(Path.Combine(modpath, formatted)))
                            camfile = File.ReadAllBytes(Path.Combine(modpath, formatted));
                        else if (File.Exists(formatted))
                            camfile = File.ReadAllBytes(formatted);

                        if (camfile != null)
                        {
                            progress.SetTask("CAM: " + formatted.Replace(Environment.CurrentDirectory, ""));

                            int count = BitConverter.ToInt32(camfile, 0);
                            int address = 0x40;
                            for (int j = 0; j < count; j++)
                            {
                                progress.SetStep(string.Format("{0}/{1}", (j + 1), count));

                                CAMItem ent = new CAMItem(camfile, address, selectedItems);
                                list.Add(ent);
                                address += 0x40;
                            }
                        }

                        LevelData.CAMItems[i] = list;
                    }

                    CAMItem.Init(d3ddevice);

                    progress.StepProgress();

                    #endregion

                    #region Loading Level Effects

                    LevelData.leveleff = null;
                    if (!string.IsNullOrEmpty(level.Effects))
                    {
                        progress.SetTaskAndStep("Loading Level Effects...");

                        LevelDefinition def = null;
                        string ty = "SADXObjectDefinitions.Level_Effects." + Path.GetFileNameWithoutExtension(level.Effects);
                        string dllfile = Path.Combine("dllcache", ty + ".dll");
                        DateTime modDate = DateTime.MinValue;

                        if (File.Exists(dllfile))
                            modDate = File.GetLastWriteTime(dllfile);

                        string fp = level.Effects.Replace('/', Path.DirectorySeparatorChar);
                        if (modDate >= File.GetLastWriteTime(fp) && modDate > File.GetLastWriteTime(Application.ExecutablePath))
                        {
                            def =
                                (LevelDefinition)
                                    Activator.CreateInstance(
                                        Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, dllfile)).GetType(ty));
                        }
                        else
                        {
                            string ext = Path.GetExtension(fp);
                            CodeDomProvider pr = null;
                            switch (ext.ToLowerInvariant())
                            {
                                case ".cs":
                                    pr = new Microsoft.CSharp.CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
                                    break;
                                case ".vb":
                                    pr = new Microsoft.VisualBasic.VBCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
                                    break;
                            }
                            if (pr != null)
                            {
                                CompilerParameters para =
                                    new CompilerParameters(new string[]
                                {
                                    "System.dll", "System.Core.dll", "System.Drawing.dll", Assembly.GetAssembly(typeof (Vector3)).Location,
                                    Assembly.GetAssembly(typeof (Texture)).Location, Assembly.GetAssembly(typeof (D3DX)).Location,
                                    Assembly.GetExecutingAssembly().Location, Assembly.GetAssembly(typeof (LandTable)).Location,
                                    Assembly.GetAssembly(typeof (EditorCamera)).Location, Assembly.GetAssembly(typeof (SA1LevelAct)).Location,
                                    Assembly.GetAssembly(typeof (Item)).Location
                                })
                                    {
                                        GenerateExecutable = false,
                                        GenerateInMemory = false,
                                        IncludeDebugInformation = true,
                                        OutputAssembly = Path.Combine(Environment.CurrentDirectory, dllfile)
                                    };
                                CompilerResults res = pr.CompileAssemblyFromFile(para, fp);
                                if (!res.Errors.HasErrors)
                                    def = (LevelDefinition)Activator.CreateInstance(res.CompiledAssembly.GetType(ty));
                            }
                        }

                        if (def != null)
                            def.Init(level, levelact.Act, d3ddevice);

                        LevelData.leveleff = def;
                    }

                    progress.StepProgress();

                    #endregion

                    #region Loading Splines

                    LevelData.LevelSplines = new List<SplineData>();
                    SplineData.Init();

                    if (!string.IsNullOrEmpty(ini.Paths))
                    {
                        progress.SetTaskAndStep("Reticulating splines...");

                        String splineDirectory = Path.Combine(Path.Combine(Environment.CurrentDirectory, ini.Paths),
                            levelact.ToString());

                        if (Directory.Exists(splineDirectory))
                        {
                            List<string> pathFiles = new List<string>();

                            for (int i = 0; i < int.MaxValue; i++)
                            {
                                string path = string.Concat(splineDirectory, string.Format("/{0}.ini", i));
                                if (File.Exists(path))
                                {
                                    pathFiles.Add(path);
                                }
                                else
                                    break;
                            }

                            foreach (string pathFile in pathFiles) // looping through path files
                            {
                                SplineData newSpline = new SplineData(PathData.Load(pathFile), selectedItems);

                                newSpline.RebuildMesh(d3ddevice);

                                LevelData.LevelSplines.Add(newSpline);
                            }
                        }
                    }

                    progress.StepProgress();

                    #endregion

                    #region Stage Lights
                    progress.SetTaskAndStep("Loading lights...");

                    if ((stageLightList != null) && (stageLightList.Count > 0))
                    {
                        List<SA1StageLightData> lightList = new List<SA1StageLightData>();

                        foreach (SA1StageLightData lightData in stageLightList)
                        {
                            if ((lightData.Level == levelact.Level) && (lightData.Act == levelact.Act))
                                lightList.Add(lightData);
                        }

                        if (lightList.Count > 0)
                        {
                            for (int i = 0; i < d3ddevice.Lights.Count; i++) // clear all default lights
                            {
                                d3ddevice.Lights[i].Enabled = false;
                            }

                            for (int i = 0; i < lightList.Count; i++)
                            {
                                SA1StageLightData lightData = lightList[i];

                                d3ddevice.Lights[i].Enabled = true;
                                d3ddevice.Lights[i].Type = (lightData.UseDirection) ? LightType.Directional : LightType.Point;
                                d3ddevice.Lights[i].Diffuse = lightData.RGB.ToColor();
                                d3ddevice.Lights[i].DiffuseColor = new ColorValue(lightData.RGB.X, lightData.RGB.Y, lightData.RGB.Z, 1.0f);
                                d3ddevice.Lights[i].Ambient = lightData.AmbientRGB.ToColor();
                                d3ddevice.Lights[i].Specular = Color.Black;
                                d3ddevice.Lights[i].Direction = lightData.Direction.ToVector3();
                                d3ddevice.Lights[i].Range = lightData.Dif; // guessing here
                            }
                        }
                        else
                        {
                            MessageBox.Show("No lights were found for this stage. Using default lights instead.", "No lights found",
                                MessageBoxButtons.OK, MessageBoxIcon.Warning);
                        }
                    }

                    progress.StepProgress();
                    #endregion

                    transformGizmo = new TransformGizmo();

                    Invoke((Action)progress.Close);
                }
            #if !DEBUG
            }
            catch (Exception ex)
            {
                MessageBox.Show(
                    ex.GetType().Name + ": " + ex.Message + "\nLog file has been saved to " + Path.Combine(Environment.CurrentDirectory, "SADXLVL2.log") + ".\nSend this to MainMemory on the Sonic Retro forums.",
                    "SADXLVL2 Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                File.WriteAllText("SADXLVL2.log", ex.ToString());
                initerror = true;
            }
            #endif
        }
Exemple #9
0
        /// <summary>
        /// Saves changes made to the currently loaded stage.
        /// </summary>
        /// <param name="autoCloseDialog">Defines whether or not the progress dialog should close on completion.</param>
        private void SaveStage(bool autoCloseDialog)
        {
            if (!isStageLoaded)
                return;

            ProgressDialog progress = new ProgressDialog("Saving stage: " + levelName, 5, true, autoCloseDialog);
            progress.Show(this);
            Application.DoEvents();

            IniLevelData level = ini.Levels[levelID];
            string syspath = Path.Combine(Environment.CurrentDirectory, ini.SystemPath);
            string modpath = ini.ModPath;
            SA1LevelAct levelact = new SA1LevelAct(level.LevelID);

            progress.SetTaskAndStep("Saving:", "Geometry...");

            if (LevelData.geo != null)
            {
                LevelData.geo.Tool = "SADXLVL2";
                LevelData.geo.SaveToFile(level.LevelGeometry, LandTableFormat.SA1);
            }

            progress.StepProgress();

            progress.Step = "Start positions...";
            Application.DoEvents();

            for (int i = 0; i < LevelData.StartPositions.Length; i++)
            {
                Dictionary<SA1LevelAct, SA1StartPosInfo> posini =
                    SA1StartPosList.Load(ini.Characters[LevelData.Characters[i]].StartPositions);

                if (posini.ContainsKey(levelact))
                    posini.Remove(levelact);

                if (LevelData.StartPositions[i].Position.X != 0 || LevelData.StartPositions[i].Position.Y != 0 ||
                    LevelData.StartPositions[i].Position.Z != 0 || LevelData.StartPositions[i].Rotation.Y != 0)
                {
                    posini.Add(levelact,
                        new SA1StartPosInfo()
                        {
                            Position = LevelData.StartPositions[i].Position,
                            YRotation = LevelData.StartPositions[i].Rotation.Y
                        });
                }
                posini.Save(ini.Characters[LevelData.Characters[i]].StartPositions);
            }

            progress.StepProgress();

            progress.Step = "Death zones...";
            Application.DoEvents();

            if (LevelData.DeathZones != null)
            {
                DeathZoneFlags[] dzini = new DeathZoneFlags[LevelData.DeathZones.Count];
                string path = Path.GetDirectoryName(level.DeathZones);
                for (int i = 0; i < LevelData.DeathZones.Count; i++)
                    dzini[i] = LevelData.DeathZones[i].Save(path, i);
                dzini.Save(level.DeathZones);
            }

            progress.StepProgress();

            #region Saving SET Items

            progress.Step = "SET items...";
            Application.DoEvents();

            if (LevelData.SETItems != null)
            {
                for (int i = 0; i < LevelData.SETItems.Length; i++)
                {
                    string setstr = Path.Combine(syspath, "SET" + LevelData.SETName + LevelData.SETChars[i] + ".bin");
                    if (modpath != null)
                        setstr = Path.Combine(modpath, setstr);

                    // TODO: Consider simply blanking the SET file instead of deleting it.
                    // Completely deleting it might be undesirable since Sonic's layout will be loaded
                    // in place of the missing file. And where mods are concerned, you could have conflicts
                    // with other mods if the file is deleted.
                    if (File.Exists(setstr))
                        File.Delete(setstr);
                    if (LevelData.SETItems[i].Count == 0)
                        continue;

                    List<byte> file = new List<byte>(LevelData.SETItems[i].Count*0x20 + 0x20);
                    file.AddRange(BitConverter.GetBytes(LevelData.SETItems[i].Count));
                    file.Align(0x20);

                    foreach (SETItem item in LevelData.SETItems[i])
                        file.AddRange(item.GetBytes());

                    File.WriteAllBytes(setstr, file.ToArray());
                }
            }

            progress.StepProgress();

            #endregion

            #region Saving CAM Items

            progress.Step = "CAM items...";
            Application.DoEvents();

            if (LevelData.CAMItems != null)
            {
                for (int i = 0; i < LevelData.CAMItems.Length; i++)
                {
                    string camString = Path.Combine(syspath, "CAM" + LevelData.SETName + LevelData.SETChars[i] + ".bin");
                    if (modpath != null)
                        camString = Path.Combine(modpath, camString);

                    // TODO: Handle this differently. File stream? If the user is using a symbolic link for example, we defeat the purpose by deleting it.
                    if (File.Exists(camString))
                        File.Delete(camString);

                    if (LevelData.CAMItems[i].Count == 0)
                        continue;

                    List<byte> file = new List<byte>(LevelData.CAMItems[i].Count*0x40 + 0x40); // setting up file size and header
                    file.AddRange(BitConverter.GetBytes(LevelData.CAMItems[i].Count));
                    file.Align(0x40);

                    foreach (CAMItem item in LevelData.CAMItems[i]) // outputting individual components
                        file.AddRange(item.GetBytes());

                    File.WriteAllBytes(camString, file.ToArray());
                }
            }

            progress.StepProgress();
            progress.SetTaskAndStep("Save complete!");
            Application.DoEvents();

            #endregion
        }
Exemple #10
0
 static void Main(string[] args)
 {
     string datafilename, inifilename;
     if (args.Length > 0)
     {
         datafilename = args[0];
         Console.WriteLine("File: {0}", datafilename);
     }
     else
     {
         Console.Write("File: ");
         datafilename = Console.ReadLine();
     }
     if (args.Length > 1)
     {
         inifilename = args[1];
         Console.WriteLine("INI File: {0}", inifilename);
     }
     else
     {
         Console.Write("INI File: ");
         inifilename = Console.ReadLine();
     }
     byte[] datafile = File.ReadAllBytes(datafilename);
     IniData inifile = IniSerializer.Deserialize<IniData>(inifilename);
     SA_Tools.ByteConverter.BigEndian = SonicRetro.SAModel.ByteConverter.BigEndian = inifile.BigEndian;
     Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(datafilename));
     if (inifile.Compressed) datafile = FraGag.Compression.Prs.Decompress(datafile);
     uint imageBase = HelperFunctions.SetupEXE(ref datafile) ?? inifile.ImageBase.Value;
     if (Path.GetExtension(datafilename).Equals(".rel", StringComparison.OrdinalIgnoreCase)) HelperFunctions.FixRELPointers(datafile);
     bool SA2 = inifile.Game == Game.SA2 | inifile.Game == Game.SA2B;
     ModelFormat modelfmt = 0;
     LandTableFormat landfmt = 0;
     switch (inifile.Game)
     {
         case Game.SA1:
             modelfmt = ModelFormat.Basic;
             landfmt = LandTableFormat.SA1;
             break;
         case Game.SADX:
             modelfmt = ModelFormat.BasicDX;
             landfmt = LandTableFormat.SADX;
             break;
         case Game.SA2:
         case Game.SA2B:
             modelfmt = ModelFormat.Chunk;
             landfmt = LandTableFormat.SA2;
             break;
     }
     int itemcount = 0;
     Dictionary<string, MasterObjectListEntry> masterobjlist = new Dictionary<string, MasterObjectListEntry>();
     Dictionary<string, Dictionary<string, int>> objnamecounts = new Dictionary<string, Dictionary<string, int>>();
     Stopwatch timer = new Stopwatch();
     timer.Start();
     foreach (KeyValuePair<string, SA_Tools.FileInfo> item in inifile.Files)
     {
         if (string.IsNullOrEmpty(item.Key)) continue;
         string filedesc = item.Key;
         SA_Tools.FileInfo data = item.Value;
         Dictionary<string, string> customProperties = data.CustomProperties;
         string type = data.Type;
         int address = data.Address;
         bool nohash = false;
         Console.WriteLine(item.Key + ": " + data.Address.ToString("X") + " → " + data.Filename);
         Directory.CreateDirectory(Path.GetDirectoryName(data.Filename));
         switch (type)
         {
             case "landtable":
                 new LandTable(datafile, address, imageBase, landfmt) { Description = item.Key, Tool = "split" }.SaveToFile(data.Filename, landfmt);
                 break;
             case "model":
                 {
                     SonicRetro.SAModel.NJS_OBJECT mdl = new SonicRetro.SAModel.NJS_OBJECT(datafile, address, imageBase, modelfmt);
                     string[] mdlanis = new string[0];
                     if (customProperties.ContainsKey("animations"))
                         mdlanis = customProperties["animations"].Split(',');
                     string[] mdlmorphs = new string[0];
                     if (customProperties.ContainsKey("morphs"))
                         mdlmorphs = customProperties["morphs"].Split(',');
                     ModelFile.CreateFile(data.Filename, mdl, mdlanis, mdlmorphs, null, item.Key, "split", null, modelfmt);
                 }
                 break;
             case "basicmodel":
                 {
                     SonicRetro.SAModel.NJS_OBJECT mdl = new SonicRetro.SAModel.NJS_OBJECT(datafile, address, imageBase, ModelFormat.Basic);
                     string[] mdlanis = new string[0];
                     if (customProperties.ContainsKey("animations"))
                         mdlanis = customProperties["animations"].Split(',');
                     string[] mdlmorphs = new string[0];
                     if (customProperties.ContainsKey("morphs"))
                         mdlmorphs = customProperties["morphs"].Split(',');
                     ModelFile.CreateFile(data.Filename, mdl, mdlanis, mdlmorphs, null, item.Key, "split", null, ModelFormat.Basic);
                 }
                 break;
             case "basicdxmodel":
                 {
                     SonicRetro.SAModel.NJS_OBJECT mdl = new SonicRetro.SAModel.NJS_OBJECT(datafile, address, imageBase, ModelFormat.BasicDX);
                     string[] mdlanis = new string[0];
                     if (customProperties.ContainsKey("animations"))
                         mdlanis = customProperties["animations"].Split(',');
                     string[] mdlmorphs = new string[0];
                     if (customProperties.ContainsKey("morphs"))
                         mdlmorphs = customProperties["morphs"].Split(',');
                     ModelFile.CreateFile(data.Filename, mdl, mdlanis, mdlmorphs, null, item.Key, "split", null, ModelFormat.BasicDX);
                 }
                 break;
             case "chunkmodel":
                 {
                     SonicRetro.SAModel.NJS_OBJECT mdl = new SonicRetro.SAModel.NJS_OBJECT(datafile, address, imageBase, ModelFormat.Chunk);
                     string[] mdlanis = new string[0];
                     if (customProperties.ContainsKey("animations"))
                         mdlanis = customProperties["animations"].Split(',');
                     string[] mdlmorphs = new string[0];
                     if (customProperties.ContainsKey("morphs"))
                         mdlmorphs = customProperties["morphs"].Split(',');
                     ModelFile.CreateFile(data.Filename, mdl, mdlanis, mdlmorphs, null, item.Key, "split", null, ModelFormat.Chunk);
                 }
                 break;
             case "action":
                 {
                     AnimationHeader ani = new AnimationHeader(datafile, address, imageBase, modelfmt);
                     ani.Animation.Name = filedesc;
                     ani.Animation.Save(data.Filename);
                 }
                 break;
             case "animation":
                 new Animation(datafile, address, imageBase, int.Parse(customProperties["numparts"], NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo)) { Name = filedesc }
                     .Save(data.Filename);
                 break;
             case "objlist":
                 {
                     ObjectListEntry[] objs = ObjectList.Load(datafile, address, imageBase, SA2);
                     if (inifile.MasterObjectList != null)
                         foreach (ObjectListEntry obj in objs)
                         {
                             if (!masterobjlist.ContainsKey(obj.CodeString))
                                 masterobjlist.Add(obj.CodeString, new MasterObjectListEntry(obj));
                             if (!objnamecounts.ContainsKey(obj.CodeString))
                                 objnamecounts.Add(obj.CodeString, new Dictionary<string, int>() { { obj.Name, 1 } });
                             else if (!objnamecounts[obj.CodeString].ContainsKey(obj.Name))
                                 objnamecounts[obj.CodeString].Add(obj.Name, 1);
                             else
                                 objnamecounts[obj.CodeString][obj.Name]++;
                         }
                     objs.Save(data.Filename);
                 }
                 break;
             case "startpos":
                 if (SA2)
                     SA2StartPosList.Load(datafile, address).Save(data.Filename);
                 else
                     SA1StartPosList.Load(datafile, address).Save(data.Filename);
                 break;
             case "texlist":
                 TextureList.Load(datafile, address, imageBase).Save(data.Filename);
                 break;
             case "leveltexlist":
                 new LevelTextureList(datafile, address, imageBase).Save(data.Filename);
                 break;
             case "triallevellist":
                 TrialLevelList.Save(TrialLevelList.Load(datafile, address, imageBase), data.Filename);
                 break;
             case "bosslevellist":
                 BossLevelList.Save(BossLevelList.Load(datafile, address), data.Filename);
                 break;
             case "fieldstartpos":
                 FieldStartPosList.Load(datafile, address).Save(data.Filename);
                 break;
             case "soundtestlist":
                 SoundTestList.Load(datafile, address, imageBase).Save(data.Filename);
                 break;
             case "musiclist":
                 {
                     int muscnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                     MusicList.Load(datafile, address, imageBase, muscnt).Save(data.Filename);
                 }
                 break;
             case "soundlist":
                 SoundList.Load(datafile, address, imageBase).Save(data.Filename);
                 break;
             case "stringarray":
                 {
                     int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                     Languages lang = Languages.Japanese;
                     if (data.CustomProperties.ContainsKey("language"))
                         lang = (Languages)Enum.Parse(typeof(Languages), data.CustomProperties["language"], true);
                     StringArray.Load(datafile, address, imageBase, cnt, lang).Save(data.Filename);
                 }
                 break;
             case "nextlevellist":
                 NextLevelList.Load(datafile, address).Save(data.Filename);
                 break;
             case "cutscenetext":
                 {
                     int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                     string[] hashes;
                     new CutsceneText(datafile, address, imageBase, cnt).Save(data.Filename, out hashes);
                     data.MD5Hash = string.Join(",", hashes);
                     nohash = true;
                 }
                 break;
             case "recapscreen":
                 {
                     int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                     string[][] hashes;
                     RecapScreenList.Load(datafile, address, imageBase, cnt).Save(data.Filename, out hashes);
                     string[] hash2 = new string[hashes.Length];
                     for (int i = 0; i < hashes.Length; i++)
                     {
                         hash2[i] = string.Join(",", hashes[i]);
                     }
                     data.MD5Hash = string.Join(":", hash2);
                     nohash = true;
                 }
                 break;
             case "npctext":
                 {
                     int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                     string[][] hashes;
                     NPCTextList.Load(datafile, address, imageBase, cnt).Save(data.Filename, out hashes);
                     string[] hash2 = new string[hashes.Length];
                     for (int i = 0; i < hashes.Length; i++)
                         hash2[i] = string.Join(",", hashes[i]);
                     data.MD5Hash = string.Join(":", hash2);
                     nohash = true;
                 }
                 break;
             case "levelclearflags":
                 LevelClearFlagList.Save(LevelClearFlagList.Load(datafile, address), data.Filename);
                 break;
             case "deathzone":
                 {
                     List<DeathZoneFlags> flags = new List<DeathZoneFlags>();
                     string path = Path.GetDirectoryName(data.Filename);
                     List<string> hashes = new List<string>();
                     int num = 0;
                     while (SA_Tools.ByteConverter.ToUInt32(datafile, address + 4) != 0)
                     {
                         flags.Add(new DeathZoneFlags(datafile, address));
                         string file = Path.Combine(path, num++.ToString(NumberFormatInfo.InvariantInfo) + (modelfmt == ModelFormat.Chunk ? ".sa2mdl" : ".sa1mdl"));
                         ModelFile.CreateFile(file, new SonicRetro.SAModel.NJS_OBJECT(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, modelfmt), null, null, null, null, "split", null, modelfmt);
                         hashes.Add(HelperFunctions.FileHash(file));
                         address += 8;
                     }
                     flags.ToArray().Save(data.Filename);
                     hashes.Insert(0, HelperFunctions.FileHash(data.Filename));
                     data.MD5Hash = string.Join(",", hashes.ToArray());
                     nohash = true;
                 }
                 break;
             case "skyboxscale":
                 {
                     int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                     SkyboxScaleList.Load(datafile, address, imageBase, cnt).Save(data.Filename);
                 }
                 break;
             case "stageselectlist":
                 {
                     int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                     StageSelectLevelList.Load(datafile, address, cnt).Save(data.Filename);
                 }
                 break;
             case "levelrankscores":
                 LevelRankScoresList.Load(datafile, address).Save(data.Filename);
                 break;
             case "levelranktimes":
                 LevelRankTimesList.Load(datafile, address).Save(data.Filename);
                 break;
             case "endpos":
                 SA2EndPosList.Load(datafile, address).Save(data.Filename);
                 break;
             case "animationlist":
                 {
                     int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
                     SA2AnimationInfoList.Load(datafile, address, cnt).Save(data.Filename);
                 }
                 break;
             case "levelpathlist":
                 {
                     List<string> hashes = new List<string>();
                     ushort lvlnum = (ushort)SA_Tools.ByteConverter.ToUInt32(datafile, address);
                     while (lvlnum != 0xFFFF)
                     {
                         int ptr = SA_Tools.ByteConverter.ToInt32(datafile, address + 4);
                         if (ptr != 0)
                         {
                             ptr = (int)((uint)ptr - imageBase);
                             SA1LevelAct level = new SA1LevelAct(lvlnum);
                             string lvldir = Path.Combine(data.Filename, level.ToString());
                             string[] lvlhashes;
                             PathList.Load(datafile, ptr, imageBase).Save(lvldir, out lvlhashes);
                             hashes.Add(level.ToString() + ":" + string.Join(",", lvlhashes));
                         }
                         address += 8;
                         lvlnum = (ushort)SA_Tools.ByteConverter.ToUInt32(datafile, address);
                     }
                     data.MD5Hash = string.Join("|", hashes.ToArray());
                     nohash = true;
                 }
                 break;
             case "stagelightdatalist":
                 SA1StageLightDataList.Load(datafile, address).Save(data.Filename);
                 break;
             default: // raw binary
                 {
                     byte[] bin = new byte[int.Parse(customProperties["size"], NumberStyles.HexNumber)];
                     Array.Copy(datafile, address, bin, 0, bin.Length);
                     File.WriteAllBytes(data.Filename, bin);
                 }
                 break;
         }
         if (!nohash)
             data.MD5Hash = HelperFunctions.FileHash(data.Filename);
         itemcount++;
     }
     if (inifile.MasterObjectList != null)
     {
         foreach (KeyValuePair<string, MasterObjectListEntry> obj in masterobjlist)
         {
             KeyValuePair<string, int> name = new KeyValuePair<string, int>();
             foreach (KeyValuePair<string, int> it in objnamecounts[obj.Key])
                 if (it.Value > name.Value)
                     name = it;
             obj.Value.Name = name.Key;
             obj.Value.Names = objnamecounts[obj.Key].Select((it) => it.Key).ToArray();
         }
         IniSerializer.Serialize(masterobjlist, inifile.MasterObjectList);
     }
     IniSerializer.Serialize(inifile, Path.Combine(Environment.CurrentDirectory, Path.GetFileNameWithoutExtension(datafilename)) + "_data.ini");
     timer.Stop();
     Console.WriteLine("Split " + itemcount + " items in " + timer.Elapsed.TotalSeconds + " seconds.");
     Console.WriteLine();
 }