[Contributing] Introduce a new "ZUN landmine" label for invisible bugs

Thanks to Clerish for the naming inspiration:

	https://twitter.com/Clerish/status/1623990678937034752

Part of P0231, funded by [Anonymous].
This commit is contained in:
nmlgc 2023-02-09 10:44:02 +01:00
parent 3ba44f08a4
commit c22299e0c8
32 changed files with 135 additions and 84 deletions

View File

@ -428,6 +428,38 @@ Bloat is removed on the aptly named `debloated` branch. That branch is the
recommended foundation for nontrivial mods that don't care about being
byte-for-byte comparable to ZUN's original binary.
#### `ZUN landmine`
Code that is technically wrong, but does *not* have [observable] effects within
the following assumptions:
* ZUN's original build of the decompiled game is correctly installed and
accessible, together with all its original data files.
* No files of this installation were modified.
* All files can be read as intended without I/O errors.
* The game runs on a clean PC-98 DOS system that matches the official minimum
system requirements, with enough free memory.
* The only active TSRs are the game's intended sound driver, an [expanded
memory][5] manager, and the resident part of `COMMAND.COM`.
* All system-level interfaces (interrupt APIs, I/O ports, and special memory
segments) behave as typically expected.
The effects might never be triggered by the original data, or they might be
mitigated by other parts of the original binary, including Turbo C++ 4.0J
code generation details.
Examples:
* Out-of-bounds memory accesses with no consequences
* Every [bug] in unused code
* Missing error handling for file I/O
Landmines are likely to cause issues as soon as the game is modded or compiled
with a different compiler, which breaks the above assumptions. Therefore, it
should be fixed on every branch that breaks the code or data layout of the
game. Most notably, they are removed from the `debloated` and `anniversary`
branches.
#### `ZUN bug`
Logic errors or incorrect programming language / interface / system use with
@ -475,11 +507,11 @@ project, but forks are welcome to do so.
#### Summary
| | Bloat | Bugs | Quirks |
| --------------------------- | ----- | ---- | ------ |
| Fix would be [observable] | No | Yes | Yes |
| Fix would desync replays | No | No | Yes |
| Might have been intentional | No* | No | Yes |
| | Bloat | Landmines | Bugs | Quirks |
| --------------------------- | ----- | --------- | ---- | ------ |
| Fix would be [observable] | No | No | Yes | Yes |
| Fix would desync replays | No | No | No | Yes |
| Might have been intentional | No* | No | No | Yes |
(* The games contain code that explicitly delays execution at microsecond,
millisecond, and frame granularity. If bloated code does not include explicit
@ -489,9 +521,9 @@ time of the respective game's development. [He admitted as much in an
interview.](https://en.touhougarakuta.com/article/specialtaidan_zun_hiroyuki_2-en))
The comments for each of these issues should be prefixed with a `ZUN
(bloat|bug|quirk):` label, and include a description that points out the
specific issue. This description can be left out for obvious cases of bloat,
like unused variables or code with no effect.
(bloat|landmine|bug|quirk):` label, and include a description that points out
the specific issue. This description can be left out for obvious cases of
bloat, like unused variables or code with no effect.
----
@ -500,5 +532,6 @@ like unused variables or code with no effect.
[2]: https://github.com/nmlgc/ReC98/invitations
[3]: Research/Borland%20C++%20decompilation.md#padding-bytes-in-code-segments
[4]: Research/Borland%20C++%20decompilation.md#memory-segmentation
[5]: https://en.wikipedia.org/wiki/Expanded_memory
[bug]: #zun-bug
[observable]: #observable

View File

@ -71,7 +71,7 @@ Crossed-out files are identical to their version in the previous game. ONGCHK.CO
## Branches
* ▶ **`master`: ZUN's original code, without mods or bugfixes** (You are here!)
* [`debloated`]: Rearchitected version of ZUN's code that is easier to read and modify, and builds smaller and faster PC-98 binaries. Only removes [bloat]; all [bugs] and [quirks] from ZUN's original code are left in place. **Ports should start from that branch**, and it's also the recommended base for mods that don't care about similarity to the original binary.
* [`debloated`]: Rearchitected version of ZUN's code that is easier to read and modify, and builds smaller and faster PC-98 binaries. Only removes [bloat] and [landmines]; all [bugs] and [quirks] from ZUN's original code are left in place. **Ports should start from that branch**, and it's also the recommended base for mods that don't care about similarity to the original binary.
* [`anniversary`]: Takes `debloated` and additionally fixes [bugs], achieving a smoother and flicker-free gameplay experience on the PC-98 platform while still leaving [quirks] in place. Might be an even better starting port for mods and ports.
* [`BossRush`]
* [`th03_no_gdc_frequency_check`]: Allows TH03 to be run with the GDC clock set to 5 MHz. The original game enforces 2.5 MHz, but doesn't functionally require it, even on real hardware.
@ -229,6 +229,7 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md).
[converter for hardcoded sprites]: https://github.com/nmlgc/ReC98/issues/8
[Borland/Embarcadero's own C++ 7.30]: https://www.embarcadero.com/de/free-tools/ccompiler/free-download
[bloat]: CONTRIBUTING.md#zun-bloat
[landmines]: CONTRIBUTING.md#zun-landmine
[bugs]: CONTRIBUTING.md#zun-bug
[quirks]: CONTRIBUTING.md#zun-quirk

View File

@ -39,9 +39,9 @@ inline void pic_caption_type_n(int line, size_t len, const char str[]) {
// MODDERS: Remove the [incorrect_extra_w] parameter.
#define pic_caption_type_2(line_1, line_2, incorrect_extra_w) { \
/* ZUN bug: This accesses one extra byte for the "STAGE 5 BOSS" string. \
* Which, luckily, happens to be the null terminator, and doesn't have \
* any visible effect. */ \
/* ZUN landmine: This accesses one extra byte for the "STAGE 5 BOSS" \
* string. Which, luckily, happens to be the null terminator, and doesn't \
* have any visible effect. */ \
pic_caption_type_n(0, (sizeof(line_1) - 1 + incorrect_extra_w), line_1); \
\
pic_caption_type_n(1, (sizeof(line_2) - 1 + incorrect_extra_w), line_2); \

View File

@ -39,9 +39,9 @@ public:
// Calls putkanji() for the next 5 TRAM rows.
void putkanji_for_5_rows(jis_t jis_kanji, int atrb);
// This is always called at the (0-based) line 21, and therefore always
// ends up writing into the second TRAM page. Luckily, that page is unused,
// and no code cares about it...
// ZUN landmine: This is always called at the (0-based) line 21, and
// therefore always ends up writing into the second TRAM page. Luckily,
// that page is unused, and no code cares about it...
void putkanji_until_end(jis_t jis_kanji, int atrb) {
putkanji_for_5_rows(jis_kanji, atrb);
}

View File

@ -263,7 +263,7 @@ void sphere_move_rotate_and_render(
egc_copy_rect_1_to_0_16(ent.cur_left, ent.cur_top, SINGYOKU_W, delta_y);
}
// ZUN bug: Why implicitly limit [delta_x] to 8? (Which is actually at
// ZUN landmine: Why implicitly limit [delta_x] to 8? (Which is actually at
// least 16, due to egc_copy_rect_1_to_0_16() rounding up to the next
// word.) The actual maximum value for [delta_x] that doesn't permanently
// leave sphere parts in VRAM is 23 at 24, a byte-aligned sphere moves at

View File

@ -268,7 +268,7 @@ void mima_unput(bool16 just_the_animated_part = false)
image++;
}
// ZUN bug (?): Why is MIMA_ANIM_H assumed to be 48 (16 above + 32 here)?
// ZUN landmine: Why is MIMA_ANIM_H assumed to be 48 (16 above + 32 here)?
// This might have even worked if the bottom 16 pixels of all [ent_anim]
// cels were identical, but they differ between C_CAST and C_METEOR.
//

View File

@ -1700,8 +1700,8 @@ void yuugenmagan_main(void)
(u2.subphase == laser_subphase(2, LSP_DONE))
) {
// ZUN bug: We can get here with a laser still on screen,
// particularly in debug mode. There should at least be an
// unblitting call here.
// particularly when holding ↵ Return in debug mode. There should
// at least be an unblitting call here.
boss_phase = 10;
boss_phase_frame = 0;

View File

@ -1930,11 +1930,14 @@ void elis_main(void)
(angle + (i * (0x100 / SPHERE_COUNT)))
);
// ZUN bug: Reads uninitialized stack memory in frame 0.
// ZUN landmine: Reads uninitialized stack memory in frame 0.
// Thankfully doesn't end up hitting the player or orb sprite
// in the original binary.
sphere_unput_and_put_head(i, head_left, head_top);
// ZUN bug: Both of these calls read uninitialized stack memory
// during frames 2 and 3, respectively.
// ZUN landmine: Both of these calls read uninitialized stack
// memory during frames 2 and 3, respectively. Invisible as
// well.
if(entrance_frame > 1) {
sphere_unput_and_put_trail(i, 0);
if(entrance_frame > 2) {

View File

@ -453,7 +453,7 @@ void CBossEntity::egc_sloppy_wave_unput_double_broken(
x_2 = wave_x(amp_2, t_2) + left_2;
t_1 += (0x100 / len_1);
t_2 += (0x100 / len_2);
// ZUN bug: Shouldn't the [h] parameter be 1?
// ZUN landmine: Shouldn't the [h] parameter be 1?
if(x_1 > x_2) {
egc_copy_rect_1_to_0_16_word_w(
x_2, (top + bos_y), (x_1 - x_2), bos_y

View File

@ -341,7 +341,7 @@ void CPellets::motion_type_apply_for_cur(void)
}
break;
case PM_BOUNCE_FROM_TOP_THEN_GRAVITY:
// ZUN bug: Wow... Three of them in one single if() expression.
// ZUN landmine: Wow... Three of them in one single if() expression.
// 1)
// 2) Pellets are clipped at both the left (1) and the right (2)
// edge of the playfield at those exact same coordinates,

View File

@ -76,7 +76,8 @@ enum pellet_sling_direction_t {
// • spreads with odd numbers of pellets are aimed *at* the player, while
// • spreads with even numbers of pellets are aimed *around* the player.
enum pellet_group_t {
// Does not actually work, due to a ZUN bug in group_velocity_set()!
// Not a valid pellet type, due to a potential ZUN bug in
// group_velocity_set()!
PG_NONE = 0,
PG_1 = 1,

View File

@ -106,7 +106,7 @@ bool16 hud_hp_render(int hp_total, int func)
// Since a .PTN quarter stores the background of two hit points, the
// calls above will unblit two hit points if [hp_total] is odd. So...
if((hp_total % 2) == 1) {
// ZUN bug: Yes, this will use the wrong section pattern when
// ZUN landmine: Yes, this will use the wrong section pattern when
// the section boundaries are odd. Just use one parameter... sigh.
hp_put((hp_total - 1), hp_total);
}

View File

@ -1,6 +1,6 @@
// First HP point in the respective color section. Assumes
// [hud_hp_first_redwhite] < [hud_hp_first_white].
// Also, these should be even, due to a bug in hud_hp_decrement()...
// Also, these should be even, due to a landmine in hud_hp_decrement()...
extern unsigned int hud_hp_first_white;
extern unsigned int hud_hp_first_redwhite;

View File

@ -118,8 +118,8 @@ bool16 pause_menu(void)
input_reset_menu_related();
pause_select_loop(sel, PAUSE_CHOICE_0_LEFT, PAUSE_CHOICE_1_LEFT);
// ZUN bug: Wrong left coordinate. Thankfully doesn't matter due to that
// function's word alignment.
// ZUN landmine: Wrong left coordinate. Thankfully doesn't matter due to
// that function's word alignment.
egc_copy_rect_1_to_0_16(
(PAUSE_TITLE_LEFT + 4), pause_top(0), shiftjis_w(PAUSE_TITLE), GLYPH_H
);
@ -145,7 +145,7 @@ bool16 pause_menu(void)
z_palette_set_all_show(stage_palette);
input_reset_sense();
// ZUN bug: Same as above.
// ZUN landmine: Same as above.
egc_copy_rect_1_to_0_16(
((PAUSE_CENTER_X - (MENU_W / 2)) + 8),
pause_top(0),

View File

@ -43,7 +43,10 @@ void invincibility_sprites_update_and_render(bool16 invincible)
sprites.top[i] = ((rand() % 48) + (player_top - 16));
sprites.frame[i] = ((rand() % 7) + 1);
}
// ZUN bug: Did you mean: `sprites.left[i]`?
// ZUN bug: Did you mean: `sprites.left[i]`? With Reimu's fixed
// position at the bottom of the playfield, this condition is always
// true, and these sprites won't be clipped at the left and right edges
// of VRAM after all.
if(
(sprites.top[i] >= 0) &&
(sprites.top[i] < (RES_X - sSHAPE8X8[0].w()))

View File

@ -128,9 +128,10 @@ void pascal orb_and_pellets_and_stage_unput_update_render__vsync_wait(
}
if(!orb_in_portal) {
// ZUN bug: Shouldn't you unblit *before* updating the rotation frame?
// While it's OK (and even sort of essential) to assume that all Orb
// sprites are shaped identically, it still violates common sense.
// ZUN landmine: Shouldn't you unblit *before* updating the rotation
// frame? While it's OK (and even sort of essential) to assume that all
// Orb sprites are shaped identically (as they are in the original
// data), ordering the calls like this still violates common sense.
ptn_unput_8(orb_prev_left, orb_prev_top, orb_anim_cel());
}

View File

@ -856,9 +856,10 @@ void player_unput_update_render(bool16 do_not_reset_player_state)
}
player_invincible_against_orb = false;
mode = M_REGULAR;
// ZUN bug: [submode.direction] should really have been initialized
// here as well. Since it's not, the next call to this function
// will interpret the current special attack as a direction.
// ZUN bug: [submode.direction] should really have been set here as
// well. Since it's not, the next call to this function will
// interpret the current special attack as a direction, adding an
// additional invisibility frame.
} else {
// Special attack still going on. Check for orb collision
switch(submode.special) {

View File

@ -52,7 +52,7 @@ void shape_ellipse_arc_put(
dots8_t cache_dots = 0;
vram_offset_t cache_vram_offset = -1;
// ZUN bug: Leaves the GRCG activated if the return condition below is
// ZUN landmine: Leaves the GRCG activated if the return condition below is
// `true`. Should be done after that branch instead.
grcg_setcolor_rmw(col);

View File

@ -123,7 +123,8 @@ void cards_score_render(void)
}
// cards_hittest() ensures that 100 is the minimum value.
// ZUN bug: Should be >=.
// ZUN bug: Should be >=. Can in fact be observed on stages 96-99
// on non-Lunatic, and stages 321-324 on Lunatic.
if(cards_score[i] > 10000) {
offset_left = -GLYPH_HALF_W;
} else if(cards_score[i] > 1000) {

View File

@ -945,7 +945,8 @@ void portal_enter_update_and_render_or_reset(int obstacle_slot, bool16 reset)
portals_blocked = false;
if(orb_in_portal) {
// ZUN bug: Missing an unblitting call. This just blits the regular
// portal sprite on top of an animated one.
// portal sprite on top of an animated one. Very noticeable when
// losing a life while a PTN_PORTAL_ANIM sprite is in VRAM.
ptn_put_8(dst_left, dst_top, PTN_PORTAL);
orb_in_portal = false;

View File

@ -526,7 +526,7 @@ int main(void)
puts(STAGESELECT_STAGE);
scanf("%d", &stage_id);
// ZUN bug: Reinterprets the 8-bit pointer &[route] as a 16-bit
// ZUN landmine: Reinterprets the 8-bit pointer &[route] as a 16-bit
// pointer, which will overwrite the byte that follows in memory.
// Luckily, it just hits the low byte of SinGyoku's phase frame,
// which is set to 0 in that fight's entrance animation.

View File

@ -557,7 +557,7 @@ void music_choice_unput_and_put(int choice, int col)
egc_copy_rect_1_to_0_16(left, top, MENU_W, GLYPH_H);
if(choice == 0) {
// ZUN bug: That's larger than the menu?
// ZUN landmine: That's larger than the menu?
egc_copy_rect_1_to_0_16(
left, (top + CHOICE_PADDED_H), (MENU_W + GLYPH_FULL_W), GLYPH_H
);
@ -769,8 +769,8 @@ void main(int argc, const char *argv[])
debug_mode = 3;
}
// ZUN bug: The string could theoretically be less than 3 characters
// long.
// ZUN landmine: The string could theoretically be less than 3
// characters long.
if(memcmp(argv[1], "CON", 3) == 0) {
arg2 = argv[2];
unused_con_arg_0 = atol(arg2);

View File

@ -24,8 +24,9 @@ static const int DASH_CELS = 2;
#define HP_POINT_W 8
#define HP_H 15
// ZUN bug: The actual limit is half of this number, due to another ZUN bug in
// hud_hp_render().
// ZUN bug: The actual limit is half of this number, due to another bug in
// hud_hp_render(). (Not a landmine because this number controls when the
// observable heap corruption in debug mode occurs.)
static const int HP_MAX = 96;
typedef enum {

View File

@ -38,7 +38,7 @@ void snd_load(const char fn[PF_FN_LEN], snd_load_func_t func)
_AX = 0x3D00;
geninterrupt(0x21);
_BX = _AX;
// ZUN bug: No error handling
// ZUN landmine: No error handling
asm { mov ax, func; }
if((_AX == SND_LOAD_SONG) && snd_midi_active) {

View File

@ -64,10 +64,10 @@ void snd_delay_until_volume(uint8_t volume);
// would still only be hit once, during the first loop.
// If no sound driver is active, the delay is replaced with
// frame_delay([SND_FALLBACK_DELAY_FRAMES]).
// ZUN bug: Neither PMD nor MMD reset the internal measure when stopping
// playback. If no BGM is playing and the previous one hasn't been played
// back for at least the given number of [measures], the function will
// deadlock.
// ZUN landmine: Neither PMD nor MMD reset the internal measure when
// stopping playback. If no BGM is playing and the previous one hasn't been
// played back for at least the given number of [measures], the function
// will deadlock.
void snd_delay_until_measure(int measure);
#endif
@ -77,8 +77,8 @@ void snd_delay_until_volume(uint8_t volume);
)
#if defined(__cplusplus) && (GAME <= 4)
static inline uint16_t snd_load_size() {
// ZUN bug: Should rather retrieve the maximum data size for song
// or sound effect data via PMD_GET_BUFFER_SIZES, instead of
// ZUN landmine: Should rather retrieve the maximum data size for
// song or sound effect data via PMD_GET_BUFFER_SIZES, instead of
// hardcoding a random maximum and risking overflowing PMD's data
// buffer.
// (Unfortunately, MMD lacks a similar function...)

View File

@ -203,7 +203,7 @@ bool16 pascal near cutscene_script_load(const char* fn)
size_t size = file_size();
#if (GAME >= 4)
// PORTERS: Required for TH03 on flat memory models as well.
// ZUN bug: Missing an error check if [size] >= sizeof(script);
// ZUN landmine: Missing an error check if [size] >= sizeof(script);
script_p = static_cast<unsigned char near *>(script);
#else
script = reinterpret_cast<unsigned char far *>(hmem_allocbyte(size));
@ -453,8 +453,8 @@ void near cursor_advance_and_animate(void)
cursor.x = BOX_LEFT;
cursor.y = BOX_TOP;
// ZUN bug: The \s command is the only place in TH05 where text is
// unblitted from any page. Not doing it here completely breaks
// ZUN landmine: The \s command is the only place in TH05 where text
// is unblitted from any page. Not doing it here completely breaks
// automatically added text boxes in that game, as any new text
// will just be blitted on top of old one.
// (Not that the feature would have been particularly usable
@ -717,8 +717,8 @@ script_ret_t pascal near script_op(unsigned char c)
script_number_param_read_first(p1);
script_number_param_read_second(p2);
if(!fast_forward) {
// ZUN bug: Does not prevent the potential deadlock issue with
// this function.
// ZUN landmine: Does not prevent the potential deadlock issue
// with this function.
#if (GAME >= 4)
snd_delay_until_measure(p1, p2);
#else
@ -825,16 +825,20 @@ script_ret_t pascal near script_op(unsigned char c)
return CONTINUE;
case 'k':
// ZUN bug: Should have also been done in TH04. Without this call, this
// command will wait on an invisible text box, and needs to be preceded
// by a \vp1 command to actually work as a mid-box pause.
// ZUN landmine: Should have also been done in TH04. Without this call,
// this command will wait on an invisible text box, and needs to be
// preceded by a \vp1 command to actually work as a mid-box pause.
#if (GAME == 5)
box_1_to_0_animate();
#endif
script_number_param_read_first(p1, 0);
if(!fast_forward) {
// ZUN bug: This parameter is ignored in TH03.
// ZUN quirk: This parameter is ignored in TH03. Labeling this as a
// quirk because the original TH03 scripts call this command with a
// non-0 parameter in 19 of 34 cases, suggesting that ZUN made the
// conscious decision to override these parameters with 0 later in
// development.
box_wait_animate((GAME >= 4) ? p1 : 0);
}
return CONTINUE;
@ -845,7 +849,7 @@ script_ret_t pascal near script_op(unsigned char c)
#if (GAME == 5)
bgimage_snap();
#else
// ZUN bug: Missing a box_bg_allocate_and_snap() or equivalent
// ZUN landmine: Missing a box_bg_allocate_and_snap() or equivalent
// call. Any future box_bg_put() call will still display the box
// area snapped from any previously displayed background image.
// This bug therefore effectively restricts usage of this command
@ -923,13 +927,14 @@ script_ret_t pascal near script_op(unsigned char c)
// anyway), the entire system would have only needed a single
// graph_showpage(0) call at the start of cutscene_animate().
//
// ZUN bug: Since TH04 renders text to VRAM page 1, calling \=
// or \== in the middle of a string of text temporarily shows
// any new text rendered since the last box_1_to_0_animate()
// call if any of the following blit operations spends more
// than one frame with page 1 visible. In practice, this only
// happens on very underclocked systems far below the game's
// target of 66 MHz, but it's a bug nonetheless.
// ZUN landmine: Since TH04 renders text to VRAM page 1,
// calling \= or \== in the middle of a string of text
// temporarily shows any text rendered since the last
// box_1_to_0_animate() call if any of the following blit
// operations spends more than one frame with page 1 visible.
// In practice, this only happens on very underclocked systems
// far below the game's target of 66 MHz, but it's a landmine
// nonetheless.
graph_showpage(1);
graph_accesspage(0);
#endif
@ -959,7 +964,7 @@ script_ret_t pascal near script_op(unsigned char c)
frame_delay(1); // ZUN quirk
#else
// ZUN bloat: See above.
// ZUN bug: See above.
// ZUN landmine: See above.
graph_showpage(1);
graph_accesspage(0);
@ -1004,7 +1009,7 @@ script_ret_t pascal near script_op(unsigned char c)
script_p++;
colmap.keys[colmap_count][0].byte[1] = *script_p;
// ZUN bug: Jumps over the additional comma separating the two
// ZUN landmine: Jumps over the additional comma separating the two
// parameters, and assumes it's always present. Come on!
// script_number_param_read_second() exists to handle exactly this
// situation in a cleaner way.
@ -1012,7 +1017,7 @@ script_ret_t pascal near script_op(unsigned char c)
script_number_param_read_first(p1, V_WHITE);
// ZUN bug: No bounds check
// ZUN landmine: No bounds check
colmap.values[colmap_count] = p1;
colmap_count++;
break;
@ -1048,8 +1053,8 @@ void near cutscene_animate(void)
// Necessary because scripts can (and do) show multiple text boxes on the
// initially black background.
// ZUN bug: TH05 assumes that they don't. While this is true for all
// scripts in the original game, it's still technically a bug.
// ZUN landmine: TH05 assumes that they don't, which is true for all
// scripts in the original game.
#if (GAME != 5)
box_bg_allocate_and_snap();
#endif

View File

@ -42,8 +42,8 @@ inline void ems_write_cdg_color_planes(
);
}
// ZUN bug: Should clamp the amount of images to the maximum amount allocated
// in the EMS cache area.
// ZUN landmine: Should clamp the amount of images to the maximum amount
// allocated in the EMS cache area.
#define ems_transfer_cdgs_until_freed_slot(offset_first, slot_first) { \
int slot = slot_first; \
uint32_t offset = offset_first; \

View File

@ -12,7 +12,7 @@ int pascal snd_determine_modes(int req_bgm_mode, int req_se_mode)
if(req_bgm_mode == SND_BGM_MIDI) {
snd_mmd_resident();
}
// ZUN bug: We should be returning if neither driver is resident!
// ZUN landmine: We should be returning if neither driver is resident!
_AH = PMD_GET_DRIVER_TYPE_AND_VERSION;
geninterrupt(PMD);

View File

@ -55,7 +55,7 @@ void pascal snd_load(const char fn[PF_FN_LEN], snd_load_func_t func)
_AX = 0x3D00;
geninterrupt(0x21);
_BX = _AX;
// ZUN bug: No error handling
// ZUN landmine: No error handling
// Using inline assembly rather than _AX to prevent parameters from being
// moved to the DI register

View File

@ -70,9 +70,9 @@ int pascal snd_determine_modes(int req_bgm_mode, int req_se_mode);
// • [fn] still needs to be null-terminated.
// • The TH04 version does not handle file errors.
//
// ZUN bug: Two of them:
// ZUN landmine: Two of them:
// • In SND_SE_BEEP mode, the TH04 version requires master.lib's BGM subsystem
// to have been initialized before.
// to have been initialized before. TH05 initializes it on demand.
// • The TH05 version will infinitely loop if neither the file for the current
// [snd_bgm_mode] nor "[fn].m" exist.
void pascal snd_load(const char fn[PF_FN_LEN], snd_load_func_t func);

View File

@ -40,8 +40,8 @@ struct laser_coords_t {
// In pixels.
union {
// ZUN bug: LF_FIXED_SHRINK and LF_FIXED_SHRINK_AND_WAIT_TO_GROW are
// effectively limited to a maximum width of 127 pixels due to an
// ZUN landmine: LF_FIXED_SHRINK and LF_FIXED_SHRINK_AND_WAIT_TO_GROW
// are effectively limited to a maximum width of 127 pixels due to an
// implementation convenience in their update code. For larger values,
// their shrink animation wouldn't play, and the laser will transition
// to its next flag immediately.

View File

@ -74,7 +74,7 @@ void pascal snd_load(const char fn[PF_FN_LEN], snd_load_func_t func)
}
ext = *reinterpret_cast<const int32_t *>(&SND_LOAD_EXT[0][_BX]);
// ZUN bug: Infinite loop if neither the file for the current
// ZUN landmine: Infinite loop if neither the file for the current
// [snd_bgm_mode] nor "[fn].m" exist.
while(1) {
*reinterpret_cast<uint32_t near *>(_DI) = ext;