Much of the information here is old and outdated!
Visit my new site at exphp.github.io/thpages.
Much of the information here is old and outdated!
Visit my new site at exphp.github.io/thpages.
I mostly just looked at this one function, so there's a lot I still don't know. Nonetheless, this single function does write to a huge portion of the ANM VM data structure, and almost no field on it is written by more than one command, meaning that a good 75% or so of the structure can be immediately associated with particular commands just by looking at this function.
I still haven't looked at 000 commands, 300-302 (textures).
This may contain many blatant factual errors. I was taking notes at 3AM and this shit is hard, yo.
All data is from TH16.
So this is obviously just an alias for rotateTime(t, mode, 0, 0, theta)
, right?
...wrong. It writes to a completely separate set of data, time, and mode variables. It enables the same flag as rotateTime
and doesn't appear to enable any others, which suggests to me that both rotations are applied together. (i.e. it appears you can probably use both rotateTime
and rotateTime2d
and have the effects stack).
If I had to guess without any further information, this is probably to allow things to be rotated onscreen after an arbitrary 3d rotation has already been applied.
This implicitly sets the move mode to 8.
The three input positions are saved to zAnmVM.pos_bezier_1
, zAnmVM.pos_bezier_2
, and zAnmVM.pos_final
. (the last of which is the same variable used by 407 moveTime
). Calling 407 moveTime
clears pos_bezier_1
and pos_bezier_2
.
There is no additional data exclusive to moveBezier
beyond those two 3-vectors, and it sets no flags.
I still haven't fully figured out the set of fields that this accesses.
The implementation of this begins similarly to moveTime
, clearing bezier_point_1
and bezier_point_2
. However, it has fewer arguments, and appears to write to a completely separate set of data, time, and mode fields.
434 is some kind of scale-related thing. 435 appears to be its time counterpart.
Each one writes its own set of variables that aren't used by any other opcode. Both enable the same bitflag that is enabled by all of the scale
commands.
415, 416, 425, and 426 are all explained on the chinese wiki but the translated descriptions are confusing to me so I haven't named them. 415 is rotation-related, 416 is scale-related. 425 and 426 are... I dunno, uv-related?
All six write to their own disjoint sets of data, time, and mode variables.
427 and 428 appear to be ___Time
versions of 425 and 426.
Strangely, the first four commands all share a bit flag (using any of those commands enables the bit). The remaining two do not automatically enable this bit for some reason, even though most other ___Time
commands usually do.
A tentative set of names I might give these:
403 alpha1(a)
404 rgb1(r, g, b)
405 alpha2(a)
406 rgb2(r, g, b)
408 rgb2Time(t, mode, r, g, b)
409 alpha2Time(t, mode a)
413 rgb1Time(t, mode, r, g, b)
414 alpha1Time(t, mode, a)
423 colorTimeEnabled(twoBit)
The numeral 1
refers to the "main" color, and the numeral 2
refers to the secondary color used by gradients.
(Initially I thought 408 and 409 were the time functions for the first color, but this assignment makes more sense)
Calling either 413 rgb1Time
or 414 alpha1Time
has the side-effect of causing a 2-bit bitfield to be set to 0b01
. This appears to be related to a statement on the chinese wiki which suggests that 414 alpha1Time
prevents the second gradient color from changing. Unknown423
can be used to set this 2-bit bitfield manually. Presumably, calling Unknown423(0b11)
AFTER calling 413 and 414 will allow both gradient colors to change with time.
(Edit: Dammit, no, things still don't make sense! Most time functions do at some point read the data for their non-time counterpart, and 408 and 409 read the data for color 1, while 413 and 414 read the data for color 2. I'm so confused!)
I have no idea what the wiki is saying about this or how it's supposed to differ from alpha1Time
and alpha2Time
, but it modifies the same fields as alpha2Time
.
EDIT 2020-08-01: Chinese wiki is mistaken; this does not toggle visibility.
On a VM that is a root VM (no parent), this determines what coordinates in the D3D surface correspond to (0,0).
ins_438(0)
: No correction (use the the upper left corner)ins_438(1)
: A setting appropriate for objects always drawn at 640x480 (most game elements). It's the location of ECL's (0,0) during the first few stages of rendering (see TH14 D3D investigation).ins_438(2)
: A setting appropriate for objects drawn at full resolution (text and UI elements). It's the location of ECL's (0,0) in rendering stages that occur after the 384x448 game region is upscaled to the window resolution.Calling setLayer
also changes this flag; it will typically automatically set it to the origin appropriate for that layer's rendering stage.
The description on the wiki makes absolutely no sense.
Judging from the implementation, this is the ___Time
version of 429 repeat
.
This takes some vec3 field—let's call it AnmVM.backup_pos
—and copies it into AnmVM.pos
. It then sets AnmVM.backup_pos
to zero.
backup_pos
also appears to be used for some purpose in the code that runs for all opcodes after exiting the switch statement, but I don't know anything more than that.
Unknown418 calls a pretty important-looking function that uses many, many of the fields stored on the VM like position, texture size, scale, as well as many of the unknown quantities and bitflags. (this function is definitely on my todo list of useful things to look at!)
The function takes an output pointer to a float[3][4]
, and it writes what appears to be (based on values seen in memory) four vertices of a quadrilateral in 3d space. After calling that function, Unknown418 copies the xy
parts of these vertices into a float[2][4]
array stored on the VM. (after dividing the components by 640 and 480)
...sadly, this instruction does not appear to be used in TH16.
Unknown419
seems to be a continuous form of Unknown418
. Basically, Unknown419
sets a bitflag that causes ins_3
to do something similar to Unknown418
on every frame. This instruction also does not seem to be used in TH16!
There is one remaining callsite for the important-looking function. This callsite IS used many times per frame... however, I tried hacking it to always write the quadrilateral [(100, 200), (100, 100), (200, 200), (200, 100)]
, and nothing out of the ordinary happened. Terribly disappointing!
When the layer number falls within particular ranges, some bit flags may be changed. In particular, it always simulates a call to setVisible
, with different arguments based on the layer number. It sometimes also simulates a call to Unknown313(0b001)
. See the bitflags listing at the end for more details.
Using any of these creates a child VM.
There is a single bitflag that is always copied from the parent to child. (see bitflags section at end). The rest are copied from a VM stored in the AnmLoaded
type. (Curiously, at first glance, the code for these instructions also appears to be explicitly enabling one particular flag; but that happens before everything gets copied, so it is overwritten)
Aside from the layer, the one bitflag, and a pointer to a "root" VM, nothing else is copied from parent to child.
The specific instruction you use decides where the object is placed:
world
list. This is a list of ANMs that exist in the game world. When you pause the game, these ANMs stop running. They are only allowed to use layers 0 to 35 (if you try to use others, the game will change the layer on you).ui
list. This is a list of ANMs that exist outside of the game world. These ANMs alre always running. They are only allowed to use layers 36 to 42 (if you try to use others, the game will change the layer on you).world
list. This probably lets you achieve a different z-ordering.ui
list.Unless stated otherwise, all other child creation opcodes insert the child in the same place as 500 does.
This is identical to 500, but then reads two more float args and stores them in the "alternate position" variable for bitflag 530:10 (described later).
Unlike 500-503, this enable the aforementioned flag after copying everything, so it actually gets enabled this time.
This one copies far more state from the parent, including rotation, position, and the alternate position. It still only copies one bitflag.
506 simply combines the effects of 504 and 505.
When a child VM calls this, it will receive the current values of all script variables that are stored per-VM. There's 16 of them in total. I believe they are specifically the following: (this likely contains errors)
10000, 10001, 10002, 10003
10004f, 10005f, 10006f, 10007f
10008f, 10009
10010f, 10011f, 10012f
. These are "random" but there's also some data associated with them (presumably scale factors?) which can be modified by scripts by attempting to store these variables. (well, at least, the first two can be modified. 10012f
has data, but for some reason you can't modify it!)10033f, 10034f, 10035f
This one also creates an object. This time, the argument is the index of some special, "global" hardcoded ANM instruction, and looks it up in some static table. A function pointer from this table is immediately called after the child is created, and then a bunch of data from the table is copied over.
This table is used for other purposes besides 508 (in fact I'm not sure if opcode 508 is ever even used!). It is used by Aya's and Cirno's bombs (both use entry 0x3), and ECL 334 calls this with the user-supplied arg. The table has four entries total in TH16.
This instruction sets a bitflag. Calling Unknown507(1)
on a child VM appears to decouple it from its parent's scale and rotation, so that scaling the parent does not scale the child.
Or at least, this is what it looks like from the small amount of code that I've witnessed using this bitflag so far.
This one subtracts an amount from the current time, simulating a wait.
Useful for waiting a random amount of time, though ironically it's often used with fixed values.
When instruction 3 is encountered, it begins by scanning ahead and doing the following:
-1
is encountered, it resumes normal execution at the instruction after the -1
.label(0)
, then one of two things occurs:
label(-1)
, it will immediately resume normal execution at the instruction after the label(-1)
.label(-1)
, it will enter an important-looking part of the code that appears to perform time evolution.I have tentatively named 3 run
, though I know someone else has named it stop
! Whatever it is, its (large) implementation will contain crucial details to understanding what many of the bitfields and variables actually do.
The following commands have been verified to have no immediate effect inside anm_parser
besides setting bitfields.
(Update Aug 2020: Though I still don't intend to update this document much, I've labeled in bold the ones whose purpose I have since identified)
Unknown305(bool)
Unknown306(bool)
Unknown307(bool)
Unknown311(bool)
(resample mode when scale != 1)Unknown312(twoBit, twoBit)
(scroll mode ("texture address mode" in D3D-speak))Unknown313(threeBit)
(now partially understood as a resolution scaling mode)Unknown314(bool)
(moves around as parent rotates)Unknown315(bool)
Unknown316()
(enables a bit)Unknown317()
(disables that bit)Unknown419(bool)
Unknown431(bool)
Unknown432(bool)
Unknown423(twoBit)
(described above)Unknown507(bool)
(described above)There is space for 64 bitflags stored on the VM, contained in positions 0x530-0x538
in TH16. The game almost always uses 32-bit dword operations to manipulate these, so I group them into two blocks of 32, which I will call 530
and 534
.
Here is a mapping of bitflags to the commands that modify them. This may contain errors. In the following, 530:2
names the 1 << 2
bit, and 530:(4-6)
names a semi-inclusive range of bits.
If you see (unknown) it means I did not spot any usage of it in this function. It may be unused, it may be used by other functions, or it might even be used by this function (and I missed it).
530:0
:
texSelect
530:1
: Enabled by 316, disabled by 317530:2
: Enabled by 401 rotate
, 410 rotateTime
, 414 rotateTime2d
530:3
: Enabled by 402 scale
, 412 scaleTime
, 434, 435530:4
: Enabled by 429 repeat
, 430530:(5-9)
: Set by 303 blendMode
530:9
: (unknown)530:10
: "Alternate start pos flag"
move
). Any command that attempts to read or write this vector will look at this flag to decide which vec3 to access.530:11
: Set by 308 mirrorX
530:12
: Set by 309 mirrorY
530:13
:
0x40b200
.530:14
: (unknown)530:15
: Set by 306530:16
: (unknown)
0x467489
. When disabled, this code computes matrix_410
based on matrix_3d0
and some scale members. When enabled, this code does not overwrite matrix_410
, and additionally uses position members somehow.0x466f1b
.530:(17-19)
:
0b01
by 413 rgb1Time
and 414 alpha1Time
530:(19-21)
: (unknown)530:(21-25)
: Set by 412 anchor
530:(25-30)
:
renderMode
530:(30-32)
: Set by 312 (second arg)
0x4651f1
. It is compared to a field on the giant ANM struct. If it does not match, sub_465a80
is called, and then the aforementioned field is set to the value stored in the bitflags.534:(0-2)
: Set by 312 (first arg)534:(2-5)
:
rotationSystem
0x46757f
534:(5-7)
: (unknown)
0x46e783
, these anms will only be rendered if these flags are 0b00
.0b01
on every ANM VM using that ANM file.0x4684ce
0x46e3b9
0b10
at 0x46e6df
534:7
:
autoRotate
0x41d414
.0x445972
.534:8
: Set by 431534:9
: Set by 432534:10
:
AnmLoaded
.0x405fc
. (oops, I think I a digit)0x41901c
.534:11
: Set by 311534:12
: (unknown)
0x46da53
. Changes some arguments to a text-drawing function.0x429df2
.0x42a570
.534:13
:
ins_3
sees this flag set, it does things that appear to be very similar to instruction 418.534:(14-16)
: (unknown)
0x46f9e0
0x419071
0b10
at 0x45f072
and 0x45f19e
.0b01
at 0x45f2a3
.0b10
at 0x45f289
.0b01
at 0x45fa91
.534:16
:
534:17
: (unknown)534:(18-20)
:
setOrigin
.setLayer(layer)
also affects this:
3 <= layer <= 19
, it calls setOrigin(0b01)
20 <= layer <= 23
, it calls setOrigin(0b10)
setOrigin(0b00)
0b00
at 0x4071da
0x406bb1
0x467f20
(comparison to 0b00
)534:(20-23)
:
setLayer(layer)
also affects this:
20 <= layer <= 31
, or if 36 <= layer <= 42
, it calls Unknown313(0b001)
.0b001
at 0x406deb
.0x4687aa
. Causes some things to optionally be scaled to the window resolution or half of that.0x406a88
. Similar effect.534:23
: Set by 314534:24
: Enabled by 415, 416, 425, 426534:25
:
534:26
:
0x46f84b
.0x46fc9f
.0x46f9c2
.0x46f1d9
.0x46f22e
.534:(27-32)
: (unknown)After discovering how update funcs work, I decided to look at the AnmManager's update funcs. The AnmManager in TH16 (address 0x4c0f48) has two on_tick
functions and 39 (!!!) on_draw
functions. Looking at this provided some intimate insight into the workings of layers, and greatly enhanced my understanding of the anmCreate
variants.
There are two on_tick
functions, one for each of the two lists that the different anmCreate
could have inserted them into.
0x09
(runs pretty early).AnmManager+0xdc
.
36 <= layer <= 42
. (more info about these in on_draw
)24 <= layer <= 30
are mapped to 36 <= layer <= 42
by adding 12.0 <= layer < 24
and invalid layers layer > 42
are all mapped to layer 38
.0x21
(runs pretty late; after all game logic).AnmManager+0xe4
.
0 <= layer < 36
. (more info about these in on_draw
)36 <= layer <= 42
are mapped to 24 <= layer <= 30
by subtracting 12.layer > 42
) are left as is and will likely crash the program.In both of these functions, if bitfield 534:(5-7)
is equal to 0 on any given VM, it will skip this VM (it will not run the VM, and it will not add it to a layer list, so it will not be rendered).
Furthermore, if either the ANM parser returns nonzero, or bitfield 534:(5-7)
is equal to 0b01
prior to running the VM, then it does... something. Presumably deletes the ANM and all of its children. (I'm just not 100% sure how it gets to the part where it removes the node from e.g. the AnmManager+0xdc
list)
Each of the 39 on_draw
function is responsible for drawing a single layer. Some of them also change the D3D transformation matrices and other stuff, causing different groups of layers to have different properties.
Notice: There are 43 layers in TH16, but only 39 on_draw
functions on the AnmManager! The remaining layers are drawn by other things: two are drawn by the Stage
object, and one is drawn by a Direct3D-related object stored directly in memory beginning at 0x4c10d0
.
(NOTE: These details were completely changed in TH17! I'm not 100% sure what happened yet; he removed the dummy AnmVms and I think the linked lists became doubly-linked? I suspect that this change is responsible for the 4 bytes added to sizeof(AnmVm)
in TH17)
The per-layer lists are singly-linked lists whose next
pointers are stored at AnmVm+0x5a8
. As described earlier, they are reconstructed every frame in the on_tick
methods, and then iterated over by the on_draw
methods.
The lists are stored on the AnmManager in the form of a AnmVm[43]
at AnmManager+0x1c6fc30
. Yes, that's an array of AnmVms, not an array of pointers! They are dummy VMs serving as list heads that are solely used for their +5a8
fields. (the byte sequence 01 c6 fc 30
never even appears in the binary data because the 5a8
offset is included into literally all interactions with this array).
To try and give an idea of what the various layers mean, I've compiled a list of all of the layers and the priority of their on_draw
functions (lower runs earlier). There are more things that have on_draw
functions (probably occupying the holes in the priority numbers) but I don't really care to look further into it.
AsciiManager
has a currently poorly-understood field which is an integer 0, 1, or 2 that classifies ASCII strings into groups that each rendered all at once. One could say these are like "layers" for ASCII. Those groups are included as well.
Most of the non-AnmManager
on_draw
functions are not responsible for drawing ANMs, because there's already functions to draw each layer. (most of the on_draw
functions just draw ascii stuff). There's some exceptions to this, like with Player::on_draw
that actually draws an ANM. Not sure what's up with that.
List of on_draw
functions:
(Unknown)
at priority 0x02Stage
at priority 0x03: Renders layers 32 and 33AsciiManager
at priority 0x04: Draws ASCII group 0AnmManager
at priority 0x05: Renders layer 0Stage
at priority 0x06AnmManager
at priority 0x07: Renders layer 1AnmManager
at priority 0x09: Renders layer 2AnmManager
at priority 0x0a: Changes coordinate system. Renders layer 3AnmManager
at priority 0x0b: Renders layer 4Spellcard
at priority 0x0cAnmManager
at priority 0x0d: Renders layer 5(Unknown)
at priority 0x0f: Draws layer 34AnmManager
at priority 0x10: Renders layer 6AnmManager
at priority 0x12: Renders layer 7ItemManager
at priority 0x13AnmManager
at priority 0x14: Renders layer 8AnmManager
at priority 0x15: Renders layer 9AnmManager
at priority 0x16: Renders layer 10EnemyManager
at priority 0x17 (no-op in TH16)AnmManager
at priority 0x18: Renders layer 11AnmManager
at priority 0x1b: Renders layer 12AnmManager
at priority 0x1c: Renders layer 13Player
at priority 0x1dAnmManager
at priority 0x1f: Renders layer 14AnmManager
at priority 0x20: Renders layer 15ItemManager
at priority 0x21 (default placement?)AnmManager
at priority 0x22: Renders layer 16LaserManager
at priority 0x23AnmManager
at priority 0x24: Renders layer 17BulletManager
at priority 0x25AnmManager
at priority 0x27: Renders layer 18ShotType
for player at priority 0x28ShotType
for subseason at priority 0x29AnmManager
at priority 0x2a: Renders layer 19AnmManager
at priority 0x2d: Changes coordinate system. Renders layer 20AnmManager
at priority 0x2e: Renders layer 21Gui
at priority 0x30Gui
again at priority 0x33 (default placement? no-op in TH16)AnmManager
at priority 0x34: Renders layer 22AsciiManager
at priority 0x35: Changes coordinate system. Draws ASCII group 2AnmManager
at priority 0x36: Renders layer 23AnmManager
at priority 0x37: Changes coordinate system. Renders UI layer 36AnmManager
at priority 0x3a: Changes coordinate system. Renders layer 24AnmManager
at priority 0x3b: Renders layer 25AnmManager
at priority 0x3c: Renders UI layer 37AnmManager
at priority 0x3d: Renders layer 26AnmManager
at priority 0x3e: Renders layer 27AnmManager
at priority 0x3f: Renders UI layer 38AnmManager
at priority 0x40: Changes coordinate system. Renders layer 28AnmManager
at priority 0x41: Changes coordinate system. Renders UI layer 39(Unknown)
at priority 0x44MainMenu
at priority 0x45HelpManual
at priority 0x48(Unknown)
at priority 0x4a. (I think this is the pause menu or game over screen)AnmManager
at priority 0x4d: Renders layer 29AnmManager
at priority 0x4e: Renders UI layer 40AnmManager
at priority 0x4f: Renders layer 30AnmManager
at priority 0x50: Renders UI layer 41AsciiManager
at priority 0x51: Changes coordinate system. Draws ASCII group 1AnmManager
at priority 0x52: Renders layer 31AnmManager
at priority 0x53: Renders UI layer 42Where's layer 33? Noooobody knooooooooows 👻
See https://gist.github.com/ExpHP/9547aee1b37682f934da399a7d2b41f4 for a list of on_tick
priorities in almost all games
MotherInf
(called Supervisor
in my ReData) contains a ton of high-level rendering stuff, including some of the most critically important on_draw
funcs, such as those that call SetRenderTarget
.
In modern games, it has 4 AnmVms
, and 3 pointers to IDirect3DSurface9
s. The three surfaces are:
The four AnmVm
s use scripts from text.anm, and they are used to paste stuff rendered from one of the two temporary surfaces to the other. Here's what happens: (DDC)
SetRenderTarget(
Surface0
)
SetRenderTarget(
Surface1
)
SetRenderTarget(
Surface0
)
SetRenderTarget(
Surface1
)
SetRenderTarget(
BackBuffer
)
Spreadsheet if you really want to drown in details https://docs.google.com/spreadsheets/d/1ovAPn9Ib7wxFBuTtT63oXgjfzbEq60ikSu7BxhZz-vM/edit#gid=0