/* AppleWin : An Apple //e emulator for Windows Copyright (C) 1994-1996, Michael O'Brien Copyright (C) 1999-2001, Oliver Schmidt Copyright (C) 2002-2005, Tom Charlesworth Copyright (C) 2006-2007, Tom Charlesworth, Michael Pohoreski AppleWin is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. AppleWin is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with AppleWin; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Description: Joystick emulation via keyboard, PC joystick or mouse * * Author: Various */ // TC: Extended for 2nd joystick: // Apple joystick #0 can be emulated with: NONE, JOYSTICKID1, KEYBOARD, MOUSE // Apple joystick #1 can be emulated with: NONE, JOYSTICKID2, KEYBOARD, MOUSE // If Apple joystick #0 is using {KEYBOARD | MOUSE} then joystick #1 can't use it. // If Apple joystick #1 is using KEYBOARD, then disable the standard keys that control Apple switches #0/#1. // - So that in a 2 player game, player 2 can't cheat by controlling player 1's buttons. // If Apple joystick #1 is not NONE, then Apple joystick #0 only gets the use of Apple switch #0. // - When using 2 joysticks, button #1 is used by joystick #1 (Archon). // Apple joystick #1's button now controls Apple switches #1 and #2. // - This is because the 2-joystick version of Mario Bros expects the 2nd joystick to control Apple switch #2. #include "StdAfx.h" #include "Joystick.h" #include "CPU.h" #include "Memory.h" #include "YamlHelper.h" #include "Interface.h" #include "CopyProtectionDongles.h" enum {DEVICE_NONE=0, DEVICE_JOYSTICK, DEVICE_KEYBOARD, DEVICE_MOUSE, DEVICE_JOYSTICK_THUMBSTICK2}; // Indexed by joytype[n] static const uint32_t joyinfo[6] = { DEVICE_NONE, DEVICE_JOYSTICK, DEVICE_KEYBOARD, // Cursors (prev: Numpad-Standard) DEVICE_KEYBOARD, // Numpad (prev: Numpad-Centering) DEVICE_MOUSE, DEVICE_JOYSTICK_THUMBSTICK2 }; // Key pad [1..9]; Key pad 0,Key pad '.'; Left ALT,Right ALT enum JOYKEY { JK_DOWNLEFT=0, JK_DOWN, JK_DOWNRIGHT, JK_LEFT, JK_CENTRE, JK_RIGHT, JK_UPLEFT, JK_UP, JK_UPRIGHT, JK_BUTTON0, JK_BUTTON1, JK_OPENAPPLE, JK_CLOSEDAPPLE, JK_MAX }; const UINT PDL_MIN = 0; const UINT PDL_CENTRAL = 127; const UINT PDL_MAX = 255; static BOOL keydown[JK_MAX] = {FALSE}; static POINT keyvalue[9] = {{PDL_MIN,PDL_MAX}, {PDL_CENTRAL,PDL_MAX}, {PDL_MAX,PDL_MAX}, {PDL_MIN,PDL_CENTRAL},{PDL_CENTRAL,PDL_CENTRAL},{PDL_MAX,PDL_CENTRAL}, {PDL_MIN,PDL_MIN}, {PDL_CENTRAL,PDL_MIN}, {PDL_MAX,PDL_MIN}}; static BOOL joybutton[3] = {0,0,0}; static int joyshrx[2] = {8,8}; static int joyshry[2] = {8,8}; static int joysubx[2] = {0,0}; static int joysuby[2] = {0,0}; // Value persisted to Registry for REGVALUE_JOYSTICK0_EMU_TYPE static uint32_t joytype[2] = {J0C_JOYSTICK1, J1C_DISABLED}; // Emulation Type for joysticks #0 & #1 static BOOL setbutton[3] = {0,0,0}; // Used when a mouse button is pressed/released static int xpos[2] = { PDL_MAX,PDL_MAX }; static int ypos[2] = { PDL_MAX,PDL_MAX }; static UINT64 g_paddleInactiveCycle[4] = { 0 }; // Abs cycle that each paddle becomes inactive after PTRIG strobe static short g_nPdlTrimX = 0; static short g_nPdlTrimY = 0; enum {JOYPORT_LEFTRIGHT=0, JOYPORT_UPDOWN=1}; static UINT g_bJoyportEnabled = 0; // Set to use Joyport to drive the 3 button inputs static UINT g_uJoyportActiveStick = 0; static UINT g_uJoyportReadMode = JOYPORT_LEFTRIGHT; static bool g_bHookAltKeys = true; static int JOYSTICK_1 = -1; static int JOYSTICK_2 = -1; //=========================================================================== void JoySetHookAltKeys(bool hook) { g_bHookAltKeys = hook; } int GetJoystick1(void) { return JOYSTICK_1; } int GetJoystick2(void) { return JOYSTICK_2; } //=========================================================================== static void CheckJoystick0() { if (JOYSTICK_1 < 0) return; static uint32_t lastcheck = 0; uint32_t currtime = GetTickCount(); if ((currtime-lastcheck >= 10) || joybutton[0] || joybutton[1]) { lastcheck = currtime; JOYINFO info; if (joyGetPos(JOYSTICK_1,&info) == JOYERR_NOERROR) { joybutton[0] = ((info.wButtons & JOY_BUTTON1) != 0); if (joyinfo[joytype[1]] == DEVICE_NONE) // Only consider 2nd button if NOT emulating a 2nd Apple joystick joybutton[1] = ((info.wButtons & JOY_BUTTON2) != 0); xpos[0] = (info.wXpos-joysubx[0]) >> joyshrx[0]; ypos[0] = (info.wYpos-joysuby[0]) >> joyshry[0]; // NB. This does not work for analogue joysticks (not self-centreing) - except if Trim=0 if(xpos[0] == 127 || xpos[0] == 128) xpos[0] += g_nPdlTrimX; if(ypos[0] == 127 || ypos[0] == 128) ypos[0] += g_nPdlTrimY; } } } static void CheckJoystick1() { static uint32_t lastcheck = 0; uint32_t currtime = GetTickCount(); if ((currtime-lastcheck >= 10) || joybutton[2]) { lastcheck = currtime; JOYINFO info; MMRESULT result = JOYERR_NOERROR; if (joyinfo[joytype[1]] == DEVICE_JOYSTICK_THUMBSTICK2) { // Use results of joystick 1 thumbstick 2 and button 2 for joystick 1 and button 1 JOYINFOEX infoEx; infoEx.dwSize = sizeof(infoEx); infoEx.dwFlags = JOY_RETURNBUTTONS | JOY_RETURNZ | JOY_RETURNR; result = joyGetPosEx(JOYSTICK_1, &infoEx); if (result == JOYERR_NOERROR) { info.wButtons = (infoEx.dwButtons & JOY_BUTTON2) ? JOY_BUTTON1 : 0; info.wXpos = infoEx.dwZpos; info.wYpos = infoEx.dwRpos; } } else { result = joyGetPos(JOYSTICK_2, &info); // NB. joyGetPos(-1, &info) returns JOYERR_PARMS (bad parameters) } if (result == JOYERR_NOERROR) { joybutton[2] = ((info.wButtons & JOY_BUTTON1) != 0); if(joyinfo[joytype[1]] != DEVICE_NONE) joybutton[1] = ((info.wButtons & JOY_BUTTON1) != 0); // Re-map this button when emulating a 2nd Apple joystick xpos[1] = (info.wXpos-joysubx[1]) >> joyshrx[1]; ypos[1] = (info.wYpos-joysuby[1]) >> joyshry[1]; // NB. This does not work for analogue joysticks (not self-centreing) - except if Trim=0 if(xpos[1] == 127 || xpos[1] == 128) xpos[1] += g_nPdlTrimX; if(ypos[1] == 127 || ypos[1] == 128) ypos[1] += g_nPdlTrimY; } } } // // ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE ----- // //=========================================================================== void JoyInitialize() { // // Detect First and Second connected JOYSTICK in WinMM API. JOYSTICKID1 == 0 but is not always the first connected joystick. // JOYSTICK_1 = -1; JOYSTICK_2 = -1; bool firstFound = false; const UINT numDevs = joyGetNumDevs(); for (UINT i = 0; i < numDevs; i++) { JOYCAPS caps; int ret = joyGetDevCaps(i, &caps, sizeof(JOYCAPS)); if (ret != JOYERR_NOERROR) continue; JOYINFO info; ret = joyGetPos(i, &info); if (ret != JOYERR_NOERROR) continue; if (firstFound) { JOYSTICK_2 = i; break; } JOYSTICK_1 = i; firstFound = true; } // // Init for emulated joystick #0: // if (joyinfo[joytype[0]] == DEVICE_JOYSTICK) { JOYCAPS caps; if (JOYSTICK_1 >= 0 && joyGetDevCaps(JOYSTICK_1, &caps, sizeof(JOYCAPS)) == JOYERR_NOERROR) { joyshrx[0] = 0; joyshry[0] = 0; joysubx[0] = (int)caps.wXmin; joysuby[0] = (int)caps.wYmin; UINT xrange = caps.wXmax - caps.wXmin; UINT yrange = caps.wYmax - caps.wYmin; while (xrange > 256) { xrange >>= 1; ++joyshrx[0]; } while (yrange > 256) { yrange >>= 1; ++joyshry[0]; } } else { joytype[0] = J0C_KEYBD_NUMPAD; } } // // Init for emulated joystick #1: // if (joyinfo[joytype[1]] == DEVICE_JOYSTICK) { JOYCAPS caps; if (JOYSTICK_2 >= 0 && joyGetDevCaps(JOYSTICK_2, &caps, sizeof(JOYCAPS)) == JOYERR_NOERROR) { joyshrx[1] = 0; joyshry[1] = 0; joysubx[1] = (int)caps.wXmin; joysuby[1] = (int)caps.wYmin; UINT xrange = caps.wXmax - caps.wXmin; UINT yrange = caps.wYmax - caps.wYmin; while (xrange > 256) { xrange >>= 1; ++joyshrx[1]; } while (yrange > 256) { yrange >>= 1; ++joyshry[1]; } } else { joytype[1] = J1C_DISABLED; } } else if (joyinfo[joytype[1]] == DEVICE_JOYSTICK_THUMBSTICK2) { JOYCAPS caps; if (JOYSTICK_1 >= 0 && joyGetDevCaps(JOYSTICK_1, &caps, sizeof(JOYCAPS)) == JOYERR_NOERROR) { joyshrx[1] = 0; joyshry[1] = 0; joysubx[1] = (int)caps.wZmin; joysuby[1] = (int)caps.wRmin; UINT xrange = caps.wZmax - caps.wZmin; UINT yrange = caps.wRmax - caps.wRmin; while (xrange > 256) { xrange >>= 1; ++joyshrx[1]; } while (yrange > 256) { yrange >>= 1; ++joyshry[1]; } } else { joytype[1] = J1C_DISABLED; } } } //=========================================================================== static UINT g_buttonVirtKey[2] = { VK_MENU, VK_MENU | KF_EXTENDED }; // VK_MENU == ALT Key void JoySetButtonVirtualKey(UINT button, UINT virtKey) { _ASSERT(button < 2); if (button >= 2) return; g_buttonVirtKey[button] = virtKey; } //=========================================================================== #define SUPPORT_CURSOR_KEYS BOOL JoyProcessKey(int virtkey, bool extended, bool down, bool autorep) { static struct { UINT32 Left:1; UINT32 Up:1; UINT32 Right:1; UINT32 Down:1; } CursorKeys = {0}; const UINT virtKeyWithExtended = ((UINT)virtkey) | (extended ? KF_EXTENDED : 0); if ( (joyinfo[joytype[0]] != DEVICE_KEYBOARD) && (joyinfo[joytype[1]] != DEVICE_KEYBOARD) && (virtKeyWithExtended != g_buttonVirtKey[0]) && (virtKeyWithExtended != g_buttonVirtKey[1]) ) { return 0; } if (!g_bHookAltKeys && virtkey == VK_MENU) // GH#583 return 0; // BOOL keychange = 0; bool bIsCursorKey = false; if (virtKeyWithExtended == g_buttonVirtKey[0]) { keychange = 1; keydown[JK_OPENAPPLE] = down; } else if (virtKeyWithExtended == g_buttonVirtKey[1]) { keychange = 1; keydown[JK_CLOSEDAPPLE] = down; } else if (!extended) { if (JoyUsingKeyboardNumpad()) { keychange = 1; if ((virtkey >= VK_NUMPAD1) && (virtkey <= VK_NUMPAD9)) // NumLock on { keydown[virtkey-VK_NUMPAD1] = down; } else // NumLock off (except for '0' and '.') { switch (virtkey) { case VK_END: keydown[JK_DOWNLEFT] = down; break; case VK_DOWN: keydown[JK_DOWN] = down; break; case VK_NEXT: keydown[JK_DOWNRIGHT] = down; break; case VK_LEFT: keydown[JK_LEFT] = down; break; case VK_CLEAR: keydown[JK_CENTRE] = down; break; case VK_RIGHT: keydown[JK_RIGHT] = down; break; 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_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; } } } } #ifdef SUPPORT_CURSOR_KEYS else if (extended) { if (JoyUsingKeyboardCursors() && (virtkey == VK_LEFT || virtkey == VK_UP || virtkey == VK_RIGHT || virtkey == VK_DOWN)) { keychange = 1; // This prevents cursors keys being available to the Apple II (eg. Lode Runner uses cursor left/right for game speed & Ctrl-J/K for joystick/keyboard) bIsCursorKey = true; switch (virtkey) { case VK_LEFT: CursorKeys.Left = down; break; case VK_UP: CursorKeys.Up = down; break; case VK_RIGHT: CursorKeys.Right = down; break; case VK_DOWN: CursorKeys.Down = down; break; } } } #endif if (!keychange) return 0; // if ((down && !autorep) || (GetPropertySheet().GetJoystickCenteringControl() == JOYSTICK_MODE_CENTERING)) { int xkeys = 0; int ykeys = 0; int xtotal = 0; int ytotal = 0; for (int keynum = JK_DOWNLEFT; keynum <= JK_UPRIGHT; keynum++) { if (keydown[keynum]) { if ((keynum % 3) != 1) // Not middle col (ie. not VK_DOWN, VK_CLEAR, VK_UP) { xkeys++; xtotal += keyvalue[keynum].x; } if ((keynum / 3) != 1) // Not middle row (ie. not VK_LEFT, VK_CLEAR, VK_RIGHT) { ykeys++; ytotal += keyvalue[keynum].y; } } } if (CursorKeys.Left) { xkeys++; xtotal += keyvalue[JK_LEFT].x; } if (CursorKeys.Right) { xkeys++; xtotal += keyvalue[JK_RIGHT].x; } if (CursorKeys.Up) { ykeys++; ytotal += keyvalue[JK_UP].y; } if (CursorKeys.Down) { ykeys++; ytotal += keyvalue[JK_DOWN].y; } // Joystick # which is being emulated by keyboard int nJoyNum = (joyinfo[joytype[0]] == DEVICE_KEYBOARD) ? 0 : 1; if (xkeys) xpos[nJoyNum] = xtotal / xkeys; else xpos[nJoyNum] = PDL_CENTRAL + g_nPdlTrimX; if (ykeys) ypos[nJoyNum] = ytotal / ykeys; else ypos[nJoyNum] = PDL_CENTRAL + g_nPdlTrimY; } if (bIsCursorKey && GetPropertySheet().GetJoystickCursorControl()) { // Allow AppleII keyboard to see this cursor keypress too return 0; } return 1; } //=========================================================================== static void DoAutofire(UINT uButton, BOOL& pressed) { static BOOL toggle[3] = {0,0,0}; static BOOL lastPressed[3] = {0,0,0}; BOOL nowPressed = pressed; if (GetPropertySheet().GetAutofire(uButton) && pressed) { toggle[uButton] = (!lastPressed[uButton]) ? TRUE : !toggle[uButton]; pressed = pressed && toggle[uButton]; } lastPressed[uButton] = nowPressed; } BYTE __stdcall JoyportReadButton(WORD address, ULONG nExecutedCycles) { BOOL pressed = 0; if (g_uJoyportActiveStick == 0) { switch (address) { case 0x61: pressed = (joybutton[0] || setbutton[0] /*|| keydown[JK_OPENAPPLE]*/); if(joyinfo[joytype[1]] != DEVICE_KEYBOARD) // BUG? joytype[1] should be [0] ? pressed = (pressed || keydown[JK_BUTTON0]); break; case 0x62: // Left or Up if (g_uJoyportReadMode == JOYPORT_LEFTRIGHT) // LEFT { if (xpos[0] == 0) // TODO: More range for mouse control? pressed = 1; } else // UP { if (ypos[0] == 0) // TODO: More range for mouse control? pressed = 1; } break; case 0x63: // Right or Down if (g_uJoyportReadMode == JOYPORT_LEFTRIGHT) // RIGHT { if (xpos[0] >= 255) // TODO: More range for mouse control? pressed = 1; } else // DOWN { if (ypos[0] >= 255) // TODO: More range for mouse control? pressed = 1; } break; } } else // TODO: stick #1 { } pressed = pressed ? 0 : 1; // Invert as Joyport signals are active low return MemReadFloatingBus(pressed, nExecutedCycles); } static BOOL CheckButton0Pressed(void) { BOOL pressed = 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 = 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; if(joyinfo[joytype[0]] == DEVICE_JOYSTICK) CheckJoystick0(); if((joyinfo[joytype[1]] == DEVICE_JOYSTICK) || (joyinfo[joytype[1]] == DEVICE_JOYSTICK_THUMBSTICK2)) CheckJoystick1(); if (g_bJoyportEnabled) { // Some extra logic to stop the Joyport forcing a self-test at CTRL+RESET if ((address != 0x62) || (address == 0x62 && pc != 0xC242 && pc != 0xC2BE)) // Original //e ($C242), Enhanced //e ($C2BE) return JoyportReadButton(address, nExecutedCycles); } const bool swapButtons0and1 = GetPropertySheet().GetButtonsSwapState(); BOOL pressed = FALSE; switch (address) { case 0x61: { pressed = !swapButtons0and1 ? CheckButton0Pressed() : CheckButton1Pressed(); const UINT button0 = !swapButtons0and1 ? 0 : 1; DoAutofire(button0, pressed); if (CopyProtectionDonglePB0() >= 0) //If a copy protection dongle needs PB0, this overrides the joystick pressed = CopyProtectionDonglePB0(); } break; case 0x62: { pressed = !swapButtons0and1 ? CheckButton1Pressed() : CheckButton0Pressed(); const UINT button1 = !swapButtons0and1 ? 1 : 0; DoAutofire(button1, pressed); if (CopyProtectionDonglePB1() >= 0) //If a copy protection dongle needs PB1, this overrides the joystick pressed = CopyProtectionDonglePB1(); } break; case 0x63: if (CopyProtectionDonglePB2() >= 0) //If a copy protection dongle needs PB2, this overrides the joystick pressed = CopyProtectionDonglePB2(); else if (IS_APPLE2 && (joyinfo[joytype[1]] == DEVICE_NONE)) { // Apple II/II+ with no joystick has the "SHIFT key mod" // See Sather's Understanding The Apple II p7-36 pressed = !(GetKeyState(VK_SHIFT) < 0); } else { pressed = (joybutton[2] || setbutton[2]); DoAutofire(2, pressed); } break; } return MemReadFloatingBus(pressed, nExecutedCycles); } //=========================================================================== // PREAD: ; $FB1E // AD 70 C0 : (4) LDA $C070 // A0 00 : (2) LDA #$00 // EA : (2) NOP // EA : (2) NOP // Lbl1: ; 11 cycles is the normal duration of the loop // BD 64 C0 : (4) LDA $C064,X // 10 04 : (2) BPL Lbl2 ; NB. 3 cycles if branch taken (not likely) // C8 : (2) INY // D0 F8 : (3) BNE Lbl1 ; NB. 2 cycles if branch not taken (not likely) // 88 : (2) DEY // Lbl2: // 60 : (6) RTS // static const double PDL_CNTR_INTERVAL = 2816.0 / 255.0; // 11.04 (From KEGS) BYTE __stdcall JoyReadPosition(WORD programcounter, WORD address, BYTE, BYTE, ULONG nExecutedCycles) { CpuCalcCycles(nExecutedCycles); BOOL nPdlCntrActive = g_nCumulativeCycles <= g_paddleInactiveCycle[address & 3]; // If no joystick connected, then this is always active (GH#778) && no copy-protection dongle connected const UINT joyNum = (address & 2) ? 1 : 0; // $C064..$C067 if (joyinfo[joytype[joyNum]] == DEVICE_NONE && CopyProtectionDonglePDL(address & 3) < 0) nPdlCntrActive = TRUE; return MemReadFloatingBus(nPdlCntrActive, nExecutedCycles); } //=========================================================================== void JoyReset() { int loop = 0; while (loop < JK_MAX) keydown[loop++] = FALSE; } //=========================================================================== static void SetPaddleInactiveCycle(UINT pdl, UINT pdlPos) { g_paddleInactiveCycle[pdl] = g_nCumulativeCycles + (UINT64)((double)pdlPos * PDL_CNTR_INTERVAL); } void JoyResetPosition(ULONG nExecutedCycles) { CpuCalcCycles(nExecutedCycles); bool isJoystick[2] = { false, false }; if (joyinfo[joytype[0]] == DEVICE_JOYSTICK) { CheckJoystick0(); isJoystick[0] = true; } if (joyinfo[joytype[1]] == DEVICE_JOYSTICK || joyinfo[joytype[1]] == DEVICE_JOYSTICK_THUMBSTICK2) { CheckJoystick1(); isJoystick[1] = true; } // If any of the timers are still running then strobe has no effect (GH#985) for (UINT pdl = 0; pdl < 4; pdl++) { if (g_nCumulativeCycles <= g_paddleInactiveCycle[pdl]) continue; const UINT joyNum = (pdl & 2) ? 1 : 0; UINT pdlPos = (pdl & 1) ? ypos[joyNum] : xpos[joyNum]; // "Square the circle" for controllers with analog sticks (but compatible with digital D-pads too) - GH#429 if (isJoystick[joyNum]) { // Convert to unit circle, centred at (0,0) const double scalar = 0.5 * 255.0; const double offset = 1.0; const double x = ((double)xpos[joyNum]) / scalar - offset; const double y = ((double)ypos[joyNum]) / scalar - offset; double axis = !(pdl & 1) ? x : y; if (x * y != 0.0) { // rescale the circle to the square const double ratio2 = (y * y) / (x * x); const double c = min(ratio2, 1.0 / ratio2); const double coeff = sqrt(1.0 + c); axis *= coeff; } if (axis < -1.0) axis = -1.0; else if (axis > 1.0) axis = 1.0; pdlPos = static_cast((axis + offset) * scalar); } // This is from KEGS. It helps games like Championship Lode Runner, Boulderdash & Learning with Leeper(GH#1128) if (pdlPos >= 255) pdlPos = 287; SetPaddleInactiveCycle(pdl, pdlPos); } // Protection dongle overrides the PDL timer // . eg. needed when Robocom PDL3 is still timing-out from the extended-255 count (eg. 287) // . really if it were at 255 (ie. not connected), then on enabling the dongle it switches to (eg) 23 it should timeout immediately for (UINT pdl = 0; pdl < 4; pdl++) { int pdlPosDongle = CopyProtectionDonglePDL(pdl); if (pdlPosDongle >= 0) SetPaddleInactiveCycle(pdl, pdlPosDongle); } } //=========================================================================== // Called when mouse is being used as a joystick && mouse button changes void JoySetButton(eBUTTON number, eBUTTONSTATE down) { if (number > 1) // Sanity check on mouse button # return; // If 2nd joystick is enabled, then both joysticks only have 1 button if((joyinfo[joytype[1]] != DEVICE_NONE) && (number != 0)) return; // If it is 2nd joystick that is being emulated with mouse, then re-map button # if(joyinfo[joytype[1]] == DEVICE_MOUSE) { number = BUTTON1; // 2nd joystick controls Apple button #1 } setbutton[number] = down; } //=========================================================================== BOOL JoySetEmulationType(HWND window, uint32_t newtype, int nJoystickNumber, const bool bMousecardActive) { if(joytype[nJoystickNumber] == newtype) return 1; // Already set to this type. Return OK. if (joyinfo[newtype] == DEVICE_JOYSTICK || joyinfo[newtype] == DEVICE_JOYSTICK_THUMBSTICK2) { JOYCAPS caps; int nJoy2ID = joyinfo[newtype] == DEVICE_JOYSTICK_THUMBSTICK2 ? JOYSTICK_1 : JOYSTICK_2; int nJoyID = nJoystickNumber == JN_JOYSTICK0 ? JOYSTICK_1 : nJoy2ID; if (nJoyID < 0 || joyGetDevCaps(nJoyID, &caps, sizeof(JOYCAPS)) != JOYERR_NOERROR) { MessageBox(window, "The emulator is unable to read your PC joystick. " "Ensure that your game port is configured properly, " "that the joystick is firmly plugged in, and that " "you have a joystick driver installed.", "Configuration", MB_ICONEXCLAMATION | MB_SETFOREGROUND); return 0; } if ((joyinfo[newtype] == DEVICE_JOYSTICK_THUMBSTICK2) && (caps.wNumAxes < 4)) { MessageBox(window, "The emulator is unable to read thumbstick 2. " "Ensure that your game port is configured properly, " "that the joystick is firmly plugged in, and that " "you have a joystick driver installed.", "Configuration", MB_ICONEXCLAMATION | MB_SETFOREGROUND); return 0; } } else if ((joyinfo[newtype] == DEVICE_MOUSE) && (joyinfo[joytype[nJoystickNumber]] != DEVICE_MOUSE)) { if (bMousecardActive) { // Shouldn't be necessary, since Property Sheet's logic should prevent this option being given to the user. MessageBox(window, "Mouse interface card is enabled - unable to use mouse for joystick emulation.", "Configuration", MB_ICONEXCLAMATION | MB_SETFOREGROUND); return 0; } MessageBox(window, "To begin emulating a joystick with your mouse, move " "the mouse cursor over the emulated screen of a running " "program and click the left mouse button. During the " "time the mouse is emulating a joystick, you will not " "be able to use it to perform mouse functions, and the " "mouse cursor will not be visible. To end joystick " "emulation and regain the mouse cursor, click the left " "mouse button while pressing Ctrl.", "Configuration", MB_ICONINFORMATION | MB_SETFOREGROUND); } else if (joyinfo[newtype] == DEVICE_KEYBOARD) { if (newtype == J0C_KEYBD_CURSORS || newtype == J1C_KEYBD_CURSORS) { MessageBox(window, "Using cursor keys to emulate a joystick can cause conflicts.\n\n" "Be aware that 'cursor-up' = CTRL+K, and 'cursor-down' = CTRL+J.\n" "EG. Lode Runner uses CTRL+K/J to switch between keyboard/joystick modes " "(and cursor-left/right to control speed).\n\n" "Also if cursor keys are blocked from being read from the Apple keyboard " "then even simple AppleSoft command-line editing (cursor left/right) will not work.", "Configuration", MB_ICONINFORMATION | MB_SETFOREGROUND); } } joytype[nJoystickNumber] = newtype; JoyInitialize(); JoyReset(); return 1; } //=========================================================================== // Called when mouse is being used as a joystick && mouse position changes void JoySetPosition(int xvalue, int xrange, int yvalue, int yrange) { int nJoyNum = (joyinfo[joytype[0]] == DEVICE_MOUSE) ? 0 : 1; xpos[nJoyNum] = (xvalue*255)/xrange; ypos[nJoyNum] = (yvalue*255)/yrange; } //=========================================================================== BOOL JoyUsingMouse() { return (joyinfo[joytype[0]] == DEVICE_MOUSE) || (joyinfo[joytype[1]] == DEVICE_MOUSE); } BOOL JoyUsingKeyboard() { return (joyinfo[joytype[0]] == DEVICE_KEYBOARD) || (joyinfo[joytype[1]] == DEVICE_KEYBOARD); } BOOL JoyUsingKeyboardCursors() { return (joytype[0] == J0C_KEYBD_CURSORS) || (joytype[1] == J1C_KEYBD_CURSORS); } BOOL JoyUsingKeyboardNumpad() { return (joytype[0] == J0C_KEYBD_NUMPAD) || (joytype[1] == J1C_KEYBD_NUMPAD); } //=========================================================================== void JoyDisableUsingMouse() { if (joyinfo[joytype[0]] == DEVICE_MOUSE) joytype[0] = J0C_DISABLED; if (joyinfo[joytype[1]] == DEVICE_MOUSE) joytype[1] = J1C_DISABLED; } //=========================================================================== void JoySetJoyType(UINT num, uint32_t type) { _ASSERT(num <= JN_JOYSTICK1); if (num > JN_JOYSTICK1) return; if (num == JN_JOYSTICK0) // GH#434 { _ASSERT(type < J0C_MAX); if (type >= J0C_MAX) return; } else { _ASSERT(type < J1C_MAX); if (type >= J1C_MAX) return; } joytype[num] = type; // Refresh centre positions whenever 'joytype' changes JoySetTrim(JoyGetTrim(true) , true); JoySetTrim(JoyGetTrim(false), false); } uint32_t JoyGetJoyType(UINT num) { _ASSERT(num <= JN_JOYSTICK1); if (num > JN_JOYSTICK1) return J0C_DISABLED; return joytype[num]; } //=========================================================================== void JoySetTrim(short nValue, bool bAxisX) { if(bAxisX) g_nPdlTrimX = nValue; else g_nPdlTrimY = nValue; int nJoyNum = -1; if(joyinfo[joytype[0]] == DEVICE_KEYBOARD) nJoyNum = 0; else if(joyinfo[joytype[1]] == DEVICE_KEYBOARD) nJoyNum = 1; if(nJoyNum >= 0) { xpos[nJoyNum] = PDL_CENTRAL+g_nPdlTrimX; ypos[nJoyNum] = PDL_CENTRAL+g_nPdlTrimY; } } short JoyGetTrim(bool bAxisX) { return bAxisX ? g_nPdlTrimX : g_nPdlTrimY; } //=========================================================================== // Joyport truth-table: // // AN0 AN1 /PB0 /PB1 /PB2 // ------------------------------------ // 0 0 Trigger-1 Left-1 Right-1 // 0 1 Trigger-1 Up-1 Down-1 // 1 0 Trigger-2 Left-2 Right-2 // 1 1 Trigger-2 Up-2 Down-2 #if 0 void JoyportEnable(const bool bEnable) { if (IS_APPLE2C()) g_bJoyportEnabled = false; else g_bJoyportEnabled = bEnable ? 1 : 0; } #endif void JoyportControl(const UINT uControl) { if (!g_bJoyportEnabled) return; switch (uControl) { case 0: // AN0 clr g_uJoyportActiveStick = 0; break; case 1: // AN0 set g_uJoyportActiveStick = 1; break; case 2: // AN1 clr g_uJoyportReadMode = JOYPORT_LEFTRIGHT; break; case 3: // AN1 set g_uJoyportReadMode = JOYPORT_UPDOWN; break; } } //=========================================================================== #define SS_YAML_KEY_COUNTERRESETCYCLE "Counter Reset Cycle" #define SS_YAML_KEY_JOY0TRIMX "Joystick0 TrimX" #define SS_YAML_KEY_JOY0TRIMY "Joystick0 TrimY" #define SS_YAML_KEY_JOY1TRIMX "Joystick1 TrimX" #define SS_YAML_KEY_JOY1TRIMY "Joystick1 TrimY" #define SS_YAML_KEY_PDL_INACTIVE_CYCLE "Paddle%1d Inactive Cycle" static const std::string& JoyGetSnapshotStructName(void) { static const std::string name("Joystick"); return name; } void JoySaveSnapshot(YamlSaveHelper& yamlSaveHelper) { YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", JoyGetSnapshotStructName().c_str()); yamlSaveHelper.SaveInt(SS_YAML_KEY_JOY0TRIMX, JoyGetTrim(true)); yamlSaveHelper.SaveInt(SS_YAML_KEY_JOY0TRIMY, JoyGetTrim(false)); yamlSaveHelper.Save("%s: %d # not implemented yet\n", SS_YAML_KEY_JOY1TRIMX, 0); // not implemented yet yamlSaveHelper.Save("%s: %d # not implemented yet\n", SS_YAML_KEY_JOY1TRIMY, 0); // not implemented yet for (UINT n = 0; n < 4; n++) { std::string str = StrFormat(SS_YAML_KEY_PDL_INACTIVE_CYCLE, n); yamlSaveHelper.SaveHexUint64(str.c_str(), g_paddleInactiveCycle[n]); } } void JoyLoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { if (!yamlLoadHelper.GetSubMap(JoyGetSnapshotStructName())) return; JoySetTrim(yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY0TRIMX), true); JoySetTrim(yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY0TRIMY), false); yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY1TRIMX); // dump value yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY1TRIMY); // dump value if (version >= 7) { for (UINT n = 0; n < 4; n++) { std::string str = StrFormat(SS_YAML_KEY_PDL_INACTIVE_CYCLE, n); g_paddleInactiveCycle[n] = yamlLoadHelper.LoadUint64(str); } } else { UINT64 resetCycle = yamlLoadHelper.LoadUint64(SS_YAML_KEY_COUNTERRESETCYCLE); for (UINT n = 0; n < 4; n++) g_paddleInactiveCycle[n] = resetCycle; } yamlLoadHelper.PopMap(); }