; yaglitch : Yet Another Glitch ; Copyright (C) 2018 Weber Yann ; ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation; either version 3 of the License, or ; any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program. If not, see . ; [bits 64] %include "sdl.asm" %include "utils.asm" %define STACK_SZ 256 %define MAX_FILE_SZ (16 * 17) ; 16 lines of 16 chars + newline chr %define NL "!" section .data op_ptrs: ; pointers on OP functions dq OP.t ; a dq OP.put ; b dq OP.drop ; c dq OP.mul ; d dq OP.div ; e dq OP.add ; f dq OP.sub ; g dq OP.mod ; h dq 0 ; i : syntax error dq OP.lshift ; j dq OP.rshift ; k dq OP.and ; l dq OP.or ; m dq OP.xor ; n dq OP.not ; o dq OP.dup ; p dq OP.pick ; q dq OP.swap ; r dq OP.lt ; s dq OP.gt ; t dq OP.eq ; u audiospec_wanted: ; SDL_audio configuration dd 8000 ; freq dw AUDIO_U8 ; format db 1 ; channels db 0 ; silence dw 0x100 ; samples dd 0 ; size dw 0 ; allign dq audio_cllbck dq 0 ; userdata ; text messages def_str usage, "Usage : " def_str opts, {" FILE.glitch",0xA} def_str openerr, {'Error opening file : "', 0xA} def_str syntax_error, {"Syntax error", 0xA} def_str bigline_error, {"Line with more than 16 tokens !", 0xA} def_str nl_error, {"Character \n is not the last one", 0xA} def_str badop_error, {"Bad OP", 0xA} window_title: db "Yaglitch", 0x0 section .bss ; glitch name (1st line) : len 16 + \0 glitch_name: resb 17 ; program internal repr for stack machine ; each token uses 2 words : 1st for the callback address 2nd for ; optional value glitch_pgm: resq 16 * 16 * 2 ; stack machine ring buffer stack_buff: resd STACK_SZ ; top of stack pointer tosp: resq 1 ; stack machine rgister t: resd 1 ; audiospec returned from SDL audiospec_recv: resb 32 ; Event receveid from SDL %ifdef SDL1 event: resb 24 %endif %ifdef SDL2 event: resb 56 evt_cnt: resb 1 %endif %ifdef MIX_AUDIO cllbck_heap: resq 1 cllbck_heapsz: resw 1 %endif ; Pipe allowing callback to send data to main thread ipc_pipe: .rd: resd 1 .wr: resd 1 visu_data_buff: resb 0x100 %ifdef SDL2 visu_win: resq 1 %endif visu_scr: resq 1 visu_surf: resq 1 section .text global _start _start: ; checking args mov rcx, [rsp] cmp rcx, 2 jne exit.badarg mov rax, 0x2 ; sys_open mov rdi, [rsp+16] ; argv[1] xor rsi, rsi ; no flags xor rdx, rdx ; O_RDONLY syscall cmp rax, 0 jl exit.err_open push rax ; source fd mov rax, 0xc ; brk xor rdi, rdi syscall push rax ; heap start mov rdi, rax add rdi, MAX_FILE_SZ ; new heap end mov rax, 0xc ; brk syscall pop rsi ; heap start mov r14, rsi xor rax, rax ; sys_read mov rdi, [rsp] ; source_fd mov rdx, MAX_FILE_SZ syscall cmp rax, 0 jl exit.err_open mov r15, rax mov rax, 0x3 ; sys_close pop rdi ; source_fd syscall mov rbx, r15 ;read size mov rdi, r14 ; heap start add rdi, rbx ; heap end mov r15, rdi mov rax, 0xc ; brk syscall ; shrink heap to read size ; init program space mov rdi, glitch_pgm xor rax, rax stosq stosq mov rsi, r14 push rsi xor r13, r13 xor rbx, rbx parse: ; go trhough file , with rsi current ptr, r15 file stop ; r13 will contain updated lineno and rbx chr in current line ; rdi store the destination pointer ; parse glitch name mov rcx, 16 mov rdi, glitch_name .name_loop: inc rbx lodsb test al, al ; EOF jz .no_nl cmp al, 0xA je .trailing_nl cmp al, "!" ; EOL je .name_end cmp al, "_" je .chrname cmp al, "0" jl exit.syntax_error cmp al, "9" jle .chrname cmp al, "a" jl exit.syntax_error cmp al, "z" jg exit.syntax_error .chrname: stosb loop .name_loop jmp exit.bigline .name_end: inc r13 ; lineno xor rbx, rbx xor al, al stosb ; parsing tokens xor edx, edx ; 32bits numeric token value xor eax, eax xor r10, r10 ; numeric token flag (1 mean in numeric token) xor r11, r11 ; numeric token len mov rcx, r15 sub rcx, r14 mov rdi, glitch_pgm .parse_loop: inc rbx lodsb test al, al jz .no_nl cmp al, 0xA je .trailing_nl cmp al, "!" ; EOL je .next_line cmp al, "." ; token separator je .next_token cmp al, "0" jl exit.syntax_error cmp al, "9" jle .dec_token cmp al, "A" jl exit.syntax_error cmp al, "F" jle .hex_token jmp .op_match ; allowing loop near jump .end_op: stosq xor rax, rax stosq loop .parse_loop .hex_token: sub al, "A" - 10 jmp .numeric_token .dec_token: sub al, "0" .numeric_token: cmp r11, 8 je exit.syntax_error ; Numeric constant OF inc r11 mov r10, 1 shl edx, 4 add edx, eax loop .parse_loop .next_token: test r10, r10 ; check for numeric constant jnz .add_numeric loop .parse_loop .next_line: inc r13 xor rbx, rbx .loop_parse_loop: loop .parse_loop .add_numeric: mov rax, OP.numeric stosq xor rax, rax xor r11, r11 xor r10, r10 xchg rax, rdx shl rax, 32 stosq xor rax, rax jmp .loop_parse_loop .trailing_nl: ; check that NL is the last chr, else syntax error cmp rsi, r15 jne exit.nl_not_last jmp .parse_end .op_match: ; allow loop .parse_loop near jump cmp al, "a" jl exit.syntax_error cmp al, "u" jg exit.syntax_error ; OP shortand matching ; checking for previous numeric token to write test r10, r10 jz .match_op push rax ; add previous numeric token mov rax, OP.numeric stosq xor rax, rax xor r11, r11 ; numeric token length raz xor r10, r10 ; numeric token flag raz xchg rax, rdx shl rax, 32 ; shl to allow reading as dword ? stosq pop rax .match_op: sub al, "a" shl rax, 3 ; mul by 8 (size of ptr) add rax, op_ptrs mov rax, [rax] test rax, rax jz exit.bad_op jmp .end_op .no_nl: ; TODO : print warning ; no NL at EOF .parse_end: ; clean heap mov rax, 0xc pop rdi syscall ; print glitch name mov rax, "Playing " push rax mov rax, 1 mov rdi, 1 mov rsi, rsp mov rdx, 8 syscall pop rax mov rdi, glitch_name call strlen mov rdx, rax mov rax, 1 mov rdi, 1 mov rsi, glitch_name syscall mov rax, `\n` push rax mov rax, 1 mov rdi, 1 mov rsi, rsp mov rdx, 1 syscall pop rax ; init stack machine runtime stack_init: mov rcx, STACK_SZ mov rdi, stack_buff xor rax, rax mov [t], eax .loop_buff_init: stosd loop .loop_buff_init mov eax, (STACK_SZ - 1) * 4 mov [tosp], eax sdl_init: ; Opening IPC pipe mov rax, 0x16 ; sys_pipe mov rdi, ipc_pipe syscall test rax, rax jnz exit_fatal mov rdi, 0x0000FFFF call SDL_Init mov rdi, audiospec_wanted mov rsi, audiospec_recv call SDL_OpenAudio ; video init 256*256 window ; init visu_scr : *screen %ifdef SDL1 mov rdi, 256 mov rsi, 256 mov rdx, 32 ;mov rcx, SDL_SWSURFACE mov rcx, SDL_DOUBLEBUF call SDL_SetVideoMode call SDL_GetVideoSurface mov [visu_scr], rax %endif %ifdef SDL2 mov rdi, window_title mov rsi, SDL_WINDOWPOS_UNDEFINED mov rdx, SDL_WINDOWPOS_UNDEFINED mov rcx, 256 mov r8, 256 xor r9, r9 call SDL_CreateWindow mov [visu_win], rax mov rdi, rax call SDL_GetWindowSurface mov [visu_scr], rax %endif ; init SDL surface according to SDL screen in visu_surf xor rdi, rdi ; flasg mov rsi, 256 ; width mov rdx, 256 ; height mov rcx, 32 ; 32 bits depth mov r8, 0xff ; rmask mov r9, 0xff00 ; gmask mov rax, 0xff0000 ; bmask push rax push rax shl rax, 8 ; amask mov [rsp+8], rax call SDL_CreateRGBSurface mov [visu_surf], rax test rax, rax jz sdl_error pop rcx pop rcx ; RAZ surface & blit push rax ; push surface addr mov rdi, rax call SDL_LockSurface %ifdef SDL2 xor rdx, rdx xor rcx, rcx MOV_surf_w ecx, visu_surf xor rax, rax MOV_surf_pitch eax, visu_surf mul ecx MOV_surf_pixels rdi, visu_surf xor rsi, rsi ;mov rsi, 0xFF ;white mov rdx, rax call SDL_memset ; seems to malloc the pixels %endif %ifdef SDL1 xor rcx, rcx MOV_surf_w ecx, visu_surf push rcx MOV_surf_h ecx, visu_surf MOV_surf_pixels rdi, visu_surf ;mov rax, 0xffcc22ff ; magenta ;mov rax, 0xffffffff ; white dec rcx .loop_razh: push rcx mov rcx, [rsp+8] dec rcx .loop_razw: stosd loop .loop_razw pop rcx loop .loop_razh pop rcx %endif pop rdi ; surface addr call SDL_UnlockSurface mov rdi, [visu_surf] xor rsi, rsi mov rdx, [visu_scr] xor rcx, rcx call SDL_BlitSurface test rax, rax jnz sdl_error %ifdef SDL2 mov rdi, [visu_win] call SDL_UpdateWindowSurface %endif %ifdef SDL1 mov rdi, [visu_scr] call SDL_Flip %endif %ifdef MIX_AUDIO ; init callback heap infos mov rax, 0xc ; brk xor rdi, rdi ; get heap start addr mov [cllbck_heapsz], rdi syscall cmp rax, -1 je exit_fatal mov [cllbck_heap], rax %endif audio_start: ;start audio xor rdi, rdi call SDL_PauseAudio loop_event: xor rdi, rdi mov [event], rdi mov rdi, event call SDL_WaitEvent cmp rax, 0 je sdl_error ; error fetching event... xor rdi, rdi %ifdef SDL1 mov dil, [event] cmp dil, SDL_QUIT je exit %endif %ifdef SDL2 mov edi, [event] cmp edi, SDL_QUIT je exit %endif evt: ; not exit event visu: ; starts by reading from IPC pipe xor rax, rax ; sys_read xor rdi, rdi mov edi, [ipc_pipe.rd] mov rsi, visu_data_buff mov rdx, 0x100 syscall cmp rax, -1 je exit_fatal jmp loop_event ; loop again sdl_error: ; display error & exit call SDL_GetError push rax .dbg: test rax, rax jz .no_err mov rdi, rax call strlen .printerr: mov rdx, rsi ; msglen pop rdi ; msg mov rax, 1 ; write mov rdi, 2 ; stderr syscall mov rdi, 0xF jmp sdl_exit_err .no_err: mov rdi, 0x5 jmp sdl_exit_err exit_fatal: mov rdi, 42 jmp exit.exit_err sdl_exit_err: push rdi call SDL_Quit pop rdi mov rax, 0x3c syscall exit: call SDL_Quit xor rdi, rdi mov rax, 0x3c syscall .err_open: mov rax, 1 mov rdi, 2 mov rsi, openerr mov rdx, openerr_len - 1 syscall mov rdi, [rsp+16] push rdi call strlen mov rdx, rax mov rax, 1 mov rdi, 2 pop rsi syscall mov rax, 1 mov rdi, 2 mov rsi, openerr + openerr_len - 2 mov rdx, 2 syscall .badarg: mov rax, 1 mov rdi, 2 mov rsi, usage mov rdx, usage_len syscall mov rdi, [rsp+8] call strlen mov rdx, rax mov rax, 1 mov rdi, 2 mov rsi, [rsp+8] syscall mov rax, 1 mov rdi, 2 mov rsi, opts mov rdx, opts_len syscall mov rdi, 1 .exit_err: ; with rdi error code mov rax, 0x3c ; exit syscall ; expect : r13 lineno, rbx chr num in line ; TODO: real error message .nl_not_last: mov rsi, nl_error mov rsi, nl_error_len push qword 3 jmp exit.parse_error .syntax_error: mov rsi, syntax_error mov rdx, syntax_error_len push qword 2 jmp exit.parse_error .bigline: mov rsi, bigline_error mov rdx, bigline_error_len push qword 2 jmp exit.parse_error .bad_op: mov rsi, badop_error mov rdx, badop_error_len push qword 2 jmp exit.parse_error .parse_error: ; print error lineno & chrno push rsi ; source ptr push rdx push rbx ; chrno in line sub r14, rsi ; chr count push 14 mov rdi, "chr:0x" mov rsi, 6 call short_err pop rdi ; chr count mov rsi, 2 call print_hnum mov rdi, ",line:0x" mov rsi, 8 call short_err mov rdi, r13 mov rsi, 2 call print_hnum mov rdi, ",col:0x" mov rsi, 7 call short_err pop rdi mov rsi, 2 call print_hnum mov rdi, ` :\t` mov rsi, 3 call short_err pop rdx pop rsi mov rax, 1 mov rdi, 2 syscall pop rdi jmp exit.exit_err short_err: ; rdi is the message (less than 8 chr) ; rsi is message len push rdi mov rdx, rsi mov rsi, rsp mov rax, 1 mov rdi, 1 syscall pop rdi ret strlen: ; rdi containing str pointer ; rax will contain strlen and rdi is unchanged mov rsi, rdi xor rdx, rdx pushf cld .loop: inc rdx lodsb mov cl, al test al, al jnz .loop dec rdx mov rax, rdx popf ret print_hnum: ; rdi : number to print ; rsi : output fd pushf mov rax, rdi xor rcx, rcx push rcx ; using stack as buffer std lea rdi, [rsp + 8] .loop: test rax, rax jz .endloop inc rcx inc rcx push rax and al, 0x0F call .al2digit stosb mov rax, [rsp] shr al, 4 call .al2digit stosb pop rax shr rax, 8 jmp .loop .endloop: mov rax, 1 xchg rdi, rsi inc rsi ;mov rdi, rsi ;mov rsi, rsp mov rdx, rcx syscall pop rax popf ret .al2digit: cmp al, 9 jg .hex add al, "0" ret .hex: add al, "A" - 10 ret %ifndef MIX_AUDIO ; simplest/shortes audio_callback : copy byte returned by run_glitch in *stream audio_cllbck: ; rdi -> *userdata ; rsi -> *stream ; rdx -> stream_len push rbx mov rcx, rdx mov rdi, rsi push rsi push rdx .loop: push rcx push rdi call run_glitch pop rdi stosb pop rcx inc dword [t] loop .loop mov rax, 1 ; write stream data to IPC pipe xor rdi, rdi mov edi, [ipc_pipe.wr] pop rdx pop rsi syscall cmp rax, -1 je exit_fatal pop rbx ret %endif %ifdef MIX_AUDIO ; another version of the audio callback using heap to store the data ; and SDL_MixAudio to copy data in *stream audio_cllbck: ; rdi -> *userdata ; rsi -> *stream ; rdx -> stream_len push rbx mov rcx, [cllbck_heapsz] cmp rcx, rdx jl .heap_brk .continue: mov rdi, [cllbck_heap] push rdx ; len push rdi push rsi ; *stream, dst mov rcx, rdx .pop_loop: ; populating heap with glitch datas push rcx push rdi call run_glitch pop rdi stosb pop rcx inc dword [t] loop .pop_loop pop rdi ; *stream pop rsi ; heap_start pop rdx ; len ;mov rsi, [rsp] ; heap_start ;mov rdx, [rsp+8] ; len mov rcx, SDL_MAX_VOLUME call SDL_MixAudio ;mov rax, 0x1 ; sys_write ;mov rdi, [ipc_pipe.wr] ;pop rsi ; heap_start ;pop rdx ; len ;syscall ;cmp rax, -1 ;je exit_fatal pop rbx ret .heap_brk: ; resize heap to handle push rdi push rsi push rdx mov rdi, [cllbck_heap] add rdi, rdx mov rax, 0xc ; brk syscall pop rdx pop rsi pop rdi mov [cllbck_heapsz], rdx jmp .continue %endif run_glitch: ; Run the glitch_pgm ; return TOSP value in eax mov rsi, glitch_pgm .loop: lodsq test rax, rax jz .end_glitch push rax lodsq mov rdi, rax pop rax push rsi call rax pop rsi jmp .loop .end_glitch: xor rbx, rbx mov ebx, [tosp] lea rdi, [stack_buff + rbx] mov eax, [rdi] ; DEBUG (can be use to output data to stdout) ;push rax ;xor rdi, rdi ;mov rdi, rax ;mov rsi, 1 ;call print_hnum ;mov rax, " " ;push rax ;mov rax, 1 ;mov rdi, 1 ;mov rsi, rsp ;mov rdx, 1 ;syscall ;pop rax ;pop rax ; /DEBUG ret OP: .numeric: ; rdi contain the number shr rdi, 32 ._push: ; push rdi (edi) on stack_buff mov eax, edi xor rbx, rbx mov ebx, [tosp] add ebx, 4 cmp ebx, STACK_SZ * 4 jl .go_push mov ebx, 0 .go_push: mov [tosp], ebx lea rdi, [stack_buff+rbx] stosd ret .drop: ; drop just calls pop ._pop: ; pop eax from stack_buff xor rbx, rbx mov ebx, [tosp] lea rsi, [stack_buff+rbx] xor rax, rax lodsd test ebx, ebx jz .pop_no_dec sub ebx, 4 jmp .pop_end .pop_no_dec: mov ebx, (STACK_SZ-1) * 4 .pop_end: mov [tosp], ebx ret .t: ; push t on the stack mov edi, [t] call ._push ret .put: pushf cld xor rbx, rbx xor rax, rax mov ebx, [tosp] lea rsi, [stack_buff+rbx] lodsd and eax, 0xFF inc eax mov edx, eax lodsd neg rdx lea rdi, [stack_buff + rdx] stosq call OP._pop popf ret .mul: call .prep_2arg mul ebx mov edi, eax call OP._push ret .div: call .prep_2arg test ebx, ebx jz .nodiv xor rdx, rdx div ebx jmp .divend .nodiv: xor eax, eax .divend: mov edi, eax call OP._push ret .add: call .prep_2arg add eax, ebx mov edi, eax call OP._push ret .sub: call .prep_2arg sub eax, ebx mov edi, eax call OP._push ret .mod: call .prep_2arg test rbx, rbx jz .nomod xor edx, edx div ebx jmp .endmod .nomod: xor edx, edx .endmod: mov edi, edx call OP._push ret .lshift: call OP._pop push rax call OP._pop pop rcx shl eax, cl mov edi, eax call OP._push ret .rshift: call OP._pop push rax call OP._pop pop rcx shr eax, cl mov edi, eax call OP._push ret .and: call .prep_2arg and eax, ebx mov edi, eax call OP._push ret .or: call .prep_2arg or eax, ebx mov edi, eax call OP._push ret .xor: call .prep_2arg xor eax, ebx mov edi, eax call OP._push ret .not: call OP._pop not eax mov edi, eax call OP._push ret .dup: call OP._pop push rax mov edi, eax call OP._push pop rdi call OP._push ret .pick: call OP._pop inc eax and eax, 0xFF mov ebx, 4 mov ecx, [tosp] mul ebx ; mul by data size cmp eax, [tosp] jg .pick_loop sub ecx, eax jmp .pick_lea .pick_loop: ; eax > tosp sub eax, ecx mov ecx, (STACK_SZ - 1) * 4 sub ecx, eax .pick_lea: lea rsi, [stack_buff+ecx] lodsd push rax call OP._pop pop rdi call OP._push ret .swap: call OP._pop push rax call OP._pop xchg rax, [rsp] mov edi, eax call OP._push pop rdi call OP._push ret .lt: call .prep_2arg xor rdi, rdi cmp eax, ebx jge .lt_false not rdi .lt_false: call OP._push ret .gt: call .prep_2arg xor rdi, rdi cmp eax, ebx jle .gt_false not rdi .gt_false: call OP._push ret .eq: call .prep_2arg xor rdi, rdi cmp eax, ebx jne .eq_false not rdi .eq_false: call OP._push ret .prep_2arg: ; utils that pop both arguments V1 in eax, V2 in ebx call OP._pop push rax call OP._pop pop rbx ret