/* * Copyright (C) 2019 Jordan Halase * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include struct Machine { /** * Memory map: * 0x000-0x1ff Chip-8 interpreter reserved * 0x050-0x0a0 4x5 pixel font set (0x0-0xf) * 0x200-0xfff Program ROM and memory */ uint8_t memory[4096]; /** 64x32 1-bit video memory */ uint8_t video[32][64]; /** Register set */ uint8_t v[16]; /** Key set */ uint8_t key[16]; /** Delay timer */ uint8_t dt; /** Sound timer */ uint8_t st; /** Address register */ uint16_t i; /** Program counter */ uint16_t pc; /** Stack pointer */ uint16_t sp; /** Stack */ uint16_t stack[24]; }; static const uint8_t font[80] = { 0xf0, 0x90, 0x90, 0x90, 0xf0, // 0 0x20, 0x60, 0x20, 0x20, 0x70, // 1 0xf0, 0x10, 0xf0, 0x80, 0xf0, // 2 0xf0, 0x10, 0xf0, 0x10, 0xf0, // 3 0x90, 0x90, 0xf0, 0x10, 0x10, // 4 0xf0, 0x80, 0xf0, 0x10, 0xf0, // 5 0xf0, 0x80, 0xf0, 0x90, 0xf0, // 6 0xf0, 0x10, 0x20, 0x40, 0x40, // 7 0xf0, 0x90, 0xf0, 0x90, 0xf0, // 8 0xf0, 0x90, 0xf0, 0x10, 0xf0, // 9 0xf0, 0x90, 0xf0, 0x90, 0x90, // a 0xe0, 0x90, 0xe0, 0x90, 0xe0, // b 0xf0, 0x80, 0x80, 0x80, 0xf0, // c 0xe0, 0x90, 0x90, 0x90, 0xe0, // d 0xf0, 0x80, 0xf0, 0x80, 0xf0, // e 0xf0, 0x80, 0xf0, 0x80, 0x80 // f }; SDL_atomic_t osc_coef; #define PROGRAM_START 0x200 #define GETX(op) (((op)>>8)&0xf) #define GETY(op) (((op)>>4)&0xf) #define ADDR(op) ((op)&0xfff) #define IMM(op) ((op)&0xff) #define FUNC(op) ((op)&0xf) int interpret(struct Machine *vm, long cycles) { uint16_t opcode; int x, y, h; while (cycles--) { opcode = vm->memory[vm->pc++] << 8 | vm->memory[vm->pc++]; //printf("%x\t%x\n", vm->pc-2, opcode); switch ((opcode>>12) & 0xf) { case 0x0: switch (opcode) { case 0x00e0: // clear screen memset(vm->video, 0, sizeof(vm->video)); break; case 0x00ee: // return vm->pc = vm->stack[--vm->sp]; break; default: goto err; } break; case 0x2: // call vm->stack[vm->sp++] = vm->pc; case 0x1: // goto vm->pc = ADDR(opcode); //printf("GOTO %x\n", vm->pc); break; case 0x3: // vx == immediate if (vm->v[GETX(opcode)] == IMM(opcode)) vm->pc += 2; break; case 0x4: // vx != immediate if (vm->v[GETX(opcode)] != IMM(opcode)) vm->pc += 2; break; case 0x5: // vx == vy if (FUNC(opcode) != 0) { goto err; } if (vm->v[GETX(opcode)] == vm->v[GETY(opcode)]) vm->pc += 2; break; case 0x6: // set vx to immediate vm->v[GETX(opcode)] = IMM(opcode); break; case 0x7: // add immediate to vx (no carry) vm->v[GETX(opcode)] += IMM(opcode); break; case 0x8: x = GETX(opcode), y = GETY(opcode); switch (FUNC(opcode)) { case 0: // vx = vy vm->v[x] = vm->v[y]; break; case 1: // vx |= vy vm->v[x] |= vm->v[y]; break; case 2: // vx &= vy vm->v[x] &= vm->v[y]; break; case 3: // vx ^= vy vm->v[x] ^= vm->v[y]; break; case 4: // FIXME: flags register vf through case 7 h = vm->v[x] + vm->v[y]; if (h > 0xff) { h &= 0xff; vm->v[0xf] = 1; } else { vm->v[0xf] = 0; } vm->v[x] = h; break; case 5: vm->v[0xf] = vm->v[x] > vm->v[y] ? 1 : 0; vm->v[x] -= vm->v[y]; break; case 6: if (vm->v[x] & 1) { vm->v[0xf] = 1; } else { vm->v[0xf] = 0; } vm->v[x] >>= 1; break; case 7: vm->v[0xf] = vm->v[y] > vm->v[x] ? 1 : 0; vm->v[x] = vm->v[y] - vm->v[x]; break; case 0xe: vm->v[0xf] = vm->v[x] & 0x80 ? 1 : 0; vm->v[x] <<= 1; break; default: goto err; } break; case 0x9: // vx != vy if (FUNC(opcode) != 0) { goto err; } if (vm->v[GETX(opcode)] != vm->v[GETY(opcode)]) vm->pc += 2; break; case 0xa: // set address register vm->i = ADDR(opcode); break; case 0xb: // jump to address offset vm->pc = vm->v[0] + ADDR(opcode); break; case 0xc: // vx = random() & immediate vm->v[GETX(opcode)] = rand() & IMM(opcode); break; case 0xd: // draw x = vm->v[GETX(opcode)]; y = vm->v[GETY(opcode)]; h = FUNC(opcode); vm->v[0xf] = 0; for (int yy = 0; yy < h; ++yy) { const uint16_t p = vm->memory[vm->i + yy]; for (int xx = 0; xx < 8; ++xx) { if (p&(0x80>>xx)) { if (vm->video[(y+yy)&0x1f][(x+xx)&0x3f]) { vm->v[0xf] = 1; } vm->video[(y+yy)&0x1f][(x+xx)&0x3f] ^= 1; } } } break; case 0xe: switch (IMM(opcode)) { case 0x9e: if (vm->key[vm->v[GETX(opcode)]]) vm->pc += 2; break; case 0xa1: if (!vm->key[vm->v[GETX(opcode)]]) vm->pc += 2; break; default: goto err; } break; case 0xf: x = GETX(opcode); switch (IMM(opcode)) { case 0x07: vm->v[x] = vm->dt; break; case 0x0a: //printf("WAITKEY\n"); // TODO vm->pc -= 2; //goto err; break; case 0x15: vm->dt = vm->v[x]; break; case 0x18: vm->st = vm->v[x]; SDL_AtomicSet(&osc_coef, 1); break; case 0x1e: vm->i += vm->v[x]; break; case 0x29: vm->i = 0x50 + vm->v[x]*5; break; case 0x33: vm->memory[vm->i+0] = vm->v[x] / 100; vm->memory[vm->i+1] = (vm->v[x] / 10) % 10; vm->memory[vm->i+2] = vm->v[x] % 10; break; case 0x55: memcpy(&vm->memory[vm->i], &vm->v, x+1); break; case 0x65: memcpy(&vm->v, &vm->memory[vm->i], x+1); break; default: goto err; } break; default: err: fprintf(stderr, "Illegal instruction\n%x:\t%x\n", vm->pc, opcode); return 1; } } return 0; } void read_file(const char *path, uint8_t *buffer, size_t *size) { FILE *fp = fopen(path, "rb"); if (!fp) { fprintf(stderr, "Cannot open file '%s'\n", path); exit(1); } const int fd = fileno(fp); struct stat statbuf; if (fstat(fd, &statbuf) == -1) { fprintf(stderr, "Cannot read file status\n"); exit(1); } printf("File of %ld bytes\n", statbuf.st_size); size_t r = fread(buffer, statbuf.st_size, 1, fp); if (r != 1) { fprintf(stderr, "Could not read file in one go (FIXME) %ld\n", r); exit(1); } fclose(fp); if (size) *size = statbuf.st_size; } void audio_callback(void *data, Uint8 *stream, int len) { int i = 0; uint64_t *buf = (uint64_t*)stream; for (i = 0; i < (len>>3); ++i) { const int c = SDL_AtomicGet(&osc_coef); buf[i] = c ? i&4 ? 0xc0c0c0c0c0c0c0c0 : 0x4040404040404040 : 0x8080808080808080; } } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "No ROM provided\n"); exit(1); } srand(time(NULL)); SDL_Init(SDL_INIT_EVERYTHING); SDL_AudioSpec want = { .freq = 48000, .format = AUDIO_U8, .channels = 1, .samples = 1024, .callback = audio_callback }, have = {0}; SDL_AudioDeviceID dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); if (!dev) { fprintf(stderr, "%s\n", SDL_GetError()); exit(1); } printf("format\t%d\nchannels\t%d\nsilence\t%d\nsamples\t%d\n", have.format, have.channels, have.silence, have.samples); SDL_PauseAudioDevice(dev, 0); struct Machine *vm = calloc(1, sizeof(*vm)); memcpy(&vm->memory[0x50], font, sizeof(font)); read_file(argv[1], &vm->memory[PROGRAM_START], NULL); SDL_Surface *surf = SDL_CreateRGBSurfaceWithFormatFrom( vm->video, 64, 32, 1, 64, SDL_PIXELFORMAT_INDEX8 ); if (!surf) { fprintf(stderr, "%s\n", SDL_GetError()); exit(1); } memset(surf->format->palette->colors, 0x22, 3); SDL_Window *win = SDL_CreateWindow("Chip8", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 320, 0); SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); SDL_Surface *scrgb = SDL_CreateRGBSurfaceWithFormat( 0, 64, 32, 8, SDL_PIXELFORMAT_ARGB8888 ); SDL_Texture *tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 64, 32); vm->pc = PROGRAM_START; printf("Machine size:\t%lu\tB\n", sizeof(struct Machine)); printf("Font size:\t%lu\tB\n", sizeof(font)); SDL_Event e; while(1) { while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_QUIT: goto quit; } } if (interpret(vm, 32)) goto quit; SDL_BlitSurface(surf, NULL, scrgb, NULL); SDL_UpdateTexture(tex, NULL, scrgb->pixels, scrgb->pitch); SDL_RenderClear(ren); SDL_RenderCopy(ren, tex, NULL, NULL); SDL_RenderPresent(ren); vm->dt = vm->dt > 0 ? vm->dt-1 : 0; if (vm->st > 0) { if (--vm->st == 0) { SDL_AtomicSet(&osc_coef, 0); } } } quit: free(vm); SDL_PauseAudioDevice(dev, 1); SDL_Quit(); return 0; }