// A segmented address is 4 bytes. The first byte contains the index of the segment in the segment table, the // other 3 bytes are the offset from the segment. Segmented addresses are used for locating object behavior scripts, // display lists, textures and other resources. // todo: not limited to Fast3D display lists, can be put in a more general utilitiies class public static uint DecodeSegmentedAddress(uint segmentedAddress) { var offset = segmentedAddress & 0xFFFFFF; var segment = (segmentedAddress >> 24); return(offset + Config.Stream.GetUInt32(4 * segment + RomVersionConfig.Switch(0x33B400, 0x33A090))); }
private void UpdateComboBoxes() { // Rom Version RomVersionConfig.UpdateRomVersion(comboBoxRomVersion); // Readonly / Read+Write Config.Stream.Readonly = (ReadWriteMode)comboBoxReadWriteMode.SelectedItem == ReadWriteMode.ReadOnly; }
public SoundManager(TabPage tabPage) { SplitContainer splitContainerSound = tabPage.Controls["splitContainerSound"] as SplitContainer; SplitContainer splitContainerSoundMusic = splitContainerSound.Panel1.Controls["splitContainerSoundMusic"] as SplitContainer; ListBox listBoxSoundMusic = splitContainerSoundMusic.Panel1.Controls["listBoxSoundMusic"] as ListBox; TextBox textBoxSoundMusic = splitContainerSoundMusic.Panel2.Controls["textBoxSoundMusic"] as TextBox; Button buttonSoundPlayMusic = splitContainerSoundMusic.Panel2.Controls["buttonSoundPlayMusic"] as Button; SplitContainer splitContainerSoundSoundEffect = splitContainerSound.Panel2.Controls["splitContainerSoundSoundEffect"] as SplitContainer; ListBox listBoxSoundSoundEffect = splitContainerSoundSoundEffect.Panel1.Controls["listBoxSoundSoundEffect"] as ListBox; TextBox textBoxSoundSoundEffect = splitContainerSoundSoundEffect.Panel2.Controls["textBoxSoundSoundEffect"] as TextBox; Button buttonSoundPlaySoundEffect = splitContainerSoundSoundEffect.Panel2.Controls["buttonSoundPlaySoundEffect"] as Button; TableConfig.MusicData.GetMusicEntryList().ForEach(musicEntry => listBoxSoundMusic.Items.Add(musicEntry)); listBoxSoundMusic.Click += (sender, e) => { MusicEntry musicEntry = listBoxSoundMusic.SelectedItem as MusicEntry; textBoxSoundMusic.Text = musicEntry.Index.ToString(); }; buttonSoundPlayMusic.Click += (sender, e) => { int?musicIndexNullable = ParsingUtilities.ParseIntNullable(textBoxSoundMusic.Text); if (musicIndexNullable == null) { return; } int musicIndex = musicIndexNullable.Value; if (musicIndex < 0 || musicIndex > 34) { return; } uint setMusic = RomVersionConfig.SwitchMap(0x80320544, 0x8031F690); InGameFunctionCall.WriteInGameFunctionCall(setMusic, 0, (uint)musicIndex, 0); }; foreach (uint soundEffect in _soundEffects) { string soundEffectString = HexUtilities.FormatValue(soundEffect, 4); listBoxSoundSoundEffect.Items.Add(soundEffectString); } listBoxSoundSoundEffect.Click += (sender, e) => { textBoxSoundSoundEffect.Text = listBoxSoundSoundEffect.SelectedItem.ToString() + "FF81"; }; buttonSoundPlaySoundEffect.Click += (sender, e) => { uint setSound = RomVersionConfig.SwitchMap(0x8031EB00, 0x8031DC78); uint soundArg = RomVersionConfig.SwitchMap(0x803331F0, 0x803320E0); uint?soundEffectNullable = ParsingUtilities.ParseHexNullable(textBoxSoundSoundEffect.Text); if (!soundEffectNullable.HasValue) { return; } uint soundEffect = soundEffectNullable.Value; InGameFunctionCall.WriteInGameFunctionCall(setSound, soundEffect, soundArg); }; }
/** * When refresh is clicked, the old GFX tree is discarded and a new one is read */ private void RefreshButton_Click(object sender, EventArgs e) { treeViewGfx.Nodes.Clear(); // A pointer to the root node of the GFX tree is stored at offset 0x04 in a certain struct var StructWithGfxRoot = Config.Stream.GetUInt32(RomVersionConfig.SwitchMap(0x8032DDCC, 0x8032CE6C)); if (StructWithGfxRoot > 0x80000000u) { AddToTreeView(Config.Stream.GetUInt32(StructWithGfxRoot + 0x04)); } ExpandNodesUpTo(treeViewGfx.Nodes, 4); }
public override void InitializeTab() { base.InitializeTab(); TableConfig.MusicData = XmlConfigParser.OpenMusicTable(@"Config/MusicData.xml"); TableConfig.MusicData.GetMusicEntryList().ForEach(musicEntry => listBoxSoundMusic.Items.Add(musicEntry)); listBoxSoundMusic.Click += (sender, e) => { MusicEntry musicEntry = listBoxSoundMusic.SelectedItem as MusicEntry; textBoxSoundMusic.Text = musicEntry.Index.ToString(); }; buttonSoundPlayMusic.Click += (sender, e) => { int?musicIndexNullable = ParsingUtilities.ParseIntNullable(textBoxSoundMusic.Text); if (musicIndexNullable == null) { return; } int musicIndex = musicIndexNullable.Value; if (musicIndex < 0 || musicIndex > 34) { return; } uint setMusic = RomVersionConfig.SwitchMap(0x80320544, 0x8031F690); InGameFunctionCall.WriteInGameFunctionCall(setMusic, 0, (uint)musicIndex, 0); }; foreach (uint soundEffect in _soundEffects) { string soundEffectString = HexUtilities.FormatValue(soundEffect, 4); listBoxSoundSoundEffect.Items.Add(soundEffectString); } listBoxSoundSoundEffect.Click += (sender, e) => { textBoxSoundSoundEffect.Text = listBoxSoundSoundEffect.SelectedItem.ToString() + "FF81"; }; buttonSoundPlaySoundEffect.Click += (sender, e) => { uint setSound = RomVersionConfig.SwitchMap(0x8031EB00, 0x8031DC78); uint soundArg = RomVersionConfig.SwitchMap(0x803331F0, 0x803320E0); uint?soundEffectNullable = ParsingUtilities.ParseHexNullable(textBoxSoundSoundEffect.Text); if (!soundEffectNullable.HasValue) { return; } uint soundEffect = soundEffectNullable.Value; InGameFunctionCall.WriteInGameFunctionCall(setSound, soundEffect, soundArg); }; }
public void HookUpTeleporters() { uint mainSegmentEnd = RomVersionConfig.SwitchMap(0x80367460, 0x803660F0); uint engineSegmentStart = RomVersionConfig.SwitchMap(0x80378800, 0x80378800); uint lastWarpNodeAddress = WatchVariableSpecialUtilities.GetWarpNodeAddresses().LastOrDefault(); if (lastWarpNodeAddress == 0) { return; } List <uint> objAddresses = Config.ObjectSlotsManager.SelectedObjects.ConvertAll(obj => obj.Address); if (objAddresses.Count < 2) { return; } uint teleporter1Address = objAddresses[0]; uint teleporter2Address = objAddresses[1]; short teleporter1Id = Config.Stream.GetShort(teleporter1Address + 0x188); short teleporter2Id = Config.Stream.GetShort(teleporter2Address + 0x188); uint warpNode1Address = mainSegmentEnd; uint warpNode2Address = mainSegmentEnd + 0xC; byte level = Config.Stream.GetByte(MiscConfig.WarpDestinationAddress + MiscConfig.LevelOffset); byte area = Config.Stream.GetByte(MiscConfig.WarpDestinationAddress + MiscConfig.AreaOffset); Config.Stream.SetValue((byte)teleporter1Id, warpNode1Address + 0x0); Config.Stream.SetValue(level, warpNode1Address + 0x1); Config.Stream.SetValue(area, warpNode1Address + 0x2); Config.Stream.SetValue((byte)teleporter2Id, warpNode1Address + 0x3); Config.Stream.SetValue(teleporter1Address, warpNode1Address + 0x4); Config.Stream.SetValue(warpNode2Address, warpNode1Address + 0x8); Config.Stream.SetValue((byte)teleporter2Id, warpNode2Address + 0x0); Config.Stream.SetValue(level, warpNode2Address + 0x1); Config.Stream.SetValue(area, warpNode2Address + 0x2); Config.Stream.SetValue((byte)teleporter1Id, warpNode2Address + 0x3); Config.Stream.SetValue(teleporter2Address, warpNode2Address + 0x4); Config.Stream.SetValue(0x00000000U, warpNode2Address + 0x8); Config.Stream.SetValue(warpNode1Address, lastWarpNodeAddress + 0x8); }
// Inject asm in the game that executes a function call once and then removes // itself. Example: // // WriteInGameFunctionCall(0x8031F690, 0, 33, 0); // play music with index 33 (JP version) // // It works by changing the function pointer of the level script update function // to some custom asm. Since this is an indirect call to something that hasn't // been code yet, it will work even if the emulator isn't in pure interpreter // mode and caches code. A memcpy at the end removes the injected asm and also forces // the emulator to remove the cache so another function can be injected later. public static void WriteInGameFunctionCall(uint address, params uint[] arguments) { const int maxArguments = 4; if (arguments.Length > maxArguments) { throw new System.Exception("trying to call function with " + arguments.Length + " arguments, max is " + maxArguments); } uint startAddress = 0x803FFF00; // some free 0-memory (hopefully) uint currAddress = startAddress; const uint A0 = 4; // register index for argument 0 // Stack frame: //ADDIU SP, SP, 0xFFE0 //SW RA, 0x0014 (SP) WriteWords(ref currAddress, 0x27BDFFE0, 0xAFBF0014); // Restore level script pointer: //LI T0, 0x8037EB04 //LUI AT, 0x8039 //SW T0, 0xB900(AT) WriteWords(ref currAddress, 0x3C088037, 0x3508EB04, 0x3C018039, 0xAC28B900); // Write function call for (int i = 0; i < arguments.Length; i++) { uint reg = (uint)(A0 + i); WriteRegisterAssign(ref currAddress, reg, arguments[i]); //WriteWords(ref baseAddress, LUI(reg, (ushort) (arguments[i] >> 16)), ORI(reg, reg, (ushort) (arguments[i] & 0xFFFF))); } WriteWords(ref currAddress, JAL(address), 0x00000000); // NOP for delay slot // Erase self and return as if nothing happened: uint memcpyAddress = RomVersionConfig.SwitchMap(0x803273F0, 0x803264C0); const uint eraseBytes = 16 * 4 + maxArguments * 8; //fixed instructions + 2 instructions per argument WriteWords(ref currAddress, 0x3C1F8037, 0x37FFEB0C); //LI RA, 0x8037EB0C WriteRegisterAssign(ref currAddress, A0, currAddress); WriteRegisterAssign(ref currAddress, A0 + 1, currAddress - 1); WriteWords(ref currAddress, J(memcpyAddress), 0x24060000 | eraseBytes); //ADDIU A2, R0, eraseBytes // Hijack level script function pointer to point to injected asm Config.Stream.SetValue(startAddress | 0x80000000, 0x8038B900); }
public MiscManager(string varFilePath, WatchVariableFlowLayoutPanel variableTable, Control miscControl) : base(varFilePath, variableTable, ALL_VAR_GROUPS, VISIBLE_VAR_GROUPS) { SplitContainer splitContainerMisc = miscControl.Controls["splitContainerMisc"] as SplitContainer; _checkBoxTurnOffMusic = splitContainerMisc.Panel1.Controls["checkBoxTurnOffMusic"] as CheckBox; GroupBox groupBoxRNGIndexTester = splitContainerMisc.Panel1.Controls["groupBoxRNGIndexTester"] as GroupBox; TextBox textBoxRNGIndexTester = groupBoxRNGIndexTester.Controls["textBoxRNGIndexTester"] as TextBox; Button buttonRNGIndexTester = groupBoxRNGIndexTester.Controls["buttonRNGIndexTester"] as Button; buttonRNGIndexTester.Click += (sender, e) => { int?rngIndexNullable = ParsingUtilities.ParseIntNullable(textBoxRNGIndexTester.Text); if (!rngIndexNullable.HasValue) { return; } int rngIndex = rngIndexNullable.Value; ushort rngValue = RngIndexer.GetRngValue(rngIndex); Config.Stream.SetValue(rngValue, MiscConfig.RngAddress); int nextRngIndex = rngIndex + 1; textBoxRNGIndexTester.Text = nextRngIndex.ToString(); }; Button buttonMiscGoToCourse = splitContainerMisc.Panel1.Controls["buttonMiscGoToCourse"] as Button; buttonMiscGoToCourse.ContextMenuStrip = new ContextMenuStrip(); foreach (CourseToGoTo courseToGoTo in _coursesToGoTo) { ToolStripMenuItem item = new ToolStripMenuItem(courseToGoTo.Name); item.Click += (sender, e) => InGameFunctionCall.WriteInGameFunctionCall( RomVersionConfig.SwitchMap(0x8024978C, 0x8024975C), (uint)courseToGoTo.Index); buttonMiscGoToCourse.ContextMenuStrip.Items.Add(item); } buttonMiscGoToCourse.Click += (sender, e) => buttonMiscGoToCourse.ContextMenuStrip.Show(Cursor.Position); }
public override void InitializeTab() { base.InitializeTab(); // Misc Image pictureBoxMisc.Image = Config.ObjectAssociations.MiscImage.Value; panelMiscBorder.BackColor = Config.ObjectAssociations.MiscColor; pictureBoxMisc.BackColor = Config.ObjectAssociations.MiscColor.Lighten(0.5); watchVariablePanelMisc.SetGroups(ALL_VAR_GROUPS, VISIBLE_VAR_GROUPS); buttonRNGIndexTester.Click += (sender, e) => { int?rngIncrementullable = ParsingUtilities.ParseIntNullable(txtRNGIncrement.Text); int?rngIndexNullable = ParsingUtilities.ParseIntNullable(textBoxRNGIndexTester.Text); if (!rngIndexNullable.HasValue || !rngIncrementullable.HasValue) { return; } int rngIndex = rngIndexNullable.Value; ushort rngValue = RngIndexer.GetRngValue(rngIndex); Config.Stream.SetValue(rngValue, MiscConfig.RngAddress); int nextRngIndex = rngIndex + rngIncrementullable.Value; textBoxRNGIndexTester.Text = nextRngIndex.ToString(); }; buttonMiscGoToCourse.ContextMenuStrip = new ContextMenuStrip(); foreach (CourseToGoTo courseToGoTo in _coursesToGoTo) { ToolStripMenuItem item = new ToolStripMenuItem(courseToGoTo.Name); item.Click += (sender, e) => InGameFunctionCall.WriteInGameFunctionCall( RomVersionConfig.SwitchMap(0x8024978C, 0x8024975C), (uint)courseToGoTo.Index); buttonMiscGoToCourse.ContextMenuStrip.Items.Add(item); } buttonMiscGoToCourse.Click += (sender, e) => buttonMiscGoToCourse.ContextMenuStrip.Show(Cursor.Position); }