/* * TR8 VM Player * Copyright (C) 2023 by Juan J. Martinez * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include #include #include "SDL.h" #include "vm.h" #include "font.h" #define ARGB(r, g, b) ((uint32_t)(0xff000000|((r)<<16)|((g)<<8)|(b))) #define APP_NAME "TR8 VM Player" #define APP_VERSION "0.1-alpha" #define TR8_W 128 #define TR8_H 128 #define WINDOW_SCALE 4 #define WINDOW_W (TR8_W * WINDOW_SCALE) #define WINDOW_H (TR8_H * WINDOW_SCALE) static void resize_full_screen(SDL_Window *win, SDL_Renderer *renderer, int fullscreen, SDL_Rect *s) { if (!fullscreen) { SDL_SetWindowFullscreen(win, 0); s->x = 0; s->y = 0; s->w = WINDOW_W; s->h = WINDOW_H; } else { SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN_DESKTOP); int sw, sh, w, h, scale; SDL_GetRendererOutputSize(renderer, &w, &h); sw = w / TR8_W; sh = h / TR8_H; scale = sw > sh ? sh : sw; s->x = (w - (TR8_W * scale)) / 2; s->y = (h - (TR8_H * scale)) / 2; s->w = TR8_W * scale; s->h = TR8_H * scale; } } static uint32_t palette[] = { ARGB(0x00, 0x00, 0x00), ARGB(0x00, 0x00, 0xaa), ARGB(0x00, 0xaa, 0x00), ARGB(0x00, 0xaa, 0xaa), ARGB(0xaa, 0x00, 0x00), ARGB(0xaa, 0x00, 0xaa), ARGB(0xaa, 0x55, 0x00), ARGB(0xaa, 0xaa, 0xaa), ARGB(0x55, 0x55, 0x55), ARGB(0x55, 0x55, 0xff), ARGB(0x55, 0xff, 0x55), ARGB(0x55, 0xff, 0xff), ARGB(0xff, 0x55, 0x55), ARGB(0xff, 0x55, 0xff), ARGB(0xff, 0xff, 0x55), ARGB(0xff, 0xff, 0xff), }; uint32_t *ui_data = NULL; static void ui_put_glyph(uint32_t x, uint32_t y, uint8_t c) { uint32_t *p = ui_data + x + (y * WINDOW_W); for (uint8_t j = 0; j < 8; j++) for (uint8_t i = 0; i < 8; i++) if (font[c][j] & (128 >> i)) p[i + j * WINDOW_W] = palette[12]; } static void ui_put_text(uint32_t x, uint32_t y, char *text) { uint32_t ox = x; while (*text) { uint8_t c = *text++; if (c == '\n') { y += 12; if (y > WINDOW_H) break; x = ox; continue; } ui_put_glyph(x, y, c); x += 8; if (x > WINDOW_W) { x = 0; y += 12; } } } uint8_t ram[UINT16_MAX + 1] = { 0 }; uint32_t *fb_data = NULL; static void update_fb() { for (uint16_t addr = 0; addr < 16384; addr++) fb_data[addr] = palette[ram[VIDEO_RAM + addr] & 15]; } void write_m(uint16_t addr, uint8_t b) { if (addr >= VIDEO_RAM && addr < VIDEO_RAM + 16384) fb_data[addr - VIDEO_RAM] = palette[b & 15]; ram[addr] = b; } uint8_t read_m(uint16_t addr) { return ram[addr]; } uint8_t blt_set = 0; uint8_t blt_paramc = 0; uint8_t blt_param[6] = { 0 }; uint8_t port(uint8_t p, uint8_t v) { switch (p) { case 0xb0: if (v & 128) { blt_set = 1; blt_paramc = 0; return p; } if (v & 1) { blt_set = 0; if (blt_paramc != 6) return 0xff; /* draw */ uint16_t src = blt_param[0] | (blt_param[1] << 8); uint16_t dst = VIDEO_RAM + blt_param[2] + blt_param[3] * 128; for (uint8_t y = 0; y < blt_param[5]; y++) for (uint8_t x = 0; x < blt_param[4]; x++) { uint8_t b = read_m(src++); /* skip transparent if transparent flag is set */ if ((v & 2) && (b & 128)) continue; write_m(dst + x + y * 128, b); } return p; } return 0xff; case 0xb1: if (!blt_set || blt_paramc > 5) return 0xff; blt_param[blt_paramc++] = v; return p; default: return 0xff; } } int main(int argc, char *argv[]) { FILE *fd; size_t size; if (argc < 2) { fprintf(stderr, "Usage: input.prg\n"); return 1; } fd = fopen(argv[1], "rb"); if (!fd) { fclose(fd); fprintf(stderr, "Error opening input\n"); return 1; } fseek(fd, 0, SEEK_END); size = ftell(fd); if (size > UINT16_MAX + 1) { fclose(fd); fprintf(stderr, "Input is too large\n"); return 1; } fseek(fd, 0, SEEK_SET); if (fread(ram, 1, size, fd) != size) { fclose(fd); fprintf(stderr, "Error reading input\n"); return 1; } fclose(fd); SDL_Window *screen = SDL_CreateWindow(APP_NAME " " APP_VERSION, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_W, WINDOW_H, SDL_WINDOW_OPENGL); if (!screen) { fprintf(stderr, "Failed to create a window: %s\n", SDL_GetError()); return 1; } SDL_Renderer *renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); if (!renderer) { fprintf(stderr, "Failed to create the renderer: %s\n", SDL_GetError()); return 1; } SDL_SetHint("SDL_HINT_RENDER_SCALE_QUALITY", "0"); SDL_Texture *canvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, TR8_W, TR8_H); if (!canvas) { fprintf(stderr, "Failed to create the canvas: %s\n", SDL_GetError()); return 1; } SDL_Texture *fb = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, TR8_W, TR8_H); if (!fb) { fprintf(stderr, "Failed to create the frame-buffer: %s\n", SDL_GetError()); return 1; } SDL_SetTextureBlendMode(fb, SDL_BLENDMODE_NONE); SDL_Texture *ui = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WINDOW_W, WINDOW_H); if (!ui) { fprintf(stderr, "Failed to create the UI: %s\n", SDL_GetError()); return 1; } SDL_SetTextureBlendMode(ui, SDL_BLENDMODE_BLEND); int fullscreen = 0; SDL_Rect dst; resize_full_screen(screen, renderer, fullscreen, &dst); SDL_RenderClear(renderer); SDL_RenderPresent(renderer); Tr8 vm; tr8_init(&vm, write_m, read_m, port); int pitch = 0; uint8_t rc = 1; /* update full fb once to sync with RAM */ pitch = 0; fb_data = NULL; SDL_LockTexture(fb, NULL, (void **)&fb_data, &pitch); update_fb(); SDL_UnlockTexture(fb); /* prepare UI layer */ pitch = 0; ui_data = NULL; SDL_LockTexture(ui, NULL, (void **)&ui_data, &pitch); memset(ui_data, 0x20202080, sizeof(uint32_t) * WINDOW_W * WINDOW_H); SDL_UnlockTexture(ui); SDL_Event ev; while (1) { if (SDL_PollEvent(&ev)) { if (ev.type == SDL_QUIT) break; if (ev.type == SDL_KEYDOWN) { if (ev.key.keysym.sym == SDLK_ESCAPE) { if (fullscreen) resize_full_screen(screen, renderer, 0, &dst); break; } if (ev.key.keysym.sym == SDLK_RETURN && (SDL_GetModState() & KMOD_LALT)) { fullscreen ^= 1; resize_full_screen(screen, renderer, fullscreen, &dst); continue; } } } if (rc) { pitch = 0; fb_data = NULL; SDL_LockTexture(fb, NULL, (void **)&fb_data, &pitch); rc = tr8_eval(&vm); SDL_UnlockTexture(fb); if (!rc) { /* non recoverable error */ pitch = 0; ui_data = NULL; SDL_LockTexture(ui, NULL, (void **)&ui_data, &pitch); ui_put_text(10, 10, "[VM HALT]"); char buffer[256]; tr8_dump(&vm, buffer); ui_put_text(10, 22, buffer); SDL_UnlockTexture(ui); } } /* render to the canvas */ SDL_SetRenderTarget(renderer, canvas); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, fb, NULL, NULL); /* now target the screen */ SDL_SetRenderTarget(renderer, NULL); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, canvas, NULL, &dst); if (!rc) SDL_RenderCopy(renderer, ui, NULL, &dst); SDL_RenderPresent(renderer); if (rc) { pitch = 0; fb_data = NULL; SDL_LockTexture(fb, NULL, (void **)&ui_data, &pitch); rc = tr8_frame_int(&vm); SDL_UnlockTexture(fb); if (!rc) { /* non recoverable error */ pitch = 0; ui_data = NULL; SDL_LockTexture(ui, NULL, (void **)&fb_data, &pitch); ui_put_text(10, 10, "[VM HALT]"); char buffer[256]; tr8_dump(&vm, buffer); ui_put_text(10, 22, buffer); SDL_UnlockTexture(ui); } } } if (canvas) SDL_DestroyTexture(canvas); if (fb) SDL_DestroyTexture(fb); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(screen); return 0; }