Difference between revisions of "Seiken Densetsu 3:Notes"

From Data Crystal
Jump to navigation Jump to search
(Created page with "== Scripting Engine == Seiken Densetsu 3 includes a script engine, with scripts written in some scripting language. It's fairly full-featured. The interpreter loop and langua...")
 
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
== Scripting Engine ==
+
[[Seiken Densetsu 3:Scripting Language|Scripting Language]]
  
Seiken Densetsu 3 includes a script engine, with scripts written in some scripting language.
+
[[Seiken Densetsu 3:Threading System|Threading System]]
It's fairly full-featured. The interpreter loop and language operations are located in bank C4.
 
 
 
The engine makes use of at least 16 threads, with the context of each thread taking 0x100 bytes. The lower portion of the context is
 
direct page memory, and the upper portion is stack memory. These are in LowRAM.
 
 
 
The scripts themselves have their own execution contexts. This memory is in bank 7F.
 
 
 
The following memory locations and register are used for the interpreter: (this is the LowRAM context)
 
* Y : PC offset (indexed on $0A)
 
* $00 : Memory location of script execution context
 
** Holds both stacks, is #100 bytes in size
 
** Memory operated on starts right afterward
 
* $02 : Local Stack pointer
 
** Push forward
 
** Holds local variables/parameters to script operations
 
** Values are removed as soon as they are used
 
* $04 : Call Stack pointer
 
** Push backward
 
** Holds call stack locations and parameters to called routines
 
** Values must be explicitly popped
 
* $08 : Previous Call Stack frame pointer
 
* $0a-0c : Script program counter
 
* $12 : Current opcode (1 byte)
 
* $14 : Control: End script on bit 1 set
 
 
 
So say $00 is ef60. Then from ef60-f05f is the script execution context memory.
 
Starting at f060 is the game RAM it's operating on.
 
 
 
Scripts are made up of routines, similar to ASM.
 
 
 
At the beginning of a routine, the script will have opcode 04,
 
and at the end, opcode 05 to reverse the operation. This pushes a new call stack frame. The routine will also
 
request a certain amount of space as part of that frame push if it wants some local memory to use.
 
<nowiki>
 
Example:
 
04 02 (new stack frame with 2 bytes of extra space)
 
now $08 holds the location of the previous frame
 
you can now use opcode 16 to get a value relative to this location:
 
16 fe (push $08 - 2 onto local stack)
 
and use that as an address:
 
14 01 00 (push 0001 onto local stack)
 
e0 (write short value to short address)
 
and then to read it:
 
16 fe (push $08 - 2 onto local stack)
 
84 (read short from short address)
 
And there, the routine's using local memory!
 
</nowiki>
 
 
 
The advantage to using stack frames is that it doesn't matter if you balance pushes and pulls to the call stack.
 
Once you call opcode 05, you're back to where you started.
 
 
 
Scripts can also call ASM routines via opcodes 40-47 or FD. These routines will often reference values on the call stack as parameters.
 
 
 
----
 
 
 
All possible opcodes with their ASM locations and functions:
 
 
Naming conventions:
 
* A and B are operands on the local stack, with A being the first one pushed (earlier in memory)
 
* J and K are the two nibbles that make up the script paramter byte (J is high, K is low)
 
 
 
These descriptions are summaries.
 
 
 
<nowiki>
 
00: 25 20 Nothing
 
01-03: 26 20 Nothing
 
06: 25 20 Nothing
 
08-09: 25 20 Nothing
 
0b-0c: 25 20 Nothing
 
0f: 96 20 Nothing
 
d6-d7: CC 1E Nothing
 
ec-ef: CC 1E Nothing
 
fe-ff: CC 1E Nothing
 
 
 
04: 27 20 ; Push new stack frame ; call Stack pointer -= param byte ; Old call stack pointer is preserved in $08, old $08 is preserved on old call stack
 
05: 43 20 ; Pop call stack (complement to 04) ; Restores call stack pointer and $08
 
07: 50 20 ; Jump absolute short/long from call stack ; If short != 0, jump short ; If short == 0, update long following (5 bytes read from stack)
 
0a: 72 20 ; return long
 
0d: 84 20 ; Remove (param byte) bytes from call stack
 
0e: 90 20 ; END script execution
 
10: 97 20 ; Push param to local stack: signed byte -> signed short
 
11: AC 20 ; Push param to local stack: signed byte -> signed long
 
12: C7 20 ; Push param to local stack: byte -> unsigned short
 
13: D4 20 ; Push param to local stack: byte -> unsigned long
 
14: E5 20 ; Push param to local stack: short -> short
 
15: F0 20 ; Push param to local stack: long -> long
 
16: 02 21 ; Push $08 + signed param byte to local stack as short (bit #50 is considered negative instead of bit #80)
 
17: 1A 21 ; Switch/case
 
18: 44 21 ; Push param as offset into memory (param byte + #100 + $00 (short))
 
 
 
19-1f: 57 21 ; Low 3 bits of opcode are combined with param byte for an 11-bit number ; Push param (11-bit) + #100 + $00 (short) to local stack
 
20-2f: 74 21 ; Low 4 bits of opcode are param signed nibble (0 becomes +8) ; Add to short on top of local stack
 
30-3f: 94 21 ; Low 4 bits of opcode are param signed nibble (0 becomes +8) ; Add to long on top of local stack
 
40-47: CA 21 ; Redirects to a routine determined by opcode and param byte (range C0-C7) ; Param byte mutated: * 4 - 1 ; Resolves Y to zero
 
48-4f: 03 22 ; Pushes previous script pointer (+Y) onto call stack ; Script pointer becomes dereferenced: [bank(CA + opcode nibble) (param byte * 2)] ; So there are tables at the beginning of banks D2-D9
 
50-5f: 4D 22 ; Jump relative (adds param to script pointer, resolves Y to zero) ; Low nibble of opcode and 1 param byte become 12-bit signed number
 
60-6f: 83 22 ; Conditional jump relative: If short on stack is truthy, do the same as 50-5F
 
70-7f: C1 22 ; Conditional jump relative: If short on stack is false, do the same as 50-5F
 
 
 
Take specified bits
 
Param byte: JK (K: skipBits, J: takeBits)
 
Skip K bits on the right, and take J on the right after that.
 
Operand and result on local stack.
 
80: 74 06 ; Signed short
 
81: D1 06 ; Signed long
 
82: 54 07 ; Short
 
83: A0 07 ; Long
 
 
 
84: 05 08 ; Dereference short to short
 
85: 16 08 ; Dereference long to short
 
86: 2D 08 ; Dereference short to long
 
87: 49 08 ; Dereference long to long
 
88: 6E 08 ; Dereference short to signed byte (writes 2)
 
89: 8A 08 ; Dereference long to signed byte (writes 2)
 
8a: AC 08 ; Dereference short to unsigned byte (writes 2)
 
8b: C0 08 ; Dereference long to unsigned byte (writes 2)
 
8c: DA 08 ; Dereference short to signed byte (writes 3)
 
8d: FE 08 ; Dereference long to signed byte (writes 3)
 
8e: 28 09 ; Dereference short to unsigned byte (writes 3)
 
8f: 40 09 ; Dereference long to unsigned byte (writes 3)
 
 
 
Compares: result written to local stack as 0000 or 0001
 
90: 5E 09 ; Signed shorts: A < B
 
91: 8F 09 ; Signed longs: A < B
 
92: D2 09 ; Unsigned shorts: A < B
 
93: F2 09 ; Unsigned longs: A < B
 
94: 24 0A ; Signed shorts: A > B
 
95: 55 0A ; Signed longs: A > B
 
96: 98 0A ; Unsigned shorts: A > B
 
97: B4 0A ; Unsigned longs: A > B
 
98: E6 0A ; Signed shorts: A <= B
 
99: 1E 0B ; Signed longs: A <= B
 
9a: 6F 0B ; Unsigned shorts: A <= B
 
9b: 91 0B ; Unsigned longs: A <= B
 
9c: D1 0B ; Signed shorts: A >= B
 
9d: 09 0C ; Signed longs: A >= B
 
9e: 5A 0C ; Unsigned shorts: A >= B
 
9f: 78 0C ; Unsigned longs: A >= B
 
 
 
Math:
 
a0: B8 0C ; Signed shorts: A * B (SMR)
 
a1: 01 0D ; Signed longs: A * B (SMR)
 
a2: 71 0D ; Unsigned shorts: A * B
 
a3: A0 0D ; Unsigned longs: A * B
 
a4: F6 0D ; Signed shorts: A / B (2's C)
 
a5: 6F 0E ; Signed longs: A / B (2's C)
 
a6: 59 0F ; Unsigned shorts: A / B
 
a7: 98 0F ; Unsigned longs: A / B
 
a8: 15 10 ; Signed shorts: A << B
 
a9: 4B 10 ; Signed longs: A << B
 
aa: 97 10 ; Unsigned shorts: A << B
 
ab: B1 10 ; Unsigned longs: A << B
 
ac: DC 10 ; Signed shorts: A >> B
 
ad: 0B 11 ; Signed longs: A >> B
 
ae: 4F 11 ; Unsigned shorts: A >> B
 
af: 69 11 ; Unsigned longs: A >> B
 
b0: 94 11 ; Signed shorts: A / B (2's C) Remainder
 
b1: 1C 12 ; Signed longs: A / B (2's C) Remainder
 
b2: 1A 13 ; Unsigned shorts: A / B Remainder
 
b3: 5E 13 ; Unsigned longs: A / B Remainder
 
 
 
b4: E5 13 ; Expand signed byte to signed short
 
b5,b7: FF 13 ; Go back 1 (local stack)
 
b6: 02 14 ; Push 00 (byte) to local stack
 
 
 
b8: 0F 14 ; A: short address, B: signed short value (SMR) ; Store low byte (signed) of B at A, put B back on stack
 
b9: 35 14 ; A: short address, B: signed long value (SMR) ; Store low byte (signed) of B at A, put B back on stack
 
ba: 66 14 ; A: short address, B: unsigned short value ; Store low byte of B at A, put B back on stack
 
bb: 84 14 ; Identical to BE (long address, unsigned short) (bug)
 
bc: A8 14 ; A: long address, B: signed short value (SMR) ; Store low byte (signed) of B at A, put B back on stack
 
bd: D4 14 ; A: long address, B: signed long value (SMR) ; Store low byte (signed) of B at A, put B back on stack
 
be: 0B 15 ; A: long address, B: unsigned short value ; Store low byte of B at A, put B back on stack
 
bf: 2F 15 ; A: long address, B: unsigned long value ; Store low byte of B at A, put B back on stack
 
 
 
c0: 5F 15 ; Shorts: A == B
 
c1: 7B 15 ; Longs: A == B
 
c2: AB 15 ; Shorts: A != B
 
c3: C7 15 ; Longs: A != B
 
c4: F7 15 ; Shorts: A != 0 || B == 0
 
c5: 15 16 ; Longs: (A != 0 || ) B == 0 (bugged)
 
c6: 41 16 ; Shorts: A != 0 || B != 0
 
c7: 5D 16 ; Longs: A != 0 || B != 0
 
c8: 89 16 ; Shorts: A + B
 
c9: 9B 16 ; Longs: A + B
 
ca: C0 16 ; Shorts: A - B
 
cb: D6 16 ; Longs: A - B
 
cc: FF 16 ; Move Short local stack -> 2
 
cd: 0C 17 ; Move Long local stack -> 2
 
ce: 21 17 ; Move Short call stack -> 1
 
cf: 2E 17 ; Move Long call stack -> 1
 
 
 
d0: 43 17 ; Shorts: A & B
 
d1: 54 17 ; Longs: A & B
 
d2: 78 17 ; Shorts: A | B
 
d3: 89 17 ; Longs: A | B
 
d4: AD 17 ; Shorts: A ^ B
 
d5: BE 17 ; Longs: A ^ B
 
d8: E6 17 ; Short: -A (1's C)
 
d9: F4 17 ; Long: -A (1's C)
 
da: 0F 18 ; Short: -A (2's C)
 
db: 1E 18 ; Long: -A (2's C)
 
dc: 49 18 ; Short: A != 0
 
dd: 5F 18 ; Long: A != 0
 
de: 7B 18 ; Write #7F to local stack
 
df: 86 18 ; Long: A != 0 (different implementation)
 
 
 
e0: A2 18 ; A (short address), B (short value) ; Writes B to A
 
e1: B9 18 ; A (long address), B (short value) ; Writes B to A
 
e2: D2 18 ; A (short address), B (long value) ; Writes B to A
 
e3: F3 18 ; A (long address), B (long value) ; Writes B to A
 
e4: 1A 19 ; A (short address), B (short value) ; Writes low byte of B to A
 
e5: 31 19 ; A (long address), B (short value) ; Writes low byte of B to A
 
e6: 4E 19 ; A (short address), B (long value) ; Writes low byte of B to A
 
e7: 67 19 ; A (long address), B (long value) ; Writes low byte of B to A
 
e8: 86 19 ; A (short address), B (short value) ; Write B to A, put B back on stack
 
e9: 9F 19 ; A (long address), B (short value) ; Write B to A, put B back on stack
 
ea: BE 19 ; A (short address), B (long value) ; Write B to A, put B back on stack
 
eb: EA 19 ; A (long address), B (long value) ; Write B to A, put B back on stack
 
 
 
; Partial-byte writing. A is address, B is value, param byte: JK (J 0 becomes 16)
 
; Takes the low J bits of B, then the low K bits of value at A ; Writes the result to A (auto byte/short depending on size
 
; All the long-value functions seem to have a bug where the low byte of B is never read.
 
f0: 1C 1A ; short address, short value
 
f1: 84 1A ; short address, long value
 
f2: F7 1A ; long address, short value
 
f3: 70 1B ; long address, long value
 
f4: F2 1B ; short address, short value ; Pushes B back on stack
 
f5: 62 1C ; short address, long value ; Pushes B back on stack
 
f6: E3 1C ; long address, short value ; Pushes B back on stack
 
f7: 64 1D ; long address, long value ; Pushes B back on stack
 
 
 
f8: F4 1D ; Pop 2 (local stack)
 
f9: F9 1D ; Pop 3 (local stack)
 
fa: 00 1E ; Repeat last 2 bytes (local stack)
 
fb: 0F 1E ; Repeat last 3 bytes (local stack)
 
fc: 27 1E ; JSR absolute (short address on local stack) (pushes current short location onto call stack)
 
fd: 3D 1E ; JSL absolute (long address on local stack) (pushes current long location onto call stack)
 
; If bank of address is 00, instead redirects to a routine at CX:YYYY where X is low nibble of middle byte of address, and YYYY is the low byte of address * 4
 
; Range: 0003-03FB in banks C0-CF
 
; (This is how the top half of op 40-47 is done)
 
</nowiki>
 

Latest revision as of 17:28, 17 October 2019