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#include "System.hxx"
19#include "TIA.hxx"
20#include "Cart3E.hxx"
21
22// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
23Cartridge3E::Cartridge3E(const ByteBuffer& image, size_t size,
24 const string& md5, const Settings& settings)
25 : Cartridge(settings, md5),
26 mySize(size),
27 myCurrentBank(0)
28{
29 // Allocate array for the ROM image
30 myImage = make_unique<uInt8[]>(mySize);
31
32 // Copy the ROM image into my buffer
33 std::copy_n(image.get(), mySize, myImage.get());
34 createCodeAccessBase(mySize + myRAM.size());
35}
36
37// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
38void Cartridge3E::reset()
39{
40 initializeRAM(myRAM.data(), myRAM.size());
41 initializeStartBank(0);
42
43 // We'll map the startup bank into the first segment upon reset
44 bank(startBank());
45}
46
47// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
48void Cartridge3E::install(System& system)
49{
50 mySystem = &system;
51
52 System::PageAccess access(this, System::PageAccessType::READWRITE);
53
54 // The hotspots ($3E and $3F) are in TIA address space, so we claim it here
55 for(uInt16 addr = 0x00; addr < 0x40; addr += System::PAGE_SIZE)
56 mySystem->setPageAccess(addr, access);
57
58 // Setup the second segment to always point to the last ROM slice
59 access.type = System::PageAccessType::READ;
60 for(uInt16 addr = 0x1800; addr < 0x2000; addr += System::PAGE_SIZE)
61 {
62 access.directPeekBase = &myImage[(mySize - 2048) + (addr & 0x07FF)];
63 access.codeAccessBase = &myCodeAccessBase[(mySize - 2048) + (addr & 0x07FF)];
64 mySystem->setPageAccess(addr, access);
65 }
66
67 // Install pages for the startup bank into the first segment
68 bank(startBank());
69}
70
71// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
72uInt8 Cartridge3E::peek(uInt16 address)
73{
74 uInt16 peekAddress = address;
75 address &= 0x0FFF;
76
77 if(address < 0x0800)
78 {
79 if(myCurrentBank < 256)
80 return myImage[(address & 0x07FF) + (myCurrentBank << 11)];
81 else
82 {
83 if(address < 0x0400)
84 return myRAM[(address & 0x03FF) + ((myCurrentBank - 256) << 10)];
85 else
86 {
87 // Reading from the write port triggers an unwanted write
88 return peekRAM(myRAM[(address & 0x03FF) + ((myCurrentBank - 256) << 10)], peekAddress);
89 }
90 }
91 }
92 else
93 {
94 return myImage[(address & 0x07FF) + mySize - 2048];
95 }
96}
97
98// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
99bool Cartridge3E::poke(uInt16 address, uInt8 value)
100{
101 uInt16 pokeAddress = address;
102 address &= 0x0FFF;
103
104 // Switch banks if necessary. Armin (Kroko) says there are no mirrored
105 // hotspots.
106 if(address < 0x0040)
107 {
108 if(address == 0x003F)
109 bank(value);
110 else if(address == 0x003E)
111 bank(value + 256);
112
113 return mySystem->tia().poke(address, value);
114 }
115 else
116 {
117 if(address & 0x0400)
118 {
119 pokeRAM(myRAM[(address & 0x03FF) + ((myCurrentBank - 256) << 10)], pokeAddress, value);
120 return true;
121 }
122 else
123 {
124 // Writing to the read port should be ignored, but trigger a break if option enabled
125 uInt8 dummy;
126
127 pokeRAM(dummy, pokeAddress, value);
128 myRamWriteAccess = pokeAddress;
129 return false;
130 }
131 }
132}
133
134// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
135bool Cartridge3E::bank(uInt16 bank)
136{
137 if(bankLocked()) return false;
138
139 if(bank < 256)
140 {
141 // Make sure the bank they're asking for is reasonable
142 if((uInt32(bank) << 11) < mySize)
143 {
144 myCurrentBank = bank;
145 }
146 else
147 {
148 // Oops, the bank they're asking for isn't valid so let's wrap it
149 // around to a valid bank number
150 myCurrentBank = bank % (mySize >> 11);
151 }
152
153 uInt32 offset = myCurrentBank << 11;
154
155 // Setup the page access methods for the current bank
156 System::PageAccess access(this, System::PageAccessType::READ);
157
158 // Map ROM image into the system
159 for(uInt16 addr = 0x1000; addr < 0x1800; addr += System::PAGE_SIZE)
160 {
161 access.directPeekBase = &myImage[offset + (addr & 0x07FF)];
162 access.codeAccessBase = &myCodeAccessBase[offset + (addr & 0x07FF)];
163 mySystem->setPageAccess(addr, access);
164 }
165 }
166 else
167 {
168 bank -= 256;
169 bank %= 32;
170 myCurrentBank = bank + 256;
171
172 uInt32 offset = bank << 10;
173
174 // Setup the page access methods for the current bank
175 System::PageAccess access(this, System::PageAccessType::READ);
176
177 // Map read-port RAM image into the system
178 for(uInt16 addr = 0x1000; addr < 0x1400; addr += System::PAGE_SIZE)
179 {
180 access.directPeekBase = &myRAM[offset + (addr & 0x03FF)];
181 access.codeAccessBase = &myCodeAccessBase[mySize + offset + (addr & 0x03FF)];
182 mySystem->setPageAccess(addr, access);
183 }
184
185 access.directPeekBase = nullptr;
186 access.type = System::PageAccessType::WRITE;
187
188 // Map write-port RAM image into the system
189 // Map access to this class, since we need to inspect all accesses to
190 // check if RWP happens
191 for(uInt16 addr = 0x1400; addr < 0x1800; addr += System::PAGE_SIZE)
192 {
193 access.codeAccessBase = &myCodeAccessBase[mySize + offset + (addr & 0x03FF)];
194 mySystem->setPageAccess(addr, access);
195 }
196 }
197 return myBankChanged = true;
198}
199
200// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
201uInt16 Cartridge3E::getBank(uInt16 address) const
202{
203 if (address & 0x800)
204 return 255; // 256 - 1 // 2K slices, fixed bank
205 else
206 return myCurrentBank;
207}
208
209// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
210uInt16 Cartridge3E::bankCount() const
211{
212 // Because the RAM banks always start at 256 and above, we require the
213 // number of ROM banks to be 256
214 // If the RAM banks were simply appended to the number of actual
215 // ROM banks, bank numbers would be ambiguous (ie, would bank 128 be
216 // the last bank of ROM, or one of the banks of RAM?)
217 return 256 + 32;
218}
219
220// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
221bool Cartridge3E::patch(uInt16 address, uInt8 value)
222{
223 address &= 0x0FFF;
224
225 if(address < 0x0800)
226 {
227 if(myCurrentBank < 256)
228 myImage[(address & 0x07FF) + (myCurrentBank << 11)] = value;
229 else
230 myRAM[(address & 0x03FF) + ((myCurrentBank - 256) << 10)] = value;
231 }
232 else
233 myImage[(address & 0x07FF) + mySize - 2048] = value;
234
235 return myBankChanged = true;
236}
237
238// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
239const uInt8* Cartridge3E::getImage(size_t& size) const
240{
241 size = mySize;
242 return myImage.get();
243}
244
245// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
246bool Cartridge3E::save(Serializer& out) const
247{
248 try
249 {
250 out.putShort(myCurrentBank);
251 out.putByteArray(myRAM.data(), myRAM.size());
252 }
253 catch(...)
254 {
255 cerr << "ERROR: Cartridge3E::save" << endl;
256 return false;
257 }
258
259 return true;
260}
261
262// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
263bool Cartridge3E::load(Serializer& in)
264{
265 try
266 {
267 myCurrentBank = in.getShort();
268 in.getByteArray(myRAM.data(), myRAM.size());
269 }
270 catch(...)
271 {
272 cerr << "ERROR: Cartridge3E::load" << endl;
273 return false;
274 }
275
276 // Now, go to the current bank
277 bank(myCurrentBank);
278
279 return true;
280}
281