• Welcome to Final Fantasy Hacktics. Please login or sign up.
April 18, 2021, 04:56:47 am


Use of ePSXe before 2.0 is highly discouraged. Mednafen/RetroArch is recommended for playing/testing, pSX is recommended for debugging.

Modifying Effects: Animation Location, Multiple Targets, Runtime Palette Change

Started by Glain, May 02, 2019, 08:08:42 pm


Recently I began investigating Effects because I wanted to use some of them for abilities in ways that wouldn't normally work.  For example, some effects only animate on one target, even if the ability hits multiple targets.  Some of them only center around the caster, even if the ability was targeted away from the caster (like the Draw Out effects). 

Here I'm going through three different ways to modify effects:

    * Changing where an Effect animates (target panel vs. source unit, for example)
    * Changing Effects that animate on a single target to animate on multiple targets
    * Changing Effect colors/palette at runtime

There wasn't a whole lot of previous documentation about this, so I had to do quite a bit of investigation, and I'm documenting what I found for posterity.  Choto's notes on effect data locations and routines (from years before!) were really helpful.
  • Modding version: Other/Unknown


Changing where an Effect animates

Using Heaven's Cloud effect (E137.BIN) as an example:
In the Effect header data (this is at the start of E137.BIN but can be more complicated to find in other effects; more detail on how to find this data in the next post):

28 00 00 00
A0 06 00 00
08 08 00 00
48 08 00 00     <-- Emitter control section offset
8C 11 00 00
00 00 00 00
F0 1A 00 00
08 1B 00 00
90 33 00 00
5C 34 00 00

At offset 0x0848 from the start of header data here, we find the number of emitters (12):
02 00 0C 00

There are 16 more bytes of header data.  After that, each emitter has a section that is 0xC4 (196) bytes long.  The fourth byte (offset 3) for each emitter section contains the number that defines where the effect animates (for more detail, see the Effect Graphics routine):

Test values (partial)

Value           Description

0x02            Animates on targeted panel
0x04            Animates on/over source unit
0x06            Animates on each target unit?

For the first emitter section, this value is at offset 0x85F in the file.  Each one after that is the previous value + 0xC4.  Given that there are 12 emitters, this means the final value is at 0x10CB.
Changing the byte from 04 to 02 at the specified locations (excluding the 06 at 0x1007) made Heaven's Cloud animate on the target panel!

I figured out how to do this by using a debugger to find the correct offsets to change for a given effect.  As of now, it isn't enough to just visually inspect the data as I don't think we (currently) know enough about the details of the animation data.

There is a data structure created before an effect is loaded that is stored at RAM address 0x801bad0c.  It's sometimes referenced in the effect code and includes a variety of data.  As I was testing with a Draw Out effect (Heaven's Cloud), my first major breakthrough in trying to change the location of the effect animation is when I realized that the acting unit's unit misc ID was being stored at 0x801badb2 (0xa6 offset from the 0x801bad0c data) and decided to try changing it.  That changed which unit the effect animated over!

That ID was being read in a few places, yielding some interesting results:

0x1badb2 read:
0x1a49c8    <-- Changes which unit does the casting animation
0x1ab050    <-- Changes which unit the camera rotates toward
0x1ab408    <-- Changes which unit the camera centers on
0x1a9108    <-- Changes which unit the effect animates over
0x1a623c    <-- ?

I also realized the data structure contained some more... interesting data:

0x1badca - x coord
0x1badcc - z coord (map level)
0x1badce - y coord

These change location of Ultima's effect but not Heaven's Cloud's...

0x1a62e0    <-- Changes effect animation's height (based on tile)?
0x1a64e0    <-- Changes where effect animates!
0x1a7cf0    <-- ?
0x1aaba8    <-- ?
0x1aabbc    <-- Changes where the camera centers

While interesting, this still didn't really tell me why some effects animated over the target panel and some over the source unit.  However, in partially documenting the large Effect Graphics routine, I came upon decision code that sometimes loaded the target panel location... and sometimes source unit data... and sometimes a few other things!

The main decision code starts at 0x801a646c, where I put an execute breakpoint.  The pointer being used here is r23's value + 2, and the test value is in r3.  Here I was able to which values in the effect data the decision code was looking at:

Execute breakpoint 0x801a646c

Heaven's Cloud:

r23             r3          file offset (r23 - 0x801c24fd) (high order byte)

0x801c2fa8      0x0400      0xAAB
0x801c2e20      0x0400      0x923
0x801c337c      0x0400      0xE7F
0x801c306c      0x0400      0xB6F
0x801c3130      0x0400      0xC33
0x801c2ee4      0x0400      0x9E7
0x801c31f4      0x0400      0xCF7
0x801c32b8      0x0400      0xDBB
0x801c2d5c      0x0400      0x85F
0x801c35c8      0x0400      0x10CB
0x801c3440      0x0400      0xF43
0x801c3504      0x0600      0x1007

There was still one issue, though:  the camera still rotated toward and centered on the source unit, not the target panel.
From the header data:

28 00 00 00
A0 06 00 00
08 08 00 00
48 08 00 00     
8C 11 00 00
00 00 00 00
F0 1A 00 00
08 1B 00 00    <-- Relevant offset that contains camera section
90 33 00 00
5C 34 00 00

The start of this section is 0x1b08.  There are three offsets that point you to camera commands:
   + 0x0806: 0x230e (Values: 7)
   + 0x168a: 0x3192 ((Non-zero) Values: 0x0444, 0x0543)
   + 0x185a: 0x3362 ((Non-zero) Values: 0x04c7, 0x0507)

The code does a bitwise-and with 0x01e0 on these values to get the final value it checks:

Test values (partial)
0x01c0: Rotate (or center) toward target panel ?
0x0140: Rotate (or center) toward source unit ?
0x0100: Rotate (or center) back to original point ?

By changing 0x543 to 0x5C3 (changing 0x43 to 0xC3 at file offset 0x3196), both the camera rotation and center went to the target panel instead of the source unit!

Camera routines:
0x1aad98 rotate
0x1ab1d4 center

Figuring out where the effect camera routines were, I set a breakpoint on the routine that calls the effect camera rotation routine...

Execute breakpoint 0x801ad1c0:

(Heaven's Cloud)
ptr             origValue   testValue   ptrOffset       ptr + ptrOffset     file offset (-0x801c2500)
0x801c400c      0x0543      0x0140      0x168a          0x801c5696          0x3196         
0x801c4008      7           0           0x0806          0x801c480e          0x230e         
0x801c400c      0x0507      0x0100      0x185a          0x801c5866          0x3366   

ptr             origValue   testValue   ptrOffset       ptr + ptrOffset
0x801c4ba2      0x0421      0x0020      0x168a          0x801c622c
0x801c4ba6      0x05c3      0x01c0      0x168a          0x801c6230
0x801c4bae      0x04c3      0x00c0      0x168a          0x801c6238
0x801c4ba2      0x0583      0x0180      0x0806          0x801c53a8
0x801c4ba2      0x0507      0x0100      0x185a          0x801c63fc

These were move involved to trace because of branching code, but the first value in each effect seemed to stand out.

Here is a patch that would make the changes:

<Patch name="Change E137 to animate on target panel">
    <Location file="EFFECT_E137_BIN" offset="AAB" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="923" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="E7F" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="B6F" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="C33" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="9E7" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="CF7" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="DBB" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="85F" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="10CB" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="F43" mode="DATA">
    <Location file="EFFECT_E137_BIN" offset="3196" mode="DATA">
  • Modding version: Other/Unknown


Changing Effects that animate on a single target to animate on multiple targets

My example effect for this one is Knife Hand / Damage Split (ID 0x155 = 341) (E341.BIN), which only animates on a single target.

At 0x801b48d0 in RAM is a table of data that defines where the effect file header is stored (in RAM) for each effect ID.
For ID 0x155, the table location is: 0x155 * 4 + 0x801b48d0 = 0x801b4e24
The value at 0x801b4e24 is 0x801c3cfc, the RAM location where the header data starts.
Effect files are loaded into RAM at 0x801c2500, so this results in a file offset of 0x801c3cfc - 0x801c2500 = 0x17FC.

Most effects have their header data stored right at the start of the file, but this one is different.  The start of this effect file is actually... an ASM subroutine.  More on that later.

Anyhow, at 0x17FC in the file, we find the header data.  These are offsets (from the start of header data) of where each section of the effect file starts.  Choto documented these (and a whole bunch of info about effect files) with a bunch more detail but I'm not sure if it's anywhere on the Wiki!

28 00 00 00
8C 02 00 00
E0 02 00 00     <-- Effect script offset (from start of header data)
08 03 00 00
F0 06 00 00
00 00 00 00
54 10 00 00
6C 10 00 00
F4 28 00 00
4C 29 00 00

"E0 02 00 00" is 0x02E0 in little endian.  0x17FC + 0x02E0 = 0x1ADC, where the effect script starts in the file.  Essentially, it's a series of script instructions that tell the effect how to animate.

Broken down by script instruction, and with the offsets shown (from the start of the effect script data), it looks like this (hex):

00: 05 30
02: 06 00 5F 00
06: 2A 00
08: 27 00
0A: 1F 00 26 00

0E: 1D 00 1A 00
12: 28 00
14: 25 00
16: 00 00 0E 00

1A: 25 00
1C: 16 00 00 00 26 00
22: 00 00 1A 00
26: 04 00

When run, it's like a header section, a loop, and an ending section.
Header section commands: 05, 06, 2A, 27, 1F
Loop commands: 1D, 28, 25, 00
Ending section commands: 25, 16, 00, 04

Compared to an effect that animates on multiple targets, say, Fire:

00: 05 20
02: 27 00
04: 1F 00 22 00

08: 1E 00 16 00
0C: 29 00 24 00
10: 25 00
12: 00 00 08 00

16: 25 00
18: 16 00 00 00 22 00
1D: 00 00 16 00
22: 04 00

24: 1D 00 30 00
28: 28 00
2A: 25 00
2C: 00 00 24 00

30: 25 00
32: 16 00 00 00 3C 00
38: 00 00 30 00
3C: 04 00

Header section commands: 05, 27, 1F
Loop commands (1): 1E, 29, 25, 00       <-- This seems to handle multiple targets
Ending section (1) commands: 25, 16, 00, 04
Loop commands (2): 1D, 28, 25, 00
Ending section (2) commands: 25, 16, 00, 04

Here is a somewhat general idea of what some of these script instructions are:

ID          Bytes       Desc

0x00        4           Jump (unconditional)
0x04        2           End Effect
0x05        2           Begin Effect
0x06        4           Setup Custom Routine Pointer
0x16        6           Decision branch?
0x1d        4           Conditional branch (Timing, duration?)
0x1e        4           Conditional branch (multi-target?)
0x1f        4           Conditional branch (based on hit counter)?
0x25        2           Effect processing (advance frame)?
0x27        2           Store motion data?
0x28        2           Graphics (timing?)
0x29        4           Graphics (multi-target?), can branch
0x2a        2           Clear temp data?

(You can also figure out the order that script instructions are being run by setting an execute breakpoint at 0x1a4d44 and noting which IDs are run in which order.)

Damage Split's header section is bigger because it has to set up a pointer to its own custom subroutine which it defines.

The key difference is that Damage Split doesn't have the first loop and ending section.  If we add it in:

00: 05 30
02: 06 00 5F 00
06: 2A 00
08: 27 00
0A: 1F 00 28 00
0E: 1E 00 1C 00
12: 29 00 2A 00
16: 25 00
18: 00 00 0E 00
1C: 25 00
1E: 16 00 00 00 28 00
24: 00 00 1C 00
28: 04 00
2A: 1D 00 36 00
2E: 28 00
30: 25 00
32: 00 00 2A 00
36: 25 00
38: 16 00 00 00 42 00
3E: 00 00 36 00
42: 04 00

As just the hex:

05 20 06 00 5F 00 2A 00 27 00 1F 00 28 00 1E 00
1C 00 29 00 2A 00 25 00 00 00 0E 00 25 00 16 00
00 00 28 00 00 00 1C 00 04 00 1D 00 36 00 28 00
25 00 00 00 2A 00 25 00 16 00 00 00 42 00 00 00
36 00 04 00

This seems to actually work as a pretty good all-purpose multi-target effect script for anything that doesn't have a custom routine or has one at the start of the file.  If there is another location for the custom routine then the 5F might need to change.

We can then replace the old effect script hex with the new effect script hex in the effect file (moving the rest of the file down). (This can be done in HxD by highlighting the old effect script and doing a Paste Insert with the new hex)

That handles the effect script itself, but this just added some bytes to the file.  In the case of Damage Split, the new script was 0x1C = 28 bytes larger.  That means the header section offsets have to be updated, way back up at file offset 0x17FC.

New offsets:

28 00 00 00
8C 02 00 00
E0 02 00 00     <-- Effect script offset (from start of header data)
24 03 00 00
0C 07 00 00
00 00 00 00
70 10 00 00
88 10 00 00
10 29 00 00
68 29 00 00

Everything below the effect script offset has 0x1C added (except for the 00 00 00 00).

Saving that and re-importing that effect file into the ISO, the Knife Hand / Damage Split effect can now animate over multiple targets. 

Since the file size changed, the file info in the ISO also has to change to reflect the new file size or you won't be able to import it.  I used TOC Changer for this.
We can get away with changing the file size as long as the size on disk of the file doesn't change.  In this case, the file size goes from 34124 to 34152 bytes but the size on disk remains at 36864 bytes.
  • Modding version: Other/Unknown


Changing Effect colors/palette at runtime

I was able to place a hook into the code at 0x801a1930 in what I'm calling the Effect Stage Processing routine.
This is after the effect has been loaded but before the palette setup has been run.  (If you do hook this code, just remember that you still have to load the effect ID (from 0x801c24d0) as a parameter for the next routine...  I may or may not have crashed the game a few times and/or turned units into glitch blocks by forgetting to do this...)

To find the palette data:

Effect data pointer = Word (4-byte value) loaded from (EffectID * 4) + 0x801b48d0
Palette offset = Word (4-byte value) loaded from (Effect data pointer + 0x24)
Palette pointer = Effect data pointer + Palette offset

Code to get palette pointer may look something like this:
(If effect ID is in r4/a0)

sll     t1, a0, 2
lw      t2, 0x801b48d0 (t1)

lw      t1, 0x24 (t2)       #   Effect palette data offset

addu    t1, t2, t1          #   Effect palette data pointer

An effect palette contains 256 (0x100) colors at 2 bytes each, so it's 512 (0x200) bytes long.  It appears from looking at the code that there is actually another palette stored directly after the first one that is usually just all zeroes, but overall the palette data seems to be treated as two 256-color palettes next to each other for a total of 1024 (0x400) bytes.

The color information for each palette entry, by bit, is [ABBBBBGG][GGGRRRRR] where the bracketed sections are the bytes and the high order byte is listed first (they would be reversed in RAM).  Alpha value is 1 bit and RGB are 5 bits each (values ranging from 0 to 31).

Extracting the ARGB values from the combined value:

red = color & 0x1f
green = (color & 0x3d0) >> 5
blue = (color & 0x7c00) >> 10
alpha = color >> 15

Re-combining ARGB values back into a single color value (assuming values are in bounds):

color = red | (green << 5) | (blue << 10) | (alpha << 15)

Whatever changes you make to the colors here at runtime should be reflected in the displayed effect!
  • Modding version: Other/Unknown


You should check out Chotos Effect Editor. It lets you edit most thing involving effects and how they work.
  • Modding version: PSX
<@Angel> Teach a man to fish and he'll open up a fishery to compete against yours.

Journey of the Five Youtube ChannelThe Lion War Current Status
Jot5 Leader :: Eventer :: OtherTLW Leader :: Eventer :: Other


I did poke around with the Effect Editor somewhat.  The effect Code Scripts section doesn't seem quite right, at least not for the effects I tested.

But the Emitter Control and Effect Timing/Control sections have some juicy knowledge buried in there regarding effect location/camera movement/rotation that I didn't notice on the first go-around.  This allows the proper data to be pinpointed without needing a debugger.  I've updated the relevant post to use the easier method.

Potentially, you could just make some of these changes in the editor directly, but it's good to know where the data is and/or how to find it.
  • Modding version: Other/Unknown



We were able to make this phoenix summon with it. Along with a hack that makes it damage enemies and revive allies within its area. Of course the latter bit was not done with the effect editor lol
  • Modding version: PSX
<@Angel> Teach a man to fish and he'll open up a fishery to compete against yours.

Journey of the Five Youtube ChannelThe Lion War Current Status
Jot5 Leader :: Eventer :: OtherTLW Leader :: Eventer :: Other


Hey Elric how did you arrange the image on thus one? I made a Phoenix myself but I was limited by the width of Salamander in order to replace it with the other graphic.
  • Modding version: PSX
Grrr, arwg, hiss, and some other zombie noises...
  • Discord username: Heisho


Chotos effect editor had a lot to do with it. Lots of edits needed to be done to the sheet and how its read. Choto worked this one out himself using his effect editor, since hes part of thr Jot5 team, so i couldnt tell you how he did it exactly
  • Modding version: PSX
<@Angel> Teach a man to fish and he'll open up a fishery to compete against yours.

Journey of the Five Youtube ChannelThe Lion War Current Status
Jot5 Leader :: Eventer :: OtherTLW Leader :: Eventer :: Other