Atari 2600 - throw NotImplemented exceptions on mappers that exist but haven't been built, and clean up some mapper code

This commit is contained in:
adelikat 2014-04-02 21:27:14 +00:00
parent 5db777afcf
commit 12cdedf299
21 changed files with 580 additions and 347 deletions

View File

@ -1,4 +1,6 @@
namespace BizHawk.Emulation.Cores.Atari.Atari2600
using System;
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
/*
This is another 8K bankswitching method with two 4K banks. The rationale is that it's
@ -19,8 +21,11 @@
B is the bank we will select. sooo, accessing 0800 will select bank 0, and 0840
will select bank 1.
*/
class m0840 : MapperBase
internal class m0840 : MapperBase
{
public m0840()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,19 +1,20 @@
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
partial class Atari2600
internal class m2K : MapperBase
{
class m2K : MapperBase
public override byte ReadMemory(ushort addr)
{
public override byte ReadMemory(ushort addr)
if (addr < 0x1000)
{
if (addr < 0x1000) return base.ReadMemory(addr);
return core.rom[addr & 0x7FF];
return base.ReadMemory(addr);
}
public override byte PeekMemory(ushort addr)
{
return ReadMemory(addr);
}
return core.rom[addr & 0x7FF];
}
public override byte PeekMemory(ushort addr)
{
return ReadMemory(addr);
}
}
}

View File

@ -20,7 +20,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
enough space for 256K of RAM. When RAM is selected, 1000-13FF is the read port while
1400-17FF is the write port.
*/
class m3E : MapperBase
internal class m3E : MapperBase
{
int lowbank_2k;
int rambank_1k;

View File

@ -21,7 +21,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
yet.
*/
class m3F :MapperBase
internal class m3F : MapperBase
{
int lowbank_2k;

View File

@ -1,6 +1,12 @@
namespace BizHawk.Emulation.Cores.Atari.Atari2600
using System;
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
class m3Fe : MapperBase
internal class m3Fe : MapperBase
{
public m3Fe()
{
throw new NotImplementedException();
}
}
}

View File

@ -27,7 +27,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
and M2 (clock) to be able to properly do most of the things it's doing.
*/
class m4A50 : MapperBase
internal class m4A50 : MapperBase
{
private int myLastData = 0xFF;
private int myLastAddress = 0xFFFF;

View File

@ -1,19 +1,16 @@
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
partial class Atari2600
internal class m4K : MapperBase
{
class m4K : MapperBase
public override byte ReadMemory(ushort addr)
{
public override byte ReadMemory(ushort addr)
{
if (addr < 0x1000) return base.ReadMemory(addr);
return core.rom[addr & 0xFFF];
}
if (addr < 0x1000) return base.ReadMemory(addr);
return core.rom[addr & 0xFFF];
}
public override byte PeekMemory(ushort addr)
{
return ReadMemory(addr);
}
public override byte PeekMemory(ushort addr)
{
return ReadMemory(addr);
}
}
}

View File

@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
Magicard
*/
class mCV: MapperBase
internal class mCV: MapperBase
{
ByteBuffer aux_ram = new ByteBuffer(1024);

View File

@ -2,283 +2,500 @@
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
partial class Atari2600
/*
DPC (Pitfall 2)
-----
Back in the day, this game was da shizzle (and IMO still is). It did its trick via
a custom chip in the cartridge. Fortunately for us, there's a patent that describes
lots of the internal workings of the chip (number 4644495, "video memory system").
Interestingly, the patent shows the DPC as a *separate* device. You plug a
passthrough cartridge into your 2600, then plug the game cartridge into the
passthrough. Apparently, Activision thought that people wouldn't like this, or
there was some other reasoning behind it and they ditched that idea and went with
the DPC inside the cartridge.
Unfortunately for Activision, it was filed in January of 1984, during the height of
the crash. The inventor is listed as David Crane.
OK, enough background. Now onto the meat:
The DPC chip is just 24 pins, and needs to pass through the chip enable to the
game ROM on the cartridge, so it can only address 2K of memory. This means the
DPC shows up twice in the address space, once at 1000-107F and again at 1800-18FF.
There's been some discussion about the pitch of the music generated by this chip,
and how different carts will play the music at different pitches. Turns out, on the
cart, the frequency is determined by a resistor (560K ohms) and a capacitor integrated
onto the die of the DPC chip itself. The resistor is a 5% tolerance part, and the
process variations of the DPC itself will control the frequency of the music produced
by it.
If you touch the resistor on the cartridge board, the music pitch will drastically
change, almost like you were playing it on a theremin! Lowering the resistance makes
the music pitch increase, increasing the resistance makes the pitch lower.
It's extremely high impedance so body effects of you touching the pin makes it
vary wildly.
Thus, I say there's really no "one true" pitch for the music. The patent, however,
says that the frequency of this oscillator is 42KHz in the "preferred embodiment".
The patent says that it can range from 15KHz to 80KHz depending on the application
and the particular design of the sound generator. I chose 21KHz (half their preferred
value) and it sounds fairly close to my actual cartridge.
Address map:
Read Only:
1000-1003 : random number generator
1004-1005 : sound value (and MOVAMT value ANDed with draw line carry, with draw line add)
1006-1007 : sound value (and MOVAMT value ANDed with draw line carry, no draw line add)
1008-100F : returned data value for fetcher 0-7
1010-1017 : returned data value for fetcher 0-7, masked
1018-101F : returned data value for fetcher 0-7, nybble swapped, masked
1020-1027 : returned data value for fetcher 0-7, byte reversed, masked
1028-102F : returned data value for fetcher 0-7, rotated right one bit, masked
1030-1037 : returned data value for fetcher 0-7, rotated left one bit, masked
1038-103F : fetcher 0-7 mask
Write Only:
1040-1047 : fetcher 0-7 start count
1048-104F : fetcher 0-7 end count
1050-1057 : fetcher 0-7 pointer low
1058-105B : fetcher 0-3 pointer high
105C : fetcher 4 pointer high and draw line enable
105D-105F : fetcher 5-7 pointer high and music enable
1060-1067 : draw line movement value (MOVAMT)
1068-106F : not used
1070-1077 : random number generator reset
1078-107F : not used
random number generator
-----------------------
The random number generator is used on Pitfall 2 to make the eel flash between white and
black, and nothing else. Failure to emulate this will result in the eel not flashing.
It's an 8 bit LFSR which can be reset to the all 0's condition by accessing 1070-1077.
Unlike a regular LFSR, this one uses three XOR gates and an inverter, so the illegal
condition is the all 1's condition.
There's 255 states and the following code emulates it:
LFSR = ((LFSR << 1) | (~(((LFSR >> 7) ^ (LFSR >> 5)) ^ ((LFSR >> 4) ^ (LFSR >> 3))) & 1)) & 0xff;
Bits 3, 4, 5, and 7 are XOR'd together and inverted and fed back into bit 0 each time the
LFSR is clocked.
The LFSR is clocked each time it is read. It wraps after it is read 255 times. (The
256th read returns the same value as the 1st).
data fetchers
-------------
Internal to the DPC is a 2K ROM containing the graphics and a few other bits and pieces
(playfield values I think) of data that can be read via the auto-incrementing data
fetchers.
Each set of 8 addresses (1008-100F for example) return the data from one of the 8
data fetcher pointers, returning the data in a slightly different format for each.
The format for the 6 possible register ranges is as follows:
For the byte "ABCDEFGH" (bit 7 to bit 0) it is returned:
1008-100F: ABCDEFGH (never masked)
1010-1017: ABCDEFGH
1018-101F: EFGHABCD (nybble swap)
1020-1027: HGFEDCBA (bit reversed)
1028-102F: 0ABCDEFG (shifted right)
1030-1037: BCDEFGH0 (shifted left)
Reading from each set of locations above returns the byte of data from the DPC's
internal ROM. Reading from 1008 accesses data at DF (data fetcher) 0's pointer,
then decrements the pointer. Reading from 1009 accesses data at DF1, and so on.
There is no difference except how the data is returned when reading from 1008,
1010, 1018, 1020, etc. All of them return data pointed to by DF0's pointer. Only
the order of the bits returned changes.
I am not sure what purpose returning the data shifted left or right 1 bit serves,
and it was not used on Pitfall 2, but that's what it does. I guess you could
use it to make a sprite appear to "wiggle" left and right a bit, if it were 6 pixels
wide.
All of these read ports returns the data masked by an enable signal, except for
1008-100F. The data here is never masked. (more about this in a minute)
To read data out of the chip, first you program in its start address into the
pointer registers. These are at 1050-1057 for the lower 8 bits of the pointer
value, and 1058-105F for the upper 4 bits of the pointer value. This forms the
12 bit address which can then be used to index the DPC's ROM.
A few of the upper bits on 105C-105F are used for a few other purposes, which will be
described later.
Masking the data:
-----------------
1038-103F is the readback for the mask value
1040-1047 is the start count
1048-104F is the end count
The mask value can be read via 1038-103F. It returns 0 when graphics are masked, and
FFh when they are not masked. (0 = reset, 1 = set)
The basic synopsis is thus:
When the lower 8 bits of the pointer equals the start count, the mask register is set.
When the lower 8 bits of the pointer equals the end count, the mask register is reset.
Writing to the start count register also sets the register.
This allows one to have the sprites only show up on specific scanlines, by programming
the proper start and end counts, and the proper starting value into the pointer. This
way, the sprite can be drawn from top to bottom of the screen, and have it only appear
where it is desired without having to do anything else in the 2600 code.
Making Music:
-------------
The music is generated by repurposing three of the fetchers, the last three.
Each fetcher can be individually selected for music or fetching.
7 0
---------
105D-105F: xxSM PPPP
S: Select clock input to fetching counter. 0 = read pulse when the proper returned
data register is read (i.e. for fetcher 5, 1015 is being read) 1 = music oscillator.
M: Music mode. 1 = enable music mode, 0 = disable music mode.
P: upper 4 bits of the 12 bit data fetcher pointer.
I am not sure why you can separately select the clock source and the music mode,
but you can. Maybe they had some plans for externally clocking the chip via some
logic to bump the pointers.
Normally you set both the M and P bits to make music.
When in music mode, the lower 8 bits of the fetcher pointer is used as an 8 bit down
counter. Each time the lower 8 bits equals FFh, it is reloaded from the start count
register.
To turn the data fetcher into a square wave generator takes very little hardware. The
start/end count registers are used as-is to toggle the flag register.
This means that the duty cycle of the square waves produced can be varied by adjusting
the end count register relative to the start count register. I suspect the game simply
right shifts the start count by one and stuffs it into the end count to produce a
50% duty cycle waveform.
The three flag outputs for fetchers 5 to 7 are fed into a cool little circuit composed
of a 3 to 8 decoder and four 4 input NAND gates to produce the 4 bit audio output.
The output is as follows:
fetcher result
567
---------------------
000 0h
001 4h
010 5h
011 9h
100 6h
101 Ah
110 Bh
111 Fh
This is a somewhat nonlinear mixing of the three channels, so the apparent volume of them
is different relative to each other.
The final 4 bit output value from the above table is then available to read at address
1004-1007, in bits 0 to 3.
Pitfall 2 just reads this location and stuffs it into the audio register every scanline or
so. The value read at 1004-1007 is the instantanious value generated by the fetchers and
mixing hardware.
*/
internal class mDPC : MapperBase
{
class mDPC : MapperBase
private ulong totalCycles = 0;
private ulong elapsedCycles = 0;
private double FractionalClocks;
private int bank_4k = 0;
private IntBuffer Counters = new IntBuffer(8);
private ByteBuffer Flags = new ByteBuffer(8);
private IntBuffer Tops = new IntBuffer(8);
private IntBuffer Bottoms = new IntBuffer(8);
private ByteBuffer DisplayBank_2k = new ByteBuffer(2048);
private byte RandomNumber = 0;
private bool[] MusicMode = new bool[3]; //TOOD: savestates
public override byte PeekMemory(ushort addr)
{
private ulong totalCycles = 0;
private ulong elapsedCycles = 0;
private double FractionalClocks;
return base.PeekMemory(addr); //TODO
}
private int bank_4k = 0;
private IntBuffer Counters = new IntBuffer(8);
private ByteBuffer Flags = new ByteBuffer(8);
private IntBuffer Tops = new IntBuffer(8);
private IntBuffer Bottoms = new IntBuffer(8);
private ByteBuffer DisplayBank_2k = new ByteBuffer(2048);
private byte RandomNumber = 0;
public override void ClockCpu()
{
totalCycles++;
}
private bool[] MusicMode = new bool[3]; //TOOD: savestates
public override byte ReadMemory(ushort addr)
{
ClockRandomNumberGenerator();
addr &= 0x0FFF;
public override byte PeekMemory(ushort addr)
if (addr < 0x0040)
{
return base.PeekMemory(addr); //TODO
}
byte result = 0;
int index = addr & 0x07;
int function = (addr >> 3) & 0x07;
public override void ClockCpu()
{
totalCycles++;
}
public override byte ReadMemory(ushort addr)
{
ClockRandomNumberGenerator();
addr &= 0x0FFF;
if (addr < 0x0040)
// Update flag register for selected data fetcher
if ((Counters[index] & 0x00ff) == Tops[index])
{
byte result = 0;
int index = addr & 0x07;
int function = (addr >> 3) & 0x07;
// Update flag register for selected data fetcher
if ((Counters[index] & 0x00ff) == Tops[index])
{
Flags[index] = 0xff;
}
else if ((Counters[index] & 0x00ff) == Bottoms[index])
{
Flags[index] = 0x00;
}
switch (function)
{
default:
result = 0;
break;
case 0x00:
if (index < 4)
{
result = RandomNumber;
}
else //it's a music read
{
byte[] MusicAmplitudes = {
0x00, 0x04, 0x05, 0x09, 0x06, 0x0a, 0x0b, 0x0f
};
//// Update the music data fetchers (counter & flag)
UpdateMusicModeDataFetchers();
byte i = 0;
if(MusicMode[0] && Flags[5] > 0)
{
i |= 0x01;
}
if(MusicMode[1] && Flags[6] > 0)
{
i |= 0x02;
}
if(MusicMode[2] && Flags[7] > 0)
{
i |= 0x04;
}
result = MusicAmplitudes[i];
}
break;
case 0x01:
result = DisplayBank_2k[2047 - Counters[index]];
break;
case 0x02:
result = DisplayBank_2k[2047 - (Counters[index] & Flags[index])];
break;
case 0x07:
result = Flags[index];
break;
}
// Clock the selected data fetcher's counter if needed
if ((index < 5) || ((index >= 5) && (!MusicMode[index - 5])))
{
Counters[index] = (Counters[index] - 1) & 0x07ff;
}
return result;
Flags[index] = 0xff;
}
else
else if ((Counters[index] & 0x00ff) == Bottoms[index])
{
Address(addr);
return core.rom[(bank_4k << 12) + addr];
}
}
public override void WriteMemory(ushort addr, byte value)
{
addr &= 0x0FFF;
// Clock the random number generator. This should be done for every
// cartridge access, however, we're only doing it for the DPC and
// hot-spot accesses to save time.
ClockRandomNumberGenerator();
if ((addr >= 0x0040) && (addr < 0x0080))
{
// Get the index of the data fetcher that's being accessed
int index = addr & 0x07;
int function = (addr >> 3) & 0x07;
switch (function)
{
case 0x00: // DFx top count
Tops[index] = value;
Flags[index] = 0x00;
break;
case 0x01: // DFx bottom count
Bottoms[index] = value;
break;
case 0x02: // DFx counter low
if ((index >= 5) && MusicMode[index - 5])
{
Counters[index] = (Counters[index] & 0x0700) | Tops[index]; // Data fetcher is in music mode so its low counter value should be loaded from the top register not the poked value
}
else
{
// Data fetcher is either not a music mode data fetcher or it
// isn't in music mode so it's low counter value should be loaded
// with the poked value
Counters[index] = (Counters[index] & 0x0700) | value;
}
break;
case 0x03: // DFx counter high
Counters[index] = ((value & 0x07) << 8) | (Counters[index] & 0x00ff);
// Execute special code for music mode data fetchers
if (index >= 5)
{
MusicMode[index - 5] = (value & 0x10) > 0 ? true : false;
// NOTE: We are not handling the clock source input for
// the music mode data fetchers. We're going to assume
// they always use the OSC input.
}
break;
case 0x06: // Random Number Generator Reset
RandomNumber = 1;
break;
}
}
else
{
Address(addr);
}
return;
}
private void Address(ushort addr)
{
if (addr == 0x0FF8)
{
bank_4k = 0;
}
else if (addr == 0x0FF9)
{
bank_4k = 1;
}
}
public override void Dispose()
{
DisplayBank_2k.Dispose();
Counters.Dispose();
Flags.Dispose();
base.Dispose();
}
public override void SyncState(Serializer ser)
{
//TODO
base.SyncState(ser);
ser.Sync("bank_4k", ref bank_4k);
ser.Sync("DisplayBank_2k", ref DisplayBank_2k);
ser.Sync("Flags", ref Flags);
ser.Sync("Counters", ref Counters);
ser.Sync("RandomNumber", ref RandomNumber);
}
private void UpdateMusicModeDataFetchers()
{
// Calculate the number of cycles since the last update
//int cycles = mySystem->cycles() - mySystemCycles;
//mySystemCycles = mySystem->cycles();
ulong cycles = totalCycles - elapsedCycles;
elapsedCycles = totalCycles;
// Calculate the number of DPC OSC clocks since the last update
double clocks = ((20000.0 * cycles) / 1193191.66666667) + FractionalClocks;
int wholeClocks = (int)clocks;
FractionalClocks = clocks - (double)wholeClocks;
if (wholeClocks <= 0)
{
return;
Flags[index] = 0x00;
}
// Let's update counters and flags of the music mode data fetchers
for (int x = 5; x <= 7; ++x)
switch (function)
{
// Update only if the data fetcher is in music mode
if (MusicMode[x - 5])
{
int top = Tops[x] + 1;
int newLow = Counters[x] & 0x00ff;
if (Tops[x] != 0)
default:
result = 0;
break;
case 0x00:
if (index < 4)
{
newLow -= (wholeClocks % top);
if (newLow < 0)
result = RandomNumber;
}
else //it's a music read
{
byte[] MusicAmplitudes = {
0x00, 0x04, 0x05, 0x09, 0x06, 0x0a, 0x0b, 0x0f
};
//// Update the music data fetchers (counter & flag)
UpdateMusicModeDataFetchers();
byte i = 0;
if(MusicMode[0] && Flags[5] > 0)
{
newLow += top;
i |= 0x01;
}
if(MusicMode[1] && Flags[6] > 0)
{
i |= 0x02;
}
if(MusicMode[2] && Flags[7] > 0)
{
i |= 0x04;
}
result = MusicAmplitudes[i];
}
break;
case 0x01:
result = DisplayBank_2k[2047 - Counters[index]];
break;
case 0x02:
result = DisplayBank_2k[2047 - (Counters[index] & Flags[index])];
break;
case 0x07:
result = Flags[index];
break;
}
// Clock the selected data fetcher's counter if needed
if ((index < 5) || ((index >= 5) && (!MusicMode[index - 5])))
{
Counters[index] = (Counters[index] - 1) & 0x07ff;
}
return result;
}
else
{
Address(addr);
return core.rom[(bank_4k << 12) + addr];
}
}
public override void WriteMemory(ushort addr, byte value)
{
addr &= 0x0FFF;
// Clock the random number generator. This should be done for every
// cartridge access, however, we're only doing it for the DPC and
// hot-spot accesses to save time.
ClockRandomNumberGenerator();
if ((addr >= 0x0040) && (addr < 0x0080))
{
// Get the index of the data fetcher that's being accessed
int index = addr & 0x07;
int function = (addr >> 3) & 0x07;
switch (function)
{
case 0x00: // DFx top count
Tops[index] = value;
Flags[index] = 0x00;
break;
case 0x01: // DFx bottom count
Bottoms[index] = value;
break;
case 0x02: // DFx counter low
if ((index >= 5) && MusicMode[index - 5])
{
Counters[index] = (Counters[index] & 0x0700) | Tops[index]; // Data fetcher is in music mode so its low counter value should be loaded from the top register not the poked value
}
else
{
newLow = 0;
// Data fetcher is either not a music mode data fetcher or it
// isn't in music mode so it's low counter value should be loaded
// with the poked value
Counters[index] = (Counters[index] & 0x0700) | value;
}
break;
case 0x03: // DFx counter high
Counters[index] = ((value & 0x07) << 8) | (Counters[index] & 0x00ff);
// Update flag register for this data fetcher
if (newLow <= Bottoms[x])
// Execute special code for music mode data fetchers
if (index >= 5)
{
Flags[x] = 0x00;
}
else if (newLow <= Tops[x])
{
Flags[x] = 0xff;
}
MusicMode[index - 5] = (value & 0x10) > 0 ? true : false;
Counters[x] = (Counters[x] & 0x0700) | newLow;
}
// NOTE: We are not handling the clock source input for
// the music mode data fetchers. We're going to assume
// they always use the OSC input.
}
break;
case 0x06: // Random Number Generator Reset
RandomNumber = 1;
break;
}
}
private void ClockRandomNumberGenerator()
else
{
// Table for computing the input bit of the random number generator's
// shift register (it's the NOT of the EOR of four bits)
byte[] f = {
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1
};
// Using bits 7, 5, 4, & 3 of the shift register compute the input
// bit for the shift register
byte bit = f[((RandomNumber >> 3) & 0x07) |
((RandomNumber & 0x80) > 0 ? 0x08 : 0x00)];
// Update the shift register
RandomNumber = (byte)(RandomNumber << 1 | bit);
Address(addr);
}
return;
}
private void Address(ushort addr)
{
if (addr == 0x0FF8)
{
bank_4k = 0;
}
else if (addr == 0x0FF9)
{
bank_4k = 1;
}
}
public override void Dispose()
{
DisplayBank_2k.Dispose();
Counters.Dispose();
Flags.Dispose();
base.Dispose();
}
public override void SyncState(Serializer ser)
{
//TODO
base.SyncState(ser);
ser.Sync("bank_4k", ref bank_4k);
ser.Sync("DisplayBank_2k", ref DisplayBank_2k);
ser.Sync("Flags", ref Flags);
ser.Sync("Counters", ref Counters);
ser.Sync("RandomNumber", ref RandomNumber);
}
private void UpdateMusicModeDataFetchers()
{
// Calculate the number of cycles since the last update
//int cycles = mySystem->cycles() - mySystemCycles;
//mySystemCycles = mySystem->cycles();
ulong cycles = totalCycles - elapsedCycles;
elapsedCycles = totalCycles;
// Calculate the number of DPC OSC clocks since the last update
double clocks = ((20000.0 * cycles) / 1193191.66666667) + FractionalClocks;
int wholeClocks = (int)clocks;
FractionalClocks = clocks - (double)wholeClocks;
if (wholeClocks <= 0)
{
return;
}
// Let's update counters and flags of the music mode data fetchers
for (int x = 5; x <= 7; ++x)
{
// Update only if the data fetcher is in music mode
if (MusicMode[x - 5])
{
int top = Tops[x] + 1;
int newLow = Counters[x] & 0x00ff;
if (Tops[x] != 0)
{
newLow -= (wholeClocks % top);
if (newLow < 0)
{
newLow += top;
}
}
else
{
newLow = 0;
}
// Update flag register for this data fetcher
if (newLow <= Bottoms[x])
{
Flags[x] = 0x00;
}
else if (newLow <= Tops[x])
{
Flags[x] = 0xff;
}
Counters[x] = (Counters[x] & 0x0700) | newLow;
}
}
}
private void ClockRandomNumberGenerator()
{
// Table for computing the input bit of the random number generator's
// shift register (it's the NOT of the EOR of four bits)
byte[] f = {
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1
};
// Using bits 7, 5, 4, & 3 of the shift register compute the input
// bit for the shift register
byte bit = f[((RandomNumber >> 3) & 0x07) |
((RandomNumber & 0x80) > 0 ? 0x08 : 0x00)];
// Update the shift register
RandomNumber = (byte)(RandomNumber << 1 | bit);
}
}
}

View File

@ -23,7 +23,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
Frogger II - Threedeep! (1983) (Parker Bros)
*/
class mE0 : MapperBase
internal class mE0 : MapperBase
{
int toggle1;
int toggle2;

View File

@ -27,7 +27,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
Accessing 1FE8 through 1FEB select which 256 byte bank shows up.
*/
class mE7 : MapperBase
internal class mE7 : MapperBase
{
private int rombank_1k;
private int rambank1_toggle;

View File

@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
1FE1 = bank 1, etc.
*/
class mEF : MapperBase
internal class mEF : MapperBase
{
private int toggle;

View File

@ -18,7 +18,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
1FF0 until the bank it is looking for comes up.
*/
class mF0 : MapperBase
internal class mF0 : MapperBase
{
int bank;

View File

@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
by accessing 1FF4 through 1FFB.
*/
class mF4 :MapperBase
internal class mF4 :MapperBase
{
int toggle;

View File

@ -11,7 +11,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
select one of the 4 banks. i.e. 1FF6 selects bank 0, 1FF7 selects bank 1, etc.
*/
class mF6 : MapperBase
internal class mF6 : MapperBase
{
int toggle;

View File

@ -2,68 +2,65 @@
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
partial class Atari2600
/*
F8 (Atari style 8K)
-----
This is the fairly standard way 8K of cartridge ROM was implemented. There are two
4K ROM banks, which get mapped into the 4K of cartridge space. Accessing 1FF8 or
1FF9 selects one of the two 4K banks. When one of these two addresses are accessed,
the banks switch spontaniously.
ANY kind of access will trigger the switching- reading or writing. Usually games use
LDA or BIT on 1FF8/1FF9 to perform the switch.
When the switch occurs, the entire 4K ROM bank switches, including the code that is
reading the 1FF8/1FF9 location. Usually, games put a small stub of code in BOTH banks
so when the switch occurs, the code won't crash.
*/
internal class mF8 : MapperBase
{
/*
F8 (Atari style 8K)
-----
int bank_4k;
This is the fairly standard way 8K of cartridge ROM was implemented. There are two
4K ROM banks, which get mapped into the 4K of cartridge space. Accessing 1FF8 or
1FF9 selects one of the two 4K banks. When one of these two addresses are accessed,
the banks switch spontaniously.
ANY kind of access will trigger the switching- reading or writing. Usually games use
LDA or BIT on 1FF8/1FF9 to perform the switch.
When the switch occurs, the entire 4K ROM bank switches, including the code that is
reading the 1FF8/1FF9 location. Usually, games put a small stub of code in BOTH banks
so when the switch occurs, the code won't crash.
*/
class mF8 : MapperBase
private byte ReadMem(ushort addr, bool peek)
{
int bank_4k;
private byte ReadMem(ushort addr, bool peek)
{
if (!peek)
{
Address(addr);
}
if (addr < 0x1000) return base.ReadMemory(addr);
return core.rom[(bank_4k << 12) + (addr & 0xFFF)];
}
public override byte ReadMemory(ushort addr)
{
return ReadMem(addr, false);
}
public override byte PeekMemory(ushort addr)
{
return ReadMem(addr, true);
}
public override void WriteMemory(ushort addr, byte value)
if (!peek)
{
Address(addr);
if (addr < 0x1000) base.WriteMemory(addr, value);
}
public override void SyncState(Serializer ser)
{
base.SyncState(ser);
ser.Sync("bank_4k", ref bank_4k);
if (addr < 0x1000) return base.ReadMemory(addr);
return core.rom[(bank_4k << 12) + (addr & 0xFFF)];
}
public override byte ReadMemory(ushort addr)
{
return ReadMem(addr, false);
}
public override byte PeekMemory(ushort addr)
{
return ReadMem(addr, true);
}
public override void WriteMemory(ushort addr, byte value)
{
Address(addr);
if (addr < 0x1000) base.WriteMemory(addr, value);
}
public override void SyncState(Serializer ser)
{
base.SyncState(ser);
ser.Sync("bank_4k", ref bank_4k);
}
}
void Address(ushort addr)
{
if (addr == 0x1FF8) bank_4k = 0;
else if (addr == 0x1FF9) bank_4k = 1;
}
void Address(ushort addr)
{
if (addr == 0x1FF8) bank_4k = 0;
else if (addr == 0x1FF9) bank_4k = 1;
}
}
}

View File

@ -14,7 +14,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
The write port is at 1000-10FF, and the read port is 1100-11FF.
*/
class mFA : MapperBase
internal class mFA : MapperBase
{
int toggle;
ByteBuffer aux_ram = new ByteBuffer(256);

View File

@ -1,4 +1,6 @@
namespace BizHawk.Emulation.Cores.Atari.Atari2600
using System;
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
/*
FE (Activision special)
@ -55,8 +57,11 @@
to simply select which 8K bank to be in.
*/
class mFE : MapperBase
internal class mFE : MapperBase
{
public mFE()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,4 +1,6 @@
namespace BizHawk.Emulation.Cores.Atari.Atari2600
using System;
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
/*
MC (Megacart)
@ -57,8 +59,11 @@
disregard accesses to 3C-3F instead.
*/
class mMC : MapperBase
internal class mMC : MapperBase
{
public mMC()
{
throw new NotImplementedException();
}
}
}

View File

@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
Accessing 0220 will select the first bank, and accessing 0240 will select the second.
*/
class mUA : MapperBase
internal class mUA : MapperBase
{
int toggle;

View File

@ -33,7 +33,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
TIA registers at 00-3F or 40-7F without incurring any overhead.
*/
class mX07 : MapperBase
internal class mX07 : MapperBase
{
int rombank_2k;