diff --git a/StarManager/MainWindow.cs b/StarManager/MainWindow.cs index f5b530c..212929e 100644 --- a/StarManager/MainWindow.cs +++ b/StarManager/MainWindow.cs @@ -338,7 +338,7 @@ private void UpdateStars(object sender) if (!mm.isReadyToRead()) return; - mm.PerformRead(); + mm.PerformRead(rm); } catch (Exception) { diff --git a/StarManager/Managers/MemoryManager.cs b/StarManager/Managers/MemoryManager.cs index 77c4cb3..2ce96e3 100644 --- a/StarManager/Managers/MemoryManager.cs +++ b/StarManager/Managers/MemoryManager.cs @@ -293,7 +293,7 @@ public void doMagic() catch (Exception) { } } - public void PerformRead() + public void PerformRead(ROMManager rm) { Igt = Process.ReadValue(igtPtr); @@ -309,8 +309,8 @@ public void PerformRead() Area = Process.ReadValue(areaPtr); Reds = Process.ReadValue(redsPtr); - RestSecrets = GetSecrets(); - ActivePanels = GetActivePanels(); + RestSecrets = GetSecrets(rm); + ActivePanels = GetActivePanels(rm); SelectedStar = Process.ReadValue(selectedStarPtr); } @@ -412,20 +412,50 @@ public sbyte GetReds() return request; } - int GetSecrets() + int GetSecrets(ROMManager rm) { - return SearchObjects(GetBehaviourRAMAddress(0x3F1C)); + if (rm == null) + return SearchObjects(GetBehaviourRAMAddress(0x3F1C)); + else + { + int objCount = 0; + foreach (uint behavAddr in rm.GetSecretsBehavAddresses()) + { + objCount += SearchObjects(GetBehaviourRAMAddress(behavAddr)); + } + return objCount; + } } - int GetActivePanels() + int GetActivePanels(ROMManager rm) { - uint request = GetBehaviourRAMAddress(0x5D8); - return SearchObjects(request, 1) + SearchObjects(request, 2); //1 - active, 2 - finalized + if (rm == null) + return SearchObjects(GetBehaviourRAMAddress(0x5D8), 1) + SearchObjects(GetBehaviourRAMAddress(0x5D8), 2); //1 - active, 2 - finalized + else + { + int objCount = 0; + foreach (uint behavAddr in rm.GetPanelsBehavAddresses()) + { + objCount += SearchObjects(GetBehaviourRAMAddress(behavAddr), 1); + objCount += SearchObjects(GetBehaviourRAMAddress(behavAddr), 2); + } + return objCount; + } } - int GetAllPanels() + int GetAllPanels(ROMManager rm) { - return SearchObjects(GetBehaviourRAMAddress(0x5D8)); + if (rm == null) + return SearchObjects(GetBehaviourRAMAddress(0x5D8)); + else + { + int objCount = 0; + foreach (uint behavAddr in rm.GetPanelsBehavAddresses()) + { + objCount += SearchObjects(GetBehaviourRAMAddress(behavAddr)); + } + return objCount; + } } public Bitmap GetImage() @@ -637,7 +667,6 @@ public int SearchObjects(UInt32 searchBehaviour) { UInt32 intparam = BitConverter.ToUInt32(data, 0x180); UInt32 behaviour = BitConverter.ToUInt32(data, 0x20C); - UInt32 scriptParameter = BitConverter.ToUInt32(data, 0x0F0); if (behaviour == searchBehaviour) { diff --git a/StarManager/Managers/ROMManager.cs b/StarManager/Managers/ROMManager.cs index 88e4511..868e2e7 100644 --- a/StarManager/Managers/ROMManager.cs +++ b/StarManager/Managers/ROMManager.cs @@ -14,7 +14,11 @@ public class ROMManager : CachedManager, IDisposable { BinaryReader reader; - static int courseBaseAddress = 0x02AC094; + static int courseBaseAddress = 0x02AC094; // BBH ("first" level, 0x04) ROM address + static uint seg15StartRomAddress = 0x02ABCA0; // global(?) segment loads before switching by course ID, incl. bank 13 (see Quad64) + static uint seg13StartRomAddress = 0x0219E00; // default in old binary (Editor) ROMs, but scanned for in seg15 on init + static uint seg13EndRomAddress = 0x021F4C0; // default in old binary (Editor) ROMs, but scanned for in seg15 on init + static uint[] seg13Words; static byte levelscriptEndDescriptor = 0x02; static byte jumpDescriptor = 0x05; @@ -45,9 +49,13 @@ public class ROMManager : CachedManager, IDisposable static byte[] bossMIPSBehaviour = { 0x00, 0x44, 0xFC }; - static byte[] redsBehaviour = { 0x00, 0x3E, 0xAC }; - static byte[] secretsBehaviour = { 0x00, 0x3F, 0x1C }; - static byte[] flipswitchBehaviour = { 0x00, 0x05, 0xD8 }; + + List redsBehaviours; + static uint redsBehavLoopCall = 0x802F2F2C; + List secretsBehaviours; + static uint secretsBehavLoopCall = 0x802F31BC; + List flipswitchBehaviours; + static uint flipswitchBehavLoopCall = 0x802A8238; Object[] boxObjects; @@ -116,6 +124,27 @@ public ROMManager(string fileName) reader = new BinaryReader(new MemoryStream(data)); boxObjects = ReadBoxBehaviours(); + + ReadSegment13ROMRangeAddrs(); + //uint[] seg13Words; + { + reader.BaseStream.Position = seg13StartRomAddress; + byte[] seg13Bytes = reader.ReadBytes((int)(seg13EndRomAddress - seg13StartRomAddress)); // casts may lose info if segment size > 7FFFFFFF, which shouldn't happen + int size = seg13Bytes.Count() / 4; + seg13Words = new uint[size]; + for (int idx = 0; idx < size; idx++) + { + byte[] dataInt = new byte[4]; + dataInt[0] = seg13Bytes[3 + 4 * idx]; + dataInt[1] = seg13Bytes[2 + 4 * idx]; + dataInt[2] = seg13Bytes[1 + 4 * idx]; + dataInt[3] = seg13Bytes[0 + 4 * idx]; + seg13Words[idx] = BitConverter.ToUInt32(dataInt, 0); + } + } + redsBehaviours = FindSeg13BehavAddrs(redsBehavLoopCall); + secretsBehaviours = FindSeg13BehavAddrs(secretsBehavLoopCall); + flipswitchBehaviours = FindSeg13BehavAddrs(flipswitchBehavLoopCall); } public ROMManager(byte[] data) @@ -123,6 +152,67 @@ public ROMManager(byte[] data) if (data == null) throw new IOException("Data is null"); reader = new BinaryReader(new MemoryStream(data)); boxObjects = ReadBoxBehaviours(); + + ReadSegment13ROMRangeAddrs(); + //uint[] seg13Words; + { + reader.BaseStream.Position = seg13StartRomAddress; + byte[] seg13Bytes = reader.ReadBytes((int)(seg13EndRomAddress - seg13StartRomAddress)); // casts may lose info if segment size > 7FFFFFFF, which shouldn't happen + int size = seg13Bytes.Count() / 4; + seg13Words = new uint[size]; + for (int idx = 0; idx < size; idx++) + { + byte[] dataInt = new byte[4]; + dataInt[0] = seg13Bytes[3 + 4 * idx]; + dataInt[1] = seg13Bytes[2 + 4 * idx]; + dataInt[2] = seg13Bytes[1 + 4 * idx]; + dataInt[3] = seg13Bytes[0 + 4 * idx]; + seg13Words[idx] = BitConverter.ToUInt32(dataInt, 0); + } + } + redsBehaviours = FindSeg13BehavAddrs(redsBehavLoopCall); + secretsBehaviours = FindSeg13BehavAddrs(secretsBehavLoopCall); + flipswitchBehaviours = FindSeg13BehavAddrs(flipswitchBehavLoopCall); + } + + public List GetRedsBehavAddresses() { return redsBehaviours; } + public List GetSecretsBehavAddresses() { return secretsBehaviours; } + public List GetPanelsBehavAddresses() { return flipswitchBehaviours; } + + private List FindSeg13BehavAddrs(uint targetCallWord) + { + List wordOffsets = new List(); + + // find addresses calling the target function + for (int i = 0; i < seg13Words.Length; i++) + { + if (seg13Words[i] == targetCallWord) + wordOffsets.Add(4 * (uint)i); + } + if (wordOffsets.Count == 0) return wordOffsets; // target function not used, we won't find a behavior below. return empty list early + + // for each offset considered valid, go backwards towards start of script to turn offset into script address + for (int i = 0; i < wordOffsets.Count; i++) + { + reader.BaseStream.Position = seg13StartRomAddress + wordOffsets[i]; + byte[] behavScriptLineBytes; + int reads = 0; + wordOffsets[i] += 0x04; // workaround for last step in while-loop happening before it figures out whether it needs to do it + do + { + behavScriptLineBytes = reader.ReadBytes(4); + reader.BaseStream.Position -= 0x08; + wordOffsets[i] -= 0x04; + reads++; + } + // FIXME: "Start behav" cmd is 00 XX 00 00, XX == 00-0C, ignore bigger values. also ignore 00 because in cmd 00 that group is only Mario. + // This MAY STILL get garbage, if a command has parameter looking like 00 [00-0C] [...] aligned to 4 bytes (see cmds 23, 30). + // XXX: Line reads are limited to 40, rounding up from longest vanilla behav (13000338, particle; 0x84 bytes/0x21 words). + // Same behav would also trip this function with its cmd parameter values. + while (reads < 0x28 && (behavScriptLineBytes[0] != 0x00 || behavScriptLineBytes[1] == 0x00 || behavScriptLineBytes[1] > 0x0C)); + } + + return wordOffsets; } public void Dispose() @@ -162,6 +252,13 @@ private byte[] ReadBehaviour(int offset) return reader.ReadBytes(3); } + // level cmd 24 18 ... [BS BS BS BS] - BS read in full. useful if you will pass it indiscriminately into a BitConverter 4-byte read. + private byte[] ReadBehaviourFullAddr(int offset) + { + reader.BaseStream.Position = offset + 0x14; + return reader.ReadBytes(4); + } + private byte ReadBParam1(int offset) { reader.BaseStream.Position = offset + 0x10; @@ -240,7 +337,10 @@ public int ParseReds(int level, int currentStar, int currentArea) int result = PrepareAddresses(level, out int levelAddressStart, out int levelAddressEnd, out int levelOffset); if (result != 0) return 0; - return GetAmountOfObjects(levelAddressStart, levelAddressEnd, levelOffset, redsBehaviour, currentStar, currentArea); + int objCount = 0; + foreach (uint redsBehav in redsBehaviours) + objCount += GetAmountOfObjects(levelAddressStart, levelAddressEnd, levelOffset, redsBehav, currentStar, currentArea); + return objCount; } public int ParseSecrets(int level, int currentStar, int currentArea) @@ -248,7 +348,10 @@ public int ParseSecrets(int level, int currentStar, int currentArea) int result = PrepareAddresses(level, out int levelAddressStart, out int levelAddressEnd, out int levelOffset); if (result != 0) return 0; - return GetAmountOfObjects(levelAddressStart, levelAddressEnd, levelOffset, secretsBehaviour, currentStar, currentArea); + int objCount = 0; + foreach (uint secretsBehav in secretsBehaviours) + objCount += GetAmountOfObjects(levelAddressStart, levelAddressEnd, levelOffset, secretsBehav, currentStar, currentArea); + return objCount; } public int ParseFlipswitches(int level, int currentStar, int currentArea) @@ -256,10 +359,13 @@ public int ParseFlipswitches(int level, int currentStar, int currentArea) int result = PrepareAddresses(level, out int levelAddressStart, out int levelAddressEnd, out int levelOffset); if (result != 0) return 0; - return GetAmountOfObjects(levelAddressStart, levelAddressEnd, levelOffset, flipswitchBehaviour, currentStar, currentArea); + int objCount = 0; + foreach (uint flipswitchBehav in flipswitchBehaviours) + objCount += GetAmountOfObjects(levelAddressStart, levelAddressEnd, levelOffset, flipswitchBehav, currentStar, currentArea); + return objCount; } - private int GetAmountOfObjects(int start, int end, int offset, byte[] searchBehaviour, int currentStar, int currentArea) + private int GetAmountOfObjects(int start, int end, int offset, uint searchBehaviour, int currentStar, int currentArea) { int area = 0; return GetAmountOfObjectsInternal(start, end, offset, searchBehaviour, currentStar, currentArea, ref area); @@ -273,8 +379,16 @@ public int SwapBytes(int x) ((x & 0xff000000) >> 24)); } + public uint SwapBytes(uint x) + { + return ((x & 0x000000ff) << 24) + + ((x & 0x0000ff00) << 8) + + ((x & 0x00ff0000) >> 8) + + ((x & 0xff000000) >> 24); + } + - private int GetAmountOfObjectsInternal (int start, int end, int Loffset, byte[] searchBehaviour, int currentStar, int currentArea, ref int area) + private int GetAmountOfObjectsInternal (int start, int end, int Loffset, uint searchBehaviour, int currentStar, int currentArea, ref int area) { if (currentArea == 0) currentArea = 1; byte currentStarMask = (byte) (1 << currentStar); @@ -342,8 +456,10 @@ private int GetAmountOfObjectsInternal (int start, int end, int Loffset, byte[] continue; } - byte[] behaviour = ReadBehaviour(offset); - if (behaviour.SequenceEqual(searchBehaviour)) + byte[] behaviour = ReadBehaviourFullAddr(offset); + behaviour[0] = 0x00; // read 4 bytes for BitConverter below, but clear the unnecessary segment byte + uint behaviorAsAddr = SwapBytes(BitConverter.ToUInt32(behaviour, 0)); + if (behaviorAsAddr == searchBehaviour) { counter++; } @@ -475,6 +591,32 @@ public Object[] ReadBoxBehaviours() } } + // hardcoded to reading for segment 13; returns void + values to class vars as lazy way to not design for "multiple returns" + // if it somehow fails, default values are used + private void ReadSegment13ROMRangeAddrs() + { + reader.BaseStream.Position = seg15StartRomAddress; + int offset = 0; + // XXX: Reads are limited to 0x20, because there are that many segments, assumption is each only needs to be loaded once. + for (int reads = 0; reads < 0x20; reads++) + { + // load cmds 0x16, 17, 18 are all length 0xC + byte[] loadCmdLineBytes = reader.ReadBytes(0xC); + + if (loadCmdLineBytes[0] == 0x1D) + break; + + if (loadCmdLineBytes[3] == 0x13) // 4th byte is segment number + { + seg13StartRomAddress = SwapBytes(BitConverter.ToUInt32(loadCmdLineBytes, 0x4)); + seg13EndRomAddress = SwapBytes(BitConverter.ToUInt32(loadCmdLineBytes, 0x8)); + } + + offset += 0xC; + reader.BaseStream.Position = seg15StartRomAddress + offset; + } + } + public Bitmap GetStarImage() { reader.BaseStream.Position = 0x807956; //0x803156 + 0x4800