Page 1 of 2

Autosplitter for PoP1 SNES 100%

Posted: November 8th, 2022, 8:12 pm
by Shauing
I want to ask if inside the game's code, is there an area where it keep track of the potions you've taken and the guards you have killed throughout the game. On the speedrun community we're trying to create the 100% category, and because the game itself doesn't have any gallery or anything similar that displays what have you grabbed, we were wondering maybe inside the game's memory/coding, there's something that keeps track of these things.

Re: Disassembly of PoP1 SNES

Posted: November 12th, 2022, 2:37 pm
by David
Shauing wrote: November 8th, 2022, 8:12 pm I want to ask if inside the game's code, is there an area where it keep track of the potions you've taken and the guards you have killed throughout the game. On the speedrun community we're trying to create the 100% category, and because the game itself doesn't have any gallery or anything similar that displays what have you grabbed, we were wondering maybe inside the game's memory/coding, there's something that keeps track of these things.
These are kept track only for the current level, so a potion you've already drunk won't reappear and such.
They are reset when you restart the level.

The level objects (including potions) are stored starting from RAM address 7F:E170, on 24*30 bytes (one byte for each tile of each room).
When the player drinks a potion, the corresponding byte changes to zero.

For example, consider the first potion on level 1, under the green guard.
It's in room 8, at tile 27 (row 2, column 7), so its RAM address is 0x7FE170 + 8*30+27 == 0x7FE27B.
(This calculation assumes that rooms, rows, and columns are all counted from 0, as in Pr1SnesLevEd.)
When that byte changes to 0 (from 0x12), you know the player picked up this potion.

In an AutoSplit file, that offset translates into this:

Code: Select all

	byte Potion_1_8_27 : "snes9x-x64.exe", 0x008D8C38, 0x1E27B; // 7F:E27B
(Assuming you are using snes9x-1.60-win32-x64.)

The final offset is calculated as: 0x7FE27B - 0x7E0000 = 0x1E27B.

Now of course you need to check if the current level is level 1 before checking any potions on level 1.

Here is the AutoSplit offset for the current level:

Code: Select all

	byte CurrentLevel  : "snes9x-x64.exe", 0x008D8C38, 0x0579; // 7E:0579
I'll see if I can find something for the guards.

Re: Disassembly of PoP1 SNES

Posted: November 13th, 2022, 5:32 pm
by Shauing
Nice. If you manage to find something that tracks the guards you kill, that would be awesome and most likely will allow us to have a way to track them via splits in Livesplit with autosplitter, thus maybe use game end and continue for faster strategies, and make Level 11 possible to complete without an intentional death.

Re: Disassembly of PoP1 SNES

Posted: November 19th, 2022, 1:50 pm
by David
Shauing wrote: November 13th, 2022, 5:32 pm Nice. If you manage to find something that tracks the guards you kill, that would be awesome
I came up with this:

Code: Select all

state("snes9x-x64")
{
	byte CurrentLevel    : "snes9x-x64.exe", 0x008D8C38, 0x0579; // 7E:0579
	sbyte Guard_alive    : "snes9x-x64.exe", 0x008D8C38, 0x0486; // 7E:0486
	byte Guard_room      : "snes9x-x64.exe", 0x008D8C38, 0x0482; // 7E:0482
	byte Guard_direction : "snes9x-x64.exe", 0x008D8C38, 0x047A; // 7E:047A
}

update
{
	//print("Guard_direction = " + current.Guard_direction);
	// 0x00=right, 0x80=left, 0x7F=no guard
	if (current.Guard_direction != 0x7F) // if there is an active guard
	{
		//print("Guard_alive = " + current.Guard_alive);
		// -1 (0xFF) if alive, 0 if dead
		//print("Guard_room = " + current.Guard_room);
		if (current.Guard_alive == 0 && old.Guard_alive == -1)
		{
			print("A guard died in room " + current.Guard_room + " of level " + (current.CurrentLevel+1));
		}
	}
}
Caveat: A guard may die in a room different from where he starts the level.
This can be a problem if you need to know which guards died.
If you only need the number of guards who died on a level, then you can increase a counter every time a guard dies, then check its value when the level ends.

Problem: This does not seem to work with the skeleton on level 3.
Guard_alive stays -1 even when the skeleton is crushed.

Re: Disassembly of PoP1 SNES

Posted: November 19th, 2022, 2:12 pm
by David
David wrote: November 19th, 2022, 1:50 pm Problem: This does not seem to work with the skeleton on level 3.
Guard_alive stays -1 even when the skeleton is crushed.
Here is a version which fixes that.

Code: Select all

state("snes9x-x64")
{
	sbyte Guard_alive    : "snes9x-x64.exe", 0x008D8C38, 0x0486; // 7E:0486
	byte Guard_room      : "snes9x-x64.exe", 0x008D8C38, 0x0482; // 7E:0482
	byte Guard_direction : "snes9x-x64.exe", 0x008D8C38, 0x047A; // 7E:047A
	byte Guard_hp        : "snes9x-x64.exe", 0x008D8C38, 0x050B; // 7E:050B
	byte Guard_frame     : "snes9x-x64.exe", 0x008D8C38, 0x0477; // 7E:0477
}

update
{
	//print("Guard_direction = " + current.Guard_direction);
	// 0x00=right, 0x80=left, 0x7F=no guard
	if (current.Guard_direction != 0x7F) // if there is an active guard
	{
		//print("Guard_alive = " + current.Guard_alive);
		// -1 (0xFF) if alive, 0 if dead
		//print("Guard_room = " + current.Guard_room);
		//print("Guard_hp = " + current.Guard_hp);
		//print("Guard_frame = " + current.Guard_frame);
		// 184 = crushed
		if ((current.Guard_alive == 0 || current.Guard_frame == 184) && (old.Guard_alive == -1 && old.Guard_frame != 184))
		{
			print("A guard died in room " + current.Guard_room + " of level " + (current.CurrentLevel+1));
		}
	}
}

Re: Disassembly of PoP1 SNES

Posted: November 20th, 2022, 9:33 am
by Shauing
Sweet; so if I understand correctly, with these findings the autosplitter will check for the potions per level (does this include the kill potion on level 18 or not?) and the number of guards per level as well. I assume that if you die, the counter for the potions already drunk and guards killed will reset?

Re: Disassembly of PoP1 SNES

Posted: November 26th, 2022, 1:54 pm
by David
Shauing wrote: November 20th, 2022, 9:33 am Sweet; so if I understand correctly, with these findings the autosplitter will check for the potions per level
(does this include the kill potion on level 18 or not?) and the number of guards per level as well.
Only if someone writes the code for that.
I only searched for the memory locations needed for these checks.
The code blocks in my previous posts just print a debug message when a guard dies, they don't count anything.
Shauing wrote: November 20th, 2022, 9:33 am I assume that if you die, the counter for the potions already drunk and guards killed will reset?
I have not yet looked into how to detect when the level is restarted.

By the way, how would these checks appear to the users of the autosplitter script?
As I see it, autosplitter scripts have limited interaction features.
They can tell LiveSplit the game time, tell when a level of the game ended, or reset the times.
They can also output debug messages which only developers can see.
But, unless I missed something, they can't directly display things on the GUI of LiveSplit.

Re: Disassembly of PoP1 SNES

Posted: November 26th, 2022, 11:45 pm
by Shauing
David wrote: November 26th, 2022, 1:54 pm By the way, how would these checks appear to the users of the autosplitter script?
As I see it, autosplitter scripts have limited interaction features.
They can tell LiveSplit the game time, tell when a level of the game ended, or reset the times.
They can also output debug messages which only developers can see.
But, unless I missed something, they can't directly display things on the GUI of LiveSplit.
As Livesplit lets you add splits for what the speedrunner needs them (an item, a location, a fight, a boss, just to mention a few examples), I myself was theorizing that, if the game kept track of the guards killed and potions drank, we could assign a split for each time it checks for when a guard is killed and a potion has been drunk, and/or at the end of each level if all the potions and guards have been drank/killed, also have a split there and continue with the first one for the next level.

For example:
Level 1 splits:
- Heal potion at room 8, tile 27
- Heal potion at room 12, tile 4
- Sword (if there's a check for it)
- Heal potion at room 23, tile 1
- Guard at room 8, tile 6
- Guard at room 1, tile 17
- Level exit

Or if it can't be specific due to the guards if they move, just check when they die:
- Heal potion
- Heal potion
- Sword (if there's a check for it)
- Heal potion
- Guard
- Guard
- Level exit

Re: Disassembly of PoP1 SNES

Posted: January 8th, 2023, 4:20 pm
by David
Shauing wrote: November 26th, 2022, 11:45 pm [...] if the game kept track of the guards killed and potions drank, we could assign a split for each time it checks for when a guard is killed and a potion has been drunk, [...]

For example:
Level 1 splits:
- Heal potion at room 8, tile 27
- Heal potion at room 12, tile 4
- Sword (if there's a check for it)
- Heal potion at room 23, tile 1
- Guard at room 8, tile 6
- Guard at room 1, tile 17
- Level exit
Here is what I came up with:
SNES PoP autosplitter 2023-01-08.zip
(6.53 KiB) Downloaded 44 times
So far it's only levels 1-2.

I figured out how to follow guards moving to other rooms.

Open questions:
  • What if the player does the actions in different order?
    Currently they won't register.
  • What if the player (dies and) restarts a level?
    Currently the current split remains the same, and all already done actions don't need to be done again.

Re: Disassembly of PoP1 SNES

Posted: January 13th, 2023, 10:44 pm
by Shauing
David wrote: January 8th, 2023, 4:20 pm
Here is what I came up with:
SNES PoP autosplitter 2023-01-08.zip

So far it's only levels 1-2.

I figured out how to follow guards moving to other rooms.

Open questions:
  • What if the player does the actions in different order?
    Currently they won't register.
  • What if the player (dies and) restarts a level?
    Currently the current split remains the same, and all already done actions don't need to be done again.
Sweet. I see the autosplitter uses in-game time, which could be rather useful for a more exact in-game time.

I tested it around to see if I could find something else, and I did find a couple of things:
- If the guard dies by falling down to a room below, the split will not register.
- If you finish the level even if you didn't do any of other splits for the level first (or use the level skip code), it will jump up to the current level exit split (of course one should not do that on a 100% run but just wanted to point out this; at least this can make testing other levels faster).

So about the open questions:
So I assume that if the player wants to do a different order, they might have to alter the script order to accommodate their routing (unless there's a way for the autosplitter to accommodate for different routing via maybe checking all the possibilities for a split per level and make it split automatically whenever one of them match).

It might be neat to have two autosplitters, one where all the already done actions don't need to be done again if you die (like this current autosplitter does), and another where you have to do everything again. The former though, might allow the possibility of getting everything in Level 11 via taking a death after getting everything in the bottom or top route first, as it won't restart.

Re: Disassembly of PoP1 SNES

Posted: January 21st, 2023, 3:15 pm
by David
Shauing wrote: January 13th, 2023, 10:44 pm Sweet. I see the autosplitter uses in-game time, which could be rather useful for a more exact in-game time.
By the way, I changed the timing method in the layout back from Game Time to Current Timing Method,
because I finally figured out how to select Game Time as the current. (right click -> Compare Against -> Game Time)
(Yeah, that's a silly thing to overlook...)
Shauing wrote: January 13th, 2023, 10:44 pm - If the guard dies by falling down to a room below, the split will not register.
Indeed, I forgot to check if the autosplitter detects that.
I hopefully fixed it now.
Shauing wrote: January 13th, 2023, 10:44 pm - If you finish the level even if you didn't do any of other splits for the level first (or use the level skip code), it will jump up to the current level exit split (of course one should not do that on a 100% run but just wanted to point out this; at least this can make testing other levels faster).
Yes, sorry, I indeed added that for testing and forgot to remove it.
Now I made it an option. It's off by default, but it's on in the included layout.
Shauing wrote: January 13th, 2023, 10:44 pm
David wrote: January 8th, 2023, 4:20 pm What if the player does the actions in different order?
So I assume that if the player wants to do a different order, they might have to alter the script order to accommodate their routing (unless there's a way for the autosplitter to accommodate for different routing via maybe checking all the possibilities for a split per level and make it split automatically whenever one of them match).
I added an option for the doing actions in any order. It's enabled by default.
If enabled, the autosplitter will recognize any action any time, but the splits display will advance only when the currently highlighted action is done.
(Because an autosplitter can't mark a later split as done, nor can it change the order of splits.)

So if the player does a later action in advance, nothing will happen in the splits list.
But when they do the previous action, the highlight will advance through all completed actions.
Shauing wrote: January 13th, 2023, 10:44 pm
David wrote: January 8th, 2023, 4:20 pm What if the player (dies and) restarts a level?
It might be neat to have two autosplitters, one where all the already done actions don't need to be done again if you die (like this current autosplitter does), and another where you have to do everything again.
How would "doing everything again" work?
Would it reset the splits to the very beginning (level 1), or to the beginning of the current level?
For resetting to level 1, do we want that?
For resetting to the current level, is that even possible?

Here is the updated version, now with splits for levels 1-4:
SNES PoP autosplitter 2023-01-21.zip
(7.89 KiB) Downloaded 45 times

Re: Autosplitter for PoP1 SNES 100%

Posted: January 28th, 2023, 9:51 pm
by David
Fixed a bug which caused the splitter to falsely detect a guard falling out of the screen, when the player merely left the room of a living guard.

Here is the updated version, now with splits for levels 1-9:
SNES PoP autosplitter 2023-01-28.zip
(9.04 KiB) Downloaded 53 times

Re: Disassembly of PoP1 SNES

Posted: January 29th, 2023, 9:58 am
by Shauing
David wrote: January 8th, 2023, 4:20 pm I added an option for the doing actions in any order. It's enabled by default.
If enabled, the autosplitter will recognize any action any time, but the splits display will advance only when the currently highlighted action is done.
(Because an autosplitter can't mark a later split as done, nor can it change the order of splits.)

So if the player does a later action in advance, nothing will happen in the splits list.
But when they do the previous action, the highlight will advance through all completed actions.
Great! I imagine if runners want to reorder their splits in a different order so that they can measure their splits more accurately (per action), they would have to change the order in the .asl file. Is cool though that later actions will be marked once the previous action is completed.
David wrote: January 8th, 2023, 4:20 pm How would "doing everything again" work?
Would it reset the splits to the very beginning (level 1), or to the beginning of the current level?
For resetting to level 1, do we want that?
For resetting to the current level, is that even possible?
If the player dies (or goes to game end), the actions completed for the current level could be remarked as not completed (if that's even possible). They could restart the level by going to game end and then select the password/continue option. This is only for the case of Level 11 that a heal potion is located at a point of no return which makes impossible to grab everything in one life, and Level 18's Kill Potion.
David wrote: January 8th, 2023, 4:20 pm Fixed a bug which caused the splitter to falsely detect a guard falling out of the screen, when the player merely left the room of a living guard.
Speaking of bugs, if the last skeleton at the end of Level 3 is killed by falling instead of killing it via the crusher, it won't register the death. This skeleton can be killed via falling by bringing him to the route of where the bottom heal potion is. I forgot if this method is faster than just throwing him into the crusher, but it's a possibility.

I'll be checking this new autosplitter and look for possible bugs/issues.

Re: Disassembly of PoP1 SNES

Posted: February 4th, 2023, 3:05 pm
by David
Shauing wrote: January 29th, 2023, 9:58 am If the player dies (or goes to game end), the actions completed for the current level could be remarked as not completed (if that's even possible).
I don't think it's possible to "partially reset" the splits from an autosplitter.
Here is a list of what's possible.

Though it's possible to manually undo a split (or multiple splits).
Shauing wrote: January 29th, 2023, 9:58 am Speaking of bugs, if the last skeleton at the end of Level 3 is killed by falling instead of killing it via the crusher, it won't register the death. This skeleton can be killed via falling by bringing him to the route of where the bottom heal potion is. I forgot if this method is faster than just throwing him into the crusher, but it's a possibility.
Oops, I didn't even realize that's possible!
I'll see what I can do.

Re: Disassembly of PoP1 SNES

Posted: February 4th, 2023, 8:36 pm
by David
David wrote: February 4th, 2023, 3:05 pm
Shauing wrote: January 29th, 2023, 9:58 am Speaking of bugs, if the last skeleton at the end of Level 3 is killed by falling instead of killing it via the crusher, it won't register the death. This skeleton can be killed via falling by bringing him to the route of where the bottom heal potion is. I forgot if this method is faster than just throwing him into the crusher, but it's a possibility.
Oops, I didn't even realize that's possible!
I'll see what I can do.
I think I solved this.

I figured out how to tell the difference between the skeleton falling into another room, and a guard (or the skeleton) falling offscreen and disappearing for good.
In the former case, the splitter now follows the skeleton, just like it already followed guards walking into another room.
In the latter case, the guard is counted as dead as before.

So whenever the skeleton dies either way, the splitter knows it's the skeleton which started in room 13.
Now that I think of it, it might have been easier to just check the type of the opponent, instead of following the skeleton through all the rooms. Oh well...

I also fixed a bug, wherein the splitter did not detect a guard falling offscreen, if he did not start in the same room where he fell offscreen.

Here is the new version:
SNES PoP autosplitter 2023-02-04.zip
(9.18 KiB) Downloaded 45 times