diff --git a/help/CommandLine.html b/help/CommandLine.html
index 1c09c7b2..0d6870aa 100644
--- a/help/CommandLine.html
+++ b/help/CommandLine.html
@@ -109,7 +109,8 @@
Use Right Alt (AltGr) & Right Control for Open Apple & Solid Apple keys respectively.
Caveat: Right Control + F2 will do the //e self test (as Right Control is now both Ctrl and Solid Apple!). A workaround is just to use the Left Control key.
-swap-buttons
- Swap the Windows keys used for Open Apple & Solid Apple keys.
+ Swap buttons 0 and 1 from all input devices.
+ EG. the Windows keys used for Open Apple & Solid Apple keys, and the current device being used to emulate a joystick (keyboard, real joystick or mouse)
-use-real-printer
Enables Advanced configuration control to allow dumping to a real printer
diff --git a/help/cfg-input.html b/help/cfg-input.html
index 854c811d..93ff7f64 100644
--- a/help/cfg-input.html
+++ b/help/cfg-input.html
@@ -33,7 +33,7 @@ then you should leave these values at 0.
When cursor keys are used for joystick emulation and are allowed to be read from the keyboard, then some games won't work correctly (eg. Lode Runner).
When cursor keys are blocked from being read from the keyboard, then simple command-line cursor editing in AppleSoft won't work.
- Swap 0/1: Swap buttons 0 and 1.
+ Swap 0/1: Swap buttons 0 and 1 from all input devices.
Auto-fire (all 3 buttons): For each button pressed, the button's state will be toggled when read.
Keyboard auto-centering: When keys used for joystick emulation are released then the joystick will return to the central position.
diff --git a/source/Disk.cpp b/source/Disk.cpp
index 7de67282..df476444 100644
--- a/source/Disk.cpp
+++ b/source/Disk.cpp
@@ -54,6 +54,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// . if false && I/O ReadWrite($C0EC) && drive is spinning, then advance the track buffer's nibble index (to simulate spinning).
// Also m_enhanceDisk is persisted to the save-state, so it's an attribute of the DiskII interface card.
+// NB. Non-standard 4&4, with Vol=0x00 and Chk=0x00 (only a few match, eg. Wasteland, Legacy of the Ancients, Planetfall, Border Zone & Wizardry). [*1]
+const BYTE Disk2InterfaceCard::m_T00S00Pattern[] = {0xD5,0xAA,0x96,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xDE};
+
Disk2InterfaceCard::Disk2InterfaceCard(UINT slot) :
Card(CT_Disk2),
m_slot(slot)
@@ -1043,6 +1046,9 @@ void Disk2InterfaceCard::ResetLogicStateSequencer(void)
m_resetSequencer = true;
m_writeStarted = false;
m_dbgLatchDelayedCnt = 0;
+
+ m_T00S00PatternIdx = 0;
+ m_foundT00S00Pattern = false;
}
UINT Disk2InterfaceCard::GetBitCellDelta(const ULONG uExecutedCycles)
@@ -1118,6 +1124,48 @@ __forceinline void Disk2InterfaceCard::IncBitStream(FloppyDisk& floppy)
}
}
+void Disk2InterfaceCard::PreJitterCheck(int phase, BYTE latch)
+{
+ if (phase != 0 || (latch & 0x80) == 0)
+ return;
+
+ if (latch == m_T00S00Pattern[m_T00S00PatternIdx])
+ {
+ m_T00S00PatternIdx++;
+ if (m_T00S00PatternIdx == sizeof(m_T00S00Pattern))
+ m_foundT00S00Pattern = true; // 6502 code has just read latch nibbles for T$00,S$00 address prologue
+ }
+ else
+ {
+ m_T00S00PatternIdx = 0;
+ }
+}
+
+// GH#930: After T$00,S$00 randomly skip 1 bit-cell.
+// . PreJitterCheck() condition met && skipped a big number of bit-cells.
+// . Fix is just for 'Wasteland' and 'Legacy of the Ancients' (but shouldn't interfere with any other woz images).
+// . NB. This is likely to be the transition from DiskII firmware ($C6xx) to user-code ($801),
+// so skipping 1 bit-cell here shouldn't matter.
+// . And (see comment [*1]) the T00S00 pattern only matches a handful of titles.
+void Disk2InterfaceCard::AddJitter(int phase, FloppyDisk& floppy)
+{
+ if (phase == 0 && m_foundT00S00Pattern)
+ {
+ if (rand() < RAND_THRESHOLD(1, 10))
+ {
+ LogOutput("Disk: T$00 jitter - slip 1 bitcell (PC=%04X)\n", regs.pc);
+ IncBitStream(floppy);
+ }
+ else
+ {
+ LogOutput("Disk: T$00 jitter - *** SKIP *** (PC=%04X)\n", regs.pc);
+ }
+ }
+
+ m_T00S00PatternIdx = 0;
+ m_foundT00S00Pattern = false;
+}
+
void __stdcall Disk2InterfaceCard::DataLatchReadWriteWOZ(WORD pc, WORD addr, BYTE bWrite, ULONG uExecutedCycles)
{
_ASSERT(m_seqFunc.function != dataShiftWrite);
@@ -1161,6 +1209,8 @@ void __stdcall Disk2InterfaceCard::DataLatchReadWriteWOZ(WORD pc, WORD addr, BYT
m_latchDelay = 0;
drive.m_headWindow = 0;
+
+ AddJitter(drive.m_phase, floppy); // Only call when skipping a big number of bit-cells (ie. >significantBitCells)
}
if (!bWrite)
@@ -1173,6 +1223,8 @@ void __stdcall Disk2InterfaceCard::DataLatchReadWriteWOZ(WORD pc, WORD addr, BYT
}
DataLatchReadWOZ(pc, addr, bitCellRemainder);
+
+ PreJitterCheck(drive.m_phase, m_floppyLatch); // Pre: m_floppyLatch just updated
}
else
{
@@ -1198,7 +1250,6 @@ void Disk2InterfaceCard::DataLatchReadWOZ(WORD pc, WORD addr, UINT bitCellRemain
#if _DEBUG
static int dbgWOZ = 0;
-
if (dbgWOZ)
{
dbgWOZ = 0;
@@ -1206,9 +1257,6 @@ void Disk2InterfaceCard::DataLatchReadWOZ(WORD pc, WORD addr, UINT bitCellRemain
}
#endif
- // Only extraCycles of 2 & 3 can hold the latch for another bitCell period, eg. m_latchDelay: 3->5 or 7->9
- UINT extraLatchDelay = ((UINT)floppy.m_extraCycles >= 2) ? 2 : 0; // GH#733 (0,1->0; 2,3->2)
-
for (UINT i = 0; i < bitCellRemainder; i++)
{
BYTE n = floppy.m_trackimage[floppy.m_byte];
@@ -1233,10 +1281,6 @@ void Disk2InterfaceCard::DataLatchReadWOZ(WORD pc, WORD addr, UINT bitCellRemain
if (m_latchDelay)
{
- if (i == bitCellRemainder-1) // On last bitCell
- m_latchDelay += extraLatchDelay; // +0 or +2
- extraLatchDelay = 0; // and always clear (even when not last bitCell)
-
m_latchDelay -= 4;
if (m_latchDelay < 0)
m_latchDelay = 0;
diff --git a/source/Disk.h b/source/Disk.h
index baaf5421..24612c19 100644
--- a/source/Disk.h
+++ b/source/Disk.h
@@ -207,6 +207,9 @@ private:
void InitFirmware(LPBYTE pCxRomPeripheral);
void UpdateLatchForEmptyDrive(FloppyDrive* pDrive);
+ void PreJitterCheck(int phase, BYTE latch);
+ void AddJitter(int phase, FloppyDisk& floppy);
+
void SaveSnapshotFloppy(YamlSaveHelper& yamlSaveHelper, UINT unit);
void SaveSnapshotDriveUnit(YamlSaveHelper& yamlSaveHelper, UINT unit);
bool LoadSnapshotFloppy(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track);
@@ -274,6 +277,11 @@ private:
SEQUENCER_FUNCTION m_seqFunc;
UINT m_dbgLatchDelayedCnt;
+ // Jitter (GH#930)
+ static const BYTE m_T00S00Pattern[];
+ UINT m_T00S00PatternIdx;
+ bool m_foundT00S00Pattern;
+
// Debug:
#if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
bool m_bLogDisk_NibblesRW; // From VS Debugger, change this to true/false during runtime for precise nibble logging
diff --git a/source/Joystick.cpp b/source/Joystick.cpp
index 192ef4fa..69cb5247 100644
--- a/source/Joystick.cpp
+++ b/source/Joystick.cpp
@@ -345,14 +345,13 @@ BOOL JoyProcessKey(int virtkey, bool extended, bool down, bool autorep)
BOOL keychange = 0;
bool bIsCursorKey = false;
- const bool swapButtons0and1 = GetPropertySheet().GetButtonsSwapState();
- if (virtKeyWithExtended == g_buttonVirtKey[!swapButtons0and1 ? 0 : 1])
+ if (virtKeyWithExtended == g_buttonVirtKey[0])
{
keychange = 1;
keydown[JK_OPENAPPLE] = down;
}
- else if (virtKeyWithExtended == g_buttonVirtKey[!swapButtons0and1 ? 1 : 0])
+ else if (virtKeyWithExtended == g_buttonVirtKey[1])
{
keychange = 1;
keydown[JK_CLOSEDAPPLE] = down;
@@ -367,7 +366,7 @@ BOOL JoyProcessKey(int virtkey, bool extended, bool down, bool autorep)
{
keydown[virtkey-VK_NUMPAD1] = down;
}
- else // NumLock off
+ else // NumLock off (except for '0' and '.')
{
switch (virtkey)
{
@@ -380,8 +379,10 @@ BOOL JoyProcessKey(int virtkey, bool extended, bool down, bool autorep)
case VK_HOME: keydown[JK_UPLEFT] = down; break;
case VK_UP: keydown[JK_UP] = down; break;
case VK_PRIOR: keydown[JK_UPRIGHT] = down; break;
- case VK_NUMPAD0: keydown[JK_BUTTON0] = down; break;
- case VK_DECIMAL: keydown[JK_BUTTON1] = down; break;
+ case VK_INSERT: // fall through... (NB. extended=0 for NumPad's Insert)
+ case VK_NUMPAD0: keydown[JK_BUTTON0] = down; break; // NumLock on
+ case VK_DELETE: // fall through... (NB. extended=0 for NumPad's Delete)
+ case VK_DECIMAL: keydown[JK_BUTTON1] = down; break; // NumLock on
default: keychange = 0; break;
}
}
@@ -502,8 +503,8 @@ BOOL JoyProcessKey(int virtkey, bool extended, bool down, bool autorep)
static void DoAutofire(UINT uButton, BOOL& pressed)
{
- static BOOL toggle[3] = {0};
- static BOOL lastPressed[3] = {0};
+ static BOOL toggle[3] = {0,0,0};
+ static BOOL lastPressed[3] = {0,0,0};
BOOL nowPressed = pressed;
if (GetPropertySheet().GetAutofire(uButton) && pressed)
@@ -565,6 +566,32 @@ BYTE __stdcall JoyportReadButton(WORD address, ULONG nExecutedCycles)
return MemReadFloatingBus(pressed, nExecutedCycles);
}
+static BOOL CheckButton0Pressed(void)
+{
+ BOOL pressed = buttonlatch[0] ||
+ joybutton[0] ||
+ setbutton[0] ||
+ keydown[JK_OPENAPPLE];
+
+ if (joyinfo[joytype[1]] != DEVICE_KEYBOARD) // NB. always joytype[1] regardless if button is 0 or 1
+ pressed = pressed || keydown[JK_BUTTON0];
+
+ return pressed;
+}
+
+static BOOL CheckButton1Pressed(void)
+{
+ BOOL pressed = buttonlatch[1] ||
+ joybutton[1] ||
+ setbutton[1] ||
+ keydown[JK_CLOSEDAPPLE];
+
+ if (joyinfo[joytype[1]] != DEVICE_KEYBOARD) // NB. always joytype[1] regardless if button is 0 or 1
+ pressed = pressed || keydown[JK_BUTTON1];
+
+ return pressed;
+}
+
BYTE __stdcall JoyReadButton(WORD pc, WORD address, BYTE, BYTE, ULONG nExecutedCycles)
{
address &= 0xFF;
@@ -581,23 +608,27 @@ BYTE __stdcall JoyReadButton(WORD pc, WORD address, BYTE, BYTE, ULONG nExecutedC
return JoyportReadButton(address, nExecutedCycles);
}
- BOOL pressed = 0;
+ const bool swapButtons0and1 = GetPropertySheet().GetButtonsSwapState();
+
+ BOOL pressed = FALSE;
switch (address)
{
case 0x61:
- pressed = (buttonlatch[0] || joybutton[0] || setbutton[0] || keydown[JK_OPENAPPLE]);
- if(joyinfo[joytype[1]] != DEVICE_KEYBOARD) // BUG? joytype[1] should be [0] ?
- pressed = (pressed || keydown[JK_BUTTON0]);
- buttonlatch[0] = 0;
- DoAutofire(0, pressed);
+ {
+ pressed = !swapButtons0and1 ? CheckButton0Pressed() : CheckButton1Pressed();
+ const UINT button0 = !swapButtons0and1 ? 0 : 1;
+ buttonlatch[button0] = 0;
+ DoAutofire(button0, pressed);
+ }
break;
case 0x62:
- pressed = (buttonlatch[1] || joybutton[1] || setbutton[1] || keydown[JK_CLOSEDAPPLE]);
- if(joyinfo[joytype[1]] != DEVICE_KEYBOARD)
- pressed = (pressed || keydown[JK_BUTTON1]);
- buttonlatch[1] = 0;
- DoAutofire(1, pressed);
+ {
+ pressed = !swapButtons0and1 ? CheckButton1Pressed() : CheckButton0Pressed();
+ const UINT button1 = !swapButtons0and1 ? 1 : 0;
+ buttonlatch[button1] = 0;
+ DoAutofire(button1, pressed);
+ }
break;
case 0x63:
diff --git a/source/Mockingboard.cpp b/source/Mockingboard.cpp
index 361cdf9c..b99c692d 100644
--- a/source/Mockingboard.cpp
+++ b/source/Mockingboard.cpp
@@ -195,9 +195,6 @@ static const SHORT nWaveDataMax = (SHORT)0x7FFF;
static short g_nMixBuffer[g_dwDSBufferSize / sizeof(short)];
static VOICE MockingboardVoice;
-static bool g_bCritSectionValid = false; // Deleting CritialSection when not valid causes crash on Win98
-static CRITICAL_SECTION g_CriticalSection; // To guard 6522's IFR
-
static UINT g_cyclesThisAudioFrame = 0;
//---------------------------------------------------------------------------
@@ -577,19 +574,13 @@ static USHORT SetTimerSyncEvent(UINT id, BYTE reg, USHORT timerLatch)
static void UpdateIFR(SY6522_AY8910* pMB, BYTE clr_ifr, BYTE set_ifr=0)
{
- // Need critical section to avoid data-race: main thread & SSI263Thread can both access IFR -- no longer a SSI263Thread
- // . NB. Loading a save-state just directly writes into 6522.IFR (which is fine)
- if (g_bCritSectionValid) EnterCriticalSection(&g_CriticalSection);
- {
- pMB->sy6522.IFR &= ~clr_ifr;
- pMB->sy6522.IFR |= set_ifr;
+ pMB->sy6522.IFR &= ~clr_ifr;
+ pMB->sy6522.IFR |= set_ifr;
- if (pMB->sy6522.IFR & pMB->sy6522.IER & 0x7F)
- pMB->sy6522.IFR |= 0x80;
- else
- pMB->sy6522.IFR &= 0x7F;
- }
- if (g_bCritSectionValid) LeaveCriticalSection(&g_CriticalSection);
+ if (pMB->sy6522.IFR & pMB->sy6522.IER & 0x7F)
+ pMB->sy6522.IFR |= 0x80;
+ else
+ pMB->sy6522.IFR &= 0x7F;
// Now update the IRQ signal from all 6522s
// . OR-sum of all active TIMER1, TIMER2 & SPEECH sources (from all 6522s)
@@ -1140,9 +1131,6 @@ void MB_Initialize()
LogFileOutput("MB_Initialize: MB_Reset()\n");
}
- InitializeCriticalSection(&g_CriticalSection);
- g_bCritSectionValid = true;
-
for (int id=0; idm_active)