1 | //============================================================================ |
2 | // |
3 | // SSSS tt lll lll |
4 | // SS SS tt ll ll |
5 | // SS tttttt eeee ll ll aaaa |
6 | // SSSS tt ee ee ll ll aa |
7 | // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" |
8 | // SS SS tt ee ll ll aa aa |
9 | // SSSS ttt eeeee llll llll aaaaa |
10 | // |
11 | // Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony |
12 | // and the Stella Team |
13 | // |
14 | // See the file "License.txt" for information on usage and redistribution of |
15 | // this file, and for a DISCLAIMER OF ALL WARRANTIES. |
16 | //============================================================================ |
17 | |
18 | #ifndef CARTRIDGECHETIRY_HXX |
19 | #define CARTRIDGECHETIRY_HXX |
20 | |
21 | class System; |
22 | |
23 | #include "bspf.hxx" |
24 | #include "Cart.hxx" |
25 | #ifdef DEBUGGER_SUPPORT |
26 | #include "CartCTYWidget.hxx" |
27 | #endif |
28 | |
29 | /** |
30 | The 'Chetiry' bankswitch scheme was developed by Chris D. Walton for a |
31 | Tetris clone game by the same name. It makes use of a Harmony cart, |
32 | whereby ARM code in bank 0 is executed to implement the bankswitch scheme. |
33 | The implementation here does not execute this ARM code, and instead |
34 | implements the bankswitching directly. Its functionality is similar to |
35 | several other schemes, as follows: |
36 | |
37 | F4SC: |
38 | The scheme contains 8 4K banks, with the first bank being inaccessible |
39 | (due to containing ARM code). The remaining banks (1 - 7) are accessed |
40 | at hotspots $FF5 - $FFB, exactly the same as F4SC. |
41 | |
42 | There is 64 bytes of RAM (vs. 128 bytes in F4SC) at $1000 - $107F |
43 | ($1000 - $103F is write port, $1040 - $107F is read port). |
44 | |
45 | FA2: |
46 | The first four bytes of RAM are actually a kind of hotspot, with the |
47 | following functionality. Data is accessed from Harmony EEPROM in |
48 | the same fashion as the FA2 scheme. |
49 | |
50 | Write Addresses: |
51 | $1000 = Operation Type (see discussion of hotspot $1FF4 below) |
52 | $1001 = Set Random Seed Value |
53 | $1002 = Reset Fetcher To Beginning Of Tune |
54 | $1003 = Advance Fetcher To Next Tune Position |
55 | |
56 | Read Addresses: |
57 | $1040 = Error Code after operation |
58 | $1041 = Get Next Random Number (8-bit LFSR) |
59 | $1042 = Get Tune Position (Low Byte) |
60 | $1043 = Get Tune Position (High Byte) |
61 | |
62 | RAM Load And Save Operations: |
63 | |
64 | Address $1FF4 is used as a special hotspot to trigger loading and saving |
65 | of the RAM, similar to FA2 bankswitching. The operation to perform is |
66 | given using the first byte of the extra RAM. The format of this byte is |
67 | XXXXYYYY, where XXXX is an index and YYYY is the operation to perform. |
68 | There are 4 different operation types: |
69 | |
70 | 1 = Load Tune (index = tune) |
71 | 2 = Load Score Table (index = table) |
72 | 3 = Save Score Table (index = table) |
73 | 4 = Wipe All Score Tables (set all 256 bytes of EEPROM to $00) |
74 | |
75 | The score table functionality is based on 256 bytes from Harmony |
76 | EEPROM, of which there are 4 64-byte 'tables'. The 'index' for |
77 | operations 2 and 3 can therefore be in the range 0 - 3, indicating |
78 | which table to use. For this implementation, the 256 byte EEPROM |
79 | is serialized to a file. |
80 | |
81 | The tune table functionality is also based on Harmony EEPROM, where |
82 | 7 4K tunes are stored (28K total). The 'index' for operation 1 can |
83 | therefore be in the range 0 - 6, indicating which tune to load. |
84 | |
85 | DPC+: |
86 | The music functionality is quite similar to the DPC+ scheme. |
87 | |
88 | Fast Fetcher |
89 | The music frequency value is fetched using a fast fetcher operation. |
90 | This operation is aliased to the instruction "LDA #$F2". Whenever this |
91 | instruction is executed, the $F2 value is replaced with the frequency |
92 | value calculated from the tune data. The pointer to the tune data does |
93 | not advance until address $1003 is written. When a new tune is loaded, |
94 | the pointer is reset to the beginning of the tune. This also happens |
95 | when the end of the tune is reached or when address $1002 is written to. |
96 | |
97 | The calculation of the frequency value is essentially the same as DPC. |
98 | There are 3 different channels that are combined together, and only a |
99 | square waveform is used. The data is formatted so that the three notes |
100 | for each position appear consecutively (note0, note1, note2). Moving |
101 | to the next tune position means incrementing by 3 bytes. The end of the |
102 | tune is marked by a note value of 1. A note value of 0 means that the |
103 | current value should not be updated, i.e continue with the previous |
104 | non-zero value. |
105 | |
106 | @author Stephen Anthony and Chris D. Walton |
107 | */ |
108 | class CartridgeCTY : public Cartridge |
109 | { |
110 | friend class CartridgeCTYWidget; |
111 | |
112 | public: |
113 | /** |
114 | Create a new cartridge using the specified image |
115 | |
116 | @param image Pointer to the ROM image |
117 | @param size The size of the ROM image |
118 | @param md5 The md5sum of the ROM image |
119 | @param settings A reference to the settings object |
120 | */ |
121 | CartridgeCTY(const ByteBuffer& image, size_t size, const string& md5, |
122 | const Settings& settings); |
123 | virtual ~CartridgeCTY() = default; |
124 | |
125 | public: |
126 | /** |
127 | Reset device to its power-on state |
128 | */ |
129 | void reset() override; |
130 | |
131 | /** |
132 | Install cartridge in the specified system. Invoked by the system |
133 | when the cartridge is attached to it. |
134 | |
135 | @param system The system the device should install itself in |
136 | */ |
137 | void install(System& system) override; |
138 | |
139 | /** |
140 | Install pages for the specified bank in the system. |
141 | |
142 | @param bank The bank that should be installed in the system |
143 | */ |
144 | bool bank(uInt16 bank) override; |
145 | |
146 | /** |
147 | Get the current bank. |
148 | |
149 | @param address The address to use when querying the bank |
150 | */ |
151 | uInt16 getBank(uInt16 address = 0) const override; |
152 | |
153 | /** |
154 | Query the number of banks supported by the cartridge. |
155 | */ |
156 | uInt16 bankCount() const override; |
157 | |
158 | /** |
159 | Patch the cartridge ROM. |
160 | |
161 | @param address The ROM address to patch |
162 | @param value The value to place into the address |
163 | @return Success or failure of the patch operation |
164 | */ |
165 | bool patch(uInt16 address, uInt8 value) override; |
166 | |
167 | /** |
168 | Access the internal ROM image for this cartridge. |
169 | |
170 | @param size Set to the size of the internal ROM image data |
171 | @return A pointer to the internal ROM image data |
172 | */ |
173 | const uInt8* getImage(size_t& size) const override; |
174 | |
175 | /** |
176 | Save the current state of this cart to the given Serializer. |
177 | |
178 | @param out The Serializer object to use |
179 | @return False on any errors, else true |
180 | */ |
181 | bool save(Serializer& out) const override; |
182 | |
183 | /** |
184 | Load the current state of this cart from the given Serializer. |
185 | |
186 | @param in The Serializer object to use |
187 | @return False on any errors, else true |
188 | */ |
189 | bool load(Serializer& in) override; |
190 | |
191 | /** |
192 | Get a descriptor for the device name (used in error checking). |
193 | |
194 | @return The name of the object |
195 | */ |
196 | string name() const override { return "CartridgeCTY" ; } |
197 | |
198 | /** |
199 | Informs the cartridge about the name of the nvram file it will use. |
200 | |
201 | @param nvramdir The full path of the nvram directory |
202 | @param romfile The name of the cart from ROM properties |
203 | */ |
204 | void setNVRamFile(const string& nvramdir, const string& romfile) override; |
205 | |
206 | #ifdef DEBUGGER_SUPPORT |
207 | /** |
208 | Get debugger widget responsible for accessing the inner workings |
209 | of the cart. |
210 | */ |
211 | CartDebugWidget* debugWidget(GuiObject* boss, const GUI::Font& lfont, |
212 | const GUI::Font& nfont, int x, int y, int w, int h) override |
213 | { |
214 | return new CartridgeCTYWidget(boss, lfont, nfont, x, y, w, h, *this); |
215 | } |
216 | #endif |
217 | |
218 | public: |
219 | /** |
220 | Get the byte at the specified address. |
221 | |
222 | @return The byte at the specified address |
223 | */ |
224 | uInt8 peek(uInt16 address) override; |
225 | |
226 | /** |
227 | Change the byte at the specified address to the given value |
228 | |
229 | @param address The address where the value should be stored |
230 | @param value The value to be stored at the address |
231 | @return True if the poke changed the device address space, else false |
232 | */ |
233 | bool poke(uInt16 address, uInt8 value) override; |
234 | |
235 | private: |
236 | /** |
237 | Either load or save internal RAM to Harmony EEPROM (represented by |
238 | a file in emulation). |
239 | |
240 | @return The value at $FF4 with bit 6 set or cleared (depending on |
241 | whether the RAM access was busy or successful) |
242 | */ |
243 | uInt8 ramReadWrite(); |
244 | |
245 | /** |
246 | Actions initiated by accessing $FF4 hotspot. |
247 | */ |
248 | void loadTune(uInt8 index); |
249 | void loadScore(uInt8 index); |
250 | void saveScore(uInt8 index); |
251 | void wipeAllScores(); |
252 | |
253 | /** |
254 | Updates any data fetchers in music mode based on the number of |
255 | CPU cycles which have passed since the last update. |
256 | */ |
257 | void updateMusicModeDataFetchers(); |
258 | |
259 | void updateTune(); |
260 | |
261 | private: |
262 | // The 32K ROM image of the cartridge |
263 | std::array<uInt8, 32_KB> myImage; |
264 | |
265 | // The 28K ROM image of the music |
266 | std::array<uInt8, 28_KB> myTuneData; |
267 | |
268 | // The 64 bytes of RAM accessible at $1000 - $1080 |
269 | std::array<uInt8, 64> myRAM; |
270 | |
271 | // Operation type (written to $1000, used by hotspot $1FF4) |
272 | uInt8 myOperationType; |
273 | |
274 | // Pointer to the 28K frequency table (points to the start of one |
275 | // of seven 4K tunes in myTuneData) |
276 | const uInt8* myFrequencyImage; |
277 | |
278 | // The counter register for the data fetcher |
279 | uInt16 myTunePosition; |
280 | |
281 | // The music mode counters |
282 | std::array<uInt32, 3> myMusicCounters; |
283 | |
284 | // The music frequency |
285 | std::array<uInt32, 3> myMusicFrequencies; |
286 | |
287 | // Flags that last byte peeked was A9 (LDA #) |
288 | bool myLDAimmediate; |
289 | |
290 | // The random number generator register |
291 | uInt32 myRandomNumber; |
292 | |
293 | // The time after which the first request of a load/save operation |
294 | // will actually be completed |
295 | // Due to Harmony EEPROM constraints, a read/write isn't instantaneous, |
296 | // so we need to emulate the delay as well |
297 | uInt64 myRamAccessTimeout; |
298 | |
299 | // Full pathname of the file to use when emulating load/save |
300 | // of internal RAM to Harmony cart EEPROM |
301 | string myEEPROMFile; |
302 | |
303 | // System cycle count from when the last update to music data fetchers occurred |
304 | uInt64 myAudioCycles; |
305 | |
306 | // Fractional DPC music OSC clocks unused during the last update |
307 | double myFractionalClocks; |
308 | |
309 | // Indicates the offset into the ROM image (aligns to current bank) |
310 | uInt16 myBankOffset; |
311 | |
312 | static const uInt32 ourFrequencyTable[63]; |
313 | |
314 | private: |
315 | // Following constructors and assignment operators not supported |
316 | CartridgeCTY() = delete; |
317 | CartridgeCTY(const CartridgeCTY&) = delete; |
318 | CartridgeCTY(CartridgeCTY&&) = delete; |
319 | CartridgeCTY& operator=(const CartridgeCTY&) = delete; |
320 | CartridgeCTY& operator=(CartridgeCTY&&) = delete; |
321 | }; |
322 | |
323 | #endif |
324 | |