[Research] [th01] Find out why defeating bosses with lasers can crash the game

Brought to you by diagonal movement and the combined effect of 4 ZUN
bugs. Most commonly reported for Elis and Mima.

Part of P0197, funded by Yanga and Ember2528.
This commit is contained in:
nmlgc 2022-05-31 07:05:43 +02:00
parent b165871ece
commit 9cd1b04894
4 changed files with 41 additions and 9 deletions

View File

@ -557,6 +557,14 @@ void graph_r_line(
grcg_put(vram_offset, pixels, 16); \
pixels = 0; \
} else { \
/* ZUN bug: Getting here with a [vram_offset] of 0x0000 will
* cause a 4-byte write starting at 0xFFFF. On the 80286 and
* later CPUs, offset overflows within an instruction are
* illegal even in Real Mode, and will raise a General
* Protection Fault.
* As of May 2022, Anex86 is the only PC-98 emulator to
* correctly replicate this behavior of real hardware,
* though. */ \
vram_offset--; \
unput32_at(vram_offset); \
} \
@ -630,6 +638,16 @@ void graph_r_line(
plot_loop(y_cur, h, y_direction, x_cur, w, 1);
}
restore_last:
// ZUN bug: Off-by-one error, as [x_cur] and [y_cur] are one past the
// intended right / bottom coordinates after the plot_loop. Should have
// calculated [vram_offset] from [x_vram] and [y_vram] just like the
// plot_loop, since those values are directly updated for the next VRAM
// byte after a blit, and would thus be correct here as well.
//
// This way, the offset could potentially end up at [right = 640] or
// [bottom = -1]. Both together are not only the same as (0, 0) and thus
// wrap from the right edge of VRAM back to the left one, but also trigger
// the same General Protection Fault seen in the plot_loop itself.
vram_offset = vram_offset_shift(x_cur, y_cur) - 1;
unput32_at(vram_offset);
end:

View File

@ -74,7 +74,13 @@ void graph_r_line_patterned(
dots16_t pattern
);
// Recovers the pixels on the given arbitrary-angle line from page 1.
// Recovers horizontal 32-pixel chunks along on the given arbitrary-angled line
// from page 1. The [right] and [bottom] points are included in the line.
//
// ZUN bug: Will raise a General Protection Fault if it ever writes to the
// topmost byte of VRAM, corresponding to the pixel coordinates from (0, 0) to
// (0, 7) inclusive. Thanks to an off-by-one error, this also happens for any
// lines ending at ((RES_X - 1), 0).
void graph_r_line_unput(
screen_x_t left, vram_y_t top, screen_x_t right, vram_y_t bottom
);

View File

@ -2146,7 +2146,7 @@ void elis_main(void)
Pellets.unput_and_reset();
girl_bg_put(1);
Missiles.reset();
shootout_lasers_unput_and_reset_broken(i);
shootout_lasers_unput_and_reset_broken(i); // MODDERS: Remove
boss_defeat_animate();
scene_init_and_load(5);
}

View File

@ -89,7 +89,8 @@ public:
// Directly sets [done] if the laser collides with the player.
void update_hittest_and_render(void);
// Tries to unblit the entire laser, but fails hilariously.
// Tries to unblit the entire laser, but fails hilariously and potentially
// even crashes the game.
void unput_and_reset(void) {
if(alive) {
// Two ZUN bugs here:
@ -101,13 +102,20 @@ public:
// who knows how accurate that actually is?
//
// 2) graph_r_line_unput() takes screen_x_t and vram_y_t, not
// LaserPixels truncated to 16-bits :zunpet: As a result, this
// call effectively unblit random 32-bit pixel chunks.
// LaserPixels truncated to 16-bits. :zunpet: The function then
// interpolates and clips these values in a rather clumsy
// attempt to find a line segment between those garbage
// coordinates that actually falls within the boundaries of
// VRAM. At best, this search fails, and the function simply
// does nothing. At worst, the resulting line triggers the ZUN
// bugs in graph_r_line_unput(), raising a General Protection
// Fault.
// The latter is exactly the cause behind potential crashes when
// defeating bosses while there are diagonally moving lasers on
// screen, which are most commonly reported for Elis and Mima.
//
// Not that it matters a lot. This function is only called at the
// end of a boss battle, immediately before transitioning to the
// tally screen. Still, not doing anything would have been the
// better choice.
// So yeah, not doing anything would have been the much better
// choice.
graph_r_line_unput(
ray_start_left.v, ray_start_y.v, origin_left.v, origin_y.v
);