Last active
January 9, 2026 13:24
-
-
Save RYSF13/3322baf0762d07352b5627bb68f99490 to your computer and use it in GitHub Desktop.
A fully functional Snake game written in GNU Assembler without libc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* ------------------------------------------------------------------- | |
| PROGRAM: SNAKE (ASSEMBLY & ELF HACK) | |
| TARGET: Linux (x86 32-bit) | |
| AUTHOR: Robert Ryan(gh@RYSF13) | |
| [COMPILE COMMAND] | |
| as --32 snake.asm -o s.o && \ | |
| ld -m elf_i386 --oformat binary -Ttext 0x08048000 s.o -o snake && \ | |
| rm -f s.o && chmod +x snake && ./snake | |
| ------------------------------------------------------------------- */ | |
| .intel_syntax noprefix | |
| .global _start | |
| /* --- Constants --- */ | |
| .equ BASE_ADDR, 0x08048000 | |
| .equ SYS_EXIT, 1 | |
| .equ SYS_READ, 3 | |
| .equ SYS_WRITE, 4 | |
| .equ SYS_IOCTL, 54 | |
| .equ SYS_SLEEP, 162 | |
| .text | |
| /* Force alignment to prevent offset errors in ELF header */ | |
| .align 1 | |
| _start: | |
| /* =================================================================== | |
| 1. ELF Header Injection | |
| =================================================================== */ | |
| /* e_ident[0..3]: Magic Number */ | |
| .byte 0x7F, 'E', 'L', 'F' | |
| /* e_ident[4..6]: Class=32, Data=LE, Version=1, ABI=0 */ | |
| .byte 1, 1, 1, 0 | |
| /* [HACK] Padding Area Entry Point (8 bytes) | |
| We inject code into e_ident[8..15] */ | |
| _entry_point: | |
| xor ebx, ebx | |
| mul ebx | |
| jmp SHORT _init | |
| nop | |
| nop | |
| /* --------------------------------------------------------------- */ | |
| .word 2 /* e_type: ET_EXEC */ | |
| .word 3 /* e_machine: I386 */ | |
| .long 1 /* e_version: Current (4 bytes) */ | |
| .long BASE_ADDR + (_entry_point - _start) /* e_entry */ | |
| .long 52 /* e_phoff: Offset of Program Header */ | |
| .long 0 /* e_shoff: Unused */ | |
| .long 0 /* e_flags: Unused */ | |
| .word 52 /* e_ehsize: Header Size */ | |
| .word 32 /* e_phentsize: Phdr Entry Size */ | |
| .word 1 /* e_phnum: 1 Segment */ | |
| .word 0, 0, 0 /* e_shentsize, e_shnum, e_shstrndx */ | |
| /* =================================================================== | |
| 2. Program Header | |
| =================================================================== */ | |
| _phdr: | |
| .long 1 /* p_type: PT_LOAD */ | |
| .long 0 /* p_offset: File start */ | |
| .long BASE_ADDR /* p_vaddr */ | |
| .long BASE_ADDR /* p_paddr */ | |
| .long _file_end - _start /* p_filesz */ | |
| .long 0x10000 /* p_memsz: 64KB Memory */ | |
| .long 7 /* p_flags: RWX */ | |
| .long 0x1000 /* p_align */ | |
| /* =================================================================== | |
| 3. Initialization | |
| =================================================================== */ | |
| _init: | |
| /* Setup Terminal (Raw Mode) */ | |
| mov al, SYS_IOCTL | |
| mov ecx, 0x5401 /* TCGETS */ | |
| mov edx, esp | |
| int 0x80 | |
| and BYTE PTR [esp + 12], 0xF5 /* Disable ICANON | ECHO */ | |
| mov BYTE PTR [esp + 23], 0 /* VMIN = 0 (Non-blocking) */ | |
| mov al, SYS_IOCTL | |
| mov cl, 0x02 /* TCSETS */ | |
| int 0x80 | |
| /* Initialize Variables in BSS area */ | |
| mov DWORD PTR [_file_end + 1080], 0 /* Head=0, Tail=0 */ | |
| mov WORD PTR [_file_end + 1084], 1 /* Dir=1 (Right) */ | |
| /* Setup Snake Head */ | |
| mov edi, OFFSET _file_end + 360 | |
| mov WORD PTR [edi], 155 | |
| mov BYTE PTR [_file_end + 155], 1 | |
| call _spawn_food | |
| /* =================================================================== | |
| 4. Game Loop | |
| =================================================================== */ | |
| _game_loop: | |
| /* --- Frame Delay (200ms) --- */ | |
| push 200000000 | |
| push 0 | |
| mov eax, SYS_SLEEP | |
| mov ebx, esp | |
| xor ecx, ecx | |
| int 0x80 | |
| add esp, 8 | |
| /* --- Input Handling --- */ | |
| mov eax, SYS_READ | |
| xor ebx, ebx | |
| mov ecx, esp | |
| mov edx, 3 | |
| int 0x80 | |
| test eax, eax | |
| jle _update_logic | |
| /* Parse Input */ | |
| mov al, [esp] | |
| /* Check for Ctrl+D (EOF, ASCII 0x04) */ | |
| cmp al, 4 | |
| je _game_over | |
| /* Check for Arrow Keys (Esc Sequence) */ | |
| cmp al, 0x1B | |
| je _check_arrow | |
| /* Check for WASD */ | |
| cmp al, 'w'; je _go_up | |
| cmp al, 's'; je _go_down | |
| cmp al, 'a'; je _go_left | |
| cmp al, 'd'; jne _update_logic | |
| jmp _go_right | |
| _check_arrow: | |
| cmp BYTE PTR [esp+1], '[' | |
| jne _update_logic | |
| mov al, [esp+2] | |
| cmp al, 'A'; je _go_up | |
| cmp al, 'B'; je _go_down | |
| cmp al, 'D'; je _go_left | |
| cmp al, 'C'; je _go_right | |
| jmp _update_logic | |
| /* Direction Setting */ | |
| _go_right: mov si, 1; jmp _try_change_dir | |
| _go_left: mov si, -1; jmp _try_change_dir | |
| _go_down: mov si, 30; jmp _try_change_dir | |
| _go_up: mov si, -30 | |
| _try_change_dir: | |
| /* Suicide Prevention Check (New Dir + Old Dir != 0) */ | |
| mov di, [_file_end + 1084] /* Load OLD dir */ | |
| mov ax, si /* NEW dir */ | |
| add ax, di /* Sum */ | |
| cmp ax, 0 /* If sum is 0 (180 turn), ignore input */ | |
| je _update_logic | |
| mov [_file_end + 1084], si | |
| _update_logic: | |
| /* --- Game Logic --- */ | |
| movzx ebp, WORD PTR [_file_end + 1080] /* Head Idx */ | |
| movzx ebx, WORD PTR [_file_end + 1082] /* Tail Idx */ | |
| movsx esi, WORD PTR [_file_end + 1084] /* Dir */ | |
| /* Calc New Head */ | |
| mov ax, [_file_end + 360 + ebp] | |
| add ax, si | |
| /* Boundary Wrap */ | |
| cmp ax, 0 | |
| jge _check_upper | |
| add ax, 360 | |
| jmp _collision_check | |
| _check_upper: | |
| cmp ax, 360 | |
| jl _collision_check | |
| sub ax, 360 | |
| _collision_check: | |
| movzx ecx, ax /* New Head Pos */ | |
| /* Check Collision */ | |
| mov al, [_file_end + ecx] | |
| cmp al, 1; je _game_over | |
| cmp al, 2; je _eat_food | |
| /* Move Tail */ | |
| mov dx, [_file_end + 360 + ebx] | |
| movzx edx, dx | |
| mov BYTE PTR [_file_end + edx], 0 | |
| add ebx, 2 | |
| cmp ebx, 720 | |
| jl _save_tail | |
| xor ebx, ebx | |
| _save_tail: | |
| mov [_file_end + 1082], bx | |
| jmp _move_head | |
| _eat_food: | |
| push ecx | |
| call _spawn_food | |
| pop ecx | |
| _move_head: | |
| add ebp, 2 | |
| cmp ebp, 720 | |
| jl _write_head | |
| xor ebp, ebp | |
| _write_head: | |
| mov [_file_end + 1080], bp | |
| mov [_file_end + 360 + ebp], cx | |
| mov BYTE PTR [_file_end + ecx], 1 | |
| call _render_frame | |
| jmp _game_loop | |
| _game_over: | |
| /* Clean Exit with Message */ | |
| /* Print "GAME OVER" */ | |
| mov eax, SYS_WRITE | |
| mov ebx, 1 | |
| mov ecx, OFFSET _msg_gameover | |
| mov edx, 12 | |
| int 0x80 | |
| /* Exit(0) */ | |
| mov eax, SYS_EXIT | |
| xor ebx, ebx | |
| int 0x80 | |
| /* =================================================================== | |
| Subroutines & Data | |
| =================================================================== */ | |
| _spawn_food: | |
| rdtsc | |
| xor edx, edx | |
| mov ecx, 360 | |
| div ecx | |
| cmp BYTE PTR [_file_end + edx], 0 | |
| jne _spawn_food | |
| mov BYTE PTR [_file_end + edx], 2 | |
| ret | |
| _render_frame: | |
| mov edi, OFFSET _file_end + 1200 | |
| mov eax, 0x485B1B /* Esc [ H */ | |
| stosd | |
| dec edi | |
| xor ecx, ecx | |
| _draw_loop: | |
| test ecx, ecx | |
| jz _draw_char | |
| mov eax, ecx | |
| mov dl, 30 | |
| div dl | |
| test ah, ah | |
| jnz _draw_char | |
| mov al, 10 | |
| stosb | |
| _draw_char: | |
| mov al, [_file_end + ecx] | |
| cmp al, 0; je _c_sp | |
| cmp al, 1; je _c_sn | |
| mov al, 'X'; jmp _put | |
| _c_sn: mov al, '#'; jmp _put | |
| _c_sp: mov al, '.' | |
| _put: | |
| stosb | |
| inc ecx | |
| cmp ecx, 360 | |
| jl _draw_loop | |
| mov edx, edi | |
| sub edx, OFFSET _file_end + 1200 | |
| mov eax, SYS_WRITE | |
| mov ebx, 1 | |
| mov ecx, OFFSET _file_end + 1200 | |
| int 0x80 | |
| ret | |
| /* Game Over Message */ | |
| _msg_gameover: | |
| .ascii "\nGAME OVER\n" | |
| _file_end: |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
🐍 Snake (ELF Header Hack Edition)
A fully functional Snake game written in pure x86 Assembly (GNU Assembler) for Linux.
This program bypasses the standard linker behavior, manually constructing the ELF header to achieve a compiled size of only ~624 bytes.
✨ Features
Ctrl+Dto quit, plus a "GAME OVER" screen.🛠 Compile & Run (Linux x86)
No special tools required—just
asandld(binutils).🧠 How it works
xor,mul,jmp) are hidden inside the unused padding bytes of the ELFe_identfield.p_memsz(64KB) much larger thanp_filesz(~600b), allowing the OS to automatically allocate BSS memory for the game variables immediately after the file code.ld --oformat binaryto strip all section headers, leaving only the bare minimum executable bytes.