Add Analog "GBA" Support

This allows hacked/homebrew DS games to read analog stick values from the emulator.
This commit is contained in:
LRFLEW 2021-02-14 02:57:00 -06:00
parent 3ba3821228
commit 40d7d02d29
11 changed files with 556 additions and 45 deletions

View File

@ -0,0 +1,80 @@
/*
Copyright (C) 2021 LRFLEW
Copyright (C) 2021 DeSmuME team
This file 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.
This file 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 the this software. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../slot2.h"
#include <cmath>
#include <cstring>
#include "../debug.h"
static u16 state[4];
class Slot2_Analog : public ISlot2Interface {
public:
Slot2Info const* info() override {
static Slot2InfoSimple info("Analog Stick", "Analog Stick input for Modded SM64DS", 0x09);
return &info;
}
void connect() override {
std::memset(state, 0, sizeof(state));
}
u8 readByte(u8 PROCNUM, u32 addr) override {
if ((addr & 0xFFFFFFF8) != 0x09000000)
return 0xFF;
else if ((addr & 1) == 0)
return (u8) state[(addr & 6) >> 1];
else
return (u8) (state[(addr & 6) >> 1] >> 8);
}
u16 readWord(u8 PROCNUM, u32 addr) override {
if ((addr & 0xFFFFFFF8) != 0x09000000)
return 0xFFFF;
return state[(addr & 6) >> 1];
}
u32 readLong(u8 PROCNUM, u32 addr) override {
if ((addr & 0xFFFFFFF8) != 0x09000000)
return 0xFFFFFFFF;
int i = (addr & 4) >> 1;
return ((u32) state[i]) | ((u32) state[i + 1] << 16);
}
};
ISlot2Interface* construct_Slot2_Analog() { return new Slot2_Analog(); }
void analog_setValue(float x, float y) {
constexpr float angle_to_short = 10430.37835047f; // 2^15 / Pi
constexpr float float_to_fixed = static_cast<float>(0x1000);
float mag = std::hypot(x, y);
float ang = std::atan2(x, y);
if (mag > 1.0f) {
x /= mag;
y /= mag;
mag = 1.0f;
}
state[0] = static_cast<u16>(std::lround(mag * float_to_fixed));
state[1] = static_cast<u16>(std::lround( x * float_to_fixed));
state[2] = static_cast<u16>(std::lround( y * float_to_fixed));
state[3] = static_cast<u16>(std::lround(ang * angle_to_short));
}

View File

@ -58,6 +58,7 @@
<ClCompile Include="..\..\addons\slot1_retail_auto.cpp" />
<ClCompile Include="..\..\addons\slot1_retail_mcrom.cpp" />
<ClCompile Include="..\..\addons\slot1_retail_mcrom_debug.cpp" />
<ClCompile Include="..\..\addons\slot2_analog.cpp" />
<ClCompile Include="..\..\addons\slot2_auto.cpp" />
<ClCompile Include="..\..\addons\slot2_passme.cpp" />
<ClCompile Include="..\..\addons\slot2_piano.cpp" />

View File

@ -930,6 +930,9 @@
<ClCompile Include="display.cpp">
<Filter>frontend\Windows</Filter>
</ClCompile>
<ClCompile Include="..\..\addons\slot2_analog.cpp">
<Filter>addons</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\utils\guid.h">

View File

@ -19,6 +19,7 @@
#include <windowsx.h>
#include <shlobj.h>
#include <richedit.h>
#include "debug.h"
#include "slot2.h"
@ -42,6 +43,7 @@ bool _OKbutton = false;
SGuitar tmp_Guitar;
SPiano tmp_Piano;
SPaddle tmp_Paddle;
SAnalog tmp_Analog;
//these are the remembered preset values for directory and filename
//they are named very verbosely to distinguish them from the currently-configured values in addons.cpp
@ -467,6 +469,94 @@ INT_PTR CALLBACK GbaSlotPiano(HWND dialog, UINT msg,WPARAM wparam,LPARAM lparam)
return FALSE;
}
INT_PTR CALLBACK GbaSlotAnalog(HWND dialog, UINT msg, WPARAM wparam, LPARAM lparam) {
int which = 0;
switch (msg) {
case WM_INITDIALOG:
{
_OKbutton = TRUE;
SendDlgItemMessage(dialog, IDC_ANALOG_X, WM_USER + 44, tmp_Analog.X, 0);
SendDlgItemMessage(dialog, IDC_ANALOG_Y, WM_USER + 44, tmp_Analog.Y, 0);
std::string deadzone_val = std::to_string(tmp_Analog.Deadzone);
SendDlgItemMessage(dialog, IDC_ANALOG_DEADZONE, EM_SETLIMITTEXT, 3, 0);
SendDlgItemMessage(dialog, IDC_ANALOG_DEADZONE, EM_SETEVENTMASK, 0, ENM_UPDATE);
SetWindowTextA(GetDlgItem(dialog, IDC_ANALOG_DEADZONE), deadzone_val.c_str());
SendDlgItemMessage(dialog, IDC_ANALOG_DEADZONE_SLIDER, TBM_SETRANGE, FALSE, MAKELONG(0, 100));
SendDlgItemMessage(dialog, IDC_ANALOG_DEADZONE_SLIDER, TBM_SETPOS, TRUE, tmp_Analog.Deadzone);
CheckDlgButton(dialog, IDC_ANALOG_JOINED, tmp_Analog.Joined);
CheckDlgButton(dialog, IDC_ANALOG_CIRCLE, tmp_Analog.Circle);
EnableWindow(GetDlgItem(dialog, IDC_ANALOG_CIRCLE), tmp_Analog.Joined);
return TRUE;
}
case WM_USER + 46:
SendDlgItemMessage(dialog, IDC_ANALOG_X, WM_USER + 44, tmp_Analog.X, 0);
SendDlgItemMessage(dialog, IDC_ANALOG_Y, WM_USER + 44, tmp_Analog.Y, 0);
return TRUE;
case WM_USER + 43:
which = GetDlgCtrlID((HWND) lparam);
switch (which) {
case IDC_ANALOG_X:
tmp_Analog.X = wparam;
break;
case IDC_ANALOG_Y:
tmp_Analog.Y = wparam;
break;
}
SendDlgItemMessage(dialog, IDC_ANALOG_X, WM_USER + 44, tmp_Analog.X, 0);
SendDlgItemMessage(dialog, IDC_ANALOG_Y, WM_USER + 44, tmp_Analog.Y, 0);
PostMessage(dialog, WM_NEXTDLGCTL, 0, 0);
return TRUE;
case WM_COMMAND:
switch (wparam) {
case MAKELONG(IDC_ANALOG_DEADZONE, EN_UPDATE):
{
char text[4];
WORD value = 0;
bool dirty = false;
HWND en = GetDlgItem(dialog, IDC_ANALOG_DEADZONE);
GetWindowTextA(en, text, 4);
if (text[0] != '\0') value = std::stoi(text);
if (value < 0) value = 0, dirty = true;
if (value > 100) value = 100, dirty = true;
if (dirty) SetWindowTextA(en, std::to_string(value).c_str());
if (tmp_Analog.Deadzone != value) {
tmp_Analog.Deadzone = value;
SendDlgItemMessage(dialog, IDC_ANALOG_DEADZONE_SLIDER, TBM_SETPOS, TRUE, value);
}
return TRUE;
}
case MAKELONG(IDC_ANALOG_JOINED, BN_CLICKED):
case MAKELONG(IDC_ANALOG_CIRCLE, BN_CLICKED):
tmp_Analog.Joined = IsDlgCheckboxChecked(dialog, IDC_ANALOG_JOINED);
tmp_Analog.Circle = IsDlgCheckboxChecked(dialog, IDC_ANALOG_CIRCLE);
EnableWindow(GetDlgItem(dialog, IDC_ANALOG_CIRCLE), tmp_Analog.Joined);
return TRUE;
}
return FALSE;
case WM_NOTIFY:
if (wparam == IDC_ANALOG_DEADZONE_SLIDER) {
HWND en = GetDlgItem(dialog, IDC_ANALOG_DEADZONE);
WORD value = (WORD) SendDlgItemMessage(dialog, IDC_ANALOG_DEADZONE_SLIDER, TBM_GETPOS, 0, 0);
if (tmp_Analog.Deadzone != value) {
tmp_Analog.Deadzone = value;
SetWindowTextA(en, std::to_string(value).c_str());
}
}
}
return FALSE;
}
u32 GBAslot_IDDs[NDS_SLOT2_COUNT] = {
IDD_GBASLOT_NONE,
IDD_GBASLOT_NONE,
@ -478,6 +568,7 @@ u32 GBAslot_IDDs[NDS_SLOT2_COUNT] = {
IDD_GBASLOT_PIANO,
IDD_GBASLOT_PADDLE, //paddle
IDD_GBASLOT_NONE, //PassME
IDD_GBASLOT_ANALOG,
};
DLGPROC GBAslot_Procs[NDS_SLOT2_COUNT] = {
@ -490,7 +581,8 @@ DLGPROC GBAslot_Procs[NDS_SLOT2_COUNT] = {
GbaSlotNone, //expmem
GbaSlotPiano,
GbaSlotPaddle,
GbaSlotNone // PassME
GbaSlotNone, // PassME
GbaSlotAnalog,
};
@ -569,6 +661,7 @@ void GBAslotDialog(HWND hwnd)
memcpy(&tmp_Guitar, &Guitar, sizeof(Guitar));
memcpy(&tmp_Piano, &Piano, sizeof(Piano));
memcpy(&tmp_Paddle, &Paddle, sizeof(Paddle));
memcpy(&tmp_Analog, &Analog, sizeof(Analog));
tmp_CFlashMode = CFlash_Mode;
_OKbutton = false;
@ -633,6 +726,14 @@ void GBAslotDialog(HWND hwnd)
break;
case NDS_SLOT2_PASSME:
break;
case NDS_SLOT2_ANALOG:
memcpy(&Analog, &tmp_Analog, sizeof(tmp_Analog));
WritePrivateProfileInt("Slot2.Analog", "X", Analog.X, IniName);
WritePrivateProfileInt("Slot2.Analog", "Y", Analog.Y, IniName);
WritePrivateProfileInt("Slot2.Analog", "Deadzone", Analog.Deadzone, IniName);
WritePrivateProfileBool("Slot2.Analog", "Joined", Analog.Joined, IniName);
WritePrivateProfileBool("Slot2.Analog", "Circle", Analog.Circle, IniName);
break;
default:
return;
}
@ -641,9 +742,10 @@ void GBAslotDialog(HWND hwnd)
WritePrivateProfileInt("Slot2", "id", slot2_List[(u8)slot2_GetCurrentType()]->info()->id(), IniName);
Guitar.Enabled = (slot2_GetCurrentType() == NDS_SLOT2_GUITARGRIP)?true:false;
Piano.Enabled = (slot2_GetCurrentType() == NDS_SLOT2_EASYPIANO)?true:false;
Paddle.Enabled = (slot2_GetCurrentType() == NDS_SLOT2_PADDLE)?true:false;
Guitar.Enabled = slot2_GetCurrentType() == NDS_SLOT2_GUITARGRIP;
Piano.Enabled = slot2_GetCurrentType() == NDS_SLOT2_EASYPIANO;
Paddle.Enabled = slot2_GetCurrentType() == NDS_SLOT2_PADDLE;
Analog.Enabled = slot2_GetCurrentType() == NDS_SLOT2_ANALOG;
}
}

View File

@ -30,6 +30,8 @@
#include <tchar.h>
#include <io.h>
#include <cmath>
#include <algorithm>
#include <string>
#if (((defined(_MSC_VER) && _MSC_VER >= 1300)) || defined(__MINGW32__))
@ -97,8 +99,8 @@
// gaming buttons and axes
#define GAMEDEVICE_JOYNUMPREFIX "(J%x)" // don't change this
#define GAMEDEVICE_JOYBUTPREFIX "#[%d]" // don't change this
#define GAMEDEVICE_XNEG "Left"
#define GAMEDEVICE_XPOS "Right"
#define GAMEDEVICE_XPOS "Left"
#define GAMEDEVICE_XNEG "Right"
#define GAMEDEVICE_YPOS "Up"
#define GAMEDEVICE_YNEG "Down"
#define GAMEDEVICE_POVLEFT "POV Left"
@ -119,12 +121,12 @@
#define GAMEDEVICE_VNEG "V Down"
#define GAMEDEVICE_BUTTON "Button %d"
#define GAMEDEVICE_XROTPOS "X Rot Up"
#define GAMEDEVICE_XROTNEG "X Rot Down"
#define GAMEDEVICE_XROTPOS "X Rot Left"
#define GAMEDEVICE_XROTNEG "X Rot Right"
#define GAMEDEVICE_YROTPOS "Y Rot Up"
#define GAMEDEVICE_YROTNEG "Y Rot Down"
#define GAMEDEVICE_ZROTPOS "Z Rot Up"
#define GAMEDEVICE_ZROTNEG "Z Rot Down"
#define GAMEDEVICE_ZROTPOS "Z Rot +"
#define GAMEDEVICE_ZROTNEG "Z Rot -"
// gaming general
#define GAMEDEVICE_DISABLED "Disabled"
@ -206,11 +208,13 @@ static TCHAR szClassName[] = _T("InputCustom");
static TCHAR szHotkeysClassName[] = _T("InputCustomHot");
static TCHAR szGuitarClassName[] = _T("InputCustomGuitar");
static TCHAR szPaddleClassName[] = _T("InputCustomPaddle");
static TCHAR szAnalogClassName[] = _T("InputCustomAnalog");
static LRESULT CALLBACK InputCustomWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK HotInputCustomWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK GuitarInputCustomWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK PaddleInputCustomWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK AnalogInputCustomWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
SJoyState Joystick [16];
SJoyState JoystickF [16];
@ -258,6 +262,9 @@ SPiano DefaultPiano = { false, 'Z', 'S', 'X', 'D', 'C', 'V', 'G', 'B', 'H', 'N',
SPaddle Paddle;
SPaddle DefaultPaddle = { false, 'K', 'L' };
SAnalog Analog;
SAnalog DefaultAnalog = { false, 1, 2, 15, true, true };
bool killStylusTopScreen = false;
bool killStylusOffScreen = false;
bool allowUpAndDown = false;
@ -390,6 +397,24 @@ static void ReadPaddleControl(const char* name, WORD& output)
}
}
static void ReadAnalogControl(const char* name, WORD& output)
{
UINT temp;
temp = GetPrivateProfileInt("Slot2.Analog", name, -1, IniName);
if (temp != -1) {
output = temp;
}
}
static void ReadAnalogBool(const char* name, BOOL& output)
{
UINT temp;
temp = GetPrivateProfileInt("Slot2.Analog", name, -1, IniName);
if (temp != -1) {
output = (BOOL) temp;
}
}
void LoadHotkeyConfig()
{
SCustomKey *key = &CustomKeys.key(0);
@ -452,6 +477,17 @@ static void LoadPaddleConfig()
ReadPaddleControl("INC", Paddle.INC);
}
static void LoadAnalogConfig()
{
memcpy(&Analog, &DefaultAnalog, sizeof(Analog));
ReadAnalogControl("X", Analog.X);
ReadAnalogControl("Y", Analog.Y);
ReadAnalogControl("Deadzone", Analog.Deadzone);
ReadAnalogBool("Joined", Analog.Joined);
ReadAnalogBool("Circle", Analog.Circle);
}
static void LoadInputConfig()
{
@ -607,7 +643,7 @@ HWND funky;
//WPARAM tid;
//
void JoystickChanged( short ID, short Movement)
void JoystickChanged( short ID, short Movement, short Axis)
{
// don't allow two changes to happen too close together in time
{
@ -625,12 +661,19 @@ void JoystickChanged( short ID, short Movement)
}
WORD JoyKey;
JoyKey = 0x8000;
JoyKey = 0x8000;
JoyKey |= (WORD)(ID << 8);
JoyKey |= Movement;
SendMessage(funky,WM_USER+45,JoyKey,0);
// KillTimer(funky,tid);
if (Axis > 0)
{
WORD JoyAxis;
JoyAxis = (WORD)(ID << 8);
JoyAxis |= Axis;
SendMessage(funky,WM_USER+47,JoyAxis,0);
}
}
int FunkyNormalize(int cur, int min, int max)
@ -652,7 +695,7 @@ int FunkyNormalize(int cur, int min, int max)
#define S9X_JOY_NEUTRAL 60
void CheckAxis (short joy, short control, int val,
void CheckAxis (short joy, short control, short axis, int val,
int min, int max,
bool &first, bool &second)
{
@ -665,7 +708,7 @@ void CheckAxis (short joy, short control, int val,
second = false;
if (!first)
{
JoystickChanged (joy, control);
JoystickChanged (joy, control, axis);
first = true;
}
@ -678,7 +721,7 @@ void CheckAxis (short joy, short control, int val,
first = false;
if (!second)
{
JoystickChanged (joy, (short) (control + 1));
JoystickChanged (joy, (short) (control + 1), (short) (8 | axis));
second = true;
}
}
@ -733,6 +776,13 @@ void S9xUpdateJoyState()
CheckAxis_game(JoyStatus.lRy,-10000,10000,Joystick[C].YRotMin,Joystick[C].YRotMax);
CheckAxis_game(JoyStatus.lRz,-10000,10000,Joystick[C].ZRotMin,Joystick[C].ZRotMax);
Joystick[C].lX = JoyStatus.lX;
Joystick[C].lY = JoyStatus.lY;
Joystick[C].lZ = JoyStatus.lZ;
Joystick[C].lRx = JoyStatus.lRx;
Joystick[C].lRy = JoyStatus.lRy;
Joystick[C].lRz = JoyStatus.lRz;
switch (JoyStatus.rgdwPOV[0])
{
case JOY_POVBACKWARD:
@ -805,18 +855,18 @@ void di_poll_scan()
if (FAILED(hr)) hr=pJoystick->Acquire();
else
{
CheckAxis(C,0,JoyStatus.lX,-10000,10000,Joystick[C].Left,Joystick[C].Right);
CheckAxis(C,2,JoyStatus.lY,-10000,10000,Joystick[C].Down,Joystick[C].Up);
CheckAxis(C,41,JoyStatus.lZ,-10000,10000,Joystick[C].ZNeg,Joystick[C].ZPos);
CheckAxis(C,53,JoyStatus.lRx,-10000,10000,Joystick[C].XRotMin,Joystick[C].XRotMax);
CheckAxis(C,55,JoyStatus.lRy,-10000,10000,Joystick[C].YRotMin,Joystick[C].YRotMax);
CheckAxis(C,57,JoyStatus.lRz,-10000,10000,Joystick[C].ZRotMin,Joystick[C].ZRotMax);
CheckAxis(C,0,1,JoyStatus.lX,-10000,10000,Joystick[C].Left,Joystick[C].Right);
CheckAxis(C,2,2,JoyStatus.lY,-10000,10000,Joystick[C].Down,Joystick[C].Up);
CheckAxis(C,41,3,JoyStatus.lZ,-10000,10000,Joystick[C].ZNeg,Joystick[C].ZPos);
CheckAxis(C,53,4,JoyStatus.lRx,-10000,10000,Joystick[C].XRotMin,Joystick[C].XRotMax);
CheckAxis(C,55,5,JoyStatus.lRy,-10000,10000,Joystick[C].YRotMin,Joystick[C].YRotMax);
CheckAxis(C,57,6,JoyStatus.lRz,-10000,10000,Joystick[C].ZRotMin,Joystick[C].ZRotMax);
switch (JoyStatus.rgdwPOV[0])
{
case JOY_POVBACKWARD:
if( !JoystickF[C].PovDown)
{ JoystickChanged( C, 7); }
{ JoystickChanged( C, 7, -1); }
JoystickF[C].PovDown = true;
JoystickF[C].PovUp = false;
@ -829,7 +879,7 @@ void di_poll_scan()
break;
case 4500:
if( !JoystickF[C].PovUpRight)
{ JoystickChanged( C, 52); }
{ JoystickChanged( C, 52, -1); }
JoystickF[C].PovDown = false;
JoystickF[C].PovUp = false;
JoystickF[C].PovLeft = false;
@ -841,7 +891,7 @@ void di_poll_scan()
break;
case 13500:
if( !JoystickF[C].PovDnRight)
{ JoystickChanged( C, 50); }
{ JoystickChanged( C, 50, -1); }
JoystickF[C].PovDown = false;
JoystickF[C].PovUp = false;
JoystickF[C].PovLeft = false;
@ -853,7 +903,7 @@ void di_poll_scan()
break;
case 22500:
if( !JoystickF[C].PovDnLeft)
{ JoystickChanged( C, 49); }
{ JoystickChanged( C, 49, -1); }
JoystickF[C].PovDown = false;
JoystickF[C].PovUp = false;
JoystickF[C].PovLeft = false;
@ -865,7 +915,7 @@ void di_poll_scan()
break;
case 31500:
if( !JoystickF[C].PovUpLeft)
{ JoystickChanged( C, 51); }
{ JoystickChanged( C, 51, -1); }
JoystickF[C].PovDown = false;
JoystickF[C].PovUp = false;
JoystickF[C].PovLeft = false;
@ -878,7 +928,7 @@ void di_poll_scan()
case JOY_POVFORWARD:
if( !JoystickF[C].PovUp)
{ JoystickChanged( C, 6); }
{ JoystickChanged( C, 6, -1); }
JoystickF[C].PovDown = false;
JoystickF[C].PovUp = true;
@ -892,7 +942,7 @@ void di_poll_scan()
case JOY_POVLEFT:
if( !JoystickF[C].PovLeft)
{ JoystickChanged( C, 4); }
{ JoystickChanged( C, 4, -1); }
JoystickF[C].PovDown = false;
JoystickF[C].PovUp = false;
@ -906,7 +956,7 @@ void di_poll_scan()
case JOY_POVRIGHT:
if( !JoystickF[C].PovRight)
{ JoystickChanged( C, 5); }
{ JoystickChanged( C, 5, -1); }
JoystickF[C].PovDown = false;
JoystickF[C].PovUp = false;
@ -935,7 +985,7 @@ void di_poll_scan()
{
if( !JoystickF[C].Button[B])
{
JoystickChanged( C, (short)(8+B));
JoystickChanged( C, (short)(8+B), -1);
JoystickF[C].Button[B] = true;
}
}
@ -967,8 +1017,8 @@ void TranslateKey(WORD keyz,char *out)
sprintf(out,GAMEDEVICE_JOYNUMPREFIX,((keyz>>8)&0xF));
switch(keyz&0xFF)
{
case 0: strcat(out,GAMEDEVICE_XNEG); break;
case 1: strcat(out,GAMEDEVICE_XPOS); break;
case 0: strcat(out,GAMEDEVICE_XPOS); break;
case 1: strcat(out,GAMEDEVICE_XNEG); break;
case 2: strcat(out,GAMEDEVICE_YPOS); break;
case 3: strcat(out,GAMEDEVICE_YNEG); break;
case 4: strcat(out,GAMEDEVICE_POVLEFT); break;
@ -1095,11 +1145,28 @@ void TranslateKey(WORD keyz,char *out)
case VK_F11: sprintf(out,GAMEDEVICE_VK_F11); break;
case VK_F12: sprintf(out,GAMEDEVICE_VK_F12); break;
}
}
return ;
void TranslateAnalog(WORD axis, char* out)
{
sprintf(out, GAMEDEVICE_JOYNUMPREFIX, ((axis >> 8) & 0xF));
switch (axis & 0xF) {
case 1: strcat(out, GAMEDEVICE_XPOS); break;
case 2: strcat(out, GAMEDEVICE_YPOS); break;
case 3: strcat(out, GAMEDEVICE_ZPOS); break;
case 4: strcat(out, GAMEDEVICE_XROTPOS); break;
case 5: strcat(out, GAMEDEVICE_YROTPOS); break;
case 6: strcat(out, GAMEDEVICE_XROTPOS); break;
case 9: strcat(out, GAMEDEVICE_XNEG); break;
case 10: strcat(out, GAMEDEVICE_YNEG); break;
case 11: strcat(out, GAMEDEVICE_ZNEG); break;
case 12: strcat(out, GAMEDEVICE_XROTNEG); break;
case 13: strcat(out, GAMEDEVICE_YROTNEG); break;
case 14: strcat(out, GAMEDEVICE_ZROTNEG); break;
default: strcat(out, "UNKNOWN"); break;
}
}
bool IsReserved (WORD Key, int modifiers)
@ -1177,8 +1244,8 @@ int GetNumButtonsAssignedTo (WORD Key)
if(Key == Joypad[J].Y) count++;
if(Key == Joypad[J].L) count++;
if(Key == Joypad[J].R) count++;
if(Key == Joypad[J].Lid) count++;
if(Key == Joypad[J].Debug) count++;
if(Key == Joypad[J].Lid) count++;
if(Key == Joypad[J].Debug) count++;
}
return count;
}
@ -1297,6 +1364,22 @@ static void InitCustomControls()
RegisterClassEx(&wc);
wc.cbSize = sizeof(wc);
wc.lpszClassName = szAnalogClassName;
wc.hInstance = GetModuleHandle(0);
wc.lpfnWndProc = AnalogInputCustomWndProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = 0;
wc.lpszMenuName = 0;
wc.hbrBackground = (HBRUSH) GetSysColorBrush(COLOR_BTNFACE);
wc.style = 0;
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof(InputCust*);
wc.hIconSm = 0;
RegisterClassEx(&wc);
}
InputCust * GetInputCustom(HWND hwnd)
@ -1782,6 +1865,136 @@ static LRESULT CALLBACK PaddleInputCustomWndProc(HWND hwnd, UINT msg, WPARAM wPa
return DefWindowProc(hwnd, msg, wParam, lParam);
}
static LRESULT CALLBACK AnalogInputCustomWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
// retrieve the custom structure POINTER for THIS window
InputCust* icp = GetInputCustom(hwnd);
HWND pappy = (HWND__*) GetWindowLongPtr(hwnd, GWLP_HWNDPARENT);
funky = hwnd;
static HWND selectedItem = NULL;
char temp[100];
COLORREF col;
switch (msg) {
case WM_GETDLGCODE:
return DLGC_WANTARROWS | DLGC_WANTALLKEYS | DLGC_WANTCHARS;
break;
case WM_NCCREATE:
// Allocate a new CustCtrl structure for this window.
icp = (InputCust*) malloc(sizeof(InputCust));
// Failed to allocate, stop window creation.
if (icp == NULL) return FALSE;
// Initialize the CustCtrl structure.
icp->hwnd = hwnd;
icp->crForeGnd = GetSysColor(COLOR_WINDOWTEXT);
icp->crBackGnd = GetSysColor(COLOR_WINDOW);
icp->hFont = (HFONT__*) GetStockObject(DEFAULT_GUI_FONT);
// Assign the window text specified in the call to CreateWindow.
SetWindowText(hwnd, ((CREATESTRUCT*) lParam)->lpszName);
// Attach custom structure to this window.
SetInputCustom(hwnd, icp);
InvalidateRect(icp->hwnd, NULL, FALSE);
UpdateWindow(icp->hwnd);
selectedItem = NULL;
SetTimer(hwnd, 777, 125, NULL);
// Continue with window creation.
return TRUE;
// Clean up when the window is destroyed.
case WM_NCDESTROY:
free(icp);
break;
case WM_PAINT:
return InputCustom_OnPaint(icp, wParam, lParam);
break;
case WM_ERASEBKGND:
return 1;
case WM_USER+47:
TranslateAnalog(wParam, temp);
icp->crForeGnd = RGB(0, 0, 0);
icp->crBackGnd = RGB(255, 255, 255);
SetWindowText(hwnd, temp);
InvalidateRect(icp->hwnd, NULL, FALSE);
UpdateWindow(icp->hwnd);
SendMessage(pappy, WM_USER + 43, wParam, (LPARAM) hwnd);
break;
case WM_USER+44:
TranslateAnalog(wParam, temp);
if (IsWindowEnabled(hwnd)) {
col = RGB(255, 255, 255);
} else {
col = RGB(192, 192, 192);
}
icp->crForeGnd = ((~col) & 0x00ffffff);
icp->crBackGnd = col;
SetWindowText(hwnd, temp);
InvalidateRect(icp->hwnd, NULL, FALSE);
UpdateWindow(icp->hwnd);
break;
case WM_SETFOCUS:
{
selectedItem = hwnd;
col = RGB(0, 255, 0);
icp->crForeGnd = ((~col) & 0x00ffffff);
icp->crBackGnd = col;
InvalidateRect(icp->hwnd, NULL, FALSE);
UpdateWindow(icp->hwnd);
// tid = wParam;
break;
}
case WM_KILLFOCUS:
{
selectedItem = NULL;
SendMessage(pappy, WM_USER + 46, wParam, (LPARAM) hwnd); // refresh fields on deselect
break;
}
case WM_TIMER:
if (hwnd == selectedItem) {
FunkyJoyStickTimer();
}
SetTimer(hwnd, 777, 125, NULL);
break;
case WM_LBUTTONDOWN:
SetFocus(hwnd);
break;
case WM_ENABLE:
COLORREF col;
if (wParam) {
col = RGB(255, 255, 255);
icp->crForeGnd = ((~col) & 0x00ffffff);
icp->crBackGnd = col;
} else {
col = RGB(192, 192, 192);
icp->crForeGnd = ((~col) & 0x00ffffff);
icp->crBackGnd = col;
}
InvalidateRect(icp->hwnd, NULL, FALSE);
UpdateWindow(icp->hwnd);
return true;
default:
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
static void TranslateKeyWithModifiers(int wParam, int modifiers, char * outStr)
{
@ -2481,6 +2694,26 @@ void S9xWinScanJoypads(const bool willAcceptInput)
}
}
static float get_analog_float(WORD axis)
{
int J = axis >> 8;
LONG val;
switch (axis & 0x7)
{
case 1: val = Joystick[J].lX; break;
case 2: val = Joystick[J].lY; break;
case 3: val = Joystick[J].lZ; break;
case 4: val = Joystick[J].lRx; break;
case 5: val = Joystick[J].lRy; break;
case 6: val = Joystick[J].lRz; break;
default: val = 0; break;
}
if (axis & 8) val = -val;
return (float) val / 10000.0f;
}
//void S9xOldAutofireAndStuff ()
//{
// // stuff ripped out of Snes9x that's no longer functional, at least for now
@ -2690,6 +2923,7 @@ void input_init()
LoadGuitarConfig();
LoadPianoConfig();
LoadPaddleConfig();
LoadAnalogConfig();
di_init();
FeedbackON = input_feedback;
@ -2816,6 +3050,23 @@ void input_acquire()
if (inc) nds.paddle += 5;
if (dec) nds.paddle -= 5;
}
if (Analog.Enabled)
{
float deadzone = Analog.Deadzone / 100.0f;
float x = get_analog_float(Analog.X);
float y = get_analog_float(Analog.Y);
if (Analog.Joined) {
float value = Analog.Circle ? std::hypot(x, y) : (std::max)(std::abs(x), std::abs(y));
if (value < deadzone) x = y = 0.0f;
} else {
if (x < deadzone && x > -deadzone) x = 0.0f;
if (y < deadzone && y > -deadzone) y = 0.0f;
}
analog_setValue(x, y);
}
}
else
{
@ -2828,6 +3079,10 @@ void input_acquire()
{
piano_setKey(false, false, false, false, false, false, false, false, false, false, false, false, false);
}
if (Analog.Enabled) {
analog_setValue(0.0f, 0.0f);
}
}
}

View File

@ -119,6 +119,13 @@ struct SJoyState{
bool Button[128];
bool FeedBack;
LPDIRECTINPUTEFFECT pEffect;
LONG lX;
LONG lY;
LONG lZ;
LONG lRx;
LONG lRy;
LONG lRz;
};
extern SJoypad Joypad[16];
@ -153,9 +160,20 @@ struct SPaddle {
WORD INC;
};
struct SAnalog {
BOOL Enabled;
WORD X;
WORD Y;
WORD Deadzone;
BOOL Joined;
BOOL Circle;
};
extern SGuitar Guitar;
extern SPiano Piano;
extern SPaddle Paddle;
extern SAnalog Analog;
#endif

View File

@ -1577,9 +1577,10 @@ static BOOL LoadROM(const char * filename, const char * physicalName, const char
INFO("Loading %s was successful\n",logicalName);
NDS_SLOT2_TYPE selectedSlot2Type = slot2_GetSelectedType();
Guitar.Enabled = (selectedSlot2Type == NDS_SLOT2_GUITARGRIP)?true:false;
Piano.Enabled = (selectedSlot2Type == NDS_SLOT2_EASYPIANO)?true:false;
Paddle.Enabled = (selectedSlot2Type == NDS_SLOT2_PADDLE)?true:false;
Guitar.Enabled = selectedSlot2Type == NDS_SLOT2_GUITARGRIP;
Piano.Enabled = selectedSlot2Type == NDS_SLOT2_EASYPIANO;
Paddle.Enabled = selectedSlot2Type == NDS_SLOT2_PADDLE;
Analog.Enabled = selectedSlot2Type == NDS_SLOT2_ANALOG;
LoadSaveStateInfo();
lagframecounter=0;
@ -2251,6 +2252,8 @@ int _main()
break;
case NDS_SLOT2_PASSME:
break;
case NDS_SLOT2_ANALOG:
break;
default:
slot2_device_type = NDS_SLOT2_NONE;
break;
@ -2258,9 +2261,10 @@ int _main()
slot2_Change((NDS_SLOT2_TYPE)slot2_device_type);
Guitar.Enabled = (slot2_device_type == NDS_SLOT2_GUITARGRIP)?true:false;
Piano.Enabled = (slot2_device_type == NDS_SLOT2_EASYPIANO)?true:false;
Paddle.Enabled = (slot2_device_type == NDS_SLOT2_PADDLE)?true:false;
Guitar.Enabled = slot2_device_type == NDS_SLOT2_GUITARGRIP;
Piano.Enabled = slot2_device_type == NDS_SLOT2_EASYPIANO;
Paddle.Enabled = slot2_device_type == NDS_SLOT2_PADDLE;
Analog.Enabled = slot2_device_type == NDS_SLOT2_ANALOG;
CommonSettings.WifiBridgeDeviceID = GetPrivateProfileInt("Wifi", "BridgeAdapter", 0, IniName);

View File

@ -514,15 +514,20 @@
#define IDC_WIFI_ENABLED 1065
#define IDC_STATIC_RANGE 1066
#define IDC_WIFI_COMPAT 1066
#define IDC_ANALOG_X 1066
#define IDC_TEXSCALE 1067
#define IDC_ANALOG_Y 1067
#define IDC_BADD 1068
#define IDC_LIST 1069
#define IDC_ANALOG_DEADZONE_SLIDER 1069
#define IDC_TEX_DEPOSTERIZE 1070
#define IDC_ANALOG_DEADZONE 1070
#define IDC_SNUMBER 1071
#define IDC_TEX_SMOOTH 1072
#define IDC_CHECK1 1074
#define IDC_CHECK2 1075
#define IDC_CAP0_SRC 1075
#define IDC_ANALOG_CIRCLE 1075
#define IDC_CHECK3 1076
#define IDC_CAP0_ONESHOT 1076
#define IDC_CHECK4 1077
@ -531,6 +536,7 @@
#define IDC_CHECK10 1079
#define IDC_CAP0_RUNNING 1079
#define IDC_BIG_ENDIAN 1079
#define IDC_ANALOG_JOINED 1079
#define IDC_CHECK6 1080
#define IDC_CAP1_SRC 1080
#define IDC_CAP1_ONESHOT 1081
@ -923,6 +929,7 @@
#define IDD_SLOT1_R4 10012
#define IDD_SLOT1_DEBUG 10013
#define IDD_GBASLOT_PADDLE 10014
#define IDD_GBASLOT_ANALOG 10015
#define IDM_FILE_STOPAVI 40000
#define IDM_SCREENSEP_NONE 40000
#define IDM_FILE_STOPWAV 40001
@ -1154,9 +1161,9 @@
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 128
#define _APS_NEXT_RESOURCE_VALUE 130
#define _APS_NEXT_COMMAND_VALUE 40159
#define _APS_NEXT_CONTROL_VALUE 1066
#define _APS_NEXT_CONTROL_VALUE 1071
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@ -1589,6 +1589,22 @@ BEGIN
RTEXT "Decrease",-1,81,40,44,8
END
IDD_GBASLOT_ANALOG DIALOGEX 7, 48, 302, 109
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE | WS_SYSMENU
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
CONTROL " ",IDC_ANALOG_X,"InputCustomAnalog",WS_TABSTOP,75,17,71,12,WS_EX_CLIENTEDGE
CONTROL " ",IDC_ANALOG_Y,"InputCustomAnalog",WS_TABSTOP,180,17,71,12,WS_EX_CLIENTEDGE
RTEXT "Left",-1,47,20,24,8
CTEXT "Analog Stick:",-1,137,2,51,12
RTEXT "Up",-1,152,20,24,8
CONTROL "",IDC_ANALOG_DEADZONE_SLIDER,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,147,41,100,15
EDITTEXT IDC_ANALOG_DEADZONE,105,41,40,14,ES_AUTOHSCROLL | ES_NUMBER
RTEXT "Deadzone",-1,61,41,38,14,SS_CENTERIMAGE
CONTROL "Joined Deadzone",IDC_ANALOG_JOINED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,66,63,71,10
CONTROL "Circular Deadzone",IDC_ANALOG_CIRCLE,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,74,77,74,10
END
/////////////////////////////////////////////////////////////////////////////
//
@ -1968,11 +1984,23 @@ BEGIN
BOTTOMMARGIN, 229
END
IDD_GBASLOT, DIALOG
BEGIN
END
IDD_GBASLOT_CFLASH, DIALOG
BEGIN
BOTTOMMARGIN, 106
END
IDD_GBASLOT_GBAGAME, DIALOG
BEGIN
END
IDD_GBASLOT_GUITARGRIP, DIALOG
BEGIN
END
IDD_MEM_VIEW, DIALOG
BEGIN
RIGHTMARGIN, 438
@ -2066,6 +2094,14 @@ BEGIN
TOPMARGIN, 7
BOTTOMMARGIN, 34
END
IDD_GBASLOT_PADDLE, DIALOG
BEGIN
END
IDD_GBASLOT_ANALOG, DIALOG
BEGIN
END
END
#endif // APSTUDIO_INVOKED

View File

@ -58,6 +58,7 @@ void slot2_Init()
extern TISlot2InterfaceConstructor construct_Slot2_EasyPiano;
extern TISlot2InterfaceConstructor construct_Slot2_Paddle;
extern TISlot2InterfaceConstructor construct_Slot2_PassME;
extern TISlot2InterfaceConstructor construct_Slot2_Analog;
slot2_List[NDS_SLOT2_NONE] = construct_Slot2_None();
slot2_List[NDS_SLOT2_AUTO] = construct_Slot2_Auto();
@ -69,6 +70,7 @@ void slot2_Init()
slot2_List[NDS_SLOT2_EASYPIANO] = construct_Slot2_EasyPiano();
slot2_List[NDS_SLOT2_PADDLE] = construct_Slot2_Paddle();
slot2_List[NDS_SLOT2_PASSME] = construct_Slot2_PassME();
slot2_List[NDS_SLOT2_ANALOG] = construct_Slot2_Analog();
}
@ -252,6 +254,7 @@ NDS_SLOT2_TYPE slot2_DetermineTypeByGameCode(const char *theGameCode)
{"CV8", NDS_SLOT2_PADDLE}, // Space Invaders Extreme 2
{"AMH", NDS_SLOT2_RUMBLEPAK}, // Metroid Prime Hunters
{"AP2", NDS_SLOT2_RUMBLEPAK}, // Metroid Prime Pinball
{"ASM", NDS_SLOT2_ANALOG}, // Super Mario 64 DS
};
for(size_t i = 0; i < ARRAY_SIZE(gameCodeDeviceTypes); i++)

View File

@ -97,6 +97,7 @@ enum NDS_SLOT2_TYPE
NDS_SLOT2_EASYPIANO, // 0x06 - Easy Piano
NDS_SLOT2_PADDLE, // 0x07 - Arkanoids DS paddle
NDS_SLOT2_PASSME, // 0x08 - PassME/Homebrew
NDS_SLOT2_ANALOG, // 0x09 - Analog Stick Hack
NDS_SLOT2_COUNT // use for counter addons - MUST TO BE LAST!!!
};
@ -157,4 +158,5 @@ void Paddle_SetValue(u16 theValue);
extern void guitarGrip_setKey(bool green, bool red, bool yellow, bool blue); // Guitar grip keys
extern void piano_setKey(bool c, bool cs, bool d, bool ds, bool e, bool f, bool fs, bool g, bool gs, bool a, bool as, bool b, bool hic); //piano keys
extern void analog_setValue(float x, float y); // Analog Stick State
#endif //__SLOT_H__