/* * 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" #define ARGB(r, g, b) ((uint32_t)(((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), }; 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) { /* blitter control */ case 0xb0: /* settings mode */ if (v & 128) { blt_set = 1; blt_paramc = 0; return p; } /* draw (read or write) */ if (v & 5) { blt_set = 0; /* can't be both */ if ((v & 1) && (v & 4)) return 0xff; /* missing parameters */ if (blt_paramc != 6) return 0xff; /* 1: write into vram */ uint16_t src = blt_param[0] | (blt_param[1] << 8); uint16_t dst = VIDEO_RAM + blt_param[2] + blt_param[3] * 128; /* 4: read from vram */ if (v & 4) { /* swap */ uint16_t t = dst; dst = src; src = t; } 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; /* blitter settings */ 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); 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; /* 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); 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; } } } pitch = 0; fb_data = NULL; SDL_LockTexture(fb, NULL, (void **)&fb_data, &pitch); rc = tr8_eval(&vm); SDL_UnlockTexture(fb); if (!rc) break; /* 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); SDL_RenderPresent(renderer); pitch = 0; fb_data = NULL; SDL_LockTexture(fb, NULL, (void **)&fb_data, &pitch); rc = tr8_frame_int(&vm); SDL_UnlockTexture(fb); if (!rc) break; } if (canvas) SDL_DestroyTexture(canvas); if (fb) SDL_DestroyTexture(fb); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(screen); return 0; }