2015-02-24 15:17:50 +00:00
|
|
|
/* ReC98
|
|
|
|
* -----
|
2015-03-03 05:47:23 +00:00
|
|
|
* Main include file
|
2015-02-24 15:17:50 +00:00
|
|
|
*/
|
|
|
|
|
2015-03-03 05:47:23 +00:00
|
|
|
#include <master.h>
|
2015-03-16 21:35:52 +00:00
|
|
|
#include <stddef.h>
|
|
|
|
|
|
|
|
// master.lib extensions
|
|
|
|
// ---------------------
|
|
|
|
#define palette_entry_rgb_show(fn) \
|
|
|
|
palette_entry_rgb(fn); \
|
|
|
|
palette_show();
|
|
|
|
// ---------------------
|
2015-03-03 05:47:23 +00:00
|
|
|
|
2015-03-01 21:52:25 +00:00
|
|
|
// Macros
|
|
|
|
// ------
|
|
|
|
#define CLAMP_INC(val, max) \
|
|
|
|
(val)++; \
|
|
|
|
if((val) > (max)) { \
|
|
|
|
(val) = (max); \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define CLAMP_DEC(val, min) \
|
|
|
|
(val)--; \
|
|
|
|
if((val) < (min)) { \
|
|
|
|
(val) = (min); \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define RING_INC(val, ring_end) \
|
|
|
|
(val)++; \
|
|
|
|
if((val) > (ring_end)) { \
|
|
|
|
(val) = 0; \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define RING_DEC(val, ring_end) \
|
|
|
|
(val)--; \
|
|
|
|
if((val) < 0) { \
|
|
|
|
(val) = ring_end; \
|
|
|
|
}
|
2015-03-07 16:35:30 +00:00
|
|
|
|
|
|
|
// Resident structure
|
|
|
|
#define RES_ID_LEN sizeof(RES_ID)
|
|
|
|
#define RES_ID_STRLEN (RES_ID_LEN - 1)
|
|
|
|
#define RES_PARASIZE ((sizeof(resident_t) + 0xF) >> 4)
|
2015-03-01 21:52:25 +00:00
|
|
|
// ------
|
|
|
|
|
2015-03-04 03:28:16 +00:00
|
|
|
#define RES_X 640
|
|
|
|
#define RES_Y 400
|
|
|
|
|
2015-03-01 21:52:25 +00:00
|
|
|
// PC-98 VRAM planes
|
|
|
|
// -----------------
|
2015-02-24 15:17:50 +00:00
|
|
|
typedef enum {
|
|
|
|
PL_B, PL_R, PL_G, PL_E, PL_COUNT
|
|
|
|
} vram_plane_t;
|
|
|
|
|
[C decompilation] [th01/reiiden] Randomly shaped VRAM copy functions, #1
So apparently, TH01 isn't double-buffered in the usual sense, and instead uses
the second hardware framebuffer (page 1) exclusively to keep the background
image and any non-animated sprites, including the cards. Then, in order to
limit flickering when animating the bullet, character and boss sprites on top
of that (or just to the limit number of VRAM accesses, who knows), ZUN goes to
great lengths and tries to make sure to only copy back the pixels that were
modified on plane 0 in the last frame.
(Which doesn't work that well though. When you play the game, you still notice
tons of flickering whenever sprites overlap.)
And by "great lengths", I mean "having a separate counterpart function for
each shape and sprite animated which recalculates and copies back the same
pixels from plane 1 to plane 0", because that's what the new functions here
lead me to believe. Both of them are only called at one place: the wave
function on the second half of Elis' entrance animation, and the horizontal
masked line function for Reimu's X attack animations.
2015-03-10 16:39:00 +00:00
|
|
|
typedef struct {
|
|
|
|
char B, R, G, E;
|
|
|
|
} vram_planar_8_pixels_t;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
int B, R, G, E;
|
|
|
|
} vram_planar_16_pixels_t;
|
|
|
|
|
2015-02-24 15:17:50 +00:00
|
|
|
// Since array subscripts create slightly different assembly in places, we
|
|
|
|
// offer both variants.
|
|
|
|
extern char *VRAM_PLANE[PL_COUNT];
|
|
|
|
extern char *VRAM_PLANE_B;
|
|
|
|
extern char *VRAM_PLANE_G;
|
|
|
|
extern char *VRAM_PLANE_R;
|
|
|
|
extern char *VRAM_PLANE_E;
|
2015-02-24 14:45:38 +00:00
|
|
|
|
[C decompilation] [th01/fuuin] Slow 2x VRAM region scaling
This function raises one of those essential questions about the eventual ports
we'd like to do. I'll explain everything more thoroughly here, since people
who might complain about the ports not being faithful enough need to
understand this.
----
The original plan was aim for "100% frame-perfect" ports and advertise them as
such. However, the PC-98 is not a console with fixed specs. As the name
implies, it's a computer architecture, and a plethora of different, more and
more powerful PC-98 models were released during its lifespan. Even if we only
consider the subset of products that fulfills the minimum requirements to run
the PC-98 Touhou games, that's still a sizable number of systems.
Therefore, the only true definition of a *frame* can be "everything that is
drawn between two Vsync wait calls". Such a *frame* may contain certain
expensive function calls, and certain systems may run these functions slower
than the developer expected, thus effectively leading to more *frames* than
the developer explicitly specified.
This is one of those functions.
Here, we have a scaling function that appears to be written deliberately to
run very slow, which ends up creating the rolling effect you see in the route
selection and the high score and continue screens of TH01. However, that
doesn't change the fact that the function is still CPU-bound, and neither
waits for Vsync nor is iteratively called by something that does. The faster
your CPU, the faster the rolling effect gets… until ultimately, it's faster
than one frame and therefore vanishes altogether. Mind you, this is true on
both emulators and real hardware. The final PC-98 model, the Ra43, had a CPU
clocked at 433 Mhz, and it may have even been instant there.
If you use more optimized algorithm, it also runs faster on the same CPU (I
tried this, and it worked beautifully)… you get the idea.
Still, it may very well be that this algorithm was not a deliberate choice and
simply resulted from a lack of experience, especially since this was ZUN's
first game.
That leaves us with two approaches to porting functions like these:
1) Look at the recommended system requirements ZUN specified, configure the
PC-98 emulator accordingly, measure how much of the work is done in each
frame, then rewrite the function to be bound to that specific frame rate…
2) …or just continue using a CPU-bound algorithm, which will pretty much
complete instantly on any modern system.
I'd argue that 2) is actually the more "faithful" approach. It will run faster
than the typical clock speeds people emulate the games at, and maybe draw a
bit of criticism because of that, but it seems a lot more rational than the
approximation provided by 1). Not to mention that it's undeniably easier to
implement, and hey, a faster game feels a lot better than a slower one, right?
… Oh well, maybe we'll still encounter some kind of CPU-bound animation that
is so essential to the experience that we do want to lock it to a certain
frame rate…
2015-03-09 16:58:30 +00:00
|
|
|
#define ROW_SIZE (RES_X / 8)
|
|
|
|
#define PLANE_SIZE (ROW_SIZE * RES_Y)
|
2015-02-24 14:45:38 +00:00
|
|
|
|
|
|
|
#define PLANE_DWORD_BLIT(dst, src) \
|
|
|
|
for(p = 0; p < PLANE_SIZE; p += 4) { \
|
|
|
|
*(long*)((dst) + p) = *(long*)((src) + p); \
|
|
|
|
}
|
2015-02-25 22:05:20 +00:00
|
|
|
|
|
|
|
#define VRAM_OFFSET(x, y) ((x) >> 3) + (y << 6) + (y << 4)
|
2015-03-14 22:25:50 +00:00
|
|
|
|
|
|
|
void pascal vram_planes_set(void);
|
2015-03-01 21:52:25 +00:00
|
|
|
// -----------------
|
2015-03-14 22:25:50 +00:00
|
|
|
|
|
|
|
// PC-98 keyboard
|
|
|
|
// --------------
|
|
|
|
typedef enum {
|
|
|
|
K0_ESC = 0x01,
|
|
|
|
K0_1 = 0x02,
|
|
|
|
K0_2 = 0x04,
|
|
|
|
K0_3 = 0x08,
|
|
|
|
K0_4 = 0x10,
|
|
|
|
K0_5 = 0x20,
|
|
|
|
K0_6 = 0x40,
|
|
|
|
K0_7 = 0x80
|
|
|
|
} keygroup_0;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K1_8 = 0x01,
|
|
|
|
K1_9 = 0x02,
|
|
|
|
K1_0 = 0x04,
|
|
|
|
K1_MINUS = 0x08,
|
|
|
|
K1_CIRCUMFLEX = 0x10,
|
|
|
|
K1_YEN = 0x20,
|
|
|
|
K1_BACKSPACE = 0x40,
|
|
|
|
K1_TAB = 0x80
|
|
|
|
} keygroup_1;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K2_Q = 0x01,
|
|
|
|
K2_W = 0x02,
|
|
|
|
K2_E = 0x04,
|
|
|
|
K2_R = 0x08,
|
|
|
|
K2_T = 0x10,
|
|
|
|
K2_Y = 0x20,
|
|
|
|
K2_U = 0x40,
|
|
|
|
K2_I = 0x80
|
|
|
|
} keygroup_2;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K3_O = 0x01,
|
|
|
|
K3_P = 0x02,
|
|
|
|
K3_AT = 0x04,
|
|
|
|
K3_LBRACKET = 0x08,
|
|
|
|
K3_RETURN = 0x10,
|
|
|
|
K3_A = 0x20,
|
|
|
|
K3_S = 0x40,
|
|
|
|
K3_D = 0x80
|
|
|
|
} keygroup_3;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K4_F = 0x01,
|
|
|
|
K4_G = 0x02,
|
|
|
|
K4_H = 0x04,
|
|
|
|
K4_J = 0x08,
|
|
|
|
K4_K = 0x10,
|
|
|
|
K4_L = 0x20,
|
|
|
|
K4_PLUS = 0x40,
|
|
|
|
K4_ASTERISK = 0x80
|
|
|
|
} keygroup_4;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K5_RBRACKET = 0x01,
|
|
|
|
K5_Z = 0x02,
|
|
|
|
K5_X = 0x04,
|
|
|
|
K5_C = 0x08,
|
|
|
|
K5_V = 0x10,
|
|
|
|
K5_B = 0x20,
|
|
|
|
K5_N = 0x40,
|
|
|
|
K5_M = 0x80
|
|
|
|
} keygroup_5;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K6_COMMA = 0x01,
|
|
|
|
K6_PERIOD = 0x02,
|
|
|
|
K6_SLASH = 0x04,
|
|
|
|
K6_UNDERSCORE = 0x08,
|
|
|
|
K6_SPACE = 0x10,
|
|
|
|
K6_XFER = 0x20,
|
|
|
|
K6_ROLL_UP = 0x40,
|
|
|
|
K6_ROLL_DOWN = 0x80
|
|
|
|
} keygroup_6;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K7_INS = 0x01,
|
|
|
|
K7_DEL = 0x02,
|
|
|
|
K7_ARROW_UP = 0x04,
|
|
|
|
K7_ARROW_LEFT = 0x08,
|
|
|
|
K7_ARROW_RIGHT = 0x10,
|
|
|
|
K7_ARROW_DOWN = 0x20,
|
|
|
|
K7_HOME_CLR = 0x40,
|
|
|
|
K7_END = 0x80
|
|
|
|
} keygroup_7;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K8_NUM_MINUS = 0x01,
|
|
|
|
K8_NUM_DIV = 0x02,
|
|
|
|
K8_NUM_7 = 0x04,
|
|
|
|
K8_NUM_8 = 0x08,
|
|
|
|
K8_NUM_9 = 0x10,
|
|
|
|
K8_NUM_MUL = 0x20,
|
|
|
|
K8_NUM_4 = 0x40,
|
|
|
|
K8_NUM_5 = 0x80
|
|
|
|
} keygroup_8;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K9_NUM_6 = 0x01,
|
|
|
|
K9_NUM_PLUS = 0x02,
|
|
|
|
K9_NUM_1 = 0x04,
|
|
|
|
K9_NUM_2 = 0x08,
|
|
|
|
K9_NUM_3 = 0x10,
|
|
|
|
K9_NUM_EQUALS = 0x20,
|
|
|
|
K9_NUM_0 = 0x40,
|
|
|
|
K9_NUM_COMMA = 0x80
|
|
|
|
} keygroup_9;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K10_NUM_PERIOD = 0x01,
|
|
|
|
K10_NFER = 0x02,
|
|
|
|
K10_VF1 = 0x04,
|
|
|
|
K10_VF2 = 0x08,
|
|
|
|
K10_VF3 = 0x10,
|
|
|
|
K10_VF4 = 0x20,
|
|
|
|
K10_VF5 = 0x40
|
|
|
|
} keygroup_10;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K11_NUM = 0x02,
|
|
|
|
|
|
|
|
// Couldn't find any info whatsoever on the next three, but they're
|
|
|
|
// listed in MEMSYS.TXT, so...
|
|
|
|
K11_SYMBOL_SHIFT = 0x04,
|
|
|
|
K11_VOWEL_SHIFT = 0x08,
|
|
|
|
K11_CONSONANT_SHIFT = 0x10,
|
|
|
|
|
|
|
|
K11_HOME = 0x40
|
|
|
|
} keygroup_11;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K12_STOP = 0x01,
|
|
|
|
K12_COPY = 0x02,
|
|
|
|
K12_F1 = 0x04,
|
|
|
|
K12_F2 = 0x08,
|
|
|
|
K12_F3 = 0x10,
|
|
|
|
K12_F4 = 0x20,
|
|
|
|
K12_F5 = 0x40,
|
|
|
|
K12_F6 = 0x80
|
|
|
|
} keygroup_12;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K13_F7 = 0x01,
|
|
|
|
K13_F8 = 0x02,
|
|
|
|
K13_F9 = 0x04,
|
|
|
|
K13_F10 = 0x08
|
|
|
|
} keygroup_13;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
K14_SHIFT = 0x01,
|
|
|
|
K14_CAPS = 0x02,
|
|
|
|
K14_KANA = 0x04,
|
|
|
|
K14_GRPH = 0x08,
|
|
|
|
K14_CTRL = 0x10
|
|
|
|
} keygroup_14;
|
|
|
|
// --------------
|