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:
parent
5db777afcf
commit
12cdedf299
|
@ -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
|
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
|
B is the bank we will select. sooo, accessing 0800 will select bank 0, and 0840
|
||||||
will select bank 1.
|
will select bank 1.
|
||||||
*/
|
*/
|
||||||
class m0840 : MapperBase
|
internal class m0840 : MapperBase
|
||||||
{
|
{
|
||||||
|
public m0840()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
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 base.ReadMemory(addr);
|
||||||
return core.rom[addr & 0x7FF];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override byte PeekMemory(ushort addr)
|
return core.rom[addr & 0x7FF];
|
||||||
{
|
}
|
||||||
return ReadMemory(addr);
|
|
||||||
}
|
public override byte PeekMemory(ushort addr)
|
||||||
|
{
|
||||||
|
return ReadMemory(addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
enough space for 256K of RAM. When RAM is selected, 1000-13FF is the read port while
|
||||||
1400-17FF is the write port.
|
1400-17FF is the write port.
|
||||||
*/
|
*/
|
||||||
class m3E : MapperBase
|
internal class m3E : MapperBase
|
||||||
{
|
{
|
||||||
int lowbank_2k;
|
int lowbank_2k;
|
||||||
int rambank_1k;
|
int rambank_1k;
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
yet.
|
yet.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class m3F :MapperBase
|
internal class m3F : MapperBase
|
||||||
{
|
{
|
||||||
int lowbank_2k;
|
int lowbank_2k;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
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 myLastData = 0xFF;
|
||||||
private int myLastAddress = 0xFFFF;
|
private int myLastAddress = 0xFFFF;
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
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)
|
public override byte PeekMemory(ushort addr)
|
||||||
{
|
{
|
||||||
return ReadMemory(addr);
|
return ReadMemory(addr);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
Magicard
|
Magicard
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mCV: MapperBase
|
internal class mCV: MapperBase
|
||||||
{
|
{
|
||||||
ByteBuffer aux_ram = new ByteBuffer(1024);
|
ByteBuffer aux_ram = new ByteBuffer(1024);
|
||||||
|
|
||||||
|
|
|
@ -2,283 +2,500 @@
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
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;
|
return base.PeekMemory(addr); //TODO
|
||||||
private ulong elapsedCycles = 0;
|
}
|
||||||
private double FractionalClocks;
|
|
||||||
|
|
||||||
private int bank_4k = 0;
|
public override void ClockCpu()
|
||||||
private IntBuffer Counters = new IntBuffer(8);
|
{
|
||||||
private ByteBuffer Flags = new ByteBuffer(8);
|
totalCycles++;
|
||||||
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 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()
|
// Update flag register for selected data fetcher
|
||||||
{
|
if ((Counters[index] & 0x00ff) == Tops[index])
|
||||||
totalCycles++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override byte ReadMemory(ushort addr)
|
|
||||||
{
|
|
||||||
ClockRandomNumberGenerator();
|
|
||||||
addr &= 0x0FFF;
|
|
||||||
|
|
||||||
if (addr < 0x0040)
|
|
||||||
{
|
{
|
||||||
byte result = 0;
|
Flags[index] = 0xff;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
else
|
else if ((Counters[index] & 0x00ff) == Bottoms[index])
|
||||||
{
|
{
|
||||||
Address(addr);
|
Flags[index] = 0x00;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's update counters and flags of the music mode data fetchers
|
switch (function)
|
||||||
for (int x = 5; x <= 7; ++x)
|
|
||||||
{
|
{
|
||||||
// Update only if the data fetcher is in music mode
|
default:
|
||||||
if (MusicMode[x - 5])
|
result = 0;
|
||||||
{
|
break;
|
||||||
int top = Tops[x] + 1;
|
case 0x00:
|
||||||
int newLow = Counters[x] & 0x00ff;
|
if (index < 4)
|
||||||
|
|
||||||
if (Tops[x] != 0)
|
|
||||||
{
|
{
|
||||||
newLow -= (wholeClocks % top);
|
result = RandomNumber;
|
||||||
if (newLow < 0)
|
}
|
||||||
|
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
|
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
|
// Execute special code for music mode data fetchers
|
||||||
if (newLow <= Bottoms[x])
|
if (index >= 5)
|
||||||
{
|
{
|
||||||
Flags[x] = 0x00;
|
MusicMode[index - 5] = (value & 0x10) > 0 ? true : false;
|
||||||
}
|
|
||||||
else if (newLow <= Tops[x])
|
|
||||||
{
|
|
||||||
Flags[x] = 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private void ClockRandomNumberGenerator()
|
|
||||||
{
|
{
|
||||||
// Table for computing the input bit of the random number generator's
|
Address(addr);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
Frogger II - Threedeep! (1983) (Parker Bros)
|
Frogger II - Threedeep! (1983) (Parker Bros)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mE0 : MapperBase
|
internal class mE0 : MapperBase
|
||||||
{
|
{
|
||||||
int toggle1;
|
int toggle1;
|
||||||
int toggle2;
|
int toggle2;
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
Accessing 1FE8 through 1FEB select which 256 byte bank shows up.
|
Accessing 1FE8 through 1FEB select which 256 byte bank shows up.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mE7 : MapperBase
|
internal class mE7 : MapperBase
|
||||||
{
|
{
|
||||||
private int rombank_1k;
|
private int rombank_1k;
|
||||||
private int rambank1_toggle;
|
private int rambank1_toggle;
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
1FE1 = bank 1, etc.
|
1FE1 = bank 1, etc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mEF : MapperBase
|
internal class mEF : MapperBase
|
||||||
{
|
{
|
||||||
private int toggle;
|
private int toggle;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
1FF0 until the bank it is looking for comes up.
|
1FF0 until the bank it is looking for comes up.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mF0 : MapperBase
|
internal class mF0 : MapperBase
|
||||||
{
|
{
|
||||||
int bank;
|
int bank;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
by accessing 1FF4 through 1FFB.
|
by accessing 1FF4 through 1FFB.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mF4 :MapperBase
|
internal class mF4 :MapperBase
|
||||||
{
|
{
|
||||||
int toggle;
|
int toggle;
|
||||||
|
|
||||||
|
|
|
@ -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.
|
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;
|
int toggle;
|
||||||
|
|
||||||
|
|
|
@ -2,68 +2,65 @@
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
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
|
||||||
{
|
{
|
||||||
/*
|
int bank_4k;
|
||||||
F8 (Atari style 8K)
|
|
||||||
-----
|
|
||||||
|
|
||||||
This is the fairly standard way 8K of cartridge ROM was implemented. There are two
|
private byte ReadMem(ushort addr, bool peek)
|
||||||
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
|
|
||||||
{
|
{
|
||||||
int bank_4k;
|
if (!peek)
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
Address(addr);
|
Address(addr);
|
||||||
if (addr < 0x1000) base.WriteMemory(addr, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SyncState(Serializer ser)
|
if (addr < 0x1000) return base.ReadMemory(addr);
|
||||||
{
|
return core.rom[(bank_4k << 12) + (addr & 0xFFF)];
|
||||||
base.SyncState(ser);
|
}
|
||||||
ser.Sync("bank_4k", ref bank_4k);
|
|
||||||
|
|
||||||
}
|
public override byte ReadMemory(ushort addr)
|
||||||
|
{
|
||||||
|
return ReadMem(addr, false);
|
||||||
|
}
|
||||||
|
|
||||||
void Address(ushort addr)
|
public override byte PeekMemory(ushort addr)
|
||||||
{
|
{
|
||||||
if (addr == 0x1FF8) bank_4k = 0;
|
return ReadMem(addr, true);
|
||||||
else if (addr == 0x1FF9) bank_4k = 1;
|
}
|
||||||
}
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
The write port is at 1000-10FF, and the read port is 1100-11FF.
|
The write port is at 1000-10FF, and the read port is 1100-11FF.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mFA : MapperBase
|
internal class mFA : MapperBase
|
||||||
{
|
{
|
||||||
int toggle;
|
int toggle;
|
||||||
ByteBuffer aux_ram = new ByteBuffer(256);
|
ByteBuffer aux_ram = new ByteBuffer(256);
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
using System;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
FE (Activision special)
|
FE (Activision special)
|
||||||
|
@ -55,8 +57,11 @@
|
||||||
to simply select which 8K bank to be in.
|
to simply select which 8K bank to be in.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mFE : MapperBase
|
internal class mFE : MapperBase
|
||||||
{
|
{
|
||||||
|
public mFE()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
using System;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
MC (Megacart)
|
MC (Megacart)
|
||||||
|
@ -57,8 +59,11 @@
|
||||||
disregard accesses to 3C-3F instead.
|
disregard accesses to 3C-3F instead.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mMC : MapperBase
|
internal class mMC : MapperBase
|
||||||
{
|
{
|
||||||
|
public mMC()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
Accessing 0220 will select the first bank, and accessing 0240 will select the second.
|
Accessing 0220 will select the first bank, and accessing 0240 will select the second.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mUA : MapperBase
|
internal class mUA : MapperBase
|
||||||
{
|
{
|
||||||
int toggle;
|
int toggle;
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
TIA registers at 00-3F or 40-7F without incurring any overhead.
|
TIA registers at 00-3F or 40-7F without incurring any overhead.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class mX07 : MapperBase
|
internal class mX07 : MapperBase
|
||||||
{
|
{
|
||||||
int rombank_2k;
|
int rombank_2k;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue