#include "drain.h" #include "ops.h" #include #include #include #define MEM_SIZE 0xffff static const uint16_t buttons[SDL_NUM_SCANCODES] = { [SDL_SCANCODE_BACKSPACE] = 1 << 2, [SDL_SCANCODE_RETURN] = 1 << 3, /* WASD JK */ [SDL_SCANCODE_J] = 1 << 0, [SDL_SCANCODE_K] = 1 << 1, [SDL_SCANCODE_W] = 1 << 4, [SDL_SCANCODE_S] = 1 << 5, [SDL_SCANCODE_A] = 1 << 6, [SDL_SCANCODE_D] = 1 << 7, /* arrows XC */ [SDL_SCANCODE_X] = 1 << 0, [SDL_SCANCODE_C] = 1 << 1, [SDL_SCANCODE_UP] = 1 << 4, [SDL_SCANCODE_DOWN] = 1 << 5, [SDL_SCANCODE_LEFT] = 1 << 6, [SDL_SCANCODE_RIGHT] = 1 << 7, }; static uint16_t *mem = NULL, *stack = NULL, *rstack = NULL; static size_t stack_ptr = 0, rstack_ptr = 0; static SDL_Window *window = NULL; static SDL_Renderer *renderer = NULL; static SDL_Texture *target = NULL; static Uint64 next_time = 0; static int fail(void) { if (target != NULL) SDL_DestroyTexture(target); if (renderer != NULL) SDL_DestroyRenderer(renderer); if (window != NULL) SDL_DestroyWindow(window); SDL_Quit(); if (mem != NULL) free(mem); if (stack != NULL) free(stack); if (rstack != NULL) free(rstack); return 1; } static uint16_t get_short(const unsigned char *s) { return s[0] | (s[1] * 0x0100); } static int cycle_events(uint16_t *input) { SDL_Event e; while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_QUIT: return 1; case SDL_KEYDOWN: if (e.key.repeat) break; if (e.key.keysym.scancode == SDL_SCANCODE_F) { static int fullscreen = 0; fullscreen = !fullscreen; SDL_SetWindowFullscreen(window, fullscreen); } *input |= buttons[e.key.keysym.scancode]; break; case SDL_KEYUP: *input &= ~(buttons[e.key.keysym.scancode]); break; default: break; } } return 0; } static void render(uint16_t *mem) { int win_w, win_h; SDL_GetWindowSize(window, &win_w, &win_h); win_w = win_w ? win_w : 1; win_h = win_h ? win_h : 1; const float ratio_w = win_w / 128.0; const float ratio_h = win_h / 128.0; float scale = (ratio_w < ratio_h) ? ratio_w : ratio_h; if (scale > 1.0) scale = (int)scale; const int off_x = (win_w - 128 * scale) / 2; const int off_y = (win_h - 128 * scale) / 2; const SDL_Rect dest = { off_x, off_y, 128 * scale, 128 * scale }; SDL_SetRenderTarget(renderer, target); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); for (int y = 0; y < 128; y++) { for (int x = 0; x < 128; x++) { const uint16_t pixel = mem[x + y * 128]; if (!pixel) continue; const uint16_t r = (pixel & 0x1f) << 3; const uint16_t g = ((pixel >> 5) & 0x3f) << 2; const uint16_t b = ((pixel >> 11) & 0x1f) << 3; SDL_SetRenderDrawColor(renderer, r, g, b, 255); SDL_RenderDrawPoint(renderer, x, y); } } SDL_SetRenderTarget(renderer, NULL); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, target, NULL, &dest); next_time += 1000 / 30; const Uint64 cur_time = SDL_GetTicks64(); if (next_time <= cur_time) { fprintf(stderr, "lagging %lu\n", cur_time - next_time); next_time = cur_time; } else SDL_Delay(next_time - cur_time); SDL_RenderPresent(renderer); } static void push(uint16_t v) { stack[stack_ptr] = v; stack_ptr += 1; if (stack_ptr >= MEM_SIZE) { fprintf(stderr, "stack overflow\n"); exit(fail()); } } static uint16_t pop(void) { if (stack_ptr == 0) { fprintf(stderr, "stack underflow\n"); exit(fail()); } stack_ptr -= 1; return stack[stack_ptr]; } static void push_rs(uint16_t v) { rstack[rstack_ptr] = v; rstack_ptr += 1; if (rstack_ptr >= MEM_SIZE) { fprintf(stderr, "return stack overflow\n"); exit(fail()); } } static uint16_t pop_rs(void) { if (rstack_ptr == 0) { fprintf(stderr, "return stack underflow\n"); exit(fail()); } rstack_ptr -= 1; return rstack[rstack_ptr]; } static long exec_op(uint16_t *mem, long pc) { switch (mem[pc]) { case OP_NOP: return pc + 1; case OP_LIT: push(mem[pc + 1]); return pc + 2; case OP_POP: pop(); return pc + 1; case OP_NIP: { const uint16_t a = pop(); pop(); push(a); return pc + 1; } case OP_SWP: { const uint16_t a = pop(); const uint16_t b = pop(); push(a); push(b); return pc + 1; } case OP_ROT: { const uint16_t a = pop(); const uint16_t b = pop(); const uint16_t c = pop(); push(b); push(a); push(c); } return pc + 1; case OP_DUP: { const uint16_t a = pop(); push(a); push(a); return pc + 1; } case OP_OVR: { const uint16_t a = pop(); const uint16_t b = pop(); push(b); push(a); push(b); return pc + 1; } case OP_EQU: push(pop() == pop()); return pc + 1; case OP_NEQ: push(pop() != pop()); return pc + 1; case OP_GTH: { const uint16_t b = pop(); const uint16_t a = pop(); push(a > b); return pc + 1; } case OP_LTH: { const uint16_t b = pop(); const uint16_t a = pop(); push(a < b); return pc + 1; } case OP_JEZ: if (pop() == 0) return mem[pc + 1]; return pc + 2; case OP_JNZ: if (pop() != 0) return mem[pc + 1]; return pc + 2; case OP_JMP: return mem[pc + 1]; case OP_JRT: push_rs(pc + 2); return mem[pc + 1]; case OP_RET: if (rstack_ptr > 0) return pop_rs(); return MEM_SIZE; case OP_LDA: push(mem[pop()]); return pc + 1; case OP_STA: { const uint16_t a = pop(); const uint16_t b = pop(); mem[a] = b; return pc + 1; } case OP_RED: push(getchar()); return pc + 1; case OP_WRT: putchar(pop()); return pc + 1; case OP_ADD: push(pop() + pop()); return pc + 1; case OP_SUB: { const uint16_t b = pop(); const uint16_t a = pop(); push(a - b); return pc + 1; } case OP_MUL: push(pop() * pop()); return pc + 1; case OP_DIV: { const uint16_t b = pop(); const uint16_t a = pop(); push(a / b); return pc + 1; } case OP_AND: push(pop() & pop()); return pc + 1; case OP_ORA: push(pop() | pop()); return pc + 1; case OP_XOR: push(pop() ^ pop()); return pc + 1; case OP_LSF: { const uint16_t shift = pop(); const uint16_t a = pop(); push(a << shift); return pc + 1; } case OP_RSF: { const uint16_t shift = pop(); const uint16_t a = pop(); push(a >> shift); return pc + 1; } case OP_SLP: if (cycle_events(mem + 0xbffe)) return MEM_SIZE; render(mem + 0xbfff); return pc + 1; case OP_INC: push(pop() + 1); return pc + 1; case OP_DEC: push(pop() - 1); return pc + 1; case OP_DBG: fprintf(stderr, "<%04x> ", (unsigned)stack_ptr); for (size_t i = 0; i < stack_ptr; i++) fprintf(stderr, "%04x ", stack[i]); fputc('\n', stderr); return pc + 1; default: fprintf(stderr, "unhandled opcode %04x\n", mem[pc]); if (mem[pc] <= OP_SLP) fprintf(stderr, "(%s)\n", ops[mem[pc]]); exit(fail()); } } static void exec_data(uint16_t *mem) { long pc = 0; do pc = exec_op(mem, pc); while (pc < MEM_SIZE); } int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "usage: %s \n", argv[0]); return 1; } FILE *file = fopen(argv[1], "rb"); if (file == NULL) { perror(argv[1]); return 1; } long size; unsigned char *const data = (unsigned char*)drain(file, &size); fclose(file); if (data == NULL) return 1; mem = calloc(MEM_SIZE, sizeof(uint16_t)); if (mem == NULL) { perror("main:calloc"); free(data); return 1; } for (long i = 0; i < size; i += 2) mem[i / 2] = get_short(data + i); free(data); stack = calloc(MEM_SIZE, sizeof(uint16_t)); if (stack == NULL) { perror("main:calloc"); return fail(); } rstack = calloc(MEM_SIZE, sizeof(uint16_t)); if (rstack == NULL) { perror("main:calloc"); return fail(); } if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "%s\n", SDL_GetError()); return fail(); } if (SDL_CreateWindowAndRenderer(384, 384, SDL_WINDOW_RESIZABLE, &window, &renderer) < 0) { fprintf(stderr, "%s\n", SDL_GetError()); return fail(); } target = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_TARGET, 128, 128); if (target == NULL) { fprintf(stderr, "%s\n", SDL_GetError()); return fail(); } next_time = SDL_GetTicks64(); exec_data(mem); return fail(), 0; }