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