NCS File Format - NickHugi/PyKotor GitHub Wiki
KotOR NCS File Format Documentation
NCS files contain compiled NWScript bytecode. Scripts run inside a stack-based virtual machine shared by KotOR, NWN, and other Aurora-derived games. KotOR inherits the same format with minor opcode additions for game-specific systems.
Table of Contents
File Structure Overview
| Offset | Size | Description |
|---|---|---|
| 0x00 | 4 | Signature "NCS " |
| 0x04 | 4 | Version "V1.0" |
| 0x08+ | — | Stream of bytecode instructions |
- The VM executes sequential instructions; control-flow opcodes (
JMP,JZ,JSR) adjust the instruction pointer. - KotOR introduces no custom container sections—scripts are a flat stream.
- All major reverse-engineered engines (
vendor/reone,vendor/xoreos,vendor/Kotor.NET,vendor/NorthernLights) decode the same structure; KotOR.js uses a wasm VM but identical byte layouts.
Implementation: Libraries/PyKotor/src/pykotor/resource/formats/ncs/
Header
| Name | Type | Offset | Size | Description |
|---|---|---|---|---|
| File Type | char[4] | 0x00 | 4 | "NCS " |
| File Version | char[4] | 0x04 | 4 | "V1.0" |
| First Opcode | uint8 | 0x08 | 1 | Opcode of the first instruction (reading continues immediately) |
Reference: vendor/reone/src/libs/script/format/ncsreader.cpp:27-48
Instruction Encoding
Every instruction is stored as:
<bytecode: uint8> <qualifier: uint8> <arguments: variable>
PyKotor reuses the NWScript opcode table; each opcode accepts a specific qualifier/argument layout described below.
Bytecode
bytecode selects the fundamental instruction (stack manipulation, arithmetic, logic, control flow, etc.). KotOR supports the standard NWN opcodes plus Bioware extensions such as STORE_STATE. See Libraries/PyKotor/src/pykotor/resource/formats/ncs/ncs_data.py:70-142.
Qualifier
qualifier refines the instruction to specific operand types (e.g., IntInt, FloatFloat, VectorVector).
Example: ADDxx with qualifier IntInt performs integer addition, while the same opcode with qualifier FloatFloat adds floats.
Reference: Libraries/PyKotor/src/pykotor/resource/formats/ncs/ncs_data.py:119-143
Arguments
- Immediate values:
CONSTI,CONSTS,CONSTO, etc., embed literal data after the qualifier. - Offsets / Jump targets: Control-flow opcodes store signed 32-bit offsets relative to the next instruction.
- Stack offsets:
CPDOWNSP,CPTOPSP,RSADDxinclude 16-bit stack offsets.
PyKotor stores instructions as objects containing the decoded arguments and a pointer to the jump target if applicable.
Reference: vendor/xoreos/src/aurora/nwscript/ncsfile.cpp:194-1649
Instruction Categories
| Category | Opcodes |
|---|---|
| Stack | CPDOWNSP, CPTOPSP, MOVSP, CPDOWNBP, CPTOPBP, SAVEBP, RESTOREBP, INCxSP, DECxSP, INCxBP, DECxBP |
| Constants | CONSTI, CONSTF, CONSTS, CONSTO, and typed variants |
| Arithmetic | ADDxx, SUBxx, MULxx, DIVxx, MODxx, NEGx |
| Bitwise/Logic | LOGANDxx, LOGORxx, INCORxx, EXCORxx, BOOLANDxx, NOTx, COMPx, SHLEFTxx, SHRIGHTxx, USHRIGHTxx |
| Comparison | EQUALxx, NEQUALxx, GTxx, GEQxx, LTxx, LEQxx |
| Control Flow | JMP, JSR, JZ, JNZ, RETN, STORE_STATE, DESTRUCT |
| Function Calls | ACTION (invokes engine-exposed script functions) |
Reference: Libraries/PyKotor/src/pykotor/resource/formats/ncs/ncs_data.py:144-242
Control Flow and Jumps
- Jump instructions store relative offsets; readers resolve them to absolute positions.
- PyKotor’s
NCSclass tracks instruction objects and rewires thejumpattribute so tooling can walk the control-flow graph without recomputing offsets. - Subroutines use
JSR(push return address) andRETN(pop address and jump back). STORE_STATE/DESTRUCTmanage VM save/restore for suspended scripts (cutscenes, dialogs); the semantics are identical in Reone and Kotor.NET, while KotOR.js mirrors them for deterministic playback.
Reference: Libraries/PyKotor/src/pykotor/resource/formats/ncs/ncs_data.py:244-421
Implementation Details
- Binary Reader/Writer:
Libraries/PyKotor/src/pykotor/resource/formats/ncs/io_ncs.py - Data Model:
Libraries/PyKotor/src/pykotor/resource/formats/ncs/ncs_data.py - Reference Implementations:
The projects above implement the same opcode set and qualifier table, so scripts authored for PyKotor remain compatible with the other engines.