Leading to slight complications in TH02's Music Room and shot type
selection menus. Thought about leaving those in C for a while, but I
still think it's worth it for the consistency we get with the VRAM
offset functions. Also, we'll have similar code for the main menus of
later games, and I'll surely won't be using C++ when starting out with
these.
Part of P0105, funded by Yanga.
"Hey, let's have separate functions for uint16_t and int16_t… and then
just *not* support negative numbers in the latter" :zunpet:
Part of P0104, funded by Ember2528.
Note how Turbo C++ auto-generates that call to `operator new`, which
you don't see in the decompilation anymore. So yeah, as soon as you add
a constructor, Turbo C++ enforces heap allocation for any instance of
that class, even function-local ones that would otherwise be
stack-allocated.
That's where the bad reputation of C++ comes from, I guess?
Part of P0102, funded by Yanga.
And immediately, we discover another two hardcoded sprites, with, of
course, another set of functions for blitting and unblitting them…
Part of P0099, funded by Ember2528.
"Physics". Not only did ZUN restrict the X velocity to the 5 discrete
states of -8, -4, 0, 4, and 8 (because hey, unaligned blitting is slow
anyway?), but gravity is also only applied every 5 frames.
We're still missing quite a bit of usage code, but these are the core
functions. One of which turned out to be undecompilable, due to… a
rigorously defined instruction order when performing arithmetic between
`double`s and `float`s?! Still, spelling out all this stuff in ASM
seems much better than somehow splitting the data segment, just so that
we can immediately use literals there.
Part of P0097, funded by Ember2528.
So even TH01 wasn't 100% C++ after all. Turns out that this function
was the only instance in all of REIIDEN.EXE where ReC98 previously had
different encodings for identical x86 instructions.
Part of P0096, funded by Ember2528.
Surprise, it's the (terribly suboptimal) setgsta() example function
from the PC-9801 Programmers' Bible! 100% identical, so ZUN must have
read that book as well.
Used for screen shaking effects, as well as the scrolling backgrounds
at the start of the Final Boss stages.
Completes P0095, funded by Yanga.
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.
That's… pretty specific. The only thing on the main menu with this
color is the "1996 ZUN" text at the bottom… probably part of an
effect that we never got to see. Every other idea would be baseless
speculation, given that the snapped buffer isn't used anywhere else.
Part of P0090, funded by Yanga.
Otherwise, TASM would simply convert all EXTRN declarations in those
files to uppercase. Then, the linker would expect them in uppercase,
forcing both the case-sensitive big 32-bit .ASM files *and* the entire
C land to declare them as uppercase as well.
For functions with __pascal convention which are always uppercased
anyway, this makes no difference. It does matter for regular __cdecl
variables, though, and the C declaration of [score_delta] in e6294c2
already showed that we'd then be forced to use macros if we wanted to
pretend that these names still had lowercase characters.
Doing this for every variable referenced in both C land and 16-bit ASM
land gets annoying quickly. So, no need to force this inconsistency if
we can get rid of it by slightly uglifying ASM land.
Part of P0089, funded by [Anonymous] and Blue Bolt.
Again, 11 necessary workarounds, vs. forcing byte aligment in at least
18 places, and that number would have significantly grown in the
future.
Part of P0085, funded by -Tom-.
5 enums where code generation wants an `int`, vs. 11 cases where using
the minimum size is exactly the right default. So it's way more
idiomatic to force those 5 to 16 bits via a dummy element… except that
we can't give it a single, consistent name, because you can't redeclare
the same element in a different enum later.
Oh well, let's have this ugly naming convention instead, which makes it
totally clear that the force element not, in fact, a valid value of
that enum.
Part of P0085, funded by -Tom-.
Which repurpose the .PTN image slots to store the background of
frequently updated VRAM sections, like all the numbers in the HUD.
Future games would simply use the text RAM and gaiji for numbers. Which
would have worked just fine for TH01 as well (especially since all the
functions we've seen so far are aligned to the 8-pixel byte grid), but
it looks as if ZUN simply wasn't aware of gaiji during the development
of TH01.
Part of P0083, funded by Yanga.
All the weird double returns in FUUIN.EXE just magically appear with
-O-! 😮
And yeah, it's a bowl of global state spaghetti once again. 🍝 Named
the functions in a way that would make sense to a user of the API, who
should be aware of typical side effects, like, y'know, a changed
hardware palette… That's how you end up with the supposed "main"
function getting a "_palette_show" suffix.
Completes P0082, funded by Ember2528.
Yet another run-length encoded graphics format, this one being
exclusively used to wastefully store Konngara's sword slash and kuji-in
kill "animation".
But for once, the terrible code generated by inline functions with
non-literal parameters perfectly matches what ZUN wrote here.
Part of P0081, funded by Ember2528.
And of course, TH05 ruins the consistency once again. Sure, the added
file error handling is nice, but we also have changes in the playful
messages (lol), and now need a third distinct optimization barrier
(🤦)… But as it turns out, inlined calls to empty functions work as
well. They also seem closer to what ZUN might have actually written
there, given that their function body could have been removed by the
preprocessor, similar to the logging functions in the Windows Touhou
games. (With the difference that the latter infamously *aren't*
inlined…)
Part of P0077, funded by Splashman and -Tom-.
Huh, C++ wants its `char`s to be unsigned in order to *not* sign-extend
them to 16 bits for comparison against ASCII literals?!
Anyway, that completes TH03's ZUN.COM, with bascially no new C code.
Part of P0077, funded by Splashman and -Tom-.
Necessary to make string literals from the first one end up at their
correct positions in the data segment even after the upcoming
deduplication…
Part of P0076, funded by [Anonymous] and -Tom-.
Oh hey, guarding declarations with complicated types via #ifdef limits
the header files we additionally have to #include!
Part of P0076, funded by [Anonymous] and -Tom-.
Yes, when clipping the start and end points to the screen area, ZUN
uses an integer division to calculate the line slopes, rather than a
floating-point one. Doesn't seem like it actually causes any incorrect
lines to be drawn, though; that case is only hit in the Mima boss
fight, which draws a few lines with a bottom coordinate of 400 rather
than 399. It *might* also restore the wrong pixels at parts of the
YuugenMagan fight, causing weird flickering, but seriously, that's an
issue everywhere you look in this game.
Part of P0069, funded by [Anonymous] and Yanga.
Which store colors as GRB, as suggested by the structure's ID string.
Even master.lib's own functions add an additional XCHG AH, AL
instruction to get colors into and out of this format. MASTER.MAN
suggests that it's some sort of standard on PC-98. It does match the
order of ths hardware's palette register ports, after all.
(0AAh = green, 0ACh = red, 0AEh = blue)
Now we also know why __seg* wasn't used more commonly, as lamented in
c8e8e98. Turbo C++ simply doesn't support a lot of arithmetic on
segment pointers.
And then that undecompilable far call to a function within the same
segment, but inside a different translation unit…
Also, thanks again to Egor for the SCOPY@ hack that debuted in 0460072.
Would have probably struggled with this a lot more without that.
And *then* you realize that TH01 effectively doesn't even use the
resident palette. 😐
And yes, we're procrastinating the whole issue of potentially using
a single translation unit for all three binaries by using a common
segment name, because it *really* isn't that easy.
Completes P0066, funded by Keyblade Wiedling Neko and Splashman.
Meh, can't overload arithmetic operators that take a Subpixel without
generating a needless load and store, even with -Z. But heck, slightly
uglifying subpixel/subpixel arithmetic is exactly the right trade-off.
Completes P0061, funded by Touhou Patch Center.
Yes, decompilation, of something that was so obviously originally
written in ASM. We're still left with two un-decompilable instructions
here, but I'm amazed at how nicely I was able to abstract away all of
the gory register details, leading to pretty clear, readable, and dare
I say *portable* code?! Turbo C++ was once again pretty helpful here:
• `static_cast<char>(_BX) = _AL` actually compiles into `MOV BL, AL`,
as you would have intended,
• and no-op assignments like _DI = _DI are optimized away, allowing
us to leave them in for clarity, so that we can have all parameter
assignments for the SPRITE16 display call in a single place.
I love this compiler.
Part of P0060, funded by Touhou Patch Center.
Yeah… such fun pretending that the original code wasn't copy-pasted.
And yes, Reimu will have to wait until the next one.
Completes P0037, funded by zorg.
And if I don't manage to cover Reimu in this push, it's because ZUN
switched around the cases in half of the functions here… 😵 Here's
some macros instead, to make the code at least *look* as table-driven
and readable as it should have been in the first place.
Part of P0037, funded by zorg.
Ooh, shot position being determined by RNG at lower shot levels? That's
some RNG manipulation TAS potential right there! Maybe.
Part of P0037, funded by zorg.