The Theorical “Proc API”
Of course there’s no such thing as an official “Proc API” that just magically happened to have come with the game. Here I’ll talk about the one that we could define ourselves from what we know (In fact, it already has been done. I’ll be using the names used by the fireemblem8u
decompilation project).
There’s a bunch of routines located in the game that were obviously meant to be used (by the original developpers) to handle Procs. I’ll try to list the most interesting ones here (with their FE8U location), and define their utility. I’ll by using C-Style signatures, so here we go:
ProcState* Proc_Create(const ProcCode* code, ProcState* parent); // FE8U:08002C7C
ProcState* Proc_CreateBlockingChild(const ProcCode* code, ProcState* parent); // FE8U:08002CE0
Those are obviously used to start Procs. You may or may not need the ProcState* pointer for further operations. In the case of Proc_Create
, the parent
argument can also be number between 0 and 7, in which case the Proc’s parent becomes the root of the tree of the corresponding index.
Note: when starting a proc, the Proc Code starts to get executed immediately! So be careful with that, and add in a Yield instruction if you need to.
ProcState* Proc_Find(const ProcCode* code); // FE8U:08002E9C
Looks for a Proc running the passed Proc Code, and returns a pointer to its State. If no Proc is running this Proc Code, returns a null pointer (aka zero).
void Proc_ForEach(void(*func)(ProcState*)); // FE8U:08002F70
void Proc_ForEachWithScript(const ProcCode* code, void(*func)(ProcState*)); // FE8U:08002F98
void Proc_ForEachWithMark(int mark, void(*func)(ProcState*)); // FE8U:08002FC0
Note: “script” is the name used by decomp to refer to proc code.
Calls the function pointed by the passed function pointer for all active Procs/for each Proc running the passed Proc Code/for each Proc whose mark match the passed one.
void Proc_Delete(ProcState* state); // FE8U:08002D6C
void Proc_DeleteAllWithScript(const ProcCode* code); // FE8U:08003078
This ends a Proc/each Proc running passed Proc Code, and all its/their descendants (if any).
void Proc_ClearNativeCallback(ProcState* state); // FE8U:08002E94
void Proc_ClearNativeCallbackEachWithScript(const ProcCode* code); // FE8U:08003094
Note: “native callback” is the term used by decomp to refer to cycle routines.
This clears the onCycle
field in the Proc State of the passed Proc/of each Proc running the passed Proc Code.
void Proc_GotoLabel(ProcState* state, int index); // FE8U:08002F24
Sets next instruction to the first label instruction of index index
(search starts for Proc Code Start) (see the subsection in The Proc Code named “Labels” for details). Also clears onCycle
.
void Proc_JumpToPointer(ProcState* state, const ProcCode* code); // FE8U:08002F5C
Sets next instruction to the one pointed by code
. Also clears onCycle
.
Note: a jump doesn’t change the start code pointer, which means that after a jump, things like labels may not work as intended.
void Proc_SetMark(ProcState* state, int mark); // FE8U:08002F64
Sets mark
field in the Proc State state
to the value of the mark
argument.
void Proc_BlockEachWithMark(int mark); // FE8U:08002FEC
void Proc_UnblockEachWithMark(int mark); // FE8U:08003014
Halts/Resumes all Procs with matching marks. (Useful for pausing entire systems. For example: pausing all map sprites related Procs when entering a stat screen).
void Proc_Run(ProcState* state); // FE8U:08002E84
Executes a Proc Cycle for the Proc, all its descendants (if any) and all its younger siblings (again, if any). This is the routine called for executing entire Proc Trees (by passing the oldest children of its root).
Routine locations in other games
The locations I gave here are the locations of the routines for FE8U, obviously, but don’t panic! We have already located all of those routines also in FE7U, FE7J and FE6 (no FE8J as of now sorry) (big thanks to Teq for doing the FE7U search), and can find all of that and more in my old-ish yet still relevant 6CNotes.txt file, available on the unified docbox.
Let’s make a Proc
Ok so let’s try to put everything we learned in practice and just make a proc.
The Goal: We’ll start our Proc through a simple ASMC
. What it will do is simply Fade into dark, Wait for user input (a key press), and Fade out of dark. Another thing to note is that we want the Proc to be a Blocking Child of the event engine proc (I’ll get to how to do that later).
We’ll be doing the thing in ASM because I’m nice. However we will want to make use of some linking ability so grab lyn
(or another linker but if you use Event Assembler then you probably want lyn
) instead of your prehistoric “Assemble ARM.bat” thing that you go from Kirb’s beginner pack.
Ok so, first thing we’ll set up our Proc Code. Since we aren’t requiring any kind of setup from the ASMC
ed routine, we don’t have to be scared of early execution, so no need to start with a yield (see my description of the Proc_Create
routine).
We want to Fade into dark. If you know how to do that don’t worry I’ll tell you. What you want to know is that this is done through creating another child proc (that will also be blocking). It sounds complicated but in reality there’s just a routine call involved, so a 0002
instruction will suffice.
We want to wait for user input. We could use 0003
(Set Cycle Routine) for that or use 0014
(Call Routine While). I’m opting towards 0014
since this it seems more fitting for a “waiting for a particular event” scenario.
Next we want to Fade out of dark. Same as for fading into, it’s an external Proc made through a routine call that blocks ours.
Finally, the end code.
Here’s what I have:
.thumb
.global MyProcASMC
.type MyProcASMC, %function
.type MyProc_FadeIntoDark, %function
.type MyProc_CheckUserInput, %function
.type MyProc_FadeOutOfDark, %function
.align
MyProcASMC:
bx lr
.align
MyProcCode:
.word 0x0002, MyProc_FadeIntoDark
.word 0x0014, MyProc_CheckUserInput
.word 0x0002, MyProc_FadeOutOfDark
.word 0x0000, 0
.align
MyProc_FadeIntoDark:
bx lr
.align
MyProc_CheckUserInput:
mov r0, #0
bx lr
.align
MyProc_FadeOutOfDark:
bx lr
.ltorg
.align
Note: I used .word
instead of .short
, yet it will still work because of how little endianness works & we don’t use instructions requiring sarg
.
Now, let’s start the Proc. it won’t do a thing, I’ll admit, we still need to get that done, but let’s start with starting.
As you may have guess before, we are going to be using the Proc_Create
routine… The Proc_CreateBlockingChild
routine that is! Remember what I said about blocking the event engine? yeah that.
See, unlike what you might think, a routine called by an ASMC
does take an argument: a pointer to the Proc State of the Event Engine Proc. So we can simply pass that to the Proc_CreateBlockingChild
to make our Proc blocking. Simple!
Here’s my MyProcASMC
:
.align
MyProcASMC:
push {lr}
mov r1, r0 @ argument r1 = parent
adr r0, MyProcCode @ argument r0 = code (needs to be later in the file, not earlier!)
ldr r3, =(Proc_CreateBlockingChild+1) @ thumb bit!
bl BXR3
@ we don't need the return value so whatever
pop {r0}
bx r0
With Proc_CreateBlockingChild
defined as the address of the corresponding routine and BXR3
a label to a bx r3
instruction.
We can already see if it works by trying to run it with some remarkable effect… Say let’s return 1 (instead of 0) in MyProc_CheckUserInput
. This should softlock the game entirely, since our Proc will never end.
Here’s my lyn
output if you can’t or don’t want to use lyn
for some reason:
PUSH
ORG (CURRENTOFFSET+$1); MyProcASMC:
POP
{
PUSH
ORG (CURRENTOFFSET+$31); _L0000000000060008:
ORG (CURRENTOFFSET+$4); _L0000000000060009:
ORG (CURRENTOFFSET+$4); _L000000000006000A:
POP
SHORT $B500 $1C01 $A002 $4B0E $F000 $F818 $BC01 $4700
BYTE $02 $00 $00 $00
POIN _L0000000000060008
BYTE $14 $00 $00 $00
POIN _L0000000000060009
BYTE $02 $00 $00 $00
POIN _L000000000006000A
BYTE $00 $00 $00 $00 $00 $00 $00 $00
SHORT $4770 $46C0 $2001 $4770 $4770 $46C0 $4718
BYTE $00 $00 $E1 $2C $00 $08
}
Now add ASMC MyProcASMC
in some event and if it softlocks at that point, mission accomplished!
Now, let’s tackle the key input part. It’s fairly easy: see, there’s a struct located at FE8U:02024CC0
(I have docced the details here, and it is also included in the Teq Doq), that contains information about what keys are pressed, which were held, et cetera. Most members mimic the format of the KEYINPUT
Register. What we will do is simply read from the “current” field (+04 short) and see if it’s non-zero:
.align
MyProc_CheckUserInput:
ldr r0, =key_status_buffer
ldrh r0, [r0, #0x04] @ current keys held bits
cmp r0, #0
bne MyProc_CheckUserInput_NonZeroKeyHeld
mov r0, #1
b MyProc_CheckUserInput_End
MyProc_CheckUserInput_NonZeroKeyHeld:
mov r0, #0
MyProc_CheckUserInput_End:
bx lr
(Yeah I know I may have my label names a bit too verbose, but that’s why I like have the ability to have namespaces so much).
Anyway assemble, lyn
, assemble (now with EA) & run and you should be in the same situation as earlier but now you can press a key to exit the not-so-softlocked-anymore state.
Yay progress! Now for the final touch: fading in and out of dark.
There’s a (well 2, one for fading in and one for fading out) Proc that manages that (actually there’s a variety of Procs that do that, but here we will make use of the ones called by the event engine). Like most Procs (but not ours), they have a dedicated “start this Proc” routine. I’ll name them the start_fade_into_dark
(FE8U:08013D08
) and the start_fade_outof_dark
(FE8U:08013D20
) routines. They both take a duration in r0
and a parent Proc State pointer in r1
.
So now to write the actual routines:
.align
MyProc_FadeIntoDark:
push {lr}
mov r1, r0 @ argument r1 = parent
mov r0, #0x10 @ argument r0 = duration
ldr r3, =(start_fade_into_dark+1)
bl BXR3
pop {r0}
bx r0
And do the same with MyProc_FadeOutOfDark
(except you use the other routine). Try it out and yay it worked!
However, I’ll tell you the following: Since we are using 0002
to call our routine, and in that routine we effectively halted our Proc, we probably want that this halt takes effect “immediately”. The thing is: we are in the middle of executing the Proc Instruction, and the Interpreter only checks for wether the Proc is active or not before any interpretation. TL;DR we want to yield here. So let’s add those yield instruction in the Proc Code:
.align
MyProcCode:
.word 0x0002, MyProc_FadeIntoDark
.word 0x000E, 0
.word 0x0014, MyProc_CheckUserInput
.word 0x0002, MyProc_FadeOutOfDark
.word 0x000E, 0
.word 0x0000, 0
There. Done. Well done even. We made a Proc, and it works. On that note I think I have done enough. The goal of this document was to teach you what they are and how they work, and at this point I think I’ve done it all.
Final ASM:
.thumb
.set start_proc_blocking, 0x08002CE0
.set start_fade_into_dark, 0x08013D08
.set start_fade_outof_dark, 0x08013D20
.set key_status_buffer, 0x02024CC0
.global MyProcASMC
.type MyProcASMC, %function
.type MyProc_FadeIntoDark, %function
.type MyProc_CheckUserInput, %function
.type MyProc_FadeOutOfDark, %function
.align
MyProcASMC:
push {lr}
mov r1, r0 @ argument r1 = parent
adr r0, MyProcCode @ argument r0 = code
ldr r3, =(Proc_CreateBlockingChild+1) @ thumb bit!
bl BXR3
@ we don't need the return value so whatever
pop {r0}
bx r0
.align
MyProcCode:
.word 0x0002, MyProc_FadeIntoDark
.word 0x000E, 0
.word 0x0014, MyProc_CheckUserInput
.word 0x0002, MyProc_FadeOutOfDark
.word 0x000E, 0
.word 0x0000, 0
.align
MyProc_FadeIntoDark:
push {lr}
mov r1, r0 @ argument r1 = parent
mov r0, #0x10 @ argument r0 = duration
ldr r3, =(start_fade_into_dark+1)
bl BXR3
pop {r0}
bx r0
.align
MyProc_CheckUserInput:
ldr r0, =key_status_buffer
ldrh r0, [r0, #0x04] @ current keys held bits
cmp r0, #0
bne MyProc_CheckUserInput_NonZeroKeyHeld
mov r0, #1
b MyProc_CheckUserInput_End
MyProc_CheckUserInput_NonZeroKeyHeld:
mov r0, #0
MyProc_CheckUserInput_End:
bx lr
.align
MyProc_FadeOutOfDark:
push {lr}
mov r1, r0 @ argument r1 = parent
mov r0, #0x10 @ argument r0 = duration
ldr r3, =(start_fade_outof_dark+1)
bl BXR3
pop {r0}
bx r0
.align
BXR3:
bx r3
.ltorg
.align
Final lyn
output:
PUSH
ORG (CURRENTOFFSET+$1); MyProcASMC:
POP
{
PUSH
ORG (CURRENTOFFSET+$41); _L0000000000060008:
ORG (CURRENTOFFSET+$10); _L0000000000060009:
ORG (CURRENTOFFSET+$10); _L000000000006000A:
POP
SHORT $B500 $1C01 $A002 $4B1B $F000 $F832 $BC01 $4700
BYTE $02 $00 $00 $00
POIN _L0000000000060008
BYTE $0E $00 $00 $00 $00 $00 $00 $00 $14 $00 $00 $00
POIN _L0000000000060009
BYTE $02 $00 $00 $00
POIN _L000000000006000A
BYTE $0E $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00
SHORT $B500 $1C01 $2010 $4B0C $F000 $F812 $BC01 $4700 $480A $8880 $2800 $D101 $2001 $E000 $2000 $4770 $B500 $1C01 $2010 $4B06 $F000 $F802 $BC01 $4700 $4718
BYTE $00 $00 $E1 $2C $00 $08 $09 $3D $01 $08 $C0 $4C $02 $02 $21 $3D $01 $08
}
Glossary
-
Proc: What this document is all about.
- Proc Code: Sequence of Proc Code Instructions usually located in ROM that get executed during a Proc Cycle, up until either encountering a Yielding Instruction or the End Instruction.
- Proc Type: Somewhat abstract concept of which a Proc belong to. Different Procs of the same type would share the same Proc State Body layout, and usually run the same Proc Code.
-
Proc Code Instruction: structure present within Proc Code containing 2 shorts and one word of data encoding a command.
- Yielding Instruction: Any Proc Code Instruction that marks the end of a Proc Cycle.
- End Instruction: Specific Proc Code Instruction (null instruction type) that marks the end of one Proc’s code.
- Proc Cycle: Event in which one Proc’s Proc Code Instructions gets executed.
-
Proc Tree: Tree where all Nodes (except the Root) represents Procs.
- Proc Tree Execution: Event where a Proc Cycle happens for each Proc in a Proc Tree, in a determinable order (Parents before Children, Younger Siblings before Older Siblings).
-
Proc State or Proc State Struct: memory block, of which the size is famously
0x6C
, containing information about the State of a Proc.- Proc State Header: Common header for all Proc States, containing general information about the Proc’s family and state of execution, among other things.
- Proc State Body: “Free Space”. The layout of this part of the Proc State is dependent on the Proc Type.
The End
That’s it. Now you know what a Proc is. Or what a 6C
is. Or what a funky struct, coroutine, thread or fiber is. You get the idea.
I hope this was useful to someone. As always, if you have spotted something fishy or have questions and/or suggestions, feel free to ask!
Have another nice day! -Stan