An assembler for Corewar — compiles .s warrior source files into .cor bytecode
ready to run inside the Corewar virtual machine.
Corewar is a programming game in which small programs called warriors fight for
control of a shared memory arena. Each warrior is written in a Redcode-like assembly
language (.s source files), compiled to a compact binary format (.cor), and
loaded into the VM. Warriors execute concurrently, one instruction per cycle; the
last warrior to stay alive wins. This assembler (asm) is the tool that translates
warrior source code into the bytecode the VM can execute.
./asm warrior.s → ./warrior.cor
asm reads a single .s source file, validates every directive, instruction, and
label reference, then writes a .cor binary in the current directory.
Exit codes: 0 on success, 84 on any error (missing file, invalid extension,
bad syntax, duplicate declarations, unresolved labels, …).
make # produces ./asm
make clean # removes object files
make fclean # removes objects and the binary
make re # full rebuildRequires a C compiler (cc) and standard POSIX headers. No external dependencies.
./asm <source.s>
./asm -h # print usage help
./asm --helpThe output file is always placed in the current directory, named after the input
file with the .s extension replaced by .cor.
Example
./asm Tester/champions/abel.s
# produces ./abel.corA .s warrior file has two mandatory sections: a header block with .name and
.comment directives, followed by an instruction block.
.name "Abel" # warrior name (≤ 128 bytes)
.comment "L'amer noir." # description (≤ 2048 bytes)
sti r1, %:hi, %1 # store r1 at address (hi + 1), indirect-indexed
hi: live %234 # declare process alive with player id 234
ld %0, r3 # load literal 0 into r3
zjmp %:hi # jump back to label 'hi' if carry flag is zero(Source: Tester/champions/abel.s)
| Element | Syntax | Example |
|---|---|---|
| Register | r1 … r16 |
r1, r16 |
| Direct (literal) | %<value> |
%42, %-1 |
| Indirect (memory offset) | bare integer | 100 |
| Label reference (direct) | %:<label> |
%:hi |
| Label definition | <name>: |
hi: |
| Comment | # to end of line |
# comment |
The 16 supported opcodes, in opcode order:
| Mnemonic | Args | Opcode | Description |
|---|---|---|---|
live |
dir | 0x01 |
Declare process alive |
ld |
dir|ind, reg | 0x02 |
Load value into register |
st |
reg, ind|reg | 0x03 |
Store register to memory |
add |
reg, reg, reg | 0x04 |
Add two registers |
sub |
reg, reg, reg | 0x05 |
Subtract two registers |
and |
any, any, reg | 0x06 |
Bitwise AND |
or |
any, any, reg | 0x07 |
Bitwise OR |
xor |
any, any, reg | 0x08 |
Bitwise XOR |
zjmp |
dir | 0x09 |
Jump if carry is zero |
ldi |
any, dir|reg, reg | 0x0a |
Load indirect-indexed |
sti |
reg, any, dir|reg | 0x0b |
Store indirect-indexed |
fork |
dir | 0x0c |
Fork process |
lld |
dir|ind, reg | 0x0d |
Long load (no IDX_MOD) |
lldi |
any, dir|reg, reg | 0x0e |
Long load indirect-indexed |
lfork |
dir | 0x0f |
Long fork |
aff |
reg | 0x10 |
Print register value as ASCII |
All multi-byte integers are big-endian.
| Offset | Size (bytes) | Field |
|---|---|---|
| 0 | 4 | Magic number (0x00ea83f3) |
| 4 | 128 | Program name (null-padded) |
| 132 | 4 | Padding (zeros) |
| 136 | 4 | Program size in bytes (code section only) |
| 140 | 2048 | Comment string (null-padded) |
| 2188 | 4 | Padding (zeros) |
| 2192 | … | Encoded instructions (code section) |
Each instruction encodes as: [opcode 1B] [coding byte 1B*] [params…]
*The coding byte is omitted for single-parameter instructions (except aff).
Each 2-bit field in the coding byte encodes the parameter type:
01 = register, 02 = direct, 03 = indirect.
RobotFactory/
├── include/
│ ├── asm.h # main assembler struct (asm_t)
│ ├── op.h # header_t, op_t, constants (COREWAR_EXEC_MAGIC, …)
│ ├── instructions.h # instruction_t, param_t, label_t, parse functions
│ ├── fill.h # fill_data / fill_header / fill_instructions
│ ├── write.h # write_output / write_header / write_data
│ ├── error_handling.h # error codes enum, print_error, handle_errors
│ ├── linked_lists.h # generic singly-linked list
│ ├── header.h # parse_name, parse_comment
│ └── lib.h # custom string/IO helpers (no libc dependency)
├── src/
│ ├── main.c
│ ├── cleanup.c
│ ├── asm/
│ │ ├── error_handling/ # handle_errors, print_error, print_help
│ │ └── fill_data/
│ │ ├── fill_data.c
│ │ ├── header/ # parse_name, parse_comment, fill_header
│ │ └── instructions/ # parse_line, parse_instructions,
│ │ # parse_labels, parse_parameters, op_utils
│ ├── write/
│ │ ├── write_output.c
│ │ ├── write_header.c
│ │ ├── write_data.c
│ │ └── write_program_size.c
│ └── lib/
│ ├── general/ # my_atoi, my_str*, skip_spaces, …
│ └── linked_lists/ # create/push/append/delete/find node, lltoa
└── Tester/
├── champions/ # .s warrior sources used as test fixtures
├── reference/ # pre-built reference asm binary (not redistributed)
├── my/ # copy of your asm binary placed here by test.sh
├── tester.sh # per-champion diff test runner
└── segfaulter.sh # crash/edge-case stress script
The test suite compiles each .s file in Tester/champions/ with both a reference
assembler and your build, then diffs the resulting .cor binaries byte-for-byte.
Prerequisites: place the Epitech reference asm binary in Tester/reference/.
# From the repo root:
bash test.shtest.sh runs make, copies ./asm to Tester/my/, then runs Tester/tester.sh.
Exit code 0 means all champions compiled identically to the reference; 1 means
at least one diff was found (hexdump comparison printed to stdout).
To run the tester directly after a manual build:
cp asm Tester/my/
cd Tester && bash tester.shThe assembler prints coloured diagnostics with file name and line number context,
then exits 84. Detected conditions include: missing/duplicate .name or
.comment, invalid file extension, unknown instruction, wrong parameter count,
invalid parameter type, invalid register number (r0 or r17+), unresolved label,
and name/comment strings that exceed their maximum lengths.
Developed as part of the Epitech CPE curriculum — B-CPE-200, 1st year, S2 (2025). The project subject (PDF) is not redistributed here per Epitech policy.