Posted: 04 Nov 2022
Deciphering a mostly obsolete file format still in use by certain parts of the game
While writing another thread I realised that there’s a file format in Psychonauts that I don’t think I’ve ever talked about or even attempted to figure out - ASD files, also known as ActionFiles and ActionTables.
I feel like it’s been too long since we had a thread about the process of figuring out a format and I thought it’d be a nice opportunity to return to this project’s roots, since way back when it started that was pretty much the whole thing.
ASD files are used to describe “Actions” in the game. As far as I can tell, these are used as a way to tie animations to more in-depth data such as being able to run Lua commands.
It’s worth noting the format seems like an obsolete one, I think it was probably supersceded by animation tables defined entirely in Lua, but a lot of classes including Raz himself still use these so I think they’re still important to figure out.
Interestingly, ASD files are completely plaintext which should theoretically make this a fairly straightforward endeavor. Let’s start by just taking a look at one. Here’s the ASD file for the crow feather collectible.
We can already gleam a lot of info from this - first the structure. They start with
ActionFile followed by a kind of header listing the actions alongside some number which seems to generally be 0.
After this we mark the end of the header using
[EndHeaders] and move on to the second segment which constitutes the rest of the file and seems to actually describe the data for the actions though it’s not clear how they’re linked as the order is different.
Each segment of data starts with
[x,y] where X & Y are two numbers and seem to take different forms - some of them contain numbers followed by either animations to play (with some other data) or Lua code (not seen in this file). This type can also reference other actions by name
The second type simply seems to read
Linked x,y where X and Y are another number pair, likely referencing other entries though once again it’s still fairly unclear how everything links together. Finally, we end the file with
We could continue doing this analysis - hypothesising, experimenting, testing - but I think that we’d make more progress by diving straight into Ghidra and decompiling the mechanism the game uses to load these (and how it uses that loaded data).
Finding this function takes no effort - we just look for
ActionFile and immediately find a reference to it in this function. Makes sense.
This function starts like this. It initialises an
param_1 (the file) and begins to read lines from it. The first read checks to make sure it’s a valid file that starts with
ActionFile, if not it errors out. It then reads the header data into
Here’s the loop after some variable name cleanup. We read a line (the action state name) and check if it’s
[EndHeaders]. If it is, we break out. If it isn’t then continue to read the next line (the number, now revealed as a flag). Initialise an
EActionState with that information.
Alright, so let’s check out the
EActionState constructor. It’s very simple and given that
EActionState has no other member functions this is likely just a data storage class.
Let’s fill out the struct data in Ghidra using this info. This doesn’t tell us a ton here since it’s just a simple constructor but it should be helpful later down the line when we see stuff start using this class.
Unfortunately still nothing to tell us what that mysterious flag is for. Let’s ignore it for now and continue to reverse the reader function. This… this is certainly some code. Not positive what is going on here. That limit check is obvious at least.
Finally, we’ve hit the meat of the code - like before we loop until we hit the end marker. First thing we do is read in those two numbers in brackets. Normally we’d have no clue what those are but we’ve got my favourite things in the world here: assertions!
I’ve talked about them before but to reiterate - the little nuggets of code like this are only present in the Linux build of the game on account of it being built in debug mode. They’re meant to check to make sure a condition is true and throw an error if it’s not.
In this case those errors include not only the source file the assert is in (giving us a cool look at the code structure) as well as the line number, but also the actual assertion itself - in this example the assert is making sure that
dwSrc is less than
This is huge because it gives us local and member variable names which are otherwise impossible to know. In this case, we now know that the numbers read from between the brackets are named
dwDst. Source and destination. Very interesting.
So with that in mind we can figure it out: for each definition the two numbers in brackets reference states since the code requires them to both be less than the state count. And the names of
dwDst tell us they define a source state and a destination state.
With these two pieces of information we can suddenly build a much better guess of what this format is in our heads - I suspect that each entry after the header is describing a state transition, telling the game how to get from state X to state Y. Let’s go back to our example.
We define 4 states -
Vertical. Let’s skip the
Linked entry for now straight to
[1,0] - that would mean going from
Fall! (Keep in mind we’re indexing from
0 here, so
So, when going from
Fall, just play the new animation we want (
fall.jan). There are some additional parameters that describe how to play the animation but we’ll worry about figuring those out later.
Let’s keep going -
Vertical. We can make a guess about the
Linked entries here. I think they tell the game to use the same transition as another entry. So
Vertical) are the same as
I’m fairly confident in all of this and keeping it in mind can help figuring out the rest of the function though it’s also important I think to not get completely rooted in it or it could be a detriment.
So, continuing on. The next thing the function does is convert the source and destination to shorts (for later use) and then checks if the character at the current index of the reader is
L. This definitely checking to see if this entry is a
The next chunk of code is about what is expected, with the game retrieving the numbers specified by the
Linked instruction. The asserts here reveal that these numbers are
There’s also this telling us that
local_34 (the count of the
ETempArray<Fixup>) is called
Simple stuff, we finally initialise a new
Fixup with all these values and then add it to the
ETempArray. I won’t go into the
Fixup constructor because similar to before with
EActionState it’s very straightforward and just sets the values. That’s it for this block.
Presumably the fixups in this array will be referenced later on, and so far it’s seeming like my hypothesis is correct about the
Linked entries just telling the game to use the same transition as another state.
That’s all for the “Linked” entries. So let’s head to the other half of this code, which handles setting up the other normal entries.
This starts very simple - or at least appears to. It does the usual thing of limit checking then makes a new
EActionListItem in an array.
This seems super simple at first because rather than parsing out the data in this function, it just passes the reader to the EActionListItem constructor which then parses the data itself. There is one thing that’s bugging me here though…
This is a really weird check. The game keeps parsing action list items until… one of the values in the most recently parsed ones is set to 8?? What is going on here? Maybe it’ll be come clear once we check the parsing code.
I… huh…??? That’s really, really odd. The constructor is basically checking if the current character is a [ (indicating the end of this set of items) and sets a flag to… 8…. which then indicates to the outer code that the end was reached…?
Maybe I’m stupid and this is reasonable but to me it seems very odd. Why not have the outer code check this? Doesn’t this leave a dummy entry at the end of the array? I… I don’t really get it but I’m just going to ignore it.
So when this… thing isn’t happening, this value is used for this instead. It reads the line for this item and then seems to parse the number at the beginning.
There’s then a switch statement, too big for me to post in its entirety of course but it would appear that the number at the start of a line essentially tells the game which command to execute! That makes a lot of sense.
Checking it, the final case is
7 and suddenly that value of
8 up there makes more sense - I imagine these commands were in an enum in development and at the end of it was something like
ACTION_COMMANDS_MAX which they used to denote the final “end” action.
Why exactly this was necessary I’m still not sure but at least 8 wasn’t some insane arbitrary number.
I won’t dive deep into the switch or the inner workings of the parser because the commands are generally understandable and understanding this function in depth likely wouldn’t tell us what each argument does anyway, only understanding where they’re stored is useful.
There isn’t a lot left to talk about when it comes to actually parsing these files. The rest of the main reader function seems to just be responsible for handling linking up the
Linked entries (or
Fixups) to their corresponding real entries. (I wonder if you can chain them?)
To figure out more about stuff like that mystery flag, or the exact nature of the commands each argument takes, it’s necessary to explore outside the reader and examine how those values are actually utilised by the game.
Starting with the mystery flag, we can find it being checked in
EMeshEntity::SetNewAction_internal. It appears to mark an action as
Locked, which from what I can surmise seems to prevent the entity from switching away from an action.
After walking through and annotating the code I am fairly confident in this but what better way to test this than checking it ingame?
The obsolete nature of this system is reinforced by this comment, which not only marks it as OLD but also screams at you to only use this function on the player. So, to be clear, DO NOT USE THIS SYSTEM IN MODS IT IS CLEARLY DEPRECATED
Alright, I built an entity to test it, based on the same entity I built to showcase some unused Raz animations a while back. It uses Oatmeal’s model and has 3 states - Stand, Pet and Pet2. It cycles through them in a set order. Pet2 has the flag set and should be locked.
(Yes, Oatmeal has petting animations and yes I am still very upset that they’re unused)
And here’s the corresponding ASD file. As you can see, we’ve set the flag on Pet2 so it should lock Oatmeal into it and make them unable to exit it once entered. This is probably the first time anybody in the world has written one of these files in almost 2 decades… wild.
Still don’t know what the values passed to command 1 (which plays animation) are but we’ll get to that.
Testing ingame and… we got it! Once the entity enters the Pet2 state, it’s no longer able to exit it! We don’t get to see the game itself complain about it because no debug logging in release builds but our own code confirms it.
Now to figure out the arguments to command 1, which plays an animation file. The first one is obviously the animation to play, and I am almost positive that the second argument is blend time. The precision on the float makes me wonder if they had a tool to make these files.
Easily confirmed ingame by fiddling with the value and watching what happens. As you can see, with all the animation commands having this value set to 5 it takes Oatmeal a very long time (about 5 seconds in fact) to go between them.
For the rest we turn to the function
EMeshEntity::ExecuteAction, responsible for actually running those commands. It gets the command value of the action item then goes into a switch statement for it.
This isn’t actually particularly hard because of a really cool shortcut we can use. This function calls
EMeshEntity::LoadAnim which is also called by
Lua_LoadAnim which is a Lua handler for this function.
ScriptBase.lua then has its own wrapper.
We can cross reference these arguments with
Lua_LoadAnim and solve the arguments for
I think it’d be a pretty safe assumption that the arguments passed through command 1 match this order.
Next is command 2, which is incredibly simple. Command 2 just lets you tell the game to transition to another action, like this:
Its only argument is very obvious, so there’s no mystery to solve here.
Command 3 is fun because it lets you embed Lua code into the transition. You begin command 3, then have a newline, then when you’re done writing your code end it with a
[LUAEND] marker. The newline seems to be important, in this example the top code works but the bottom doesn’t
Just to be sure, proven in this ingame example.
Command 5 seems to be used to set up physics flags in some way. It’s only used by Raz and the thought bubble.
Command 6 is of interest though. It’s again only used by Raz and the thought bubble & it calls down into a sub-handler. It seems to be made to handle stuff super specific to those two things? It’s only used twice and most of the actions go unused.
Not positive what exactly the used ones do but the other two relate to disabling/pausing the player’s input.
And… that wraps it up. We didn’t solve absolutely all the details but once again this is a mostly obsolete system, only really in used in the final game by entities that are quite old (if they haven’t been converted to animation tables in Lua).
A couple of examples include Crispin, emotional baggage and of course Raz himself. This was mostly just for fun as a result but of course since some of those entities are quite relevant (especially Raz!) I think it’s valuable to know how these files work.
Any new mod entities should definitely be using animation tables but who knows, maybe you need to understand or modify an entity that still uses this system.
With this finally figured out, I’m going to get back to writing the thread that prompted this tangent in the first place, which is once again a thread shining a spotlight on a piece of cut content.
Speaking of which, I suppose as a final note it’s worth mentioning that while across the whole Lua scriptbase we have there’s only 42 references to
SetActionTable (which includes some duplicates!) there’s 92 ASD files in the game.
Some of those are ASD files for existing entities that no longer use ASD files, and some seem to be for things that aren’t even in the game/code anymore. Maybe later I’ll comb through them and see if there’s any interesting remnants of cut content in them.
Especially since the ASD files that exist for entities that got converted to animtables will be old and won’t have been updated since the conversion happened. Who knows what might be hiding in ‘em?
Psychonauts, associated logos, branding and game assets are copyright Double Fine Productions and Xbox Game Studios.