in atari_py/ale_interface/src/emucore/TIA.cxx [2133:2910]
void TIA::poke(uInt16 addr, uInt8 value)
{
addr = addr & 0x003f;
Int32 clock = mySystem->cycles() * 3;
Int16 delay = ourPokeDelayTable[addr];
// See if this is a poke to a PF register
if(delay == -1)
{
static uInt32 d[4] = {4, 5, 2, 3};
Int32 x = ((clock - myClockWhenFrameStarted) % 228);
delay = d[(x / 3) & 3];
}
// Update frame to current CPU cycle before we make any changes!
updateFrame(clock + delay);
// If a VSYNC hasn't been generated in time go ahead and end the frame
if(((clock - myClockWhenFrameStarted) / 228) > myMaximumNumberOfScanlines)
{
mySystem->m6502().stop();
myPartialFrameFlag = false;
}
switch(addr)
{
case 0x00: // Vertical sync set-clear
{
myVSYNC = value;
if(myVSYNC & 0x02)
{
// Indicate when VSYNC should be finished. This should really
// be 3 * 228 according to Atari's documentation, however, some
// games don't supply the full 3 scanlines of VSYNC.
myVSYNCFinishClock = clock + 228;
}
else if(!(myVSYNC & 0x02) && (clock >= myVSYNCFinishClock))
{
// We're no longer interested in myVSYNCFinishClock
myVSYNCFinishClock = 0x7FFFFFFF;
// Since we're finished with the frame tell the processor to halt
mySystem->m6502().stop();
myPartialFrameFlag = false;
}
break;
}
case 0x01: // Vertical blank set-clear
{
// Is the dump to ground path being set for I0, I1, I2, and I3?
if(!(myVBLANK & 0x80) && (value & 0x80))
{
myDumpEnabled = true;
}
// Is the dump to ground path being removed from I0, I1, I2, and I3?
if((myVBLANK & 0x80) && !(value & 0x80))
{
myDumpEnabled = false;
myDumpDisabledCycle = mySystem->cycles();
}
myVBLANK = value;
break;
}
case 0x02: // Wait for leading edge of HBLANK
{
// It appears that the 6507 only halts during a read cycle so
// we test here for follow-on writes which should be ignored as
// far as halting the processor is concerned.
//
// TODO - 08-30-2006: This halting isn't correct since it's
// still halting on the original write. The 6507 emulation
// should be expanded to include a READY line.
if(mySystem->m6502().lastAccessWasRead())
{
// Tell the cpu to waste the necessary amount of time
waitHorizontalSync();
}
break;
}
case 0x03: // Reset horizontal sync counter
{
// cerr << "TIA Poke: " << hex << addr << endl;
break;
}
case 0x04: // Number-size of player-missle 0
{
myNUSIZ0 = value;
// TODO: Technically the "enable" part, [0], should depend on the current
// enabled or disabled state. This mean we probably need a data member
// to maintain that state (01/21/99).
myCurrentP0Mask = &ourPlayerMaskTable[myPOSP0 & 0x03]
[0][myNUSIZ0 & 0x07][160 - (myPOSP0 & 0xFC)];
myCurrentM0Mask = &ourMissleMaskTable[myPOSM0 & 0x03]
[myNUSIZ0 & 0x07][(myNUSIZ0 & 0x30) >> 4][160 - (myPOSM0 & 0xFC)];
break;
}
case 0x05: // Number-size of player-missle 1
{
myNUSIZ1 = value;
// TODO: Technically the "enable" part, [0], should depend on the current
// enabled or disabled state. This mean we probably need a data member
// to maintain that state (01/21/99).
myCurrentP1Mask = &ourPlayerMaskTable[myPOSP1 & 0x03]
[0][myNUSIZ1 & 0x07][160 - (myPOSP1 & 0xFC)];
myCurrentM1Mask = &ourMissleMaskTable[myPOSM1 & 0x03]
[myNUSIZ1 & 0x07][(myNUSIZ1 & 0x30) >> 4][160 - (myPOSM1 & 0xFC)];
break;
}
case 0x06: // Color-Luminance Player 0
{
uInt32 color = (uInt32)(value & 0xfe);
if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
{
color |= 0x01;
}
myCOLUP0 = (((((color << 8) | color) << 8) | color) << 8) | color;
break;
}
case 0x07: // Color-Luminance Player 1
{
uInt32 color = (uInt32)(value & 0xfe);
if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
{
color |= 0x01;
}
myCOLUP1 = (((((color << 8) | color) << 8) | color) << 8) | color;
break;
}
case 0x08: // Color-Luminance Playfield
{
uInt32 color = (uInt32)(value & 0xfe);
if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
{
color |= 0x01;
}
myCOLUPF = (((((color << 8) | color) << 8) | color) << 8) | color;
break;
}
case 0x09: // Color-Luminance Background
{
uInt32 color = (uInt32)(value & 0xfe);
if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
{
color |= 0x01;
}
myCOLUBK = (((((color << 8) | color) << 8) | color) << 8) | color;
break;
}
case 0x0A: // Control Playfield, Ball size, Collisions
{
myCTRLPF = value;
// The playfield priority and score bits from the control register
// are accessed when the frame is being drawn. We precompute the
// necessary value here so we can save time while drawing.
myPlayfieldPriorityAndScore = ((myCTRLPF & 0x06) << 5);
// Update the playfield mask based on reflection state if
// we're still on the left hand side of the playfield
if(((clock - myClockWhenFrameStarted) % 228) < (68 + 79))
{
myCurrentPFMask = ourPlayfieldTable[myCTRLPF & 0x01];
}
myCurrentBLMask = &ourBallMaskTable[myPOSBL & 0x03]
[(myCTRLPF & 0x30) >> 4][160 - (myPOSBL & 0xFC)];
break;
}
case 0x0B: // Reflect Player 0
{
// See if the reflection state of the player is being changed
if(((value & 0x08) && !myREFP0) || (!(value & 0x08) && myREFP0))
{
myREFP0 = (value & 0x08);
myCurrentGRP0 = ourPlayerReflectTable[myCurrentGRP0];
}
break;
}
case 0x0C: // Reflect Player 1
{
// See if the reflection state of the player is being changed
if(((value & 0x08) && !myREFP1) || (!(value & 0x08) && myREFP1))
{
myREFP1 = (value & 0x08);
myCurrentGRP1 = ourPlayerReflectTable[myCurrentGRP1];
}
break;
}
case 0x0D: // Playfield register byte 0
{
myPF = (myPF & 0x000FFFF0) | ((value >> 4) & 0x0F);
if(!myBitEnabled[TIA::PF] || myPF == 0)
myEnabledObjects &= ~myPFBit;
else
myEnabledObjects |= myPFBit;
break;
}
case 0x0E: // Playfield register byte 1
{
myPF = (myPF & 0x000FF00F) | ((uInt32)value << 4);
if(!myBitEnabled[TIA::PF] || myPF == 0)
myEnabledObjects &= ~myPFBit;
else
myEnabledObjects |= myPFBit;
break;
}
case 0x0F: // Playfield register byte 2
{
myPF = (myPF & 0x00000FFF) | ((uInt32)value << 12);
if(!myBitEnabled[TIA::PF] || myPF == 0)
myEnabledObjects &= ~myPFBit;
else
myEnabledObjects |= myPFBit;
break;
}
case 0x10: // Reset Player 0
{
Int32 hpos = (clock - myClockWhenFrameStarted) % 228;
Int32 newx = hpos < HBLANK ? 3 : (((hpos - HBLANK) + 5) % 160);
// Find out under what condition the player is being reset
Int8 when = ourPlayerPositionResetWhenTable[myNUSIZ0 & 7][myPOSP0][newx];
// Player is being reset during the display of one of its copies
if(when == 1)
{
// So we go ahead and update the display before moving the player
// TODO: The 11 should depend on how much of the player has already
// been displayed. Probably change table to return the amount to
// delay by instead of just 1 (01/21/99).
updateFrame(clock + 11);
myPOSP0 = newx;
// Setup the mask to skip the first copy of the player
myCurrentP0Mask = &ourPlayerMaskTable[myPOSP0 & 0x03]
[1][myNUSIZ0 & 0x07][160 - (myPOSP0 & 0xFC)];
}
// Player is being reset in neither the delay nor display section
else if(when == 0)
{
myPOSP0 = newx;
// So we setup the mask to skip the first copy of the player
myCurrentP0Mask = &ourPlayerMaskTable[myPOSP0 & 0x03]
[1][myNUSIZ0 & 0x07][160 - (myPOSP0 & 0xFC)];
}
// Player is being reset during the delay section of one of its copies
else if(when == -1)
{
myPOSP0 = newx;
// So we setup the mask to display all copies of the player
myCurrentP0Mask = &ourPlayerMaskTable[myPOSP0 & 0x03]
[0][myNUSIZ0 & 0x07][160 - (myPOSP0 & 0xFC)];
}
break;
}
case 0x11: // Reset Player 1
{
Int32 hpos = (clock - myClockWhenFrameStarted) % 228;
Int32 newx = hpos < HBLANK ? 3 : (((hpos - HBLANK) + 5) % 160);
// Find out under what condition the player is being reset
Int8 when = ourPlayerPositionResetWhenTable[myNUSIZ1 & 7][myPOSP1][newx];
// Player is being reset during the display of one of its copies
if(when == 1)
{
// So we go ahead and update the display before moving the player
// TODO: The 11 should depend on how much of the player has already
// been displayed. Probably change table to return the amount to
// delay by instead of just 1 (01/21/99).
updateFrame(clock + 11);
myPOSP1 = newx;
// Setup the mask to skip the first copy of the player
myCurrentP1Mask = &ourPlayerMaskTable[myPOSP1 & 0x03]
[1][myNUSIZ1 & 0x07][160 - (myPOSP1 & 0xFC)];
}
// Player is being reset in neither the delay nor display section
else if(when == 0)
{
myPOSP1 = newx;
// So we setup the mask to skip the first copy of the player
myCurrentP1Mask = &ourPlayerMaskTable[myPOSP1 & 0x03]
[1][myNUSIZ1 & 0x07][160 - (myPOSP1 & 0xFC)];
}
// Player is being reset during the delay section of one of its copies
else if(when == -1)
{
myPOSP1 = newx;
// So we setup the mask to display all copies of the player
myCurrentP1Mask = &ourPlayerMaskTable[myPOSP1 & 0x03]
[0][myNUSIZ1 & 0x07][160 - (myPOSP1 & 0xFC)];
}
break;
}
case 0x12: // Reset Missle 0
{
int hpos = (clock - myClockWhenFrameStarted) % 228;
myPOSM0 = hpos < HBLANK ? 2 : (((hpos - HBLANK) + 4) % 160);
// TODO: Remove the following special hack for Dolphin by
// figuring out what really happens when Reset Missle
// occurs 20 cycles after an HMOVE (04/13/02).
if(((clock - myLastHMOVEClock) == (20 * 3)) && (hpos == 69))
{
myPOSM0 = 8;
}
myCurrentM0Mask = &ourMissleMaskTable[myPOSM0 & 0x03]
[myNUSIZ0 & 0x07][(myNUSIZ0 & 0x30) >> 4][160 - (myPOSM0 & 0xFC)];
break;
}
case 0x13: // Reset Missle 1
{
int hpos = (clock - myClockWhenFrameStarted) % 228;
myPOSM1 = hpos < HBLANK ? 2 : (((hpos - HBLANK) + 4) % 160);
// TODO: Remove the following special hack for Pitfall II by
// figuring out what really happens when Reset Missle
// occurs 3 cycles after an HMOVE (04/13/02).
if(((clock - myLastHMOVEClock) == (3 * 3)) && (hpos == 18))
{
myPOSM1 = 3;
}
myCurrentM1Mask = &ourMissleMaskTable[myPOSM1 & 0x03]
[myNUSIZ1 & 0x07][(myNUSIZ1 & 0x30) >> 4][160 - (myPOSM1 & 0xFC)];
break;
}
case 0x14: // Reset Ball
{
int hpos = (clock - myClockWhenFrameStarted) % 228 ;
myPOSBL = hpos < HBLANK ? 2 : (((hpos - HBLANK) + 4) % 160);
// TODO: Remove the following special hack for Escape from the
// Mindmaster by figuring out what really happens when Reset Ball
// occurs 18 cycles after an HMOVE (01/09/99).
if(((clock - myLastHMOVEClock) == (18 * 3)) &&
((hpos == 60) || (hpos == 69)))
{
myPOSBL = 10;
}
// TODO: Remove the following special hack for Decathlon by
// figuring out what really happens when Reset Ball
// occurs 3 cycles after an HMOVE (04/13/02).
else if(((clock - myLastHMOVEClock) == (3 * 3)) && (hpos == 18))
{
myPOSBL = 3;
}
// TODO: Remove the following special hack for Robot Tank by
// figuring out what really happens when Reset Ball
// occurs 7 cycles after an HMOVE (04/13/02).
else if(((clock - myLastHMOVEClock) == (7 * 3)) && (hpos == 30))
{
myPOSBL = 6;
}
// TODO: Remove the following special hack for Hole Hunter by
// figuring out what really happens when Reset Ball
// occurs 6 cycles after an HMOVE (04/13/02).
else if(((clock - myLastHMOVEClock) == (6 * 3)) && (hpos == 27))
{
myPOSBL = 5;
}
myCurrentBLMask = &ourBallMaskTable[myPOSBL & 0x03]
[(myCTRLPF & 0x30) >> 4][160 - (myPOSBL & 0xFC)];
break;
}
case 0x15: // Audio control 0
{
myAUDC0 = value & 0x0f;
mySound->set(addr, value, mySystem->cycles());
break;
}
case 0x16: // Audio control 1
{
myAUDC1 = value & 0x0f;
mySound->set(addr, value, mySystem->cycles());
break;
}
case 0x17: // Audio frequency 0
{
myAUDF0 = value & 0x1f;
mySound->set(addr, value, mySystem->cycles());
break;
}
case 0x18: // Audio frequency 1
{
myAUDF1 = value & 0x1f;
mySound->set(addr, value, mySystem->cycles());
break;
}
case 0x19: // Audio volume 0
{
myAUDV0 = value & 0x0f;
mySound->set(addr, value, mySystem->cycles());
break;
}
case 0x1A: // Audio volume 1
{
myAUDV1 = value & 0x0f;
mySound->set(addr, value, mySystem->cycles());
break;
}
case 0x1B: // Graphics Player 0
{
// Set player 0 graphics
myGRP0 = (myBitEnabled[TIA::P0] ? value : 0);
// Copy player 1 graphics into its delayed register
myDGRP1 = myGRP1;
// Get the "current" data for GRP0 base on delay register and reflect
uInt8 grp0 = myVDELP0 ? myDGRP0 : myGRP0;
myCurrentGRP0 = myREFP0 ? ourPlayerReflectTable[grp0] : grp0;
// Get the "current" data for GRP1 base on delay register and reflect
uInt8 grp1 = myVDELP1 ? myDGRP1 : myGRP1;
myCurrentGRP1 = myREFP1 ? ourPlayerReflectTable[grp1] : grp1;
// Set enabled object bits
if(myCurrentGRP0 != 0)
myEnabledObjects |= myP0Bit;
else
myEnabledObjects &= ~myP0Bit;
if(myCurrentGRP1 != 0)
myEnabledObjects |= myP1Bit;
else
myEnabledObjects &= ~myP1Bit;
break;
}
case 0x1C: // Graphics Player 1
{
// Set player 1 graphics
myGRP1 = (myBitEnabled[TIA::P1] ? value : 0);
// Copy player 0 graphics into its delayed register
myDGRP0 = myGRP0;
// Copy ball graphics into its delayed register
myDENABL = myENABL;
// Get the "current" data for GRP0 base on delay register
uInt8 grp0 = myVDELP0 ? myDGRP0 : myGRP0;
myCurrentGRP0 = myREFP0 ? ourPlayerReflectTable[grp0] : grp0;
// Get the "current" data for GRP1 base on delay register
uInt8 grp1 = myVDELP1 ? myDGRP1 : myGRP1;
myCurrentGRP1 = myREFP1 ? ourPlayerReflectTable[grp1] : grp1;
// Set enabled object bits
if(myCurrentGRP0 != 0)
myEnabledObjects |= myP0Bit;
else
myEnabledObjects &= ~myP0Bit;
if(myCurrentGRP1 != 0)
myEnabledObjects |= myP1Bit;
else
myEnabledObjects &= ~myP1Bit;
if(myVDELBL ? myDENABL : myENABL)
myEnabledObjects |= myBLBit;
else
myEnabledObjects &= ~myBLBit;
break;
}
case 0x1D: // Enable Missile 0 graphics
{
myENAM0 = (myBitEnabled[TIA::M0] ? value & 0x02 : 0);
if(myENAM0 && !myRESMP0)
myEnabledObjects |= myM0Bit;
else
myEnabledObjects &= ~myM0Bit;
break;
}
case 0x1E: // Enable Missile 1 graphics
{
myENAM1 = (myBitEnabled[TIA::M1] ? value & 0x02 : 0);
if(myENAM1 && !myRESMP1)
myEnabledObjects |= myM1Bit;
else
myEnabledObjects &= ~myM1Bit;
break;
}
case 0x1F: // Enable Ball graphics
{
myENABL = (myBitEnabled[TIA::BL] ? value & 0x02 : 0);
if(myVDELBL ? myDENABL : myENABL)
myEnabledObjects |= myBLBit;
else
myEnabledObjects &= ~myBLBit;
break;
}
case 0x20: // Horizontal Motion Player 0
{
myHMP0 = value >> 4;
break;
}
case 0x21: // Horizontal Motion Player 1
{
myHMP1 = value >> 4;
break;
}
case 0x22: // Horizontal Motion Missle 0
{
Int8 tmp = value >> 4;
// Should we enabled TIA M0 "bug" used for stars in Cosmic Ark?
if((clock == (myLastHMOVEClock + 21 * 3)) && (myHMM0 == 7) && (tmp == 6))
{
myM0CosmicArkMotionEnabled = true;
myM0CosmicArkCounter = 0;
}
myHMM0 = tmp;
break;
}
case 0x23: // Horizontal Motion Missle 1
{
myHMM1 = value >> 4;
break;
}
case 0x24: // Horizontal Motion Ball
{
myHMBL = value >> 4;
break;
}
case 0x25: // Vertial Delay Player 0
{
myVDELP0 = value & 0x01;
uInt8 grp0 = myVDELP0 ? myDGRP0 : myGRP0;
myCurrentGRP0 = myREFP0 ? ourPlayerReflectTable[grp0] : grp0;
if(myCurrentGRP0 != 0)
myEnabledObjects |= myP0Bit;
else
myEnabledObjects &= ~myP0Bit;
break;
}
case 0x26: // Vertial Delay Player 1
{
myVDELP1 = value & 0x01;
uInt8 grp1 = myVDELP1 ? myDGRP1 : myGRP1;
myCurrentGRP1 = myREFP1 ? ourPlayerReflectTable[grp1] : grp1;
if(myCurrentGRP1 != 0)
myEnabledObjects |= myP1Bit;
else
myEnabledObjects &= ~myP1Bit;
break;
}
case 0x27: // Vertial Delay Ball
{
myVDELBL = value & 0x01;
if(myVDELBL ? myDENABL : myENABL)
myEnabledObjects |= myBLBit;
else
myEnabledObjects &= ~myBLBit;
break;
}
case 0x28: // Reset missle 0 to player 0
{
if(myRESMP0 && !(value & 0x02))
{
uInt16 middle;
if((myNUSIZ0 & 0x07) == 0x05)
middle = 8;
else if((myNUSIZ0 & 0x07) == 0x07)
middle = 16;
else
middle = 4;
myPOSM0 = (myPOSP0 + middle) % 160;
myCurrentM0Mask = &ourMissleMaskTable[myPOSM0 & 0x03]
[myNUSIZ0 & 0x07][(myNUSIZ0 & 0x30) >> 4][160 - (myPOSM0 & 0xFC)];
}
myRESMP0 = value & 0x02;
if(myENAM0 && !myRESMP0)
myEnabledObjects |= myM0Bit;
else
myEnabledObjects &= ~myM0Bit;
break;
}
case 0x29: // Reset missle 1 to player 1
{
if(myRESMP1 && !(value & 0x02))
{
uInt16 middle;
if((myNUSIZ1 & 0x07) == 0x05)
middle = 8;
else if((myNUSIZ1 & 0x07) == 0x07)
middle = 16;
else
middle = 4;
myPOSM1 = (myPOSP1 + middle) % 160;
myCurrentM1Mask = &ourMissleMaskTable[myPOSM1 & 0x03]
[myNUSIZ1 & 0x07][(myNUSIZ1 & 0x30) >> 4][160 - (myPOSM1 & 0xFC)];
}
myRESMP1 = value & 0x02;
if(myENAM1 && !myRESMP1)
myEnabledObjects |= myM1Bit;
else
myEnabledObjects &= ~myM1Bit;
break;
}
case 0x2A: // Apply horizontal motion
{
// Figure out what cycle we're at
Int32 x = ((clock - myClockWhenFrameStarted) % 228) / 3;
// See if we need to enable the HMOVE blank bug
if(myAllowHMOVEBlanks && ourHMOVEBlankEnableCycles[x])
{
// TODO: Allow this to be turned off using properties...
myHMOVEBlankEnabled = true;
}
myPOSP0 += ourCompleteMotionTable[x][myHMP0];
myPOSP1 += ourCompleteMotionTable[x][myHMP1];
myPOSM0 += ourCompleteMotionTable[x][myHMM0];
myPOSM1 += ourCompleteMotionTable[x][myHMM1];
myPOSBL += ourCompleteMotionTable[x][myHMBL];
if(myPOSP0 >= 160)
myPOSP0 -= 160;
else if(myPOSP0 < 0)
myPOSP0 += 160;
if(myPOSP1 >= 160)
myPOSP1 -= 160;
else if(myPOSP1 < 0)
myPOSP1 += 160;
if(myPOSM0 >= 160)
myPOSM0 -= 160;
else if(myPOSM0 < 0)
myPOSM0 += 160;
if(myPOSM1 >= 160)
myPOSM1 -= 160;
else if(myPOSM1 < 0)
myPOSM1 += 160;
if(myPOSBL >= 160)
myPOSBL -= 160;
else if(myPOSBL < 0)
myPOSBL += 160;
myCurrentBLMask = &ourBallMaskTable[myPOSBL & 0x03]
[(myCTRLPF & 0x30) >> 4][160 - (myPOSBL & 0xFC)];
myCurrentP0Mask = &ourPlayerMaskTable[myPOSP0 & 0x03]
[0][myNUSIZ0 & 0x07][160 - (myPOSP0 & 0xFC)];
myCurrentP1Mask = &ourPlayerMaskTable[myPOSP1 & 0x03]
[0][myNUSIZ1 & 0x07][160 - (myPOSP1 & 0xFC)];
myCurrentM0Mask = &ourMissleMaskTable[myPOSM0 & 0x03]
[myNUSIZ0 & 0x07][(myNUSIZ0 & 0x30) >> 4][160 - (myPOSM0 & 0xFC)];
myCurrentM1Mask = &ourMissleMaskTable[myPOSM1 & 0x03]
[myNUSIZ1 & 0x07][(myNUSIZ1 & 0x30) >> 4][160 - (myPOSM1 & 0xFC)];
// Remember what clock HMOVE occured at
myLastHMOVEClock = clock;
// Disable TIA M0 "bug" used for stars in Cosmic ark
myM0CosmicArkMotionEnabled = false;
break;
}
case 0x2b: // Clear horizontal motion registers
{
myHMP0 = 0;
myHMP1 = 0;
myHMM0 = 0;
myHMM1 = 0;
myHMBL = 0;
break;
}
case 0x2c: // Clear collision latches
{
myCollision = 0;
break;
}
default:
{
#ifdef DEBUG_ACCESSES
ale::Logger::Info << "BAD TIA Poke: " << hex << addr << endl;
#endif
break;
}
}
}