Skip to content
Open
Show file tree
Hide file tree
Changes from 16 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
21 changes: 10 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,20 @@ public sbyte GetReds()
return request;
}

int GetSecrets()
int GetSecrets(ROMManager rm)
{
return SearchObjects(GetBehaviourRAMAddress(0x3F1C));
return SearchObjects(rm == null ? GetBehaviourRAMAddress(0x3F1C) : GetBehaviourRAMAddress(rm.GetSecretsBehavAddress()));
}

int GetActivePanels()
int GetActivePanels(ROMManager rm)
{
uint request = GetBehaviourRAMAddress(0x5D8);
return SearchObjects(request, 1) + SearchObjects(request, 2); //1 - active, 2 - finalized
uint panelsBehav = rm == null ? GetBehaviourRAMAddress(0x5D8) : GetBehaviourRAMAddress(rm.GetPanelsBehavAddress());
return SearchObjects(panelsBehav, 1) + SearchObjects(panelsBehav, 2); //1 - active, 2 - finalized
}

int GetAllPanels()
int GetAllPanels(ROMManager rm)
{
return SearchObjects(GetBehaviourRAMAddress(0x5D8));
return SearchObjects(rm == null ? GetBehaviourRAMAddress(0x5D8) : GetBehaviourRAMAddress(rm.GetPanelsBehavAddress()));
}

public Bitmap GetImage()
Expand Down Expand Up @@ -637,7 +637,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
169 changes: 168 additions & 1 deletion StarManager/Managers/ROMManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ 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 byte levelscriptEndDescriptor = 0x02;
static byte jumpDescriptor = 0x05;
Expand Down Expand Up @@ -45,9 +47,29 @@ public class ROMManager : CachedManager, IDisposable

static byte[] bossMIPSBehaviour = { 0x00, 0x44, 0xFC };

// default behav script addresses here; a hack (*cough* SS4 *cough*) may use others.
// loop call addrs here are only to search for those others
static byte[] redsBehaviour = { 0x00, 0x3E, 0xAC };
static uint redsBehavLoopCall = 0x802F2F2C;
static byte[] secretsBehaviour = { 0x00, 0x3F, 0x1C };
static uint secretsBehavLoopCall = 0x802F31BC;
static byte[] flipswitchBehaviour = { 0x00, 0x05, 0xD8 };
static uint flipswitchBehavLoopCall = 0x802A8238;

// TODO?: if changing byte[]s above to proper addresses(uints), remove this helper
private UInt32 ConstructAddrFromBytes(byte[] bytes)
{
UInt32 addr = 0x0;
for (int i = 0; i < bytes.Length; i++)
{
addr |= (UInt32)(bytes[i] << (8 * (bytes.Length - 1 - i)));
}
return addr;
}
public uint GetRedsBehavAddress() { return ConstructAddrFromBytes(redsBehaviour); }
public uint GetSecretsBehavAddress() { return ConstructAddrFromBytes(secretsBehaviour); }
public uint GetPanelsBehavAddress() { return ConstructAddrFromBytes(flipswitchBehaviour); }


Object[] boxObjects;

Expand Down Expand Up @@ -116,13 +138,107 @@ public ROMManager(string fileName)

reader = new BinaryReader(new MemoryStream(data));
boxObjects = ReadBoxBehaviours();

seg13StartRomAddress = ReadSegmentROMStartAddress(0x13);
GetCollectablesBehavAddrs();
}

public ROMManager(byte[] data)
{
if (data == null) throw new IOException("Data is null");
reader = new BinaryReader(new MemoryStream(data));
boxObjects = ReadBoxBehaviours();

seg13StartRomAddress = ReadSegmentROMStartAddress(0x13);
GetCollectablesBehavAddrs();
}

// Search for vanilla reds func call, vanilla secrets func call, and default panels func call to determine
// the behavior addresses that we can call "reds", "secrets", "flipswitches" for this application's counting.
// Always tries the entire bank 0x13 area, so if somehow multiple behavs use one of these calls, the LAST one will be "returned".
private void GetCollectablesBehavAddrs()
{
uint behavStartAddress = seg13StartRomAddress;
bool isInLoopBlock = false;
bool isInvalidScriptByte = false;
int scriptLinePtr = 0x0;

while (true)
{
reader.BaseStream.Position = behavStartAddress + scriptLinePtr;

byte[] behavScriptLineBytes = reader.ReadBytes(4);
scriptLinePtr += 0x4;

if (SM64CmdHelpers.behavTerminatingCmds.Contains(behavScriptLineBytes[0]))
{
behavStartAddress = (uint)reader.BaseStream.Position;
scriptLinePtr = 0x0;
}

// advance past the "parameter" bytes if the cmd is not of interest
if (SM64CmdHelpers.behavCmdLengths.ContainsKey(behavScriptLineBytes[0]))
scriptLinePtr += SM64CmdHelpers.behavCmdLengths[behavScriptLineBytes[0]] - 0x4;

if (behavScriptLineBytes[0] == 0x08)
isInLoopBlock = true;
else if (behavScriptLineBytes[0] == 0x01)
{
// 0x01 is a legit behav cmd, but also 01 01 01 01... padding is common for extended roms.
// the behav cmd is 01 00 XX XX, so if second byte is 01, assume padding (end of behav scripts).
if (behavScriptLineBytes[1] == 0x01)
{
isInvalidScriptByte = true;
}
}
// I don't know what's directly after the behav scripts area, but we're bound to read "obviously invalid" commands/bytes
// shortly, use that to indicate exit. (chance to read a "correct" sequence into a 0x08 then ASM address should be close to 0.)
else if (behavScriptLineBytes[0] > 0x37)
isInvalidScriptByte = true;

while (isInLoopBlock)
{
byte[] loopedCmdBytes = reader.ReadBytes(4);

if (SM64CmdHelpers.behavCmdLengths.ContainsKey(behavScriptLineBytes[0]))
scriptLinePtr += SM64CmdHelpers.behavCmdLengths[behavScriptLineBytes[0]] - 0x4;

if (loopedCmdBytes[0] == 0x09)
{
// technically it would be better to update address on start(0x00), but this is good enough.
// it would only betray us if a behavior of interest is right after a poorly terminated behavior
// (see vanilla (not of interest): 130035B0-13003628), and it would only lead to 0 detected collectables, not crashes.
behavStartAddress = (uint)reader.BaseStream.Position;
isInLoopBlock = false;
scriptLinePtr = -0x4;
}
else if (loopedCmdBytes[0] == 0x0C)
{
uint calledASMAddr = SwapBytes(BitConverter.ToUInt32(reader.ReadBytes(4), 0x0));
if (calledASMAddr == redsBehavLoopCall)
{
// FIXME?: aglab, why did you invent this representation for behav addresses instead of directly using an address(uint)
byte[] behavAsArray = BitConverter.GetBytes(behavStartAddress - seg13StartRomAddress);
redsBehaviour = new byte[] { behavAsArray[2], behavAsArray[1], behavAsArray[0] };
}
else if (calledASMAddr == secretsBehavLoopCall)
{
byte[] behavAsArray = BitConverter.GetBytes(behavStartAddress - seg13StartRomAddress);
secretsBehaviour = new byte[] { behavAsArray[2], behavAsArray[1], behavAsArray[0] };
}
else if (calledASMAddr == flipswitchBehavLoopCall)
{
byte[] behavAsArray = BitConverter.GetBytes(behavStartAddress - seg13StartRomAddress);
flipswitchBehaviour = new byte[] { behavAsArray[2], behavAsArray[1], behavAsArray[0] };
}
}

scriptLinePtr += 0x4;
}

if (isInvalidScriptByte) // done with behav scripts area
break;
}
}

public void Dispose()
Expand Down Expand Up @@ -162,6 +278,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 @@ -273,6 +396,14 @@ 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)
{
Expand Down Expand Up @@ -352,6 +483,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 +607,41 @@ public Object[] ReadBoxBehaviours()
}
}

// intended for only bank 13 until further notice
private uint ReadSegmentROMStartAddress(uint segment)
{
uint segmentROMStartAddress = 0;

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] == segment)
{
// get only start address; if necessary, you can also get end address from offset 0x8
segmentROMStartAddress = SwapBytes(BitConverter.ToUInt32(loadCmdLineBytes, 0x4));
}

offset += 0xC;
reader.BaseStream.Position = seg15StartRomAddress + offset;
}

return segmentROMStartAddress;
}

// 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