OpenKh

This is a centralized place for the documentation and other discoveries about the internal working of Kingdom Hearts games.

View on GitHub

Kingdom Hearts II - AI

The AI is always located into BAR archives (under the extension of MDLX, MAG, etc.). They are used as a scripting language, compiled into bytecode to comunicate with the game engine.

By courtesy of GovanifY, there is a fairly complete documentation of KH2 AI ISA available. This entire documentation is generated from his repository and it is considered the most up-to-date version.

File format

struct header {
    char name[16];
    s32 workSize;
    s32 stackSize;
    s32 termSize;
    struct {
        s32 trigger; // key
        s32 entry; // pointer to code
    } triggers[n];
    s32 endTrigger; // 0
    s32 endEntry; // 0
};

Memory spaces

wp (work memory pointer)

Single uninitialized memory space.

It is like .bss .bss - Wikipedia

char table1[128];
char table2[128];
char table3[128];

void main() {

}

sp (stack memory pointer)

Single stack memory space.

Main usage: local variable storage of local function.

The allocation unit is always 4 bytes: DWORD (double word).

The fixed count of DWORD is allocated by caller, whenever the local function call is occurred.

void func() {
    int var1;
}

tp (temp memory pointer)

Single temporary memory space.

Main usage: function arguments, function return value, input/output storage, etc.

The allocation unit is always 4 bytes: DWORD (double word).

Many opcodes (like push, pop, unary and binary operators, conditional branch operators, arithmetic operators, and so on) use this memory space for input/output purpose.

As representative things, push* and pop* use this space.

push* will write a DWORD to tp, and then tp is increased by 4.

pop* will read a DWORD from tp, and then tp is decreased by 4.

This is used to resolve input/output of function calls of both 2 patterns:

// 2 stack in
// 1 stack out
int func(int a, int b) {
    return a + b;
}

void main() {
    int res = func(123, 456);
}

Another usage is arithmetic operation.

test10036:
 pushImm 12
 pushImm 3
 div ; 12 ÷ 3 → 4
 pushImm 4
 sub ; 4 - 4 → 0
 jnz fail
 jmp pass

bd

Single bdx data memory space starting at 16th byte.

bd pointer is always doubled by 2 when it is accessed. So referenced data must be aligned on 2 byte alignment.

bd pointer and pc (program counter) share same format.

This is used to resolve:

static char text[] = "hello";

void main() {
    puts(text);
}

Opcode cheat sheet

code old new name description
0,0,x pushImm push push int32
0,1,x pushImmf push.s push single
0,2,0 pushFromPSp push.sp push sp + int16
0,2,1 pushFromPWp push.wp push wp + int16
0,2,2 pushFromPSpVal push.sp.d push *sp + int16
0,2,3 pushFromPAi push.bd push bd + int16
0,3,0 pushFromFSp push.d.sp push *( sp + int16)
0,3,1 pushFromFWp push.d.wp push *( wp + int16)
0,3,2 pushFromFSpVal push.d.sp.d push *(*sp + int16)
0,3,3 pushFromFAi push.d.bd push *( bd + int16)
1,x,0 popToSp pop.sp pop *( sp + int16)
1,x,1 popToWp pop.wp pop *( wp + int16)
1,x,2 popToSpVal pop.sp.d pop *(*sp + int16)
1,x,3 popToAi pop.bd pop *( bd + int16)
2,x,0 memcpyToSp memcpy.sp memcpy to ( sp + int16)
2,x,1 memcpyToWp memcpy.wp memcpy to ( wp + int16)
2,x,2 memcpyToSpVal memcpy.sp.d memcpy to (*sp + int16)
2,x,3 memcpyToSpAi memcpy.bd memcpy to ( bd + int16)
3,x,x fetchValue push.d.pop push *( pop() + int16)
4,x,x   memcpy generic memcpy
5,0,0 citf cvt.w.s int to float (convert word to single)
5,0,2   neg 1 to -1, -1 to 1
5,0,3 inv not bitwise not
5,0,4 eqz seqz set 1 if: equal to 0
5,0,5   abs get absolute integer (negative to positive)
5,0,6 msb sltz set 1 if: less than 0 (negative)
5,0,7 info slez set 1 if: less than or equal to 0 (negative and zero)
5,0,8 eqz seqz set 1 if: equal to 0
5,0,9 neqz snez set 1 if: not equal to 0
5,0,10 msbi sgez set 1 if: greater than or equal to 0 (zero and positive)
5,0,11 ipos sgtz set 1 if: greater than 0 (positive)
5,1,1 cfti cvt.s.w float to int (convert single to word)
5,1,2 negf neg.s 1.0 to -1.0, -1.0 to 1.0
5,1,5 absf abs.s get absolute float (negative to positive)
5,1,6 infzf sltz.s set 1 if: less than 0 (negative)
5,1,7 infoezf slez.s set 1 if: less than or equal to 0 (negative and zero)
5,1,8 eqzf seqz.s set 1 if: equal to 0
5,1,9 neqzf snez.s set 1 if: not equal to 0
5,1,10 supoezf sgez.s set 1 if: greater than or equal to 0 (zero and positive)
5,1,11 supzf sgtz.s set 1 if: greater than 0 (positive)
6,0,0   add push a; push b; (a + b)
6,0,1   sub push a; push b; (a - b)
6,0,2   mul push a; push b; (a * b)
6,0,3   div push a; push b; (a / b)
6,0,4   mod push a; push b; (a % b)
6,0,5   and Bitwise and
6,0,6   or Bitwise or
6,0,7   xor Bitwise xor
6,0,8   sll Shift left logical (unsigned)
6,0,9   sra Shift right arithmetic (signed)
6,0,10 eqzv land push a; push b; (a && b)
6,0,11 neqzv lor push a; push b; (a \|\| b)
6,1,0 addf add.s push a; push b; (a + b) float
6,1,1 subf sub.s push a; push b; (a - b) float
6,1,2 mulf mul.s push a; push b; (a * b) float
6,1,3 divf div.s push a; push b; (a + b) float
6,1,4 modf mod.s push a; push b; (a % b) float
7,x,0 jmp b branch unconditionally
7,x,1 jz beqz branch if zero
7,x,2 jnz bnez branch if non-zero
8,x,x gosub jal local function call (16-bit address)
9,x,0   halt halt
9,x,1   exit exit
9,x,2   ret ret
9,x,3   drop drop one value from tp
9,x,5   dup duplicate one value at tp
9,x,6   sin sin
9,x,7   cos cos
9,x,8   degr radian to degree: value / PI * 180.0
9,x,9   radd degree to radian: value / 180.0 * PI
10,x,x   syscall syscall
11,x,x gosub32 jal32 local function call (32-bit address)

Notes:

Reading of comparators

Comparator Reading
EQZ x == 0
GEZ x >= 0
GTZ x > 0
LEZ x <= 0
LTZ x < 0
NEZ x != 0