⮜ Blog

⮜ List of tags

Showing all posts tagged score-

📝 Posted:
🚚 Summary of:
P0092, P0093, P0094
⌨ Commits:
29c5a73...4403308, 4403308...0e73029, 0e73029...57a8487
💰 Funded by:
Yanga, Ember2528
🏷 Tags:
rec98+ th01+ file-format+ menu+ score- jank+ waste+

Three pushes to decompile the TH01 high score menu… because it's completely terrible, and needlessly complicated in pretty much every aspect:

  • Another, final set of differences between the REIIDEN.EXE and FUUIN.EXE versions of the code. Which are so insignificant that it must mean that ZUN kept this code in two separate, manually and imperfectly synced files. The REIIDEN.EXE version, only shown when game-overing, automatically jumps to the enter/終 button after the 8th character was entered, and also has a completely invisible timeout that force-enters a high score name after 1000… key presses? Not frames? Why. Like, how do you even realistically such a number. (Best guess: It's a hidden easter egg to amuse players who place drinking glasses on cursor keys. Or beer bottles.)
    That's all the differences that are maybe visible if you squint hard enough. On top of that though, we got a bunch of further, minor code organization differences that serve no purpose other than to waste decompilation time, and certainly did their part in stretching this out to 3 pushes instead of 2.
  • Entered names are restricted to a set of 16-bit, full-width Shift-JIS codepoints, yet are still accessed as 8-bit byte arrays everywhere. This bloats both the C++ and generated ASM code with needless byte splits, swaps, and bit shifts. Same for the route kanji. You have this 16-, heck, even 32-bit CPU, why not use it?! (Fun fact: FUUIN.EXE is explicitly compiled for a 80186, for the most part – unlike REIIDEN.EXE, which does use Turbo C++'s 80386 mode.)
  • The sensible way of storing the current position of the alphabet cursor would simply be two variables, indicating the logical row and column inside the character map. When rendering, you'd then transform these into screen space. This can keep the on-screen position constants in a single place of code.
    TH01 does the opposite: The selected character is stored directly in terms of its on-screen position, which is then mapped back to a character index for every processed input and the subsequent screen update. There's no notion of a logical row or column anywhere, and consequently, the position constants are vomited all over the code.
  • Which might not be as bad if the character map had a uniform grid structure, with no gaps. But the one in TH01 looks like this: And with no sense of abstraction anywhere, both input handling and rendering end up with a separate if branch for at least 4 of the 6 rows.

In the end, I just gave up with my usual redundancy reduction efforts for this one. Anyone wanting to change TH01's high score name entering code would be better off just rewriting the entire thing properly.

And that's all of the shared code in TH01! Both OP.EXE and FUUIN.EXE are now only missing the actual main menu and ending code, respectively. Next up, though: The long awaited TH01 PI push. Which will not only deliver 100% PI for OP.EXE and FUUIN.EXE, but also probably quite some gains in REIIDEN.EXE. With now over 30% of the game decompiled, it's about time we get to look at some gameplay code!

📝 Posted:
🚚 Summary of:
P0084
⌨ Commits:
dfac2f2...110d6dd
💰 Funded by:
Yanga
🏷 Tags:
rec98+ th01+ file-format+ score- tasm+

Final TH01 RE push for the time being, and as expected, we've got the superficially final piece of shared code between the TH01 executables. However, just having a single implementation for loading and recreating the REYHI*.DAT score files would have been way above ZUN's standards of consistency. So ZUN had the unique idea to mix up the file I/O APIs, using master.lib functions in REIIDEN.EXE, and POSIX functions (along with error messages and disabled interrupts) in FUUIN.EXE… :zunpet: Could have been worse though, as it was possible to abstract that away quite nicely.

That code wasn't quite in the natural way of decompilation either. As it turns out though, 📝 segment splitting isn't so painful after all if one of the new segments only has a few functions. Definitely going to do that more often from now on, since it allows a much larger number of functions to be immediately decompiled. Which is always superior to somehow transforming a function's ASM into a form that I can confidently call "reverse-engineered", only to revisit it again later for its decompilation.

And while I unfortunately missed 25% of total RE by a bit, this push reached two other and perhaps even more significant milestones:

  • 📝 In a little over 6 months, we've doubled the amount of reverse-engineered PC-98 Touhou game code! 📈
  • After (finally) compressing all unknown parts of the BSS segments using arrays, the number of remaining lines in the REIIDEN.EXE ASM dump has fallen below TASM's limit of 65,535. Which means that we no longer need that annoying th01_reiiden_2.inc file that everyone has forgotten about at least once.

Next up, PI milestones!

📝 Posted:
🚚 Summary of:
P0070
⌨ Commits:
a931758...327021b
💰 Funded by:
KirbyComment
🏷 Tags:
rec98+ th03+ gameplay+ score-

As noted in 📝 P0061, TH03 gameplay RE is indeed going to progress very slowly in the beginning. A lot of the initial progress won't even be reflected in the RE% – there are just so many features in this game that are intertwined into each other, and I only consider functions to be "reverse-engineered" once we understand every involved piece of code and data, and labeled every absolute memory reference in it. (Yes, that means that the percentages on the front page are actually underselling ReC98's progress quite a bit, and reflect a pretty low bound of our actual understanding of the games.)

So, when I get asked to look directly at gameplay code right now, it's quite the struggle to find a place that can be covered within a push or two and that would immediately benefit scoreplayers. The basics of score and combo handling themselves managed to fit in pretty well, though:

  • Just like TH04 and TH05, TH03 stores the current score as 8 binary-coded decimal digits. Since the last constant 0 is not included, the maximum score displayable without glitches therefore is 999,999,990 points, but the game will happily store up to 24,699,999,990 points before the score wraps back to 0.
  • There are (surprisingly?) only 6 places where the game actually adds points to the score. Not quite sure about all of them yet, but they (of course) include ending a combo, killing enemies, and the bonus at the end of a round.
  • Combos can be continued for 80 frames after a 2-hit. The hit counter can only be increased in the first 48, and effectively resets to 0 for the last 32, when the Spell Point value starts blinking.
  • TH03 can track a total of 16 independent "hit combo sources" per player, simultaneously. These are not related to the number of actual explosions; rather, each explosion is assigned to one of the 16 slots when it spawns, and all consecutive explosions spawned from that one will then add to the hit combo in that slot. The hit number displayed in the top left is simply the largest one among all these.

Oh well, at least we still got a bit of PI% out of this one. From this point though, the next push (or two) should be enough to cover the big 128-byte player structure – which by itself might not be immediately interesting to scoreplayers, but surely is quite a blocker for everything else.

📝 Posted:
🚚 Summary of:
P0063
⌨ Commits:
034ae4b...8dbb450
💰 Funded by:
-Tom-
🏷 Tags:
rec98+ th04+ th05+ file-format+ score- waste+ jank+

Almost!

Just like most of the time, it was more sensible to cover GENSOU.SCR, the last structure missing in TH05's OP.EXE, everywhere it's used, rather than just rushing out OP.EXE position independence. I did have to look into all of the functions to fully RE it after all, and to find out whether the unused fields actually are unused. The only thing that kept this push from yielding even more above-average progress was the sheer inconsistency in how the games implemented the operations on this PC-98 equivalent of score*.dat:

  • OP.EXE declares two structure instances, for simultaneous access to both Reimu and Marisa scores. TH05 with its 4 playable characters instead uses a single one, and overwrites it successively for each character when drawing the high score menu – meaning, you'd only see Yuuka's scores when looking at the structure inside the rendered high score menu. However, it still declares the TH04 "Marisa" structure as a leftover… and also decodes it and verifies its checksum, despite nothing being ever loaded into it
  • MAIN.EXE uses a separate ASM implementation of the decoding and encoding functions :godzun:
  • TH05's MAIN.EXE also reimplements the basic loading functions in ASM – without the code to regenerate GENSOU.SCR with default data if the file is missing or corrupted. That actually makes sense, since any regeneration is already done in OP.EXE, which always has to load that file anyway to check how much has been cleared
  • However, there is a regeneration function in TH05's MAINE.EXE… which actually generates different default data: OP.EXE consistently sets Extra Stage records to Stage 1, while MAINE.EXE uses the same place-based stage numbering that both versions use for the regular ranks

Technically though, TH05's OP.EXE is position-independent now, and the rest are (should be? :tannedcirno:) merely false positives. However, TH04's is still missing another structure, in addition to its false positives. So, let's wait with the big announcement until the next push… which will also come with a demo video of what will be possible then.

📝 Posted:
🚚 Summary of:
P0031, P0032, P0033
⌨ Commits:
dea40ad...9f764fa, 9f764fa...e6294c2, e6294c2...6cdd229
💰 Funded by:
zorg
🏷 Tags:
rec98+ th02+ th04+ th05+ file-format+ hud+ score- tasm+ tcc+ micro-optimization+ jank+

The glacial pace continues, with TH05's unnecessarily, inappropriately micro-optimized, and hence, un-decompilable code for rendering the current and high score, as well as the enemy health / dream / power bars. While the latter might still pass as well-written ASM, the former goes to such ridiculous levels that it ends up being technically buggy. If you enjoy quality ZUN code, it's definitely worth a read.

In TH05, this all still is at the end of code segment #1, but in TH04, the same code lies all over the same segment. And since I really wanted to move that code into its final form now, I finally did the research into decompiling from anywhere else in a segment.

Turns out we actually can! It's kinda annoying, though: After splitting the segment after the function we want to decompile, we then need to group the two new segments back together into one "virtual segment" matching the original one. 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. 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.

So while you now definitely can request any specific thing in any of the 5 games to be decompiled right now, it will take slightly longer, and cost slightly more.
(Except for that one big segment in TH04, of course.)

Only one function away from the TH05 shot type control functions now!

📝 Posted:
🚚 Summary of:
P0018
⌨ Commits:
746681d...178d589
💰 Funded by:
zorg
🏷 Tags:
rec98+ th02+ th04+ th05+ gameplay+ player+ score- uth05win+

What do you do if the TH06 text image feature for thcrap should have been done 3 days™ ago, but keeps getting more and more complex, and you have a ton of other pushes to deliver anyway? Get some distraction with some light ReC98 reverse-engineering work. This is where it becomes very obvious how much uth05win helps us with all the games, not just TH05.

5a5c347 is the most important one in there, this was the missing substructure that now makes every other sprite-like structure trivial to figure out.