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-.
In the end, the explosions remain the only constant in these phases,
everything else ("defeat"? "last phase"?) could have been
misunderstood. And yes, the algorithm really treated these 111
constants as potential memory references…
Completes P0058, funded by -Tom-.
> not using the Point structure, which is not only perfectly suitable
here, but would have also generated much better code
Part of P0058, funded by -Tom-.
Welcome to this new [brand] of reverse-engineering, where we only fix
things that might be mistaken for addresses, without looking deeper
into what the actual functions do… unless that directly leaps into
the eye.
Part of P0057, funded by [Anonymous] and -Tom-.
"Splashes"? That's what uth05win calls them. Oh well, "circle" is
already taken, and merely keeping the slices in the item/ directory
won't avoid the name collision when looking at game code.
These are hardly visible on top of regular enemy explosion animations;
you can most clearly see them at the end of the TH04 Extra Stage
midboss. Easy enough to *almost* completely cover right now, even
during a position independence push. Struct strITEM_SPLASH in uth05win.
Part of P0057, funded by [Anonymous] and -Tom-.
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.
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.
The TH05 difference: A ridiculous out-of-bounds structure field access.
Could have done safely by just passing a shot_t*, but ZUN's too cool
for that?
Also, about time we started putting the function prototypes directly
into the C headers, and the C headers *only*, upon reverse-engineering…
Part of P0036, funded by zorg.
It seems that the main th0?/ directories should only contain actual
translation units (of which there are more than previously assumed)
as well as other not really further classifiable slices?
Part of P0036, funded by zorg.
And once again, the TH05 version is un-decompilable. :/ It was pretty
close this time, though, as the entire block between PUSH DI and POP DI
kind of resembles a separate inlined function, in accordance with Turbo
C++'s automatic backup of the DI register, as researched in 7f971a0.
Except that it contains a loop, and Turbo C++ refuses to inline any
function with `do`, `while`, `for`, or `goto`. If it didn't, it would
have totally worked.
Also, yes, C++ class methods are treated identically in this regard.
Oh well. Shot type control functions next, finally!
Completes P0035, funded by zorg.
Including the confirmation that both games have an 8-frame deathbomb
window.
The placement of the variables is all over the place though, what the
hell?
Part of P0034, funded by zorg.
They are supposed to lag behind the player's movement by one frame, and
therefore have to be tracked separately.
I would have also included TH02, if it weren't for the weird sprite
indirection system (not storing X and Y positions directly in the
structure, but taking them from a buffer, with their addresses
always changing, WTF?!) that absolutely needs separate attention.
Part of P0034, 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.
The TH02 version is a piece of cake…
… but TH04 starts turning it into this un-decompilable piece of
unnecessarily micro-optimized ZUN code. Couldn't have chosen anything
better for the first separate ASM translation unit.
Aside from now having to convert names of exported *variables* to
uppercase for visibility in ASM translation units, the most notable
lesson in this was the one about avoiding fixup overflows. From the
Borland C++ Version 4.0 User's Guide:
"In an assembly language program, a fixup overflow frequently
occurs if you have declared an external variable within a
segment definition, but this variable actually exists in a
different segment."
Can't be restated often enough.
Completes P0032, 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.
So yes, we *can* technically decompile from anywhere, by splitting the
segment after the function we want, then .SEQuentially GROUPing the two
segments back together into one virtual segment matching the original
one. This gives us one more point where we can slot in new compilation
units that emit their code into the same segment, in the order given on
the link command line.
*But* since all ASM in ReC98 heavily relies on being assembled in MASM
mode, we then start to suffer from MASM's group addressing quirk,
described in the "Accessing data in a segment belonging to a group"
section in the Turbo Assembler Version 5 User's Guide.
Which then forces us to manually prefix every single function call
• from inside the group
• to anywhere else within the newly created segment
with the group name. It's stupidly boring busywork, because of all the
function calls you *mustn't* prefix. Special tooling might make this
easier, but I don't have it, and I'm not getting crowdfunded for it.
And while this is faster than porting the entire codebase to Ideal
mode, I'll only do this on rare occasions.
Like the upcoming, particularly awful piece of reverse-engineering.
Completes P0031, 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.
I've had the idea to hide this implementation detail and improve code
readability for some time now, but it obviously must still all inline,
to be indistinguishable from a direct assignment of the correct value…
… which, amazingly, it does! Even the static_cast from float to int.
The latter allows us to exclusively implement this for float, since we
do have to express the occasional value smaller than 16.
Who needs macros anyway. Yay, C++ in TH04 and TH05 after all!
Part of 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.
These are used from quite a few places, so it seems best to just name
them after the rect on the playfield they leave out, which is then
typically where the background picture goes.
…*except* that in doing this, we quickly run up against the symbol
length limit of 32 characters. TASM can expand it via the /mv option,
but TCC only lets you *reduce* it to even less. (Why?)
So, my initial idea of `playfield_fill_around_(x)_(y)_(w)_(h)` wouldn't
have worked. But those coordinates are kinda important, I'd say…
Well then, let's just go with `fillm` instead of `fill_around` then.
"Fill with mask at the given coordinates"… yeah, that would work.
Part of P0029, funded by zorg.
… yeah, I don't really like these ambiguous "mode" and "mode change"
variable names either, but what's the alternative? Something something
"sub-phase", to distinguish them from regular phases? Feels way too
early to decide on something more specific. And pretty much nothing I
could come up with right now would have made their inconsistent use any
clearer.
But I need to decide on *something* before moving on, so… eh, let's
just go with what uth05win chose.
Also, yeah, dealing with those 0xFE and 0xFD boss_phase constants some
other time 😛
Also, today in "Weird TASM crashes": Trailing commas at the end of
`public` lines…
Completes P0028, funded by zorg.
Turning these into a struct will be very painful with all the
collisions. Not going to do that until we decompiled every single
reference to those.
Part of P0028, funded by zorg.
As in, "HiScore Entry!!", "Extend!!", "Full PowerUp!!", etc.
Class CFloatingText in uth05win… but I decided against naming it like
that because of some stage/BGM title-related variables in the middle.
Funded by -Tom-.
Many thanks to http://bytepointer.com/tasm/index.htm for providing a
better searchable resource for TASM's default `LEA imm16` → `MOV imm16`
optimization, which we initially had to hack around here.
Funded by -Tom-.
In which ZUN uses little-endian BCD as the exclusive internal storage
for both the current and the high score. Which are then updated using,
once again, ridiculously micro-optimized ASM code that uses the
venerable x86 BCD instructions.
Funded by -Tom-.
So apparently, this way of distorting a circle into an ellipse (?) by
adding a value to the angle for one of the two coordinates isn't
actually widely known in math and doesn't have a name. Fair enough.
Funded by -Tom-.