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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
|||
yet.
|
||||
*/
|
||||
|
||||
class m3F :MapperBase
|
||||
internal class m3F : MapperBase
|
||||
{
|
||||
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.
|
||||
*/
|
||||
|
||||
class m4A50 : MapperBase
|
||||
internal class m4A50 : MapperBase
|
||||
{
|
||||
private int myLastData = 0xFF;
|
||||
private int myLastAddress = 0xFFFF;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
|||
Magicard
|
||||
*/
|
||||
|
||||
class mCV: MapperBase
|
||||
internal class mCV: MapperBase
|
||||
{
|
||||
ByteBuffer aux_ram = new ByteBuffer(1024);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
|||
1FE1 = bank 1, etc.
|
||||
*/
|
||||
|
||||
class mEF : MapperBase
|
||||
internal class mEF : MapperBase
|
||||
{
|
||||
private int toggle;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
|||
by accessing 1FF4 through 1FFB.
|
||||
*/
|
||||
|
||||
class mF4 :MapperBase
|
||||
internal class mF4 :MapperBase
|
||||
{
|
||||
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.
|
||||
*/
|
||||
|
||||
class mF6 : MapperBase
|
||||
internal class mF6 : MapperBase
|
||||
{
|
||||
int toggle;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue