Skip to content

Commit

Permalink
[Decompilation] [th01] Keyboard input in REIIDEN.EXE
Browse files Browse the repository at this point in the history
Yes, TH01's memory info screen will recurse into itself for every 3
frames the PgUp key is held, requiring one additional PgDown press per
recursion to actually get out of it.
You can, of course, also crash the system via a stack overflow this
way, if that's your thing.

Part of P0091, funded by Ember2528.
  • Loading branch information
nmlgc committed May 12, 2020
1 parent 07dab29 commit f2543c8
Show file tree
Hide file tree
Showing 8 changed files with 396 additions and 634 deletions.
2 changes: 1 addition & 1 deletion Makefile.mak
Expand Up @@ -53,7 +53,7 @@ bin\th01\op.exe: bin\th01\op.obj th01\op_01.cpp th01\op_02.c th01\op_03.c th01\o
$**
|

bin\th01\reiiden.exe: bin\th01\reiiden.obj th01\main_02.c th01\main_03.c th01\main_04.c th01\main_05.c th01\main_06.cpp th01\main_07.cpp th01\main_08.cpp th01\main_12.c th01\main_13.c th01\main_14.c th01\main_16.c th01\main_19.cpp
bin\th01\reiiden.exe: bin\th01\reiiden.obj th01\main_01.cpp th01\main_02.c th01\main_03.c th01\main_04.c th01\main_05.c th01\main_06.cpp th01\main_07.cpp th01\main_08.cpp th01\main_12.c th01\main_13.c th01\main_14.c th01\main_16.c th01\main_19.cpp
$(CC) $(CFLAGS) -ml -3 -DGAME=1 -DBINARY='M' -nbin\th01\ -eREIIDEN.EXE @&&|
$**
|
Expand Down
8 changes: 8 additions & 0 deletions th01/hardware/input.hpp
Expand Up @@ -21,6 +21,14 @@ extern unsigned char paused;
extern bool done;
extern bool input_bomb;

// Updates all input-related variables if the held state of their associated
// keys changed compared to the last input.
void input_sense(bool16 reset_repeat);

// Resets all input-related variables, then updates them according to the
// keyboard state.
void input_reset_sense(void);

#define input_func_flag(var, flag) { var |= flag; } else { var &= ~flag; }

#define input_onchange(prev_slot, cur_sensed, if_pressed) \
Expand Down
17 changes: 17 additions & 0 deletions th01/hardware/input_main_end[data].asm
@@ -0,0 +1,17 @@
public _input_up, _input_down, _input_lr, _input_shot, _input_strike, _input_ok
public _input_bomb, _done, _paused, _input_mem_enter, _input_mem_leave
_input_lr db 0
_input_mem_enter db 0
_input_mem_leave db 0
_input_shot db 0
_done db 0
_input_bomb db 0
_paused db 0
_input_ok db 0
_input_strike db 0
_input_up db 0
_input_down db 0

INPUT_RIGHT = 01h
INPUT_LEFT = 02h
BOMB_DOUBLETAP_WINDOW = 20
14 changes: 14 additions & 0 deletions th01/hardware/input_rs.cpp
@@ -0,0 +1,14 @@
void input_reset_sense(void)
{
input_lr = false;
input_up = false;
input_down = false;
input_ok = false;
paused = false;
input_shot = false;
input_strike = false;
input_mem_enter = false;
input_mem_leave = false;

input_sense(true);
}
6 changes: 6 additions & 0 deletions th01/main/bomb.hpp
@@ -0,0 +1,6 @@
// Current frame within the BOMB_DOUBLETAP_WINDOW. A bomb is fired if both
// shot and strike keys are double-tapped before this variable reaches that
// window.
extern int bomb_doubletap_frames;

#define BOMB_DOUBLETAP_WINDOW 20
13 changes: 13 additions & 0 deletions th01/main/debug.hpp
@@ -0,0 +1,13 @@
extern bool mode_debug;
extern bool16 mode_test;

/// Memory info screen
/// ------------------
// Hides the graphics layer and enters a memory usage screen with some debug
// options, in a blocking way. Set [input_mem_leave] to `true` to return to
// the game.
void test_mem();

// Reactivates the graphics layer and clears the text layer.
void test_show_game();
/// ------------------
133 changes: 133 additions & 0 deletions th01/main_01.cpp
@@ -0,0 +1,133 @@
/* ReC98
* -----
* Code segment #1 of TH01's REIIDEN.EXE
*/

extern "C" {
#include "ReC98.h"
#include "th01/hardware/input.hpp"
#include "th01/main/bomb.hpp"
#include "th01/main/debug.hpp"

inline void bomb_doubletap_update(uint8_t& pressed, uint8_t& other)
{
if(bomb_doubletap_frames < BOMB_DOUBLETAP_WINDOW) {
pressed++;
} else {
bomb_doubletap_frames = 0;
pressed = 1;
other = 0;
}
}

void input_sense(bool16 reset_repeat)
{
extern uint8_t input_prev[16];
int group_1, group_2, group_3, group_4;

if(reset_repeat == true) {
input_prev[0] = 0;
input_prev[1] = 0;
input_prev[2] = 0;
input_prev[3] = 0;
input_prev[8] = 0;
input_prev[9] = 0;
input_prev[10] = 0;
input_prev[11] = 0;
input_prev[4] = 0;
input_prev[5] = 0;
input_prev[6] = 0;
input_prev[7] = 0;
input_prev[12] = 0;
input_prev[13] = 0;
// Yup, no reset for 14 or 15.
input_bomb = 0;
return;
}
#define bomb_doubletap_shot input_prev[12]
#define bomb_doubletap_strike input_prev[13]

group_1 = key_sense(7);
group_2 = key_sense(5);
group_3 = key_sense(8);
group_4 = key_sense(9);
group_1 |= key_sense(7);
group_2 |= key_sense(5);
group_3 |= key_sense(8);
group_4 |= key_sense(9);

input_onchange_bool_2(0, 8,
input_up, (group_1 & K7_ARROW_UP), (group_3 & K8_NUM_8)
);
input_onchange_bool_2(1, 9,
input_down, (group_1 & K7_ARROW_DOWN), (group_4 & K9_NUM_2)
);
input_onchange_flag_2(2, 10,
input_lr, INPUT_LEFT, (group_1 & K7_ARROW_LEFT), (group_3 & K8_NUM_4)
);
input_onchange_flag_2(3, 11,
input_lr, INPUT_RIGHT, (group_1 & K7_ARROW_RIGHT), (group_4 & K9_NUM_6)
);
input_onchange(4, (group_2 & K5_Z), {
input_shot = true;
bomb_doubletap_update(bomb_doubletap_shot, bomb_doubletap_strike);
} else {
input_shot = false;
});
input_onchange(5, (group_2 & K5_X), {
input_strike = true;
bomb_doubletap_update(bomb_doubletap_strike, bomb_doubletap_shot);
} else {
input_strike = false;
});

input_pause_ok_sense(6, 7, group_1, group_2);

if(
(bomb_doubletap_strike >= 2 && bomb_doubletap_shot >= 2) ||
(input_lr == (INPUT_LEFT | INPUT_RIGHT) && input_shot)
) {
input_bomb = true;
bomb_doubletap_strike = 0;
bomb_doubletap_shot = 0;
}

if(mode_test == true) {
group_1 = key_sense(6);
group_1 |= key_sense(6);

// Hilarious bug: test_mem() itself renders a sub-screen in a blocking
// way, and senses input after a 3-frame delay, thus recursing back
// into this function. Therefore, test_mem() will also be recursively
// called for every 3 frames you're holding this key.
// [input_prev[14]], which is supposed to prevent that, isn't set
// until test_mem() returns, making this variable entirely pointless.
input_onchange(14, (group_1 & K6_ROLL_UP), {
input_mem_enter = true;
/* TODO: Replace with the decompiled call
* test_mem();
* once that function is part of this translation unit */
asm { nop; push cs; call near ptr test_mem; }
} else {
input_mem_enter = false;
});
// And since this works as intended and only sets [input_mem_leave] to
// true on the first non-repeated key down event, you need to actually
// press and release this key once for every call to test_mem() to get
// back into the game - even though test_show_game() will make it
// appear as if you're already back in the game.
input_onchange(15, (group_1 & K6_ROLL_DOWN), {
input_mem_leave = true;
/* TODO: Replace with the decompiled call
* test_show_game();
* once that function is part of this translation unit */
asm { nop; push cs; call near ptr test_show_game; }
} else {
input_mem_leave = false;
});
}
}

#include "th01/hardware/input_rs.cpp"

}

0 comments on commit f2543c8

Please sign in to comment.