From c22299e0c8b2c4d44b5ef060b187b2ac12f62137 Mon Sep 17 00:00:00 2001 From: nmlgc Date: Thu, 9 Feb 2023 10:44:02 +0100 Subject: [PATCH] [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]. --- CONTRIBUTING.md | 49 ++++++++++++++++++++++++++++++------ README.md | 3 ++- th01/end/end.cpp | 6 ++--- th01/hardware/tram_x16.hpp | 6 ++--- th01/main/boss/b05.cpp | 2 +- th01/main/boss/b10j.cpp | 2 +- th01/main/boss/b10m.cpp | 4 +-- th01/main/boss/b15m.cpp | 9 ++++--- th01/main/boss/entity_a.cpp | 2 +- th01/main/bullet/pellet.cpp | 2 +- th01/main/bullet/pellet.hpp | 3 ++- th01/main/hud/hp.cpp | 2 +- th01/main/hud/hp.hpp | 2 +- th01/main/hud/menu.cpp | 6 ++--- th01/main/player/inv_spr.cpp | 5 +++- th01/main/player/orb.cpp | 7 +++--- th01/main/player/player.cpp | 7 +++--- th01/main/shape.cpp | 2 +- th01/main/stage/card.cpp | 3 ++- th01/main/stage/stageobj.cpp | 3 ++- th01/main_01.cpp | 2 +- th01/op_01.cpp | 6 ++--- th01/sprites/main_ptn.h | 5 ++-- th02/snd/load.cpp | 2 +- th02/snd/snd.h | 12 ++++----- th03/cutscene/cutscene.cpp | 49 ++++++++++++++++++++---------------- th04/main/ems.cpp | 4 +-- th04/snd/detmodes.cpp | 2 +- th04/snd/load.cpp | 2 +- th04/snd/snd.h | 4 +-- th05/main/bullet/laser.hpp | 4 +-- th05/snd/load.cpp | 2 +- 32 files changed, 135 insertions(+), 84 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b99b1b7..d846a068 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/README.md b/README.md index eb7dab7c..fa74c96c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/th01/end/end.cpp b/th01/end/end.cpp index 4ecae51a..56b962d9 100644 --- a/th01/end/end.cpp +++ b/th01/end/end.cpp @@ -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); \ diff --git a/th01/hardware/tram_x16.hpp b/th01/hardware/tram_x16.hpp index 549cfa4f..b2db7183 100644 --- a/th01/hardware/tram_x16.hpp +++ b/th01/hardware/tram_x16.hpp @@ -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); } diff --git a/th01/main/boss/b05.cpp b/th01/main/boss/b05.cpp index 7b69ba2a..ca4d7d0b 100644 --- a/th01/main/boss/b05.cpp +++ b/th01/main/boss/b05.cpp @@ -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 diff --git a/th01/main/boss/b10j.cpp b/th01/main/boss/b10j.cpp index cee8dee0..ea09ecac 100644 --- a/th01/main/boss/b10j.cpp +++ b/th01/main/boss/b10j.cpp @@ -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. // diff --git a/th01/main/boss/b10m.cpp b/th01/main/boss/b10m.cpp index e4170f42..6475ab2e 100644 --- a/th01/main/boss/b10m.cpp +++ b/th01/main/boss/b10m.cpp @@ -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; diff --git a/th01/main/boss/b15m.cpp b/th01/main/boss/b15m.cpp index 82b8b735..701df8cb 100644 --- a/th01/main/boss/b15m.cpp +++ b/th01/main/boss/b15m.cpp @@ -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) { diff --git a/th01/main/boss/entity_a.cpp b/th01/main/boss/entity_a.cpp index 5e449ccb..24afc69a 100644 --- a/th01/main/boss/entity_a.cpp +++ b/th01/main/boss/entity_a.cpp @@ -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 diff --git a/th01/main/bullet/pellet.cpp b/th01/main/bullet/pellet.cpp index 78b1fa0b..f8f16b62 100644 --- a/th01/main/bullet/pellet.cpp +++ b/th01/main/bullet/pellet.cpp @@ -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, diff --git a/th01/main/bullet/pellet.hpp b/th01/main/bullet/pellet.hpp index 430c4f0c..9c9b65a1 100644 --- a/th01/main/bullet/pellet.hpp +++ b/th01/main/bullet/pellet.hpp @@ -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, diff --git a/th01/main/hud/hp.cpp b/th01/main/hud/hp.cpp index 80cbae71..5f313359 100644 --- a/th01/main/hud/hp.cpp +++ b/th01/main/hud/hp.cpp @@ -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); } diff --git a/th01/main/hud/hp.hpp b/th01/main/hud/hp.hpp index cacacf17..ebebdb5f 100644 --- a/th01/main/hud/hp.hpp +++ b/th01/main/hud/hp.hpp @@ -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; diff --git a/th01/main/hud/menu.cpp b/th01/main/hud/menu.cpp index d98029bb..6faaca9d 100644 --- a/th01/main/hud/menu.cpp +++ b/th01/main/hud/menu.cpp @@ -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), diff --git a/th01/main/player/inv_spr.cpp b/th01/main/player/inv_spr.cpp index bf93a255..f9d77349 100644 --- a/th01/main/player/inv_spr.cpp +++ b/th01/main/player/inv_spr.cpp @@ -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())) diff --git a/th01/main/player/orb.cpp b/th01/main/player/orb.cpp index 0ed70803..3b2911c6 100644 --- a/th01/main/player/orb.cpp +++ b/th01/main/player/orb.cpp @@ -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()); } diff --git a/th01/main/player/player.cpp b/th01/main/player/player.cpp index 72ae775e..5cf54073 100644 --- a/th01/main/player/player.cpp +++ b/th01/main/player/player.cpp @@ -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) { diff --git a/th01/main/shape.cpp b/th01/main/shape.cpp index 7569ab7c..1e6aa700 100644 --- a/th01/main/shape.cpp +++ b/th01/main/shape.cpp @@ -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); diff --git a/th01/main/stage/card.cpp b/th01/main/stage/card.cpp index cde7195c..1f046019 100644 --- a/th01/main/stage/card.cpp +++ b/th01/main/stage/card.cpp @@ -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) { diff --git a/th01/main/stage/stageobj.cpp b/th01/main/stage/stageobj.cpp index 53284f98..2a7507e2 100644 --- a/th01/main/stage/stageobj.cpp +++ b/th01/main/stage/stageobj.cpp @@ -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; diff --git a/th01/main_01.cpp b/th01/main_01.cpp index 981e0ba1..0ddcdb2f 100644 --- a/th01/main_01.cpp +++ b/th01/main_01.cpp @@ -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. diff --git a/th01/op_01.cpp b/th01/op_01.cpp index 1069eb05..8f000f61 100644 --- a/th01/op_01.cpp +++ b/th01/op_01.cpp @@ -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); diff --git a/th01/sprites/main_ptn.h b/th01/sprites/main_ptn.h index 3ec5f2e7..b9399218 100644 --- a/th01/sprites/main_ptn.h +++ b/th01/sprites/main_ptn.h @@ -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 { diff --git a/th02/snd/load.cpp b/th02/snd/load.cpp index 2eca9979..d93c7ff4 100644 --- a/th02/snd/load.cpp +++ b/th02/snd/load.cpp @@ -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) { diff --git a/th02/snd/snd.h b/th02/snd/snd.h index f2fff1e3..e9f2d8c1 100644 --- a/th02/snd/snd.h +++ b/th02/snd/snd.h @@ -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...) diff --git a/th03/cutscene/cutscene.cpp b/th03/cutscene/cutscene.cpp index 16043653..deadca07 100644 --- a/th03/cutscene/cutscene.cpp +++ b/th03/cutscene/cutscene.cpp @@ -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(script); #else script = reinterpret_cast(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 diff --git a/th04/main/ems.cpp b/th04/main/ems.cpp index b856664f..1e14bf7c 100644 --- a/th04/main/ems.cpp +++ b/th04/main/ems.cpp @@ -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; \ diff --git a/th04/snd/detmodes.cpp b/th04/snd/detmodes.cpp index 7f3737c1..e759395c 100644 --- a/th04/snd/detmodes.cpp +++ b/th04/snd/detmodes.cpp @@ -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); diff --git a/th04/snd/load.cpp b/th04/snd/load.cpp index e0ec0de2..638649cc 100644 --- a/th04/snd/load.cpp +++ b/th04/snd/load.cpp @@ -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 diff --git a/th04/snd/snd.h b/th04/snd/snd.h index 09be9391..3715b847 100644 --- a/th04/snd/snd.h +++ b/th04/snd/snd.h @@ -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); diff --git a/th05/main/bullet/laser.hpp b/th05/main/bullet/laser.hpp index 8a086c77..6a852f36 100644 --- a/th05/main/bullet/laser.hpp +++ b/th05/main/bullet/laser.hpp @@ -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. diff --git a/th05/snd/load.cpp b/th05/snd/load.cpp index 4fa471bf..a3b605c2 100644 --- a/th05/snd/load.cpp +++ b/th05/snd/load.cpp @@ -74,7 +74,7 @@ void pascal snd_load(const char fn[PF_FN_LEN], snd_load_func_t func) } ext = *reinterpret_cast(&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(_DI) = ext;