Thanks for telling me this! I thought SGB was supposed to be emulated completely under SNES mode. Duh.
I tried the very first clipping for both SGB mode and monochrome mode, and they work the same. The GBC version doesn't behave differently under SGB mode ether. Therefore the conclusion is that SGB mode does not affect the layout of a glitch hell in a rom.
I was recently asked about this game in terms of "All Stages"/"100%" routing. Some discoveries as follows:
1. Secret doors in final boss stages count as boss defeated. Normal exits count as next level.
2. Stage ID addresses are 0x1510 (GBC) or 0x0510 (GB).
3. With respect to the above, this means if you can find a secret exit in the robot spear man stage (ID 29), you can trigger the credits. Beating it via normal exit OoB advances to ID 30 (Chapter 2 basement)
So with that said, from the submission text:
I can now say, sorry, me and mugg remembered it wrong.
4. If you tried the trick at the factory final stage (ID 49), you can advance to the Secret stage. If you managed to beat the secret stage, this occurs:
Then, you're stuck forever.
However, if you tried it AFTER beating the game once on the stage select, this happens instead:
5. Factory Final GBC OoB is mostly unbreakable blocks below the stage, and unreachable glitch blocks at the top. Even attempting to use the hammer NPC to get the bouncy status fails, since there's a ceiling of unbreakable blocks.
In GB, the glitched blocks are much lower, thus a secret exit (and trigger credits) can easily be reached
6. The same situation almost occurred with ID 29, but luckily there's an NPC that allowed you to reach an opening.
The closest normal exit I found in GBC was:
https://cdn.discordapp.com/attachments/688394402472788033/689339903649120267/Wario_Land_II_USA_Europe_exit.bk2
And for 100%:
https://cdn.discordapp.com/attachments/688394402472788033/689375423548882984/Wario_Land_II_USA_Europe_treasure__boss.bk2
This input file finds an OoB bonus door, then immediately goes to the boss room in the first room. While that is normally slower than using exit door, since getting the level select as fast as possible was apparently quicker to skip cutscenes, this may help.
7. I managed to find a workable OoB exit in the latest BizHawk version in GB for Chapter 2 To The Castle (ID 25):
In GBC, this completely breaks apart, since the red blocks vanish shortly after touching them from the sides or above.
8. For both "Defeat the giant spear man" and "Go through the grand hall" (IDs 27, 28), I found OoB bonus rooms right near a exit in GBC. Need to test GB as well.
9. The original script only works for GBC. After messing around, it appears the layout is similar, but with different addresses and IDs. So I changed it (among other things) to include GB support:
Download WL2Overlay.lua
Language: lua
local x, y, camx, camy, tref, ttype, ttypehigh, tcolor, warpdest
local address = {
gbc = {
x = 0x153c,
y = 0x153a,
camx = 0x660,
camy = 0x65f,
tref = 0x704,
warpdest = 0xa1,
stageid = 0x1510
},
gb = {
x = 0x053c,
y = 0x053a,
camx = 0x1652,
camy = 0x1651,
tref = 0x16EB,
stageid = 0x0510
}
}
local tiles = {
gbc = {
[0x47bc] = "Switch platform",
[0x49cb] = "Slideable slope (left)", --Press down to roll
[0x495e] = "Slideable slope (right)", --Press down to roll
[0x4a10] = "Platform",
[0x4a33] = "Platform (NPC)", --NPCs can walk on them, you cannot
[0x4a70] = "Breakable", --(ttype>0x4a70) and (ttype<0x4d9f) or (ttype==0x4a10)
[0x4d9f] = "Breakable",
[0x4da0] = "Breakable (NPC)", --throw npcs to destroy them
[0x4e8a] = "Water",
[0x4fbe] = "Minigame", --varies by stage
[0x4ecd] = "Minigame", --varies by stage
[0x4edb] = "Door",
[0x4ef6] = "Boss door", --varies by stage
[0x4f3a] = "Exit",
[0x4f60] = "Secret exit",
[0x4ffb] = "Switch"
},
gb = {
[0x47bc] = "Switch platform",
[0x49cb] = "Slideable slope (left)",
[0x495e] = "Slideable slope (right)",
[0x4a10] = "Platform",
[0x4a33] = "Platform (NPC)", --NPCs can walk on them, you cannot
[0x4a70] = "Breakable", --(ttype>0x4a70) and (ttype<0x4d9f) or (ttype==0x4a10)
[0x4d9f] = "Breakable",
[0x4d95] = "Breakable (NPC)", --throw npcs to destroy them
[0x4d99] = "Breakable (NPC)", --throw npcs to destroy them
[0x4e83] = "Water",
[0x4fb7] = "Minigame",
[0x4ed4] = "Door",
[0x4eef] = "Boss door",
[0x4f33] = "Exit",
[0x4f59] = "Secret exit",
[0x4cef] = "Breakable (Invisible)",
[0x4ff4] = "Switch",
[0x50fb] = "Water"
}
}
function bitswap (swappy)
nib2 = bit.band(swappy,0xF)
return (nib2*0x10+bit.rshift(swappy,4))
end
function gettile (wx,wy)
hix = bit.rshift(bit.band(0xFF00,wx),8)
hiy = bit.rshift(bit.band(0xFF00,wy),8)
lox = bit.band(0xFF,wx)
loy = bit.band(0xFF,wy)
ccea = bit.band(bitswap(bit.band(hiy,0x0F))+bit.band(bitswap(loy),0x0F)+0xA0,0xFF)
cceb = bitswap(bit.band(hix,0x0F))+bit.band(bitswap(lox),0x0F)
rawloc = ccea*0x100+cceb -- not the final location!!! can vary if above 0xa000
return (rawloc)
-- return (bit.band(0x2000 + 0x100*math.floor(wy/16+1) + math.floor(wx/16),0x7FFF)) works for most space, not glitch rooms
end
function tileid (ntile)
if (ntile >= 0xa000) then
memory.usememorydomain('CartRAM') -- where normal level data is
realloc = ntile - 0x8000
else
memory.usememorydomain('System Bus')
realloc = ntile
end
tlookup = memory.readbyte(realloc)
memory.usememorydomain('ROM')
local address = 0x7c002+tref+bit.band(tlookup*2,0xFF)
local result = memory.read_u16_le(address)
-- -- if result == 0x4f60 then
-- if result == 0x4f3a then
-- -- memory.write_u16_le(address,0x4f3a)
-- memory.write_u16_le(address,0x4f60)
-- end
return result
end
local game_address = address.gbc
local game_tiles = tiles.gbc
while true do
x = mainmemory.read_u16_be(game_address.x) -- position in level
y = mainmemory.read_u16_be(game_address.y)
camx = mainmemory.readbyte(game_address.camx) -- position relative to upper left camera edge
camy = mainmemory.readbyte(game_address.camy)
tref = mainmemory.read_u16_be(game_address.tref)
-- warpdest = mainmemory.readbyte(game_address.warpdest) -- sector coordinates for a warp (??)
-- 160x144
-- gui.drawText(3,130,string.format("%X",bit.rshift(warpdest,4))..' '..string.format("%X",bit.band(warpdest,0xF)))
for i = -1,17,1 do
for j = -1,17,1 do
ttype = tileid(gettile(x-camx+15+16*i,y-camy+15+16*j))
ttypehigh = bit.band(ttype,0xFF00)
if (ttype~=0x47ab) and (ttype~=0x49a7) and not ((ttype>=0x4e29) and (ttype<=0x4e39)) and not ((ttype>=0x5400) and (ttype<=0x54ff)) then
--if (ttype~=0x47ab) and (ttype~=0x4cf3) and (ttype~=0x4cef) and (ttype~=0x4d03) and (ttype~=0x4cff) and (ttype~=0x4e29) and (ttype~=0x4e35) and (ttype~=0x4f3a) then
-- if (ttype==0x4ecd) or (ttype==0x4edb) or (ttype==0x4f3a) or (ttype==0x4f60) then -- door, minigame, exit
-- tcolor = 'GREEN'
if game_tiles[ttype] ~= nil then
if (game_tiles[ttype]=="Door") then --Regular door
tcolor = 'BLACK'
elseif (game_tiles[ttype]=="Boss door") then --Boss door
tcolor = 'GREEN'
elseif (game_tiles[ttype]=="Minigame") then -- Minigame
tcolor = 'PURPLE'
elseif (game_tiles[ttype]=="Exit") then -- exit
tcolor = 'CYAN'
elseif (game_tiles[ttype]=="Secret exit") then --secret exit
tcolor = 'GOLD'
elseif (game_tiles[ttype]=="Water") then -- water
tcolor = 'BLUE'
elseif (game_tiles[ttype]=="Platform") then -- platform
tcolor = 'WHITE'
elseif (game_tiles[ttype]=="Platform (NPC)") then -- platform
tcolor = 'GREY'
elseif (game_tiles[ttype]=="Slideable slope (left)") or (game_tiles[ttype]=="Slideable slope (right)") then -- platform
tcolor = 'BROWN'
elseif (ttype>0x4a70) and (ttype<0x4d9f) or (ttype==0x4a10) then -- breakable
tcolor = 'PINK'
elseif (game_tiles[ttype]=="Breakable (NPC)") then
tcolor = "DEEPPINK"
else -- solid or unknown
tcolor = 'RED'
end
end
gui.drawBox((camx-x)%16-8+16*i,(camy-y)%16-16+16*j,(camx-x)%16+7+16*i,(camy-y)%16+16*j-1,tcolor)
end
end
end
gui.drawText(3,3,string.format("%X",gettile(x,y-32))..' '..string.format("%X",tileid(gettile(x,y-32))))
gui.drawText(3,12,string.format("%X",gettile(x,y-16))..' '..string.format("%X",tileid(gettile(x,y-16))))
gui.drawText(3,21,string.format("%X",gettile(x,y))..' '..string.format("%X",tileid(gettile(x,y))),"BLACK","BLACK")
gui.drawText(4,22,string.format("%X",gettile(x,y))..' '..string.format("%X",tileid(gettile(x,y))))
gui.drawText(3,31,"X:"..x.." Y:"..y,"BLACK","BLACK")
gui.drawText(4,32,"X:"..x.." Y:"..y,"WHITE")
emu.frameadvance()
end
Original script: http://tasvideos.org/userfiles/info/16322306121342073
Thanks very much for Slamo for the initial version!
10. For floating OoB doors, you need swimming status to enter them
11. Last night, when exploring Factory Final (ID 49) on GB, the glitched area was solidish, and easily reachable:
Attempting to record it from start of stage however, changed it into this:
Which made the glitched area much harder to reach. I have no idea what changed the layout, since the 1st attempt was from a savestate, not start of stage.
Lots of very interesting things and funny to see how broken this game can be. Are you working on a 100% run or do you planning to do this on the futur ? And would it be better to run it on GBC or GB for faster execution ?
GBC has a different OoB layout; for instance the glitched area in 2-2 in the current TAS still works in GB in BizHawk, but doesn't in GBC. However, certain tricks that do work in GBC (such as the one mentioned in the post above with bonus room leading to final boss room, and similar tricks in the 2 stages before it) can't seem to be done in GB (yet), or is slower. So might need to check every slow stage to see what is faster.
Not helped by the fact it seems the OoB layout sometimes changes in the same room, in the same game version. I just edited the post from before, but it seems in GB Factory Final, the OoB can be either extremely easy to reach, or mostly out of reach like in GBC, and I have no idea what caused the change.
Unrelated, but messing around while not recording changed all the bees into skull in 2-2:
I can't seem to replicate it however.
While routing for 100%, I asked TiKevin83 to check for if the OoB in this game was emulated right by given them input files for any% improvement. An input file that did not soft reset the game synced back, but the one that did use a soft reset to alter OoB layout did not. Since the Spearman stage for 100% needs reset to reach the OoB exit after obtaining the treasure, I'm uncertain if the route works on console. I gave him the input file, but since he's busy, I decided to work on the OoB-less run instead.
With that said, I rediscovered that from old posts, there's a 724 frame improvement to the previous TAS that does not use OoB exits. Given the links to the WIPs are dead, I decided to resync the current run (up to last boss, because diff rng ruined it) and see if the improvement could be refound:
http://tasvideos.org/userfiles/info/63000414314992766
Edit: 879 frames improved, mostly from using inputs from the latest run, + wallclip savings mentioned by MUGG.
https://youtu.be/-siFfVFZxUYhttp://tasvideos.org/userfiles/info/63043461467067961
Edit2: Improved 7 more frames by cancelling 1st dash jump instead of crouching in the giant spearman room with frostie
Also the 4 frame rule between transitions also affect the minigame screens. Since the number for the end stage minigame is determined at start of minigame, this means it can only be changed once every 4 frames
Edit 3: I investigated avoiding coins for the any%. Every time you have less than 50 coins you save 56 frames at the number guessing minigame since it just gives you a message "You have no coins" that can accept input sooner.
Stage 1 (One Noisy Morning) 0 coins
Stage 25 (To the Castle) 4 coins (3 from 1st screen, 1 from OoB)
Stage 26 (Storm the Castle) 45 coins
Stage 27 (Defeat the Giant Spearman) 1 coin (OoB)
Stage 28 (Through the grand hall) 19 coins, assuming 0 from blocks
Stage 29 (Kick em out!) varies, way more than 50 due to OoB
https://docs.google.com/spreadsheets/d/1XevsUFWknCozzJ7IRKbHigPpSeJnk5FJFZm4lNmFnjM/edit?usp=sharing
This means the main issue is stage 26. From the spreadsheet, I timed every screen in this stage. This was done on GBC instead of GB, since there was no lag in GBC, but the results apply as well.
Compared to ignoring coins, it seems to be fastest is skip 4 coins at beginning, then avoid as much coins as possible reset of stage. This will give 25 coins.
0 + 4 + 25 + 1 + 19 = 49 coins by 2nd last stage.
Last stage coins are unavoidable, so ignoring that.
This takes an additional 49 frames for 2-2, but saves 56 frames per end level for the next 2 stages, so it's a gain.
I wasn't going to post, but:
https://cdn.discordapp.com/attachments/280806848909541376/750422414109704350/Wario_Land_II_100_GBC.bk2
I decided to compare it with the 100% TAS that currently exists, and discovered that the 1st room of 2-1 (Stage ID 25) was slower than the existing run by 1 frame. Due to the 4 frame rule, this doesn't affect the run since it ends up taking the same amount of time to fade out.
However, when I tried to replace that particular room's input with the current published run's input to make it neater, I discovered the run the run desyncs next stage due to different RNG, despite everything else syncing:
Left: The WIP I posted.
Right: The same, except the 1st room of 2-1 has been changed.
Given I even got a different coin drop amount, this suggest it's possible to manipulate silver coins along with the minigame results without losing too much time by doing different things slower (while still within the 4 frame window) on a previous stage.
I do not know the RNG addresses for the game however, so the use of this might be limited to manual testing. Also I'm not changing anything before the robot spearman because I hate that boss.
Also for future reference on how I approached the boss:
See this antenna on top? It cycles through its animation every 12 frames or so. Note the fuel at start of cycle
If the fuel counter goes down every frame, dont press anything unless you have to. If it doesn't, try pressing buttons on TAStudios. Note the fuel counter by the start of the next cycle
In this case, it dropped by 3. Save. Then try other inputs & see if you can get the fuel counter lower at the same frame (eg. 252 instead of 253). Do this for every single cycle; it makes it more manageable to compare progress. Try to match the previous run's fuel in terms of it's antenna animation
These are the "milestone" points I aim for +/- 2. The bottom right panel is in the middle of a cycle, but if the robot stops 1 pixel earlier I wont be able to hit it 3 times.
The fuel for the top left in the previous run was 215 instead, but as long as you're +/- 2 from that you can make it
Afterwards you need to manipulate it such that the last 4-10 units of fuel ALL deplete with no delay, or else you won't be able to charge early. Failing to do so allows the robot to replenish their HP
When the robot is at the right hand side of the screen, you can move around a bit w/o affecting fuel drop rate. Be sure to position to somewhere close to the bottom left panel (1872 X or so) during this brief downtime
Joined: 4/2/2017
Posts: 13
Location: TASVideos.org, of course...
I suppose this could warrant a post.
Basically, I discovered(?) that you can suspend dash "storage"/wall clip state by sliding (I didn't exactly discover it, since it's a known trick in WL3, but I also sincerely doubt that I was the first to find it in this game either). This means that it is possible to clip out of bounds in the far right of the second room of the Really Final Chapter. Due to how the level is laid out in ROM, this allows us to get inside the tile data for the 5th room and enter a door to the 6th, skipping a couple rooms along the way and eliminating the need to deal with the quite slow 4th room, saving 3 in-game seconds from mugg's TAS.
User movie #637807299845550294Link to video
Another thing to note is that slide-buffering a clip makes finding wall clip positions significantly easier than fishing for a good lineup with the dashjump method (although maybe "easier" isn't what we want here since this method is not as direct or fast). It also allows clipping into spaces where there is not room to dashjump (an example that comes to mind is the tiny Wario room in 5-2 Storm the Castle, and I think possibly the top of the "bee bounce" room in 3-4).
I also found a way to get a wall clip with only 1 tile of walking space, similar to Wario Land 3's turnaround wall clips used in N4 in the Any% route. I don't know if there is any reason to use a trick like this, but I still made a quick movie just in case:
User movie #637807399158871261
And here is one last wall clip style that I don't think I've seen in this game yet — water clips (technically, they're water-buffered clips, since the actual clipping happens in the air still, but I'm just calling them what the WL3 community calls them):
User movie #637807409719924707
I'd be curious to see how any of these could be used in a full level TAS, although the last two that I shared may be too specific of use cases. Who knows?
Those are all amazing! In the GB 100% OoB run I'm making, almost every stage is basically
1st Room -> Bonus -> OoB exit
A better way to easily clip out would definitely help.
Additionally, some ideas for GBC in bounds:
1. "Kick em Out!": Instead of dropping down after the minigame, use the 1 tile clip method to get up?
2. "Storm the castle!": Is there any way to prevent backtracking after the minigame?
3. "Go down the cellar": No idea, but right now, the last room seems unbreakable. Not sure if there's a way to prevent getting crushed at the end.
The following applies to gameboy. I found out that when you ground pound in certain areas below the stage, you can crash the game. I checked why, and it was because it jumped to 0xE200
0xC504, and thus 0xE504 is the treasure flags. So there's a small spot to manipulate for a credits warp. Specifically:
C503 Amount of times saved
C504 Treasure flags for stages 0-7
C505 Treasure flags for stages 8-15
C506 Treasure flags for stages 16-23
C507 Treasure flags for stages 24-31
C508 Treasure flags for stages 32-39
C509 Treasure flags for stages 40-47
C50A Treasure flags for stages 48-49 (only bits 0,1 possible)
C50B Coins bank (Displays decimal value as a hex value. eg. 40 coins in decimal becomes 0x40)
C50C Coins bank
C50D Coins bank
C50E Coins overworld (Displays decimal value as a hex value. eg. 40 coins in decimal becomes 0x40)
C50F Coins overworld
C510 Stage ID (Displays decimal value as a hex value. eg. Stage 10 in decimal becomes 0x10)
C511
C512 Stage select flag (0 or 1)
C513 Puzzle flags for stages 0-7
C514 Puzzle flags for stages 8-15
C515 Puzzle flags for stages 16-23
C516 Puzzle flags for stages 24-31
C517 Puzzle flags for stages 32-39
C518 Puzzle flags for stages 40-47
C519 Puzzle flags for stages 48-49 (only bits 0,1 possible)
I couldn't figure out what to do to trigger the credits and avoid a crash however. I did find the following:
0xC004 - cutscene id
0 - none (skips cutscene)
1 - stage 0 (1-1)
2 - stage 5 (2-1)
3 - stage 10 (3-1)
4 - stage 15 (4-1)
5 - stage 20 (5-1)
6 - stage 25 (secret 2-1, you fell asleep)
7 - stage 30 (secret 2-1, you didnt fight snake)
8 - stage 35 (secret 3-1, you sunk the ship)
9 - stage 40 (mansion)
10 - stage 45 (factory)
11 - stage 50 (true final)
12 - crash
0xD6F3 - ending flag. If this is set to 1, and the ending id is a valid value, the ending is triggered at stage end.
0xD70A - ending id
0 - nothing
1 - normal 5-5
2 - secret 2-5 (you fell asleep)
3 - secret 3-5 (underwater)
4 - secret 5-5 (mansion)
5 - secret 5-5 (factory)
6 - true final
7 - reset game
I know it's impractical time wise to set up, but it's a different route that uses ACE, so I'm still interested in it.
Edit: 0xC000 to 0xFFFF area
https://docs.google.com/spreadsheets/d/1x0ahgWoO948yNwCFYOYbA7bYH6OOarJgoSSVkF_hyB0/edit?usp=sharing
most of it is undocumented however.
Edit 2:
ok
C503
Number of times saved
0x3C
inc a
set a from 0 to 1; assumes a was 0
C504
Treasure flags for stages 0-7
0xEA
ld [$D70A], a
loads ending 1 to ending id
C505
Treasure flags for stages 8-15
0x0A
C506
Treasure flags for stages 16-23
0xD7
C507
Treasure flags for stages 24-31
0x3E
ld a, $0B
loads 0x0B to register A
C508
Treasure flags for stages 32-39
0x0B
C509
Treasure flags for stages 40-47
0x00
nop
C50A
Treasure flags for stages 48-49 (only bits 0,1 possible)
0x00
nop
C50B
Coins bank
0x00
nop
C50C
Coins bank
0x18
jr $05
jump to C513 to avoid stage select opcode 0x01
C50D
Coins bank
0x05
nop
C50E
Coins overworld
0x00
nop
Skipped
C50F
Coins overworld
0x00
nop
Skipped
C510
Stage ID
?
Varies
Skipped
C511
0x00
nop
Skipped
C512
Stage select flag
0x01
ld bc, _ _ _ _
Skipped
C513
Puzzle flags for stages 0-7
0xEA
ld [$D6D8], a
Changes game state to 11
C514
Puzzle flags for stages 8-15
0xD8
C515
Puzzle flags for stages 16-23
0xD6
C516
Puzzle flags for stages 24-31
?
?
Remaining bytes for avoiding crash
C517
Puzzle flags for stages 32-39
?
?
C518
Puzzle flags for stages 40-47
?
?
C519
Puzzle flags for stages 48-49 (only bits 0,1 possible)
?
?
I think C3 00 15 allowed me to survive a jump to WRAM. Additionally, if I can jump to C000, the area before, specifically C200, is related to the minigame. Maybe some way to get a payload there?
I am still working on a run for this. The current payload idea is
C503
Number of times saved
-
Varies
I'm not going to save/reset repeatedly; ignoring this.
C504
Treasure flags for stages 0-7
0x3E
ld a, $06
loads 6 (real final chapter ending) in to register A
C505
Treasure flags for stages 8-15
0x06
C506
Treasure flags for stages 16-23
0xEA
ld [$D70A], a
loads ending 6 to ending id
C507
Treasure flags for stages 24-31
0x0A
C508
Treasure flags for stages 32-39
0xD7
C509
Treasure flags for stages 40-47
0x00
nop
C50A
Treasure flags for stages 48-49 (only bits 0,1 possible)
0x00
nop
C50B
Coins bank
0x00
nop
C50C
Coins bank
0x18
jr $05
jump to C513 to avoid stage select opcode 0x01
C50D
Coins bank
0x05
C50E
Coins overworld
0x00
nop
Skipped
C50F
Coins overworld
0x00
nop
Skipped
C510
Stage ID
?
Varies
Skipped
C511
0x00
nop
Skipped
C512
Stage select flag
0x01
ld bc, _ _ _ _
Skipped
C513
Puzzle flags for stages 0-7
0xC2
jp $4000
jumps to somewhere not crash
C514
Puzzle flags for stages 8-15
0x00
C515
Puzzle flags for stages 16-23
0x04
C516
Puzzle flags for stages 24-31
0x00
nop
C517
Puzzle flags for stages 32-39
0x00
nop
C518
Puzzle flags for stages 40-47
0x00
nop
C519
Puzzle flags for stages 48-49 (only bits 0,1 possible)
0x00
nop
This means the following stages are needed :
0
One Noisy Morning
Turn off the alarm clock!
1
One Noisy Morning
Turn off the giant faucet!
2
One Noisy Morning
Let the water out!
3
One Noisy Morning
Go down to the cellar
4
One Noisy Morning
Defeat the giant snake
5
SS Tea Cup
Return the hen to her nest
6
SS Tea Cup
Escape from the woods!
7
SS Tea Cup
Get in the Tea Cup
8
SS Tea Cup
Drop the anchor!!
9
SS Tea Cup
Defeat Bobo!!
10
Maze Woods
Get to Maze Woods
15
In Town
Stop that train!
16
In Town
Up on the rooftop!!
17
In Town
Down in the cellar
18
In Town
Escape from the factory!
19
In Town
Anyone for B-ball?
20
Syrup Castle
Get to the Castle!!
21
Syrup Castle
Storm the castle!!
22
Syrup Castle
Defeat four ducks!
23
Syrup Castle
Find the hidden door!!
25
Invade Wario Castle
To the castle!!
26
Invade Wario Castle
Storm the castle!!
27
Invade Wario Castle
Defeat the giant spear man
28
Invade Wario Castle
Go through the grand hall
30
Go to the cellar!
Defeat the giant spear man
31
Go to the cellar!
Avoid the rocks!
32
Go to the cellar!
Stop that train!
33
Go to the cellar!
Find the exit!!
34
Go to the cellar!
Defeat the cave master!!
35
Ruins at the Bottom of the sea
Escape from the Tea Cup!
36
Ruins at the Bottom of the sea
Defeat the giant spear man
37
Ruins at the Bottom of the sea
Inside the ruins!
38
Ruins at the Bottom of the sea
Escape fromt the ruins!
39
Ruins at the Bottom of the sea
Captured Syrup!
I end the game at stage 28 below the stage, where a certain block when I swim at jumps to C000, then nops to C503. This sets the ending ID to 6, but more importantly, doesn't crash the game. I then need to beat the stage somehow, then the final ending plays. It would be around 7 times longer than beating the game normally (at least 33 stages, some repeats to unlock alt paths), but would be game end glitch.
Notes:
* Technically, I could've shorten the number of stages used by makign C503 be 0x3E, then shifting the payload up by 1 byte, allowing me to skip stages 32-39. Then get C509 to be 0xC3, have C50A be 0, and C50B to be 04, for the final jump, but that would need 62 (0x3E) saves, and 4000 coins. I save at least 33 times, so that leaves the run save/reset another 30 times. I would also need to manipulate multiple silver coins every single stage (they give 100, but I use 50 for treasure in some stages).
* Right now, the path reaches the stage select screen using stage 39 Captured Syrup!. I could technically reach that using underground by beating stage 34 OoB, which if I haven't unlocked stage select yet, will take me to stage 35 Escape from the Tea Cup!. However, after unlocking stage select, I would be blocked from going back, since I never beaten stage 8 (Drop the anchor!!)
* An alternate path, using the exact same payload above, would be to do what the any% does and unlock stage select using stage 29 (Kick 'em out!), then doing that again to unlock underground. Then beat stage 34 (Defeat the cave master!!) OoB to unlock underwater. This skips beating stage 4 (Defeat the giant snake) and stage 8 (Drop the anchor!!) twice, but at the cost of doing 2 extra stages (28, 29) + I'm forced to see stage select screen. But it also allows me to skip cutscenes.
* The OoB area is harder to reach if you haven't unlocked stage select. A number of stages have the section 1 tile above reach, so I have to either reset, or find some innovative way to get up. This also meant the 100% GB input file wasn't as helpful as I thought.
Exploit code constructed out of treasure and puzzle flags, that's amazing :) It's not every day you have to think about the Hamming weight of your shellcode. I'm enjoying reading about this.
If the contents of the a register are predictable, it may be possible to replace ld a, $06 (3e06) with add a, $XX (c6XX), where XX is the difference between 0x06 and the value of a. 0xc6 saves one bit relative to 0x3e, but whether it requires fewer treasures overall depends on what XX is.
I see why the contents of 0xe504 are the same as 0xc504—that's echo RAM. But how does the program counter get from 0xe200 to 0xe504? Is it all NOPs in between?
Thanks for the comment!
https://tasvideos.org/UserFiles/Info/638320581598112959
I think this was the input file that had it.
I meant C200, sorry. There's a section in memory around there that seems to be based on the tiles in the treasure/number guessing minigame.
I would love to somehow get 3E 06 to appear there, but I don't think I succeeded. I do not know if register A would be predictable, so I had 3E 06 just to at least get a run working first.
https://tasvideos.org/UserFiles/Info/638616165666042151Link to video
I wasn't able to replicate the trigger from my previous input file, so I found a new spot right below an exit tile that jumps to 0xC000 once you ground pound on it. The game then jumps back to the main loop, and I beat the stage with ending id set to 6. This plays the real final chapter ending credits after saving.
This took 35 minutes, so it is completely unpractical for any%. It is also slower than just reaching stage 49 (Awaiting Syrup!) and beating it using an OoB exit to reach the real final stage, since that takes 22 stages, rather than 35+ here. It does execute arbitrary code via stage flags, so I hope someone finds a better way than beating half the game + actually beat the game at least once to jump to credits.
I wasn't able to replicate the trigger from my previous input file, so I found a new spot right below an exit tile that jumps to 0xC000 once you ground pound on it. The game then jumps back to the main loop, and I beat the stage with ending id set to 6. This plays the real final chapter ending credits after saving.
This is awesome! I'm impressed. The disassembly visualization in the video is great, too.
An alternative to writing the instructions you need would be to find a place where those instructions already exist in the ROM, and jump to them. (Something like finding a "gadget" in return-oriented programming.) There is a sequence in the ROM at address 0x00264b0e that is tailor-made, ld a, 0x06; ld [0xd70a], a, followed by a ret:
264afa: 3e01 ld a, 0x01
264afc: 1816 jr 0x16
264afe: 3e02 ld a, 0x02
264b00: 1812 jr 0x12
264b02: 3e03 ld a, 0x03
264b04: 180e jr 0x0e
264b06: 3e04 ld a, 0x04
264b08: 180a jr 0x0a
264b0a: 3e05 ld a, 0x05
264b0c: 1806 jr 0x06
264b0e: 3e06 ld a, 0x06
264b10: ea0ad7 ld [0xd70a], a
264b13: c9 ret
264b14: ea0ad7 ld [0xd70a], a
264b17: cd9e06 call 0x069e
264b1a: c9 ret
Instead of encoding your own ld a, 0x06; ld [0xd70a], a (5 bytes), you could do a call 0x4b0e (3 bytes). Or even jp 0x4b0e: maybe you'll get lucky and there will be a non-crashing return address on the stack for the ret to return to.
However, I tried dumping memory at the time when the game starts executing 0xc000, and it looks like the above code is not mapped at that point. (At least I think—I don't know Game Boy architecture that well.) So this idea may be a dead end.
I looked at an instruction trace of User movie #638616165666042151. I traced things back as far as where the game starts executing some data as instructions, until it hits an 0xff opcode (which is rst $38) at 0x4cb3. The code starting at 0x0038 is just NOPs, so execution falls through to the vblank interrupt handler at 0x0040. The vblank interrupt handler returns with a reti instruction, returning to the address 0x4cb4, which another 0xff rst $38 instruction, so the vblank interrupt handler gets called again, returns again, and continues at 0x4cb5. What we get is a long sequence of instructions, most of which are 0xff, which means that the code repeatedly invokes the vblank interrupt handler, punctuated by a few other instructions, until finally a long buffer of 0xff bytes ends at 0xc000.
Stripping out the code of the jumping into and returning from the interrupt handler, what we have is this:
The trace that leads to the first execution of 0xff rst $38 is strange. It looks like two separate pieces of code that have been mashed together. Here is the trace that leads to the execution of 0x4cb3:
The trace stops making sense as code at 0x4ca9 rlca. What it looks like, to me, is that the instructions before that point come from one part of the ROM, and the instructions after that come from a different part of the ROM.
Here's the code that starts at 0x1f4ca1 in the ROM. Notice how it agrees with the above trace up through 0x1f4ca8 ld [hl], a, and disagrees thereafter:
1f4ca1 2a ldi a, [hl]
1f4ca2 6e ld l, [hl]
1f4ca3 67 ld h, a
1f4ca4 7e ld a, [hl]
1f4ca5 e680 and 0x80
1f4ca7 b0 or b
1f4ca8 77 ld [hl], a
1f4ca9 e5 push hl
1f4caa cd181e call 0x1e18
1f4cad e1 pop hl
1f4cae cdd34d call 0x4dd3
1f4cb1 fa79d6 ld a, [0xd679]
And here is the code (actually looks more like data) that starts at 0x374ca1 in the ROM. Notice how it agrees with the above trace after 0x374ca9 rlca, but disagrees before that:
374ca1 0d dec c
374ca2 09 add hl, bc
374ca3 0a ld a, [bc]
374ca4 0604 ld b, 0x04
374ca6 04 inc b
374ca7 84 add h
374ca8 02 ld [bc], a
374ca9 07 rlca
374caa 0d dec c
374cab 09 add hl, bc
374cac 03 inc bc
374cad 0f rrca
374cae a1 and c
374caf 04 inc b
374cb0 3f ccf
374cb1 7f ld a, a
374cb2 7f ld a, a
374cb3 ff rst 0x38
Again, I don't know much about the Game Boy architecture, but what it looks like to me is, the game had 0x1f4cxx mapped in, and while that code was executing, 0x374cxx got mapped in on top of it. Is that possible?
However, I tried dumping memory at the time when the game starts executing 0xc000, and it looks like the above code is not mapped at that point. (At least I think—I don't know Game Boy architecture that well.) So this idea may be a dead end.
The trace stops making sense as code at 0x4ca9 rlca. What it looks like, to me, is that the instructions before that point come from one part of the ROM, and the instructions after that come from a different part of the ROM.
Here's the code that starts at 0x1f4ca1 in the ROM. Notice how it agrees with the above trace up through 0x1f4ca8 ld [hl], a, and disagrees thereafter:
Switching to a ROM Bank < $20
Switching to banks $01-$1F is very simple. We only need to write our intended bank to $2000-$3FFF. Here we are switching to bank $05:
ld $2000, $05
; Now able to read data from bank $05
In this case, HL was $2071, so the memory right afterwards points to a different location after execution.
That actually sounds like a good idea, if you could somehow get the rom bank where ld a, 0x06; ld [0xd70a], a is. The earlier parts of the memory before C500 changes from the minigames (the one where you guess the npc in the card, and the one you guess the number). I don't think I managed to get much out of it though. I recall at least 2 bytes, separate from each other, act as a timer that can have any value from 00 to FF, but I'm not sure if it would be useful.
Another idea I had was get register A to shift right 6 times somehow using the minigame values, but I don't know if that is possible.