omnispeak
omnispeak copied to clipboard
Crash/unexpected behavior in Keen 6 when exiting level 2 via Impossible Bullet after level 1 has been loaded
A glitch commonly used to finish level 2 early in Keen 6 appears to crash Omnispeak, but only if level 1 has been loaded in the current game.
Steps to reproduce the crash scenario:
- Start a new game in Keen 6 (1.4 or 1.5 both appear to work).
- Enter Level 1 (Bloogwater Crossing) either from the world map or by F10+W warping.
- Exit Level 1 in any way (finish it, die and return to world map, F10+E, or F10+W warp directly to level 2.
- Enter Level 2 (Guard Post One) either from the world map or by F10+W warp.
- Immediately at the start of Level 2, hug the right edge of the map (a few pixels off is fine), then Impossible Bullet up, looking down as you fly up to hit the top edge of the map (the trick and expected outcome can be seen in this speedrun at about 0:47)
- Omnispeak terminates.
Note that if you enter level 2 without first having loaded level 1, the trick works and exits the level as expected.
Additionally, I was able to produce some other weird behavior (e.g. Keen teleporting to a completely unexpected part of level 2 instead of exiting the level) by substituting other levels for level 1 in the steps above, but these seem harder to consistently reproduce and most levels do not trigger a problem.
My guess is that Impossible Bulleting out of bounds in this level ends up reading something out of bounds of level 2's actual map data and runs into data left there by the previously loaded map. The outcome of this is probably different on DOS due to its different memory model, hence the trick reliably exiting the level on DOS vs. having unpredictable outcomes in Omnispeak depending on which level was loaded previously, the dimensions of that level, etc.
Tested with the Windows build from davidgow.net as well as my own win64 and win32 builds (FWIW, the Windows builds in the GitHub artifacts seem to crash upon entering a game for me).
Here is a playloop dump from the crash scenario, but I don't know if it tells you anything useful or manages to capture the frame that triggers the crash.
Interesting…
I can't actually reproduce the crash on my machine (it works fine with the Linux builds, as well as the windows builds under wine) — the glitch is working correctly whether or not I enter level 1 first.
That being said, valgrind gave me some out-of-bounds memory accesses in TI_ForeBottom():
==276343== Invalid read of size 1
==276343== at 0x117BA3: TI_ForeBottom(unsigned short) (id_ti.c:49)
==276343== by 0x1442EA: CK_PhysClipVert(CK_object*) (ck_phys.c:300)
==276343== by 0x14471B: CK_PhysUpdateNormalObj(CK_object*) (ck_phys.c:431)
==276343== by 0x145447: CK_SetAction2(CK_object*, CK_action*) (ck_phys.c:816)
==276343== by 0x12B039: CK_KeenDrawFunc(CK_object*) (ck_keen.c:910)
==276343== by 0x14C609: CK_PlayLoop() (ck_play.c:2269)
==276343== by 0x1481CA: CK_GameLoop() (ck_game.c:887)
==276343== by 0x1537CE: CK_DemoLoop() (ck_main.c:446)
==276343== by 0x153C94: main (ck_main.c:763)
It looks like the bug may involve integer underflow in the y coordinate in CK_PhysClipVert(), causing a whole row of invalid tiles to be checked. Making TI_ForeBottom() always return 0 for invalid tiles seems to not break the glitch itself, so I suspect that adding the appropriate checks to the tileinfo functions may work around the crash. In any case, I want to experiment a bit more before I try to fix this properly.
If you want, try adding something like this to all of the TI_Fore*()
functions in id_ti.c
:
if (tile > ca_gfxInfoE.numTiles16m)
return 0;
If that fixes the crash, then it's the tileinfo going out of bounds that's causing the actual. If it still crashes, I'd bet it's the level data going out-of-bounds (which I think is slightly less likely, and fixing that may break the glitch totally).
Cheers, — David
I was just coming back here to post that I found the same thing as you re. everything working fine on Linux when I got around to trying it there. I also realized upon looking at my Event Viewer logs that under Windows, Omnispeak is basically coredumping with an access violation, which comports with what you're seeing there in valgrind.
The change you recommended fixes the crash and that particular Impossible Bullet out-of-bounds clip now works as expected. There are some other out-of-bounds tricks used to finish the other two Guard Posts (levels 9 and 12) that I can't do consistently enough to say for sure whether they are affected.
Thanks again for the bug report. I've added the fix, and this works at the moment. I've also fixed the Windows GitHub artefacts builds.
However, when I spent some time looking at how we handle out-of-bounds map acesses (which cause this glitch), I've stumbled across a couple of problems. We're actually already trying to handle out-of-bounds map acesses by generating pseudo-random tile values, but those values can still be out-of-bounds. This is partly due to integer underflow and its interactions with the way the mod operator ('%') handles negative numbers. (Technically, we always allowed slightly out-of-bounds values.)
If I fix that, to only generate valid values, I'm losing the glitch. The very similar Keen 4 Lifewater Oasis top-of-screen glitch still works, though its behaviour is slightly different. Even the Guard Post 1 glitch still can be made to work by running into the right-hand side of the screen instead of looking down.
Given these are important-enough glitches, I'll look into a better way of generating random tile values to see if I can find some to reproduce the glitch without having actually invalid tile values being passed through the code. In the meantime, though, this should work.
(I haven't been able to reproduce the other Keen 6 glitches you mentioned, but I can't manage to do them on the real Keen 6 either, so that's on me.)