/* Creating a DCPU-16 (version 1.1) emulator! */ /* For all information, see http://dcpu.com/ ҂ ՅԄƖֆׇ */ #include /* Using libSDL for graphics & keyboard handling */ #include #include #include #include typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; // Graphics settings and memory addresses. static constexpr int WIDTH=128, HEIGHT=96, SCALE=4, GFX_BASE = 0x8000, // end at 0x8540 INPUT_BASE = 0x9000, // end at 0x9010 SPRITE_BASE = 0x9050, // end at 0x9070 N_SPRITES = 16; enum reg_index { A,B,C, X,Y,Z, I,J, PC,SP,EX }; struct Emulator { u16 memory[0x10000] = {}, reg[11] = {}; SDL_Surface* s = nullptr; Emulator() { // Initialize the SDL 1.2 library for graphics output. SDL_Init(SDL_INIT_VIDEO); SDL_InitSubSystem(SDL_INIT_VIDEO); std::signal(SIGINT, SIG_DFL); s = SDL_SetVideoMode(WIDTH*SCALE,HEIGHT*SCALE, 32,0); // For now, I am not using SDL 2.0, because it requires // considerably more lines of code to accomplish the // same things that I'm doing here. } void RenderGraphics(u8 pixbuf[WIDTH*HEIGHT]) { // Render the screen background. // It consists of 3x3 pixel cells, where each 3x3 cell can have // total of two colors from the global selection of 16 colors. for(unsigned y=0; y> ((x%3) + (y%3)*3)); if(x > 126) pixel = false; pixbuf[y*WIDTH+x] = (bg_cell >> (pixel ? 12 : 8)) & 0xF; } // Render the sprites atop that background const u16* ctrl = &memory[SPRITE_BASE], *ctrlend = ctrl + 2*N_SPRITES; for(; ctrl < ctrlend; ctrl += 2) { // Read the sprite header unsigned mode = (ctrl[1] >> 14) & 0x3; unsigned color = (ctrl[1] >> 10) & 0xF; unsigned bitsperpixel = ((mode&2)?3:4)-mode; // 4,2,2,1 // Calculate the sprite location and size int sx = -64 + (ctrl[0] >> 8 ); unsigned w = (mode&2) ? 16 : 8; int sy = -64 + (ctrl[0] & 0xFF); unsigned h = (mode&1) ? 16 : 8; // Render the sprite graphics. unsigned addr = GFX_BASE + 2*(ctrl[1] & 0x3FF); for(unsigned pixno=0, y=0; y> (16-bitsperpixel); if(pix != (mode ? color : 0)) if(sx >= 0 && sy >= 0 && sx < WIDTH && sy < HEIGHT) pixbuf[sy*WIDTH+sx] = (mode==3 ? color : pix); } } } void tick(unsigned n=1) { // For every 2000 CPU instructions, the screen will be refreshed. static unsigned count=0; count += n; if(count < 2000) return; count -= 2000; u8 pixbuf[WIDTH*HEIGHT]; RenderGraphics(pixbuf); // This is our global palette. static const u32 palette[16] = { 0x000000, 0x1B2632, 0x493C2B, 0x2F484E, 0x005784, 0xBE2633, 0x44891A, 0xA46422, 0x31A2F2, 0xE06F8B, 0xEB8931, 0x9D9D9D, 0xA3CE27, 0xB2DCEF, 0xFFE26B, 0xFFFFFF, }; // Place the pixels in the screen surface, also scaling it in the process. constexpr int span=1536, wid = span*12/2048, ywid=wid, iqwid=wid*18/10, buf=wid*2; static float sines[17][span + buf*3], iqkernel[iqwid], Y[16]; static bool cache_valid = false; if(!cache_valid) { for(int x=0; x> 16) + 0.587*((a >> 8)&0xFF) + 0.114*(a & 0xFF))/255.; float i = (0.596*(a >> 16) +-0.274*((a >> 8)&0xFF) +-0.322*(a & 0xFF)); float q = (0.211*(a >> 16) +-0.523*((a >> 8)&0xFF) + 0.312*(a & 0xFF)); // Save the luma in Y[p]; convert the chroma from polar into angular format float d = std::hypot(q, i) / 255., A = std::atan2(q, i); for(int x=0; x255)k=255; } int g; {int&k=g; k = ((I*-0.27f + Q*-0.65f) + Y/ywid)*255; if(k<0) k=0; if(k>255)k=255; } int b; {int&k=b; k = ((I*-1.10f + Q* 1.70f) + Y/ywid)*255; if(k<0) k=0; if(k>255)k=255; } ((u32*)(s->pixels))[ y*WIDTH*SCALE + x] = (r<<16) + (g<<8) + b; } } // Refresh the screen. SDL_Flip(s); // Also check for keyboard input events. SDL_Event event = { }; SDL_PollEvent( &event ); Uint8 *keystate = SDL_GetKeyState(NULL); #define keyonce(n) \ [&keystate]() -> bool { \ static bool old=false; \ bool res = keystate[n] && !old; \ old = keystate[n]; \ return res; }() // Put new keys into the device's keyboard buffer. static unsigned keyptr = 0; if(keyonce(SDLK_UP)) { if(!memory[INPUT_BASE+keyptr]) memory[INPUT_BASE+keyptr]=3; keyptr=(keyptr+1)&0xF; } if(keyonce(SDLK_DOWN)) { if(!memory[INPUT_BASE+keyptr]) memory[INPUT_BASE+keyptr]=4; keyptr=(keyptr+1)&0xF; } if(keyonce(SDLK_LEFT)) { if(!memory[INPUT_BASE+keyptr]) memory[INPUT_BASE+keyptr]=1; keyptr=(keyptr+1)&0xF; } if(keyonce(SDLK_RIGHT)) { if(!memory[INPUT_BASE+keyptr]) memory[INPUT_BASE+keyptr]=2; keyptr=(keyptr+1)&0xF; } } // Return a parameter to an instruction. Parameters are read-write, // i.e. the instruction may either read or write the register/memory location. template u16& value(u16& v) { // When skipping=true, the return value is insignificant; it only matters // whether PC is incremented the right amount, and that there are no side effects. switch( ((v&0x38)==0x18) ? (v&7)^skipping : 8+v/8 ) { enum { POP,PEEK,PUSH,reg_SP,reg_PC,reg_O,abs_mem,mem_lit, regs,reg_mem,idx_mem }; // Values 0x00-0x07 decode into basic registers: A,B,C,X,Y,Z,I,J case regs: return reg[v&7]; // Values 0x08-0x0F decode into indirect memory references. case reg_mem: return memory[ reg[v&7] ]; // Values 0x10-0x17 decode into indirect indexed memory references. case idx_mem: tick(); return memory[u16( reg[v&7] + memory[ reg[PC]++ ] )]; // Values 0x18-0x1F decode into various things: case POP: return memory[ reg[SP]++ ]; case PEEK: return memory[ reg[SP] ]; case PUSH: return memory[ --reg[SP] ]; case reg_SP: return reg[SP]; case reg_PC: return reg[PC]; case reg_O: return reg[EX]; case abs_mem: tick(); return memory[ memory[ reg[PC]++ ] ]; case mem_lit: tick(); return v = memory[ reg[PC]++ ]; // Values 0x20-0x3F decode into literals 0x00-0x1F. default: return v &= 0x1F; // Literals are actually read-only. Because the instruction _may_ // write into the target operand even if it is a literal, the // literals will be first stored into a temporary variable (v), // which can then be harmlessly overwritten. } } template void op() { tick(); // Read the next instruction from memory: u16 v = memory[reg[PC]++]; // Parse the individual components of the instruction: // Instruction format: bbbbbb aaaaaa oooo u16 o = (v & 0x0F), aa = (v>>4) & 0x3F, bb = (v>>10) & 0x3F; // List of basic instructions. Chosen by the value of "o": enum { nbi,SET,ADD,SUB,MUL,DIV,MOD,SHL,SHR,AND,BOR,XOR,IFE,IFN,IFG,IFB }; // List of non-basic instructions (nbi), chosen by the value of "aa": enum { JSR=0x11 }; // Parse the two parameters. (Note: "a" is skipped when nbi.) u16& a = o==nbi ? v : value(aa); u32 wa = a; u16& b = value(bb); u32 wr; // Then execute the instruction (unless the instruction is to be skipped). if(skipping) { if(o>=IFE && o<=IFB) op(); } else switch(o==nbi ? aa+0x10 : o) { // A simple move. It can also be used as // a PUSH/POP/RET/JMP with properly chosen parameters. case SET: tick(0); a = b; break; // Simple binary operations. case AND: tick(0); a &= b; break; case BOR: tick(0); a |= b; break; case XOR: tick(0); a ^= b; break; // Fundamental arithmetic operations. The overflow is stored in the EX register. case ADD: tick(1); wr = a+b; a = wr; reg[EX] = wr>>16; break; case SUB: tick(1); wr = a-b; a = wr; reg[EX] = wr>>16; break; case MUL: tick(1); wr = wa*b; a = wr; reg[EX] = wr>>16; break; // Bit-shifting left. EX stores the bits that were shifted out from the register. case SHL: tick(0); wr = wa<>16; break; // Division and modulo. If the divisor is 0, result is 0. case MOD: tick(2); a = b ? a%b : 0; break; case DIV: tick(2); wr = b ?(wa<<16)/b :0; a = wr>>16; reg[EX] = wr; break; // Bit-shifting right. EX stores the bits that were shifted out from the register. case SHR: tick(0); wr = (wa<<16) >> b; a = wr>>16; reg[EX] = wr; break; // Conditional execution. Skips the next instruction if the condition doesn't match. case IFE: tick(1); if(!(a == b)) op(); break; case IFN: tick(1); if(!(a != b)) op(); break; case IFG: tick(1); if(!(a > b)) op(); break; case IFB: tick(1); if(!(a & b)) op(); break; // Jump-To-Subroutine: Pushes the current program counter and chooses a new one. case JSR: tick(1); memory[--reg[SP]] = reg[PC]; reg[PC] = b; break; // Anything else is invalid. default: std::fprintf(stderr, "Invalid opcode %04X at PC=%X\n", v, reg[PC]); } } public: void run() { // Infinite loop! for(;;) op(); } }; int main() { // Declare the emulator. Emulator emu; // Load the initial RAM contents. std::fread(emu.memory, 0x10000, 2, stdin); // Launch the emulator. emu.run(); }