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-2021 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 "CompuMate.hxx"
19 #include "System.hxx"
20 #include "M6532.hxx"
21 #include "CartCM.hxx"
22 
23 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeCM(const ByteBuffer & image,size_t size,const string & md5,const Settings & settings)24 CartridgeCM::CartridgeCM(const ByteBuffer& image, size_t size,
25                          const string& md5, const Settings& settings)
26   : Cartridge(settings, md5),
27     myImage{make_unique<uInt8[]>(16_KB)}
28 {
29   // Copy the ROM image into my buffer
30   std::copy_n(image.get(), std::min(16_KB, size), myImage.get());
31   createRomAccessArrays(16_KB);
32 }
33 
34 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
reset()35 void CartridgeCM::reset()
36 {
37   initializeRAM(myRAM.data(), myRAM.size());
38 
39   // On powerup, the last bank of ROM is enabled and RAM is disabled
40   mySWCHA = 0xFF;
41   initializeStartBank(mySWCHA & 0x3);
42 
43   // Upon reset we switch to the startup bank
44   bank(startBank());
45 }
46 
47 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
install(System & system)48 void CartridgeCM::install(System& system)
49 {
50   mySystem = &system;
51 
52   // Mirror all access in RIOT; by doing so we're taking responsibility
53   // for that address space in peek and poke below.
54   mySystem->m6532().installDelegate(system, *this);
55 
56   // Install pages for the startup bank
57   bank(startBank());
58 }
59 
60 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
peek(uInt16 address)61 uInt8 CartridgeCM::peek(uInt16 address)
62 {
63   // NOTE: This does not handle accessing cart ROM/RAM, however, this method
64   // should never be called for ROM/RAM because of the way page accessing
65   // has been setup (it will only ever be called for RIOT reads)
66   return mySystem->m6532().peek(address);
67 }
68 
69 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
poke(uInt16 address,uInt8 value)70 bool CartridgeCM::poke(uInt16 address, uInt8 value)
71 {
72   // NOTE: This could be called for RIOT writes or cart ROM writes
73   // In the latter case, the write is ignored
74   if(!(address & 0x1000))
75   {
76     // RIOT mirroring, check bankswitch
77     if(address == 0x280)
78     {
79       mySWCHA = value;
80       bank(mySWCHA & 0x3);
81       if(myCompuMate)
82       {
83         uInt8& column = myCompuMate->column();
84         if(value & 0x20)
85           column = 0;
86         if(value & 0x40)
87           column = (column + 1) % 10;
88       }
89     }
90     mySystem->m6532().poke(address, value);
91   }
92   return myBankChanged;
93 }
94 
95 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
column() const96 uInt8 CartridgeCM::column() const
97 {
98   return myCompuMate->column();
99 }
100 
101 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bank(uInt16 bank,uInt16)102 bool CartridgeCM::bank(uInt16 bank, uInt16)
103 {
104   if(hotspotsLocked()) return false;
105 
106   // Remember what bank we're in
107   myBankOffset = bank << 12;
108 
109   // Although this scheme contains four 4K ROM banks and one 2K RAM bank,
110   // it's easier to think of things in terms of 2K slices, as follows:
111   //
112   // The lower 2K of cart address space always points to the lower 2K of the
113   // current ROM bank
114   // The upper 2K of cart address space can point to either the 2K of RAM or
115   // the upper 2K of the current ROM bank
116 
117   System::PageAccess access(this, System::PageAccessType::READ);
118 
119   // Lower 2K (always ROM)
120   for(uInt16 addr = 0x1000; addr < 0x1800; addr += System::PAGE_SIZE)
121   {
122     access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)];
123     access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)];
124     access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)];
125     access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize];
126     mySystem->setPageAccess(addr, access);
127   }
128 
129   // Upper 2K (RAM or ROM)
130   for(uInt16 addr = 0x1800; addr < 0x2000; addr += System::PAGE_SIZE)
131   {
132     access.type = System::PageAccessType::READWRITE;
133 
134     if(mySWCHA & 0x10)
135     {
136       access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)];
137       access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)];
138       access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)];
139       access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize];
140     }
141     else
142     {
143       access.directPeekBase = &myRAM[addr & 0x7FF];
144       access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x07FF)];
145       access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x07FF)];
146       access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x07FF) + myAccessSize];
147     }
148 
149     if((mySWCHA & 0x30) == 0x20)
150       access.directPokeBase = &myRAM[addr & 0x7FF];
151     else
152       access.directPokeBase = nullptr;
153 
154     mySystem->setPageAccess(addr, access);
155   }
156 
157   return myBankChanged = true;
158 }
159 
160 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getBank(uInt16) const161 uInt16 CartridgeCM::getBank(uInt16) const
162 {
163   return myBankOffset >> 12;
164 }
165 
166 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
romBankCount() const167 uInt16 CartridgeCM::romBankCount() const
168 {
169   // We report 4 banks (of ROM), even though RAM can overlap the upper 2K
170   // of cart address space at some times
171   // However, this RAM isn't enabled in the normal way that bankswitching
172   // works, so it is ignored here
173   return 4;
174 }
175 
176 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
patch(uInt16 address,uInt8 value)177 bool CartridgeCM::patch(uInt16 address, uInt8 value)
178 {
179   if((mySWCHA & 0x30) == 0x20)
180     myRAM[address & 0x7FF] = value;
181   else
182     myImage[myBankOffset + address] = value;
183 
184   return myBankChanged = true;
185 }
186 
187 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getImage(size_t & size) const188 const ByteBuffer& CartridgeCM::getImage(size_t& size) const
189 {
190   size = 16_KB;
191   return myImage;
192 }
193 
194 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
save(Serializer & out) const195 bool CartridgeCM::save(Serializer& out) const
196 {
197   try
198   {
199     out.putShort(myBankOffset);
200     out.putByte(mySWCHA);
201     out.putByte(myCompuMate->column());
202     out.putByteArray(myRAM.data(), myRAM.size());
203   }
204   catch(...)
205   {
206     cerr << "ERROR: CartridgeCM::save" << endl;
207     return false;
208   }
209 
210   return true;
211 }
212 
213 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
load(Serializer & in)214 bool CartridgeCM::load(Serializer& in)
215 {
216   try
217   {
218     myBankOffset = in.getShort();
219     mySWCHA = in.getByte();
220     myCompuMate->column() = in.getByte();
221     in.getByteArray(myRAM.data(), myRAM.size());
222   }
223   catch(...)
224   {
225     cerr << "ERROR: CartridgeCM::load" << endl;
226     return false;
227   }
228 
229   // Remember what bank we were in
230   bank(myBankOffset >> 12);
231 
232   return true;
233 }
234