mirror of https://github.com/nmlgc/ReC98.git
[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:
parent
3ba44f08a4
commit
c22299e0c8
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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); \
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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...)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; \
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue