1 module nes.console; 2 3 import std.base64; 4 import std.conv; 5 import std.file; 6 import std.zlib; 7 8 import nes.apu; 9 import nes.cartridge; 10 import nes.color; 11 import nes.controller; 12 import nes.cpu; 13 import nes.filter; 14 import nes.image; 15 import nes.ines; 16 import nes.mapper; 17 import nes.palette; 18 import nes.ppu; 19 20 class Console { 21 CPU cpu; 22 APU apu; 23 PPU ppu; 24 Cartridge cartridge; 25 Controller controller1; 26 Controller controller2; 27 Mapper mapper; 28 ubyte[] ram; 29 30 this(string path) { 31 this.cartridge = LoadNESFile(path); 32 33 this.ram = new ubyte[2048]; 34 this.controller1 = new Controller(); 35 this.controller2 = new Controller(); 36 37 this.mapper = NewMapper(this); 38 39 this.cpu = new CPU(this); 40 this.apu = new APU(this); 41 this.ppu = new PPU(this); 42 } 43 44 void reset() { 45 this.cpu.reset(); 46 } 47 48 int step() { 49 auto cpuCycles = this.cpu.step(); 50 auto ppuCycles = cpuCycles * 3; 51 52 for (auto i = 0; i < ppuCycles; i++) { 53 this.ppu.step(); 54 this.mapper.step(); 55 } 56 57 for (auto i = 0; i < cpuCycles; i++) { 58 this.apu.step(); 59 } 60 61 return cpuCycles; 62 } 63 64 int stepFrame() { 65 auto cpuCycles = 0; 66 auto frame = this.ppu.frame; 67 68 while (frame == this.ppu.frame) { 69 cpuCycles += this.step(); 70 } 71 72 return cpuCycles; 73 } 74 75 void stepSeconds(double seconds) { 76 auto cycles = cast(int)(CPUFrequency * seconds); 77 78 while (cycles > 0) { 79 cycles -= this.step(); 80 } 81 } 82 83 ImageRGBA buffer() { 84 return this.ppu.front; 85 } 86 87 RGBA backgroundColor() { 88 return Palette[this.ppu.readPalette(0) % 64]; 89 } 90 91 void setButtons1(bool[8] buttons) { 92 this.controller1.setButtons(buttons); 93 } 94 95 void setButtons2(bool[8] buttons) { 96 this.controller2.setButtons(buttons); 97 } 98 99 void setAudioCallback(ApuCallbackFuncType callback) { 100 this.apu.callback = callback; 101 } 102 103 void setAudioSampleRate(double sampleRate) { 104 if (sampleRate != 0) { 105 // Convert samples per second to cpu steps per sample 106 this.apu.sampleRate = CPUFrequency / sampleRate; 107 // Initialize filters 108 this.apu.filterChain = new FilterChain( 109 HighPassFilter(cast(float)sampleRate, 90), 110 HighPassFilter(cast(float)sampleRate, 440), 111 LowPassFilter(cast(float)sampleRate, 14000) 112 ); 113 } else { 114 this.apu.filterChain = null; 115 } 116 } 117 118 void saveState(string fileName) { 119 string[string] state = ["version": "1"]; 120 121 this.save(state); 122 123 auto stateText = to!string(state); 124 125 auto data = std.zlib.compress(cast(void[])stateText); 126 127 write(fileName, data); 128 } 129 130 void loadState(string fileName) { 131 auto stateData = read(fileName); 132 133 stateData = cast(ubyte[])std.zlib.uncompress(cast(void[])stateData); 134 135 string[string] state = to!(string[string])(cast(string)stateData); 136 137 load(state); 138 } 139 140 void saveBatteryBackedRam(string fileName) { 141 if (!this.cartridge.battery) return; 142 143 auto data = std.zlib.compress(cast(void[])this.cartridge.sram); 144 145 write(fileName, data); 146 } 147 148 void loadBatteryBackedRam(string fileName) { 149 if (!this.cartridge.battery) return; 150 151 auto data = read(fileName); 152 153 this.cartridge.sram = cast(ubyte[])std.zlib.uncompress(cast(void[])data); 154 } 155 156 private: 157 void save(string[string] state) { 158 state["console.ram"] = Base64.encode(this.ram); 159 160 this.cpu.save(state); 161 this.apu.save(state); 162 this.ppu.save(state); 163 this.cartridge.save(state); 164 this.mapper.save(state); 165 } 166 167 void load(string[string] state) { 168 this.ram = Base64.decode(state["console.ram"]); 169 170 this.cpu.load(state); 171 this.apu.load(state); 172 this.ppu.load(state); 173 this.cartridge.load(state); 174 this.mapper.load(state); 175 } 176 }