diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m0840.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m0840.cs index 3fb3ea6070..5ff0bc572c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m0840.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m0840.cs @@ -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(); + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m2K.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m2K.cs index d6fb45c455..9d9321461b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m2K.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m2K.cs @@ -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); } } } \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3E.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3E.cs index 5b89be1f6a..c3e15c9c0c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3E.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3E.cs @@ -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; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3F.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3F.cs index b4b2076401..8fb5796530 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3F.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3F.cs @@ -21,7 +21,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 yet. */ - class m3F :MapperBase + internal class m3F : MapperBase { int lowbank_2k; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3Fe.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3Fe.cs index 8c3627ac7e..07693f46a3 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3Fe.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m3Fe.cs @@ -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(); + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m4A50.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m4A50.cs index 26d5b6ea51..ed18ac9c81 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m4A50.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m4A50.cs @@ -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; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m4K.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m4K.cs index f676f34c98..464b423cb8 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m4K.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/m4K.cs @@ -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); } } } \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mCV.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mCV.cs index 7f2dcf412e..64863f87fc 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mCV.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mCV.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 Magicard */ - class mCV: MapperBase + internal class mCV: MapperBase { ByteBuffer aux_ram = new ByteBuffer(1024); diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mDPC.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mDPC.cs index 7b19acee40..0e641091a1 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mDPC.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mDPC.cs @@ -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); } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mE0.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mE0.cs index d4c365a11d..89f5ef0a85 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mE0.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mE0.cs @@ -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; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mE7.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mE7.cs index 3dd79b091d..4966a39975 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mE7.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mE7.cs @@ -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; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mEF.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mEF.cs index d6bd81abfc..03a7ca612b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mEF.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mEF.cs @@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 1FE1 = bank 1, etc. */ - class mEF : MapperBase + internal class mEF : MapperBase { private int toggle; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF0.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF0.cs index 8aefa0d837..ae851c4870 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF0.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF0.cs @@ -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; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF4.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF4.cs index 3276ff4df7..aa4e274d92 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF4.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF4.cs @@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 by accessing 1FF4 through 1FFB. */ - class mF4 :MapperBase + internal class mF4 :MapperBase { int toggle; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF6.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF6.cs index e54f33d322..75f5562ab8 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF6.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF6.cs @@ -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; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF8.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF8.cs index 70fd2c6770..26f09e0b57 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF8.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mF8.cs @@ -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; } } } \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mFA.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mFA.cs index b0cecdd05e..c3d0c399a6 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mFA.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mFA.cs @@ -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); diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mFE.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mFE.cs index b2aad4381e..fa88f47b41 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mFE.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mFE.cs @@ -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(); + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mMC.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mMC.cs index 6a880861e4..0ca8918345 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mMC.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mMC.cs @@ -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(); + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mUA.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mUA.cs index 329151cb9b..fe0e125b5b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mUA.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mUA.cs @@ -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; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mX07.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mX07.cs index 245c113806..0c940ce387 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mX07.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mX07.cs @@ -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;