[Decompilation] [th04] Stage 4 Marisa: Point-reflected movement

This is the function that causes Marisa's `Divide Error` crash when
called during a 4-frame window during the end of certain patterns.
Just like with Kurumi's division by zero, it's just as undefined here
what should happen instead, and any possible fix can only ever be a
fan-fiction interpretation of the code. And since the community will
have an easier time understanding and debating a C++-level hack instead
of an ASM-level one, it makes sense to first decompile this function…

…and document it, which is actually much harder!

Part of P0189, funded by Arandui and Lmocinemod.
This commit is contained in:
nmlgc 2022-04-06 20:12:44 +02:00
parent 9f4e5ae1ac
commit c90c7d52c4
4 changed files with 105 additions and 125 deletions

View File

@ -126,7 +126,7 @@ bin\th04\op.exe: bin\th04\op.obj th04\m_char.cpp bin\th01\vplanset.obj bin\th02\
$**
|
bin\th04\main.exe: bin\th04\main.obj bin\th04\slowdown.obj th04\ems.cpp th04\playfld.cpp th04\f_dialog.cpp th04\dialog.cpp bin\th04\player_p.obj bin\th04\scoreupd.obj th04\hud_ovrl.cpp bin\th04\cfg_lres.obj bin\th01\vplanset.obj bin\th03\vector2.obj bin\th02\frmdely1.obj bin\th03\hfliplut.obj th04\mpn_free.cpp bin\th04\input_w.obj th04\mpn_l_i.cpp bin\th04\vector.obj bin\th04\snd_pmdr.obj bin\th04\snd_mmdr.obj bin\th04\snd_kaja.obj bin\th04\snd_mode.obj bin\th04\snd_load.obj bin\th04\cdg_put.obj bin\th04\exit.obj bin\th04\initmain.obj bin\th04\cdg_p_na.obj bin\th04\cdg_p_pr.obj bin\th04\input_s.obj bin\th04\snd_se_r.obj bin\th04\snd_se.obj bin\th04\cdg_load.obj th04\gather.cpp bin\th04\scrolly3.obj bin\th04\motion_3.obj th04\mb_dft.cpp bin\th04\it_spl_u.obj th04\bullet_u.cpp th04\bullet_a.cpp th04\boss.cpp th04\boss_4r.cpp th04\boss_x2.cpp
bin\th04\main.exe: bin\th04\main.obj bin\th04\slowdown.obj th04\ems.cpp th04\playfld.cpp th04\f_dialog.cpp th04\dialog.cpp bin\th04\player_p.obj bin\th04\scoreupd.obj th04\hud_ovrl.cpp bin\th04\cfg_lres.obj bin\th01\vplanset.obj bin\th03\vector2.obj bin\th02\frmdely1.obj bin\th03\hfliplut.obj th04\mpn_free.cpp bin\th04\input_w.obj th04\mpn_l_i.cpp bin\th04\vector.obj bin\th04\snd_pmdr.obj bin\th04\snd_mmdr.obj bin\th04\snd_kaja.obj bin\th04\snd_mode.obj bin\th04\snd_load.obj bin\th04\cdg_put.obj bin\th04\exit.obj bin\th04\initmain.obj bin\th04\cdg_p_na.obj bin\th04\cdg_p_pr.obj bin\th04\input_s.obj bin\th04\snd_se_r.obj bin\th04\snd_se.obj bin\th04\cdg_load.obj th04\gather.cpp bin\th04\scrolly3.obj bin\th04\motion_3.obj th04\mb_dft.cpp bin\th04\it_spl_u.obj th04\boss_4m.cpp th04\bullet_u.cpp th04\bullet_a.cpp th04\boss.cpp th04\boss_4r.cpp th04\boss_x2.cpp
$(CC) $(CFLAGS) $(LARGE_LFLAGS) -DGAME=4 -DBINARY='M' -3 -Z -nbin\th04\ -eMAIN.EXE @&&|
$**
|

1
th04/boss_4m.cpp Normal file
View File

@ -0,0 +1 @@
#include "th04/main/boss/b4m.cpp"

59
th04/main/boss/b4m.cpp Normal file
View File

@ -0,0 +1,59 @@
/// Stage 4 Boss - Marisa
/// ---------------------
#include "platform.h"
#include "pc98.h"
#include "th01/math/subpixel.hpp"
#include "th04/math/motion.hpp"
extern "C" {
#include "th04/main/playfld.hpp"
}
#include "th04/main/phase.hpp"
#include "th04/main/boss/boss.hpp"
/// State
/// -----
#define flystep_pointreflected_frame boss_statebyte[13]
/// -----
// On [flystep_pointreflected_frame] 0, this function sets up [boss] movement
// towards the point reflection of Marisa's position across a fixed position
// near the top of the sealed moon in the background. The velocity is
// calculated to reach this exact point at [duration - 12], with Marisa braking
// on the last 12 frames by halving that velocity each frame. Every call to
// this function, including the one on frame 0, then applies this velocity to
// [boss].
// [duration] values <12 will move Marisa into the opposite direction instead.
// Returns `true` if the function was called for [duration] frames.
//
// ZUN bug: Not defined for [duration] values of 12 or 13, which will crash the
// game with a division by zero ("Divide Error"). The two patterns that pass a
// variable [duration] to this function also only happen to call this function
// every 4 frames rather than every frame, introducing additional jerkiness.
bool pascal near marisa_flystep_pointreflected(int duration)
{
enum {
POINT_X = TO_SP(PLAYFIELD_W / 2),
POINT_Y = TO_SP((PLAYFIELD_H * 7) / 23),
BRAKE_DURATION = 12,
};
if(flystep_pointreflected_frame == 0) {
boss.pos.velocity.x.v = (
(POINT_X - boss.pos.cur.x) / ((duration / 2) - (BRAKE_DURATION / 2))
);
boss.pos.velocity.y.v = (
(POINT_Y - boss.pos.cur.y) / ((duration / 2) - (BRAKE_DURATION / 2))
);
}
flystep_pointreflected_frame++;
if(flystep_pointreflected_frame >= (duration - BRAKE_DURATION)) {
boss.pos.velocity.x.v /= 2;
boss.pos.velocity.y.v /= 2;
}
if(flystep_pointreflected_frame >= duration) {
return true;
}
boss.pos.update_seg3();
return false;
}

View File

@ -45,7 +45,7 @@ include th04/main/enemy/enemy.inc
main_01 group SLOWDOWN_TEXT, ma_TEXT, EMS_TEXT, mai_TEXT, PLAYFLD_TEXT, main_TEXT, DIALOG_TEXT, main__TEXT, PLAYER_P_TEXT, main_0_TEXT, HUD_OVRL_TEXT, main_01_TEXT, main_012_TEXT, CFG_LRES_TEXT, main_013_TEXT
g_SHARED group SHARED, SHARED_
main_03 group GATHER_TEXT, SCROLLY3_TEXT, MOTION_3_TEXT, main_032_TEXT, IT_SPL_U_TEXT, main_033_TEXT, MB_DFT_TEXT, main_034_TEXT, BULLET_U_TEXT, BULLET_A_TEXT, main_035_TEXT, BOSS_TEXT, main_036_TEXT
main_03 group GATHER_TEXT, SCROLLY3_TEXT, MOTION_3_TEXT, main_032_TEXT, IT_SPL_U_TEXT, BOSS_4M_TEXT, main_033_TEXT, MB_DFT_TEXT, main_034_TEXT, BULLET_U_TEXT, BULLET_A_TEXT, main_035_TEXT, BOSS_TEXT, main_036_TEXT
; ===========================================================================
@ -12081,7 +12081,7 @@ IT_SPL_U_TEXT segment word public 'CODE' use16
@item_splashes_update$qv procdesc pascal near
IT_SPL_U_TEXT ends
main_033_TEXT segment word public 'CODE' use16
BOSS_4M_TEXT segment word public 'CODE' use16
; =============== S U B R O U T I N E =======================================
@ -16758,84 +16758,11 @@ loc_16B7D:
retn
marisa_16AE9 endp
@MARISA_FLYSTEP_POINTREFLECTED$QI procdesc pascal near \
duration:word
BOSS_4M_TEXT ends
; =============== S U B R O U T I N E =======================================
; Attributes: bp-based frame
marisa_16B85 proc near
arg_0 = word ptr 4
push bp
mov bp, sp
push si
mov si, [bp+arg_0]
cmp _boss_statebyte[13].BSB_motion_lerp_frames, 0
jnz short loc_16BC5
mov ax, si
cwd
sub ax, dx
sar ax, 1
add ax, -6
push ax
mov ax, (192 shl 4)
sub ax, _boss_pos.cur.x
cwd
pop bx
idiv bx
mov _boss_pos.velocity.x, ax
mov ax, si
cwd
sub ax, dx
sar ax, 1
add ax, -6
push ax
mov ax, (112 shl 4)
sub ax, _boss_pos.cur.y
cwd
pop bx
idiv bx
mov _boss_pos.velocity.y, ax
loc_16BC5:
inc _boss_statebyte[13].BSB_motion_lerp_frames
mov al, _boss_statebyte[13].BSB_motion_lerp_frames
mov ah, 0
lea dx, [si-0Ch]
cmp ax, dx
jl short loc_16BEB
mov ax, _boss_pos.velocity.x
cwd
sub ax, dx
sar ax, 1
mov _boss_pos.velocity.x, ax
mov ax, _boss_pos.velocity.y
cwd
sub ax, dx
sar ax, 1
mov _boss_pos.velocity.y, ax
loc_16BEB:
mov al, _boss_statebyte[13].BSB_motion_lerp_frames
mov ah, 0
cmp ax, si
jl short loc_16BF8
mov al, 1
jmp short loc_16C00
; ---------------------------------------------------------------------------
loc_16BF8:
push offset _boss_pos
call @PlayfieldMotion@update_seg3$qv
mov al, 0
loc_16C00:
pop si
pop bp
retn 2
marisa_16B85 endp
main_033_TEXT segment byte public 'CODE' use16
; =============== S U B R O U T I N E =======================================
@ -17314,8 +17241,7 @@ loc_16FC6:
mov ah, 0
mov dx, 160
sub dx, ax
push dx
call marisa_16B85
call @marisa_flystep_pointreflected$qi pascal, dx
mov _bullet_template.spawn_type, BST_BULLET16
mov _bullet_template.patnum, PAT_BULLET16_D_BLUE
mov _bullet_template.speed, (3 shl 4) + 4
@ -17463,8 +17389,7 @@ loc_17102:
mov ah, 0
mov dx, 160
sub dx, ax
push dx
call marisa_16B85
call @marisa_flystep_pointreflected$qi pascal, dx
mov _bullet_template.spawn_type, BST_BULLET16
mov _bullet_template.patnum, PAT_BULLET16_N_STAR
mov _bullet_template.BT_group, BG_SPREAD_AIMED
@ -17628,8 +17553,7 @@ loc_17266:
; ---------------------------------------------------------------------------
loc_1726E:
push 60h
call marisa_16B85
call @marisa_flystep_pointreflected$qi pascal, 96
cmp _boss_statebyte[15].BSB_subpattern_num, 0
jnz short loc_1729F
mov _boss_statebyte[15].BSB_subpattern_num, 1
@ -17776,8 +17700,7 @@ loc_173CB:
; ---------------------------------------------------------------------------
loc_173D3:
push 40h
call marisa_16B85
call @marisa_flystep_pointreflected$qi pascal, 64
or al, al
jnz loc_1747D
mov ax, _boss_phase_frame
@ -18025,8 +17948,7 @@ loc_1760D:
; ---------------------------------------------------------------------------
loc_17615:
push 80h
call marisa_16B85
call @marisa_flystep_pointreflected$qi pascal, 128
cmp _boss_statebyte[15].BSB_bitless_pattern_started, 0
jnz short loc_17634
call randring2_next16_and pascal, 1Fh
@ -18164,8 +18086,7 @@ loc_17794:
; ---------------------------------------------------------------------------
loc_1779E:
push 0A0h
call marisa_16B85
call @marisa_flystep_pointreflected$qi pascal, 160
cmp _boss_statebyte[15].BSB_bitless_pattern_started, 0
jnz short loc_177B8
call randring2_next16_and pascal, 1Fh
@ -18359,8 +18280,7 @@ loc_17958:
; ---------------------------------------------------------------------------
loc_1795F:
push 48h ; 'H'
call marisa_16B85
call @marisa_flystep_pointreflected$qi pascal, 72
or al, al
jnz short loc_179A8
mov ax, _boss_phase_frame
@ -18489,7 +18409,7 @@ loc_17A75:
mov byte_25674, 0
mov byte_2566E, 0Ah
mov byte_2566F, 0
mov _boss_statebyte[13].BSB_motion_lerp_frames, 0
mov _boss_statebyte[13].BSB_flystep_pointreflected_frame, 0
jmp loc_17CA4
; ---------------------------------------------------------------------------
@ -18567,7 +18487,7 @@ loc_17B1E:
cmp _boss_phase_frame, 64
jl short loc_17B98 ; default
inc _boss_mode_change
mov _boss_statebyte[13].BSB_motion_lerp_frames, 0
mov _boss_statebyte[13].BSB_flystep_pointreflected_frame, 0
cmp byte_2566F, 0
jnz short loc_17B62
cmp byte_25672, 0
@ -32388,35 +32308,35 @@ byte_2D00E db ?
include th04/main/boss/funcs[bss].asm
boss_statebyte_t union
BSB_angle_mirror_y db ?
BSB_bitless_pattern_started db ?
BSB_cluster_angle db ?
BSB_delta_angle_between_rings db ?
BSB_direction db ?
BSB_gengetsu_started db ?
BSB_motion_lerp_frames db ?
BSB_orb_count db ?
BSB_orb_interval db ?
BSB_origin_offset_x db ? ; pixel_t
BSB_phase_frame db ?
BSB_pattern_num_prev db ?
BSB_patterns_done db ?
BSB_pellet_stack_angle db ?
BSB_thicklaser_radius db ?
BSB_spread db ?
BSB_spread_angle db ?
BSB_spread_angle_range db ?
BSB_spread_delta_angle db ?
BSB_spread_interval db ?
BSB_spread_speed db ?
BSB_spread_turns_max db ?
BSB_spin_ring db ?
BSB_stack db ?
BSB_stack_left_angle db ?
BSB_stack_right_angle db ?
BSB_stacks_fired db ?
BSB_subpattern_id db ?
BSB_subpattern_num db ?
BSB_angle_mirror_y db ?
BSB_bitless_pattern_started db ?
BSB_cluster_angle db ?
BSB_delta_angle_between_rings db ?
BSB_direction db ?
BSB_gengetsu_started db ?
BSB_flystep_pointreflected_frame db ?
BSB_orb_count db ?
BSB_orb_interval db ?
BSB_origin_offset_x db ? ; pixel_t
BSB_phase_frame db ?
BSB_pattern_num_prev db ?
BSB_patterns_done db ?
BSB_pellet_stack_angle db ?
BSB_thicklaser_radius db ?
BSB_spread db ?
BSB_spread_angle db ?
BSB_spread_angle_range db ?
BSB_spread_delta_angle db ?
BSB_spread_interval db ?
BSB_spread_speed db ?
BSB_spread_turns_max db ?
BSB_spin_ring db ?
BSB_stack db ?
BSB_stack_left_angle db ?
BSB_stack_right_angle db ?
BSB_stacks_fired db ?
BSB_subpattern_id db ?
BSB_subpattern_num db ?
boss_statebyte_t ends
public _boss_statebyte