Snes Support (#205)

* SNES product ID found, logic partially filled. Icon added.

* Bluetooth working, icon working. Wired still laggy.

* Fixed wired connection and stick centering issues
This commit is contained in:
Kurt Yilmaz 2019-11-10 09:17:23 -05:00 committed by David Khachaturov
parent 8838f2edba
commit ac17d06aa0
8 changed files with 223 additions and 191 deletions

View file

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2036
# Visual Studio Version 16
VisualStudioVersion = 16.0.29418.71
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterJoyForCemu", "BetterJoyForCemu\BetterJoyForCemu.csproj", "{1BF709E9-C133-41DF-933A-C9FF3F664C7B}"
EndProject
@ -15,8 +15,8 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Debug|Any CPU.ActiveCfg = Debug|x64
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Debug|Any CPU.Build.0 = Debug|x64
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Debug|x64.ActiveCfg = Debug|x64
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Debug|x64.Build.0 = Debug|x64
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Debug|x86.ActiveCfg = Debug|x86

View file

@ -174,6 +174,7 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Icons\snes.png" />
<None Include="Properties\app.manifest" />
<Content Include="Icons\betterjoyforcemu_icon.ico" />
<Content Include="hidapi.dll">
@ -215,9 +216,9 @@
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Fody.4.2.1\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.4.2.1\build\Fody.targets'))" />
<Error Condition="!Exists('..\packages\Costura.Fody.3.3.1\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.3.3.1\build\Costura.Fody.props'))" />
<Error Condition="!Exists('..\packages\Fielder.Fody.1.2.3\build\Fielder.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fielder.Fody.1.2.3\build\Fielder.Fody.props'))" />
<Error Condition="!Exists('..\packages\Fody.4.2.1\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.4.2.1\build\Fody.targets'))" />
</Target>
<Import Project="..\packages\Fody.4.2.1\build\Fody.targets" Condition="Exists('..\packages\Fody.4.2.1\build\Fody.targets')" />
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View file

@ -18,6 +18,7 @@ namespace BetterJoyForCemu {
public string path = String.Empty;
public bool isPro = false;
public bool isSnes = false;
bool isUSB = false;
public Joycon other;
@ -234,7 +235,7 @@ namespace BetterJoyForCemu {
private float[] activeData;
public Joycon(IntPtr handle_, bool imu, bool localize, float alpha, bool left, string path, string serialNum, int id = 0, bool isPro = false) {
public Joycon(IntPtr handle_, bool imu, bool localize, float alpha, bool left, string path, string serialNum, int id = 0, bool isPro = false, bool isSnes = false) {
serial_number = serialNum;
activeData = new float[6];
handle = handle_;
@ -246,7 +247,8 @@ namespace BetterJoyForCemu {
PadId = id;
LED = (byte)(0x1 << PadId);
this.isPro = isPro;
this.isPro = isPro || isSnes;
this.isSnes = isSnes;
isUSB = serialNum == "000000000001";
this.path = path;
@ -474,7 +476,7 @@ namespace BetterJoyForCemu {
xin.SendReport(report);
}
if (ts_en == raw_buf[1]) {
if (ts_en == raw_buf[1] && !isSnes) {
form.AppendTextBox("Duplicate timestamp enqueued.\r\n");
DebugPrint(string.Format("Duplicate timestamp enqueued. TS: {0:X2}", ts_en), DebugType.THREADING);
}
@ -490,7 +492,7 @@ namespace BetterJoyForCemu {
Stopwatch watch = new Stopwatch();
watch.Start();
while (!stop_polling & state > state_.NO_JOYCONS) {
if (isUSB || rumble_obj.t > 0)
if (!isSnes && (isUSB || rumble_obj.t > 0))
SendRumble(rumble_obj.GetData());
else if (watch.ElapsedMilliseconds >= 1000) {
// Send a no-op operation as heartbeat to keep connection alive.
@ -541,39 +543,40 @@ namespace BetterJoyForCemu {
bool swapXY = Boolean.Parse(ConfigurationManager.AppSettings["SwapXY"]);
private int ProcessButtonsAndStick(byte[] report_buf) {
if (report_buf[0] == 0x00) return -1;
if (!isSnes) {
stick_raw[0] = report_buf[6 + (isLeft ? 0 : 3)];
stick_raw[1] = report_buf[7 + (isLeft ? 0 : 3)];
stick_raw[2] = report_buf[8 + (isLeft ? 0 : 3)];
stick_raw[0] = report_buf[6 + (isLeft ? 0 : 3)];
stick_raw[1] = report_buf[7 + (isLeft ? 0 : 3)];
stick_raw[2] = report_buf[8 + (isLeft ? 0 : 3)];
if (isPro) {
stick2_raw[0] = report_buf[6 + (!isLeft ? 0 : 3)];
stick2_raw[1] = report_buf[7 + (!isLeft ? 0 : 3)];
stick2_raw[2] = report_buf[8 + (!isLeft ? 0 : 3)];
}
if (isPro) {
stick2_raw[0] = report_buf[6 + (!isLeft ? 0 : 3)];
stick2_raw[1] = report_buf[7 + (!isLeft ? 0 : 3)];
stick2_raw[2] = report_buf[8 + (!isLeft ? 0 : 3)];
}
stick_precal[0] = (UInt16)(stick_raw[0] | ((stick_raw[1] & 0xf) << 8));
stick_precal[1] = (UInt16)((stick_raw[1] >> 4) | (stick_raw[2] << 4));
ushort[] cal = form.nonOriginal ? new ushort[6] { 2048, 2048, 2048, 2048, 2048, 2048 } : stick_cal;
ushort dz = form.nonOriginal ? (ushort)200 : deadzone;
stick = CenterSticks(stick_precal, cal, dz);
stick_precal[0] = (UInt16)(stick_raw[0] | ((stick_raw[1] & 0xf) << 8));
stick_precal[1] = (UInt16)((stick_raw[1] >> 4) | (stick_raw[2] << 4));
ushort[] cal = form.nonOriginal ? new ushort[6] { 2048, 2048, 2048, 2048, 2048, 2048 } : stick_cal;
ushort dz = form.nonOriginal ? (ushort)200 : deadzone;
stick = CenterSticks(stick_precal, cal, dz);
if (isPro) {
stick2_precal[0] = (UInt16)(stick2_raw[0] | ((stick2_raw[1] & 0xf) << 8));
stick2_precal[1] = (UInt16)((stick2_raw[1] >> 4) | (stick2_raw[2] << 4));
stick2 = CenterSticks(stick2_precal, form.nonOriginal ? cal : stick2_cal, deadzone2);
}
if (isPro) {
stick2_precal[0] = (UInt16)(stick2_raw[0] | ((stick2_raw[1] & 0xf) << 8));
stick2_precal[1] = (UInt16)((stick2_raw[1] >> 4) | (stick2_raw[2] << 4));
stick2 = CenterSticks(stick2_precal, form.nonOriginal ? cal : stick2_cal, deadzone2);
}
// Read other Joycon's sticks
if (isLeft && other != null && other != this) {
stick2 = otherStick;
other.otherStick = stick;
}
// Read other Joycon's sticks
if (isLeft && other != null && other != this) {
stick2 = otherStick;
other.otherStick = stick;
}
if (!isLeft && other != null && other != this) {
Array.Copy(stick, stick2, 2);
stick = otherStick;
other.otherStick = stick2;
if (!isLeft && other != null && other != this) {
Array.Copy(stick, stick2, 2);
stick = otherStick;
other.otherStick = stick2;
}
}
//
@ -688,14 +691,16 @@ namespace BetterJoyForCemu {
}
if (xin != null) {
if (other != null || isPro) { // no need for && other != this
report.SetAxis(Xbox360Axes.LeftThumbX, CastStickValue((other == this && !isLeft) ? stick2[0] : stick[0]));
report.SetAxis(Xbox360Axes.LeftThumbY, CastStickValue((other == this && !isLeft) ? stick2[1] : stick[1]));
report.SetAxis(Xbox360Axes.RightThumbX, CastStickValue((other == this && !isLeft) ? stick[0] : stick2[0]));
report.SetAxis(Xbox360Axes.RightThumbY, CastStickValue((other == this && !isLeft) ? stick[1] : stick2[1]));
} else { // single joycon mode
report.SetAxis(Xbox360Axes.LeftThumbY, CastStickValue((isLeft ? 1 : -1) * stick[0]));
report.SetAxis(Xbox360Axes.LeftThumbX, CastStickValue((isLeft ? -1 : 1) * stick[1]));
if (!isSnes) {
if (other != null || isPro) { // no need for && other != this
report.SetAxis(Xbox360Axes.LeftThumbX, CastStickValue((other == this && !isLeft) ? stick2[0] : stick[0]));
report.SetAxis(Xbox360Axes.LeftThumbY, CastStickValue((other == this && !isLeft) ? stick2[1] : stick[1]));
report.SetAxis(Xbox360Axes.RightThumbX, CastStickValue((other == this && !isLeft) ? stick[0] : stick2[0]));
report.SetAxis(Xbox360Axes.RightThumbY, CastStickValue((other == this && !isLeft) ? stick[1] : stick2[1]));
} else { // single joycon mode
report.SetAxis(Xbox360Axes.LeftThumbY, CastStickValue((isLeft ? 1 : -1) * stick[0]));
report.SetAxis(Xbox360Axes.LeftThumbX, CastStickValue((isLeft ? -1 : 1) * stick[1]));
}
}
report.SetAxis(Xbox360Axes.LeftTrigger, (short)(buttons[(int)(isLeft ? Button.SHOULDER_2 : Button.SHOULDER2_2)] ? Int16.MaxValue : 0));
report.SetAxis(Xbox360Axes.RightTrigger, (short)(buttons[(int)(isLeft ? Button.SHOULDER2_2 : Button.SHOULDER_2)] ? Int16.MaxValue : 0));
@ -705,82 +710,84 @@ namespace BetterJoyForCemu {
}
private void ExtractIMUValues(byte[] report_buf, int n = 0) {
gyr_r[0] = (Int16)(report_buf[19 + n * 12] | ((report_buf[20 + n * 12] << 8) & 0xff00));
gyr_r[1] = (Int16)(report_buf[21 + n * 12] | ((report_buf[22 + n * 12] << 8) & 0xff00));
gyr_r[2] = (Int16)(report_buf[23 + n * 12] | ((report_buf[24 + n * 12] << 8) & 0xff00));
acc_r[0] = (Int16)(report_buf[13 + n * 12] | ((report_buf[14 + n * 12] << 8) & 0xff00));
acc_r[1] = (Int16)(report_buf[15 + n * 12] | ((report_buf[16 + n * 12] << 8) & 0xff00));
acc_r[2] = (Int16)(report_buf[17 + n * 12] | ((report_buf[18 + n * 12] << 8) & 0xff00));
if (!isSnes) {
gyr_r[0] = (Int16)(report_buf[19 + n * 12] | ((report_buf[20 + n * 12] << 8) & 0xff00));
gyr_r[1] = (Int16)(report_buf[21 + n * 12] | ((report_buf[22 + n * 12] << 8) & 0xff00));
gyr_r[2] = (Int16)(report_buf[23 + n * 12] | ((report_buf[24 + n * 12] << 8) & 0xff00));
acc_r[0] = (Int16)(report_buf[13 + n * 12] | ((report_buf[14 + n * 12] << 8) & 0xff00));
acc_r[1] = (Int16)(report_buf[15 + n * 12] | ((report_buf[16 + n * 12] << 8) & 0xff00));
acc_r[2] = (Int16)(report_buf[17 + n * 12] | ((report_buf[18 + n * 12] << 8) & 0xff00));
if (form.nonOriginal) {
for (int i = 0; i < 3; ++i) {
switch (i) {
case 0:
acc_g.X = (acc_r[i] - activeData[3]) * (1.0f / acc_sen[i]) * 4.0f;
gyr_g.X = (gyr_r[i] - activeData[0]) * (816.0f / gyr_sen[i]);
if (form.calibrate) {
form.xA.Add(acc_r[i]);
form.xG.Add(gyr_r[i]);
}
break;
case 1:
acc_g.Y = (!isLeft ? -1 : 1) * (acc_r[i] - activeData[4]) * (1.0f / acc_sen[i]) * 4.0f;
gyr_g.Y = -(!isLeft ? -1 : 1) * (gyr_r[i] - activeData[1]) * (816.0f / gyr_sen[i]);
if (form.calibrate) {
form.yA.Add(acc_r[i]);
form.yG.Add(gyr_r[i]);
}
break;
case 2:
acc_g.Z = (!isLeft ? -1 : 1) * (acc_r[i] - activeData[5]) * (1.0f / acc_sen[i]) * 4.0f;
gyr_g.Z = -(!isLeft ? -1 : 1) * (gyr_r[i] - activeData[2]) * (816.0f / gyr_sen[i]);
if (form.calibrate) {
form.zA.Add(acc_r[i]);
form.zG.Add(gyr_r[i]);
}
break;
if (form.nonOriginal) {
for (int i = 0; i < 3; ++i) {
switch (i) {
case 0:
acc_g.X = (acc_r[i] - activeData[3]) * (1.0f / acc_sen[i]) * 4.0f;
gyr_g.X = (gyr_r[i] - activeData[0]) * (816.0f / gyr_sen[i]);
if (form.calibrate) {
form.xA.Add(acc_r[i]);
form.xG.Add(gyr_r[i]);
}
break;
case 1:
acc_g.Y = (!isLeft ? -1 : 1) * (acc_r[i] - activeData[4]) * (1.0f / acc_sen[i]) * 4.0f;
gyr_g.Y = -(!isLeft ? -1 : 1) * (gyr_r[i] - activeData[1]) * (816.0f / gyr_sen[i]);
if (form.calibrate) {
form.yA.Add(acc_r[i]);
form.yG.Add(gyr_r[i]);
}
break;
case 2:
acc_g.Z = (!isLeft ? -1 : 1) * (acc_r[i] - activeData[5]) * (1.0f / acc_sen[i]) * 4.0f;
gyr_g.Z = -(!isLeft ? -1 : 1) * (gyr_r[i] - activeData[2]) * (816.0f / gyr_sen[i]);
if (form.calibrate) {
form.zA.Add(acc_r[i]);
form.zG.Add(gyr_r[i]);
}
break;
}
}
}
} else {
Int16[] offset;
if (isPro)
offset = pro_hor_offset;
else if (isLeft)
offset = left_hor_offset;
else
offset = right_hor_offset;
for (int i = 0; i < 3; ++i) {
switch (i) {
case 0:
acc_g.X = (acc_r[i] - offset[i]) * (1.0f / (acc_sensiti[i] - acc_neutral[i])) * 4.0f;
gyr_g.X = (gyr_r[i] - gyr_neutral[i]) * (816.0f / (gyr_sensiti[i] - gyr_neutral[i]));
break;
case 1:
acc_g.Y = (!isLeft ? -1 : 1) * (acc_r[i] - offset[i]) * (1.0f / (acc_sensiti[i] - acc_neutral[i])) * 4.0f;
gyr_g.Y = -(!isLeft ? -1 : 1) * (gyr_r[i] - gyr_neutral[i]) * (816.0f / (gyr_sensiti[i] - gyr_neutral[i]));
break;
case 2:
acc_g.Z = (!isLeft ? -1 : 1) * (acc_r[i] - offset[i]) * (1.0f / (acc_sensiti[i] - acc_neutral[i])) * 4.0f;
gyr_g.Z = -(!isLeft ? -1 : 1) * (gyr_r[i] - gyr_neutral[i]) * (816.0f / (gyr_sensiti[i] - gyr_neutral[i]));
break;
}
}
}
if (other == null && !isPro) { // single joycon mode; Z do not swap, rest do
if (isLeft) {
acc_g.X = -acc_g.X;
gyr_g.X = -gyr_g.X;
} else {
gyr_g.Y = -gyr_g.Y;
Int16[] offset;
if (isPro)
offset = pro_hor_offset;
else if (isLeft)
offset = left_hor_offset;
else
offset = right_hor_offset;
for (int i = 0; i < 3; ++i) {
switch (i) {
case 0:
acc_g.X = (acc_r[i] - offset[i]) * (1.0f / (acc_sensiti[i] - acc_neutral[i])) * 4.0f;
gyr_g.X = (gyr_r[i] - gyr_neutral[i]) * (816.0f / (gyr_sensiti[i] - gyr_neutral[i]));
break;
case 1:
acc_g.Y = (!isLeft ? -1 : 1) * (acc_r[i] - offset[i]) * (1.0f / (acc_sensiti[i] - acc_neutral[i])) * 4.0f;
gyr_g.Y = -(!isLeft ? -1 : 1) * (gyr_r[i] - gyr_neutral[i]) * (816.0f / (gyr_sensiti[i] - gyr_neutral[i]));
break;
case 2:
acc_g.Z = (!isLeft ? -1 : 1) * (acc_r[i] - offset[i]) * (1.0f / (acc_sensiti[i] - acc_neutral[i])) * 4.0f;
gyr_g.Z = -(!isLeft ? -1 : 1) * (gyr_r[i] - gyr_neutral[i]) * (816.0f / (gyr_sensiti[i] - gyr_neutral[i]));
break;
}
}
}
float temp = acc_g.X; acc_g.X = acc_g.Y; acc_g.Y = temp;
temp = gyr_g.X; gyr_g.X = gyr_g.Y; gyr_g.Y = temp;
if (other == null && !isPro) { // single joycon mode; Z do not swap, rest do
if (isLeft) {
acc_g.X = -acc_g.X;
gyr_g.X = -gyr_g.X;
} else {
gyr_g.Y = -gyr_g.Y;
}
float temp = acc_g.X; acc_g.X = acc_g.Y; acc_g.Y = temp;
temp = gyr_g.X; gyr_g.X = gyr_g.Y; gyr_g.Y = temp;
}
}
}
@ -855,31 +862,9 @@ namespace BetterJoyForCemu {
}
private void dump_calibration_data() {
byte[] buf_ = ReadSPI(0x80, (isLeft ? (byte)0x12 : (byte)0x1d), 9); // get user calibration data if possible
bool found = false;
for (int i = 0; i < 9; ++i) {
if (buf_[i] != 0xff) {
form.AppendTextBox("Using user stick calibration data.\r\n");
found = true;
break;
}
}
if (!found) {
form.AppendTextBox("Using factory stick calibration data.\r\n");
buf_ = ReadSPI(0x60, (isLeft ? (byte)0x3d : (byte)0x46), 9); // get user calibration data if possible
}
stick_cal[isLeft ? 0 : 2] = (UInt16)((buf_[1] << 8) & 0xF00 | buf_[0]); // X Axis Max above center
stick_cal[isLeft ? 1 : 3] = (UInt16)((buf_[2] << 4) | (buf_[1] >> 4)); // Y Axis Max above center
stick_cal[isLeft ? 2 : 4] = (UInt16)((buf_[4] << 8) & 0xF00 | buf_[3]); // X Axis Center
stick_cal[isLeft ? 3 : 5] = (UInt16)((buf_[5] << 4) | (buf_[4] >> 4)); // Y Axis Center
stick_cal[isLeft ? 4 : 0] = (UInt16)((buf_[7] << 8) & 0xF00 | buf_[6]); // X Axis Min below center
stick_cal[isLeft ? 5 : 1] = (UInt16)((buf_[8] << 4) | (buf_[7] >> 4)); // Y Axis Min below center
PrintArray(stick_cal, len: 6, start: 0, format: "Stick calibration data: {0:S}");
if (isPro) {
buf_ = ReadSPI(0x80, (!isLeft ? (byte)0x12 : (byte)0x1d), 9); // get user calibration data if possible
found = false;
if(!isSnes) {
byte[] buf_ = ReadSPI(0x80, (isLeft ? (byte)0x12 : (byte)0x1d), 9); // get user calibration data if possible
bool found = false;
for (int i = 0; i < 9; ++i) {
if (buf_[i] != 0xff) {
form.AppendTextBox("Using user stick calibration data.\r\n");
@ -889,69 +874,93 @@ namespace BetterJoyForCemu {
}
if (!found) {
form.AppendTextBox("Using factory stick calibration data.\r\n");
buf_ = ReadSPI(0x60, (!isLeft ? (byte)0x3d : (byte)0x46), 9); // get user calibration data if possible
buf_ = ReadSPI(0x60, (isLeft ? (byte)0x3d : (byte)0x46), 9); // get user calibration data if possible
}
stick2_cal[!isLeft ? 0 : 2] = (UInt16)((buf_[1] << 8) & 0xF00 | buf_[0]); // X Axis Max above center
stick2_cal[!isLeft ? 1 : 3] = (UInt16)((buf_[2] << 4) | (buf_[1] >> 4)); // Y Axis Max above center
stick2_cal[!isLeft ? 2 : 4] = (UInt16)((buf_[4] << 8) & 0xF00 | buf_[3]); // X Axis Center
stick2_cal[!isLeft ? 3 : 5] = (UInt16)((buf_[5] << 4) | (buf_[4] >> 4)); // Y Axis Center
stick2_cal[!isLeft ? 4 : 0] = (UInt16)((buf_[7] << 8) & 0xF00 | buf_[6]); // X Axis Min below center
stick2_cal[!isLeft ? 5 : 1] = (UInt16)((buf_[8] << 4) | (buf_[7] >> 4)); // Y Axis Min below center
stick_cal[isLeft ? 0 : 2] = (UInt16)((buf_[1] << 8) & 0xF00 | buf_[0]); // X Axis Max above center
stick_cal[isLeft ? 1 : 3] = (UInt16)((buf_[2] << 4) | (buf_[1] >> 4)); // Y Axis Max above center
stick_cal[isLeft ? 2 : 4] = (UInt16)((buf_[4] << 8) & 0xF00 | buf_[3]); // X Axis Center
stick_cal[isLeft ? 3 : 5] = (UInt16)((buf_[5] << 4) | (buf_[4] >> 4)); // Y Axis Center
stick_cal[isLeft ? 4 : 0] = (UInt16)((buf_[7] << 8) & 0xF00 | buf_[6]); // X Axis Min below center
stick_cal[isLeft ? 5 : 1] = (UInt16)((buf_[8] << 4) | (buf_[7] >> 4)); // Y Axis Min below center
PrintArray(stick2_cal, len: 6, start: 0, format: "Stick calibration data: {0:S}");
PrintArray(stick_cal, len: 6, start: 0, format: "Stick calibration data: {0:S}");
buf_ = ReadSPI(0x60, (!isLeft ? (byte)0x86 : (byte)0x98), 16);
deadzone2 = (UInt16)((buf_[4] << 8) & 0xF00 | buf_[3]);
}
if (isPro) {
buf_ = ReadSPI(0x80, (!isLeft ? (byte)0x12 : (byte)0x1d), 9); // get user calibration data if possible
found = false;
for (int i = 0; i < 9; ++i) {
if (buf_[i] != 0xff) {
form.AppendTextBox("Using user stick calibration data.\r\n");
found = true;
break;
}
}
if (!found) {
form.AppendTextBox("Using factory stick calibration data.\r\n");
buf_ = ReadSPI(0x60, (!isLeft ? (byte)0x3d : (byte)0x46), 9); // get user calibration data if possible
}
stick2_cal[!isLeft ? 0 : 2] = (UInt16)((buf_[1] << 8) & 0xF00 | buf_[0]); // X Axis Max above center
stick2_cal[!isLeft ? 1 : 3] = (UInt16)((buf_[2] << 4) | (buf_[1] >> 4)); // Y Axis Max above center
stick2_cal[!isLeft ? 2 : 4] = (UInt16)((buf_[4] << 8) & 0xF00 | buf_[3]); // X Axis Center
stick2_cal[!isLeft ? 3 : 5] = (UInt16)((buf_[5] << 4) | (buf_[4] >> 4)); // Y Axis Center
stick2_cal[!isLeft ? 4 : 0] = (UInt16)((buf_[7] << 8) & 0xF00 | buf_[6]); // X Axis Min below center
stick2_cal[!isLeft ? 5 : 1] = (UInt16)((buf_[8] << 4) | (buf_[7] >> 4)); // Y Axis Min below center
buf_ = ReadSPI(0x60, (isLeft ? (byte)0x86 : (byte)0x98), 16);
deadzone = (UInt16)((buf_[4] << 8) & 0xF00 | buf_[3]);
PrintArray(stick2_cal, len: 6, start: 0, format: "Stick calibration data: {0:S}");
buf_ = ReadSPI(0x80, 0x28, 10);
acc_neutral[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
acc_neutral[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
acc_neutral[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
buf_ = ReadSPI(0x60, (!isLeft ? (byte)0x86 : (byte)0x98), 16);
deadzone2 = (UInt16)((buf_[4] << 8) & 0xF00 | buf_[3]);
}
buf_ = ReadSPI(0x80, 0x2E, 10);
acc_sensiti[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
acc_sensiti[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
acc_sensiti[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
buf_ = ReadSPI(0x60, (isLeft ? (byte)0x86 : (byte)0x98), 16);
deadzone = (UInt16)((buf_[4] << 8) & 0xF00 | buf_[3]);
buf_ = ReadSPI(0x80, 0x34, 10);
gyr_neutral[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
gyr_neutral[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
gyr_neutral[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
buf_ = ReadSPI(0x80, 0x3A, 10);
gyr_sensiti[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
gyr_sensiti[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
gyr_sensiti[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
PrintArray(gyr_neutral, len: 3, d: DebugType.IMU, format: "User gyro neutral position: {0:S}");
// This is an extremely messy way of checking to see whether there is user stick calibration data present, but I've seen conflicting user calibration data on blank Joy-Cons. Worth another look eventually.
if (gyr_neutral[0] + gyr_neutral[1] + gyr_neutral[2] == -3 || Math.Abs(gyr_neutral[0]) > 100 || Math.Abs(gyr_neutral[1]) > 100 || Math.Abs(gyr_neutral[2]) > 100) {
buf_ = ReadSPI(0x60, 0x20, 10);
buf_ = ReadSPI(0x80, 0x28, 10);
acc_neutral[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
acc_neutral[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
acc_neutral[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
buf_ = ReadSPI(0x60, 0x26, 10);
buf_ = ReadSPI(0x80, 0x2E, 10);
acc_sensiti[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
acc_sensiti[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
acc_sensiti[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
buf_ = ReadSPI(0x60, 0x2C, 10);
buf_ = ReadSPI(0x80, 0x34, 10);
gyr_neutral[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
gyr_neutral[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
gyr_neutral[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
buf_ = ReadSPI(0x60, 0x32, 10);
buf_ = ReadSPI(0x80, 0x3A, 10);
gyr_sensiti[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
gyr_sensiti[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
gyr_sensiti[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
PrintArray(gyr_neutral, len: 3, d: DebugType.IMU, format: "Factory gyro neutral position: {0:S}");
PrintArray(gyr_neutral, len: 3, d: DebugType.IMU, format: "User gyro neutral position: {0:S}");
// This is an extremely messy way of checking to see whether there is user stick calibration data present, but I've seen conflicting user calibration data on blank Joy-Cons. Worth another look eventually.
if (gyr_neutral[0] + gyr_neutral[1] + gyr_neutral[2] == -3 || Math.Abs(gyr_neutral[0]) > 100 || Math.Abs(gyr_neutral[1]) > 100 || Math.Abs(gyr_neutral[2]) > 100) {
buf_ = ReadSPI(0x60, 0x20, 10);
acc_neutral[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
acc_neutral[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
acc_neutral[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
buf_ = ReadSPI(0x60, 0x26, 10);
acc_sensiti[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
acc_sensiti[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
acc_sensiti[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
buf_ = ReadSPI(0x60, 0x2C, 10);
gyr_neutral[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
gyr_neutral[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
gyr_neutral[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
buf_ = ReadSPI(0x60, 0x32, 10);
gyr_sensiti[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00));
gyr_sensiti[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00));
gyr_sensiti[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00));
PrintArray(gyr_neutral, len: 3, d: DebugType.IMU, format: "Factory gyro neutral position: {0:S}");
}
}
}

View file

@ -33,6 +33,7 @@ namespace BetterJoyForCemu {
private const ushort product_l = 0x2006;
private const ushort product_r = 0x2007;
private const ushort product_pro = 0x2009;
private const ushort product_snes = 0x2017;
public List<Joycon> j; // Array of all connected Joy-Cons
static JoyconManager instance;
@ -119,7 +120,9 @@ namespace BetterJoyForCemu {
enumerate.product_id = product_pro;
}
if ((enumerate.product_id == product_l || enumerate.product_id == product_r || enumerate.product_id == product_pro) && !ControllerAlreadyAdded(enumerate.path)) {
bool validController = (enumerate.product_id == product_l || enumerate.product_id == product_r ||
enumerate.product_id == product_pro || enumerate.product_id == product_snes);
if (validController && !ControllerAlreadyAdded(enumerate.path)) {
switch (enumerate.product_id) {
case product_l:
isLeft = true;
@ -130,6 +133,9 @@ namespace BetterJoyForCemu {
case product_pro:
isLeft = true;
form.AppendTextBox("Pro controller connected.\r\n"); break;
case product_snes:
isLeft = true;
form.AppendTextBox("SNES controller connected.\r\n"); break;
default:
form.AppendTextBox("Non Joy-Con Nintendo input device skipped.\r\n"); break;
}
@ -168,8 +174,10 @@ namespace BetterJoyForCemu {
break;
}
j.Add(new Joycon(handle, EnableIMU, EnableLocalize & EnableIMU, 0.05f, isLeft, enumerate.path, enumerate.serial_number, j.Count, enumerate.product_id == product_pro));
bool isPro = enumerate.product_id == product_pro;
bool isSnes = enumerate.product_id == product_snes;
j.Add(new Joycon(handle, EnableIMU, EnableLocalize & EnableIMU, 0.05f, isLeft, enumerate.path, enumerate.serial_number, j.Count, isPro, isSnes));
foundNew = true;
j.Last().form = form;
@ -186,6 +194,8 @@ namespace BetterJoyForCemu {
temp = Properties.Resources.jc_right_s; break;
case (product_pro):
temp = Properties.Resources.pro; break;
case (product_snes):
temp = Properties.Resources.snes; break;
default:
temp = Properties.Resources.cross; break;
}
@ -257,13 +267,12 @@ namespace BetterJoyForCemu {
jc.Attach(leds_: jc.LED);
bool on = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).AppSettings.Settings["HomeLEDOn"].Value.ToLower() == "true";
foreach (Joycon j in Program.mgr.j)
{
j.SetHomeLight(on);
}
bool on = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).AppSettings.Settings["HomeLEDOn"].Value.ToLower() == "true";
foreach (Joycon j in Program.mgr.j) {
j.SetHomeLight(on);
}
jc.Begin();
jc.Begin();
if (form.nonOriginal) {
jc.getActiveData();
}

View file

@ -19,7 +19,7 @@ namespace BetterJoyForCemu.Properties {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
@ -129,5 +129,15 @@ namespace BetterJoyForCemu.Properties {
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap snes {
get {
object obj = ResourceManager.GetObject("snes", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

View file

@ -118,6 +118,9 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="snes" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\snes.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="pro" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\pro.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>

View file

@ -220,4 +220,4 @@ Many thanks to [nefarius](https://github.com/nefarius/ViGEm) for his ViGEm proje
A last thanks goes out to [dekuNukem](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering) for his documentation, especially on the SPI calibration data and the IMU sensor notes!
Icons (modified): "[Switch Pro Controller](https://thenounproject.com/term/nintendo-switch/930119/)", "[
Switch Detachable Controller Left](https://thenounproject.com/remsing/uploads/?i=930115)", "[Switch Detachable Controller Right](https://thenounproject.com/remsing/uploads/?i=930121)" icons by Chad Remsing from [the Noun Project](http://thenounproject.com/).
Switch Detachable Controller Left](https://thenounproject.com/remsing/uploads/?i=930115)", "[Switch Detachable Controller Right](https://thenounproject.com/remsing/uploads/?i=930121)" icons by Chad Remsing from [the Noun Project](http://thenounproject.com/). [Super Nintendo Controller](https://thenounproject.com/themizarkshow/collection/vectogram/?i=193592) icon by Mark Davis from the [the Noun Project](http://thenounproject.com/); icon modified by [Amy Alexander](https://www.linkedin.com/in/-amy-alexander/).