using System; using System.IO; using System.Reflection; using System.Resources; using System.Windows.Forms; using System.Linq; using DSPRE.ROMFiles; using System.Collections.Generic; using DSPRE.Resources.ROMToolboxDB; using DSPRE.Resources; using static DSPRE.RomInfo; using System.Threading.Tasks; using static DSPRE.Resources.ROMToolboxDB.ToolboxDB; using static NSMBe4.ROM; namespace DSPRE { public partial class PatchToolboxDialog : Form { public static uint expandedARMfileID = ToolboxDB.syntheticOverlayFileNumbersDB[RomInfo.gameFamily]; public static bool flag_standardizedItems { get; private set; } = false; public static bool flag_arm9Expanded { get; private set; } = false; public static bool flag_BDHCamPatchApplied { get; private set; } = false; public static bool flag_DynamicHeadersPatchApplied { get; private set; } = false; public static bool flag_MatrixExpansionApplied { get; private set; } = false; public static bool flag_MainComboTableRepointed { get; set; } = false; public static bool flag_TrainerClassBattleTableRepointed { get; set; } = false; public static bool flag_PokemonBattleTableRepointed { get; set; } = false; public static bool flag_TrainerNamesExpanded { get; set; } = false; public static bool overlay1MustBeRestoredFromBackup { get; private set; } = true; public static readonly int expandedTrainerNameLength = 12; #region Constructor public PatchToolboxDialog() { InitializeComponent(); CheckStandardizedItems(); if (RomInfo.gameLanguage == GameLanguages.English || RomInfo.gameLanguage == GameLanguages.Spanish) { CheckARM9ExpansionApplied(); } else { DisableARM9patch("Unsupported\nlanguage"); DisableBDHCamPatch("Unsupported\nlanguage"); DisableScrcmdRepointPatch("Unsupported\nlanguage"); } CheckExpandedTrainerNamesPatchApplied(); switch (RomInfo.gameFamily) { case GameFamilies.DP: DisableOverlay1patch("Unsupported"); DisableDynamicHeadersPatch("Unsupported"); DisableMatrixExpansionPatch("Unsupported"); DisableScrcmdRepointPatch("Unsupported"); DisableKillTextureAnimationsPatch("Unsupported"); break; case GameFamilies.Plat: DisableOverlay1patch("Unsupported"); DisableMatrixExpansionPatch("Unsupported"); DisableScrcmdRepointPatch("Unsupported"); DisableKillTextureAnimationsPatch("Unsupported"); if (RomInfo.gameLanguage == GameLanguages.English || RomInfo.gameLanguage == GameLanguages.Spanish) { CheckBDHCamPatchApplied(); } CheckDynamicHeadersPatchApplied(); break; case GameFamilies.HGSS: if (!OverlayUtils.OverlayTable.IsDefaultCompressed(1)) { DisableOverlay1patch("Already applied"); overlay1CB.Visible = true; } if (RomInfo.gameLanguage == GameLanguages.English || RomInfo.gameLanguage == GameLanguages.Spanish) { CheckBDHCamPatchApplied(); CheckMatrixExpansionApplied(); CheckScrcmdRepointPatchApplied(); } else { DisableMatrixExpansionPatch("Unsupported\nlanguage"); DisableScrcmdRepointPatch("Unsupported\nlanguage"); } CheckDynamicHeadersPatchApplied(); break; } } #region Patch Disable private void DisableOverlay1patch(string reason) { overlay1uncomprButton.Enabled = false; overlay1uncompressedLBL.Enabled = false; overlay1patchtextLBL.Enabled = false; overlay1uncomprButton.Text = reason; } private void DisableBDHCamPatch(string reason) { BDHCamPatchButton.Enabled = false; BDHCamPatchLBL.Enabled = false; BDHCamPatchTextLBL.Enabled = false; BDHCamARM9requiredLBL.Enabled = false; BDHCamPatchButton.Text = reason; } private void DisableARM9patch(string reason) { applyARM9ExpansionButton.Enabled = false; arm9expansionTextLBL.Enabled = false; arm9expansionLBL.Enabled = false; applyARM9ExpansionButton.Text = reason; } private void DisableDynamicHeadersPatch(string reason) { applyDynamicHeadersButton.Enabled = false; dynamicHeadersTextLBL.Enabled = false; dynamicHeadersLBL.Enabled = false; applyDynamicHeadersButton.Text = reason; } private void DisableMatrixExpansionPatch(string reason) { expandMatrixButton.Enabled = false; matrixExpansionLBL.Enabled = false; matrixExpansionTextLBL.Enabled = false; expandMatrixButton.Text = reason; } private void DisableStandardizeItemsPatch(string reason) { applyItemStandardizeButton.Enabled = false; standardizePatchLBL.Enabled = false; standardizePatchTextLBL.Enabled = false; applyItemStandardizeButton.Text = reason; } private void DisableScrcmdRepointPatch(string reason) { repointScrcmdButton.Enabled = false; repointScrcmdLBL.Enabled = false; repointScrcmdTextLBL.Enabled = false; scrcmdARM9requiredLBL.Enabled = false; repointScrcmdButton.Text = reason; } private void DisableKillTextureAnimationsPatch(string reason) { disableTextureAnimationsButton.Enabled = false; disableTextureAnimationsLBL.Enabled = false; disableTextureAnimationsTextLBL.Enabled = false; disableTextureAnimationsButton.Text = reason; } private void DisableTrainerNameExpansionPatch(string reason) { expandTrainerNamesButton.Enabled = false; expandTrainerNamesLBL.Enabled = false; expandTrainerNamesTextLBL.Enabled = false; expandTrainerNamesButton.Text = reason; } #endregion Patch Disable #endregion Constructor #region Patch private static bool CheckFilesArm9ExpansionApplied() { ARM9PatchData data = new ARM9PatchData(); byte[] branchCode = DSUtils.HexStringToByteArray(data.branchString); byte[] branchCodeRead = ARM9.ReadBytes(data.branchOffset, data.branchString.Length / 3 + 1); //Read branchCode if (branchCodeRead.Length != branchCode.Length || !branchCodeRead.SequenceEqual(branchCode)) return false; byte[] initCode = DSUtils.HexStringToByteArray(data.initString); byte[] initCodeRead = ARM9.ReadBytes(data.initOffset, data.initString.Length / 3 + 1); //Read initCode if (initCodeRead.Length != initCode.Length || !initCodeRead.SequenceEqual(initCode)) return false; return true; } public static bool CheckFilesBDHCamPatchApplied() { BDHCAMPatchData data = new BDHCAMPatchData(); byte[] branchCode = DSUtils.HexStringToByteArray(data.branchString); byte[] branchCodeRead = ARM9.ReadBytes(data.branchOffset, branchCode.Length); if (branchCode.Length != branchCodeRead.Length || !branchCode.SequenceEqual(branchCodeRead)) { return false; } string overlayFilePath = OverlayUtils.GetPath(data.overlayNumber); OverlayUtils.Decompress(data.overlayNumber); byte[] overlayCode1 = DSUtils.HexStringToByteArray(data.overlayString1); byte[] overlayCode1Read = DSUtils.ReadFromFile(overlayFilePath, data.overlayOffset1, overlayCode1.Length); if (overlayCode1.Length != overlayCode1Read.Length || !overlayCode1.SequenceEqual(overlayCode1Read)) return false; byte[] overlayCode2 = DSUtils.HexStringToByteArray(data.overlayString2); byte[] overlayCode2Read = DSUtils.ReadFromFile(overlayFilePath, data.overlayOffset2, overlayCode2.Length); //Write new overlayCode1 if (overlayCode2.Length != overlayCode2Read.Length || !overlayCode2.SequenceEqual(overlayCode2Read)) return false; //0 means BDHCAM patch has not been applied String fullFilePath = RomInfo.gameDirs[DirNames.synthOverlay].unpackedDir + '\\' + PatchToolboxDialog.expandedARMfileID.ToString("D4"); byte[] subroutineRead = DSUtils.ReadFromFile(fullFilePath, BDHCAMPatchData.BDHCamSubroutineOffset, data.subroutine.Length); //Write new overlayCode1 if (data.subroutine.Length != subroutineRead.Length || !data.subroutine.SequenceEqual(subroutineRead)) return false; //0 means BDHCAM patch has not been applied return true; } public static bool CheckFilesMatrixExpansionApplied() { foreach (KeyValuePair kv in ToolboxDB.matrixExpansionDB) { foreach (uint offset in kv.Key) { int languageOffset = 0; if (RomInfo.romID == "IPKE" || RomInfo.romID == "IPGE" || RomInfo.romID == "IPGS") { languageOffset = +8; } byte[] read = ARM9.ReadBytes((uint)(offset - ARM9.address + languageOffset), kv.Value.Length / 3 + 1); byte[] code = DSUtils.HexStringToByteArray(kv.Value); if (read.Length != code.Length || !read.SequenceEqual(code)) return false; } } return true; } public static bool CheckScriptsStandardizedItemNumbers() { ScriptFile itemScript = new ScriptFile(RomInfo.itemScriptFileNumber); //if (itemScript.allScripts.Count - 1 < new TextArchive(RomInfo.itemNamesTextNumber).messages.Count) //{ // return false; //} //for (ushort i = 0; i < itemScript.allScripts.Count - 1; i++) //{ // if (BitConverter.ToUInt16(itemScript.allScripts[i].commands[0].Parameters[1].RawData, 0) != i || BitConverter.ToUInt16(itemScript.allScripts[i].commands[1].Parameters[1].RawData, 0) != 1) // { // return false; // } //} return true; } public bool CheckStandardizedItems() { DSUtils.TryUnpackNarcs(new List { RomInfo.DirNames.scripts }); if (!PatchToolboxDialog.flag_standardizedItems) { if (!PatchToolboxDialog.CheckScriptsStandardizedItemNumbers()) { return false; } } itemNumbersCB.Visible = true; PatchToolboxDialog.flag_standardizedItems = true; DisableStandardizeItemsPatch("Already applied"); return true; } public bool CheckMatrixExpansionApplied() { if (!PatchToolboxDialog.flag_MatrixExpansionApplied) { if (!PatchToolboxDialog.CheckFilesMatrixExpansionApplied()) { return false; } } DisableMatrixExpansionPatch("Already applied"); PatchToolboxDialog.flag_MatrixExpansionApplied = true; expandedMatrixCB.Visible = true; return true; } public string backupSuffix = ".backup"; private bool CheckARM9ExpansionApplied() { if (!PatchToolboxDialog.flag_arm9Expanded) { if (!PatchToolboxDialog.CheckFilesArm9ExpansionApplied()) { return false; } } PatchToolboxDialog.flag_arm9Expanded = true; arm9patchCB.Visible = true; DisableARM9patch("Already applied"); switch (RomInfo.gameFamily) { case GameFamilies.Plat: case GameFamilies.HGSS: BDHCamARM9requiredLBL.Visible = false; BDHCamPatchButton.Enabled = true; BDHCamPatchLBL.Enabled = true; BDHCamPatchTextLBL.Enabled = true; break; } return true; } public bool CheckDynamicHeadersPatchApplied() { if (!flag_DynamicHeadersPatchApplied) { if (!PatchToolboxDialog.CheckFilesDynamicHeadersPatchApplied()) { return false; } } PatchToolboxDialog.flag_DynamicHeadersPatchApplied = true; dynamicHeadersPatchCB.Visible = true; DisableDynamicHeadersPatch("Already applied"); return true; } public static bool CheckFilesDynamicHeadersPatchApplied() { DynamicHeadersPatchData data = new DynamicHeadersPatchData(); ushort initValue = BitConverter.ToUInt16(ARM9.ReadBytes(data.initOffset, 0x2), 0); return initValue == 0xB500; } public bool CheckBDHCamPatchApplied() { if (!CheckARM9ExpansionApplied()) { BDHCamARM9requiredLBL.Visible = true; DisableBDHCamPatch("ARM9 not expanded!"); return false; } if (!PatchToolboxDialog.flag_BDHCamPatchApplied) { if (!PatchToolboxDialog.CheckFilesBDHCamPatchApplied()) { return false; } } PatchToolboxDialog.flag_BDHCamPatchApplied = true; BDHCamCB.Visible = true; DisableBDHCamPatch("Already applied"); return true; } public void CheckScrcmdRepointPatchApplied() { //throw new NotImplementedException(); } public void CheckExpandedTrainerNamesPatchApplied() { if (flag_TrainerNamesExpanded) { DisableTrainerNameExpansionPatch("Already\nApplied"); } else { if (RomInfo.trainerNameLenOffset < 0) { DisableTrainerNameExpansionPatch("Unsupported"); } else { if (RomInfo.trainerNameMaxLen > TrainerFile.defaultNameLen) { DisableTrainerNameExpansionPatch("Already\nApplied"); PatchToolboxDialog.flag_TrainerNamesExpanded = true; } } } } #endregion Patch #region Button Actions private void SentenceCasePatchButton_Click(object sender, EventArgs e) { DialogResult d; d = MessageBox.Show("Confirming this process will apply the following changes:\n\n" + "- Every Pokémon name will be converted to Sentence Case." + "\n\n" + "Do you wish to continue?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.Yes) { Parallel.ForEach(RomInfo.pokemonNamesTextNumbers, ID => { TextArchive pokeName = new TextArchive(ID); Parallel.For(1, pokeName.messages.Count, i => { if (pokeName.messages[i].Length <= 1) { i++; } pokeName.messages[i] = pokeName.messages[i].Replace(PokeDatabase.System.pokeNames[(ushort)i].ToUpper(), PokeDatabase.System.pokeNames[(ushort)i]); }); pokeName.SaveToFileDefaultDir(ID, showSuccessMessage: false); }); //sentenceCaseCB.Visible = true; MessageBox.Show("Pokémon names have been converted to Sentence Case.", "Operation successful", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show("No changes have been made.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void BDHCAMPatchButton_Click(object sender, EventArgs e) { BDHCAMPatchData data = new BDHCAMPatchData(); if (RomInfo.gameFamily == GameFamilies.HGSS) { if (OverlayUtils.OverlayTable.IsDefaultCompressed(data.overlayNumber)) { DialogResult d1 = MessageBox.Show("It is STRONGLY recommended to configure Overlay1 as uncompressed before proceeding.\n\n" + "More details in the following dialog.\n\n" + "Do you want to know more?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d1 == DialogResult.Yes) { overlay1uncomprButton_Click(null, null); } } } DialogResult d2; d2 = MessageBox.Show("This process will apply the following changes:\n\n" + "- Backup ARM9 file (arm9.bin" + backupSuffix + " will be created)." + "\n\n" + "- Backup Overlay" + data.overlayNumber + " file (overlay" + data.overlayNumber + ".bin" + backupSuffix + " will be created)." + "\n\n" + "- Replace " + (data.branchString.Length / 3 + 1) + " bytes of data at arm9 offset 0x" + data.branchOffset.ToString("X") + " with " + '\n' + data.branchString + "\n\n" + "- Replace " + (data.overlayString1.Length / 3 + 1) + " bytes of data at overlay" + data.overlayNumber + " offset 0x" + data.overlayOffset1.ToString("X") + " with " + '\n' + data.overlayString1 + "\n\n" + "- Replace " + (data.overlayString2.Length / 3 + 1) + " bytes of data at overlay" + data.overlayNumber + " offset 0x" + data.overlayOffset2.ToString("X") + " with " + '\n' + data.overlayString2 + "\n\n" + "- Modify file #" + expandedARMfileID + " inside " + '\n' + RomInfo.gameDirs[DirNames.synthOverlay].unpackedDir + '\n' + "to insert the BDHCAM routine (any data between 0x" + BDHCAMPatchData.BDHCamSubroutineOffset.ToString("X") + " and 0x" + (BDHCAMPatchData.BDHCamSubroutineOffset + data.subroutine.Length).ToString("X") + " will be overwritten)." + "\n\n" + "Do you wish to continue?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d2 == DialogResult.Yes) { File.Copy(RomInfo.arm9Path, RomInfo.arm9Path + backupSuffix, overwrite: true); try { ARM9.WriteBytes(DSUtils.HexStringToByteArray(data.branchString), data.branchOffset); //Write new branchOffset /* Write to overlayfile */ string overlayFilePath = OverlayUtils.GetPath(data.overlayNumber); if (OverlayUtils.IsCompressed(data.overlayNumber)) { OverlayUtils.Decompress(data.overlayNumber); } DSUtils.WriteToFile(overlayFilePath, DSUtils.HexStringToByteArray(data.overlayString1), data.overlayOffset1); //Write new overlayCode1 DSUtils.WriteToFile(overlayFilePath, DSUtils.HexStringToByteArray(data.overlayString2), data.overlayOffset2); //Write new overlayCode2 overlay1MustBeRestoredFromBackup = false; String fullFilePath = RomInfo.gameDirs[DirNames.synthOverlay].unpackedDir + '\\' + expandedARMfileID.ToString("D4"); /*Write Expanded ARM9 File*/ DSUtils.WriteToFile(fullFilePath, data.subroutine, BDHCAMPatchData.BDHCamSubroutineOffset); } catch { MessageBox.Show("Operation failed. It is strongly advised that you restore the arm9 and overlay from their respective backups.", "Something went wrong", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } overlay1MustBeRestoredFromBackup = false; DisableBDHCamPatch("Already applied"); PatchToolboxDialog.flag_BDHCamPatchApplied = true; BDHCamCB.Visible = true; MessageBox.Show("The BDHCAM patch has been applied.", "Operation successful.", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show("No changes have been made.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void overlay1uncomprButton_Click(object sender, EventArgs e) { if (ConfigureOverlay1Uncompressed()) { DisableOverlay1patch("Already applied"); overlay1CB.Visible = true; } } public static bool ConfigureOverlay1Uncompressed() { bool isCompressed = false; string stringDecompressOverlay = ""; if (OverlayUtils.IsCompressed(1)) { isCompressed = true; stringDecompressOverlay = "- Overlay 1 will be decompressed.\n\n"; } DialogResult d = MessageBox.Show("This process will apply the following changes:\n\n" + stringDecompressOverlay + "- Overlay 1 will be configured as \"uncompressed\" in the overlay table.\n\n" + "Do you wish to continue?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.Yes) { OverlayUtils.OverlayTable.SetDefaultCompressed(1, false); if (isCompressed) { OverlayUtils.Decompress(1); } MessageBox.Show("Overlay1 is now configured as uncompressed.", "Operation successful", MessageBoxButtons.OK, MessageBoxIcon.Information); return true; } else { MessageBox.Show("No changes have been made.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); return false; } } private void ApplyItemStandardizeButton_Click(object sender, EventArgs e) { DialogResult d = MessageBox.Show("This process will apply the following changes:\n\n" + "- Item scripts will be rearranged to follow the natural, ascending index order.\n\n" + "- Any unsaved change to the current Event File will be discarded.\n\n", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.Yes) { DSUtils.TryUnpackNarcs(new List { RomInfo.DirNames.scripts }); DSUtils.TryUnpackNarcs(new List { RomInfo.DirNames.eventFiles }); if (PatchToolboxDialog.flag_standardizedItems) { AlreadyApplied(); } else { // Load item script file data ScriptFile itemScriptFile = new ScriptFile(RomInfo.itemScriptFileNumber); // Create map for: script no. -> vanilla item //int[] vanillaItemsArray = new int[itemScriptFile.allScripts.Count - 1]; // //for (int i = 0; i < itemScriptFile.allScripts.Count - 1; i++) //{ // vanillaItemsArray[i] = BitConverter.ToInt16(itemScriptFile.allScripts[i].commands[0].Parameters[1].RawData, 0); //}; // Parse all event files and fix instances of ground items according to the new order int cnt = Filesystem.GetEventFileCount(); (int itemScrMin, int itemScrMax) = (7000, 8000); for (int i = 0; i < cnt; i++) { bool dirty = false; EventFile eventFile = new EventFile(i); for (int j = 0; j < eventFile.overworlds.Count; j++) { // If ow is marked as an item, or in the rare case it is not but script no. falls within item script range: bool isItem = eventFile.overworlds[j].type == (ushort)Overworld.OwType.ITEM || (eventFile.overworlds[j].scriptNumber >= itemScrMin && eventFile.overworlds[j].scriptNumber <= itemScrMax); if (isItem) { int itemScriptID = eventFile.overworlds[j].scriptNumber - (itemScrMin - 1); //eventFile.overworlds[j].scriptNumber = (ushort)(itemScrMin + vanillaItemsArray[itemScriptID - 1]); dirty = true; } } // Save event file if (dirty) { eventFile.SaveToFileDefaultDir(i, showSuccessMessage: false); } }; //Distortion world - turnback cave Griseous Orb fix if (gameFamily.Equals(GameFamilies.Plat)) { string ow9path = OverlayUtils.GetPath(9); int ow9offs = 0x8E20 + 10; int itemScriptID; using (DSUtils.EasyReader ewr = new DSUtils.EasyReader(ow9path, ow9offs)) { itemScriptID = ewr.ReadUInt16() - (itemScrMin - 1); } using (DSUtils.EasyWriter ewr = new DSUtils.EasyWriter(ow9path, ow9offs)) { //ewr.Write((ushort)(itemScrMin + vanillaItemsArray[itemScriptID - 1])); } } // Sort scripts in the Script File according to item indices int itemCount = new TextArchive(RomInfo.itemNamesTextNumber).messages.Count; //ScriptCommandContainer executeGive = new ScriptCommandContainer((uint)itemCount + 1, itemScriptFile.allScripts[itemScriptFile.allScripts.Count - 1]); //itemScriptFile.allScripts.Clear(); //for (ushort i = 0; i < itemCount; i++) //{ // List cmdList = new List { // new ScriptCommand("SetVar 0x8008 " + i), // new ScriptCommand("SetVar 0x8009 0x1"), // new ScriptCommand("Jump Function_#1") // }; // // itemScriptFile.allScripts.Add(new ScriptCommandContainer((ushort)(i + 1), ScriptFile.ContainerTypes.Script, commandList: cmdList)); //} // //itemScriptFile.allScripts.Add(executeGive); //itemScriptFile.allFunctions[0].usedScriptID = itemCount + 1; itemScriptFile.SaveToFileDefaultDir(RomInfo.itemScriptFileNumber, showSuccessMessage: false); MessageBox.Show("Operation successful.", "Process completed.", MessageBoxButtons.OK, MessageBoxIcon.Information); DisableStandardizeItemsPatch("Already applied"); itemNumbersCB.Visible = true; PatchToolboxDialog.flag_standardizedItems = true; } } else { MessageBox.Show("No changes have been made.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void ApplyARM9ExpansionButton_Click(object sender, EventArgs e) { ARM9PatchData data = new ARM9PatchData(); DialogResult d = MessageBox.Show("Confirming this process will apply the following changes:\n\n" + "- Backup ARM9 file (arm9.bin" + backupSuffix + " will be created)." + "\n\n" + "- Replace " + (data.branchString.Length / 3 + 1) + " bytes of data at arm9 offset 0x" + data.branchOffset.ToString("X") + " with " + '\n' + data.branchString + "\n\n" + "- Replace " + (data.initString.Length / 3 + 1) + " bytes of data at arm9 offset 0x" + data.initOffset.ToString("X") + " with " + '\n' + data.initString + "\n\n" + "- Modify file #" + expandedARMfileID + " inside " + '\n' + RomInfo.gameDirs[DirNames.synthOverlay].unpackedDir + '\n' + " to accommodate for 88KB of data (no backup)." + "\n\n" + "Do you wish to continue?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.Yes) { File.Copy(RomInfo.arm9Path, RomInfo.arm9Path + backupSuffix, overwrite: true); try { ARM9.WriteBytes(DSUtils.HexStringToByteArray(data.branchString), data.branchOffset); //Write new branchOffset ARM9.WriteBytes(DSUtils.HexStringToByteArray(data.initString), data.initOffset); //Write new initOffset string fullFilePath = RomInfo.gameDirs[DirNames.synthOverlay].unpackedDir + '\\' + expandedARMfileID.ToString("D4"); File.Delete(fullFilePath); using (BinaryWriter f = new BinaryWriter(File.Create(fullFilePath))) { for (int i = 0; i < 0x16000; i++) f.Write((byte)0x00); // Write Expanded ARM9 File } DisableARM9patch("Already applied"); arm9patchCB.Visible = true; PatchToolboxDialog.flag_arm9Expanded = true; switch (RomInfo.gameFamily) { case GameFamilies.Plat: case GameFamilies.HGSS: BDHCamPatchButton.Text = "Apply Patch"; BDHCamPatchButton.Enabled = true; BDHCamPatchLBL.Enabled = true; BDHCamPatchTextLBL.Enabled = true; BDHCamARM9requiredLBL.Visible = false; break; } MessageBox.Show("The ARM9's usable memory has been expanded.", "Operation successful.", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch { MessageBox.Show("Operation failed. It is strongly advised that you restore the arm9 backup (arm9.bin" + backupSuffix + ").", "Something went wrong", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else { MessageBox.Show("No changes have been made.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void expandMatrixButton_Click(object sender, EventArgs e) { string listOfChanges = ""; int languageOffset = 0; if (RomInfo.romID == "IPKE" || RomInfo.romID == "IPGE" || RomInfo.romID == "IPGS") { languageOffset = +8; } foreach (KeyValuePair kv in ToolboxDB.matrixExpansionDB) { listOfChanges += " - Replace " + (kv.Value.Length / 3 + 1) + " bytes of data at arm9 offset"; if (kv.Key.Length > 1) listOfChanges += "s"; for (int i = 0; i < kv.Key.Length; i++) { listOfChanges += " 0x" + (kv.Key[i] - ARM9.address + languageOffset).ToString("X"); if (i < kv.Key.Length - 1) listOfChanges += ","; } listOfChanges += " with " + '\n' + kv.Value + "\n\n"; } DialogResult d; d = MessageBox.Show("Confirming this process will apply the following changes:\n\n" + listOfChanges + "Do you wish to continue?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.Yes) { try { foreach (KeyValuePair kv in ToolboxDB.matrixExpansionDB) { foreach (uint offset in kv.Key) { ARM9.WriteBytes(DSUtils.HexStringToByteArray(kv.Value), (uint)(offset - ARM9.address + languageOffset)); } } } catch { MessageBox.Show("Operation failed. It is strongly advised that you restore the arm9 backup (arm9.bin" + backupSuffix + ").", "Something went wrong", MessageBoxButtons.OK, MessageBoxIcon.Error); } DisableMatrixExpansionPatch("Already applied"); expandedMatrixCB.Visible = true; PatchToolboxDialog.flag_MatrixExpansionApplied = true; MessageBox.Show("Matrix 0 can now be freely expanded up to twice its size.", "Operation successful.", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show("No changes have been made.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void dynamicHeadersButton_Click(object sender, EventArgs e) { DynamicHeadersPatchData data = new DynamicHeadersPatchData(); var headersDir = RomInfo.gameDirs[DirNames.dynamicHeaders]; bool specialCase = RomInfo.gameFamily == GameFamilies.HGSS && RomInfo.gameLanguage != GameLanguages.Japanese && RomInfo.gameLanguage != GameLanguages.Spanish; string specialCaseChanges = ""; if (specialCase) { specialCaseChanges = "- Replace " + (data.specialCaseData1.Length / 3 + 1) + " bytes of data at arm9 offset 0x" + (data.specialCaseOffset1 + data.pointerDiff).ToString("X") + " with " + '\n' + data.specialCaseData1 + "\n\n" + "- Replace " + (data.specialCaseData2.Length / 3 + 1) + " bytes of data at arm9 offset 0x" + (data.specialCaseOffset2 + data.pointerDiff).ToString("X") + " with " + '\n' + data.specialCaseData2 + "\n\n" + "- Replace " + (data.specialCaseData3.Length / 3 + 1) + " bytes of data at arm9 offset 0x" + (data.specialCaseOffset3 + data.pointerDiff).ToString("X") + " with " + '\n' + data.specialCaseData3 + "\n\n"; } DialogResult d; d = MessageBox.Show("Confirming this process will apply the following changes:\n\n" + "- Backup ARM9 file (arm9.bin" + backupSuffix + " will be created)." + "\n\n" + "- NARC file at " + headersDir.packedDir + " will become the new header container." + "\n\n" + "- The default ARM9 header table will be split into multiple files (one per header), each one saved into NARC" + headersDir.packedDir + " upon saving the ROM." + "\n\n" + "- Replace " + (data.initString.Length / 3 + 1) + " bytes of data at arm9 offset 0x" + data.initOffset.ToString("X") + " with " + '\n' + data.initString + "\n\n" + "- Neutralize instances of (HeaderID * 0x18) so the base offset which the data is read from is always 0x0." + "\n\n" + "- Change pointers to header fields, from(ARM9_HEADER_TABLE_OFFSET + n) to simply(0 + n)" + "\n\n" + specialCaseChanges + "Do you wish to continue?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.Yes) { File.Copy(RomInfo.arm9Path, RomInfo.arm9Path + backupSuffix, overwrite: true); try { /* Write main routine (HG USA): 00 B5 push (lr) 01 1C mov r1, r0 32 20* mov r0, #0x32 00 22 mov r2, #0x0 CC F7 58 F9** bl 0x02007524 @Load_Memory 03 1C mov r3, r0 DF F7 49 FC** bl 0x0201AB0C @Free_Memory 00 BD pop, pc *FOR PLATINUM (all languages): 94 20 mov r0, #0x94 **BRANCHES FOR OTHER VERSIONS/LANGUAGES: HG ESP (IPKS): CC F7 5C F9 bl 0x02007524 @Load_Memory DF F7 4D FC bl 0x0201AB0C @Free_Memory HG JAP (IPKJ) and SS JAP (IPGJ): CC F7 08 FB bl 0x0200743C @Load_Memory DF F7 C7 FC bl 0x0201A7C0 @Free_Memory Plat USA (CPUE): CC F7 48 FD bl 0x02006AC0 @Load_Memory DE F7 C7 F8 bl 0x020181C4 @Free_Memory Plat ESP (CPUS), ITA (CPUI), FRA (CPUF), GER (CPUD): CC F7 00 FD bl 0x02006AD4 @Load_Memory CC F7 74 FC bl 0x02018234 @Free_Memory Plat JAP (CPUJ): CC F7 0A FF bl 0x02006A00 @Load_Memory DE F7 3D F9 bl 0x02017E6C @Free_Memory */ ARM9.WriteBytes(DSUtils.HexStringToByteArray(data.initString), data.initOffset); /* - Neutralize instances of (HeaderID * 0x18) so the base offset which the data is read from is always 0x0: Replace this: 18 21 mov r1, #0x18 41 43 mul r1, r0 with this: 19 00 lsl r1, r3, 0 C0 46 nop - Change pointers to header fields, from (ARM9_HEADER_TABLE_OFFSET + n) to simply (0 + n) * for ESP HG (IPKS): subtract 0x8 from every reference offset * for JAP HG (IPKJ) and SS (IPGJ): subtract 0x448 from every reference offset * for Plat ESP, ITA, FRA, GER, JAP: add 0xA4 to every reference offset * for Plat JAP: subtract 0x444 from every reference offset */ foreach (Tuple reference in DynamicHeadersPatchData.dynamicHeadersPointersDB[RomInfo.gameFamily]) { ARM9.WriteBytes(DSUtils.HexStringToByteArray(data.REFERENCE_STRING), (uint)(reference.Item1 + data.pointerDiff)); uint pointerValue = BitConverter.ToUInt32(ARM9.ReadBytes((uint)(reference.Item2 + data.pointerDiff), 4), 0) - RomInfo.headerTableOffset - ARM9.address; ARM9.WriteBytes(BitConverter.GetBytes(pointerValue), (uint)(reference.Item2 + data.pointerDiff)); } if (specialCase) { /* Special case: at 0x3B522 (non-JAP and non-Spanish HG offset) there is an instruction between the (mov r1, #0x18) and (mul r1, r0) commands, so we must handle this separately */ ARM9.WriteBytes(DSUtils.HexStringToByteArray(data.specialCaseData1), (uint)(data.specialCaseOffset1 + data.pointerDiff)); ARM9.WriteBytes(DSUtils.HexStringToByteArray(data.specialCaseData2), (uint)(data.specialCaseOffset2 + data.pointerDiff)); ARM9.WriteBytes(DSUtils.HexStringToByteArray(data.specialCaseData3), (uint)(data.specialCaseOffset3 + data.pointerDiff)); } // Clear the dynamic headers directory in 'unpacked' Directory.Delete(headersDir.unpackedDir, true); Directory.CreateDirectory(headersDir.unpackedDir); /* Now move the headers data from arm9 to the new directory. Upon saving the ROM, the data will be packed into a NARC and replace a/0/5/0 in HGSS or debug/cb_edit/d_test.narc in Platinum */ int headerCount = RomInfo.GetHeaderCount(); for (int i = 0; i < headerCount; i++) { byte[] headerData = MapHeader.LoadFromARM9((ushort)i).ToByteArray(); DSUtils.WriteToFile(headersDir.unpackedDir + "\\" + i.ToString("D4"), headerData); } DisableDynamicHeadersPatch("Already applied"); dynamicHeadersPatchCB.Visible = true; PatchToolboxDialog.flag_DynamicHeadersPatchApplied = true; MessageBox.Show("The headers are now dynamically allocated in memory.", "Operation successful.", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch { MessageBox.Show("Operation failed. It is strongly advised that you restore the arm9 backup (arm9.bin" + backupSuffix + ").", "Something went wrong", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else { MessageBox.Show("No changes have been made.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void disableDynamicTexturesButton_Click(object sender, EventArgs e) { DialogResult d; d = MessageBox.Show("Applying this patch will set the Dynamic Textures field of all AreaData files to 0xFFFF.\n\n" + "Are you sure you want to proceed?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.Yes) { DSUtils.TryUnpackNarcs(new List { DirNames.areaData }); string[] adFiles = Directory.GetFiles(gameDirs[DirNames.areaData].unpackedDir); foreach (string s in adFiles) { AreaData a = new AreaData(new FileStream(s, FileMode.Open)) { dynamicTextureType = 0xFFFF }; a.SaveToFile(s, showSuccessMessage: false); } DisableKillTextureAnimationsPatch("Already applied"); disableTextureAnimationsCB.Visible = true; MessageBox.Show("Texture Animations have been disabled in every AreaData.", "Operation successful.", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show("No changes have been made.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void expandTrainerNamesButton_Click(object sender, EventArgs e) { // Pearl USA ARM9 at 0x6AC32 // TODO: Verify // Pearl Spain ARM9 at 0x6AC8E // TODO: Verify // Diamond USA ARM9 at 0x6AC32 // Diamond Spain ARM9 at 0x6AC8E // Platinum USA ARM9 at 0x791DE // Platinum Spain ARM9 at 0x7927E // HeartGold USA ARM9 at 0x7342E // HeartGold Spain ARM9 at 0x73426 // SoulSilver USA ARM9 at 0x7342E // SoulSilver Spain ARM9 at 0x7342E // TODO: Verify DialogResult d = MessageBox.Show($"Applying this patch will set the Trainer Name max length to {PatchToolboxDialog.expandedTrainerNameLength - 1} usable characters.\n" + "Are you sure you want to proceed?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.Yes) { try { using (ARM9.Writer wr = new ARM9.Writer(RomInfo.trainerNameLenOffset)) { wr.Write((byte)PatchToolboxDialog.expandedTrainerNameLength); } PatchToolboxDialog.flag_TrainerNamesExpanded = true; DisableTrainerNameExpansionPatch("Already applied"); expandTrainerNamesCB.Visible = true; MessageBox.Show("Trainer Names have been extended.", "Operation successful.", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (IOException) { MessageBox.Show("ARM9 could not be written.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else { MessageBox.Show("No changes have been made.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); } } #region Mikelan's custom commands private void applyCustomCommands(object sender, EventArgs e) { int expTableOffset = GetCommandTableOffset(); if (expTableOffset < 0) { DialogResult d; d = MessageBox.Show("Script command table has not been repointed.\n\n" + "Do you wish to repoint it to the expanded ARM9 file?\n\n" + "By default it will be written from 0x200 to 0x1700.\n" + "If you already have something there, you must cancel this window and move these things to a new location, or you can manually repoint the script command table to a different free location in the expanded ARM9 file", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.Yes) { RepointCommandTable(); } else { return; } } if (ImportCustomCommand()) { MessageBox.Show("Script commands succesfully installed in the ROM", "Done", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private int GetCommandTableOffset() { // Checks if command table is repointed IN THE EXPANDED ARM9 FILE, returns pointer inside this file ResourceManager customcmdDB = new ResourceManager("DSPRE.Resources.ROMToolboxDB.CustomScrCmdDB", Assembly.GetExecutingAssembly()); int pointerOffset = int.Parse(customcmdDB.GetString("pointerOffset" + "_" + RomInfo.gameVersion + "_" + RomInfo.gameLanguage)); using (ARM9.Reader r = new ARM9.Reader(pointerOffset)) { uint cmdTable = r.ReadUInt32(); uint offset = cmdTable - synthOverlayLoadAddress; if ((offset >= 0) && (offset <= 0x12B00)) { return (int)offset; // Table position inside the expanded arm9 file } } return -1; // No table in expanded arm9 file } private void RepointCommandTable() { string expandedPath = RomInfo.gameDirs[DirNames.synthOverlay].unpackedDir + "\\0000"; ResourceManager customcmdDB = new ResourceManager("DSPRE.Resources.ROMToolboxDB.CustomScrCmdDB", Assembly.GetExecutingAssembly()); FileStream arm9FileStream = new FileStream(RomInfo.arm9Path, FileMode.Open); // I make a copy of the stream so the file is free for writing MemoryStream arm9Stream = new MemoryStream(); arm9FileStream.CopyTo(arm9Stream); byte[] cmdTbl = arm9Stream.ToArray(); using (BinaryWriter expArmWriter = new BinaryWriter(new FileStream(expandedPath, FileMode.Open))) { expArmWriter.BaseStream.Position = 0x200; // Command table default offset expArmWriter.Write(cmdTbl, int.Parse(customcmdDB.GetString("originalTableOffset" + "_" + RomInfo.gameVersion + "_" + RomInfo.gameLanguage)), 4 * 0x355); } arm9FileStream.Close(); using (ARM9.Writer wr = new ARM9.Writer()) { // Change both the pointer and the limit wr.BaseStream.Position = int.Parse(customcmdDB.GetString("pointerOffset" + "_" + RomInfo.gameVersion + "_" + RomInfo.gameLanguage)); wr.Write((uint)0x023C8200); wr.BaseStream.Position = int.Parse(customcmdDB.GetString("limitOffset" + "_" + RomInfo.gameVersion + "_" + RomInfo.gameLanguage)); wr.Write((uint)0x053C); } } private bool ImportCustomCommand() { string expandedPath = RomInfo.gameDirs[DirNames.synthOverlay].unpackedDir + "\\0000"; int appliedPatches = 0; OpenFileDialog of = new OpenFileDialog(); of.Filter = "Custom Script Command File (*.scrcmd)|*.scrcmd"; if (of.ShowDialog(this) != DialogResult.OK) { return false; } FileStream expandedFileStream = new FileStream(expandedPath, FileMode.Open); MemoryStream expandedStream = new MemoryStream(); expandedFileStream.CopyTo(expandedStream); expandedFileStream.Close(); using (DSUtils.EasyWriter expandedWriter = new DSUtils.EasyWriter(expandedPath, fmode: FileMode.Open)) { using (BinaryReader expandedReader = new BinaryReader(expandedStream)) { try { System.Xml.Linq.XDocument xmldoc = System.Xml.Linq.XDocument.Load(new FileStream(of.FileName, FileMode.Open)); foreach (var node in xmldoc.Root.Elements("scriptcommand")) { ushort commandID = ushort.Parse(node.Attribute("ID").Value, System.Globalization.NumberStyles.HexNumber); string targetROM = node.Element("ROM").Value; string targetLang = node.Element("lang").Value; string commandName = node.Element("name").Value; string paramCount = node.Element("paramcount").Value; string paramCode = node.Element("paramcode").Value; int asmOffset = Int32.Parse(node.Element("asmoffset").Value, System.Globalization.NumberStyles.HexNumber); string asmCode = node.Element("asmcode").Value.Replace("\n", "").Replace("\t", "").Replace(" ", ""); if (RomInfo.gameVersion.ToString().Equals(targetROM) && RomInfo.gameLanguage.Equals(targetLang)) { expandedReader.BaseStream.Position = 0x200 + commandID * 4; if (expandedReader.ReadUInt32() != 0) { DialogResult d; d = MessageBox.Show("Script command " + commandID.ToString("X4") + " is already used.\n\n" + "Do you really want to overwrite it?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.No) { continue; } } expandedWriter.BaseStream.Position = 0x200 + commandID * 4; expandedWriter.Write((int)(synthOverlayLoadAddress + asmOffset + 1)); byte[] asmCodeBytes = DSUtils.StringToByteArray(asmCode); expandedWriter.BaseStream.Position = asmOffset; expandedWriter.Write(asmCodeBytes); appliedPatches++; } } } catch { MessageBox.Show("Selected command installation file is corrupted.\n\n" + "Please, download it again or contact its creator.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } } } if (appliedPatches == 0) { MessageBox.Show("No command could be installed from this file.\n\n" + "Make sure the command installation file supports your current ROM.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); return false; } return true; } #endregion Mikelan's custom commands #endregion Button Actions #region Error Messsages private void AlreadyApplied() { MessageBox.Show("This patch has already been applied.", "Can't reapply patch", MessageBoxButtons.OK, MessageBoxIcon.Information); } #endregion Error Messsages } }