Which finally allows us to use the PLANE_SIZE macro in ASM land. Yeah,
(ROW_SIZE * RES_Y) has finally got old.
Part of P0073, funded by [Anonymous] and -Tom-.
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.
Yup, function parameters that can clearly be identified as coordinates
are by far the fastest way to raise the calculated position
independence percentage. Kinda makes it sound like useless work, which
I'm only doing because it's dictated by some counting algorithm on a
website, but decompilation will want to un-hex all of these values
anyway. We're merely doing that right now, across all games.
Part of P0058, funded by -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].
Which was actually found out by @m1yur1 last year, who thankfully
tweeted this in reference to ReC98:
https://twitter.com/m1yur1/status/1018855232371998720
Thanks a lot! But what makes this more than a piece of trivia is the
fact that the StormySpace release actually *was* bundled with
documentation. Shoutout to the Neo Kobe PC-98 collection for preserving
the original release! This should now greatly simplify any RE efforts
related to TH03's INT 42h calls. (Not *trivialize*, because there's
still all this EGC hardware to be understood…)
And sure, you *were* allowed to use this driver in your own game, but
replacing the copyright with your own isn't exactly the nicest thing to
do… That now makes three library programmers that ZUN didn't credit.
Makes me wonder what makes M. Kajihara so special. Probably the fact
that Touhou has always been about the music for ZUN, first and
foremost.
Part of P0056, funded by rosenrose and [Anonymous].
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.
In which ZUN actively refuses help from master.lib and rips out
everything that isn't immediately related to reading the one joystick
port of the PC-9801-86 sound board.
Maybe there actually was a good reason for that?
Funded by -Tom-.
TASM crashes if you try to define a structure member with the same name
as an existing numeric equate.
And yes, we should have solved this by finally librarifying master.lib,
but that'll be a rather involved subtask as well.
Oh, right, these functions can have parameters. So, let's turn snd_kaja_func()
into a macro that combines the function number and the parameter into the AX
value for the driver.
Yes, all of it. Including the bouncing polygons, of course. And since it's
placed at the end of ZUN's code inside the executable, the code's already
position-independent and fully hackable.
This took long enough, so we're not covering the COM files right now. Like, I
can't even tell how you're supposed to work around the forced word alignment
for the _TEXT segment. Guess we'll just have to decompile all of these in one
go, just like we did with ZUNSOFT.COM.
Also, it really seems as if we're merely trading one ugly workaround for
another in our quest for identical binaries.
Yup, we'll be linking against the original binary blob for the time being.
Don't worry though, we will (and in fact, have to) recompile the libraries
from source, separately for each game, as part of the build process in the
future, but we'll get to that once we've decompiled some of the non-TH01 code.
Yup, the code for the first ZUN Soft logo is now completely position-
independent and ready to be decompiled.
(Also, TIL that the PC-98 GRCG has hardware support for double-buffering
through page flipping. Heh, at least one feature that makes it a viable system
for games...)
Turns out we're not quite done with reduction yet, as there still are a bunch
of macros in master.h that #define PC-98-specific hardware constants and I/O
ports.
This executable is embedded into all 4 versions of ZUN.COM. It was written by
KAJA, not ZUN, so we don't care about anything in there - not that it would
matter for porting anyway. We only need that binary to be able to create
bit-perfect rebuilds of ZUN.COM in the future.
From what I can tell, this program does exactly three things:
• preparing the initial high score list
• writing default settings to HUUMA.CFG
• and allocating the game's resident configuration structure and writing its
segment address to bytes 6-7 of HUUMA.CFG
All that results in a COM file of 6.84 KiB, 83% of which is library code.
That's why C was once seen as a bloated high-level language as well.
Thanks to the LOCALS directive, we do need to break compatibility to TASM at
one point after all. This is the rest we can reasonably change to get at least
through JWasm's first pass without errors while maintaining compatibility to
TASM.
Includes:
* the OPTION syntax to switch in and out of floating-point emulation mode
* REP CMPSB → REPE CMPSB
* Hacks for two 80-byte short jumps
* lack of support for floating-point stupidity ♥
as well as other issues that I covered in previous commits and overlooked in
some files.
From the TASM manual:
"NEAR labels defined with the colon directive (:) are considered block-scoped
if they are located inside a procedure, and you've selected a language
interfacing convention with the MODEL statement. However, these symbols are
not truly block-scoped; they can't be defined as anything other than a near
label elsewhere in the program."
MASM's own local label syntax - declaring labels using @@ and then jumping to
the next and previous @@ using @F and @B - is obviously too limiting for any
longer function, and is not even supported by TASM unless we switch it to MASM
mode completely.
While this is indeed ugly, it only affected 16 files, which is way less than
what we would get in a TASM build without LOCALS. In comparison to having a
modern, cross-platform assembler, that really is a small price to pay.
Really, Borland? You considered it necessary to add directives for object-
oriented programming (in Assembly!) and convenience features like bitfield
records or PUSHSTATE/POPSTATE, yet you never came up with the actually
*helpful* idea of just adding a simple basic pointer data type that depends
on the current memory model's data size?
Like, something like DP... oh wait, that's already taken, as an alias for
DF, the 48-bit 80386 far pointer type.
And this, exactly, is the problem with assemblers. The language itself is
undefined beyond the instructions themselves, but it's obviously very
uncomfortable to program anything with just that, so your assembler needs to
add custom directives on top of that, and of course everyone has different
ideas of the features and use cases that should (and should not) be covered by
syntax. (I'm looking especially at you, NASM.)
And then one of those developers sells their compiler division to a different
company, which then subsequently discontinues all products without ever
releasing the source code, trapping their nice extensions in a single
executable for a single platform that is not even legally available anymore.
tl;dr: http://xkcd.com/927/
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.)
(Damn, the other commit prepared for today is not getting done, why does IDA
have to be so terrible...!)
Anyway, here's a small consistency edit instead.