Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
72fb886
MemoryManager: Try search secrets by looped ASM call instead of behav ID
Mushie64 Feb 4, 2025
e16af5f
MemoryManager: Add segmented_to_virtual-like function
Mushie64 Feb 5, 2025
ae2f5c6
MemoryManager: Fix behav script start pointer arithmetic + endianness
Mushie64 Feb 5, 2025
1004354
MemoryManager: Add 0C to checked lengthy behav cmds outside loops
Mushie64 Feb 5, 2025
a635e4b
MemoryManager: SegmentedToAddress() -> SegmentedToVirtual()
Mushie64 Feb 6, 2025
ed87a71
MemoryManager: Cache searched behavs by call + move cmd length checks…
Mushie64 Feb 6, 2025
37f10ef
Move behavCmdLengths dict out of concrete class code
Mushie64 Feb 10, 2025
c6ffef2
ROMManager: Search objects by looped ASM call instead of behav ID
Mushie64 Feb 10, 2025
8fbe8b5
MemoryManager: Clean up debug Console.WriteLines
Mushie64 Feb 10, 2025
bdd023e
MemoryManager: Break read on behav terminating commands
Mushie64 Feb 11, 2025
5768590
MemoryManager: Copy SearchObjectsByBehavCalls with state param + adapt
Mushie64 Feb 11, 2025
6574b52
MemoryManager: Clean up comments
Mushie64 Feb 11, 2025
cf5739f
ROMManager: Replace array copy hack with full behav address read
Mushie64 Feb 11, 2025
51642aa
MemoryManager: Fix counting of active panels
Mushie64 Mar 18, 2025
124380e
ROMManager: Optimize reads for collectable behavs
Mushie64 Apr 25, 2025
6cf9681
MemoryManager: Revert SearchObjects(), get target behavs from ROMManager
Mushie64 Apr 25, 2025
30dc032
ROMManager: Read entire bank 13 + optimize reads for behav addrs
Mushie64 Aug 27, 2025
4b4bf5e
Allow multiple trackable behaviors per collectible
Mushie64 Aug 27, 2025
cadda93
Clean up leftover code
Mushie64 Aug 28, 2025
7ad347f
Limit loops in scanning functions
Mushie64 Aug 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions StarManager/LevelInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,27 @@ public static LevelOffsetsDescription FindByNaturalIndex(int naturalIndex)
return Array.Find(Description, descr => descr.NaturalIndex == naturalIndex);
}
}



public class SM64CmdHelpers
{
// for reading through behavior script commands and skipping the non-cmd bytes we don't need
public static Dictionary<uint, int> behavCmdLengths = new Dictionary<uint, int>()
{
{ 0x02, 0x8 }, { 0x04, 0x8 }, { 0x0C, 0x8 }, { 0x13, 0x8 },
{ 0x14, 0x8 }, { 0x15, 0x8 }, { 0x16, 0x8 }, { 0x17, 0x8 },
{ 0x23, 0x8 }, { 0x27, 0x8 }, { 0x2A, 0x8 }, { 0x2E, 0x8 },
{ 0x2F, 0x8 }, { 0x31, 0x8 }, { 0x33, 0x8 }, { 0x36, 0x8 },
{ 0x37, 0x8 },
{ 0x1C, 0xC }, { 0x29, 0xC }, { 0x2B, 0xC }, { 0x2C, 0xC },
{ 0x30, 0x14 }
};

public static HashSet<uint> behavTerminatingCmds = new HashSet<uint>()
{
{ 0x03 }, { 0x04 }, { 0x09 }, { 0x0A }, { 0x1D }
// NOT 02 (jump and link?); it is NEVER found at the end of a (vanilla) behavior.
};
}
}
2 changes: 1 addition & 1 deletion StarManager/MainWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ private void UpdateStars(object sender)
if (!mm.isReadyToRead())
return;

mm.PerformRead();
mm.PerformRead(rm);
}
catch (Exception)
{
Expand Down
51 changes: 40 additions & 11 deletions StarManager/Managers/MemoryManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ public void doMagic()
catch (Exception) { }
}

public void PerformRead()
public void PerformRead(ROMManager rm)
{
Igt = Process.ReadValue<int>(igtPtr);

Expand All @@ -309,8 +309,8 @@ public void PerformRead()
Area = Process.ReadValue<byte>(areaPtr);
Reds = Process.ReadValue<sbyte>(redsPtr);

RestSecrets = GetSecrets();
ActivePanels = GetActivePanels();
RestSecrets = GetSecrets(rm);
ActivePanels = GetActivePanels(rm);

SelectedStar = Process.ReadValue<byte>(selectedStarPtr);
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
{
Expand Down
167 changes: 156 additions & 11 deletions StarManager/Managers/ROMManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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, overwrite this if found to be repointed in level script
static uint seg13EndRomAddress; // get this (together with start) from the bank 15 read on init
static uint[] seg13Words;

static byte levelscriptEndDescriptor = 0x02;
static byte jumpDescriptor = 0x05;
Expand Down Expand Up @@ -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<uint> redsBehaviours;
static uint redsBehavLoopCall = 0x802F2F2C;
List<uint> secretsBehaviours;
static uint secretsBehavLoopCall = 0x802F31BC;
List<uint> flipswitchBehaviours;
static uint flipswitchBehavLoopCall = 0x802A8238;

Object[] boxObjects;

Expand Down Expand Up @@ -116,13 +124,92 @@ 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)
{
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<uint> GetRedsBehavAddresses() { return redsBehaviours; }
public List<uint> GetSecretsBehavAddresses() { return secretsBehaviours; }
public List<uint> GetPanelsBehavAddresses() { return flipswitchBehaviours; }

private List<uint> FindSeg13BehavAddrs(uint targetCallWord)
{
List<uint> wordOffsets = new List<uint>();

// find address of target call
for (int i = 0; i < seg13Words.Length; i++)
{
if (seg13Words[i] == targetCallWord)
wordOffsets.Add(4 * (uint)i);
}
if (wordOffsets.Count == 0) return wordOffsets; // target call 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;
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;
}
// XXX: start behav is 00 XX 00 00, XX == 00-0C, ignore bigger values. also ignore 00 because in cmd 00 that group is only Mario.
// this is STILL error-prone, if a command uses 00 XX aligned to 4 bytes as parameter (see cmds 23, 30), we'll get garbage here.
// sure, the while-loop can never run longer than one valid behav script anyway, but not getting correct behav over this sounds dumb.
while (behavScriptLineBytes[0] != 0x00 || behavScriptLineBytes[1] == 0x00 || behavScriptLineBytes[1] > 0x0D);
}

return wordOffsets;
}

public void Dispose()
Expand Down Expand Up @@ -162,6 +249,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;
Expand Down Expand Up @@ -240,26 +334,35 @@ 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)
{
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)
{
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);
Expand All @@ -273,8 +376,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);
Expand Down Expand Up @@ -342,8 +453,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 drop the leading (segment) byte
uint behaviorAsAddr = SwapBytes(BitConverter.ToUInt32(behaviour, 0));
if (behaviorAsAddr == searchBehaviour)
{
counter++;
}
Expand All @@ -352,6 +465,7 @@ private int GetAmountOfObjectsInternal (int start, int end, int Loffset, byte[]
{
reader.BaseStream.Position = offset + 0x3;
int bank = reader.ReadByte();

if (bank != 0xE)
continue;

Expand Down Expand Up @@ -475,6 +589,37 @@ public Object[] ReadBoxBehaviours()
}
}

// hardcoded to reading for segment 13; returns void + values to class vars as lazy way to not design for "multiple returns"
private void ReadSegment13ROMRangeAddrs()
{
reader.BaseStream.Position = seg15StartRomAddress;
int offset = 0;
while (true)
{
// load cmds 0x16, 17, 18 are all length 0xC
byte[] loadCmdLineBytes = reader.ReadBytes(0xC);

if (loadCmdLineBytes[0] == 0x1D)
break;

//if (loadCmdLineBytes[0] != 0x17) return 0; // only deal with 0x17; I'm not sure why I would force that
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;
}
}

// assumes bank 13 until Read_() and GetAmountOfObjectInternal stuff are reimplemented without that assumption
private uint Bank13_BehavSegmentedToROM(uint segmented)
{
return seg13StartRomAddress + (segmented & 0x00FFFFFF);
}

public Bitmap GetStarImage()
{
reader.BaseStream.Position = 0x807956; //0x803156 + 0x4800
Expand Down