crskycode / NeXAS_Tool

Some tools that works with new version NeXAS ADV engine.
6 stars 2 forks source link

About the script format #1

Open koukdw opened 10 months ago

koukdw commented 10 months ago

It's pretty good but there is some mistake in the format I've cross referenced Aonatsu Line chinese demo and Aonatsu line trial. So i have a pretty good understanding of the script format I also reverse engineered the bin loading function and all the opcodes. NeXAS seems to separate numeric and string variables you named those variable VariableDeclarations and ParametersDeclarations but those aren't what they are. It's a table of variable names used for debugging, one for int and one for strings. The unknown block after that are bank arguments, i have documented what a bank is below (i haven't invented the name thats how it is named in the unity version aonatsu line chinese demo).

All the formats for the DAT files are also documented in the unity version. Anyway here is my personal document if that can help. I'm also working on my own thing but i'm not very good at programming.

Banks: Banks are entry point, most script only have one. But title and system tends to have multiple (for menu interaction) The START (id) and END opcode are the boundary of each bank. The Start ID reference the bank structure in the file format and get the arguments from there (max 8 arguments) In the bank structure when the args aren't -1(which mean no arg), you can get the argument name by getting the int variable name table and using the arg value to index into it.

 struct Bank {
       unsigned int32 id;
       int32 args[8];
       byte pad[32]; // I've not seen this used anywhere
 }

Registers: R0, R1

Scope: Global Script Local

Opcode: 0x0 - VAL (value u32) Move literal value into R0 0x4 - PUSH (register_num u32) Push the value register[register_num] into the calculation stack 0x5 - PARAM (type u32) Push parameter into the arg stack if type is 0 it's a a numeric value if it's 1 it's a string 0x6 - POP (register_num u32) POP the value at the top of the calculation stack into register[register_num] 0x7 - CALL (id u16, arg_count u16) Call the function(native or script) if the id & 0x8000 != 0, it's a script function you need to xor it with 0x8000 to get the id, else it's a native function implemented in the engine you can just take the id as is. 0x8 - GET_VAR (register_num u32) Get an int variable at a specific scope and return into into Register[register_num] Register value is an index into an array + a flags that determine the scope

if (index & 0x40000000 != 0)  =>  Global scope
else 
     if (callStack.size != 0)  Local Scope
     else   Script scope 

0x9 - ADD/APPEND (type u32)

If type == 0 (int)
       R0 = R0 + R1
If type == 1 (string)   R0 and R1 are string
       Append R0 to R1 and return string variable index to R0

0xA - SUB R0 = R1 - R0 0xB - MUL R0 = R0 * R1 0xC - DIV R0 = R1 / R0 0xD - MOD R0 = R1 % R0 0xE - SET_VAR (operand u32) That won't be a complete explanation. This opcode is pretty complex with scoping rules

If operand == -1   // 2 Ways of getting the values
     i32 index = R0
     i32 value = R1
 else
    i32 index = (i32)operand
    i32 value = R0    

// there's flags for scope in the index/value but it would make the explanation too complicated
// use ida and look for aonatsu line trial (paid version is protected), aquarium is optimized it's harder to read 
// but it's very close to the same code
// i can provide an idb
 if  index >= 0  // if it's positive it set an int variable
     GET_INT_VAR(index) = value
 else  // if it's not positive it's a string variable
    index = index ^ 0x80000000
    GET_STRING_VAR(index) = GET_STRING(value) // GET_STRING can get value from a variable or from the string table

0xF - OR R0 = R0 || R1 0x10 - AND R0 = R0 && R1 0x11 - BIT_OR R0 = R0 | R1 0x12 - BIT_AND R0 = R0 & R1 0x13 - XOR R0 = R0 ^ R1 0x14 - NOT R0 = !R0 0x15 - CMP_LE R0 = R0 <= R1 0x16 - CMP_GE R0 = R0 >= R1 0x17- CMP_LT R0 = R0 < R1 0x18 - CMP_GT R0 = R0 > R1 0x19 - CMP_EQ (type u32)

if type == 1
  R0 = compare_string_equal(r1, r0)
else if type == 0
  R0 = R0 == R1

0x1A - CMP_NE (type u32)

if type == 1
  R0 = compare_string_not_equal(r1, r0)
else if type == 0
  R0 = R0 != R1

0x1B - START (bank_id u32) Start of a bank bank_id reference a specific index in the bank_table 0x1C - END End of a bank 0x1D - ???MARKER???(operand u32) Not sure what this is, it set a bit in a bit array everytime it's called. It also seems to be present at the boundary of each basic block that's why i call it marker But it's not called in functions only in the main script But i can't guarantee it's what it is 0x22 - INC(operand i32)

 if  operand == -1
     int_var = GET_INT_VARIABLE(R0)
     int_var++
 else
     int_var = GET_INT_VARIABLE(operand)
     int_var++

0x23 - DEC(operand i32)

 if  operand == -1
     int_var = GET_INT_VARIABLE(R0)
     int_var--
 else
     int_var = GET_INT_VARIABLE(operand)
     int_var--

0x2B - CMP_ZERO R0 = R0 == 0 0x2C - LINE_NUMBER (operand u32) Set vm->source_line_number to operand 0x2D - SAR Shift arithmetic right R0 = R1 >> R0 0x2E - SHL Shift Logical Left R0 = R1 << R0 0x2D - SHR Shift Logical Right R0 = (u32)R1 >> R0

Here the functions are a similar to previous ones 0x30 - Variable ADD/APPEND (operand u32) Similar to SET_VAR but instead of setting the variable it add/append to it 0x31 - Variable SUB(operand i32)

if operand == -1 
   int_var = GET_INT_VARIABLE(R0)
   int_var -= R1
else 
  int_var = GET_INT_VARIABLE(operand)
  int_var -= R0

0x32 - Variable MUL(operand i32) Similar to SUB 0x33 - Variable DIV(operand i32) Similar to SUB 0x34 - Variable MOD(operand i32) Similar to SUB 0x35 - Variable OR(operand i32) Similar to SUB 0x36 - Variable AND(operand i32) Similar to SUB 0x37 - Variable XOR(operand i32) Similar to SUB 0x38 - Variable SAR(operand i32) Similar to SUB 0x39 - Variable SHL(operand i32) Similar to SUB 0x3A - Variable SHR(operand i32) Similar to SUB 0x3F - TO_STRING(register_num u32) Convert int register[register_num] to string and return the variable index to register[register_num] 0x40 - RETURN (type u32) Set return value to r0 (if operand is 1 string value, if operand is 0 int value) 0x41 - GOTO(offset u32) Set program counter to instruction number (not address) 0x42 - BRFALSE(offset u32) Set program counter to instruction number (not address) Condition in R0 0x43 - BRTRUE(offset u32) Set program counter to instruction number (not address) Condition in R0 0x44 - TO_INT(register_num u32) Same as To_String

0x47 to 0x49 Not reversed It's new

crskycode commented 10 months ago

Thanks for the info, I've only analyzed the X86 version a little bit so far. If I have time, I'll check out the Unity version.