#include "SDL.h" /* atari GTIA palette, currently only for NTSC */ #include "colortable.h" /* ROM data. rom.h and rom.c are generated from jumpmanjr.rom */ #include "rom.h" /* load address of the ROM image */ #define ROM_START 0x8000 /* address within ROM where the level descriptors start */ #define LEVEL_BASE 0xa000 /* size of each level descriptor in the ROM */ #define LEVEL_SIZE 0x40 /* address within ROM where the GR.1/2 font starts */ #define FONT 0x9e00 /* where the level names start in the ROM */ #define LEVEL_NAMES 0xbb00 /* length of each level name */ #define LEVEL_NAME_SIZE 20 /* pixels to blit to our texture. modify these at will, they will be blitted once per frame. */ Uint32 texture_pixels[160 * 200]; /* color registers for the 4 playfield colors on each level. these hold indices into colortable. */ Uint8 color_regs[4]; /* read 1 byte at atari address from the ROM */ Uint8 peek(int addr) { return jumpmanjr_rom[addr - ROM_START]; } /* read 1 word at atari address from the ROM */ Uint16 dpeek(int addr) { return (peek(addr) | (peek(addr + 1) << 8)); } /* get a pointer within the ROM, give an atari address */ Uint8 *romptr(int addr) { return jumpmanjr_rom + (addr - ROM_START); } /* deltas are stored in ROM as twos-complement. we'll interpret them programatically instead of casting them to signed chars. */ int getdelta(Uint8 d) { return (d < 128) ? (d) : (d - 256); } /* clear out texture_pixels. generally only needs doing just before rendering a new level. */ void clear_pixels(void) { memset(texture_pixels, 0, sizeof(texture_pixels)); } /* draw one copy of a shape (see ../level_maps.txt) */ void draw_shape(int shapeaddr, int xpos, int ypos) { int w = 0, xoffs = 0, yoffs = 0, c = 0; Uint8 *p = romptr(shapeaddr); ypos *= 2; fprintf(stderr, "draw_shape(%04x, %d, %d);\n", shapeaddr, xpos, ypos); while(1) { if(*p == 0xff) break; w = *p++; xoffs = *p++; yoffs = 2 * (*p++); for(c = 0; c < w; c++) { Uint8 reg = *p++; fprintf(stderr, "drawing pixel reg %d at %d, %d\n", reg, xpos + xoffs, ypos + yoffs); texture_pixels[xpos + xoffs + c + (160 * (ypos + yoffs))] = colortable[color_regs[reg]]; texture_pixels[xpos + xoffs + c + (160 * (ypos + yoffs + 1))] = colortable[color_regs[reg]]; } } } /* draw multiple copies of a shape, separated by dx/dy pixels */ void draw_shapes(int shapeaddr, int dx, int dy, int xpos, int ypos, int copies) { fprintf(stderr, "draw_shapes(%04x, %d, %d, %d, %d, %d);\n", shapeaddr, dx, dy, xpos, ypos, copies); while(copies--) { draw_shape(shapeaddr, xpos, ypos); xpos += dx; ypos += dy; } } /* draw map data. this just takes a rom address, so it can be used for map changes and such (doesn't have to be in the level descriptor even). basically this is a C port of draw_map from the disassembly. */ void draw_map(int addr) { Uint8 *p = romptr(addr); int shapeaddr = 0, dx = 0, dy = 0, xpos = 0, ypos = 0, copies = 0; while(1) { switch(*p) { case 0xff: /* end opcode */ return; case 0xfe: /* set shape */ shapeaddr = p[1] | ( p[2] << 8); p += 3; break; case 0xfd: /* set delta */ dx = getdelta(p[1]); dy = getdelta(p[2]); p += 3; break; case 0xfc: /* jump */ p = romptr(p[1] | ( p[2] << 8)); break; default: /* draw shape */ xpos = p[0]; ypos = p[1]; copies = p[2]; draw_shapes(shapeaddr, dx, dy, xpos, ypos, copies); p += 3; } } } /* there are only 64 chars in the font! */ void gr1_char(int x, int y, Uint8 color, Uint8 chr) { int row, pix; Uint8 *p; chr -= 32; chr %= 64; p = romptr(FONT + chr * 8); for(row = 0; row < 8; row++) { int b = *p++; for(pix = 0; pix < 8; pix++) { if(b & 0x80) { texture_pixels[x + pix + 160 * y] = colortable[color]; } else { texture_pixels[x + pix + 160 * y] = colortable[0]; } b <<= 1; } y++; } } void gr1_text(int x, int y, Uint8 color, int addr, int len) { Uint8 *p = romptr(addr); while(len--) { gr1_char(x, y, color, *p++); x += 8; } } /* draw the initial map for a level, given the *zero based* level number (range 0 to 11). eventually this should play the level intro music and mess with the color regs. */ void render_level(int levelnum) { int leveldesc = LEVEL_BASE + LEVEL_SIZE * levelnum; int mapaddr = dpeek(leveldesc + 22); int levelname = LEVEL_NAMES + LEVEL_NAME_SIZE * levelnum; Uint32 start = SDL_GetTicks(); color_regs[0] = 0; color_regs[1] = peek(leveldesc + 47); color_regs[2] = peek(leveldesc + 48); color_regs[3] = peek(leveldesc + 49); clear_pixels(); gr1_text(0, 178, 12, levelname, LEVEL_NAME_SIZE); fprintf(stderr, "leveldesc %04x, mapaddr %04x\n", leveldesc, mapaddr); draw_map(mapaddr); fprintf(stderr, "render_level(%d) took %dms\n", levelnum, SDL_GetTicks() - start); } int main(int argc, char **argv) { int lev = 0, done = 0, fullscreen = 0; Uint32 start, end, now; SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; SDL_Event event; if(SDL_Init(SDL_INIT_VIDEO) < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError()); return 3; } if(SDL_CreateWindowAndRenderer(320, 200, 0, &window, &renderer)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window and renderer: %s", SDL_GetError()); return 3; } texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, 160, 200); SDL_RenderSetLogicalSize(renderer, 320, 200); render_level(lev); while(!done) { start = SDL_GetTicks(); end = start + 16; // NTSC timing while(SDL_PollEvent(&event)) { if(event.type == SDL_QUIT) { break; } if(event.type == SDL_KEYDOWN) { switch(event.key.keysym.sym) { case SDLK_SPACE: lev++; if(lev == 12) lev = 0; render_level(lev); break; case SDLK_BACKSPACE: lev--; if(lev < 0) lev = 11; render_level(lev); break; case SDLK_ESCAPE: done = 1; break; case SDLK_RETURN: fullscreen = !fullscreen; SDL_SetWindowFullscreen(window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); break; default: break; } } } SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00); SDL_RenderClear(renderer); SDL_UpdateTexture(texture, NULL, texture_pixels, 160 * 4); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); now = SDL_GetTicks(); if(now < end) { SDL_Delay(end - now); // fprintf(stderr, "frame took %d ms\n", now - start); } else { fprintf(stderr, "!! frame took too long by %d ms\n", now - end); } } SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; }