Raw, uninteresting position independence work. Or maybe not, given that
this was one of the few things that also apply to TH01, and reveal just
how chaotically this game was coded. And so we've got three ways that
ZUN stored regular 2D points: Regularly (X first, Y second), Y first
and X second, and multiple points stored in a structure of arrays…
Completes P0059, funded by [Anonymous] and -Tom-.
Aha! TH03's in-game graphics run in line-doubled 640×200 simply because
that's what this SPRITE16.COM version was written for, making use of
the PC-98 EGC for optimized blitting.
Doesn't seem all *too* optimized though, given that it chooses to
effectively draw every sprite twice, just in case it might overlap with
something that's already in VRAM. It first clears the previous VRAM
content at the drawing position according to the sprite's alpha mask,
then ORs in the actual sprite data. The EGC can do monochrome alpha-
tested blitting just as well as the GRCG, but once the sprite data
covers all bitplanes, ORing is apparently the best it can do by itself?
More technical details on the raster operations in the next push!
Completes P0056, funded by rosenrose and [Anonymous].
That should make this convoluted copypasta a bit easier to read. And
sure, I could have done something about the loop as well, but
SHOT_FUNC_INIT already hides enough control flow behind a macro…
Part of P0037, funded by zorg.
So it's *_put(), inherited from master.lib, for everything just writing
to text RAM, and *_render() for everything more involved? But what
about master.lib's own graphics RAM functions like super_put()? Need to
fix that inconsistency some day.
Once again no decompilation, because…
Part of P0033, funded by zorg.
Rule of thumb going forward: Everything that emits data is .asm,
everything that doesn't is .inc.
(Let's hope that th01_reiiden_2.inc won't exist for that much longer!)
Part of P0032, funded by zorg.
"Yeah, let's do this real quick, how can this possibly be hard, it's
just MOVs and a few function calls"…
…except that these MOVs access quite a lot of data, which we now all
have to declare in the C world, hooray.
Once it came to midbosses and bosses, I just turned them into C structs
after all. Despite what I said in 260edd8… after all, the ASM world
doesn't care about the representation in the C world, so they don't
necessarily have to be the same.
Since these structs can't contain everything related to midbosses and
bosses (really, why did all those variables have to be spread out like
this, ZUN?), it also made for a nice occasion to continue the "stuff"
naming scheme, describing "an obviously incomplete collection of
variables related to a thing", first seen in 160d4eb.
Also, PROCDESC apparently is the only syntactically correct option to
declare an extern near proc?
Also, that `boss_phase_timed_out` variable only needs to be here
already because TCC enforces word alignment for the .data segment…
yeah, it's technically not related to this commit, but why waste time
working around it if we can just include that one variable.
Completes P0030, funded by zorg.
No leading underscore for functions with Pascal calling convention, but
we do have one for all variables, because it's not worth it to put
keywords in front of everything for no reason.
Seemed to have forgotten this rule in 2017?
Part of P0030, funded by zorg.
Right, the last path component in an INCLUDE file name is "limited" to 28
bytes. Turns out it only crashes on every system that *isn't* the main one I
develop on, though…
With TH05 definitely being the Galaxy Brain version of this function.
You'll see once I get to push the C decompilation for that one…
Anyway, that covers all shared input functions of TH02-TH05!
Funded by zorg.
Mostly used for animations, but NOPping out the assignments causes
Ultra-like behavior in some (mid)bosses, at least in TH04 and TH05?
Funded by -Tom-.
Yes, you're reading that correctly. If the cursor is at 255, reading a
16-bit value will fill the upper 8 bits with the neighboring cursor
value, which always is 0xFF.
Funded by -Tom-.
Look at that TH05 vector2_at_opt function. What the hell, the caller is
supposed to set up the stack frame for the function? How do you even get
a compiler to do this (and no, I haven't found a compiler switch)? No
way around writing a separate "optimizer" as part of the compilation
pipeline, it seems.
OK, let's not identify the arrays in a file-based fashion just yet, and
first reduce all shared ZUN code that uses arrays. Less stressful, we'll
have to do this anyway, and I just can't resist the urge to immediately
reverse-engineer everything I find.
And renaming them all to the short filenames they will be decompiled to for
consistency. These functions aren't really immediately hardware-related, as
we've established earlier in the decompilation.
Only one code segment left in both OP and FUUIN! its-happening.gif
Yeah, that commit is way larger than I'm comfortable with, but none of these
functions is particularly large or difficult to decompile (with the exception
of graph_putsa_fx(), which I actually did weeks ago), and OP and MAIN have
their own unique functions in between the shared ones, so…
Also covering the two variations for blitting only every second row or
blitting only a 320x200 quarter, as seen in the endings.
So yeah, there's indeed nothing wrong with piread.cpp. TH03 just uses that
separate function that only blits every second row of an image, and indeed
always loads the entire image as it would appear in a PNG conversion. Here's
what happens if you display these images using the non-interlacing function:
https://www.dropbox.com/s/885krj09d9l0890/th03%20PI%20no%20interlace.png
With TH03 changing the calling convention for most of the code from __cdecl to
__pascal, I've been getting more and more confused about this myself. So,
let's settle on the following consistent syntax for function calls:
* C where the calling convention is actually __cdecl and where TASM's emitted
__cdecl code matches the original binary
* PASCAL where the calling convention is actually __pascal
* STDCALL where the calling convention is actually __cdecl, but where
the caller either defers stack cleanup (summing up the stack size of
multiple functions, then cleaning it all in a single "add sp" instruction)
or where the stack is cleared in a different way (e.g. "pop cx").
Unfortunately though, when using the ARG directive to automatically generate
an appropriate RET instruction for the given calling convention, TASM always
emits ENTER and LEAVE instructions even when no local variables are declared,
which greatly limits the number of functions where we can use that syntax. -.-
For 32-bit immediate values, PUSH by itself is enough. For everything else,
PUSHD works in both TASM and JWasm.
Also, could it be...? Could we actually move to JWasm without breaking the
build in TASM at all?
... and then I end up copying modified versions into the individual game
subdirectories after all, because the changes between games were simply too
drastic. (That's also why I'm counting pfopen() itself twice.)
Only one slice left now, and then we're done with reduction!
Yup, packfiles finally proved that we really have a different set of changes
to master.lib in every game. Also, there are bound to be more of these game-
specific small changes to otherwise identical code in ZUN's own code.
And hey, no need to define that value in the build scripts anymore.
(I've also considered just copying modified versions into the individual game
subdirectories, but it's not too nice to expect people to diff them in order
to actually understand why these copies exist and where the changes actually
are.)