diff --git a/.gitignore b/.gitignore
index 0f947f2..7350d1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+# VS Code configuration folder
+.vscode/
+
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
@@ -83,7 +86,6 @@ StyleCopReport.xml
*.tmp
*.tmp_proj
*_wpftmp.csproj
-*.log
*.vspscc
*.vssscc
.builds
@@ -346,3 +348,5 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
+
+RB4InstrumentMapper_log.txt
\ No newline at end of file
diff --git a/App.config b/App.config
deleted file mode 100644
index fb35790..0000000
--- a/App.config
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/App.xaml.cs b/App.xaml.cs
deleted file mode 100644
index 1608c97..0000000
--- a/App.xaml.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Diagnostics;
-using System.Globalization;
-using System.Linq;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-
-namespace RB4InstrumentMapper
-{
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App : Application
- {
- ///
- /// Event handler for AppDomain.CurrentDomain.UnhandledException.
- ///
- ///
- /// Logs the exception info to a file and prompts the user with the exception message.
- ///
- public static void App_UnhandledException(object sender, UnhandledExceptionEventArgs args)
- {
- // The unhandled exception that fired off the event
- Exception unhandledException = args.ExceptionObject as Exception;
-
- // String representation of exception info
- string exceptionString = unhandledException.ToString();
-
- // Index to substring with to create exceptionMessage
- int removeIndex = exceptionString.IndexOf(Environment.NewLine);
- // First line of exceptionString (can't use Split since \n isn't a valid char)
- // Not using Exception.Message since it doesn't contain the exception type
- string exceptionMessage = exceptionString.Substring(0, removeIndex);
-
- try // Use an alternate message if log can't be written due to an exception
- {
- // User's Documents folder
- string docsFolder = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
- // Documents\RB4InstrumentMapper\Logs
- string logFolderPath = Path.Combine(docsFolder, "RB4InstrumentMapper\\Logs");
- if (!Directory.Exists(logFolderPath))
- {
- // Create if it doesn't exist
- Directory.CreateDirectory(logFolderPath);
- }
-
- // Current date/time
- DateTime currentTime = DateTime.Now;
- // Date to append to the log file name
- string fileDateTime = currentTime.ToString("yyyy-MM-dd_HH-mm-ss", CultureInfo.InvariantCulture);
- // Log file name with date appended
- string logFile = $"log_{fileDateTime}.txt";
- // Documents\RB4InstrumentMapper\Logs\log_.txt
- string logFilePath = Path.Combine(logFolderPath, logFile);
-
- // Write to log file
- using (StreamWriter errorLog = File.AppendText(logFilePath))
- {
- // Message to write to the log
- StringBuilder message = new StringBuilder();
-
- // Current date and time, formatted in Yeat/Month/Date Hour:Minute:Second with the invariant culture
- string logDateTime = currentTime.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture);
- message.AppendLine($"[{logDateTime}] ERROR:");
- message.AppendLine("------------------------------");
- message.AppendLine(exceptionString);
- message.AppendLine("------------------------------");
-
- errorLog.Write(message.ToString());
- }
-
- // Prompt user
- MessageBoxResult result = MessageBox.Show(
- $"An unhandled error has occured:\n\n{exceptionMessage}\n\nA log of the error has been created, do you want to open it?",
- "Error",
- MessageBoxButton.YesNo,
- MessageBoxImage.Error
- );
- // If user requested to, open the log
- if (result == MessageBoxResult.Yes)
- {
- Process.Start(logFilePath);
- }
- }
- catch (Exception e)
- {
- // String representation of exception info
- string fileExceptionString = e.ToString();
- // Index to substring with to create exceptionMessage
- int fileExRemoveIndex = fileExceptionString.IndexOf(Environment.NewLine);
- // First line of exceptionString (can't use Split since \n isn't a valid char)
- // Not using Exception.Message since it doesn't contain the exception type
- string fileExceptionMessage = fileExceptionString.Substring(0, fileExRemoveIndex);
-
- // Alternate prompt indicating log wasn't able to be created
- MessageBox.Show(
- $"An unhandled error has occured:\n\n{exceptionMessage}\n\nAn attempt to log the error was made, but failed:\n\n{fileExceptionMessage}",
- "Error",
- MessageBoxButton.OK,
- MessageBoxImage.Error
- );
- }
-
- // Close program
- MessageBox.Show(
- "The program will now shut down.",
- "Error",
- MessageBoxButton.OK,
- MessageBoxImage.Error
- );
- Application.Current.Shutdown();
- }
- }
-}
diff --git a/Dependencies/x86/vJoyInterface.dll b/Dependencies/x86/vJoyInterface.dll
deleted file mode 100644
index ba94762..0000000
Binary files a/Dependencies/x86/vJoyInterface.dll and /dev/null differ
diff --git a/Dependencies/x86/vJoyInterfaceWrap.dll b/Dependencies/x86/vJoyInterfaceWrap.dll
deleted file mode 100644
index e00f683..0000000
Binary files a/Dependencies/x86/vJoyInterfaceWrap.dll and /dev/null differ
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..378d43c
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,9 @@
+
+
+
+
+ 5.3.0.0
+ $(MSBuildThisFileDirectory)Resources\
+
+
+
\ No newline at end of file
diff --git a/Docs/Images/Peripherals/guitar-hero-live-guitar.png b/Docs/Images/Peripherals/guitar-hero-live-guitar.png
new file mode 100644
index 0000000..4ebe3b0
Binary files /dev/null and b/Docs/Images/Peripherals/guitar-hero-live-guitar.png differ
diff --git a/Docs/Images/Peripherals/riffmaster-dongle-front.png b/Docs/Images/Peripherals/riffmaster-dongle-front.png
new file mode 100644
index 0000000..2d55e72
Binary files /dev/null and b/Docs/Images/Peripherals/riffmaster-dongle-front.png differ
diff --git a/Docs/Images/Peripherals/riffmaster.png b/Docs/Images/Peripherals/riffmaster.png
new file mode 100644
index 0000000..47d6125
Binary files /dev/null and b/Docs/Images/Peripherals/riffmaster.png differ
diff --git a/Docs/Images/Peripherals/rock-band-4-drums.png b/Docs/Images/Peripherals/rock-band-4-drums.png
new file mode 100644
index 0000000..ae3097f
Binary files /dev/null and b/Docs/Images/Peripherals/rock-band-4-drums.png differ
diff --git a/Docs/Images/Peripherals/rock-band-4-jaguar-small.png b/Docs/Images/Peripherals/rock-band-4-jaguar-small.png
new file mode 100644
index 0000000..6b4d40f
Binary files /dev/null and b/Docs/Images/Peripherals/rock-band-4-jaguar-small.png differ
diff --git a/Docs/Images/Peripherals/rock-band-4-jaguar.png b/Docs/Images/Peripherals/rock-band-4-jaguar.png
new file mode 100644
index 0000000..0efaf6f
Binary files /dev/null and b/Docs/Images/Peripherals/rock-band-4-jaguar.png differ
diff --git a/Docs/Images/Peripherals/rock-band-4-stratocaster.png b/Docs/Images/Peripherals/rock-band-4-stratocaster.png
new file mode 100644
index 0000000..a14ad08
Binary files /dev/null and b/Docs/Images/Peripherals/rock-band-4-stratocaster.png differ
diff --git a/Docs/Images/Peripherals/rock-band-4-wireless-legacy.png b/Docs/Images/Peripherals/rock-band-4-wireless-legacy.png
new file mode 100644
index 0000000..4e8b693
Binary files /dev/null and b/Docs/Images/Peripherals/rock-band-4-wireless-legacy.png differ
diff --git a/Docs/Images/Peripherals/xbox-one-receiver-gen-1.png b/Docs/Images/Peripherals/xbox-one-receiver-gen-1.png
new file mode 100644
index 0000000..b78cb55
Binary files /dev/null and b/Docs/Images/Peripherals/xbox-one-receiver-gen-1.png differ
diff --git a/Docs/Images/Peripherals/xbox-one-receiver-gen-2.png b/Docs/Images/Peripherals/xbox-one-receiver-gen-2.png
new file mode 100644
index 0000000..7d7559d
Binary files /dev/null and b/Docs/Images/Peripherals/xbox-one-receiver-gen-2.png differ
diff --git a/Docs/Images/ProgramScreenshot.png b/Docs/Images/ProgramScreenshot.png
deleted file mode 100644
index 5f0f194..0000000
Binary files a/Docs/Images/ProgramScreenshot.png and /dev/null differ
diff --git a/Docs/Images/Readme/clone-hero-assign-controller.png b/Docs/Images/Readme/clone-hero-assign-controller.png
new file mode 100644
index 0000000..9e00b02
Binary files /dev/null and b/Docs/Images/Readme/clone-hero-assign-controller.png differ
diff --git a/Docs/Images/Readme/clone-hero-assign-controller_es.png b/Docs/Images/Readme/clone-hero-assign-controller_es.png
new file mode 100644
index 0000000..43523d4
Binary files /dev/null and b/Docs/Images/Readme/clone-hero-assign-controller_es.png differ
diff --git a/Docs/Images/Readme/clone-hero-bind-controls.png b/Docs/Images/Readme/clone-hero-bind-controls.png
new file mode 100644
index 0000000..20a8a56
Binary files /dev/null and b/Docs/Images/Readme/clone-hero-bind-controls.png differ
diff --git a/Docs/Images/Readme/clone-hero-bind-controls_es.png b/Docs/Images/Readme/clone-hero-bind-controls_es.png
new file mode 100644
index 0000000..3c2735d
Binary files /dev/null and b/Docs/Images/Readme/clone-hero-bind-controls_es.png differ
diff --git a/Docs/Images/Readme/clone-hero-press-space.png b/Docs/Images/Readme/clone-hero-press-space.png
new file mode 100644
index 0000000..6bc974e
Binary files /dev/null and b/Docs/Images/Readme/clone-hero-press-space.png differ
diff --git a/Docs/Images/Readme/clone-hero-press-space_es.png b/Docs/Images/Readme/clone-hero-press-space_es.png
new file mode 100644
index 0000000..2d47b6a
Binary files /dev/null and b/Docs/Images/Readme/clone-hero-press-space_es.png differ
diff --git a/Docs/Images/Readme/controller-emulation-mode.png b/Docs/Images/Readme/controller-emulation-mode.png
new file mode 100644
index 0000000..1214ef4
Binary files /dev/null and b/Docs/Images/Readme/controller-emulation-mode.png differ
diff --git a/Docs/Images/Readme/program-screenshot.png b/Docs/Images/Readme/program-screenshot.png
new file mode 100644
index 0000000..b1fe409
Binary files /dev/null and b/Docs/Images/Readme/program-screenshot.png differ
diff --git a/Docs/Images/Readme/rpcs3-gamepad-settings.png b/Docs/Images/Readme/rpcs3-gamepad-settings.png
new file mode 100644
index 0000000..e66f752
Binary files /dev/null and b/Docs/Images/Readme/rpcs3-gamepad-settings.png differ
diff --git a/Docs/Images/Readme/rpcs3-pads-icon.png b/Docs/Images/Readme/rpcs3-pads-icon.png
new file mode 100644
index 0000000..f445bcc
Binary files /dev/null and b/Docs/Images/Readme/rpcs3-pads-icon.png differ
diff --git a/Docs/Images/Readme/usb-configure-button.png b/Docs/Images/Readme/usb-configure-button.png
new file mode 100644
index 0000000..3ac38d5
Binary files /dev/null and b/Docs/Images/Readme/usb-configure-button.png differ
diff --git a/Docs/Images/Readme/usb-configure-left.png b/Docs/Images/Readme/usb-configure-left.png
new file mode 100644
index 0000000..c8253f3
Binary files /dev/null and b/Docs/Images/Readme/usb-configure-left.png differ
diff --git a/Docs/Images/Readme/usb-configure-right.png b/Docs/Images/Readme/usb-configure-right.png
new file mode 100644
index 0000000..a1c0cd3
Binary files /dev/null and b/Docs/Images/Readme/usb-configure-right.png differ
diff --git a/Docs/Images/vJoyConfiguration.png b/Docs/Images/Readme/vjoy-configuration.png
similarity index 100%
rename from Docs/Images/vJoyConfiguration.png
rename to Docs/Images/Readme/vjoy-configuration.png
diff --git a/Docs/Images/WinUSB/Install/zadig-list-all-devices.png b/Docs/Images/WinUSB/Install/zadig-list-all-devices.png
new file mode 100644
index 0000000..9a58583
Binary files /dev/null and b/Docs/Images/WinUSB/Install/zadig-list-all-devices.png differ
diff --git a/Docs/Images/WinUSB/Install/zadig-select-device.png b/Docs/Images/WinUSB/Install/zadig-select-device.png
new file mode 100644
index 0000000..6808025
Binary files /dev/null and b/Docs/Images/WinUSB/Install/zadig-select-device.png differ
diff --git a/Docs/Images/WinUSB/Install/zadig-select-driver.png b/Docs/Images/WinUSB/Install/zadig-select-driver.png
new file mode 100644
index 0000000..775cf20
Binary files /dev/null and b/Docs/Images/WinUSB/Install/zadig-select-driver.png differ
diff --git a/Docs/Images/WinUSB/Uninstall/device-manager-delete-driver-software.png b/Docs/Images/WinUSB/Uninstall/device-manager-delete-driver-software.png
new file mode 100644
index 0000000..77530ff
Binary files /dev/null and b/Docs/Images/WinUSB/Uninstall/device-manager-delete-driver-software.png differ
diff --git a/Docs/Images/WinUSB/Uninstall/device-manager-restored-device.png b/Docs/Images/WinUSB/Uninstall/device-manager-restored-device.png
new file mode 100644
index 0000000..9674f8b
Binary files /dev/null and b/Docs/Images/WinUSB/Uninstall/device-manager-restored-device.png differ
diff --git a/Docs/Images/WinUSB/Uninstall/device-manager-uninstall-device.png b/Docs/Images/WinUSB/Uninstall/device-manager-uninstall-device.png
new file mode 100644
index 0000000..a001956
Binary files /dev/null and b/Docs/Images/WinUSB/Uninstall/device-manager-uninstall-device.png differ
diff --git a/Docs/Images/WinUSB/Uninstall/device-manager-usb-device.png b/Docs/Images/WinUSB/Uninstall/device-manager-usb-device.png
new file mode 100644
index 0000000..90c88d1
Binary files /dev/null and b/Docs/Images/WinUSB/Uninstall/device-manager-usb-device.png differ
diff --git a/Docs/Images/WinUSB/Uninstall/windows-x-device-manager.png b/Docs/Images/WinUSB/Uninstall/windows-x-device-manager.png
new file mode 100644
index 0000000..f8f30db
Binary files /dev/null and b/Docs/Images/WinUSB/Uninstall/windows-x-device-manager.png differ
diff --git a/Docs/Images/WinUSB/no-xbox-one-receiver.png b/Docs/Images/WinUSB/no-xbox-one-receiver.png
new file mode 100644
index 0000000..71c3ccd
Binary files /dev/null and b/Docs/Images/WinUSB/no-xbox-one-receiver.png differ
diff --git a/Docs/Packet Logs/Debugging/README.md b/Docs/Packet Logs/Debugging/README.md
new file mode 100644
index 0000000..aa1a6c6
--- /dev/null
+++ b/Docs/Packet Logs/Debugging/README.md
@@ -0,0 +1,3 @@
+# Debugging Packet Logs
+
+Packet logs created for use with the replay backend, to ensure proper mapper functionality.
diff --git a/Docs/Packet Logs/Debugging/drumsPacketLog.txt b/Docs/Packet Logs/Debugging/drumsPacketLog.txt
new file mode 100644
index 0000000..658746a
--- /dev/null
+++ b/Docs/Packet Logs/Debugging/drumsPacketLog.txt
@@ -0,0 +1,38 @@
+// Initialization
+02-20-01-1C | 62-BC-CE-85-ED-7E-00-00-6F-0E-71-01-01-00-00-00-07-00-1E-00-00-02-01-00-01-00-01-00
+04-F0-01-3A C3-01 | 10-00-01-00-00-00-00-00-00-00-00-00-00-00-C3-00-9B-00-16-00-1B-00-1C-00-23-00-29-00-6A-00-00-00-00-00-00-00-00-00-01-01-00-00-00-00-06-01-02-03-04-06-07-05-01-04-05-06-0A-02
+04-A0-01-BA-00 3A | 15-00-50-44-50-2E-58-62-6F-78-2E-44-72-75-6D-73-2E-54-61-62-6C-61-68-27-00-57-69-6E-64-6F-77-73-2E-58-62-6F-78-2E-49-6E-70-75-74-2E-4E-61-76-69-67-61-74-69-6F-6E-43-6F-6E-74
+04-A0-01-BA-00 74 | 72-6F-6C-6C-65-72-03-B0-F9-03-A5-5E-95-C4-47-A2-ED-B1-33-6F-A7-70-3E-E7-1F-F3-B8-86-73-E9-40-A9-F8-2F-21-26-3A-CF-B7-56-FF-76-97-FD-9B-81-45-AD-45-B6-45-BB-A5-26-D6-01-17-00
+04-B0-01-15 AE-01 | 20-0A-00-01-00-14-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+04-A0-01-00 C3-01
+
+// Face buttons
+20-00-01-06 | 10-00-00-00-00-00 // A
+20-00-02-06 | 20-00-00-00-00-00 // B
+20-00-03-06 | 40-00-00-00-00-00 // X
+20-00-04-06 | 80-00-00-00-00-00 // Y
+
+// D-pad
+20-00-01-06 | 00-01-00-00-00-00 // Up
+20-00-02-06 | 00-02-00-00-00-00 // Down
+20-00-03-06 | 00-04-00-00-00-00 // Left
+20-00-04-06 | 00-08-00-00-00-00 // Right
+
+// Menu/view
+20-00-01-06 | 04-00-00-00-00-00 // Menu
+20-00-02-06 | 08-00-00-00-00-00 // View
+
+// Pads
+20-00-01-06 | 20-00-40-00-00-00 // Red
+20-00-02-06 | 00-00-04-00-00-00 // Yellow
+20-00-03-06 | 00-00-00-40-00-00 // Blue
+20-00-04-06 | 10-00-00-04-00-00 // Green
+
+// Cymbals
+20-00-01-06 | 00-00-00-00-40-00 // Yellow
+20-00-02-06 | 00-00-00-00-04-00 // Blue
+20-00-03-06 | 00-00-00-00-00-40 // Green
+
+// Kicks
+20-00-01-06 | 00-10-00-00-00-00 // Kick 1 (Orange)
+20-00-02-06 | 00-20-00-00-00-00 // Kick 2
\ No newline at end of file
diff --git a/Docs/Packet Logs/Debugging/guitarPacketLog.txt b/Docs/Packet Logs/Debugging/guitarPacketLog.txt
new file mode 100644
index 0000000..ff6ba87
--- /dev/null
+++ b/Docs/Packet Logs/Debugging/guitarPacketLog.txt
@@ -0,0 +1,105 @@
+// Initialization
+02-20-01-1C | 7E-ED-8F-FF-73-00-00-00-38-07-61-41-01-00-00-00-E5-00-00-00-00-02-01-00-01-00-01-00
+04-F0-01-3A E5-01 | 10-00-01-00-00-00-00-00-00-00-00-00-00-00-E5-00-A6-00-16-00-1B-00-1C-00-23-00-29-00-75-00-00-00-00-00-00-00-00-00-01-01-00-00-00-00-06-01-02-03-04-06-07-05-01-04-05-06-0A-02
+04-A0-01-BA-00 3A | 20-00-4D-61-64-43-61-74-7A-2E-58-62-6F-78-2E-47-75-69-74-61-72-2E-53-74-72-61-74-6F-63-61-73-74-65-72-27-00-57-69-6E-64-6F-77-73-2E-58-62-6F-78-2E-49-6E-70-75-74-2E-4E-61-76
+04-A0-01-BA-00 74 | 69-67-61-74-69-6F-6E-43-6F-6E-74-72-6F-6C-6C-65-72-03-38-E4-2A-0D-7D-7F-33-49-86-93-30-FC-55-01-8E-77-E7-1F-F3-B8-86-73-E9-40-A9-F8-2F-21-26-3A-CF-B7-56-FF-76-97-FD-9B-81-45
+04-B0-01-37 AE-01 | AD-45-B6-45-BB-A5-26-D6-02-17-00-20-0E-00-01-00-14-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-17-00-21-05-00-01-00-0C-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+04-A0-01-00 E5-01
+
+// Upper frets
+20-00-01-0A | 10-00-00-00-00-01-00-00-00-00 // Green
+20-00-02-0A | 20-00-00-00-00-02-00-00-00-00 // Red
+20-00-03-0A | 80-00-00-00-00-04-00-00-00-00 // Yellow
+20-00-04-0A | 40-00-00-00-00-08-00-00-00-00 // Blue
+20-00-05-0A | 00-10-00-00-00-10-00-00-00-00 // Orange
+
+// Lower frets
+20-00-01-0A | 10-40-00-00-00-00-01-00-00-00 // Green
+20-00-02-0A | 20-40-00-00-00-00-02-00-00-00 // Red
+20-00-03-0A | 80-40-00-00-00-00-04-00-00-00 // Yellow
+20-00-04-0A | 40-40-00-00-00-00-08-00-00-00 // Blue
+20-00-05-0A | 00-50-00-00-00-00-10-00-00-00 // Orange
+
+// D-pad/strum
+20-00-01-0A | 00-01-00-00-00-00-00-00-00-00 // Up
+20-00-02-0A | 00-02-00-00-00-00-00-00-00-00 // Down
+20-00-03-0A | 00-04-00-00-00-00-00-00-00-00 // Left
+20-00-04-0A | 00-08-00-00-00-00-00-00-00-00 // Right
+
+// Menu buttons
+20-00-01-0A | 04-00-00-00-00-00-00-00-00-00 // Menu
+20-00-02-0A | 08-00-00-00-00-00-00-00-00-00 // View
+
+// Tilt
+20-00-01-0A | 00-00-11-00-00-00-00-00-00-00
+20-00-02-0A | 00-00-22-00-00-00-00-00-00-00
+20-00-03-0A | 00-00-33-00-00-00-00-00-00-00
+20-00-04-0A | 00-00-44-00-00-00-00-00-00-00
+20-00-05-0A | 00-00-55-00-00-00-00-00-00-00
+20-00-06-0A | 00-00-66-00-00-00-00-00-00-00
+20-00-07-0A | 00-00-77-00-00-00-00-00-00-00
+20-00-08-0A | 00-00-88-00-00-00-00-00-00-00
+20-00-09-0A | 00-00-99-00-00-00-00-00-00-00
+20-00-0A-0A | 00-00-AA-00-00-00-00-00-00-00
+20-00-0B-0A | 00-00-BB-00-00-00-00-00-00-00
+20-00-0C-0A | 00-00-CC-00-00-00-00-00-00-00
+20-00-0D-0A | 00-00-DD-00-00-00-00-00-00-00
+20-00-0E-0A | 00-00-EE-00-00-00-00-00-00-00
+20-00-0F-0A | 00-00-FF-00-00-00-00-00-00-00
+20-00-10-0A | 00-00-EE-00-00-00-00-00-00-00
+20-00-11-0A | 00-00-DD-00-00-00-00-00-00-00
+20-00-12-0A | 00-00-CC-00-00-00-00-00-00-00
+20-00-13-0A | 00-00-BB-00-00-00-00-00-00-00
+20-00-14-0A | 00-00-AA-00-00-00-00-00-00-00
+20-00-15-0A | 00-00-99-00-00-00-00-00-00-00
+20-00-16-0A | 00-00-88-00-00-00-00-00-00-00
+20-00-17-0A | 00-00-77-00-00-00-00-00-00-00
+20-00-18-0A | 00-00-66-00-00-00-00-00-00-00
+20-00-19-0A | 00-00-55-00-00-00-00-00-00-00
+20-00-1A-0A | 00-00-44-00-00-00-00-00-00-00
+20-00-1B-0A | 00-00-33-00-00-00-00-00-00-00
+20-00-1C-0A | 00-00-22-00-00-00-00-00-00-00
+20-00-1D-0A | 00-00-11-00-00-00-00-00-00-00
+20-00-1E-0A | 00-00-00-00-00-00-00-00-00-00
+
+// Whammy
+20-00-01-0A | 00-00-00-11-00-00-00-00-00-00
+20-00-02-0A | 00-00-00-22-00-00-00-00-00-00
+20-00-03-0A | 00-00-00-33-00-00-00-00-00-00
+20-00-04-0A | 00-00-00-44-00-00-00-00-00-00
+20-00-05-0A | 00-00-00-55-00-00-00-00-00-00
+20-00-06-0A | 00-00-00-66-00-00-00-00-00-00
+20-00-07-0A | 00-00-00-77-00-00-00-00-00-00
+20-00-08-0A | 00-00-00-88-00-00-00-00-00-00
+20-00-09-0A | 00-00-00-99-00-00-00-00-00-00
+20-00-0A-0A | 00-00-00-AA-00-00-00-00-00-00
+20-00-0B-0A | 00-00-00-BB-00-00-00-00-00-00
+20-00-0C-0A | 00-00-00-CC-00-00-00-00-00-00
+20-00-0D-0A | 00-00-00-DD-00-00-00-00-00-00
+20-00-0E-0A | 00-00-00-EE-00-00-00-00-00-00
+20-00-0F-0A | 00-00-00-FF-00-00-00-00-00-00
+20-00-10-0A | 00-00-00-EE-00-00-00-00-00-00
+20-00-11-0A | 00-00-00-DD-00-00-00-00-00-00
+20-00-12-0A | 00-00-00-CC-00-00-00-00-00-00
+20-00-13-0A | 00-00-00-BB-00-00-00-00-00-00
+20-00-14-0A | 00-00-00-AA-00-00-00-00-00-00
+20-00-15-0A | 00-00-00-99-00-00-00-00-00-00
+20-00-16-0A | 00-00-00-88-00-00-00-00-00-00
+20-00-17-0A | 00-00-00-77-00-00-00-00-00-00
+20-00-18-0A | 00-00-00-66-00-00-00-00-00-00
+20-00-19-0A | 00-00-00-55-00-00-00-00-00-00
+20-00-1A-0A | 00-00-00-44-00-00-00-00-00-00
+20-00-1B-0A | 00-00-00-33-00-00-00-00-00-00
+20-00-1C-0A | 00-00-00-22-00-00-00-00-00-00
+20-00-1D-0A | 00-00-00-11-00-00-00-00-00-00
+20-00-1E-0A | 00-00-00-00-00-00-00-00-00-00
+
+// Pickup
+20-00-01-0A | 00-00-00-00-10-00-00-00-00-00
+20-00-02-0A | 00-00-00-00-20-00-00-00-00-00
+20-00-03-0A | 00-00-00-00-30-00-00-00-00-00
+20-00-04-0A | 00-00-00-00-40-00-00-00-00-00
+20-00-05-0A | 00-00-00-00-30-00-00-00-00-00
+20-00-06-0A | 00-00-00-00-20-00-00-00-00-00
+20-00-07-0A | 00-00-00-00-10-00-00-00-00-00
+20-00-08-0A | 00-00-00-00-00-00-00-00-00-00
\ No newline at end of file
diff --git a/Docs/Packet Logs/Debugging/riffmasterPacketLog.txt b/Docs/Packet Logs/Debugging/riffmasterPacketLog.txt
new file mode 100644
index 0000000..e80d062
--- /dev/null
+++ b/Docs/Packet Logs/Debugging/riffmasterPacketLog.txt
@@ -0,0 +1,175 @@
+// Initialization
+02-20-01-1C | 6B-A7-24-43-17-4A-00-00-6F-0E-48-02-01-00-00-00-01-00-01-00-01-00-01-00-01-00-01-00
+04-F0-01-3A EB-01 | 10-00-01-00-00-00-00-00-00-00-00-00-00-00-EB-00-AC-00-16-00-1B-00-1C-00-23-00-29-00-6B-00-00-00-00-00-00-00-00-00-01-01-00-00-00-00-06-01-02-03-04-06-07-05-01-04-05-06-0A-02
+04-A0-01-BA-00 3A | 16-00-50-44-50-2E-58-62-6F-78-2E-47-75-69-74-61-72-2E-4A-61-67-75-61-72-27-00-57-69-6E-64-6F-77-73-2E-58-62-6F-78-2E-49-6E-70-75-74-2E-4E-61-76-69-67-61-74-69-6F-6E-43-6F-6E
+04-A0-01-BA-00 74 | 74-72-6F-6C-6C-65-72-04-F6-6A-26-1A-46-3A-E3-45-B9-B6-0F-2C-0B-2C-1E-BE-FE-D2-DD-EC-87-D3-94-42-BD-96-1A-71-2E-3D-C7-7D-E7-1F-F3-B8-86-73-E9-40-A9-F8-2F-21-26-3A-CF-B7-56-FF
+04-B0-01-3A AE-01 | 76-97-FD-9B-81-45-AD-45-B6-45-BB-A5-26-D6-02-17-00-20-20-00-01-00-14-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-17-00-21-05-00-01-00-0C-00-00-00-00-00-00-00-00-00-00-00-00
+04-B0-01-03 E8-01 | 00-00-00
+04-A0-01-00 EB-01
+
+// Upper frets
+20-00-01-1C | 10-00-00-00-00-01-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Green
+20-00-02-1C | 20-00-00-00-00-02-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Red
+20-00-03-1C | 80-00-00-00-00-04-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Yellow
+20-00-04-1C | 40-00-00-00-00-08-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Blue
+20-00-05-1C | 00-10-00-00-00-10-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Orange
+
+// Lower frets
+20-00-01-1C | 10-40-00-00-00-00-01-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Green
+20-00-02-1C | 20-40-00-00-00-00-02-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Red
+20-00-03-1C | 80-40-00-00-00-00-04-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Yellow
+20-00-04-1C | 40-40-00-00-00-00-08-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Blue
+20-00-05-1C | 00-50-00-00-00-00-10-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Orange
+
+// D-pad/strum
+20-00-01-1C | 00-01-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Up
+20-00-02-1C | 00-02-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Down
+20-00-03-1C | 00-04-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Left
+20-00-04-1C | 00-08-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Right
+
+// Menu buttons
+20-00-01-1C | 04-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // Menu
+20-00-02-1C | 08-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 // View
+
+// Joystick click
+20-00-03-1C | 00-40-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+
+// Joystick X
+20-00-01-1C | 00-00-00-00-00-00-00-00-00-00-00-80-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-02-1C | 00-00-00-00-00-00-00-00-00-00-11-91-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-03-1C | 00-00-00-00-00-00-00-00-00-00-22-A2-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-04-1C | 00-00-00-00-00-00-00-00-00-00-33-B3-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-05-1C | 00-00-00-00-00-00-00-00-00-00-44-C4-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-06-1C | 00-00-00-00-00-00-00-00-00-00-55-D5-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-07-1C | 00-00-00-00-00-00-00-00-00-00-66-E6-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-08-1C | 00-00-00-00-00-00-00-00-00-00-77-F7-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-09-1C | 00-00-00-00-00-00-00-00-00-00-88-08-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0A-1C | 00-00-00-00-00-00-00-00-00-00-99-19-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0B-1C | 00-00-00-00-00-00-00-00-00-00-AA-2A-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0C-1C | 00-00-00-00-00-00-00-00-00-00-BB-3B-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0D-1C | 00-00-00-00-00-00-00-00-00-00-CC-4C-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0E-1C | 00-00-00-00-00-00-00-00-00-00-DD-5D-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0F-1C | 00-00-00-00-00-00-00-00-00-00-EE-6E-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-10-1C | 00-00-00-00-00-00-00-00-00-00-FF-7F-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-11-1C | 00-00-00-00-00-00-00-00-00-00-EE-6E-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-12-1C | 00-00-00-00-00-00-00-00-00-00-DD-5D-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-13-1C | 00-00-00-00-00-00-00-00-00-00-CC-4C-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-14-1C | 00-00-00-00-00-00-00-00-00-00-BB-3B-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-15-1C | 00-00-00-00-00-00-00-00-00-00-AA-2A-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-16-1C | 00-00-00-00-00-00-00-00-00-00-99-19-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-17-1C | 00-00-00-00-00-00-00-00-00-00-88-08-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-18-1C | 00-00-00-00-00-00-00-00-00-00-77-F7-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-19-1C | 00-00-00-00-00-00-00-00-00-00-66-E6-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1A-1C | 00-00-00-00-00-00-00-00-00-00-55-D5-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1B-1C | 00-00-00-00-00-00-00-00-00-00-44-C4-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1C-1C | 00-00-00-00-00-00-00-00-00-00-33-B3-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1D-1C | 00-00-00-00-00-00-00-00-00-00-22-A2-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1E-1C | 00-00-00-00-00-00-00-00-00-00-11-91-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1F-1C | 00-00-00-00-00-00-00-00-00-00-00-80-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+
+// Joystick Y
+20-00-01-1C | 00-00-00-00-00-00-00-00-00-00-00-00-00-80-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-02-1C | 00-00-00-00-00-00-00-00-00-00-00-00-11-91-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-03-1C | 00-00-00-00-00-00-00-00-00-00-00-00-22-A2-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-04-1C | 00-00-00-00-00-00-00-00-00-00-00-00-33-B3-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-05-1C | 00-00-00-00-00-00-00-00-00-00-00-00-44-C4-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-06-1C | 00-00-00-00-00-00-00-00-00-00-00-00-55-D5-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-07-1C | 00-00-00-00-00-00-00-00-00-00-00-00-66-E6-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-08-1C | 00-00-00-00-00-00-00-00-00-00-00-00-77-F7-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-09-1C | 00-00-00-00-00-00-00-00-00-00-00-00-88-08-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0A-1C | 00-00-00-00-00-00-00-00-00-00-00-00-99-19-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0B-1C | 00-00-00-00-00-00-00-00-00-00-00-00-AA-2A-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0C-1C | 00-00-00-00-00-00-00-00-00-00-00-00-BB-3B-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0D-1C | 00-00-00-00-00-00-00-00-00-00-00-00-CC-4C-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0E-1C | 00-00-00-00-00-00-00-00-00-00-00-00-DD-5D-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0F-1C | 00-00-00-00-00-00-00-00-00-00-00-00-EE-6E-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-10-1C | 00-00-00-00-00-00-00-00-00-00-00-00-FF-7F-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-11-1C | 00-00-00-00-00-00-00-00-00-00-00-00-EE-6E-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-12-1C | 00-00-00-00-00-00-00-00-00-00-00-00-DD-5D-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-13-1C | 00-00-00-00-00-00-00-00-00-00-00-00-CC-4C-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-14-1C | 00-00-00-00-00-00-00-00-00-00-00-00-BB-3B-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-15-1C | 00-00-00-00-00-00-00-00-00-00-00-00-AA-2A-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-16-1C | 00-00-00-00-00-00-00-00-00-00-00-00-99-19-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-17-1C | 00-00-00-00-00-00-00-00-00-00-00-00-88-08-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-18-1C | 00-00-00-00-00-00-00-00-00-00-00-00-77-F7-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-19-1C | 00-00-00-00-00-00-00-00-00-00-00-00-66-E6-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1A-1C | 00-00-00-00-00-00-00-00-00-00-00-00-55-D5-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1B-1C | 00-00-00-00-00-00-00-00-00-00-00-00-44-C4-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1C-1C | 00-00-00-00-00-00-00-00-00-00-00-00-33-B3-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1D-1C | 00-00-00-00-00-00-00-00-00-00-00-00-22-A2-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1E-1C | 00-00-00-00-00-00-00-00-00-00-00-00-11-91-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1F-1C | 00-00-00-00-00-00-00-00-00-00-00-00-00-80-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+
+// Tilt
+20-00-01-1C | 00-00-11-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-02-1C | 00-00-22-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-03-1C | 00-00-33-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-04-1C | 00-00-44-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-05-1C | 00-00-55-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-06-1C | 00-00-66-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-07-1C | 00-00-77-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-08-1C | 00-00-88-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-09-1C | 00-00-99-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0A-1C | 00-00-AA-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0B-1C | 00-00-BB-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0C-1C | 00-00-CC-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0D-1C | 00-00-DD-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0E-1C | 00-00-EE-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0F-1C | 00-00-FF-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-10-1C | 00-00-EE-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-11-1C | 00-00-DD-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-12-1C | 00-00-CC-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-13-1C | 00-00-BB-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-14-1C | 00-00-AA-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-15-1C | 00-00-99-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-16-1C | 00-00-88-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-17-1C | 00-00-77-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-18-1C | 00-00-66-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-19-1C | 00-00-55-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1A-1C | 00-00-44-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1B-1C | 00-00-33-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1C-1C | 00-00-22-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1D-1C | 00-00-11-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1E-1C | 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+
+// Whammy
+20-00-01-1C | 00-00-00-11-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-02-1C | 00-00-00-22-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-03-1C | 00-00-00-33-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-04-1C | 00-00-00-44-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-05-1C | 00-00-00-55-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-06-1C | 00-00-00-66-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-07-1C | 00-00-00-77-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-08-1C | 00-00-00-88-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-09-1C | 00-00-00-99-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0A-1C | 00-00-00-AA-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0B-1C | 00-00-00-BB-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0C-1C | 00-00-00-CC-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0D-1C | 00-00-00-DD-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0E-1C | 00-00-00-EE-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-0F-1C | 00-00-00-FF-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-10-1C | 00-00-00-EE-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-11-1C | 00-00-00-DD-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-12-1C | 00-00-00-CC-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-13-1C | 00-00-00-BB-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-14-1C | 00-00-00-AA-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-15-1C | 00-00-00-99-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-16-1C | 00-00-00-88-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-17-1C | 00-00-00-77-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-18-1C | 00-00-00-66-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-19-1C | 00-00-00-55-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1A-1C | 00-00-00-44-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1B-1C | 00-00-00-33-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1C-1C | 00-00-00-22-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1D-1C | 00-00-00-11-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-1E-1C | 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+
+// Pickup
+20-00-01-1C | 00-00-00-00-10-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-02-1C | 00-00-00-00-20-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-03-1C | 00-00-00-00-30-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-04-1C | 00-00-00-00-40-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-05-1C | 00-00-00-00-30-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-06-1C | 00-00-00-00-20-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-07-1C | 00-00-00-00-10-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+20-00-08-1C | 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
\ No newline at end of file
diff --git a/Docs/Packet Logs/GuitarSniffer/README.md b/Docs/Packet Logs/GuitarSniffer/README.md
new file mode 100644
index 0000000..f6ae8ff
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/README.md
@@ -0,0 +1,5 @@
+# GuitarSniffer Packet Logs
+
+These logs are reformatted versions of the ones available for download on GuitarSniffer's repository. They are provided here for preservation and reference.
+
+The originals may be found [here](https://1drv.ms/f/s!AgQGk0OeTMLwhA-uDO9IQHEHqGhv).
diff --git a/Docs/Packet Logs/GuitarSniffer/blue_lower.log b/Docs/Packet Logs/GuitarSniffer/blue_lower.log
new file mode 100644
index 0000000..3e172c6
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/blue_lower.log
@@ -0,0 +1,16 @@
+2019-04-24 04:14:18.057 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-06-00-00 | 20-00-69-0A 40-40-00-00-40-00-08-00-00-00
+2019-04-24 04:14:18.162 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-06-00-00 | 20-00-6A-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:18.216 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-06-00-00 | 20-00-6B-0A 40-40-00-00-40-00-08-00-00-00
+2019-04-24 04:14:18.319 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-06-00-00 | 20-00-6C-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:18.373 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-06-00-00 | 20-00-6D-0A 40-40-00-00-40-00-08-00-00-00
+2019-04-24 04:14:18.549 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-06-00-00 | 20-00-6E-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:18.552 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-06-00-00 | 20-00-6F-0A 40-40-00-00-40-00-08-00-00-00
+2019-04-24 04:14:18.607 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-06-00-00 | 20-00-70-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:18.710 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-07-00-00 | 20-00-71-0A 40-40-00-00-40-00-08-00-00-00
+2019-04-24 04:14:18.764 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-07-00-00 | 20-00-72-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:18.869 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-07-00-00 | 20-00-73-0A 40-40-00-00-40-00-08-00-00-00
+2019-04-24 04:14:18.922 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-07-00-00 | 20-00-74-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:19.026 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-07-00-00 | 20-00-75-0A 40-40-00-00-40-00-08-00-00-00
+2019-04-24 04:14:19.080 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-07-00-00 | 20-00-76-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:19.183 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-07-00-00 | 20-00-77-0A 40-40-00-00-40-00-08-00-00-00
+2019-04-24 04:14:19.286 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-07-00-00 | 20-00-78-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/blue_upper.log b/Docs/Packet Logs/GuitarSniffer/blue_upper.log
new file mode 100644
index 0000000..98b2b94
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/blue_upper.log
@@ -0,0 +1,14 @@
+2019-04-24 04:13:22.822 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-02-00-00 | 20-00-24-0A 40-00-00-00-40-08-00-00-00-00
+2019-04-24 04:13:22.929 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-02-00-00 | 20-00-25-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:22.983 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-02-00-00 | 20-00-26-0A 40-00-00-00-40-08-00-00-00-00
+2019-04-24 04:13:23.087 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-02-00-00 | 20-00-27-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:23.141 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-02-00-00 | 20-00-28-0A 40-00-00-00-40-08-00-00-00-00
+2019-04-24 04:13:23.245 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-02-00-00 | 20-00-29-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:23.299 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-02-00-00 | 20-00-2A-0A 40-00-00-00-40-08-00-00-00-00
+2019-04-24 04:13:23.402 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-02-00-00 | 20-00-2B-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:23.455 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-02-00-00 | 20-00-2C-0A 40-00-00-00-40-08-00-00-00-00
+2019-04-24 04:13:23.559 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-02-00-00 | 20-00-2D-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:23.612 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-02-00-00 | 20-00-2E-0A 40-00-00-00-40-08-00-00-00-00
+2019-04-24 04:13:23.716 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-02-00-00 | 20-00-2F-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:23.770 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-02-00-00 | 20-00-30-0A 40-00-00-00-40-08-00-00-00-00
+2019-04-24 04:13:23.873 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-03-00-00 | 20-00-31-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/dpad_down.log b/Docs/Packet Logs/GuitarSniffer/dpad_down.log
new file mode 100644
index 0000000..e3c4bb4
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/dpad_down.log
@@ -0,0 +1,8 @@
+2019-04-24 04:16:02.095 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-11-00-00 | 20-00-17-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:16:02.301 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-11-00-00 | 20-00-18-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:02.708 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-11-00-00 | 20-00-19-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:16:02.914 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-11-00-00 | 20-00-1A-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:03.268 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-11-00-00 | 20-00-1B-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:16:03.422 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-11-00-00 | 20-00-1C-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:03.775 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-11-00-00 | 20-00-1D-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:16:03.878 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-11-00-00 | 20-00-1E-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/dpad_left.log b/Docs/Packet Logs/GuitarSniffer/dpad_left.log
new file mode 100644
index 0000000..66d489a
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/dpad_left.log
@@ -0,0 +1,10 @@
+2019-04-24 04:16:27.950 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-12-00-00 | 20-00-29-0A 00-04-00-00-40-00-00-00-00-00
+2019-04-24 04:16:28.075 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-12-00-00 | 20-00-2A-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:28.571 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-12-00-00 | 20-00-2B-0A 00-04-00-00-40-00-00-00-00-00
+2019-04-24 04:16:28.633 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-12-00-00 | 20-00-2C-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:29.234 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-12-00-00 | 20-00-2D-0A 00-04-00-00-40-00-00-00-00-00
+2019-04-24 04:16:29.359 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-12-00-00 | 20-00-2E-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:29.719 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-12-00-00 | 20-00-2F-0A 00-04-00-00-40-00-00-00-00-00
+2019-04-24 04:16:29.844 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-12-00-00 | 20-00-30-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:30.094 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-13-00-00 | 20-00-31-0A 00-04-00-00-40-00-00-00-00-00
+2019-04-24 04:16:30.156 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-13-00-00 | 20-00-32-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/dpad_right.log b/Docs/Packet Logs/GuitarSniffer/dpad_right.log
new file mode 100644
index 0000000..9c04078
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/dpad_right.log
@@ -0,0 +1,10 @@
+2019-04-24 04:16:39.540 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-13-00-00 | 20-00-33-0A 00-08-00-00-40-00-00-00-00-00
+2019-04-24 04:16:39.664 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-13-00-00 | 20-00-34-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:39.971 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-13-00-00 | 20-00-35-0A 00-08-00-00-40-00-00-00-00-00
+2019-04-24 04:16:40.091 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-13-00-00 | 20-00-36-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:40.403 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-13-00-00 | 20-00-37-0A 00-08-00-00-40-00-00-00-00-00
+2019-04-24 04:16:40.466 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-13-00-00 | 20-00-38-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:40.716 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-13-00-00 | 20-00-39-0A 00-08-00-00-40-00-00-00-00-00
+2019-04-24 04:16:40.841 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-13-00-00 | 20-00-3A-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:41.087 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-13-00-00 | 20-00-3B-0A 00-08-00-00-40-00-00-00-00-00
+2019-04-24 04:16:41.198 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-13-00-00 | 20-00-3C-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/dpad_up.log b/Docs/Packet Logs/GuitarSniffer/dpad_up.log
new file mode 100644
index 0000000..1cf98b5
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/dpad_up.log
@@ -0,0 +1,10 @@
+2019-04-24 04:16:14.475 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-11-00-00 | 20-00-1F-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:16:14.581 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-11-00-00 | 20-00-20-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:14.936 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-12-00-00 | 20-00-21-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:16:15.089 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-12-00-00 | 20-00-22-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:15.292 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-12-00-00 | 20-00-23-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:16:15.445 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-12-00-00 | 20-00-24-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:15.701 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-12-00-00 | 20-00-25-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:16:15.805 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-12-00-00 | 20-00-26-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:16:16.059 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-12-00-00 | 20-00-27-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:16:16.163 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-12-00-00 | 20-00-28-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/green_lower.log b/Docs/Packet Logs/GuitarSniffer/green_lower.log
new file mode 100644
index 0000000..bd20a8f
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/green_lower.log
@@ -0,0 +1,10 @@
+2019-04-24 04:13:44.036 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-04-00-00 | 20-00-43-0A 10-40-00-00-40-00-01-00-00-00
+2019-04-24 04:13:44.142 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-04-00-00 | 20-00-44-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:44.196 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-04-00-00 | 20-00-45-0A 10-40-00-00-40-00-01-00-00-00
+2019-04-24 04:13:44.299 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-04-00-00 | 20-00-46-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:44.353 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-04-00-00 | 20-00-47-0A 10-40-00-00-40-00-01-00-00-00
+2019-04-24 04:13:44.457 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-04-00-00 | 20-00-48-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:44.511 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-04-00-00 | 20-00-49-0A 10-40-00-00-40-00-01-00-00-00
+2019-04-24 04:13:44.616 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-04-00-00 | 20-00-4A-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:44.671 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-04-00-00 | 20-00-4B-0A 10-40-00-00-40-00-01-00-00-00
+2019-04-24 04:13:44.775 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-04-00-00 | 20-00-4C-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/green_upper.log b/Docs/Packet Logs/GuitarSniffer/green_upper.log
new file mode 100644
index 0000000..c264431
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/green_upper.log
@@ -0,0 +1,18 @@
+2019-04-24 04:11:55.151 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-00-00-00 | 20-00-03-0A 10-00-00-00-40-01-00-00-00-00
+2019-04-24 04:11:55.306 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-00-00-00 | 20-00-04-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:11:55.309 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-00-00-00 | 20-00-05-0A 00-00-00-00-40-00-00-00-00-00
+
+2019-04-24 04:11:55.413 [34] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-02-01-00 | 03-20-02-04 86-01-00-00
+
+2019-04-24 04:11:57.076 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-00-00-00 | 20-00-06-0A 10-00-00-00-40-01-00-00-00-00
+2019-04-24 04:12:00.206 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-00-00-00 | 20-00-07-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:12:02.630 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-00-00-00 | 20-00-08-0A 10-00-00-00-40-01-00-00-00-00
+2019-04-24 04:12:03.138 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-00-00-00 | 20-00-09-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:12:03.545 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-00-00-00 | 20-00-0A-0A 10-00-00-00-40-01-00-00-00-00
+2019-04-24 04:12:03.950 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-00-00-00 | 20-00-0B-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:12:04.356 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-00-00-00 | 20-00-0C-0A 10-00-00-00-40-01-00-00-00-00
+2019-04-24 04:12:04.762 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-00-00-00 | 20-00-0D-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:12:05.167 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-00-00-00 | 20-00-0E-0A 10-00-00-00-40-01-00-00-00-00
+2019-04-24 04:12:05.674 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-00-00-00 | 20-00-0F-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:12:05.981 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-00-00-00 | 20-00-10-0A 10-00-00-00-40-01-00-00-00-00
+2019-04-24 04:12:06.489 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-01-00-00 | 20-00-11-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/guitar_off.log b/Docs/Packet Logs/GuitarSniffer/guitar_off.log
new file mode 100644
index 0000000..6744ae7
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/guitar_off.log
@@ -0,0 +1,2 @@
+2019-04-24 04:19:12.994 [32] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-04-01-00 | 07-20-09-02 01-5B
+2019-04-24 04:19:13.702 [34] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-04-01-00 | 03-20-17-04 85-01-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/guitar_on.log b/Docs/Packet Logs/GuitarSniffer/guitar_on.log
new file mode 100644
index 0000000..4e34acb
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/guitar_on.log
@@ -0,0 +1,52 @@
+2019-04-24 04:11:13.534 [58] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-00 01-00 | 02-20-01-1C 7E-ED-8F-FF-73-00-00-00-38-07-61-41-01-00-00-00-E5-00-00-00-00-02-01-00-01-00-01-00
+2019-04-24 04:11:13.540 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-00 01-00 | 04-F0-01-3A E5-01 10-00-01-00-00-00-00-00-00-00-00-00-00-00-E5-00-A6-00-16-00-1B-00-1C-00-23-00-29-00-75-00-00-00-00-00-00-00-00-00-01-01-00-00-00-00-06-01-02-03-04-06-07-05-01-04-05-06-0A-02
+2019-04-24 04:11:13.543 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-00 01-00 | 04-A0-01-BA-00 3A 20-00-4D-61-64-43-61-74-7A-2E-58-62-6F-78-2E-47-75-69-74-61-72-2E-53-74-72-61-74-6F-63-61-73-74-65-72-27-00-57-69-6E-64-6F-77-73-2E-58-62-6F-78-2E-49-6E-70-75-74-2E-4E-61-76
+2019-04-24 04:11:13.546 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-00 01-00 | 04-A0-01-BA-00 74 69-67-61-74-69-6F-6E-43-6F-6E-74-72-6F-6C-6C-65-72-03-38-E4-2A-0D-7D-7F-33-49-86-93-30-FC-55-01-8E-77-E7-1F-F3-B8-86-73-E9-40-A9-F8-2F-21-26-3A-CF-B7-56-FF-76-97-FD-9B-81-45
+2019-04-24 04:11:13.600 [87] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-00 01-00 | 04-B0-01-37 AE-01 AD-45-B6-45-BB-A5-26-D6-02-17-00-20-0E-00-01-00-14-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-17-00-21-05-00-01-00-0C-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+2019-04-24 04:11:13.603 [32] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-00 01-00 | 04-A0-01-00 E5-01
+
+2019-04-24 04:11:13.606 [34] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-00 01-00 | 03-20-00-04 86-00-00-00
+
+2019-04-24 04:11:13.660 [39] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-00 01-00 | 01-20-01-09 00-06-30-3A-00-00-00-3A-00
+2019-04-24 04:11:13.664 [36] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-00 01-00 | 06-30-01-06 00-C1-00-01-00-00
+2019-04-24 04:11:13.717 [32] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-00 01-00 | 06-A0-01-00 06-00
+
+2019-04-24 04:11:13.720 [39] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-00 01-00 | 01-20-02-09 00-06-30-0E-00-00-00-0E-00
+2019-04-24 04:11:13.774 [90] -> 88-19 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-00 01-00 | 06-F0-02-3A DA-00 00-C2-00-02-00-54-02-01-00-50-E6-BB-71-9F-7D-A1-95-A5-51-23-50-3D-0D-5D-AF-DD-87-27-BE-ED-99-2F-A5-76-45-94-19-28-0B-38-5A-DA-E3-89-76-E6-00-00-03-E9-00-41-02-05-5F-5C-00-18
+2019-04-24 04:11:13.778 [64] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-00 01-00 | 06-B0-02-A0 00-3A 00-68-00-1F-70-6D-D1-CE-03-00-02-80-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+2019-04-24 04:11:13.781 [32] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-00 01-00 | 06-A0-02-00 5A-00
+
+2019-04-24 04:11:13.836 [39] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-00 01-00 | 01-20-03-09 00-06-30-0E-00-00-00-0E-00
+2019-04-24 04:11:13.989 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-00 01-00 | 06-F0-03-3A B9-06 04-C2-00-03-03-33-03-01-03-2F-30-82-03-2B-30-82-02-13-A0-03-02-01-02-02-04-35-79-38-A7-30-0D-06-09-2A-86-48-86-F7-0D-01-01-0B-05-00-30-76-31-0B-30-09-06-03-55-04-06-13-02-44
+2019-04-24 04:11:13.993 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-01 01-00 | 06-A0-03-BA-00 3A 45-31-0F-30-0D-06-03-55-04-08-13-06-53-61-78-6F-6E-79-31-16-30-14-06-03-55-04-0A-13-0D-53-75-62-63-6C-61-73-73-20-30-30-30-32-31-11-30-0F-06-03-55-04-0B-13-08-43-6C-61-73-73
+2019-04-24 04:11:14.098 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-01 01-00 | 06-A0-03-BA-00 74 20-30-33-31-2B-30-29-06-03-55-04-03-13-22-58-62-6F-78-20-41-63-63-65-73-73-6F-72-69-65-73-20-43-6C-61-73-73-20-50-72-6F-64-20-43-41-20-30-30-35-30-1E-17-0D-31-35-30-36-31-31
+2019-04-24 04:11:14.206 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-01 01-00 | 06-A0-03-3A AE-01 31-39-34-31-31-33-5A-17-0D-34-34-31-30-31-35-32-33-35-39-35-39-5A-30-00-30-82-01-22-30-0D-06-09-2A-86-48-86-F7-0D-01-01-01-05-00-03-82-01-0F-00-30-82-01-0A-02-82-01-01-00-C2
+2019-04-24 04:11:14.311 [84] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-01 01-00 | 06-B0-03-34 E8-01 77-66-43-5B-FE-90-B5-ED-06-1E-27-68-D3-8F-E7-49-DF-68-08-46-30-91-70-8F-5A-34-B9-1D-DC-0B-9C-58-95-3A-88-EC-C4-CB-C9-29-64-14-2C-2B-4C-25-22-A2-B7-44-73-95
+2019-04-24 04:11:14.315 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-01 01-00 | 06-A0-03-3A 9C-02 28-80-AF-33-6B-33-7A-F7-C7-78-30-53-3C-D5-7D-B2-63-CA-4E-06-F7-EE-C5-5F-F5-26-7F-A7-E2-9E-14-DB-94-4E-D6-6B-C1-8B-05-64-28-71-E1-58-53-78-07-56-B3-3F-6E-15-1E-96-8F-93-CB-1B
+2019-04-24 04:11:14.422 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-01 01-00 | 06-A0-03-3A D6-02 83-A0-44-38-DF-12-9F-20-E4-E1-F8-56-CB-44-A2-3C-01-19-E1-82-34-51-67-E5-24-8E-69-9A-9E-F3-53-2E-26-D1-CC-1D-F9-D1-A6-55-6F-E2-A6-B3-A9-4A-3A-9D-FB-82-98-DA-3C-DC-B0-F7-30-2A
+2019-04-24 04:11:14.528 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-01 01-00 | 06-A0-03-3A 90-03 D3-46-43-1F-3D-05-48-19-37-00-AF-95-09-60-FA-79-D6-9B-41-06-C7-BF-D7-DA-2C-0E-56-83-16-AB-71-EE-57-8A-76-FE-57-97-45-13-A4-92-C8-82-39-89-EF-0D-08-E8-AA-C0-8E-CD-6D-DD-3E-E6
+2019-04-24 04:11:14.633 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-01 01-00 | 06-A0-03-3A CA-03 57-F0-F0-28-51-52-B2-35-95-34-C9-6A-14-73-10-78-4B-6A-E5-63-2A-4A-17-7E-49-2F-84-C6-61-02-03-01-00-01-A3-37-30-35-30-0E-06-03-55-1D-0F-01-01-FF-04-04-03-02-00-B0-30-0C-06-03
+2019-04-24 04:11:14.738 [84] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-01 01-00 | 06-B0-03-34 84-04 55-1D-13-01-01-FF-04-02-30-00-30-15-06-03-55-1D-25-04-0E-30-0C-06-0A-2B-06-01-04-01-82-37-78-03-01-30-0D-06-09-2A-86-48-86-F7-0D-01-01-0B-05-00-03-82-01-01
+2019-04-24 04:11:14.792 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-01 01-00 | 06-A0-03-3A B8-04 00-04-29-C4-02-8D-60-36-0A-B6-57-2E-75-7E-D5-FB-CD-B1-1D-B4-9E-E0-7C-DB-EE-F3-A4-10-BB-20-5F-2D-45-FE-44-9B-FB-49-C6-5D-F8-20-3D-BB-27-60-57-0A-35-40-36-01-6B-9C-43-04-87-5E
+2019-04-24 04:11:14.897 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-01 01-00 | 06-A0-03-3A F2-04 F1-9E-17-AF-46-B4-35-D8-AB-39-DD-A6-66-3C-9F-40-AD-98-96-F5-DA-9C-52-A5-EC-07-D1-7E-13-CB-3D-05-28-2C-21-95-51-23-C5-D2-DE-0B-17-42-93-F5-E2-67-3C-FE-18-16-5D-D1-47-6D-83-B8
+2019-04-24 04:11:14.953 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-01 01-00 | 06-A0-03-3A AC-05 B6-DF-6D-87-98-1A-82-F7-CB-AF-41-8A-41-D8-14-F0-8A-78-C1-7A-DE-FB-53-8E-CD-93-B6-FB-FE-34-AF-D3-1E-E3-38-ED-DB-D2-3C-7E-FF-F4-FD-ED-D8-0D-AA-87-7F-8F-D6-A7-85-76-18-47-3E-64
+2019-04-24 04:11:15.109 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-01 01-00 | 06-A0-03-3A E6-05 B7-DC-76-3D-F8-BF-68-A6-8D-3D-FF-C0-91-C0-D6-3B-77-35-AC-D8-CC-4C-A8-4E-76-59-17-05-03-07-43-D5-43-00-0E-3D-A6-CC-C4-A2-A8-BF-7E-C9-37-03-57-6E-F5-05-A5-20-A6-0F-29-D5-FA-8A
+2019-04-24 04:11:15.164 [57] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-01 01-00 | 06-B0-03-19 A0-06 E0-73-9A-F8-F0-51-02-AB-5B-BE-B0-4F-3B-3B-6F-BA-62-1A-82-2F-7F-4F-9B-66-95
+2019-04-24 04:11:15.218 [32] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-01 01-00 | 06-A0-03-00 B9-06
+
+2019-04-24 04:11:15.272 [39] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-01 01-00 | 01-20-04-09 00-06-F0-3A-00-00-00-D8-00
+2019-04-24 04:11:15.326 [39] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-02 01-00 | 01-20-04-09 00-06-B0-12-01-00-00-00-00
+2019-04-24 04:11:15.939 [36] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-02 01-00 | 06-30-04-06 00-C1-00-01-00-00
+2019-04-24 04:11:15.993 [32] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-02 01-00 | 06-A0-04-00 06-00
+
+2019-04-24 04:11:15.997 [39] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-02 01-00 | 01-20-05-09 00-06-30-32-00-00-00-32-00
+2019-04-24 04:11:16.103 [36] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-02 01-00 | 06-30-05-06 00-C1-00-01-00-00
+2019-04-24 04:11:16.157 [32] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-02 01-00 | 06-A0-05-00 06-00
+
+2019-04-24 04:11:16.161 [39] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-02 01-00 | 01-20-06-09 00-06-30-0E-00-00-00-0E-00
+2019-04-24 04:11:16.317 [90] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-02 01-00 | 06-F0-06-3A CA-00 00-C2-00-08-00-44-08-01-00-40-41-C6-9E-FB-59-40-0A-3A-FC-6A-3A-5D-77-D1-C9-B9-5F-03-E0-C5-D3-F7-AB-1D-E1-74-E0-6E-21-96-67-C4-8C-F7-CB-97-7F-48-0B-ED-41-C4-90-C5-5A-DF-49-81
+2019-04-24 04:11:16.322 [48] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-02 01-00 | 06-B0-06-90 00-3A 34-5A-47-FF-13-01-DC-B0-14-9D-DC-17-E2-BB-C0-FD
+2019-04-24 04:11:16.326 [32] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-02 01-00 | 06-A0-06-00 4A-00
+
+2019-04-24 04:11:16.380 [40] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-00 00-00 | 20-00-01-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:11:16.384 [40] -> 88-11 A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-00 00-00 | 20-00-02-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/menu.log b/Docs/Packet Logs/GuitarSniffer/menu.log
new file mode 100644
index 0000000..43058bb
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/menu.log
@@ -0,0 +1,10 @@
+2019-04-24 04:15:11.147 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-0A-00-00 | 20-00-A5-0A 04-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:11.303 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-0A-00-00 | 20-00-A6-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:11.811 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-0A-00-00 | 20-00-A7-0A 04-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:11.915 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-0A-00-00 | 20-00-A8-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:12.371 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-0A-00-00 | 20-00-A9-0A 04-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:12.533 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-0A-00-00 | 20-00-AA-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:12.939 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-0A-00-00 | 20-00-AB-0A 04-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:13.044 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-0A-00-00 | 20-00-AC-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:13.398 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-0A-00-00 | 20-00-AD-0A 04-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:13.502 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-0A-00-00 | 20-00-AE-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/options.log b/Docs/Packet Logs/GuitarSniffer/options.log
new file mode 100644
index 0000000..b6dfe14
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/options.log
@@ -0,0 +1,14 @@
+2019-04-24 04:15:22.511 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-0A-00-00 | 20-00-AF-0A 08-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:22.616 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-0A-00-00 | 20-00-B0-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:22.970 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-0B-00-00 | 20-00-B1-0A 08-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:23.125 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-0B-00-00 | 20-00-B2-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:23.429 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-0B-00-00 | 20-00-B3-0A 08-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:23.583 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-0B-00-00 | 20-00-B4-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:23.837 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-0B-00-00 | 20-00-B5-0A 08-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:23.991 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-0B-00-00 | 20-00-B6-0A 00-00-00-00-40-00-00-00-00-00
+
+2019-04-24 04:15:23.994 [34] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-03-01-00 | 03-20-0C-04 86-01-00-00
+
+2019-04-24 04:15:23.997 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-0B-00-00 | 20-00-B7-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:24.203 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-0B-00-00 | 20-00-B8-0A 08-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:24.306 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-0B-00-00 | 20-00-B9-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/orange_lower.log b/Docs/Packet Logs/GuitarSniffer/orange_lower.log
new file mode 100644
index 0000000..887449b
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/orange_lower.log
@@ -0,0 +1,14 @@
+2019-04-24 04:14:29.741 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-07-00-00 | 20-00-79-0A 00-50-00-00-40-00-10-00-00-00
+2019-04-24 04:14:29.847 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-07-00-00 | 20-00-7A-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:30.002 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-07-00-00 | 20-00-7B-0A 00-50-00-00-40-00-10-00-00-00
+2019-04-24 04:14:30.055 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-07-00-00 | 20-00-7C-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:30.159 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-07-00-00 | 20-00-7D-0A 00-50-00-00-40-00-10-00-00-00
+2019-04-24 04:14:30.262 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-07-00-00 | 20-00-7E-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:30.367 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-07-00-00 | 20-00-7F-0A 00-50-00-00-40-00-10-00-00-00
+2019-04-24 04:14:30.470 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-07-00-00 | 20-00-80-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:30.573 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-08-00-00 | 20-00-81-0A 00-50-00-00-40-00-10-00-00-00
+2019-04-24 04:14:30.627 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-08-00-00 | 20-00-82-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:30.731 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-08-00-00 | 20-00-83-0A 00-50-00-00-40-00-10-00-00-00
+2019-04-24 04:14:30.786 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-08-00-00 | 20-00-84-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:31.093 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-08-00-00 | 20-00-85-0A 00-50-00-00-40-00-10-00-00-00
+2019-04-24 04:14:31.197 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-08-00-00 | 20-00-86-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/orange_upper.log b/Docs/Packet Logs/GuitarSniffer/orange_upper.log
new file mode 100644
index 0000000..d86e70a
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/orange_upper.log
@@ -0,0 +1,17 @@
+2019-04-24 04:13:30.978 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-03-00-00 | 20-00-32-0A 00-10-00-00-40-10-00-00-00-00
+2019-04-24 04:13:31.033 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-03-00-00 | 20-00-33-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:31.036 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-03-00-00 | 20-00-34-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:31.140 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-03-00-00 | 20-00-35-0A 00-10-00-00-40-10-00-00-00-00
+2019-04-24 04:13:31.244 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-03-00-00 | 20-00-36-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:31.298 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-03-00-00 | 20-00-37-0A 00-10-00-00-40-10-00-00-00-00
+2019-04-24 04:13:31.403 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-03-00-00 | 20-00-38-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:31.458 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-03-00-00 | 20-00-39-0A 00-10-00-00-40-10-00-00-00-00
+2019-04-24 04:13:31.562 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-03-00-00 | 20-00-3A-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:31.615 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-03-00-00 | 20-00-3B-0A 00-10-00-00-40-10-00-00-00-00
+2019-04-24 04:13:31.719 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-03-00-00 | 20-00-3C-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:31.772 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-03-00-00 | 20-00-3D-0A 00-10-00-00-40-10-00-00-00-00
+2019-04-24 04:13:31.876 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-03-00-00 | 20-00-3E-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:31.930 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-03-00-00 | 20-00-3F-0A 00-10-00-00-40-10-00-00-00-00
+2019-04-24 04:13:32.035 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-03-00-00 | 20-00-40-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:32.138 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-04-00-00 | 20-00-41-0A 00-10-00-00-40-10-00-00-00-00
+2019-04-24 04:13:32.192 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-04-00-00 | 20-00-42-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/red_lower.log b/Docs/Packet Logs/GuitarSniffer/red_lower.log
new file mode 100644
index 0000000..8ba0611
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/red_lower.log
@@ -0,0 +1,14 @@
+2019-04-24 04:13:56.255 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-04-00-00 | 20-00-4D-0A 20-40-00-00-40-00-02-00-00-00
+2019-04-24 04:13:56.362 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-04-00-00 | 20-00-4E-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:56.466 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-04-00-00 | 20-00-4F-0A 20-40-00-00-40-00-02-00-00-00
+2019-04-24 04:13:56.520 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-04-00-00 | 20-00-50-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:56.624 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-05-00-00 | 20-00-51-0A 20-40-00-00-40-00-02-00-00-00
+2019-04-24 04:13:56.739 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-05-00-00 | 20-00-52-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:56.793 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-05-00-00 | 20-00-53-0A 20-40-00-00-40-00-02-00-00-00
+2019-04-24 04:13:56.846 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-05-00-00 | 20-00-54-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:57.150 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-05-00-00 | 20-00-55-0A 20-40-00-00-40-00-02-00-00-00
+2019-04-24 04:13:57.204 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-05-00-00 | 20-00-56-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:57.308 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-05-00-00 | 20-00-57-0A 20-40-00-00-40-00-02-00-00-00
+2019-04-24 04:13:57.362 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-05-00-00 | 20-00-58-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:57.466 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-05-00-00 | 20-00-59-0A 20-40-00-00-40-00-02-00-00-00
+2019-04-24 04:13:57.569 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-05-00-00 | 20-00-5A-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/red_upper.log b/Docs/Packet Logs/GuitarSniffer/red_upper.log
new file mode 100644
index 0000000..d1c1809
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/red_upper.log
@@ -0,0 +1,8 @@
+2019-04-24 04:12:20.176 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-01-00-00 | 20-00-12-0A 20-00-00-00-40-02-00-00-00-00
+2019-04-24 04:12:20.385 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-01-00-00 | 20-00-13-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:12:20.592 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-01-00-00 | 20-00-14-0A 20-00-00-00-40-02-00-00-00-00
+2019-04-24 04:12:20.747 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-01-00-00 | 20-00-15-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:12:20.904 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-01-00-00 | 20-00-16-0A 20-00-00-00-40-02-00-00-00-00
+2019-04-24 04:12:21.060 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-01-00-00 | 20-00-17-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:12:21.214 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-01-00-00 | 20-00-18-0A 20-00-00-00-40-02-00-00-00-00
+2019-04-24 04:12:21.318 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-01-00-00 | 20-00-19-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/slider.log b/Docs/Packet Logs/GuitarSniffer/slider.log
new file mode 100644
index 0000000..250741e
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/slider.log
@@ -0,0 +1,18 @@
+2019-04-24 04:15:34.373 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-0B-00-00 | 20-00-BA-0A 00-00-00-00-30-00-00-00-00-00
+2019-04-24 04:15:34.428 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-0B-00-00 | 20-00-BB-0A 00-00-00-00-20-00-00-00-00-00
+2019-04-24 04:15:34.631 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-0B-00-00 | 20-00-BC-0A 00-00-00-00-10-00-00-00-00-00
+2019-04-24 04:15:34.736 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-0B-00-00 | 20-00-BD-0A 00-00-00-00-00-00-00-00-00-00
+2019-04-24 04:15:35.246 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-0B-00-00 | 20-00-BE-0A 00-00-00-00-10-00-00-00-00-00
+2019-04-24 04:15:35.300 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-0B-00-00 | 20-00-BF-0A 00-00-00-00-20-00-00-00-00-00
+2019-04-24 04:15:35.354 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-0B-00-00 | 20-00-C0-0A 00-00-00-00-30-00-00-00-00-00
+2019-04-24 04:15:35.357 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-0C-00-00 | 20-00-C1-0A 00-00-00-00-30-00-00-00-00-00
+2019-04-24 04:15:35.410 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-0C-00-00 | 20-00-C2-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:35.816 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-0C-00-00 | 20-00-C3-0A 00-00-00-00-30-00-00-00-00-00
+2019-04-24 04:15:35.870 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-0C-00-00 | 20-00-C4-0A 00-00-00-00-20-00-00-00-00-00
+2019-04-24 04:15:35.923 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-0C-00-00 | 20-00-C5-0A 00-00-00-00-10-00-00-00-00-00
+2019-04-24 04:15:35.976 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-0C-00-00 | 20-00-C6-0A 00-00-00-00-00-00-00-00-00-00
+2019-04-24 04:15:36.330 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-0C-00-00 | 20-00-C7-0A 00-00-00-00-10-00-00-00-00-00
+2019-04-24 04:15:36.384 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-0C-00-00 | 20-00-C8-0A 00-00-00-00-20-00-00-00-00-00
+2019-04-24 04:15:36.437 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-0C-00-00 | 20-00-C9-0A 00-00-00-00-30-00-00-00-00-00
+2019-04-24 04:15:36.490 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-0C-00-00 | 20-00-CA-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:36.543 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-0C-00-00 | 20-00-CB-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/strum_down.log b/Docs/Packet Logs/GuitarSniffer/strum_down.log
new file mode 100644
index 0000000..1092ea6
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/strum_down.log
@@ -0,0 +1,14 @@
+2019-04-24 04:14:56.557 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-09-00-00 | 20-00-97-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:14:56.764 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-09-00-00 | 20-00-98-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:57.069 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-09-00-00 | 20-00-99-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:14:57.072 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-09-00-00 | 20-00-9A-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:57.476 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-09-00-00 | 20-00-9B-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:14:57.530 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-09-00-00 | 20-00-9C-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:57.885 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-09-00-00 | 20-00-9D-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:14:57.888 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-09-00-00 | 20-00-9E-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:58.246 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-09-00-00 | 20-00-9F-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:14:58.299 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-09-00-00 | 20-00-A0-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:58.655 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-0A-00-00 | 20-00-A1-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:14:58.658 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-0A-00-00 | 20-00-A2-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:59.371 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-0A-00-00 | 20-00-A3-0A 00-02-00-00-40-00-00-00-00-00
+2019-04-24 04:14:59.374 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-0A-00-00 | 20-00-A4-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/strum_up.log b/Docs/Packet Logs/GuitarSniffer/strum_up.log
new file mode 100644
index 0000000..bc4bebc
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/strum_up.log
@@ -0,0 +1,19 @@
+2019-04-24 04:14:40.937 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-08-00-00 | 20-00-87-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:14:42.151 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-08-00-00 | 20-00-88-0A 00-00-00-00-40-00-00-00-00-00
+
+2019-04-24 04:14:42.204 [34] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-03-01-00 | 03-20-0A-04 86-01-00-00
+
+2019-04-24 04:14:42.961 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-08-00-00 | 20-00-89-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:14:43.166 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-08-00-00 | 20-00-8A-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:43.622 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-08-00-00 | 20-00-8B-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:14:43.827 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-08-00-00 | 20-00-8C-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:44.182 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-08-00-00 | 20-00-8D-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:14:44.389 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-08-00-00 | 20-00-8E-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:44.692 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-08-00-00 | 20-00-8F-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:14:44.897 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-08-00-00 | 20-00-90-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:45.154 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-09-00-00 | 20-00-91-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:14:45.410 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-09-00-00 | 20-00-92-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:45.664 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-09-00-00 | 20-00-93-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:14:45.867 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-09-00-00 | 20-00-94-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:46.122 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-09-00-00 | 20-00-95-0A 00-01-00-00-40-00-00-00-00-00
+2019-04-24 04:14:46.325 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-09-00-00 | 20-00-96-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/tilt.log b/Docs/Packet Logs/GuitarSniffer/tilt.log
new file mode 100644
index 0000000..e07aeb2
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/tilt.log
@@ -0,0 +1,165 @@
+2019-04-24 04:16:51.694 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-13-00-00 | 20-00-3D-0A 00-00-77-00-40-00-00-00-00-00
+2019-04-24 04:16:51.695 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-13-00-00 | 20-00-3E-0A 00-00-7D-00-40-00-00-00-00-00
+2019-04-24 04:16:51.695 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-13-00-00 | 20-00-3F-0A 00-00-82-00-40-00-00-00-00-00
+2019-04-24 04:16:51.695 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-13-00-00 | 20-00-40-0A 00-00-84-00-40-00-00-00-00-00
+2019-04-24 04:16:51.757 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-14-00-00 | 20-00-41-0A 00-00-8A-00-40-00-00-00-00-00
+2019-04-24 04:16:51.757 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-14-00-00 | 20-00-42-0A 00-00-8D-00-40-00-00-00-00-00
+2019-04-24 04:16:51.757 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-14-00-00 | 20-00-43-0A 00-00-90-00-40-00-00-00-00-00
+2019-04-24 04:16:51.757 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-14-00-00 | 20-00-44-0A 00-00-93-00-40-00-00-00-00-00
+2019-04-24 04:16:51.757 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-14-00-00 | 20-00-45-0A 00-00-9C-00-40-00-00-00-00-00
+2019-04-24 04:16:51.757 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-14-00-00 | 20-00-46-0A 00-00-A2-00-40-00-00-00-00-00
+2019-04-24 04:16:51.773 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-14-00-00 | 20-00-47-0A 00-00-A6-00-40-00-00-00-00-00
+2019-04-24 04:16:51.835 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-14-00-00 | 20-00-48-0A 00-00-AC-00-40-00-00-00-00-00
+2019-04-24 04:16:51.835 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-14-00-00 | 20-00-49-0A 00-00-B4-00-40-00-00-00-00-00
+2019-04-24 04:16:51.835 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-14-00-00 | 20-00-4A-0A 00-00-B9-00-40-00-00-00-00-00
+2019-04-24 04:16:51.835 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-14-00-00 | 20-00-4B-0A 00-00-BE-00-40-00-00-00-00-00
+2019-04-24 04:16:51.835 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-14-00-00 | 20-00-4C-0A 00-00-C2-00-40-00-00-00-00-00
+2019-04-24 04:16:51.851 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-14-00-00 | 20-00-4D-0A 00-00-C5-00-40-00-00-00-00-00
+2019-04-24 04:16:51.851 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-14-00-00 | 20-00-4E-0A 00-00-C7-00-40-00-00-00-00-00
+2019-04-24 04:16:51.914 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-14-00-00 | 20-00-4F-0A 00-00-C9-00-40-00-00-00-00-00
+2019-04-24 04:16:51.914 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-14-00-00 | 20-00-50-0A 00-00-CA-00-40-00-00-00-00-00
+2019-04-24 04:16:51.914 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-15-00-00 | 20-00-51-0A 00-00-CD-00-40-00-00-00-00-00
+2019-04-24 04:16:51.914 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-15-00-00 | 20-00-52-0A 00-00-CF-00-40-00-00-00-00-00
+2019-04-24 04:16:51.914 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-15-00-00 | 20-00-53-0A 00-00-D0-00-40-00-00-00-00-00
+2019-04-24 04:16:51.929 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-15-00-00 | 20-00-54-0A 00-00-D1-00-40-00-00-00-00-00
+2019-04-24 04:16:51.929 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-15-00-00 | 20-00-55-0A 00-00-D4-00-40-00-00-00-00-00
+2019-04-24 04:16:51.992 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-15-00-00 | 20-00-56-0A 00-00-D5-00-40-00-00-00-00-00
+2019-04-24 04:16:51.992 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-15-00-00 | 20-00-57-0A 00-00-D7-00-40-00-00-00-00-00
+2019-04-24 04:16:51.992 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-15-00-00 | 20-00-58-0A 00-00-DB-00-40-00-00-00-00-00
+2019-04-24 04:16:51.992 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-15-00-00 | 20-00-59-0A 00-00-DC-00-40-00-00-00-00-00
+2019-04-24 04:16:51.992 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-15-00-00 | 20-00-5A-0A 00-00-DD-00-40-00-00-00-00-00
+2019-04-24 04:16:52.007 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-15-00-00 | 20-00-5B-0A 00-00-DF-00-40-00-00-00-00-00
+2019-04-24 04:16:52.007 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-15-00-00 | 20-00-5C-0A 00-00-E0-00-40-00-00-00-00-00
+2019-04-24 04:16:52.070 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-15-00-00 | 20-00-5D-0A 00-00-E1-00-40-00-00-00-00-00
+2019-04-24 04:16:52.070 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-15-00-00 | 20-00-5E-0A 00-00-E5-00-40-00-00-00-00-00
+2019-04-24 04:16:52.070 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-15-00-00 | 20-00-5F-0A 00-00-E8-00-40-00-00-00-00-00
+2019-04-24 04:16:52.070 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-15-00-00 | 20-00-60-0A 00-00-EB-00-40-00-00-00-00-00
+2019-04-24 04:16:52.070 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-16-00-00 | 20-00-61-0A 00-00-ED-00-40-00-00-00-00-00
+2019-04-24 04:16:52.085 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-16-00-00 | 20-00-62-0A 00-00-F4-00-40-00-00-00-00-00
+2019-04-24 04:16:52.148 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-16-00-00 | 20-00-63-0A 00-00-F5-00-40-00-00-00-00-00
+2019-04-24 04:16:52.148 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-16-00-00 | 20-00-64-0A 00-00-F6-00-40-00-00-00-00-00
+2019-04-24 04:16:52.148 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-16-00-00 | 20-00-65-0A 00-00-F7-00-40-00-00-00-00-00
+2019-04-24 04:16:52.148 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-16-00-00 | 20-00-66-0A 00-00-F6-00-40-00-00-00-00-00
+2019-04-24 04:16:52.148 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-16-00-00 | 20-00-67-0A 00-00-F5-00-40-00-00-00-00-00
+2019-04-24 04:16:52.214 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-16-00-00 | 20-00-68-0A 00-00-F7-00-40-00-00-00-00-00
+2019-04-24 04:16:52.218 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-16-00-00 | 20-00-69-0A 00-00-F8-00-40-00-00-00-00-00
+2019-04-24 04:16:52.221 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-16-00-00 | 20-00-6A-0A 00-00-FA-00-40-00-00-00-00-00
+2019-04-24 04:16:52.224 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-16-00-00 | 20-00-6B-0A 00-00-FB-00-40-00-00-00-00-00
+2019-04-24 04:16:52.228 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-16-00-00 | 20-00-6C-0A 00-00-FF-00-40-00-00-00-00-00
+2019-04-24 04:16:52.333 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-16-00-00 | 20-00-6D-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:52.335 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-16-00-00 | 20-00-6E-0A 00-00-F7-00-40-00-00-00-00-00
+2019-04-24 04:16:52.335 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-16-00-00 | 20-00-6F-0A 00-00-F2-00-40-00-00-00-00-00
+2019-04-24 04:16:52.335 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-16-00-00 | 20-00-70-0A 00-00-EE-00-40-00-00-00-00-00
+2019-04-24 04:16:52.335 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-17-00-00 | 20-00-71-0A 00-00-EB-00-40-00-00-00-00-00
+2019-04-24 04:16:52.335 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-17-00-00 | 20-00-72-0A 00-00-EC-00-40-00-00-00-00-00
+2019-04-24 04:16:52.413 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-17-00-00 | 20-00-73-0A 00-00-EE-00-40-00-00-00-00-00
+2019-04-24 04:16:52.413 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-17-00-00 | 20-00-74-0A 00-00-F2-00-40-00-00-00-00-00
+2019-04-24 04:16:52.413 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-17-00-00 | 20-00-75-0A 00-00-F5-00-40-00-00-00-00-00
+2019-04-24 04:16:52.413 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-17-00-00 | 20-00-76-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:52.429 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-17-00-00 | 20-00-77-0A 00-00-FF-00-40-00-00-00-00-00
+2019-04-24 04:16:52.492 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-17-00-00 | 20-00-78-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:52.492 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-17-00-00 | 20-00-79-0A 00-00-FB-00-40-00-00-00-00-00
+2019-04-24 04:16:52.492 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-17-00-00 | 20-00-7A-0A 00-00-F9-00-40-00-00-00-00-00
+2019-04-24 04:16:52.492 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-17-00-00 | 20-00-7B-0A 00-00-FA-00-40-00-00-00-00-00
+2019-04-24 04:16:52.492 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-17-00-00 | 20-00-7C-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:52.507 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-17-00-00 | 20-00-7D-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:52.570 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-17-00-00 | 20-00-7E-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:52.570 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-17-00-00 | 20-00-7F-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:52.570 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-17-00-00 | 20-00-80-0A 00-00-FB-00-40-00-00-00-00-00
+2019-04-24 04:16:52.570 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-18-00-00 | 20-00-81-0A 00-00-F9-00-40-00-00-00-00-00
+2019-04-24 04:16:52.570 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-18-00-00 | 20-00-82-0A 00-00-F7-00-40-00-00-00-00-00
+2019-04-24 04:16:52.648 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-18-00-00 | 20-00-83-0A 00-00-F8-00-40-00-00-00-00-00
+2019-04-24 04:16:52.648 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-18-00-00 | 20-00-84-0A 00-00-F9-00-40-00-00-00-00-00
+2019-04-24 04:16:52.648 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-18-00-00 | 20-00-85-0A 00-00-FA-00-40-00-00-00-00-00
+2019-04-24 04:16:52.648 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-18-00-00 | 20-00-86-0A 00-00-FB-00-40-00-00-00-00-00
+2019-04-24 04:16:52.663 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-18-00-00 | 20-00-87-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:52.733 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-18-00-00 | 20-00-88-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:52.733 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-18-00-00 | 20-00-89-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:52.733 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-18-00-00 | 20-00-8A-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:52.733 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-18-00-00 | 20-00-8B-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:52.733 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-18-00-00 | 20-00-8C-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:52.748 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-18-00-00 | 20-00-8D-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:52.748 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-18-00-00 | 20-00-8E-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:52.811 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-18-00-00 | 20-00-8F-0A 00-00-FF-00-40-00-00-00-00-00
+2019-04-24 04:16:52.811 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-18-00-00 | 20-00-90-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:52.811 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-19-00-00 | 20-00-91-0A 00-00-FF-00-40-00-00-00-00-00
+2019-04-24 04:16:52.811 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-19-00-00 | 20-00-92-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:52.811 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-19-00-00 | 20-00-93-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:52.889 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-19-00-00 | 20-00-94-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:52.889 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-19-00-00 | 20-00-95-0A 00-00-FF-00-40-00-00-00-00-00
+2019-04-24 04:16:52.951 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-19-00-00 | 20-00-96-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:52.951 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-19-00-00 | 20-00-97-0A 00-00-FF-00-40-00-00-00-00-00
+2019-04-24 04:16:53.076 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-19-00-00 | 20-00-98-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:53.076 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-19-00-00 | 20-00-99-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:53.076 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-19-00-00 | 20-00-9A-0A 00-00-FB-00-40-00-00-00-00-00
+2019-04-24 04:16:53.076 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-19-00-00 | 20-00-9B-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:53.092 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-19-00-00 | 20-00-9C-0A 00-00-FB-00-40-00-00-00-00-00
+2019-04-24 04:16:53.155 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-19-00-00 | 20-00-9D-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:53.155 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-19-00-00 | 20-00-9E-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:53.155 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-19-00-00 | 20-00-9F-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:53.155 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-19-00-00 | 20-00-A0-0A 00-00-FF-00-40-00-00-00-00-00
+2019-04-24 04:16:53.289 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-1A-00-00 | 20-00-A1-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:53.289 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-1A-00-00 | 20-00-A2-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:53.289 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-1A-00-00 | 20-00-A3-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:53.289 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-1A-00-00 | 20-00-A4-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:53.305 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-1A-00-00 | 20-00-A5-0A 00-00-FB-00-40-00-00-00-00-00
+2019-04-24 04:16:53.305 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-1A-00-00 | 20-00-A6-0A 00-00-F9-00-40-00-00-00-00-00
+2019-04-24 04:16:53.360 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-1A-00-00 | 20-00-A7-0A 00-00-FA-00-40-00-00-00-00-00
+2019-04-24 04:16:53.360 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-1A-00-00 | 20-00-A8-0A 00-00-FB-00-40-00-00-00-00-00
+2019-04-24 04:16:53.360 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-1A-00-00 | 20-00-A9-0A 00-00-FC-00-40-00-00-00-00-00
+2019-04-24 04:16:53.360 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-1A-00-00 | 20-00-AA-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:53.360 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-1A-00-00 | 20-00-AB-0A 00-00-FF-00-40-00-00-00-00-00
+2019-04-24 04:16:53.563 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-1A-00-00 | 20-00-AC-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:53.563 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-1A-00-00 | 20-00-AD-0A 00-00-FF-00-40-00-00-00-00-00
+2019-04-24 04:16:53.563 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-1A-00-00 | 20-00-AE-0A 00-00-FE-00-40-00-00-00-00-00
+2019-04-24 04:16:53.626 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-1A-00-00 | 20-00-AF-0A 00-00-FF-00-40-00-00-00-00-00
+2019-04-24 04:16:53.689 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-1A-00-00 | 20-00-B0-0A 00-00-FD-00-40-00-00-00-00-00
+2019-04-24 04:16:53.689 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-1B-00-00 | 20-00-B1-0A 00-00-FB-00-40-00-00-00-00-00
+2019-04-24 04:16:53.752 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-1B-00-00 | 20-00-B2-0A 00-00-F9-00-40-00-00-00-00-00
+2019-04-24 04:16:53.752 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-1B-00-00 | 20-00-B3-0A 00-00-F7-00-40-00-00-00-00-00
+2019-04-24 04:16:53.752 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-1B-00-00 | 20-00-B4-0A 00-00-F5-00-40-00-00-00-00-00
+2019-04-24 04:16:53.752 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-1B-00-00 | 20-00-B5-0A 00-00-F4-00-40-00-00-00-00-00
+2019-04-24 04:16:53.767 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-1B-00-00 | 20-00-B6-0A 00-00-F3-00-40-00-00-00-00-00
+2019-04-24 04:16:53.767 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-1B-00-00 | 20-00-B7-0A 00-00-F2-00-40-00-00-00-00-00
+2019-04-24 04:16:53.829 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-1B-00-00 | 20-00-B8-0A 00-00-F1-00-40-00-00-00-00-00
+2019-04-24 04:16:53.829 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-1B-00-00 | 20-00-B9-0A 00-00-EF-00-40-00-00-00-00-00
+2019-04-24 04:16:53.829 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-1B-00-00 | 20-00-BA-0A 00-00-EB-00-40-00-00-00-00-00
+2019-04-24 04:16:53.829 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-1B-00-00 | 20-00-BB-0A 00-00-E9-00-40-00-00-00-00-00
+2019-04-24 04:16:53.845 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-1B-00-00 | 20-00-BC-0A 00-00-E5-00-40-00-00-00-00-00
+2019-04-24 04:16:53.845 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-1B-00-00 | 20-00-BD-0A 00-00-E0-00-40-00-00-00-00-00
+2019-04-24 04:16:53.845 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-1B-00-00 | 20-00-BE-0A 00-00-DC-00-40-00-00-00-00-00
+2019-04-24 04:16:53.908 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-1B-00-00 | 20-00-BF-0A 00-00-D7-00-40-00-00-00-00-00
+2019-04-24 04:16:53.908 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-1B-00-00 | 20-00-C0-0A 00-00-D4-00-40-00-00-00-00-00
+2019-04-24 04:16:53.908 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-1C-00-00 | 20-00-C1-0A 00-00-D3-00-40-00-00-00-00-00
+2019-04-24 04:16:53.908 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-1C-00-00 | 20-00-C2-0A 00-00-D1-00-40-00-00-00-00-00
+2019-04-24 04:16:53.923 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-1C-00-00 | 20-00-C3-0A 00-00-CF-00-40-00-00-00-00-00
+2019-04-24 04:16:53.923 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-1C-00-00 | 20-00-C4-0A 00-00-CD-00-40-00-00-00-00-00
+2019-04-24 04:16:53.923 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-1C-00-00 | 20-00-C5-0A 00-00-C9-00-40-00-00-00-00-00
+2019-04-24 04:16:53.923 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-1C-00-00 | 20-00-C6-0A 00-00-C8-00-40-00-00-00-00-00
+2019-04-24 04:16:54.001 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-1C-00-00 | 20-00-C7-0A 00-00-C6-00-40-00-00-00-00-00
+2019-04-24 04:16:54.001 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-1C-00-00 | 20-00-C8-0A 00-00-C3-00-40-00-00-00-00-00
+2019-04-24 04:16:54.001 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-1C-00-00 | 20-00-C9-0A 00-00-BE-00-40-00-00-00-00-00
+2019-04-24 04:16:54.001 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-1C-00-00 | 20-00-CA-0A 00-00-BC-00-40-00-00-00-00-00
+2019-04-24 04:16:54.017 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-1C-00-00 | 20-00-CB-0A 00-00-BA-00-40-00-00-00-00-00
+2019-04-24 04:16:54.017 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-1C-00-00 | 20-00-CC-0A 00-00-B7-00-40-00-00-00-00-00
+2019-04-24 04:16:54.017 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-1C-00-00 | 20-00-CD-0A 00-00-B4-00-40-00-00-00-00-00
+2019-04-24 04:16:54.017 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-1C-00-00 | 20-00-CE-0A 00-00-AF-00-40-00-00-00-00-00
+2019-04-24 04:16:54.032 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-1C-00-00 | 20-00-CF-0A 00-00-AA-00-40-00-00-00-00-00
+2019-04-24 04:16:54.095 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-1C-00-00 | 20-00-D0-0A 00-00-A4-00-40-00-00-00-00-00
+2019-04-24 04:16:54.095 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-1D-00-00 | 20-00-D1-0A 00-00-A0-00-40-00-00-00-00-00
+2019-04-24 04:16:54.095 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-1D-00-00 | 20-00-D2-0A 00-00-9F-00-40-00-00-00-00-00
+2019-04-24 04:16:54.095 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-1D-00-00 | 20-00-D3-0A 00-00-9C-00-40-00-00-00-00-00
+2019-04-24 04:16:54.110 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-1D-00-00 | 20-00-D4-0A 00-00-99-00-40-00-00-00-00-00
+2019-04-24 04:16:54.110 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-1D-00-00 | 20-00-D5-0A 00-00-97-00-40-00-00-00-00-00
+2019-04-24 04:16:54.110 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-1D-00-00 | 20-00-D6-0A 00-00-95-00-40-00-00-00-00-00
+2019-04-24 04:16:54.110 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-1D-00-00 | 20-00-D7-0A 00-00-93-00-40-00-00-00-00-00
+2019-04-24 04:16:54.126 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-1D-00-00 | 20-00-D8-0A 00-00-8E-00-40-00-00-00-00-00
+2019-04-24 04:16:54.184 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-1D-00-00 | 20-00-D9-0A 00-00-8D-00-40-00-00-00-00-00
+2019-04-24 04:16:54.184 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-1D-00-00 | 20-00-DA-0A 00-00-89-00-40-00-00-00-00-00
+2019-04-24 04:16:54.184 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-1D-00-00 | 20-00-DB-0A 00-00-86-00-40-00-00-00-00-00
+2019-04-24 04:16:54.184 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-1D-00-00 | 20-00-DC-0A 00-00-81-00-40-00-00-00-00-00
+2019-04-24 04:16:54.200 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-1D-00-00 | 20-00-DD-0A 00-00-7F-00-40-00-00-00-00-00
+2019-04-24 04:16:54.200 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-1D-00-00 | 20-00-DE-0A 00-00-7C-00-40-00-00-00-00-00
+2019-04-24 04:16:54.200 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-1D-00-00 | 20-00-DF-0A 00-00-79-00-40-00-00-00-00-00
+2019-04-24 04:16:54.200 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-1D-00-00 | 20-00-E0-0A 00-00-72-00-40-00-00-00-00-00
+2019-04-24 04:16:54.279 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-1E-00-00 | 20-00-E1-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/whammy.log b/Docs/Packet Logs/GuitarSniffer/whammy.log
new file mode 100644
index 0000000..66d7050
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/whammy.log
@@ -0,0 +1,75 @@
+2019-04-24 04:15:48.405 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-0C-00-00 | 20-00-CC-0A 00-00-00-02-40-00-00-00-00-00
+2019-04-24 04:15:48.410 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-0C-00-00 | 20-00-CD-0A 00-00-00-07-40-00-00-00-00-00
+2019-04-24 04:15:48.463 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-0C-00-00 | 20-00-CE-0A 00-00-00-0E-40-00-00-00-00-00
+2019-04-24 04:15:48.466 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-0C-00-00 | 20-00-CF-0A 00-00-00-10-40-00-00-00-00-00
+2019-04-24 04:15:48.469 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-0C-00-00 | 20-00-D0-0A 00-00-00-21-40-00-00-00-00-00
+2019-04-24 04:15:48.472 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-0D-00-00 | 20-00-D1-0A 00-00-00-28-40-00-00-00-00-00
+2019-04-24 04:15:48.475 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-0D-00-00 | 20-00-D2-0A 00-00-00-2B-40-00-00-00-00-00
+2019-04-24 04:15:48.528 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-0D-00-00 | 20-00-D3-0A 00-00-00-30-40-00-00-00-00-00
+2019-04-24 04:15:48.531 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-0D-00-00 | 20-00-D4-0A 00-00-00-32-40-00-00-00-00-00
+2019-04-24 04:15:48.534 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-0D-00-00 | 20-00-D5-0A 00-00-00-37-40-00-00-00-00-00
+2019-04-24 04:15:48.537 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-0D-00-00 | 20-00-D6-0A 00-00-00-4A-40-00-00-00-00-00
+2019-04-24 04:15:48.540 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-0D-00-00 | 20-00-D7-0A 00-00-00-54-40-00-00-00-00-00
+2019-04-24 04:15:48.543 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-0D-00-00 | 20-00-D8-0A 00-00-00-5B-40-00-00-00-00-00
+2019-04-24 04:15:48.546 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-0D-00-00 | 20-00-D9-0A 00-00-00-6C-40-00-00-00-00-00
+2019-04-24 04:15:48.598 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-0D-00-00 | 20-00-DA-0A 00-00-00-75-40-00-00-00-00-00
+2019-04-24 04:15:48.601 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-0D-00-00 | 20-00-DB-0A 00-00-00-7F-40-00-00-00-00-00
+2019-04-24 04:15:48.604 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-0D-00-00 | 20-00-DC-0A 00-00-00-92-40-00-00-00-00-00
+2019-04-24 04:15:48.607 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-0D-00-00 | 20-00-DD-0A 00-00-00-9C-40-00-00-00-00-00
+2019-04-24 04:15:48.610 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-0D-00-00 | 20-00-DE-0A 00-00-00-A5-40-00-00-00-00-00
+2019-04-24 04:15:48.613 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-0D-00-00 | 20-00-DF-0A 00-00-00-C0-40-00-00-00-00-00
+2019-04-24 04:15:48.668 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-0D-00-00 | 20-00-E0-0A 00-00-00-CA-40-00-00-00-00-00
+2019-04-24 04:15:48.671 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-0E-00-00 | 20-00-E1-0A 00-00-00-EB-40-00-00-00-00-00
+2019-04-24 04:15:48.675 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-0E-00-00 | 20-00-E2-0A 00-00-00-FF-40-00-00-00-00-00
+2019-04-24 04:15:49.082 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-0E-00-00 | 20-00-E3-0A 00-00-00-F6-40-00-00-00-00-00
+2019-04-24 04:15:49.086 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-0E-00-00 | 20-00-E4-0A 00-00-00-F1-40-00-00-00-00-00
+2019-04-24 04:15:49.089 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-0E-00-00 | 20-00-E5-0A 00-00-00-DF-40-00-00-00-00-00
+2019-04-24 04:15:49.143 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-0E-00-00 | 20-00-E6-0A 00-00-00-D2-40-00-00-00-00-00
+2019-04-24 04:15:49.146 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-0E-00-00 | 20-00-E7-0A 00-00-00-C7-40-00-00-00-00-00
+2019-04-24 04:15:49.149 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-0E-00-00 | 20-00-E8-0A 00-00-00-BF-40-00-00-00-00-00
+2019-04-24 04:15:49.153 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-0E-00-00 | 20-00-E9-0A 00-00-00-A3-40-00-00-00-00-00
+2019-04-24 04:15:49.156 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-0E-00-00 | 20-00-EA-0A 00-00-00-9A-40-00-00-00-00-00
+2019-04-24 04:15:49.159 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-0E-00-00 | 20-00-EB-0A 00-00-00-89-40-00-00-00-00-00
+2019-04-24 04:15:49.162 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-0E-00-00 | 20-00-EC-0A 00-00-00-6B-40-00-00-00-00-00
+2019-04-24 04:15:49.215 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-0E-00-00 | 20-00-ED-0A 00-00-00-58-40-00-00-00-00-00
+2019-04-24 04:15:49.219 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-0E-00-00 | 20-00-EE-0A 00-00-00-4E-40-00-00-00-00-00
+2019-04-24 04:15:49.222 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-0E-00-00 | 20-00-EF-0A 00-00-00-39-40-00-00-00-00-00
+2019-04-24 04:15:49.226 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-0E-00-00 | 20-00-F0-0A 00-00-00-26-40-00-00-00-00-00
+2019-04-24 04:15:49.229 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-0F-00-00 | 20-00-F1-0A 00-00-00-13-40-00-00-00-00-00
+2019-04-24 04:15:49.232 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-0F-00-00 | 20-00-F2-0A 00-00-00-01-40-00-00-00-00-00
+2019-04-24 04:15:49.236 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-0F-00-00 | 20-00-F3-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:15:49.742 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-0F-00-00 | 20-00-F4-0A 00-00-00-01-40-00-00-00-00-00
+2019-04-24 04:15:49.746 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-0F-00-00 | 20-00-F5-0A 00-00-00-08-40-00-00-00-00-00
+2019-04-24 04:15:49.749 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-0F-00-00 | 20-00-F6-0A 00-00-00-15-40-00-00-00-00-00
+2019-04-24 04:15:49.753 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-0F-00-00 | 20-00-F7-0A 00-00-00-1D-40-00-00-00-00-00
+2019-04-24 04:15:49.806 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-0F-00-00 | 20-00-F8-0A 00-00-00-2B-40-00-00-00-00-00
+2019-04-24 04:15:49.809 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-0F-00-00 | 20-00-F9-0A 00-00-00-39-40-00-00-00-00-00
+2019-04-24 04:15:49.813 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-0F-00-00 | 20-00-FA-0A 00-00-00-41-40-00-00-00-00-00
+2019-04-24 04:15:49.816 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-0F-00-00 | 20-00-FB-0A 00-00-00-4F-40-00-00-00-00-00
+2019-04-24 04:15:49.820 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-0F-00-00 | 20-00-FC-0A 00-00-00-57-40-00-00-00-00-00
+2019-04-24 04:15:49.824 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-0F-00-00 | 20-00-FD-0A 00-00-00-65-40-00-00-00-00-00
+2019-04-24 04:15:49.879 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-0F-00-00 | 20-00-FE-0A 00-00-00-6B-40-00-00-00-00-00
+2019-04-24 04:15:49.883 [40] -> 88-19-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-0F-00-00 | 20-00-FF-0A 00-00-00-7E-40-00-00-00-00-00
+2019-04-24 04:15:49.886 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-0F-00-00 | 20-00-00-0A 00-00-00-8E-40-00-00-00-00-00
+2019-04-24 04:15:49.890 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-10-00-00 | 20-00-01-0A 00-00-00-9C-40-00-00-00-00-00
+2019-04-24 04:15:49.894 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-10-00-00 | 20-00-02-0A 00-00-00-A3-40-00-00-00-00-00
+2019-04-24 04:15:49.897 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-10-00-00 | 20-00-03-0A 00-00-00-B5-40-00-00-00-00-00
+2019-04-24 04:15:49.901 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-10-00-00 | 20-00-04-0A 00-00-00-C4-40-00-00-00-00-00
+2019-04-24 04:15:49.955 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-10-00-00 | 20-00-05-0A 00-00-00-CE-40-00-00-00-00-00
+2019-04-24 04:15:49.959 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-10-00-00 | 20-00-06-0A 00-00-00-DC-40-00-00-00-00-00
+2019-04-24 04:15:49.962 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-10-00-00 | 20-00-07-0A 00-00-00-EE-40-00-00-00-00-00
+2019-04-24 04:15:49.966 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-10-00-00 | 20-00-08-0A 00-00-00-F3-40-00-00-00-00-00
+2019-04-24 04:15:49.969 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 80-10-00-00 | 20-00-09-0A 00-00-00-FF-40-00-00-00-00-00
+2019-04-24 04:15:50.174 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-10-00-00 | 20-00-0A-0A 00-00-00-F6-40-00-00-00-00-00
+2019-04-24 04:15:50.178 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-10-00-00 | 20-00-0B-0A 00-00-00-EB-40-00-00-00-00-00
+2019-04-24 04:15:50.181 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-10-00-00 | 20-00-0C-0A 00-00-00-DC-40-00-00-00-00-00
+2019-04-24 04:15:50.185 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-10-00-00 | 20-00-0D-0A 00-00-00-C9-40-00-00-00-00-00
+2019-04-24 04:15:50.238 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-10-00-00 | 20-00-0E-0A 00-00-00-C2-40-00-00-00-00-00
+2019-04-24 04:15:50.242 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-10-00-00 | 20-00-0F-0A 00-00-00-A4-40-00-00-00-00-00
+2019-04-24 04:15:50.245 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-10-00-00 | 20-00-10-0A 00-00-00-99-40-00-00-00-00-00
+2019-04-24 04:15:50.249 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-11-00-00 | 20-00-11-0A 00-00-00-7D-40-00-00-00-00-00
+2019-04-24 04:15:50.253 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-11-00-00 | 20-00-12-0A 00-00-00-61-40-00-00-00-00-00
+2019-04-24 04:15:50.256 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-11-00-00 | 20-00-13-0A 00-00-00-44-40-00-00-00-00-00
+2019-04-24 04:15:50.310 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-11-00-00 | 20-00-14-0A 00-00-00-2E-40-00-00-00-00-00
+2019-04-24 04:15:50.314 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-11-00-00 | 20-00-15-0A 00-00-00-15-40-00-00-00-00-00
+2019-04-24 04:15:50.318 [40] -> 88-11-A0-00-62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-11-00-00 | 20-00-16-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/xbox_button.log b/Docs/Packet Logs/GuitarSniffer/xbox_button.log
new file mode 100644
index 0000000..52072e5
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/xbox_button.log
@@ -0,0 +1,6 @@
+2019-04-24 04:18:23.729 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-03-01-00 | 07-20-01-02 01-5B
+2019-04-24 04:18:23.885 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-03-01-00 | 07-20-02-02 00-5B
+2019-04-24 04:18:24.592 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-04-01-00 | 07-20-03-02 01-5B
+2019-04-24 04:18:24.939 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-04-01-00 | 07-20-04-02 00-5B
+2019-04-24 04:18:25.395 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-04-01-00 | 07-20-05-02 01-5B
+2019-04-24 04:18:25.499 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-04-01-00 | 07-20-06-02 00-5B
diff --git a/Docs/Packet Logs/GuitarSniffer/yellow_lower.log b/Docs/Packet Logs/GuitarSniffer/yellow_lower.log
new file mode 100644
index 0000000..0f95858
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/yellow_lower.log
@@ -0,0 +1,14 @@
+2019-04-24 04:14:07.822 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-05-00-00 | 20-00-5B-0A 80-40-00-00-40-00-04-00-00-00
+2019-04-24 04:14:07.928 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-05-00-00 | 20-00-5C-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:07.981 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-05-00-00 | 20-00-5D-0A 80-40-00-00-40-00-04-00-00-00
+2019-04-24 04:14:08.035 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-05-00-00 | 20-00-5E-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:08.139 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-05-00-00 | 20-00-5F-0A 80-40-00-00-40-00-04-00-00-00
+2019-04-24 04:14:08.193 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-05-00-00 | 20-00-60-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:08.297 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-06-00-00 | 20-00-61-0A 80-40-00-00-40-00-04-00-00-00
+2019-04-24 04:14:08.403 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-06-00-00 | 20-00-62-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:08.456 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-06-00-00 | 20-00-63-0A 80-40-00-00-40-00-04-00-00-00
+2019-04-24 04:14:08.560 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 30-06-00-00 | 20-00-64-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:08.614 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 40-06-00-00 | 20-00-65-0A 80-40-00-00-40-00-04-00-00-00
+2019-04-24 04:14:08.719 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 50-06-00-00 | 20-00-66-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:14:08.824 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 60-06-00-00 | 20-00-67-0A 80-40-00-00-40-00-04-00-00-00
+2019-04-24 04:14:08.877 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 70-06-00-00 | 20-00-68-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/GuitarSniffer/yellow_upper.log b/Docs/Packet Logs/GuitarSniffer/yellow_upper.log
new file mode 100644
index 0000000..c88ee69
--- /dev/null
+++ b/Docs/Packet Logs/GuitarSniffer/yellow_upper.log
@@ -0,0 +1,10 @@
+2019-04-24 04:13:12.600 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 90-01-00-00 | 20-00-1A-0A 80-00-00-00-40-04-00-00-00-00
+2019-04-24 04:13:12.757 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C A0-01-00-00 | 20-00-1B-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:12.912 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C B0-01-00-00 | 20-00-1C-0A 80-00-00-00-40-04-00-00-00-00
+2019-04-24 04:13:13.066 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C C0-01-00-00 | 20-00-1D-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:13.271 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C D0-01-00-00 | 20-00-1E-0A 80-00-00-00-40-04-00-00-00-00
+2019-04-24 04:13:13.374 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C E0-01-00-00 | 20-00-1F-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:13.527 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C F0-01-00-00 | 20-00-20-0A 80-00-00-00-40-04-00-00-00-00
+2019-04-24 04:13:13.630 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 00-02-00-00 | 20-00-21-0A 00-00-00-00-40-00-00-00-00-00
+2019-04-24 04:13:13.733 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 10-02-00-00 | 20-00-22-0A 80-00-00-00-40-04-00-00-00-00
+2019-04-24 04:13:13.836 [40] -> 88-11-A0-00 62-45-B4-F0-85-2C 7E-ED-8F-FF-73-00 62-45-B4-F0-85-2C 20-02-00-00 | 20-00-23-0A 00-00-00-00-40-00-00-00-00-00
diff --git a/Docs/Packet Logs/drums_inputs.log b/Docs/Packet Logs/drums_inputs.log
new file mode 100644
index 0000000..92c164c
--- /dev/null
+++ b/Docs/Packet Logs/drums_inputs.log
@@ -0,0 +1,8 @@
+2021-10-31 02:25:31.725 [36] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FF-CF-6B 62-45-B4-E9-D1-8A A0-02-00-00 | 20-00-2B-06 00-00-04-00-00-00
+2021-10-31 02:25:31.773 [36] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FF-CF-6B 62-45-B4-E9-D1-8A B0-02-00-00 | 20-00-2C-06 00-00-00-00-00-00
+2021-10-31 02:25:32.038 [36] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FF-CF-6B 62-45-B4-E9-D1-8A C0-02-00-00 | 20-00-2D-06 20-00-40-00-00-00
+2021-10-31 02:25:32.086 [36] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FF-CF-6B 62-45-B4-E9-D1-8A D0-02-00-00 | 20-00-2E-06 00-00-00-00-00-00
+2021-10-31 02:25:32.327 [36] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FF-CF-6B 62-45-B4-E9-D1-8A E0-02-00-00 | 20-00-2F-06 00-00-04-00-00-00
+2021-10-31 02:25:32.367 [36] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FF-CF-6B 62-45-B4-E9-D1-8A F0-02-00-00 | 20-00-30-06 00-00-00-00-00-00
+2021-10-31 02:25:32.608 [36] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FF-CF-6B 62-45-B4-E9-D1-8A 00-03-00-00 | 20-00-31-06 00-00-00-40-00-00
+2021-10-31 02:25:32.656 [36] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FF-CF-6B 62-45-B4-E9-D1-8A 10-03-00-00 | 20-00-32-06 00-00-00-00-00-00
\ No newline at end of file
diff --git a/Docs/Packet Logs/drums_power_on.log b/Docs/Packet Logs/drums_power_on.log
new file mode 100644
index 0000000..b808754
--- /dev/null
+++ b/Docs/Packet Logs/drums_power_on.log
@@ -0,0 +1,102 @@
+2022-11-02 11:52:31.923 [58] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 00-00-01-00 | 02-20-01-1C 62-BC-CE-85-ED-7E-00-00-6F-0E-71-01-01-00-00-00-07-00-1E-00-00-02-01-00-01-00-01-00
+2022-11-02 11:52:31.923 [58] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 00-00-01-00 | 02-20-01-1C 62-BC-CE-85-ED-7E-00-00-6F-0E-71-01-01-00-00-00-07-00-1E-00-00-02-01-00-01-00-01-00
+2022-11-02 11:52:31.980 [90] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 10-00-01-00 | 04-F0-03-3A C3-01-10-00-01-00-00-00-00-00-00-00-00-00-00-00-C3-00-9B-00-16-00-1B-00-1C-00-23-00-29-00-6A-00-00-00-00-00-00-00-00-00-01-01-00-00-00-00-06-01-02-03-04-06-07-05-01-04-05-06-0A-02
+2022-11-02 11:52:31.980 [90] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 10-00-01-00 | 04-F0-03-3A C3-01-10-00-01-00-00-00-00-00-00-00-00-00-00-00-C3-00-9B-00-16-00-1B-00-1C-00-23-00-29-00-6A-00-00-00-00-00-00-00-00-00-01-01-00-00-00-00-06-01-02-03-04-06-07-05-01-04-05-06-0A-02
+2022-11-02 11:52:31.996 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 20-00-01-00 | 04-A0-63-BA-00 3A 15-00-50-44-50-2E-58-62-6F-78-2E-44-72-75-6D-73-2E-54-61-62-6C-61-68-27-00-57-69-6E-64-6F-77-73-2E-58-62-6F-78-2E-49-6E-70-75-74-2E-4E-61-76-69-67-61-74-69-6F-6E-43-6F-6E-74
+2022-11-02 11:52:31.996 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 20-00-01-00 | 04-A0-63-BA-00 3A 15-00-50-44-50-2E-58-62-6F-78-2E-44-72-75-6D-73-2E-54-61-62-6C-61-68-27-00-57-69-6E-64-6F-77-73-2E-58-62-6F-78-2E-49-6E-70-75-74-2E-4E-61-76-69-67-61-74-69-6F-6E-43-6F-6E-74
+2022-11-02 11:52:31.996 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 00-00-04-00 | 04-A0-63-BA-00 74 72-6F-6C-6C-65-72-03-B0-F9-03-A5-5E-95-C4-47-A2-ED-B1-33-6F-A7-70-3E-E7-1F-F3-B8-86-73-E9-40-A9-F8-2F-21-26-3A-CF-B7-56-FF-76-97-FD-9B-81-45-AD-45-B6-45-BB-A5-26-D6-01-17-00
+2022-11-02 11:52:31.996 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 00-00-04-00 | 04-A0-63-BA-00 74 72-6F-6C-6C-65-72-03-B0-F9-03-A5-5E-95-C4-47-A2-ED-B1-33-6F-A7-70-3E-E7-1F-F3-B8-86-73-E9-40-A9-F8-2F-21-26-3A-CF-B7-56-FF-76-97-FD-9B-81-45-AD-45-B6-45-BB-A5-26-D6-01-17-00
+2022-11-02 11:52:32.004 [53] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 30-00-01-00 | 04-B0-63-15 AE-01 20-0A-00-01-00-14-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+2022-11-02 11:52:32.004 [53] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 30-00-01-00 | 04-B0-63-15 AE-01 20-0A-00-01-00-14-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+2022-11-02 11:52:32.028 [32] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 40-00-01-00 | 04-A0-64-00 C3-01
+2022-11-02 11:52:32.028 [32] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 40-00-01-00 | 04-A0-64-00 C3-01
+2022-11-02 11:52:32.080 [36] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 00-00-00-00 | 20-00-08-06 00-00-00-00-00-00
+2022-11-02 11:52:32.080 [36] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 00-00-00-00 | 20-00-08-06 00-00-00-00-00-00
+2022-11-02 11:52:32.088 [34] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 50-00-01-00 | 03-20-09-04 80-01-00-00
+2022-11-02 11:52:32.088 [34] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 50-00-01-00 | 03-20-09-04 80-01-00-00
+2022-11-02 11:52:32.160 [39] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 60-00-01-00 | 01-20-0A-09 00-06-30-3A-00-00-00-00-00
+2022-11-02 11:52:32.160 [39] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 60-00-01-00 | 01-20-0A-09 00-06-30-3A-00-00-00-00-00
+2022-11-02 11:52:32.160 [36] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 10-00-04-00 | 06-30-0B-06 00-C1-00-01-00-00
+2022-11-02 11:52:32.160 [36] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 10-00-04-00 | 06-30-0B-06 00-C1-00-01-00-00
+2022-11-02 11:52:32.282 [39] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 70-00-01-00 | 01-20-0D-09 00-06-30-0E-00-00-00-00-00
+2022-11-02 11:52:32.282 [39] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 70-00-01-00 | 01-20-0D-09 00-06-30-0E-00-00-00-00-00
+2022-11-02 11:52:32.282 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 20-00-04-00 | 06-F0-0E-BA-00 5A 00-C2-00-02-00-54-02-01-00-50-0E-CA-DE-78-45-F4-FF-EC-FB-16-3B-C6-23-2A-2F-20-6C-7F-C4-54-82-2F-68-EA-0A-6F-B2-9F-A1-C5-1C-B4-31-26-E1-51-00-00-03-E9-00-41-02-05-B8-0D-00-0D
+2022-11-02 11:52:32.282 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 20-00-04-00 | 06-F0-0E-BA-00 5A 00-C2-00-02-00-54-02-01-00-50-0E-CA-DE-78-45-F4-FF-EC-FB-16-3B-C6-23-2A-2F-20-6C-7F-C4-54-82-2F-68-EA-0A-6F-B2-9F-A1-C5-1C-B4-31-26-E1-51-00-00-03-E9-00-41-02-05-B8-0D-00-0D
+2022-11-02 11:52:32.306 [64] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 80-00-01-00 | 06-B0-6B-20 BA-00 00-6F-00-13-DA-DE-CE-4B-03-00-02-80-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+2022-11-02 11:52:32.306 [64] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 80-00-01-00 | 06-B0-6B-20 BA-00 00-6F-00-13-DA-DE-CE-4B-03-00-02-80-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
+2022-11-02 11:52:32.330 [32] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 90-00-01-00 | 06-A0-6C-00 DA-00
+2022-11-02 11:52:32.330 [32] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 90-00-01-00 | 06-A0-6C-00 DA-00
+2022-11-02 11:52:32.539 [39] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 A0-00-01-00 | 01-20-11-09 00-06-30-0E-00-00-00-00-00
+2022-11-02 11:52:32.539 [39] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 A0-00-01-00 | 01-20-11-09 00-06-30-0E-00-00-00-00-00
+2022-11-02 11:52:32.548 [90] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 B0-00-01-00 | 06-F0-12-3A B9-06-00-C2-00-03-03-33-03-01-03-2F-30-82-03-2B-30-82-02-13-A0-03-02-01-02-02-04-7A-5D-35-D5-30-0D-06-09-2A-86-48-86-F7-0D-01-01-0B-05-00-30-76-31-0B-30-09-06-03-55-04-06-13-02-44
+2022-11-02 11:52:32.548 [90] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 B0-00-01-00 | 06-F0-12-3A B9-06-00-C2-00-03-03-33-03-01-03-2F-30-82-03-2B-30-82-02-13-A0-03-02-01-02-02-04-7A-5D-35-D5-30-0D-06-09-2A-86-48-86-F7-0D-01-01-0B-05-00-30-76-31-0B-30-09-06-03-55-04-06-13-02-44
+2022-11-02 11:52:32.564 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 C0-00-01-00 | 06-A0-6F-BA-00 3A 45-31-0F-30-0D-06-03-55-04-08-13-06-53-61-78-6F-6E-79-31-16-30-14-06-03-55-04-0A-13-0D-53-75-62-63-6C-61-73-73-20-30-30-30-32-31-11-30-0F-06-03-55-04-0B-13-08-43-6C-61-73-73
+2022-11-02 11:52:32.564 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 C0-00-01-00 | 06-A0-6F-BA-00 3A 45-31-0F-30-0D-06-03-55-04-08-13-06-53-61-78-6F-6E-79-31-16-30-14-06-03-55-04-0A-13-0D-53-75-62-63-6C-61-73-73-20-30-30-30-32-31-11-30-0F-06-03-55-04-0B-13-08-43-6C-61-73-73
+2022-11-02 11:52:32.564 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 30-00-04-00 | 06-A0-6F-BA-00 74 20-30-33-31-2B-30-29-06-03-55-04-03-13-22-58-62-6F-78-20-41-63-63-65-73-73-6F-72-69-65-73-20-43-6C-61-73-73-20-50-72-6F-64-20-43-41-20-30-30-35-30-1E-17-0D-31-36-30-33-32-34
+2022-11-02 11:52:32.564 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 30-00-04-00 | 06-A0-6F-BA-00 74 20-30-33-31-2B-30-29-06-03-55-04-03-13-22-58-62-6F-78-20-41-63-63-65-73-73-6F-72-69-65-73-20-43-6C-61-73-73-20-50-72-6F-64-20-43-41-20-30-30-35-30-1E-17-0D-31-36-30-33-32-34
+2022-11-02 11:52:32.564 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 40-00-04-00 | 06-A0-6F-3A AE-01 31-32-32-30-34-34-5A-17-0D-34-34-31-30-31-35-32-33-35-39-35-39-5A-30-00-30-82-01-22-30-0D-06-09-2A-86-48-86-F7-0D-01-01-01-05-00-03-82-01-0F-00-30-82-01-0A-02-82-01-01-00-98
+2022-11-02 11:52:32.564 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 40-00-04-00 | 06-A0-6F-3A AE-01 31-32-32-30-34-34-5A-17-0D-34-34-31-30-31-35-32-33-35-39-35-39-5A-30-00-30-82-01-22-30-0D-06-09-2A-86-48-86-F7-0D-01-01-01-05-00-03-82-01-0F-00-30-82-01-0A-02-82-01-01-00-98
+2022-11-02 11:52:32.572 [90] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 D0-00-01-00 | 06-B0-6F-3A E8-01 14-43-DD-A4-F5-29-BF-E0-7F-08-DD-CF-26-72-F0-F2-DC-97-2C-A7-19-95-47-F7-75-F8-FD-9F-67-17-FD-58-82-5D-0C-32-3B-B3-6D-EF-D1-CB-86-04-78-38-7D-C6-2E-6E-BD-B9-BF-53-17-48-82-98
+2022-11-02 11:52:32.572 [90] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 D0-00-01-00 | 06-B0-6F-3A E8-01 14-43-DD-A4-F5-29-BF-E0-7F-08-DD-CF-26-72-F0-F2-DC-97-2C-A7-19-95-47-F7-75-F8-FD-9F-67-17-FD-58-82-5D-0C-32-3B-B3-6D-EF-D1-CB-86-04-78-38-7D-C6-2E-6E-BD-B9-BF-53-17-48-82-98
+2022-11-02 11:52:32.588 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 E0-00-01-00 | 06-A0-70-3A A2-02 52-1E-82-06-A7-AA-38-AE-36-84-BB-83-8D-D9-36-8F-A7-01-11-D2-04-A7-18-3E-1F-DD-D3-39-DD-E4-9D-00-AA-1A-79-64-16-2B-2E-EC-F0-F7-D7-09-44-D2-47-C7-2A-42-59-B7-71-FB-9B-26-A5-4E
+2022-11-02 11:52:32.588 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 E0-00-01-00 | 06-A0-70-3A A2-02 52-1E-82-06-A7-AA-38-AE-36-84-BB-83-8D-D9-36-8F-A7-01-11-D2-04-A7-18-3E-1F-DD-D3-39-DD-E4-9D-00-AA-1A-79-64-16-2B-2E-EC-F0-F7-D7-09-44-D2-47-C7-2A-42-59-B7-71-FB-9B-26-A5-4E
+2022-11-02 11:52:32.588 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 50-00-04-00 | 06-A0-70-3A DC-02 39-43-D1-EA-8A-16-88-C5-23-D2-19-EA-12-EA-AB-4F-03-02-1E-FF-7D-CD-3A-EE-0B-81-F1-81-15-0F-83-E5-BC-F1-83-A2-46-43-F5-04-AE-A4-5D-3A-40-71-04-0D-2F-4A-4F-52-E5-0A-77-27-B0-29
+2022-11-02 11:52:32.588 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 50-00-04-00 | 06-A0-70-3A DC-02 39-43-D1-EA-8A-16-88-C5-23-D2-19-EA-12-EA-AB-4F-03-02-1E-FF-7D-CD-3A-EE-0B-81-F1-81-15-0F-83-E5-BC-F1-83-A2-46-43-F5-04-AE-A4-5D-3A-40-71-04-0D-2F-4A-4F-52-E5-0A-77-27-B0-29
+2022-11-02 11:52:32.588 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 60-00-04-00 | 06-A0-70-3A 96-03 6A-67-DB-C2-17-9E-69-6D-55-DD-81-86-4E-DC-2F-B7-69-BA-FA-A3-B0-49-60-09-62-46-B7-A2-49-A4-53-1E-25-08-1F-E3-46-3F-4B-09-7E-4C-09-9E-A2-F7-38-D8-C8-8B-04-17-74-31-52-CD-FB-CD
+2022-11-02 11:52:32.588 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 60-00-04-00 | 06-A0-70-3A 96-03 6A-67-DB-C2-17-9E-69-6D-55-DD-81-86-4E-DC-2F-B7-69-BA-FA-A3-B0-49-60-09-62-46-B7-A2-49-A4-53-1E-25-08-1F-E3-46-3F-4B-09-7E-4C-09-9E-A2-F7-38-D8-C8-8B-04-17-74-31-52-CD-FB-CD
+2022-11-02 11:52:32.588 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 70-00-04-00 | 06-A0-70-3A D0-03 5C-2B-80-D5-CC-87-94-55-71-DF-38-23-81-3A-41-A9-E5-6C-28-02-4D-02-7F-02-03-01-00-01-A3-37-30-35-30-0E-06-03-55-1D-0F-01-01-FF-04-04-03-02-00-B0-30-0C-06-03-55-1D-13-01-01-FF
+2022-11-02 11:52:32.588 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 70-00-04-00 | 06-A0-70-3A D0-03 5C-2B-80-D5-CC-87-94-55-71-DF-38-23-81-3A-41-A9-E5-6C-28-02-4D-02-7F-02-03-01-00-01-A3-37-30-35-30-0E-06-03-55-1D-0F-01-01-FF-04-04-03-02-00-B0-30-0C-06-03-55-1D-13-01-01-FF
+2022-11-02 11:52:32.597 [90] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 F0-00-01-00 | 06-B0-70-3A 8A-04 04-02-30-00-30-15-06-03-55-1D-25-04-0E-30-0C-06-0A-2B-06-01-04-01-82-37-78-03-01-30-0D-06-09-2A-86-48-86-F7-0D-01-01-0B-05-00-03-82-01-01-00-01-9F-0C-94-8A-28-86-FB-0B-5F-D5
+2022-11-02 11:52:32.597 [90] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 F0-00-01-00 | 06-B0-70-3A 8A-04 04-02-30-00-30-15-06-03-55-1D-25-04-0E-30-0C-06-0A-2B-06-01-04-01-82-37-78-03-01-30-0D-06-09-2A-86-48-86-F7-0D-01-01-0B-05-00-03-82-01-01-00-01-9F-0C-94-8A-28-86-FB-0B-5F-D5
+2022-11-02 11:52:32.605 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 00-01-01-00 | 06-A0-01-3A FE-04 EA-76-FD-2B-12-46-6A-B5-3C-3C-8C-16-A6-25-4E-59-6D-C4-23-A6-8A-1D-5B-FB-89-84-90-C3-99-EB-9A-4A-F1-1D-81-D3-A1-FA-9C-51-E0-59-A5-E8-8E-4B-6C-ED-12-E9-11-61-72-6D-9F-70-0C-37
+2022-11-02 11:52:32.605 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 00-01-01-00 | 06-A0-01-3A FE-04 EA-76-FD-2B-12-46-6A-B5-3C-3C-8C-16-A6-25-4E-59-6D-C4-23-A6-8A-1D-5B-FB-89-84-90-C3-99-EB-9A-4A-F1-1D-81-D3-A1-FA-9C-51-E0-59-A5-E8-8E-4B-6C-ED-12-E9-11-61-72-6D-9F-70-0C-37
+2022-11-02 11:52:32.606 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 80-00-04-00 | 06-A0-01-3A B8-05 BE-5A-3B-11-6C-67-6A-B9-38-73-20-DD-A8-BC-19-F6-BE-AE-6B-FD-28-97-1F-90-2D-80-FF-7E-62-CF-18-A5-56-A9-BE-87-DC-CB-3C-11-5E-60-DC-EC-2C-87-7A-81-58-FF-4B-DE-9D-65-05-93-23-80
+2022-11-02 11:52:32.606 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 80-00-04-00 | 06-A0-01-3A B8-05 BE-5A-3B-11-6C-67-6A-B9-38-73-20-DD-A8-BC-19-F6-BE-AE-6B-FD-28-97-1F-90-2D-80-FF-7E-62-CF-18-A5-56-A9-BE-87-DC-CB-3C-11-5E-60-DC-EC-2C-87-7A-81-58-FF-4B-DE-9D-65-05-93-23-80
+2022-11-02 11:52:32.606 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 90-00-04-00 | 06-A0-01-3A F2-05 4B-00-AE-38-01-9E-93-82-B5-9F-99-BE-77-10-2B-EF-B5-81-BA-FE-E9-7F-97-DF-79-97-20-D5-FB-63-CF-CE-A2-85-86-36-9C-29-D1-F4-F0-A3-97-90-7A-B8-B4-C2-58-17-40-43-1C-BF-E4-B6-0B-AE
+2022-11-02 11:52:32.606 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 90-00-04-00 | 06-A0-01-3A F2-05 4B-00-AE-38-01-9E-93-82-B5-9F-99-BE-77-10-2B-EF-B5-81-BA-FE-E9-7F-97-DF-79-97-20-D5-FB-63-CF-CE-A2-85-86-36-9C-29-D1-F4-F0-A3-97-90-7A-B8-B4-C2-58-17-40-43-1C-BF-E4-B6-0B-AE
+2022-11-02 11:52:32.606 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 A0-00-04-00 | 06-B0-70-3A 8A-04 04-02-30-00-30-15-06-03-55-1D-25-04-0E-30-0C-06-0A-2B-06-01-04-01-82-37-78-03-01-30-0D-06-09-2A-86-48-86-F7-0D-01-01-0B-05-00-03-82-01-01-00-01-9F-0C-94-8A-28-86-FB-0B-5F-D5
+2022-11-02 11:52:32.606 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 A0-00-04-00 | 06-B0-70-3A 8A-04 04-02-30-00-30-15-06-03-55-1D-25-04-0E-30-0C-06-0A-2B-06-01-04-01-82-37-78-03-01-30-0D-06-09-2A-86-48-86-F7-0D-01-01-0B-05-00-03-82-01-01-00-01-9F-0C-94-8A-28-86-FB-0B-5F-D5
+2022-11-02 11:52:32.614 [45] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 10-01-01-00 | 06-B0-01-0D AC-06 A9-9D-C6-B1-E4-90-B2-A9-52-44-F5-1F-8D
+2022-11-02 11:52:32.614 [45] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 10-01-01-00 | 06-B0-01-0D AC-06 A9-9D-C6-B1-E4-90-B2-A9-52-44-F5-1F-8D
+2022-11-02 11:52:32.622 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 20-01-01-00 | 06-A0-03-3A C4-04 9F-FC-C5-F7-35-3F-71-BF-E9-D0-56-E9-D8-4E-99-F1-F9-C2-6B-3B-FA-8D-ED-10-35-5F-A6-BB-67-51-80-E6-85-81-C6-95-DE-35-E2-6D-BA-06-2F-62-24-CE-5E-66-56-16-40-44-EB-24-A0-31-EA-6C
+2022-11-02 11:52:32.622 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 20-01-01-00 | 06-A0-03-3A C4-04 9F-FC-C5-F7-35-3F-71-BF-E9-D0-56-E9-D8-4E-99-F1-F9-C2-6B-3B-FA-8D-ED-10-35-5F-A6-BB-67-51-80-E6-85-81-C6-95-DE-35-E2-6D-BA-06-2F-62-24-CE-5E-66-56-16-40-44-EB-24-A0-31-EA-6C
+2022-11-02 11:52:32.622 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 B0-00-04-00 | 06-A0-03-3A FE-04 EA-76-FD-2B-12-46-6A-B5-3C-3C-8C-16-A6-25-4E-59-6D-C4-23-A6-8A-1D-5B-FB-89-84-90-C3-99-EB-9A-4A-F1-1D-81-D3-A1-FA-9C-51-E0-59-A5-E8-8E-4B-6C-ED-12-E9-11-61-72-6D-9F-70-0C-37
+2022-11-02 11:52:32.622 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 B0-00-04-00 | 06-A0-03-3A FE-04 EA-76-FD-2B-12-46-6A-B5-3C-3C-8C-16-A6-25-4E-59-6D-C4-23-A6-8A-1D-5B-FB-89-84-90-C3-99-EB-9A-4A-F1-1D-81-D3-A1-FA-9C-51-E0-59-A5-E8-8E-4B-6C-ED-12-E9-11-61-72-6D-9F-70-0C-37
+2022-11-02 11:52:32.622 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 C0-00-04-00 | 06-A0-03-3A B8-05 BE-5A-3B-11-6C-67-6A-B9-38-73-20-DD-A8-BC-19-F6-BE-AE-6B-FD-28-97-1F-90-2D-80-FF-7E-62-CF-18-A5-56-A9-BE-87-DC-CB-3C-11-5E-60-DC-EC-2C-87-7A-81-58-FF-4B-DE-9D-65-05-93-23-80
+2022-11-02 11:52:32.622 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 C0-00-04-00 | 06-A0-03-3A B8-05 BE-5A-3B-11-6C-67-6A-B9-38-73-20-DD-A8-BC-19-F6-BE-AE-6B-FD-28-97-1F-90-2D-80-FF-7E-62-CF-18-A5-56-A9-BE-87-DC-CB-3C-11-5E-60-DC-EC-2C-87-7A-81-58-FF-4B-DE-9D-65-05-93-23-80
+2022-11-02 11:52:32.622 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 D0-00-04-00 | 06-A0-03-3A F2-05 4B-00-AE-38-01-9E-93-82-B5-9F-99-BE-77-10-2B-EF-B5-81-BA-FE-E9-7F-97-DF-79-97-20-D5-FB-63-CF-CE-A2-85-86-36-9C-29-D1-F4-F0-A3-97-90-7A-B8-B4-C2-58-17-40-43-1C-BF-E4-B6-0B-AE
+2022-11-02 11:52:32.622 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 D0-00-04-00 | 06-A0-03-3A F2-05 4B-00-AE-38-01-9E-93-82-B5-9F-99-BE-77-10-2B-EF-B5-81-BA-FE-E9-7F-97-DF-79-97-20-D5-FB-63-CF-CE-A2-85-86-36-9C-29-D1-F4-F0-A3-97-90-7A-B8-B4-C2-58-17-40-43-1C-BF-E4-B6-0B-AE
+2022-11-02 11:52:32.630 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 30-01-01-00 | 06-A0-63-3A C4-04 9F-FC-C5-F7-35-3F-71-BF-E9-D0-56-E9-D8-4E-99-F1-F9-C2-6B-3B-FA-8D-ED-10-35-5F-A6-BB-67-51-80-E6-85-81-C6-95-DE-35-E2-6D-BA-06-2F-62-24-CE-5E-66-56-16-40-44-EB-24-A0-31-EA-6C
+2022-11-02 11:52:32.630 [90] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 30-01-01-00 | 06-A0-63-3A C4-04 9F-FC-C5-F7-35-3F-71-BF-E9-D0-56-E9-D8-4E-99-F1-F9-C2-6B-3B-FA-8D-ED-10-35-5F-A6-BB-67-51-80-E6-85-81-C6-95-DE-35-E2-6D-BA-06-2F-62-24-CE-5E-66-56-16-40-44-EB-24-A0-31-EA-6C
+2022-11-02 11:52:32.630 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 E0-00-04-00 | 06-A0-63-3A FE-04 EA-76-FD-2B-12-46-6A-B5-3C-3C-8C-16-A6-25-4E-59-6D-C4-23-A6-8A-1D-5B-FB-89-84-90-C3-99-EB-9A-4A-F1-1D-81-D3-A1-FA-9C-51-E0-59-A5-E8-8E-4B-6C-ED-12-E9-11-61-72-6D-9F-70-0C-37
+2022-11-02 11:52:32.630 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 E0-00-04-00 | 06-A0-63-3A FE-04 EA-76-FD-2B-12-46-6A-B5-3C-3C-8C-16-A6-25-4E-59-6D-C4-23-A6-8A-1D-5B-FB-89-84-90-C3-99-EB-9A-4A-F1-1D-81-D3-A1-FA-9C-51-E0-59-A5-E8-8E-4B-6C-ED-12-E9-11-61-72-6D-9F-70-0C-37
+2022-11-02 11:52:32.630 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 F0-00-04-00 | 06-A0-63-3A B8-05 BE-5A-3B-11-6C-67-6A-B9-38-73-20-DD-A8-BC-19-F6-BE-AE-6B-FD-28-97-1F-90-2D-80-FF-7E-62-CF-18-A5-56-A9-BE-87-DC-CB-3C-11-5E-60-DC-EC-2C-87-7A-81-58-FF-4B-DE-9D-65-05-93-23-80
+2022-11-02 11:52:32.630 [90] -> 88-31-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 F0-00-04-00 | 06-A0-63-3A B8-05 BE-5A-3B-11-6C-67-6A-B9-38-73-20-DD-A8-BC-19-F6-BE-AE-6B-FD-28-97-1F-90-2D-80-FF-7E-62-CF-18-A5-56-A9-BE-87-DC-CB-3C-11-5E-60-DC-EC-2C-87-7A-81-58-FF-4B-DE-9D-65-05-93-23-80
+2022-11-02 11:52:32.631 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 00-01-04-00 | 06-A0-63-3A F2-05 4B-00-AE-38-01-9E-93-82-B5-9F-99-BE-77-10-2B-EF-B5-81-BA-FE-E9-7F-97-DF-79-97-20-D5-FB-63-CF-CE-A2-85-86-36-9C-29-D1-F4-F0-A3-97-90-7A-B8-B4-C2-58-17-40-43-1C-BF-E4-B6-0B-AE
+2022-11-02 11:52:32.631 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 00-01-04-00 | 06-A0-63-3A F2-05 4B-00-AE-38-01-9E-93-82-B5-9F-99-BE-77-10-2B-EF-B5-81-BA-FE-E9-7F-97-DF-79-97-20-D5-FB-63-CF-CE-A2-85-86-36-9C-29-D1-F4-F0-A3-97-90-7A-B8-B4-C2-58-17-40-43-1C-BF-E4-B6-0B-AE
+2022-11-02 11:52:32.648 [45] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 40-01-01-00 | 06-B0-63-0D AC-06 A9-9D-C6-B1-E4-90-B2-A9-52-44-F5-1F-8D
+2022-11-02 11:52:32.648 [45] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 40-01-01-00 | 06-B0-63-0D AC-06 A9-9D-C6-B1-E4-90-B2-A9-52-44-F5-1F-8D
+2022-11-02 11:52:32.671 [32] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 50-01-01-00 | 06-A0-64-00 B9-06
+2022-11-02 11:52:32.671 [32] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 50-01-01-00 | 06-A0-64-00 B9-06
+2022-11-02 11:52:32.771 [39] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 60-01-01-00 | 01-20-2C-09 00-06-F0-3A-00-00-00-D8-00
+2022-11-02 11:52:32.771 [39] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 60-01-01-00 | 01-20-2C-09 00-06-F0-3A-00-00-00-D8-00
+2022-11-02 11:52:32.809 [39] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 70-01-01-00 | 01-20-2D-09 00-06-B0-12-01-00-00-00-00
+2022-11-02 11:52:32.809 [39] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 70-01-01-00 | 01-20-2D-09 00-06-B0-12-01-00-00-00-00
+2022-11-02 11:52:33.502 [36] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 80-01-01-00 | 06-30-2E-06 00-C1-00-01-00-00
+2022-11-02 11:52:33.502 [36] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 80-01-01-00 | 06-30-2E-06 00-C1-00-01-00-00
+2022-11-02 11:52:33.712 [39] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 90-01-01-00 | 01-20-30-09 00-06-30-32-00-00-00-00-00
+2022-11-02 11:52:33.712 [39] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 90-01-01-00 | 01-20-30-09 00-06-30-32-00-00-00-00-00
+2022-11-02 11:52:33.712 [36] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 10-01-04-00 | 06-30-31-06 00-C1-00-01-00-00
+2022-11-02 11:52:33.712 [36] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 10-01-04-00 | 06-30-31-06 00-C1-00-01-00-00
+2022-11-02 11:52:33.937 [39] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 A0-01-01-00 | 01-20-33-09 00-06-30-0E-00-00-00-00-00
+2022-11-02 11:52:33.937 [39] -> 88-31-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 A0-01-01-00 | 01-20-33-09 00-06-30-0E-00-00-00-00-00
+2022-11-02 11:52:33.937 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 20-01-04-00 | 06-F0-34-BA-00 4A 00-C2-00-08-00-44-08-01-00-40-84-77-73-55-1D-BE-20-25-51-36-A4-1C-8E-5C-41-3F-64-C2-B2-02-45-CF-17-4D-23-D3-EB-15-84-C6-D5-A0-36-93-69-B2-23-E4-7D-57-05-64-20-00-87-CC-30-4E
+2022-11-02 11:52:33.937 [90] -> 88-11-3C-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 60-45-BD-0F-8B-46 20-01-04-00 | 06-F0-34-BA-00 4A 00-C2-00-08-00-44-08-01-00-40-84-77-73-55-1D-BE-20-25-51-36-A4-1C-8E-5C-41-3F-64-C2-B2-02-45-CF-17-4D-23-D3-EB-15-84-C6-D5-A0-36-93-69-B2-23-E4-7D-57-05-64-20-00-87-CC-30-4E
+2022-11-02 11:52:33.961 [48] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 B0-01-01-00 | 06-B0-6C-10 BA-00 D8-C0-73-67-19-C5-3F-D2-A9-35-2F-DE-76-D4-4E-37
+2022-11-02 11:52:33.961 [48] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 B0-01-01-00 | 06-B0-6C-10 BA-00 D8-C0-73-67-19-C5-3F-D2-A9-35-2F-DE-76-D4-4E-37
+2022-11-02 11:52:33.985 [32] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 C0-01-01-00 | 06-A0-11-00 CA-00
+2022-11-02 11:52:33.985 [32] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 C0-01-01-00 | 06-A0-11-00 CA-00
+2022-11-02 11:52:39.008 [36] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 10-00-00-00 | 20-00-37-06 10-00-00-01-00-00
+2022-11-02 11:52:39.008 [36] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 10-00-00-00 | 20-00-37-06 10-00-00-01-00-00
+2022-11-02 11:52:39.040 [36] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 20-00-00-00 | 20-00-38-06 00-00-00-00-00-00
+2022-11-02 11:52:39.040 [36] -> 88-11-A0-00 62-45-BD-0F-8B-46 7E-ED-85-CE-BC-62 62-45-BD-0F-8B-46 20-00-00-00 | 20-00-38-06 00-00-00-00-00-00
diff --git a/Docs/Packet Logs/guitar_inputs.log b/Docs/Packet Logs/guitar_inputs.log
new file mode 100644
index 0000000..056361d
--- /dev/null
+++ b/Docs/Packet Logs/guitar_inputs.log
@@ -0,0 +1,16 @@
+2021-10-31 01:35:10.730 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FE-19-8A 62-45-B4-E9-D1-8A 20-55-00-00 | 20-00-53-0A 00-00-3C-00-00-00-00-00-00-00
+2021-10-31 01:35:10.742 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FE-19-8A 62-45-B4-E9-D1-8A 30-55-00-00 | 20-00-54-0A 00-00-3E-00-00-00-00-00-00-00
+2021-10-31 01:35:10.750 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FE-19-8A 62-45-B4-E9-D1-8A 40-55-00-00 | 20-00-55-0A 00-00-3D-00-00-00-00-00-00-00
+2021-10-31 01:35:10.758 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FE-19-8A 62-45-B4-E9-D1-8A 50-55-00-00 | 20-00-56-0A 00-00-3C-00-00-00-00-00-00-00
+2021-10-31 01:35:10.766 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FE-19-8A 62-45-B4-E9-D1-8A 60-55-00-00 | 20-00-57-0A 00-00-3B-00-00-00-00-00-00-00
+2021-10-31 01:35:10.783 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FE-19-8A 62-45-B4-E9-D1-8A 70-55-00-00 | 20-00-58-0A 00-00-3C-00-00-00-00-00-00-00
+2021-10-31 01:35:10.799 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FE-19-8A 62-45-B4-E9-D1-8A 80-55-00-00 | 20-00-59-0A 00-00-3B-00-00-00-00-00-00-00
+2021-10-31 01:35:10.807 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FE-19-8A 62-45-B4-E9-D1-8A 90-55-00-00 | 20-00-5A-0A 00-00-3C-00-00-00-00-00-00-00
+
+2021-10-31 01:58:23.354 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FB-14-BF 62-45-B4-E9-D1-8A 20-9B-00-00 | 20-00-C6-0A 10-00-8A-00-40-01-00-00-00-00
+2021-10-31 01:58:23.363 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FB-14-BF 62-45-B4-E9-D1-8A 30-9B-00-00 | 20-00-C7-0A 10-00-89-00-40-01-00-00-00-00
+2021-10-31 01:58:23.371 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FB-14-BF 62-45-B4-E9-D1-8A 40-9B-00-00 | 20-00-C8-0A 10-00-8A-00-40-01-00-00-00-00
+2021-10-31 01:58:23.403 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FB-14-BF 62-45-B4-E9-D1-8A 50-9B-00-00 | 20-00-C9-0A 10-00-8B-00-40-01-00-00-00-00
+2021-10-31 01:58:23.411 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FB-14-BF 62-45-B4-E9-D1-8A 60-9B-00-00 | 20-00-CA-0A 10-00-8D-00-40-01-00-00-00-00
+2021-10-31 01:58:23.443 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FB-14-BF 62-45-B4-E9-D1-8A 70-9B-00-00 | 20-00-CB-0A 10-00-8F-00-40-01-00-00-00-00
+2021-10-31 01:58:23.459 [40] -> 88-11-A0-00 62-45-B4-E9-D1-8A 7E-ED-8F-FB-14-BF 62-45-B4-E9-D1-8A 80-9B-00-00 | 20-00-CC-0A 10-00-90-00-40-01-00-00-00-00
\ No newline at end of file
diff --git a/Docs/PacketFormats.md b/Docs/PacketFormats.md
deleted file mode 100644
index 8b7a6c4..0000000
--- a/Docs/PacketFormats.md
+++ /dev/null
@@ -1,188 +0,0 @@
-# Xbox One RB4 Instrument Data Packets
-
-This documentation is far from fully comprehensive yet, and it also needs some better formatting than just long lists.
-
-Byte numbers in lists are 0-indexed.
-
-References:
-
-- [GuitarSniffer guitar packet logs](https://1drv.ms/f/s!AgQGk0OeTMLwhA-uDO9IQHEHqGhv)
-- GuitarSniffer guitar packet spreadsheets: [New](https://docs.google.com/spreadsheets/d/1ITZUvRniGpfS_HV_rBpSwlDdGukc3GC1CeOe7SavQBo/edit?usp=sharing), [Old](https://1drv.ms/x/s!AgQGk0OeTMLwg3GBDXFUC3Erj4Wb)
-
-To be referenced later:
-
--
-
-## Packet Frames
-
-- Bytes 0-21: Xbox header
-- Bytes 22-29: Packet metadata
-- Bytes 30 onward: Input data
-
-## Header Data
-
-- 22 bytes long
-
-Bytes:
-
-- Not fully understood, more research needed
- - 0-6 seem constant, `8811A0006245B4`
- - 2 seems to be `19` instead of `11` for a single packet in the guitar power-on log and in the whammy log
- - 7-9 have been observed to be different between the examples here and the GuitarSniffer packet logs folder
- - 10-11 seem constant, `7EED`
-- 12-15 - Instrument ID
- - 12 appears to be constant, `8F`
-- 16-21 seem to mirror 4-9
-
-## Packet Metadata
-
-- 8 bytes long, comes after the 22 header bytes
-
-Bytes:
-
-- Not well understood, there are some edge cases that need to be researched further
-- 22:
- - High 4 bits seem to increment by 1 with every packet
-- 23:
- - Seems to increment every time 22's high 4 bits roll over from F to 0
-- 24:
- - Seems to be `01` during power-on or Xbox button packets, and `00` everywhere else
-- 25:
- - Seems to be a constant `00`
-- 26:
- - Type of data? seems to be a constant `20` during regular packets and `07` in Xbox button packets
-- 27:
- - Unsure, seems to be a constant `00` during regular packets and `20` in Xbox button packets
-- 28:
- - Seems to increment with every packet, but its value doesn't seem to start at the same time as 22 and 23
-- 29:
- - Data bytes length?
- - This does not seem to be the case for all of the power-on packets logged, but it seems to be the case 100% of the time after power-on
- - On guitars, this is `0A`, except in Xbox button packets where it is `02`
- - On drums, this is `06`, except presumably in Xbox button packets where it is probably `02`
-
-## Guitar Input Data
-
-- 40 bytes long, including the Xbox header and packet count data
-- 10 bytes long without the header and count data
-- 32(?) bytes long when the Xbox button is pressed, including the Xbox header and packet count data
-- Some random packets here and there are 34 bytes long
-- Packet length varies wildly during power-on (anywhere from 32 to 90), but none there seem to be 40 bytes long.
-
-Bytes:
-
-- 30 - Buttons
- - Bit 0 (`0x01`) - Xbox
- - Bit 1 (`0x02`) - Unknown (maybe equivalent to the Share button?)
- - Bit 2 (`0x04`) - Menu button
- - Bit 3 (`0x08`) - Options button
- - Bit 4 (`0x10`) - Active when pressing either of the Green frets (equivalent to the A button on a regular controller?)
- - Bit 5 (`0x20`) - Active when pressing either of the Red frets (equivalent to the B button on a regular controller?)
- - Bit 6 (`0x40`) - Active when pressing either of the Blue frets (equivalent to the X button on a regular controller?)
- - Bit 7 (`0x80`) - Active when pressing either of the Yellow frets (equivalent to the Y button on a regular controller?)
-- 31 - D-pad/Strum Bar / Bumpers/Stick Presses
- - Bit 0 (`0x01`) - Down (Strum Up)
- - Bit 1 (`0x02`) - Up (Strum Down)
- - Bit 2 (`0x04`) - Left
- - Bit 3 (`0x08`) - Right
- - Bit 4 (`0x10`) - Active when pressing either of the Orange frets (equivalent to the LB button on a regular controller?)
- - Bit 5 (`0x20`) - Unused (equivalent to the RB button on a regular controller?)
- - Bit 6 (`0x40`) - Active when pressing the lower frets (equivalent to the left stick button on a regular controller?)
- - Bit 7 (`0x80`) - Unused (equivalent to the right stick button on a regular controller?)
-- 32 - Tilt (Axis)
- - Has a threshold of `70`? (values below get cut off to `00`)
-- 33 - Whammy Bar (Axis)
- - Uses full byte range
-- 34 - Pickup Switch/Slider (Axis)
- - Uses top 4 bytes, possible values are `00`, `10`, `20`, `30`, and `40`
-- 35 - Upper Frets
- - Bit 0 (`0x01`) - Green
- - Bit 1 (`0x02`) - Red
- - Bit 2 (`0x04`) - Yellow
- - Bit 3 (`0x08`) - Blue
- - Bit 4 (`0x10`) - Orange
- - Bits 5-7 - Unused
-- 36 - Lower Frets
- - Bit 0 (`0x01`) - Green
- - Bit 1 (`0x02`) - Red
- - Bit 2 (`0x04`) - Yellow
- - Bit 3 (`0x08`) - Blue
- - Bit 4 (`0x10`) - Orange
- - Bits 5-7 - Unused
-- 37-39 are unknown
-
-## Drums Input Data
-
-- 36 bytes long, including the Xbox header and packet count data
-- 6 bytes long without the header and count data
-- Presumably 32(?) bytes long when the Xbox button is pressed, including the Xbox header and packet count data
-
-Bytes:
-
-- 30 - Buttons + Red/Green Pads
- - Bit 0 (`0x01`) - Xbox
- - Bit 1 (`0x02`) - Unknown (maybe equivalent to the Share button?)
- - Bit 2 (`0x04`) - Menu button
- - Bit 3 (`0x08`) - Options button
- - Bit 4 (`0x10`) - Green Pad (equivalent to the A button on a regular controller?)
- - Bit 5 (`0x20`) - Red Pad (equivalent to the B button on a regular controller?)
- - Bit 6 (`0x40`) - (Interpolated) Blue Pad? (equivalent to the X button on a regular controller?)
- - Bit 7 (`0x80`) - (Interpolated) Yellow Pad? (equivalent to the Y button on a regular controller?)
-- 31 - D-pad
- - Bit 0 (`0x01`) - Down
- - Bit 1 (`0x02`) - Up
- - Bit 2 (`0x04`) - Left
- - Bit 3 (`0x08`) - Right
- - Bit 4 (`0x10`) - 1st Kick Pedal (equivalent to the LB button on a regular controller?)
- - Bit 5 (`0x20`) - 2nd Kick Pedal (equivalent to the RB button on a regular controller?)
- - Bit 6 (`0x40`) - Unused? (equivalent to the left stick button on a regular controller?)
- - Bit 7 (`0x80`) - Unused? (equivalent to the right stick button on a regular controller?)
-- 32 - Yellow Pad
- - Uses bits 0-3
-- 33 - Blue Pad
- - Uses bits 4-7
-- 34 - Yellow & Blue Cymbal
- - Bits 0-3 - Blue Cymbal
- - Bits 4-7 - Yellow Cymbal
-- 35 - Green Cymbal
- - Uses bits 4-7
-
-## Samples
-
-Guitar 1 Sample
-
-```
-2021-10-31 01:35:10.730 [40] 8811A0006245B4E9D18A7EED 8FFE198A 6245B4E9D18A 205500002000530A 00003C00000000000000
-2021-10-31 01:35:10.742 [40] 8811A0006245B4E9D18A7EED 8FFE198A 6245B4E9D18A 305500002000540A 00003E00000000000000
-2021-10-31 01:35:10.750 [40] 8811A0006245B4E9D18A7EED 8FFE198A 6245B4E9D18A 405500002000550A 00003D00000000000000
-2021-10-31 01:35:10.758 [40] 8811A0006245B4E9D18A7EED 8FFE198A 6245B4E9D18A 505500002000560A 00003C00000000000000
-2021-10-31 01:35:10.766 [40] 8811A0006245B4E9D18A7EED 8FFE198A 6245B4E9D18A 605500002000570A 00003B00000000000000
-2021-10-31 01:35:10.783 [40] 8811A0006245B4E9D18A7EED 8FFE198A 6245B4E9D18A 705500002000580A 00003C00000000000000
-2021-10-31 01:35:10.799 [40] 8811A0006245B4E9D18A7EED 8FFE198A 6245B4E9D18A 805500002000590A 00003B00000000000000
-2021-10-31 01:35:10.807 [40] 8811A0006245B4E9D18A7EED 8FFE198A 6245B4E9D18A 9055000020005A0A 00003C00000000000000
-```
-
-Guitar 2 Sample
-
-```
-2021-10-31 01:58:23.354 [40] 8811A0006245B4E9D18A7EED 8FFB14BF 6245B4E9D18A 209B00002000C60A 10008A00400100000000
-2021-10-31 01:58:23.363 [40] 8811A0006245B4E9D18A7EED 8FFB14BF 6245B4E9D18A 309B00002000C70A 10008900400100000000
-2021-10-31 01:58:23.371 [40] 8811A0006245B4E9D18A7EED 8FFB14BF 6245B4E9D18A 409B00002000C80A 10008A00400100000000
-2021-10-31 01:58:23.403 [40] 8811A0006245B4E9D18A7EED 8FFB14BF 6245B4E9D18A 509B00002000C90A 10008B00400100000000
-2021-10-31 01:58:23.411 [40] 8811A0006245B4E9D18A7EED 8FFB14BF 6245B4E9D18A 609B00002000CA0A 10008D00400100000000
-2021-10-31 01:58:23.443 [40] 8811A0006245B4E9D18A7EED 8FFB14BF 6245B4E9D18A 709B00002000CB0A 10008F00400100000000
-2021-10-31 01:58:23.459 [40] 8811A0006245B4E9D18A7EED 8FFB14BF 6245B4E9D18A 809B00002000CC0A 10009000400100000000
-```
-
-Drum Sample
-
-```
-2021-10-31 02:25:31.725 [36] 8811A0006245B4E9D18A7EED 8FFFCF6B 6245B4E9D18A A002000020002B06 000004000000
-2021-10-31 02:25:31.773 [36] 8811A0006245B4E9D18A7EED 8FFFCF6B 6245B4E9D18A B002000020002C06 000000000000
-2021-10-31 02:25:32.038 [36] 8811A0006245B4E9D18A7EED 8FFFCF6B 6245B4E9D18A C002000020002D06 200040000000
-2021-10-31 02:25:32.086 [36] 8811A0006245B4E9D18A7EED 8FFFCF6B 6245B4E9D18A D002000020002E06 000000000000
-2021-10-31 02:25:32.327 [36] 8811A0006245B4E9D18A7EED 8FFFCF6B 6245B4E9D18A E002000020002F06 000004000000
-2021-10-31 02:25:32.367 [36] 8811A0006245B4E9D18A7EED 8FFFCF6B 6245B4E9D18A F002000020003006 000000000000
-2021-10-31 02:25:32.608 [36] 8811A0006245B4E9D18A7EED 8FFFCF6B 6245B4E9D18A 0003000020003106 000000400000
-2021-10-31 02:25:32.656 [36] 8811A0006245B4E9D18A7EED 8FFFCF6B 6245B4E9D18A 1003000020003206 000000000000
-```
diff --git a/Docs/WinUSB/manual-winusb-install.md b/Docs/WinUSB/manual-winusb-install.md
new file mode 100644
index 0000000..9603be9
--- /dev/null
+++ b/Docs/WinUSB/manual-winusb-install.md
@@ -0,0 +1,67 @@
+# Manual WinUSB Install Instructions
+
+RB4InstrumentMapper is capable of installing the WinUSB driver on Xbox One devices directly, through the `Configure Devices` button on its main menu. However, if you run into any issues, it is possible to install the driver manually as an alternative.
+
+## Disclaimer: This page is for advanced users!
+
+Manually switching device drivers is a potentially dangerous process if not done carefully. You are responsible for any problems that may arise from this process, and you are largely on your own if something bugs out.
+
+If you are using a Riffmaster and a guide has directed you to this page, you are either being misdirected or are receiving outdated info. This process is not required for the Riffmaster, use the GameInput backend instead and make sure you're using the [latest version of RB4InstrumentMapper](https://github.com/TheNathannator/RB4InstrumentMapper/releases/latest).
+
+**You have been warned!**
+
+## Warning
+
+***DO NOT INSTALL THE DRIVER ON YOUR XBOX ONE RECEIVER!!!***
+
+
+
+RB4InstrumentMapper is not capable of handling it in this state, and it will become nonfunctional until you uninstall the device in Device Manager!
+
+The driver should only be installed on the Wireless Legacy Adapter or Guitar Hero Live dongle.
+
+## Install WinUSB
+
+1. Download [Zadig](https://zadig.akeo.ie/) and run it.
+2. Under Options, enable `List All Devices`.
+
+ 
+
+3. Select your device from the dropdown list.
+
+ 
+
+4. Change the box to the right of the green/orange arrow to the `WinUSB` driver.
+
+ 
+
+5. Hit `Replace Driver` and wait for it to finish.
+6. Repeat for any additional peripherals you wish to use.
+
+You can now use the device in RB4InstrumentMapper. Note that games that natively support the device will no longer work directly with it until you uninstall the WinUSB driver, you will have to use RB4InstrumentMapper.
+
+## Remove WinUSB
+
+1. Press the Windows key + X and select `Device Manager`.
+
+ 
+
+2. Expand the `Universal Serial Bus devices` category and find the device you want to reset.
+
+ 
+
+ - If this category is not present, you may need to look in the `Other devices` category instead. In particular, if you're here to fix a failed revert from within the `Configure USB Devices` menu, this is where it will most likely be located.
+
+3. Right-click the device and hit `Uninstall device`.
+
+ 
+
+ - If a checkbox that says `Delete the driver software for this device` is displayed on the confirmation dialog, be sure to click it.
+
+ 
+
+4. After confirming and restarting your PC, the device should now appear under the `Xbox Peripherals` category.
+
+ 
+
+The device is now restored to its original state. Games that natively support the device will work with it again, but RB4InstrumentMapper will no longer be able to make it usable in other games.
diff --git a/Installer/Product.wxs b/Installer/Product.wxs
deleted file mode 100644
index 19aca14..0000000
--- a/Installer/Product.wxs
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Installer/RB4InstrumentMapperInstaller.wixproj b/Installer/RB4InstrumentMapperInstaller.wixproj
deleted file mode 100644
index 3be620a..0000000
--- a/Installer/RB4InstrumentMapperInstaller.wixproj
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
- Debug
- x86
- 3.10
- 047562bb-6d63-4259-8e2e-0a5e834190b1
- 2.0
- RB4InstrumentMapperInstaller
- Package
- RB4InstrumentMapperInstaller
-
-
- bin\$(Configuration)\
- obj\$(Configuration)\
- Debug
-
-
- bin\$(Configuration)\
- obj\$(Configuration)\
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $(WixToolPath)WixUIExtension.dll
- WixUIExtension
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/MainWindow/FixedSizeConcurrentQueue.cs b/MainWindow/FixedSizeConcurrentQueue.cs
deleted file mode 100644
index 72d5f5f..0000000
--- a/MainWindow/FixedSizeConcurrentQueue.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace RB4InstrumentMapper
-{
- ///
- /// Implementation of a fixed-size concurrent queue.
- ///
- ///
- public class FixedSizeConcurrentQueue : ConcurrentQueue
- {
- ///
- /// Sync root of the queue for locking.
- ///
- private readonly object privateLockObject = new object();
-
- ///
- /// Size of the queue.
- ///
- public int Size { get; private set; }
-
- ///
- /// Create a new instance of the FixedSizeConcurrentQueue.
- ///
- /// Size of the queue.
- public FixedSizeConcurrentQueue(int size)
- {
- Size = size;
- }
-
- ///
- /// Enqueue an object into the queue. Maintains size limit of queue.
- ///
- /// Object to enqueue
- public new void Enqueue(T obj)
- {
- base.Enqueue(obj);
- lock (privateLockObject)
- {
- while (base.Count > Size)
- {
- T outObj;
- base.TryDequeue(out outObj);
- }
- }
- }
- }
-}
diff --git a/MainWindow/MainWindow.xaml b/MainWindow/MainWindow.xaml
deleted file mode 100644
index b586cdf..0000000
--- a/MainWindow/MainWindow.xaml
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MainWindow/MainWindow.xaml.cs b/MainWindow/MainWindow.xaml.cs
deleted file mode 100644
index a2d0130..0000000
--- a/MainWindow/MainWindow.xaml.cs
+++ /dev/null
@@ -1,1773 +0,0 @@
-using PcapDotNet.Core;
-using PcapDotNet.Packets;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-using vJoyInterfaceWrap;
-using System.Runtime.InteropServices;
-using System.Windows.Threading;
-using Nefarius.ViGEm.Client;
-using Nefarius.ViGEm.Client.Targets;
-
-namespace RB4InstrumentMapper
-{
- ///
- /// Interaction logic for MainWindow.xaml
- ///
- public partial class MainWindow : Window
- {
- ///
- /// Dispatcher to send changes to UI.
- ///
- private static Dispatcher uiDispatcher = null;
-
- ///
- /// Default Pcap packet capture timeout in milliseconds.
- ///
- private const int DefaultPacketCaptureTimeoutMilliseconds = 50;
-
- ///
- /// List of available Pcap devices.
- ///
- private IList pcapDeviceList = null;
-
- ///
- /// The selected Pcap device.
- ///
- private LivePacketDevice pcapSelectedDevice = null;
-
- ///
- /// Pcap packet communicator.
- ///
- private PacketCommunicator pcapCommunicator;
-
- ///
- /// Thread that handles Pcap capture.
- ///
- private Thread pcapCaptureThread;
-
- ///
- /// Flag indicating that packet capture is active.
- ///
- private static bool packetCaptureActive = false;
-
- ///
- /// Flag indicating if packets should be shown.
- ///
- private static bool packetDebug = false;
-
- ///
- /// Flag indicating that guitar 1 ID auto assigning is in progress.
- ///
- private static bool packetGuitar1AutoAssign = false;
- ///
- /// Flag indicating that guitar 2 ID auto assigning is in progress.
- ///
- private static bool packetGuitar2AutoAssign = false;
- ///
- /// Flag indicating that drum ID auto assigning is in progress.
- ///
- private static bool packetDrumAutoAssign = false;
-
- ///
- /// Common name for Pcap combo box items.
- ///
- private const string pcapComboBoxItemName = "pcapDeviceComboBoxItem";
-
- ///
- /// Common name for controller combo box items.
- ///
- private const string controllerComboBoxItemName = "controllerComboBoxItem";
-
- ///
- /// vJoy client.
- ///
- private static vJoy joystick;
-
- ///
- /// ViGEmBus client.
- ///
- private static ViGEmClient vigemClient = null;
-
- ///
- /// Index of the selected guitar 1 device.
- ///
- private static uint guitar1DeviceIndex = 0;
-
- ///
- /// Instrument ID for guitar 1.
- ///
- ///
- /// An ID of 0x00000000 is assumed to be invalid.
- ///
- private static uint guitar1InstrumentId = 0;
-
- ///
- /// Index of the selected guitar 2 device.
- ///
- private static uint guitar2DeviceIndex = 0;
-
- ///
- /// Instrument ID for guitar 2.
- ///
- ///
- /// An ID of 0x00000000 is assumed to be invalid.
- ///
- private static uint guitar2InstrumentId = 0;
-
- ///
- /// Index of the selected drum device.
- ///
- private static uint drumDeviceIndex = 0;
-
- ///
- /// Instrument ID for the drumkit.
- ///
- ///
- /// An ID of 0x00000000 is assumed to be invalid.
- ///
- private static uint drumInstrumentId = 0;
-
- ///
- /// Analyzed packet for guitar.
- ///
- private static GuitarPacket guitarPacket = new GuitarPacket();
-
- ///
- /// Analyzed packet for the drumkit.
- ///
- private static DrumPacket drumPacket = new DrumPacket();
-
- ///
- /// Counter for processed packets.
- ///
- private static ulong processedPacketCount = 0;
-
- ///
- /// Dictionary for ViGEmBus controllers.
- ///
- ///
- /// uint = identifier for the instrument (1 for guitar 1, 2 for guitar 2, and 3 for drum)
- ///
IXbox360Controller = the controller associated with the instrument.
- ///
- private static Dictionary vigemDictionary = new Dictionary();
-
- ///
- /// Enumeration for ViGEmBus stuff.
- ///
- private enum VigemEnum
- {
- Guitar1 = 1,
- Guitar2 = 2,
- Drum = 3,
- DeviceIndex = 17
- }
-
- ///
- /// Initializes a new MainWindow.
- ///
- public MainWindow()
- {
- // Assign event handler for unhandled exceptions
- AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(App.App_UnhandledException);
-
- InitializeComponent();
-
- // Capture Dispatcher object for use in callback
- uiDispatcher = this.Dispatcher;
- }
-
- ///
- /// Called when the window loads.
- ///
- ///
- ///
- private void Window_Loaded(object sender, RoutedEventArgs e)
- {
- // Connect to console
- TextBoxConsole.RedirectConsoleToTextBox(messageConsole, displayLinesWithTimestamp: false);
-
- // Initialize dropdowns
- try // PcapDotNet can't be loaded if Pcap isn't installed, so it will cause a run-time exception here
- {
- PopulatePcapDropdown();
- }
- catch(System.IO.FileNotFoundException)
- {
- MessageBox.Show("Could not load Pcap interface.\nThe program will now shut down.", "Error Starting Program", MessageBoxButton.OK, MessageBoxImage.Error);
- Application.Current.Shutdown();
- return;
- }
- PopulateControllerDropdowns();
- }
-
- ///
- /// Called when the window has closed.
- ///
- ///
- ///
- private void Window_Closed(object sender, EventArgs e)
- {
- // Shutdown
- if (packetCaptureActive)
- {
- // Same situation as PopulatePcapDropdown can happen here,
- // but this function will only be called if the program successfully starts in the first place due to the if(packetCaptureActive)
- StopCapture();
- }
-
- // Dispose of the ViGEmBus client
- if (vigemClient != null)
- {
- vigemClient.Dispose();
- }
- }
-
- ///
- /// Acquires a vJoy device.
- ///
- /// The vJoy client to use.
- /// The device ID of the vJoy device to acquire.
- /// True if device was successfully acquired, false otherwise.
- static bool AcquirevJoyDevice(vJoy joystick, uint deviceId)
- {
- // Get the state of the requested device
- VjdStat status = joystick.GetVJDStatus(deviceId);
-
- // Acquire the device
- if ((status == VjdStat.VJD_STAT_OWN) || ((status == VjdStat.VJD_STAT_FREE) && (!joystick.AcquireVJD(deviceId))))
- {
- Console.WriteLine($"Failed to acquire vJoy device number {deviceId}.");
- return false;
- }
- else
- {
- // Get the number of buttons
- int nButtons = joystick.GetVJDButtonNumber(deviceId);
-
- Console.WriteLine($"Acquired vJoy device number {deviceId} with {nButtons} buttons.");
- return true;
- }
- }
-
- ///
- /// Creates a ViGEmBus device.
- ///
- /// The user index to index into the ViGEm dictionary.
- /// True if device was successfully created or already exists, false otherwise.
- static bool CreateVigemDevice(uint userIndex)
- {
- // Don't add duplicate entries
- if (vigemDictionary.ContainsKey(userIndex))
- {
- // Returns true since it's already added
- return true;
- }
-
- IXbox360Controller vigemDevice = vigemClient.CreateXbox360Controller(0x1BAD, 0x0719); // Xbox 360 Rock Band wireless instrument vendor/product IDs
- // Rock Band Guitar: USB\VID_1BAD&PID_0719&IG_00 XUSB\TYPE_00\SUB_86\VEN_1BAD\REV_0002
- // Rock Band Drums: USB\VID_1BAD&PID_0719&IG_02 XUSB\TYPE_00\SUB_88\VEN_1BAD\REV_0002
- // If subtype ID specification through ViGEmBus becomes possible at some point,
- // the guitar should be subtype 6, and the drums should be subtype 8
-
- try
- {
- // Throws one of 5 exceptions:
- // VigemBusNotFoundException
- // VigemTargetUninitializedException
- // VigemAlreadyConnectedException
- // VigemNoFreeSlotException
- // Win32Exception
- // These shouldn't happen in 99% of cases, catching just in case
- vigemDevice.Connect();
-
- // Throws Xbox360UserIndexNotReportedException
- // This also shouldn't happen in 99% of cases, managed to encounter the 1% with someone
- int _userIndex = vigemDevice.UserIndex;
- Console.WriteLine($"Created new ViGEmBus device with user index {_userIndex}");
- }
- catch (Exception e)
- {
- // Create brief exception string
- // Not using Exception.Message since it doesn't contain the exception type
- string exceptionString = e.ToString();
- int removeIndex = exceptionString.IndexOf(Environment.NewLine);
- string exceptionMessage = exceptionString.Substring(0, removeIndex);
-
- string instrumentName = Enum.GetName(typeof(VigemEnum), userIndex);
- Console.WriteLine($"Could not create ViGEmBus device for {instrumentName}: {exceptionMessage}");
- return false;
- }
-
- vigemDictionary.Add(userIndex, vigemDevice);
- return true;
- }
-
- ///
- /// Populates controller device selection combos.
- ///
- ///
- /// Used both when initializing and when refreshing.
- ///
- private void PopulateControllerDropdowns()
- {
- // Initialize the vJoy client
- joystick = new vJoy();
-
- // Check if vJoy is enabled
- bool vjoyFound = joystick.vJoyEnabled();
- if (!vjoyFound)
- {
- Console.WriteLine("No vJoy driver found, or vJoy is disabled. vJoy selections will be unavailable.");
- }
- else
- {
- // Log vJoy driver attributes (Vendor Name, Product Name, Version Number)
- Console.WriteLine("vJoy found! - Vendor: " + joystick.GetvJoyManufacturerString() + ", Product: " + joystick.GetvJoyProductString() + ", Version Number: " + joystick.GetvJoySerialNumberString());
- }
-
- // Check if ViGEmBus is installed
- bool vigemFound = false;
- if (vigemClient == null)
- {
- try
- {
- vigemClient = new ViGEmClient();
- vigemFound = true;
- Console.WriteLine("ViGEmBus found!");
- }
- catch(Nefarius.ViGEm.Client.Exceptions.VigemBusNotFoundException)
- {
- vigemClient = null;
- vigemFound = false;
- Console.WriteLine("ViGEmBus not found. ViGEmBus selection will be unavailable.");
- }
- }
- else
- {
- vigemFound = true;
- }
-
- // Check if neither vJoy nor ViGEmBus were found
- if (!(vjoyFound || vigemFound))
- {
- MessageBox.Show("No controller emulators found! Please install either vJoy or ViGEmBus.\nThe program will now shut down.", "No Controller Emulators Found", MessageBoxButton.OK, MessageBoxImage.Error);
- Application.Current.Shutdown();
- return;
- }
-
- // Get default settings
- string currentGuitar1Selection = Properties.Settings.Default.currentGuitar1Selection;
- string currentGuitar2Selection = Properties.Settings.Default.currentGuitar2Selection;
- string currentDrumSelection = Properties.Settings.Default.currentDrumSelection;
-
- // Reset combo boxes
- guitar1Combo.Items.Clear();
- guitar2Combo.Items.Clear();
- drumCombo.Items.Clear();
-
- // Loop through vJoy IDs and populate dropdowns
- int freeDeviceCount = 0;
- for (uint id = 1; id <= 16; id++)
- {
- string vjoyDeviceName = $"vJoy Device {id}";
- string vjoyItemName = $"{controllerComboBoxItemName}{id}";
- bool isEnabled = false;
-
- // Get the state of the requested device
- if (vjoyFound)
- {
- VjdStat status = joystick.GetVJDStatus(id);
- switch (status)
- {
- case VjdStat.VJD_STAT_OWN:
- vjoyDeviceName += " (device is already owned by this feeder)";
- break;
- case VjdStat.VJD_STAT_FREE:
- int numButtons = joystick.GetVJDButtonNumber(id);
- int numContPov = joystick.GetVJDContPovNumber(id);
- bool xExists = joystick.GetVJDAxisExist(id, HID_USAGES.HID_USAGE_X); // X axis
- bool yExists = joystick.GetVJDAxisExist(id, HID_USAGES.HID_USAGE_Y); // Y axis
- bool zExists = joystick.GetVJDAxisExist(id, HID_USAGES.HID_USAGE_Z); // Z axis
- // Check that vJoy device is configured correctly
- if (numButtons >= 16 &&
- numContPov >= 1 &&
- xExists &&
- yExists &&
- zExists
- )
- {
- isEnabled = true;
- freeDeviceCount++;
- }
- else
- {
- vjoyDeviceName += " (device misconfigured, use 16 buttons, X/Y/Z axes, and 1 continuous POV)";
- }
- break;
- case VjdStat.VJD_STAT_BUSY:
- vjoyDeviceName += " (device is already owned by another feeder)";
- break;
- case VjdStat.VJD_STAT_MISS:
- vjoyDeviceName += " (device is not installed or disabled)";
- break;
- default:
- vjoyDeviceName += " (general error)";
- break;
- };
- }
- else
- {
- vjoyDeviceName += " (vJoy disabled/not found)";
- }
-
- // Add combo item to combos
- // Guitar 1 combo
- ComboBoxItem vjoyComboBoxItem = new ComboBoxItem();
- vjoyComboBoxItem.Content = vjoyDeviceName;
- vjoyComboBoxItem.Name = vjoyItemName;
- vjoyComboBoxItem.IsEnabled = isEnabled;
- vjoyComboBoxItem.IsSelected = vjoyItemName.Equals(currentGuitar1Selection) && isEnabled;
- guitar1Combo.Items.Add(vjoyComboBoxItem);
-
- // Guitar 2 combo
- vjoyComboBoxItem = new ComboBoxItem();
- vjoyComboBoxItem.Content = vjoyDeviceName;
- vjoyComboBoxItem.Name = vjoyItemName;
- vjoyComboBoxItem.IsEnabled = isEnabled;
- vjoyComboBoxItem.IsSelected = vjoyItemName.Equals(currentGuitar2Selection) && isEnabled;
- guitar2Combo.Items.Add(vjoyComboBoxItem);
-
- // Drum combo
- vjoyComboBoxItem = new ComboBoxItem();
- vjoyComboBoxItem.Content = vjoyDeviceName;
- vjoyComboBoxItem.Name = vjoyItemName;
- vjoyComboBoxItem.IsEnabled = isEnabled;
- vjoyComboBoxItem.IsSelected = vjoyItemName.Equals(currentDrumSelection) && isEnabled;
- drumCombo.Items.Add(vjoyComboBoxItem);
- }
-
- if (vjoyFound)
- {
- Console.WriteLine($"Discovered {freeDeviceCount} free vJoy devices.");
- }
-
- // Create ViGEmBus device dropdown item
- string vigemDeviceName = $"ViGEmBus Device";
- if (!vigemFound)
- {
- vigemDeviceName += " (ViGEmBus not found)";
- }
- string vigemItemName = $"{controllerComboBoxItemName}17";
-
- // Add ViGEmBus combo item
- // Guitar 1 combo
- ComboBoxItem vigemComboBoxItem = new ComboBoxItem();
- vigemComboBoxItem.Content = vigemDeviceName;
- vigemComboBoxItem.Name = vigemItemName;
- vigemComboBoxItem.IsEnabled = vigemFound;
- vigemComboBoxItem.IsSelected = vigemItemName.Equals(currentGuitar1Selection) && vigemFound;
- guitar1Combo.Items.Add(vigemComboBoxItem);
-
- // Guitar 2 combo
- vigemComboBoxItem = new ComboBoxItem();
- vigemComboBoxItem.Content = vigemDeviceName;
- vigemComboBoxItem.Name = vigemItemName;
- vigemComboBoxItem.IsEnabled = vigemFound;
- vigemComboBoxItem.IsSelected = vigemItemName.Equals(currentGuitar2Selection) && vigemFound;
- guitar2Combo.Items.Add(vigemComboBoxItem);
-
- // Drum combo
- vigemComboBoxItem = new ComboBoxItem();
- vigemComboBoxItem.Content = vigemDeviceName;
- vigemComboBoxItem.Name = vigemItemName;
- vigemComboBoxItem.IsEnabled = vigemFound;
- vigemComboBoxItem.IsSelected = vigemItemName.Equals(currentDrumSelection) && vigemFound;
- drumCombo.Items.Add(vigemComboBoxItem);
-
- // Add None option
- // Guitar 1 combo
- string noneItemName = $"{controllerComboBoxItemName}0";
- ComboBoxItem noneComboBoxItem = new ComboBoxItem();
- noneComboBoxItem.Content = "None";
- noneComboBoxItem.Name = noneItemName;
- noneComboBoxItem.IsEnabled = true;
- noneComboBoxItem.IsSelected = noneItemName.Equals(currentGuitar1Selection) || string.IsNullOrEmpty(currentGuitar1Selection); // Default to this selection
- guitar1Combo.Items.Add(noneComboBoxItem);
-
- // Guitar 2 combo
- noneComboBoxItem = new ComboBoxItem();
- noneComboBoxItem.Content = "None";
- noneComboBoxItem.Name = noneItemName;
- noneComboBoxItem.IsEnabled = true;
- noneComboBoxItem.IsSelected = noneItemName.Equals(currentGuitar2Selection) || string.IsNullOrEmpty(currentGuitar2Selection); // Default to this selection
- guitar2Combo.Items.Add(noneComboBoxItem);
-
- // Drum combo
- noneComboBoxItem = new ComboBoxItem();
- noneComboBoxItem.Content = "None";
- noneComboBoxItem.Name = noneItemName;
- noneComboBoxItem.IsEnabled = true;
- noneComboBoxItem.IsSelected = noneItemName.Equals(currentDrumSelection) || string.IsNullOrEmpty(currentDrumSelection); // Default to this selection
- drumCombo.Items.Add(noneComboBoxItem);
-
- // Load default device IDs
- // Guitar 1
- string hexString = Properties.Settings.Default.currentGuitar1Id;
- if (!ParsingHelpers.HexStringToUInt32(hexString, out guitar1InstrumentId))
- {
- if (string.IsNullOrEmpty(hexString))
- {
- guitar1InstrumentId = 0;
- }
- else
- {
- guitar1InstrumentId = 0;
- Console.WriteLine("Attempted to load an invalid Guitar 1 instrument ID. The ID has been reset.");
- }
- }
- guitar1IdTextBox.Text = (guitar1InstrumentId == 0) ? string.Empty : hexString;
-
- // Guitar 2
- hexString = Properties.Settings.Default.currentGuitar2Id;
- if (!ParsingHelpers.HexStringToUInt32(hexString, out guitar2InstrumentId))
- {
- if (string.IsNullOrEmpty(hexString))
- {
- guitar2InstrumentId = 0;
- }
- else
- {
- guitar2InstrumentId = 0;
- Console.WriteLine("Attempted to load an invalid Guitar 2 instrument ID. The ID has been reset.");
- }
- }
- guitar2IdTextBox.Text = (guitar2InstrumentId == 0) ? string.Empty : hexString;
-
- // Drum
- hexString = Properties.Settings.Default.currentDrumId;
- if (!ParsingHelpers.HexStringToUInt32(hexString, out drumInstrumentId))
- {
- if (string.IsNullOrEmpty(hexString))
- {
- drumInstrumentId = 0;
- }
- else
- {
- drumInstrumentId = 0;
- Console.WriteLine("Attempted to load an invalid Drum instrument ID. The ID has been reset.");
- }
- }
- drumIdTextBox.Text = (drumInstrumentId == 0) ? string.Empty : hexString;
- }
-
- ///
- /// Populates the Pcap device combo.
- ///
- ///
- /// Used both when initializing, and when refreshing.
- ///
- private void PopulatePcapDropdown()
- {
- // Disable auto-detect ID buttons
- guitar1IdAutoDetectButton.IsEnabled = false;
- guitar2IdAutoDetectButton.IsEnabled = false;
- drumIdAutoDetectButton.IsEnabled = false;
-
- // Clear combo list
- pcapDeviceCombo.Items.Clear();
-
- // Retrieve the device list from the local machine
- try
- {
- pcapDeviceList = LivePacketDevice.AllLocalMachine;
- }
- catch(InvalidOperationException)
- {
- Console.WriteLine("Could not retrieve list of Pcap interfaces.");
- return;
- }
-
- if (pcapDeviceList == null || pcapDeviceList.Count == 0)
- {
- Console.WriteLine("No Pcap interfaces found!");
- return;
- }
-
- // Get default settings
- string currentPcapSelection = Properties.Settings.Default.currentPcapSelection;
-
- // Populate combo and print the list
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < pcapDeviceList.Count; i++)
- {
- LivePacketDevice device = pcapDeviceList[i];
- sb.Clear();
- string itemNumber = $"{i + 1}";
- sb.Append($"{itemNumber}. ");
- if (device.Description != null)
- {
- sb.Append(device.Description);
- }
- sb.Append($" ({device.Name})");
-
- string deviceName = sb.ToString();
- string itemName = pcapComboBoxItemName + itemNumber;
- ComboBoxItem comboBoxItem = new ComboBoxItem();
- comboBoxItem.Name = itemName;
- comboBoxItem.Content = deviceName;
- comboBoxItem.IsEnabled = true;
- bool isSelected = device.Name.Equals(currentPcapSelection) || device.Name.Equals(pcapSelectedDevice?.Name);
- comboBoxItem.IsSelected = isSelected;
- if (isSelected)
- {
- // Re-enable auto-detect ID buttons and assign internal device reference
- guitar1IdAutoDetectButton.IsEnabled = true;
- guitar2IdAutoDetectButton.IsEnabled = true;
- drumIdAutoDetectButton.IsEnabled = true;
- pcapSelectedDevice = device;
- }
-
- pcapDeviceCombo.Items.Add(comboBoxItem);
- }
-
- // Set selection to nothing if saved device not detected
- if (pcapSelectedDevice == null)
- {
- pcapDeviceCombo.SelectedIndex = -1;
- }
-
- // Preset debugging flag
- string currentPacketDebugState = Properties.Settings.Default.currentPacketDebugState;
- if (currentPacketDebugState == "true")
- {
- packetDebugCheckBox.IsChecked = true;
- }
-
- Console.WriteLine($"Discovered {pcapDeviceList.Count} Pcap devices.");
- }
-
- ///
- /// Handles Pcap device selection changes.
- ///
- ///
- ///
- private void pcapDeviceCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- // Get selected combo box item
- ComboBoxItem typeItem = (ComboBoxItem)pcapDeviceCombo.SelectedItem;
- // Attempting to use typeItem's properties while null will cause a NullReferenceException
- if (typeItem == null)
- {
- Properties.Settings.Default.currentPcapSelection = String.Empty;
- Properties.Settings.Default.Save();
- return;
- }
- string itemName = typeItem.Name;
-
- // Get index of selected Pcap device
- int pcapDeviceIndex = -1;
- if (int.TryParse(itemName.Substring(pcapComboBoxItemName.Length), out pcapDeviceIndex))
- {
- // Adjust index count (UI->Logical)
- pcapDeviceIndex -= 1;
-
- // Assign device
- pcapSelectedDevice = pcapDeviceList[pcapDeviceIndex];
- Console.WriteLine($"Selected Pcap device {pcapSelectedDevice.Description}");
-
- // Enable auto-detect ID buttons
- guitar1IdAutoDetectButton.IsEnabled = true;
- guitar2IdAutoDetectButton.IsEnabled = true;
- drumIdAutoDetectButton.IsEnabled = true;
-
- // Remember selected Pcap device's name
- Properties.Settings.Default.currentPcapSelection = pcapSelectedDevice.Name;
- Properties.Settings.Default.Save();
- }
- }
-
- ///
- /// Handles guitar 1 controller selection changes.
- ///
- ///
- ///
- private void guitar1Combo_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- // Only allow a device to be selected by one selection, unless it is the None or ViGEmBus Device selections
- if (guitar1Combo.SelectedIndex < (int)VigemEnum.DeviceIndex)
- {
- if (guitar1Combo.SelectedIndex == guitar2Combo.SelectedIndex)
- {
- guitar2Combo.SelectedIndex = -1;
- }
-
- if (guitar1Combo.SelectedIndex == drumCombo.SelectedIndex)
- {
- drumCombo.SelectedIndex = -1;
- }
- }
-
- // Get selected guitar device
- ComboBoxItem typeItem = (ComboBoxItem)guitar1Combo.SelectedItem;
- // Attempting to use typeItem's properties while null will cause a NullReferenceException
- if (typeItem == null)
- {
- Properties.Settings.Default.currentGuitar1Selection = String.Empty;
- Properties.Settings.Default.Save();
- return;
- }
- string itemName = typeItem.Name;
-
- // Get index of selected guitar device
- if (uint.TryParse(typeItem.Name.Substring(controllerComboBoxItemName.Length), out guitar1DeviceIndex))
- {
- // Remember selected guitar device
- Properties.Settings.Default.currentGuitar1Selection = itemName;
- Properties.Settings.Default.Save();
- }
- }
-
- ///
- /// Handles guitar 2 controller selection changes.
- ///
- ///
- ///
- private void guitar2Combo_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- // Only allow a device to be selected by one selection, unless it is the None or ViGEmBus Device selections
- if (guitar2Combo.SelectedIndex < (int)VigemEnum.DeviceIndex)
- {
- if (guitar2Combo.SelectedIndex == guitar1Combo.SelectedIndex)
- {
- guitar1Combo.SelectedIndex = -1;
- }
-
- if (guitar2Combo.SelectedIndex == drumCombo.SelectedIndex)
- {
- drumCombo.SelectedIndex = -1;
- }
- }
-
- // Get selected guitar device
- ComboBoxItem typeItem = (ComboBoxItem)guitar2Combo.SelectedItem;
- // Attempting to use typeItem's properties while null will cause a NullReferenceException
- if (typeItem == null)
- {
- Properties.Settings.Default.currentGuitar2Selection = String.Empty;
- Properties.Settings.Default.Save();
- return;
- }
- string itemName = typeItem.Name;
-
- // Get index of selected guitar device
- if (uint.TryParse(typeItem.Name.Substring(controllerComboBoxItemName.Length), out guitar2DeviceIndex))
- {
- // Remember selected guitar device
- Properties.Settings.Default.currentGuitar2Selection = itemName;
- Properties.Settings.Default.Save();
- }
- }
-
- ///
- /// Handles drum controller selection changes.
- ///
- ///
- ///
- private void drumCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- // Only allow a device to be selected by one selection, unless it is the None or ViGEmBus Device selections
- if (drumCombo.SelectedIndex < (int)VigemEnum.DeviceIndex)
- {
- if (drumCombo.SelectedIndex == guitar1Combo.SelectedIndex)
- {
- guitar1Combo.SelectedIndex = -1;
- }
-
- if (drumCombo.SelectedIndex == guitar2Combo.SelectedIndex)
- {
- guitar2Combo.SelectedIndex = -1;
- }
- }
-
- // Get selected drum device
- ComboBoxItem typeItem = (ComboBoxItem)drumCombo.SelectedItem;
- // Attempting to use typeItem's properties while null will cause a NullReferenceException
- if (typeItem == null)
- {
- Properties.Settings.Default.currentDrumSelection = String.Empty;
- Properties.Settings.Default.Save();
- return;
- }
- string itemName = typeItem.Name;
-
- // Get index of selected drum device
- if (uint.TryParse(typeItem.Name.Substring(controllerComboBoxItemName.Length), out drumDeviceIndex))
- {
- // Remember selected drum device
- Properties.Settings.Default.currentDrumSelection = itemName;
- Properties.Settings.Default.Save();
- }
- }
-
- ///
- /// Configures the Pcap device and controller devices, and starts packet capture.
- ///
- private void StartCapture()
- {
- // Check if a device has been selected
- if (pcapSelectedDevice == null)
- {
- Console.WriteLine("Please select a Pcap device from the Pcap dropdown.");
- return;
- }
-
- // Retrieve the device list from the local machine
- IList allDevices = LivePacketDevice.AllLocalMachine;
-
- // Check if the device is still present
- bool deviceStillPresent = false;
- foreach(LivePacketDevice device in allDevices)
- {
- if (device.Name == pcapSelectedDevice.Name)
- {
- deviceStillPresent = true;
- break;
- }
- }
-
- if (!deviceStillPresent)
- {
- // Invalidate selected device (but not the saved preference)
- pcapSelectedDevice = null;
- // Notify user
- MessageBox.Show(
- "Pcap device list has changed and the selected device is no longer present.\nPlease re-select your device from the list and try again.",
- "Pcap Device Not Found",
- MessageBoxButton.OK,
- MessageBoxImage.Exclamation
- );
- // Force a refresh
- PopulatePcapDropdown();
- return;
- }
-
- // Enable packet capture active flag
- packetCaptureActive = true;
-
- // Disable window controls
- pcapDeviceCombo.IsEnabled = false;
- pcapAutoDetectButton.IsEnabled = false;
- pcapRefreshButton.IsEnabled = false;
- packetDebugCheckBox.IsEnabled = false;
-
- guitar1Combo.IsEnabled = false;
- guitar1IdTextBox.IsEnabled = false;
- guitar1IdAutoDetectButton.IsEnabled = false;
-
- guitar2Combo.IsEnabled = false;
- guitar2IdTextBox.IsEnabled = false;
- guitar2IdAutoDetectButton.IsEnabled = false;
-
- drumCombo.IsEnabled = false;
- drumIdTextBox.IsEnabled = false;
- drumIdAutoDetectButton.IsEnabled = false;
-
- controllerRefreshButton.IsEnabled = false;
- startButton.Content = "Stop";
-
- // Enable packet count display
- packetsProcessedLabel.Visibility = Visibility.Visible;
- packetsProcessedCountLabel.Visibility = Visibility.Visible;
- packetsProcessedCountLabel.Content = "0";
- processedPacketCount = 0;
-
- // Initialize vJoy
- bool vjoyResult;
- if (joystick != null)
- {
- // Reset buttons and axis
- joystick.ResetAll();
-
- // Acquire vJoy devices
- if (guitar1DeviceIndex > 0 && guitar1DeviceIndex < (int)VigemEnum.DeviceIndex)
- {
- vjoyResult = AcquirevJoyDevice(joystick, guitar1DeviceIndex);
- if (!vjoyResult)
- {
- StopCapture();
- return;
- }
- }
-
- if (guitar2DeviceIndex > 0 && guitar2DeviceIndex < (int)VigemEnum.DeviceIndex)
- {
- vjoyResult = AcquirevJoyDevice(joystick, guitar2DeviceIndex);
- if (!vjoyResult)
- {
- StopCapture();
- return;
- }
- }
-
- if (drumDeviceIndex > 0 && drumDeviceIndex < (int)VigemEnum.DeviceIndex)
- {
- vjoyResult = AcquirevJoyDevice(joystick, drumDeviceIndex);
- if (!vjoyResult)
- {
- StopCapture();
- return;
- }
- }
- }
-
- // Initialize ViGEmBus devices
- bool vigemResult;
- if (vigemClient != null)
- {
- // Create ViGEmBus devices for each
- if (guitar1DeviceIndex == (int)VigemEnum.DeviceIndex)
- {
- vigemResult = CreateVigemDevice((uint)VigemEnum.Guitar1);
- if (!vigemResult)
- {
- StopCapture();
- return;
- }
- }
-
- if (guitar2DeviceIndex == (int)VigemEnum.DeviceIndex)
- {
- vigemResult = CreateVigemDevice((uint)VigemEnum.Guitar2);
- if (!vigemResult)
- {
- StopCapture();
- return;
- }
- }
-
- if (drumDeviceIndex == (int)VigemEnum.DeviceIndex)
- {
- vigemResult = CreateVigemDevice((uint)VigemEnum.Drum);
- if (!vigemResult)
- {
- StopCapture();
- return;
- }
- }
- }
-
- // Open the device
- pcapCommunicator =
- pcapSelectedDevice.Open(
- 45, // small packets
- PacketDeviceOpenAttributes.Promiscuous | PacketDeviceOpenAttributes.MaximumResponsiveness, // promiscuous mode with maximum speed
- DefaultPacketCaptureTimeoutMilliseconds); // read timeout
-
- // Read data
- pcapCaptureThread = new Thread(ReadContinously);
- pcapCaptureThread.Start();
-
- Console.WriteLine($"Listening on {pcapSelectedDevice.Description}...");
- }
-
- ///
- /// Continously reads packets from the Pcap device.
- ///
- private void ReadContinously()
- {
- // start the capture
- pcapCommunicator.ReceivePackets(0, PacketHandler);
- }
-
- ///
- /// Callback function invoked by Pcap.Net for every incoming packet
- ///
- /// The received packet
- private void PacketHandler(Packet packet)
- {
- // Don't use null packets
- if (packet == null)
- {
- return;
- }
-
- // Analyze guitar packets
- if (GuitarPacketReader.AnalyzePacket(packet.Buffer, ref guitarPacket))
- {
- // Map guitar 1 (if enabled)
- if (guitar1DeviceIndex > 0 && guitar1InstrumentId != 0 && guitar1InstrumentId == guitarPacket.InstrumentID)
- {
- // vJoy
- if (guitar1DeviceIndex < (int)VigemEnum.DeviceIndex && joystick != null)
- {
- if (GuitarPacketVjoyMapper.MapPacket(guitarPacket, joystick, guitar1DeviceIndex, guitar1InstrumentId))
- {
- // Used packet
- processedPacketCount++;
- }
- }
- // ViGEmBus
- else if (guitar1DeviceIndex == (int)VigemEnum.DeviceIndex && vigemClient != null)
- {
- if (GuitarPacketViGEmMapper.MapPacket(guitarPacket, vigemDictionary[(uint)VigemEnum.Guitar1], guitar1InstrumentId))
- {
- // Used packet
- processedPacketCount++;
- }
- }
- }
- // Map guitar 2 (if enabled)
- else if (guitar2DeviceIndex > 0 && guitar2InstrumentId != 0 && guitar2InstrumentId == guitarPacket.InstrumentID)
- {
- // vJoy
- if (guitar2DeviceIndex < (int)VigemEnum.DeviceIndex && joystick != null)
- {
- if (GuitarPacketVjoyMapper.MapPacket(guitarPacket, joystick, guitar2DeviceIndex, guitar2InstrumentId))
- {
- // Used packet
- processedPacketCount++;
- }
- }
- // ViGEmBus
- else if (guitar2DeviceIndex == (int)VigemEnum.DeviceIndex && vigemClient != null)
- {
- if (GuitarPacketViGEmMapper.MapPacket(guitarPacket, vigemDictionary[(uint)VigemEnum.Guitar2], guitar2InstrumentId))
- {
- // Used packet
- processedPacketCount++;
- }
- }
- }
- }
- // Analyze drum packets
- else if (DrumPacketReader.AnalyzePacket(packet.Buffer, ref drumPacket))
- {
- // Map drum (if enabled)
- if (drumDeviceIndex > 0 && drumInstrumentId != 0 && drumInstrumentId == drumPacket.InstrumentID)
- {
- // vJoy
- if (drumDeviceIndex < (int)VigemEnum.DeviceIndex && joystick != null)
- {
- if (DrumPacketVjoyMapper.MapPacket(drumPacket, joystick, drumDeviceIndex, drumInstrumentId))
- {
- // Used packet
- processedPacketCount++;
- }
- }
- // ViGEmBus
- else if (drumDeviceIndex == (int)VigemEnum.DeviceIndex && vigemClient != null)
- {
- if (DrumPacketViGEmMapper.MapPacket(drumPacket, vigemDictionary[(uint)VigemEnum.Drum], drumInstrumentId))
- {
- // Used packet
- processedPacketCount++;
- }
- }
- }
- }
-
- // Debugging (if enabled)
- if (packetDebug)
- {
- string packetHexString = ParsingHelpers.ByteArrayToHexString(packet.Buffer);
- Console.WriteLine(packet.Timestamp.ToString("yyyy-MM-dd hh:mm:ss.fff") + $" [{packet.Length}] " + packetHexString);
- }
-
- // Status reporting (slow)
- if ((processedPacketCount < 10) ||
- ((processedPacketCount < 100) && (processedPacketCount % 10 == 0)) ||
- (processedPacketCount % 100 == 0))
- {
- // Update UI
- uiDispatcher.Invoke(() =>
- {
- string ulongString = processedPacketCount.ToString("N0");
- packetsProcessedCountLabel.Content = ulongString;
- });
- }
- }
-
- ///
- /// Stops packet capture/mapping and resets Pcap/controller objects.
- ///
- private void StopCapture()
- {
- // Stop packet capture
- if (pcapCommunicator != null)
- {
- pcapCommunicator.Break();
- pcapCommunicator = null;
- }
-
- // Stop processing thread
- if (pcapCaptureThread != null)
- {
- pcapCaptureThread.Join();
- pcapCaptureThread = null;
- }
-
- // Release drum device
- if (joystick != null && drumDeviceIndex > 0)
- {
- joystick.RelinquishVJD(drumDeviceIndex);
- }
-
- // Release guitar 1 device
- if (joystick != null && guitar1DeviceIndex > 0)
- {
- joystick.RelinquishVJD(guitar1DeviceIndex);
- }
-
- // Release guitar 2 device
- if (joystick != null && guitar2DeviceIndex > 0)
- {
- joystick.RelinquishVJD(guitar2DeviceIndex);
- }
-
- // Disconnect ViGEmBus controllers
- if (vigemDictionary.Count != 0)
- {
- for (uint i = 0; i < vigemDictionary.Count; i++)
- {
- if (vigemDictionary.ContainsKey(i) && vigemDictionary[i] != null)
- {
- vigemDictionary[i].Disconnect();
- }
- }
- }
- vigemDictionary.Clear();
-
- // Disable packet capture active flag
- packetCaptureActive = false;
-
- // Enable window controls
- pcapDeviceCombo.IsEnabled = true;
- pcapAutoDetectButton.IsEnabled = true;
- pcapRefreshButton.IsEnabled = true;
- packetDebugCheckBox.IsEnabled = true;
-
- guitar1Combo.IsEnabled = true;
- guitar1IdTextBox.IsEnabled = true;
- guitar1IdAutoDetectButton.IsEnabled = true;
-
- guitar2Combo.IsEnabled = true;
- guitar2IdTextBox.IsEnabled = true;
- guitar2IdAutoDetectButton.IsEnabled = true;
-
- drumCombo.IsEnabled = true;
- drumIdTextBox.IsEnabled = true;
- drumIdAutoDetectButton.IsEnabled = true;
-
- controllerRefreshButton.IsEnabled = true;
- startButton.Content = "Start";
-
- // Disable packet count display
- packetsProcessedLabel.Visibility = Visibility.Hidden;
- packetsProcessedCountLabel.Visibility = Visibility.Hidden;
- packetsProcessedCountLabel.Content = string.Empty;
- processedPacketCount = 0;
-
- Console.WriteLine("Stopped capture.");
- }
-
- ///
- /// Handles the click of the Start button.
- ///
- ///
- ///
- private void startButton_Click(object sender, RoutedEventArgs e)
- {
- if (!packetCaptureActive)
- {
- StartCapture();
- }
- else
- {
- StopCapture();
- }
- }
-
- ///
- /// Handles the packet debug checkbox being checked.
- ///
- ///
- ///
- private void packetDebugCheckBox_Checked(object sender, RoutedEventArgs e)
- {
- packetDebug = true;
-
- // Remember selected packet debug state
- Properties.Settings.Default.currentPacketDebugState = "true";
- Properties.Settings.Default.Save();
-
- }
-
- ///
- /// Handles the packet debug checkbox being unchecked.
- ///
- ///
- ///
- private void packetDebugCheckBox_Unchecked(object sender, RoutedEventArgs e)
- {
- packetDebug = false;
-
- // Remember selected packet debug state
- Properties.Settings.Default.currentPacketDebugState = "false";
- Properties.Settings.Default.Save();
- }
-
- ///
- /// Handles the click of the Pcap Refresh button.
- ///
- ///
- ///
- private void pcapRefreshButton_Click(object sender, RoutedEventArgs e)
- {
- // Re-populate dropdown
- PopulatePcapDropdown();
- }
-
- ///
- /// Handles the click of the controller Refresh button.
- ///
- ///
- ///
- private void controllerRefreshButton_Click(object sender, RoutedEventArgs e)
- {
- // Re-populate dropdowns
- PopulateControllerDropdowns();
- }
-
- ///
- /// Handles the guitar 1 instrument ID textbox having its text changed.
- ///
- ///
- ///
- private void guitar1IdTextBox_TextChanged(object sender, TextChangedEventArgs e)
- {
- // Reset assignment
- guitar1InstrumentId = 0;
-
- // Set new ID
- string hexString = guitar1IdTextBox.Text.ToUpperInvariant();
- uint enteredId;
- if (ParsingHelpers.HexStringToUInt32(hexString, out enteredId))
- {
- if (enteredId == 0)
- {
- // Clear ID
- Console.WriteLine("Cleared Hex ID for Guitar 1.");
- hexString = string.Empty;
- }
- else if (enteredId == guitar2InstrumentId)
- {
- // Enforce unique guitar instrument ID
- Console.WriteLine("Guitar 1 ID must be different from Guitar 2 ID.");
- hexString = string.Empty;
- }
- else
- {
- // Set ID
- guitar1InstrumentId = enteredId;
- Console.WriteLine($"Guitar 1 instrument Hex ID set to {hexString}.");
- }
- }
- else if (string.IsNullOrEmpty(hexString))
- {
- // Clear ID
- Console.WriteLine("Cleared Hex ID for Guitar 1.");
- hexString = string.Empty;
- }
- else
- {
- Console.WriteLine("Invalid Hex ID entered for Guitar 1.");
- hexString = string.Empty;
- }
-
- // Update UI
- uiDispatcher.Invoke(() =>
- {
- guitar1IdTextBox.Text = hexString;
- });
-
- // Remember guitar 1 ID
- Properties.Settings.Default.currentGuitar1Id = hexString;
- Properties.Settings.Default.Save();
- }
-
- ///
- /// Handles the guitar 2 instrument ID textbox having its text changed.
- ///
- ///
- ///
- private void guitar2IdTextBox_TextChanged(object sender, TextChangedEventArgs e)
- {
- // Reset assignment
- guitar2InstrumentId = 0;
-
- // Set new ID
- string hexString = guitar2IdTextBox.Text.ToUpperInvariant();
- uint enteredId;
- if (ParsingHelpers.HexStringToUInt32(hexString, out enteredId))
- {
- if (enteredId == 0)
- {
- // Clear ID
- Console.WriteLine("Cleared Hex ID for Guitar 2.");
- hexString = string.Empty;
- }
- else if (enteredId == guitar1InstrumentId)
- {
- // Enforce unique guitar instrument ID
- Console.WriteLine("Guitar 2 ID must be different from Guitar 1 ID.");
- hexString = string.Empty;
- }
- else
- {
- // Set ID
- guitar2InstrumentId = enteredId;
- Console.WriteLine($"Guitar 2 instrument Hex ID set to {hexString}.");
- }
- }
- else if (string.IsNullOrEmpty(hexString))
- {
- // Clear ID
- Console.WriteLine("Cleared Hex ID for Guitar 2.");
- hexString = string.Empty;
- }
- else
- {
- Console.WriteLine("Invalid Hex ID entered for Guitar 2.");
- hexString = string.Empty;
- }
-
- // Update UI
- uiDispatcher.Invoke(() =>
- {
- guitar2IdTextBox.Text = hexString;
- });
-
- // Remember guitar 2 ID
- Properties.Settings.Default.currentGuitar2Id = hexString;
- Properties.Settings.Default.Save();
- }
-
- ///
- /// Handles the drum instrument ID textbox having its text changed.
- ///
- ///
- ///
- private void drumIdTextBox_TextChanged(object sender, TextChangedEventArgs e)
- {
- // Reset assignment
- drumInstrumentId = 0;
-
- // Set new ID
- string hexString = drumIdTextBox.Text.ToUpperInvariant();
- uint enteredId;
- if (ParsingHelpers.HexStringToUInt32(hexString, out enteredId))
- {
- if (enteredId == 0)
- {
- // Clear ID
- Console.WriteLine("Cleared Hex ID for Drum.");
- hexString = string.Empty;
- }
- else
- {
- // Set ID
- drumInstrumentId = enteredId;
- Console.WriteLine($"Drum instrument Hex ID set to {hexString}.");
- }
- }
- else if (string.IsNullOrEmpty(hexString))
- {
- // Clear ID
- Console.WriteLine("Cleared Hex ID for Drum.");
- hexString = string.Empty;
- }
- else
- {
- Console.WriteLine("Invalid Hex ID entered for Drum.");
- hexString = string.Empty;
- }
-
- // Update UI
- uiDispatcher.Invoke(() =>
- {
- drumIdTextBox.Text = hexString;
- });
-
- // Remember drum ID
- Properties.Settings.Default.currentDrumId = hexString;
- Properties.Settings.Default.Save();
- }
-
- ///
- /// Handles the Pcap auto-detect button being clicked.
- ///
- ///
- ///
- private void pcapAutoDetectButton_Click(object sender, RoutedEventArgs e)
- {
- // Prompt user to unplug their receiver
- if (MessageBox.Show("Unplug your receiver, then click OK.", "Auto-Detect Receiver", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
- {
- // Get the list of devices for when receiver is unplugged
- IList notPlugged = null;
- try
- {
- notPlugged = LivePacketDevice.AllLocalMachine;
- }
- catch (InvalidOperationException)
- {
- MessageBox.Show("Could not auto-assign; an error occured.", "Auto-Detect Receiver", MessageBoxButton.OK, MessageBoxImage.Warning);
- return;
- }
-
- // Prompt user to plug in their receiver
- if (MessageBox.Show("Now plug in your receiver, wait a bit for it to register, then click OK.\n(A 1-second delay will be taken after clicking OK to ensure that it registers.)", "Auto-Detect Receiver", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
- {
- // Wait for a moment before getting the new list, seems like clicking OK too quickly after plugging it in makes it not get registered
- Thread.Sleep(1000);
-
- // Get the list of devices for when receiver is plugged in
- IList plugged = null;
- try
- {
- plugged = LivePacketDevice.AllLocalMachine;
- }
- catch (InvalidOperationException)
- {
- MessageBox.Show("Could not auto-assign; an error occured.", "Auto-Detect Receiver", MessageBoxButton.OK, MessageBoxImage.Warning);
- return;
- }
-
- // Check for devices in the new list that aren't in the initial list
- // Have to check names specifically, because doing `notPlugged.Contains(newDevice)`
- // always adds every device in the list, even if it's not new
-
- // Get device names for both not plugged and plugged lists
- List notPluggedNames = new List();
- List pluggedNames = new List();
- foreach (LivePacketDevice oldDevice in notPlugged)
- {
- notPluggedNames.Add(oldDevice.Name);
- }
- foreach (LivePacketDevice newDevice in plugged)
- {
- pluggedNames.Add(newDevice.Name);
- }
-
- // Compare the lists and find what notPlugged doesn't contain
- List newNames = new List();
- foreach (string pluggedName in pluggedNames)
- {
- if (!notPluggedNames.Contains(pluggedName))
- {
- newNames.Add(pluggedName);
- }
- }
-
- // Create a list of new devices based on the list of new device names
- List newDevices = new List();
- foreach (LivePacketDevice newDevice in plugged)
- {
- if (newNames.Contains(newDevice.Name))
- {
- newDevices.Add(newDevice);
- }
- }
-
- // If there's (strictly) one new device, assign it
- if (newDevices.Count == 1)
- {
- // Assign the new device
- pcapSelectedDevice = newDevices.First();
-
- // Remember the new device
- Properties.Settings.Default.currentPcapSelection = pcapSelectedDevice.Name;
- Properties.Settings.Default.Save();
- }
- else
- {
- // If there's more than one, don't auto-assign any of them
- if (newDevices.Count > 1)
- {
- MessageBox.Show("Could not auto-assign; more than one new device was detected.", "Auto-Detect Receiver", MessageBoxButton.OK, MessageBoxImage.Warning);
- }
- // If there's no new ones, don't do anything
- else if (newDevices.Count == 0)
- {
- MessageBox.Show("Could not auto-assign; no new devices were detected.", "Auto-Detect Receiver", MessageBoxButton.OK, MessageBoxImage.Warning);
- }
- }
-
- // Refresh the dropdown
- PopulatePcapDropdown();
- }
- }
- }
-
- ///
- /// Handles the guitar 1 ID auto-detect button being clicked.
- ///
- ///
- ///
- private void guitar1IdAutoDetectButton_Click(object sender, RoutedEventArgs e)
- {
- // Set auto-assign flag
- packetGuitar1AutoAssign = true;
-
- // Auto-detect ID
- AutoDetectID();
- }
-
- ///
- /// Handles the guitar 2 ID auto-detect button being clicked.
- ///
- ///
- ///
- private void guitar2IdAutoDetectButton_Click(object sender, RoutedEventArgs e)
- {
- // Set auto-assign flag
- packetGuitar2AutoAssign = true;
-
- // Auto-detect ID
- AutoDetectID();
- }
-
- ///
- /// Handles the drum ID auto-detect button being clicked.
- ///
- ///
- ///
- private void drumIdAutoDetectButton_Click(object sender, RoutedEventArgs e)
- {
- // Set auto-assign flag
- packetDrumAutoAssign = true;
-
- // Auto-detect ID
- AutoDetectID();
- }
-
- ///
- /// Automatically detects the instrument ID of a given packet.
- ///
- ///
- ///
- private async void AutoDetectID()
- {
- // Disable all controls and show the auto-assign instruction label
- uiDispatcher.Invoke(() =>
- {
- pcapDeviceCombo.IsEnabled = false;
- pcapAutoDetectButton.IsEnabled = false;
- pcapRefreshButton.IsEnabled = false;
- packetDebugCheckBox.IsEnabled = false;
-
- guitar1Combo.IsEnabled = false;
- guitar1IdTextBox.IsEnabled = false;
- guitar1IdAutoDetectButton.IsEnabled = false;
-
- guitar2Combo.IsEnabled = false;
- guitar2IdTextBox.IsEnabled = false;
- guitar2IdAutoDetectButton.IsEnabled = false;
-
- drumCombo.IsEnabled = false;
- drumIdTextBox.IsEnabled = false;
- drumIdAutoDetectButton.IsEnabled = false;
-
- controllerRefreshButton.IsEnabled = false;
- controllerAutoAssignLabel.Visibility = Visibility.Visible;
-
- startButton.IsEnabled = false;
- });
-
- // Await the result of auto-assignment
- bool result = await Task.Run(Read_AutoDetectID);
- if (!result)
- {
- MessageBox.Show("Failed to auto-assign ID.", "Auto-Assign ID", MessageBoxButton.OK, MessageBoxImage.Warning);
- }
-
- // Re-enable all controls and hide the auto-assign instruction label
- uiDispatcher.Invoke(() =>
- {
- pcapDeviceCombo.IsEnabled = true;
- pcapAutoDetectButton.IsEnabled = true;
- pcapRefreshButton.IsEnabled = true;
- packetDebugCheckBox.IsEnabled = true;
-
- guitar1Combo.IsEnabled = true;
- guitar1IdTextBox.IsEnabled = true;
- guitar1IdAutoDetectButton.IsEnabled = true;
-
- guitar2Combo.IsEnabled = true;
- guitar2IdTextBox.IsEnabled = true;
- guitar2IdAutoDetectButton.IsEnabled = true;
-
- drumCombo.IsEnabled = true;
- drumIdTextBox.IsEnabled = true;
- drumIdAutoDetectButton.IsEnabled = true;
-
- controllerRefreshButton.IsEnabled = true;
- controllerAutoAssignLabel.Visibility = Visibility.Hidden;
-
- startButton.IsEnabled = true;
- });
-
- // Disable auto-assignment flags
- packetGuitar1AutoAssign = false;
- packetGuitar2AutoAssign = false;
- packetDrumAutoAssign = false;
- }
-
- ///
- /// Task function for auto-detecting an instrument ID.
- ///
- ///
- ///
- private bool Read_AutoDetectID()
- {
- // Assume failure
- bool result = false;
-
- // Check if a device has been selected
- if (pcapSelectedDevice == null)
- {
- Console.WriteLine("Please select a Pcap device from the Pcap dropdown.");
- return false;
- }
-
- // Retrieve the device list from the local machine
- IList allDevices = LivePacketDevice.AllLocalMachine;
-
- // Check if the device is still present
- bool deviceStillPresent = false;
- foreach(LivePacketDevice device in allDevices)
- {
- if (device.Name == pcapSelectedDevice.Name)
- {
- deviceStillPresent = true;
- break;
- }
- }
-
- if (!deviceStillPresent)
- {
- uiDispatcher.Invoke((Action)(() =>
- {
- // Invalidate selected device
- pcapSelectedDevice = null;
- // Notify user
- Console.WriteLine("Pcap device list has changed and the selected device is no longer present. Please re-select your device from the list and try again.");
- // Force a refresh
- PopulatePcapDropdown();
- }));
- return false;
- }
-
- // Open the device
- pcapCommunicator =
- pcapSelectedDevice.Open(
- 45, // small packets
- PacketDeviceOpenAttributes.Promiscuous | PacketDeviceOpenAttributes.MaximumResponsiveness, // promiscuous mode with maximum speed
- DefaultPacketCaptureTimeoutMilliseconds // read timeout
- );
-
- // Receive packet
- Packet packet = null;
- int attempts = 6;
- while (attempts > 0)
- {
- pcapCommunicator.ReceivePacket(out packet);
- if (packet != null)
- {
- break;
- }
-
- // Short pause before retry
- Thread.Sleep(333);
- attempts--;
- }
-
- // Process if we got a packet
- if (packet != null)
- {
- // Debugging (if enabled)
- if (packetDebug)
- {
- string packetHexString = ParsingHelpers.ByteArrayToHexString(packet.Buffer);
- Console.WriteLine(packet.Timestamp.ToString("yyyy-MM-dd hh:mm:ss.fff") + $" [{packet.Length}] " + packetHexString);
- }
-
- // Get ID from packet as Hex string
- string idString = null;
- if (packet.Length == 40 || packet.Length == 36)
- {
- // String representation: AA BB CC DD
- uint id = (uint)(
- packet[15] | // DD
- (packet[14] << 8) | // CC
- (packet[13] << 16) | // BB
- (packet[12] << 24) // AA
- );
-
- idString = Convert.ToString(id, 16).ToUpperInvariant();
- }
-
- // Check assignment flags and packet length
- if (packetGuitar1AutoAssign && packet.Length == 40)
- {
- // Update UI (assigns instrument ID)
- uiDispatcher.Invoke((Action)(() =>
- {
- guitar1IdTextBox.Text = idString;
- }));
-
- result = true;
- }
- else if (packetGuitar2AutoAssign && packet.Length == 40)
- {
- // Update UI (assigns instrument ID)
- uiDispatcher.Invoke((Action)(() =>
- {
- guitar2IdTextBox.Text = idString;
- }));
-
- result = true;
- }
- else if (packetDrumAutoAssign && packet.Length == 36)
- {
- // Update UI (assigns instrument ID)
- uiDispatcher.Invoke((Action)(() =>
- {
- drumIdTextBox.Text = idString;
- }));
-
- result = true;
- }
- }
-
- // Stop packet reading
- pcapCommunicator.Break();
- pcapCommunicator = null;
-
- return result;
- }
- }
-}
diff --git a/MainWindow/ParsingHelpers.cs b/MainWindow/ParsingHelpers.cs
deleted file mode 100644
index a2063f5..0000000
--- a/MainWindow/ParsingHelpers.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace RB4InstrumentMapper
-{
- public static class ParsingHelpers
- {
- ///
- /// Converts a hex string representing a 32-bit integer into a byte array.
- ///
- /// The string to be converted.
- /// A byte array converted from the hex string, or null if the conversion failed.
- public static byte[] Int32HexStringToByteArray(string hexString)
- {
- byte[] byteArray = null;
- uint number;
- if (uint.TryParse(hexString, NumberStyles.AllowHexSpecifier, NumberFormatInfo.CurrentInfo, out number))
- {
- byteArray = BitConverter.GetBytes(number);
- }
-
- return byteArray;
- }
-
- ///
- /// Converts a byte array into a hex string.
- ///
- /// The byte array to converted.
- /// A hex string representing the byte array, or null if input is null or empty.
- public static string ByteArrayToHexString(byte[] byteArray)
- {
- string hexString = null;
- if (byteArray != null && byteArray.Length > 0)
- {
- hexString = BitConverter.ToString(byteArray).Replace("-", string.Empty);
- }
-
- return hexString;
- }
-
- ///
- /// Converts a string representing a 32-bit hexadecimal number into a 32-bit unsigned integer.
- ///
- /// The string to be converted.
- /// The converted number.
- /// True if the conversion was successful, or false if it failed.
- public static bool HexStringToUInt32(string hexString, out uint number)
- {
- if (hexString.StartsWith("0x") || hexString.StartsWith("&h"))
- {
- hexString = hexString.Remove(0, 2);
- }
-
- return uint.TryParse(hexString, NumberStyles.HexNumber, NumberFormatInfo.CurrentInfo, out number);
- }
-
- ///
- /// Converts a 32-bit unsigned integer into a string representing a 32-bit hexadecimal number.
- ///
- /// The number to be converted.
- /// A flag indicating if this is an instrument ID.
- /// A string representing the input number, or String.Empty if the input is 0 and isID is set.
- public static string UInt32ToHexString(uint number, bool isID)
- {
- if (isID)
- {
- return number == 0 ? String.Empty : Convert.ToString(number, 16);
- }
- else
- {
- return Convert.ToString(number, 16);
- }
- }
- }
-}
diff --git a/MainWindow/TextBoxConsole.cs b/MainWindow/TextBoxConsole.cs
deleted file mode 100644
index a7f0bcf..0000000
--- a/MainWindow/TextBoxConsole.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-using System;
-using System.IO;
-using System.Text;
-using System.Windows.Controls;
-using System.Linq;
-
-// Adds a Debug/Output Console to WPF application
-// - XAML:
-// - MainWindow(): TextBoxOutputter.RedirectConsoleToTextBox(messageConsole);
-
-namespace RB4InstrumentMapper
-{
- ///
- /// Adds a Debug/Output Console to WPF application
- ///
- ///
- /// https://social.technet.microsoft.com/wiki/contents/articles/12347.wpfhowto-add-a-debugoutput-console-to-your-application.aspx
- ///
- public class TextBoxConsole : TextWriter
- {
- ///
- /// Default maximum number of lines to keep in console.
- ///
- private const int DefaultMaxNumberLines = 100;
-
- ///
- /// Line split characters.
- ///
- private static char[] newlineChars = Environment.NewLine.ToCharArray();
-
- ///
- /// Cache for current line.
- ///
- private static StringBuilder currentLineCache = null;
-
- ///
- /// Cache of the visible text.
- ///
- private static FixedSizeConcurrentQueue visibleTextCache = null;
-
- ///
- /// Text box handle that displays text.
- ///
- private TextBox textBox = null;
-
- ///
- /// Display lines in reverse order (newest first) if set. Defaults to false.
- ///
- private bool displayLinesInReverseOrder = false;
-
- ///
- /// Display timestamp for each line. Defaults to true.
- ///
- private bool displayLinesWithTimestamp = true;
-
- ///
- /// Connects console output to a given text box.
- ///
- /// Text box to receive console output.
- /// Maximum number of lines to display in the text box. Defaults to 100.
- /// Display lines in reverse order (newest first). Defaults to true.
- /// Display timestamp for each line. Defaults to true.
- public static void RedirectConsoleToTextBox(
- TextBox textBox,
- int maxNumberOfLines = DefaultMaxNumberLines,
- bool displayLinesInReverseOrder = false,
- bool displayLinesWithTimestamp = true
- )
- {
- TextBoxConsole textBoxOutputter = new TextBoxConsole(textBox, displayLinesInReverseOrder, displayLinesWithTimestamp);
- Console.SetOut(textBoxOutputter);
- currentLineCache = new StringBuilder();
- visibleTextCache = new FixedSizeConcurrentQueue(maxNumberOfLines);
- }
-
- ///
- /// Creates a TextBoxOutputter instance.
- ///
- /// Target text box
- /// Reverse text.
- public TextBoxConsole(TextBox output, bool reverse = false, bool timestamp = true)
- {
- textBox = output;
- displayLinesInReverseOrder = reverse;
- displayLinesWithTimestamp = timestamp;
- }
-
- ///
- /// Write text to outputter.
- ///
- ///
- public override void Write(char value)
- {
- base.Write(value);
- if (textBox != null &&
- currentLineCache != null &&
- visibleTextCache != null)
- {
- textBox.Dispatcher.BeginInvoke(new Action(() =>
- {
- if (newlineChars.Contains(value))
- {
- // Newline
- if (currentLineCache.Length > 0)
- {
- // Store line in text cache and flush it
- string newText = (displayLinesWithTimestamp) ? "[" + DateTime.Now.ToString("s") + "] " : string.Empty;
- newText += currentLineCache.ToString();
- visibleTextCache.Enqueue(newText);
- currentLineCache.Clear();
-
- // Display text cache
- if (displayLinesInReverseOrder)
- {
- textBox.Text = string.Join(Environment.NewLine, visibleTextCache.ToArray().Reverse());
- }
- else
- {
- textBox.Text = string.Join(Environment.NewLine, visibleTextCache.ToArray());
- }
- }
- }
- else
- {
- // Collect characters in line
- currentLineCache.Append(value);
- }
- }));
- }
- }
-
- ///
- /// Gets the encoding of the outputter.
- ///
- public override Encoding Encoding
- {
- get { return System.Text.Encoding.UTF8; }
- }
- }
-}
diff --git a/PacketParsing/DrumPacket.cs b/PacketParsing/DrumPacket.cs
deleted file mode 100644
index f5735b8..0000000
--- a/PacketParsing/DrumPacket.cs
+++ /dev/null
@@ -1,194 +0,0 @@
-using System;
-
-namespace RB4InstrumentMapper
-{
- ///
- /// Data for a drumkit packet.
- ///
- public struct DrumPacket
- {
- public uint InstrumentID;
-
- public bool MenuButton;
- public bool OptionsButton;
- public bool XboxButton;
-
- public bool DpadUp;
- public bool DpadDown;
- public bool DpadLeft;
- public bool DpadRight;
-
- public bool RedDrum;
- public bool YellowDrum;
- public bool BlueDrum;
- public bool GreenDrum;
-
- public bool YellowCymbal;
- public bool BlueCymbal;
- public bool GreenCymbal;
-
- public bool BassOne;
- public bool BassTwo;
- }
-
- ///
- /// Functionality to analyze drumkit packets into a DrumPacket struct.
- ///
- public class DrumPacketReader
- {
- ///
- /// Packet definitions for the drums/cymbals/kicks.
- ///
- public enum Drums : byte
- {
- RedDrum = 0x20,
- YellowDrum = 0x0F,
- BlueDrum = 0xF0,
- GreenDrum = 0x10,
-
- YellowCymbal = 0xF0,
- BlueCymbal = 0x0F,
- GreenCymbal = 0xF0,
-
- BassOne = 0x10,
- BassTwo = 0x20,
- }
-
- ///
- /// Packet definitions for the face buttons.
- ///
- [Flags]
- public enum Buttons : byte
- {
- Xbox = 0x01,
- Menu = 0x04,
- Options = 0x08,
- }
-
- ///
- /// Packet definitions for the Dpad.
- ///
- [Flags]
- public enum Dpad : byte
- {
- Up = 0x01,
- Down = 0x02,
- Left = 0x04,
- Right = 0x08,
- }
-
- ///
- /// Size of drumkit packets.
- ///
- private const int DrumPacketLength = 36;
-
- ///
- /// Size of the packet header.
- ///
- private const int XboxHeaderLength = 22;
-
- ///
- /// Position in the packet from the header.
- ///
- public enum PacketPosition : int
- {
- RedGreenDrum = 8,
- YellowDrum = 10,
- BlueDrum = 11,
- YellowBlueCymbal = 12,
- GreenCymbal = 13,
- BassPedal = 9,
- Buttons = 8,
- Dpad = 9,
- }
-
- ///
- /// Analyzes a packet and assigns its data to a DrumPacket struct.
- ///
- /// The data packet to be analyzed.
- /// A returned DrumPacket.
- /// True if packet was used and analyzed, false otherwise.
- public static bool AnalyzePacket(byte[] packet, ref DrumPacket data)
- {
- if (packet != null && packet.Length == DrumPacketLength)
- {
- // Assign instrument ID
- // String representation: AA BB CC DD
- data.InstrumentID = (uint)(
- packet[15] | // DD
- (packet[14] << 8) | // CC
- (packet[13] << 16) | // BB
- (packet[12] << 24) // AA
- );
-
- // Map buttons
- byte buttons = packet[XboxHeaderLength + (int)PacketPosition.Buttons];
-
- // Menu
- data.MenuButton = (buttons & (byte)Buttons.Menu) != 0;
-
- // Options
- data.OptionsButton = (buttons & (byte)Buttons.Options) != 0;
-
- // Xbox
- data.XboxButton = (buttons & (byte)Buttons.Xbox) != 0;
-
- // Map Dpad
- byte dpad = packet[XboxHeaderLength + (int)PacketPosition.Dpad];
-
- // Dpad Up
- data.DpadUp = (dpad & (byte)Dpad.Up) != 0;
-
- // Dpad Down
- data.DpadDown = (dpad & (byte)Dpad.Down) != 0;
-
- // Dpad Left
- data.DpadLeft = (dpad & (byte)Dpad.Left) != 0;
-
- // Dpad Right
- data.DpadRight = (dpad & (byte)Dpad.Right) != 0;
-
- // Map drums
- byte redGreenDrum = packet[XboxHeaderLength + (int)PacketPosition.RedGreenDrum];
- byte yellowDrum = packet[XboxHeaderLength + (int)PacketPosition.YellowDrum];
- byte blueDrum = packet[XboxHeaderLength + (int)PacketPosition.BlueDrum];
- byte bassDrum = packet[XboxHeaderLength + (int)PacketPosition.BassPedal];
-
- // Red drum
- data.RedDrum = (redGreenDrum & (byte)Drums.RedDrum) != 0;
-
- // Yellow drum
- data.YellowDrum = (yellowDrum & (byte)Drums.YellowDrum) != 0;
-
- // Blue drum
- data.BlueDrum = (blueDrum & (byte)Drums.BlueDrum) != 0;
-
- // Green drum
- data.GreenDrum = (redGreenDrum & (byte)Drums.GreenDrum) != 0;
-
- // Bass drums
- data.BassOne = (bassDrum & (byte)Drums.BassOne) != 0;
- data.BassTwo = (bassDrum & (byte)Drums.BassTwo) != 0;
-
- // Map cymbals
- byte yellowBlueCymbal = packet[XboxHeaderLength + (int)PacketPosition.YellowBlueCymbal];
- byte greenCymbal = packet[XboxHeaderLength + (int)PacketPosition.GreenCymbal];
-
- // Yellow cymbal
- data.YellowCymbal = (yellowBlueCymbal & (byte)Drums.YellowCymbal) != 0;
-
- // Blue cymbal
- data.BlueCymbal = (yellowBlueCymbal & (byte)Drums.BlueCymbal) != 0;
-
- // Green cymbal
- data.GreenCymbal = (greenCymbal & (byte)Drums.GreenCymbal) != 0;
-
- // Packet handled
- return true;
- }
-
- // Packet ignored
- return false;
- }
- }
-}
diff --git a/PacketParsing/DrumPacketVjoyMapper.cs b/PacketParsing/DrumPacketVjoyMapper.cs
deleted file mode 100644
index 451b22b..0000000
--- a/PacketParsing/DrumPacketVjoyMapper.cs
+++ /dev/null
@@ -1,194 +0,0 @@
-using System;
-using vJoyInterfaceWrap;
-
-namespace RB4InstrumentMapper
-{
- ///
- /// Functionality to map analyzed drumkit packets to a vJoy device.
- ///
- public class DrumPacketVjoyMapper
- {
- ///
- /// The vJoy device state.
- ///
- private static vJoy.JoystickState iReport;
-
- [Flags]
- ///
- /// Button flag definitions.
- ///
- private enum Buttons : uint
- {
- One = (uint)1 << 0,
- Two = (uint)1 << 1,
- Three = (uint)1 << 2,
- Four = (uint)1 << 3,
- Five = (uint)1 << 4,
- Six = (uint)1 << 5,
- Seven = (uint)1 << 6,
- Eight = (uint)1 << 7,
- Nine = (uint)1 << 8,
- Ten = (uint)1 << 9,
- Eleven = (uint)1 << 10,
- Twelve = (uint)1 << 11,
- Thirteen = (uint)1 << 12,
- Fourteen = (uint)1 << 13,
- Fifteen = (uint)1 << 14,
- Sixteen = (uint)1 << 15
- }
-
- ///
- /// Maps a DrumPacket to a vJoy device.
- ///
- /// The pre-analyzed data packet to map.
- /// The vJoy client to use.
- /// The vJoy device ID to use.
- /// The ID of the instrument being mapped.
- /// True if packet was used and converted, false otherwise.
- public static bool MapPacket(DrumPacket packet, vJoy vjoyClient, uint joystickDeviceIndex, uint instrumentId)
- {
- // Ensure instrument ID is assigned
- if(instrumentId == 0)
- {
- return false;
- }
-
- // Match instrument ID
- if (instrumentId != packet.InstrumentID)
- {
- return false;
- }
-
- // Reset report and assign device index
- iReport.Buttons = 0;
- iReport.bDevice = (byte)joystickDeviceIndex;
-
- // Face buttons
- // Menu
- if (packet.MenuButton)
- {
- iReport.Buttons |= (uint)Buttons.Fifteen;
- }
-
- // Options
- if (packet.OptionsButton)
- {
- iReport.Buttons |= (uint)Buttons.Sixteen;
- }
-
- // Xbox - not mapped
-
- // D-pad to POV
- // Ranges from 0 to 35999 (measured in 1/100 of a degree), clockwise, top 0
- if (packet.DpadUp)
- {
- if (packet.DpadLeft)
- {
- iReport.bHats = 31500;
- }
- else if (packet.DpadRight)
- {
- iReport.bHats = 4500;
- }
- else
- {
- iReport.bHats = 0;
- }
- }
- else if (packet.DpadDown)
- {
- if (packet.DpadLeft)
- {
- iReport.bHats = 22500;
- }
- else if (packet.DpadRight)
- {
- iReport.bHats = 13500;
- }
- else
- {
- iReport.bHats = 18000;
- }
- }
- else
- {
- if (packet.DpadLeft)
- {
- iReport.bHats = 27000;
- }
- else if (packet.DpadRight)
- {
- iReport.bHats = 9000;
- }
- else
- {
- // Set the PoV hat to neutral
- iReport.bHats = 0xFFFFFFFF;
- }
- }
-
- // Drums
- // Red drum
- if (packet.RedDrum)
- {
- iReport.Buttons |= (uint)Buttons.One;
- }
-
- // Yellow drum
- if (packet.YellowDrum)
- {
- iReport.Buttons |= (uint)Buttons.Two;
- }
-
- // Blue drum
- if (packet.BlueDrum)
- {
- iReport.Buttons |= (uint)Buttons.Three;
- }
-
- // Green drum
- if (packet.GreenDrum)
- {
- iReport.Buttons |= (uint)Buttons.Four;
- }
-
- // Bass 1
- if (packet.BassOne)
- {
- iReport.Buttons |= (uint)Buttons.Five;
- }
-
- // Bass 2
- if (packet.BassTwo)
- {
- iReport.Buttons |= (uint)Buttons.Nine;
- }
-
-
- // Cymbals
- // Yellow cymbal
- if (packet.YellowCymbal)
- {
- iReport.Buttons |= (uint)Buttons.Six;
- }
-
- // Blue cymbal
- if (packet.BlueCymbal)
- {
- iReport.Buttons |= (uint)Buttons.Seven;
- }
-
- // Green cymbal
- if (packet.GreenCymbal)
- {
- iReport.Buttons |= (uint)Buttons.Eight;
- }
-
- // Send data
- vjoyClient.UpdateVJD(joystickDeviceIndex, ref iReport);
-
- // Packet handled
- return true;
- }
- }
-}
diff --git a/PacketParsing/DrumViGEmMapper.cs b/PacketParsing/DrumViGEmMapper.cs
deleted file mode 100644
index 9eeb187..0000000
--- a/PacketParsing/DrumViGEmMapper.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-using Nefarius.ViGEm.Client.Targets;
-using Nefarius.ViGEm.Client.Targets.Xbox360;
-
-namespace RB4InstrumentMapper
-{
- ///
- /// Functionality to map analyzed drumkit packets to a ViGEmBus device.
- ///
- public class DrumPacketViGEmMapper
- {
- ///
- /// Maps a DrumPacket to a ViGEmBus Xbox 360 controller.
- ///
- /// The pre-analyzed data packet.
- /// The ViGEmBus device to map to.
- /// The instrument ID.
- /// True if packet was mapped, false otherwise.
- public static bool MapPacket(DrumPacket packet, IXbox360Controller vigemDevice, uint instrumentId)
- {
- // Ensure instrument ID is assigned
- if(instrumentId == 0)
- {
- return false;
- }
-
- // Match instrument ID
- if (instrumentId != packet.InstrumentID)
- {
- return false;
- }
-
- // Don't auto-submit input reports for performance optimization
- if (vigemDevice.AutoSubmitReport)
- {
- vigemDevice.AutoSubmitReport = false;
- }
-
- // Reset report
- vigemDevice.ResetReport();
-
- // Menu
- vigemDevice.SetButtonState(Xbox360Button.Start,
- packet.MenuButton);
- // Options
- vigemDevice.SetButtonState(Xbox360Button.Back,
- packet.OptionsButton);
- // Xbox
- vigemDevice.SetButtonState(Xbox360Button.Guide,
- packet.XboxButton);
-
- // Dpad Up
- vigemDevice.SetButtonState(Xbox360Button.Up,
- packet.DpadUp);
- // Dpad Down
- vigemDevice.SetButtonState(Xbox360Button.Down,
- packet.DpadDown);
- // Dpad Left
- vigemDevice.SetButtonState(Xbox360Button.Left,
- packet.DpadLeft);
- // Dpad Right
- vigemDevice.SetButtonState(Xbox360Button.Right,
- packet.DpadRight);
-
- // Red
- vigemDevice.SetButtonState(Xbox360Button.B,
- packet.RedDrum);
- // Yellow
- vigemDevice.SetButtonState(Xbox360Button.Y,
- packet.YellowDrum ||
- packet.YellowCymbal);
- // Blue
- vigemDevice.SetButtonState(Xbox360Button.X,
- packet.BlueDrum ||
- packet.BlueCymbal);
- // Green
- vigemDevice.SetButtonState(Xbox360Button.A,
- packet.GreenDrum ||
- packet.GreenCymbal);
-
- // Pad Flag
- vigemDevice.SetButtonState(Xbox360Button.RightThumb,
- packet.RedDrum ||
- packet.YellowDrum ||
- packet.BlueDrum ||
- packet.GreenDrum);
- // Cymbal Flag
- vigemDevice.SetButtonState(Xbox360Button.RightShoulder,
- packet.YellowCymbal ||
- packet.BlueCymbal ||
- packet.GreenCymbal);
-
- // Bass One
- vigemDevice.SetButtonState(Xbox360Button.LeftShoulder,
- packet.BassOne);
- // Bass Two
- vigemDevice.SetButtonState(Xbox360Button.LeftThumb,
- packet.BassTwo);
-
- // Pad/cymbal velocities, for when those get researched
- /*
- // Red velocity
- vigemDevice.SetAxisValue(Xbox360Axis.LeftThumbX,
- packet.RedVelocity != 0 ? (short)((256 - packet.RedVelocity) * 128) : 0);
- // if packet velocity is not 0,
- // return the velocity inverted (i.e. 0 = hardest hit, 255 = softest)
- // and scaled to the positive half of a short
- // Yellow velocity
- vigemDevice.SetAxisValue(Xbox360Axis.LeftThumbY,
- packet.YellowVelocity != 0 ? (short)((256 - packet.YellowVelocity) * -128) : 0);
- // if packet velocity is not 0,
- // return the velocity inverted (i.e. 0 = hardest hit, 255 = softest)
- // and scaled to the negative half of a short
- // Blue velocity
- vigemDevice.SetAxisValue(Xbox360Axis.LeftThumbX,
- packet.BlueVelocity != 0 ? (short)((256 - packet.BlueVelocity) * 128) : 0);
- // same as Red
- // Green velocity
- vigemDevice.SetAxisValue(Xbox360Axis.LeftThumbX,
- packet.GreenVelocity != 0 ? (short)((256 - packet.GreenVelocity) * -128) : 0);
- // same as Yellow
- */
-
- // Send data
- vigemDevice.SubmitReport();
-
- // Packet handled
- return true;
- }
- }
-}
diff --git a/PacketParsing/GuitarPacket.cs b/PacketParsing/GuitarPacket.cs
deleted file mode 100644
index 219c274..0000000
--- a/PacketParsing/GuitarPacket.cs
+++ /dev/null
@@ -1,192 +0,0 @@
-using System;
-
-namespace RB4InstrumentMapper
-{
- ///
- /// Data for a guitar packet.
- ///
- public struct GuitarPacket
- {
- public uint InstrumentID;
- public string InstrumentIDString;
-
- public bool MenuButton;
- public bool OptionsButton;
- public bool XboxButton;
-
- public bool DpadUp;
- public bool DpadDown;
- public bool DpadLeft;
- public bool DpadRight;
-
- public bool UpperGreen;
- public bool UpperRed;
- public bool UpperYellow;
- public bool UpperBlue;
- public bool UpperOrange;
-
- public bool LowerGreen;
- public bool LowerRed;
- public bool LowerYellow;
- public bool LowerBlue;
- public bool LowerOrange;
-
- public byte PickupSwitch;
- public byte WhammyBar;
- public byte Tilt;
- }
-
- ///
- /// Functionality to analyze guitar packets into a GuitarPacket struct.
- ///
- public class GuitarPacketReader
- {
- ///
- /// Packet definitions for the frets.
- ///
- [Flags]
- public enum Frets : byte
- {
- Green = 0x01,
- Red = 0x02,
- Yellow = 0x04,
- Blue = 0x08,
- Orange = 0x10,
- }
-
- ///
- /// Packet definitions for the buttons.
- ///
- [Flags]
- public enum Buttons : byte
- {
- Xbox = 0x01,
- Menu = 0x04,
- Options = 0x08,
- }
-
- ///
- /// Packet definitions for the Dpad.
- ///
- [Flags]
- public enum Dpad : byte
- {
- Down = 0x01, // up/down inverted to match usage geometry
- Up = 0x02,
- Left = 0x04,
- Right = 0x08,
- }
-
- ///
- /// Size of guitar packets.
- ///
- private const int GuitarPacketLength = 40;
-
- ///
- /// Size of the packet header.
- ///
- private const int XboxHeaderLength = 22;
-
- ///
- /// Position in the packet from the header.
- ///
- public enum PacketPosition : int
- {
- Buttons = 8,
- Dpad = 9,
- Tilt = 10,
- Whammy = 11,
- Slider = 12,
- UpperFret = 13,
- LowerFret = 14,
- }
-
- ///
- /// Analyzes a packet and assigns its data to a GuitarPacket struct.
- ///
- /// The data packet to use.
- /// A returned GuitarPacket.
- /// True if packet was used and analyzed, false otherwise.
- public static bool AnalyzePacket(byte[] packet, ref GuitarPacket data)
- {
- // Check packet
- if (packet != null && packet.Length == GuitarPacketLength)
- {
- // Assign instrument ID
- // String representation: AA BB CC DD
- data.InstrumentID = (uint)(
- packet[15] | // DD
- (packet[14] << 8) | // CC
- (packet[13] << 16) | // BB
- (packet[12] << 24) // AA
- );
-
- // Map buttons
- byte buttons = packet[XboxHeaderLength + (int)PacketPosition.Buttons];
-
- // Menu
- data.MenuButton = (buttons & (byte)Buttons.Menu) != 0;
-
- // Options
- data.OptionsButton = (buttons & (byte)Buttons.Options) != 0;
-
- // Xbox
- data.XboxButton = (buttons & (byte)Buttons.Xbox) != 0;
-
- // Map Dpad
- byte dpad = packet[XboxHeaderLength + (int)PacketPosition.Dpad];
-
- // Dpad Up
- data.DpadUp = (dpad & (byte)Dpad.Up) != 0;
-
- // Dpad Down
- data.DpadDown = (dpad & (byte)Dpad.Down) != 0;
-
- // Dpad Left
- data.DpadLeft = (dpad & (byte)Dpad.Left) != 0;
-
- // Dpad Right
- data.DpadRight = (dpad & (byte)Dpad.Right) != 0;
-
- // Frets
- byte upperFret = packet[XboxHeaderLength + (int)PacketPosition.UpperFret];
- byte lowerFret = packet[XboxHeaderLength + (int)PacketPosition.LowerFret];
-
- // Fret Green
- data.UpperGreen = (upperFret & (byte)Frets.Green) != 0;
- data.LowerGreen = (lowerFret & (byte)Frets.Green) != 0;
-
- // Fret Red
- data.UpperRed = (upperFret & (byte)Frets.Red) != 0;
- data.LowerRed = (lowerFret & (byte)Frets.Red) != 0;
-
- // Fret Yellow
- data.UpperYellow = (upperFret & (byte)Frets.Yellow) != 0;
- data.LowerYellow = (lowerFret & (byte)Frets.Yellow) != 0;
-
- // Fret Blue
- data.UpperBlue = (upperFret & (byte)Frets.Blue) != 0;
- data.LowerBlue = (lowerFret & (byte)Frets.Blue) != 0;
-
- // Fret Orange
- data.UpperOrange = (upperFret & (byte)Frets.Orange) != 0;
- data.LowerOrange = (lowerFret & (byte)Frets.Orange) != 0;
-
- // Pickup Switch
- data.PickupSwitch = packet[XboxHeaderLength + (int)PacketPosition.Slider];
-
- // Whammy Bar
- data.WhammyBar = packet[XboxHeaderLength + (int)PacketPosition.Whammy];
-
- // Tilt
- data.Tilt = packet[XboxHeaderLength + (int)PacketPosition.Tilt];
-
- // Packet handled
- return true;
- }
-
- // Packet ignored
- return false;
- }
- }
-}
diff --git a/PacketParsing/GuitarPacketVjoyMapper.cs b/PacketParsing/GuitarPacketVjoyMapper.cs
deleted file mode 100644
index d82ca52..0000000
--- a/PacketParsing/GuitarPacketVjoyMapper.cs
+++ /dev/null
@@ -1,186 +0,0 @@
-using System;
-using vJoyInterfaceWrap;
-
-namespace RB4InstrumentMapper
-{
- ///
- /// Functionality to map analyzed guitar packets to a vJoy device.
- ///
- public class GuitarPacketVjoyMapper
- {
- ///
- /// The vJoy device state.
- ///
- private static vJoy.JoystickState iReport;
-
- [Flags]
- ///
- /// Button flag definitions.
- ///
- private enum Buttons : uint
- {
- One = (uint)1 << 0,
- Two = (uint)1 << 1,
- Three = (uint)1 << 2,
- Four = (uint)1 << 3,
- Five = (uint)1 << 4,
- Six = (uint)1 << 5,
- Seven = (uint)1 << 6,
- Eight = (uint)1 << 7,
- Nine = (uint)1 << 8,
- Ten = (uint)1 << 9,
- Eleven = (uint)1 << 10,
- Twelve = (uint)1 << 11,
- Thirteen = (uint)1 << 12,
- Fourteen = (uint)1 << 13,
- Fifteen = (uint)1 << 14,
- Sixteen = (uint)1 << 15
- }
-
- ///
- /// Maps a GuitarPacket to a vJoy device.
- ///
- /// The pre-analyzed data packet to map.
- /// The vJoy client to use.
- /// The vJoy device ID to map to.
- /// The ID of the instrument being mapped.
- /// True if packet was used and converted, false otherwise.
- public static bool MapPacket(GuitarPacket packet, vJoy vjoyClient, uint joystickDeviceIndex, uint instrumentId)
- {
- // Ensure instrument ID is assigned
- if (instrumentId == 0)
- {
- return false;
- }
-
- // Match instrument ID
- if (instrumentId != packet.InstrumentID)
- {
- return false;
- }
-
- // Reset report and assign device index
- iReport.Buttons = 0;
- iReport.bDevice = (byte)joystickDeviceIndex;
-
- // Face buttons
- // Menu
- if (packet.MenuButton)
- {
- iReport.Buttons |= (uint)Buttons.Fifteen;
- }
-
- // Options
- if (packet.OptionsButton)
- {
- iReport.Buttons |= (uint)Buttons.Sixteen;
- }
-
- // Xbox - not mapped
- // Ranges from 0 to 35999 (measured in 1/100 of a degree), clockwise, top 0
-
- // D-pad to POV
- if (packet.DpadUp)
- {
- if (packet.DpadLeft)
- {
- iReport.bHats = 31500;
- }
- else if (packet.DpadRight)
- {
- iReport.bHats = 4500;
- }
- else
- {
- iReport.bHats = 0;
- }
- }
- else if (packet.DpadDown)
- {
- if (packet.DpadLeft)
- {
- iReport.bHats = 22500;
- }
- else if (packet.DpadRight)
- {
- iReport.bHats = 13500;
- }
- else
- {
- iReport.bHats = 18000;
- }
- }
- else
- {
- if (packet.DpadLeft)
- {
- iReport.bHats = 27000;
- }
- else if (packet.DpadRight)
- {
- iReport.bHats = 9000;
- }
- else
- {
- // Set the PoV hat to neutral
- iReport.bHats = 0xFFFFFFFF;
- }
- }
-
- // Frets
- // Fret Green
- if (packet.UpperGreen || packet.LowerGreen)
- {
- iReport.Buttons |= (uint)Buttons.One;
- }
- // Fret Red
- if (packet.UpperRed || packet.LowerRed)
- {
- iReport.Buttons |= (uint)Buttons.Two;
- }
- // Fret Yellow
- if (packet.UpperYellow || packet.LowerYellow)
- {
- iReport.Buttons |= (uint)Buttons.Three;
- }
- // Fret Blue
- if (packet.UpperBlue || packet.LowerBlue)
- {
- iReport.Buttons |= (uint)Buttons.Four;
- }
- // Fret Orange
- if (packet.UpperOrange || packet.LowerOrange)
- {
- iReport.Buttons |= (uint)Buttons.Five;
- }
-
- // Axes
-
- // vJoy axis range is 0x0...0x7FFF(0...32767), 50 % = 0x4000(16384).
-
- // Map pickup switch to X-axis
- // input is 0, 16, 32, 48, and 64.
- int xAxis = packet.PickupSwitch;
- xAxis *= (32768 / 64);
- iReport.AxisX = xAxis;
-
- // Map whammy to Y-axis
- // input ranges from 0 (default) to 255 (depressed)
- int yAxis = packet.WhammyBar;
- yAxis *= (32768 / 256);
- iReport.AxisY = yAxis;
-
- // Map tilt to Z-axis
- // input ranges from 0 (horizontal) to 255 (vertical)
- int zAxis = packet.Tilt;
- zAxis *= (32768 / 256);
- iReport.AxisZ = zAxis;
-
- // Send data
- vjoyClient.UpdateVJD(joystickDeviceIndex, ref iReport);
-
- // Packet handled
- return true;
- }
- }
-}
diff --git a/PacketParsing/GuitarViGEmMapper.cs b/PacketParsing/GuitarViGEmMapper.cs
deleted file mode 100644
index 487f355..0000000
--- a/PacketParsing/GuitarViGEmMapper.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-using Nefarius.ViGEm.Client.Targets;
-using Nefarius.ViGEm.Client.Targets.Xbox360;
-
-namespace RB4InstrumentMapper
-{
- ///
- /// Functionality to map analyzed guitar packets to a ViGEmBus device.
- ///
- public class GuitarPacketViGEmMapper
- {
- ///
- /// Maps a GuitarPacket to a ViGEmBus Xbox 360 controller.
- ///
- /// The pre-analyzed data packet.
- /// The ViGEmBus device to map to.
- /// The instrument ID.
- /// True if packet was mapped, false otherwise.
- public static bool MapPacket(GuitarPacket packet, IXbox360Controller vigemDevice, uint instrumentId)
- {
- // Ensure instrument ID is assigned
- if(instrumentId == 0)
- {
- return false;
- }
-
- // Match instrument ID
- if (instrumentId != packet.InstrumentID)
- {
- return false;
- }
-
- // Don't auto-submit input reports for performance optimization
- if (vigemDevice.AutoSubmitReport)
- {
- vigemDevice.AutoSubmitReport = false;
- }
-
- // Reset report
- vigemDevice.ResetReport();
-
- // Face buttons
- // Menu
- vigemDevice.SetButtonState(Xbox360Button.Start,
- packet.MenuButton);
- // Options
- vigemDevice.SetButtonState(Xbox360Button.Back,
- packet.OptionsButton);
- // Xbox
- vigemDevice.SetButtonState(Xbox360Button.Guide,
- packet.XboxButton);
-
- // D-pad
- // Dpad Up
- vigemDevice.SetButtonState(Xbox360Button.Up,
- packet.DpadUp);
- // Dpad Down
- vigemDevice.SetButtonState(Xbox360Button.Down,
- packet.DpadDown);
- // Dpad Left
- vigemDevice.SetButtonState(Xbox360Button.Left,
- packet.DpadLeft);
- // Dpad Right
- vigemDevice.SetButtonState(Xbox360Button.Right,
- packet.DpadRight);
-
- // Frets
- // Fret Green
- vigemDevice.SetButtonState(Xbox360Button.A,
- packet.UpperGreen ||
- packet.LowerGreen);
- // Fret Red
- vigemDevice.SetButtonState(Xbox360Button.B,
- packet.UpperRed ||
- packet.LowerRed);
- // Fret Yellow
- vigemDevice.SetButtonState(Xbox360Button.Y,
- packet.UpperYellow ||
- packet.LowerYellow);
- // Fret Blue
- vigemDevice.SetButtonState(Xbox360Button.X,
- packet.UpperBlue ||
- packet.LowerBlue);
- // Fret Orange
- vigemDevice.SetButtonState(Xbox360Button.LeftShoulder,
- packet.UpperOrange ||
- packet.LowerOrange);
- // Solo fret flag
- vigemDevice.SetButtonState(Xbox360Button.LeftThumb,
- packet.LowerGreen ||
- packet.LowerRed ||
- packet.LowerYellow ||
- packet.LowerBlue ||
- packet.LowerOrange);
-
- // Axes
- // Whammy
- vigemDevice.SetAxisValue(Xbox360Axis.RightThumbX,
- (short)((packet.WhammyBar * 257) - 32768));
- // Multiply by 257 to scale into a ushort, then subtract by 32768 to shift into a signed short
- // Tilt
- vigemDevice.SetAxisValue(Xbox360Axis.RightThumbY,
- (short)((packet.Tilt * 257) - 32768));
- // Pickup Switch
- vigemDevice.SetSliderValue(Xbox360Slider.LeftTrigger,
- packet.PickupSwitch);
-
- // Send data
- vigemDevice.SubmitReport();
-
- // Packet handled
- return true;
- }
- }
-}
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
deleted file mode 100644
index cf4731f..0000000
--- a/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System.Reflection;
-using System.Resources;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Windows;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("RB4InstrumentMapper")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("RB4InstrumentMapper")]
-[assembly: AssemblyCopyright("Copyright © 2021")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-//In order to begin building localizable applications, set
-//CultureYouAreCodingWith in your .csproj file
-//inside a . For example, if you are using US english
-//in your source files, set the to en-US. Then uncomment
-//the NeutralResourceLanguage attribute below. Update the "en-US" in
-//the line below to match the UICulture setting in the project file.
-
-//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
-
-
-[assembly: ThemeInfo(
- ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
- //(used if a resource is not found in the page,
- // or application resource dictionaries)
- ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
- //(used if a resource is not found in the page,
- // app, or any theme specific resource dictionaries)
-)]
-
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs
deleted file mode 100644
index 56dd778..0000000
--- a/Properties/Resources.Designer.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-
-namespace RB4InstrumentMapper.Properties
-{
- ///
- /// A strongly-typed resource class, for looking up localized strings, etc.
- ///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // 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", "4.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources
- {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources()
- {
- }
-
- ///
- /// Returns the cached ResourceManager instance used by this class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager
- {
- get
- {
- if ((resourceMan == null))
- {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RB4InstrumentMapper.Properties.Resources", typeof(Resources).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture
- {
- get
- {
- return resourceCulture;
- }
- set
- {
- resourceCulture = value;
- }
- }
- }
-}
diff --git a/Properties/Resources.resx b/Properties/Resources.resx
deleted file mode 100644
index af7dbeb..0000000
--- a/Properties/Resources.resx
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/Properties/Settings.settings b/Properties/Settings.settings
deleted file mode 100644
index a99c4a9..0000000
--- a/Properties/Settings.settings
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/RB4InstrumentMapper.CLI/Arguments.cs b/RB4InstrumentMapper.CLI/Arguments.cs
new file mode 100644
index 0000000..22f5eaf
--- /dev/null
+++ b/RB4InstrumentMapper.CLI/Arguments.cs
@@ -0,0 +1,238 @@
+using System;
+
+using MappingMode = RB4InstrumentMapper.Core.Mapping.MappingMode;
+
+namespace RB4InstrumentMapper.CLI
+{
+ ///
+ /// The parsed representation of the CLI's available arguments.
+ ///
+ public class Arguments
+ {
+ private const string HelpOption = "--help";
+
+ private const string ModeOption = "--mode";
+ private const string AccurateDrumsOption = "--accurate-drums";
+
+ private const string WaitForDevicesOption = "--wait-for-devices";
+ private const string TimeoutOption = "--timeout";
+
+ private const string VerboseOption = "--verbose";
+ private const string LogFileOption = "--log-file";
+ private const string LogPacketsOption = "--log-packets";
+
+ ///
+ /// The mapping mode to be used.
+ ///
+ ///
+ public MappingMode MappingMode = MappingMode.NotSet;
+
+ ///
+ ///
+ public bool HardwareAccurateDrums = false;
+
+ ///
+ /// The amount of time to wait for devices to be connected.
+ /// Null means to not wait at all.
+ ///
+ public int? WaitForDevicesPeriod = null;
+
+ ///
+ /// The amount of time to run the program for.
+ /// Null means to run indefinitely.
+ ///
+ public int? TimeoutPeriod = null;
+
+ ///
+ /// The path to use for the main log file.
+ /// Null means to use the default location (Documents\RB4InstrumentMapper\Logs\log_{yyyy-MM-dd_HH-mm-ss}.txt).
+ ///
+ ///
+ public string LogFilePath;
+
+ ///
+ /// The path to use for the packet log file.
+ /// Null means to not log packets.
+ ///
+ ///
+ public string PacketLogFilePath;
+
+ ///
+ /// Whether to log verbose messages.
+ ///
+ ///
+ public bool VerboseLogging;
+
+ ///
+ /// Attempts to parse the given command-line arguments.
+ ///
+ public static bool TryParse(string[] args, out Arguments parsed)
+ {
+ parsed = new Arguments();
+
+ if (args.Length == 0)
+ {
+ PrintHelp();
+ return false;
+ }
+
+ for (int i = 0; i < args.Length; i++)
+ {
+ string arg = args[i];
+ switch (arg)
+ {
+ case HelpOption:
+ {
+ PrintHelp();
+ return false;
+ }
+ case ModeOption:
+ {
+ if (i + 1 >= args.Length)
+ {
+ Console.WriteLine("Error: Value for " + ModeOption + " argument is missing.");
+ PrintHelp();
+ return false;
+ }
+
+ string modeStr = args[++i];
+ if (modeStr.Equals("vigem", StringComparison.OrdinalIgnoreCase) ||
+ modeStr.Equals("vigembus", StringComparison.OrdinalIgnoreCase))
+ {
+ parsed.MappingMode = MappingMode.ViGEmBus;
+ }
+ else if (modeStr.Equals("vjoy", StringComparison.OrdinalIgnoreCase))
+ {
+ parsed.MappingMode = MappingMode.vJoy;
+ }
+ else if (modeStr.Equals("rpcs3", StringComparison.OrdinalIgnoreCase))
+ {
+ parsed.MappingMode = MappingMode.RPCS3;
+ }
+ else
+ {
+ Console.WriteLine($"Error: Invalid mapping mode '{modeStr}'");
+ PrintHelp();
+ return false;
+ }
+
+ break;
+ }
+ case AccurateDrumsOption:
+ {
+ parsed.HardwareAccurateDrums = true;
+ break;
+ }
+ case WaitForDevicesOption:
+ {
+ const int defaultDeviceWaitPeriod = 30;
+
+ parsed.WaitForDevicesPeriod = defaultDeviceWaitPeriod;
+
+ // Optional timeout value
+ if (i + 1 < args.Length && int.TryParse(args[i + 1], out int parsedTimeout))
+ {
+ if (parsedTimeout < 0)
+ {
+ Console.WriteLine("Error: Invalid wait timeout value. Please provide a positive integer.");
+ return false;
+ }
+
+ parsed.WaitForDevicesPeriod = parsedTimeout;
+ i++;
+ }
+
+ break;
+ }
+ case TimeoutOption:
+ {
+ if (i + 1 >= args.Length)
+ {
+ Console.WriteLine("Error: Value for " + TimeoutOption + " argument is missing.");
+ PrintHelp();
+ return false;
+ }
+
+ if (!int.TryParse(args[++i], out int timeout) || timeout < 0)
+ {
+ Console.WriteLine("Error: Invalid timeout value. Please provide a positive integer.");
+ return false;
+ }
+
+ parsed.TimeoutPeriod = timeout;
+ break;
+ }
+ case VerboseOption:
+ {
+ parsed.VerboseLogging = true;
+ break;
+ }
+ case LogFileOption:
+ {
+ if (i + 1 >= args.Length)
+ {
+ Console.WriteLine("Error: Value for " + LogFileOption + " argument is missing.");
+ PrintHelp();
+ return false;
+ }
+
+ parsed.LogFilePath = args[++i];
+ break;
+ }
+ case LogPacketsOption:
+ {
+ if (i + 1 >= args.Length)
+ {
+ Console.WriteLine("Error: Value for " + LogPacketsOption + " argument is missing.");
+ PrintHelp();
+ return false;
+ }
+
+ parsed.PacketLogFilePath = args[++i];
+ break;
+ }
+ default:
+ {
+ Console.WriteLine($"Error: Unknown option '{arg}'");
+ PrintHelp();
+ return false;
+ }
+ }
+ }
+
+ // Validate arguments
+ if (parsed.MappingMode == MappingMode.NotSet)
+ {
+ Console.WriteLine("Error: Required argument --mode not found.");
+ PrintHelp();
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Prints argument help information to the console.
+ ///
+ public static void PrintHelp()
+ {
+ Console.WriteLine($"RB4InstrumentMapper CLI v{Program.GetVersion()}");
+ Console.WriteLine($"Usage: {Program.GetExecutableName()} [options]");
+ Console.WriteLine();
+ Console.WriteLine("Options:");
+ Console.WriteLine(" --mode The mapping mode to use.");
+ Console.WriteLine(" - mode: one of 'vigembus', 'vigem', 'vjoy', or 'rpcs3', case insensitive.");
+ Console.WriteLine(" --accurate-drums Use hardware-accurate drum mappings for ViGEmBus mode.");
+ Console.WriteLine();
+ Console.WriteLine(" --wait-for-devices [timeout] Wait for devices to be detected before starting (default timeout: 30s).");
+ Console.WriteLine(" --timeout Run for the specified number of seconds, and then exit.");
+ Console.WriteLine();
+ Console.WriteLine(" --verbose Enable verbose logging.");
+ Console.WriteLine(" --log-file Path to write logging output to.");
+ Console.WriteLine(" (default: Documents\\RB4InstrumentMapper\\Logs\\log_{yyyy-MM-dd_HH-mm-ss}.txt)");
+ Console.WriteLine(" --log-packets Log packets to the given file path.");
+ Console.WriteLine();
+ Console.WriteLine(" --help Display this help message.");
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.CLI/Program.cs b/RB4InstrumentMapper.CLI/Program.cs
new file mode 100644
index 0000000..f2e3422
--- /dev/null
+++ b/RB4InstrumentMapper.CLI/Program.cs
@@ -0,0 +1,277 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using RB4InstrumentMapper.Core;
+using RB4InstrumentMapper.Core.Mapping;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.CLI
+{
+ public class Program
+ {
+ private static bool captureActive = false;
+ private static int deviceCount = 0;
+
+ public static int Main(string[] args)
+ {
+ // Register exception handler
+ AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
+
+ // Read arguments
+ if (!Arguments.TryParse(args, out var parsedArgs))
+ {
+ return 1;
+ }
+
+ // Set up arguments
+ Logging.PrintVerbose = parsedArgs.VerboseLogging;
+
+ if (!string.IsNullOrEmpty(parsedArgs.LogFilePath))
+ {
+ Logging.CreateMainLog(parsedArgs.LogFilePath);
+ }
+
+ if (!string.IsNullOrEmpty(parsedArgs.PacketLogFilePath))
+ {
+ Logging.CreatePacketLog(parsedArgs.PacketLogFilePath);
+ }
+
+ BackendSettings.MapperMode = parsedArgs.MappingMode;
+ BackendSettings.UseAccurateDrumMappings = parsedArgs.HardwareAccurateDrums;
+
+ // Initialize
+ Logging.WriteLine($"RB4InstrumentMapper CLI Version {GetVersion()}");
+ Logging.WriteLine($"Using mapping mode: {BackendSettings.MapperMode}");
+ if (!Initialize())
+ {
+ return 1;
+ }
+
+ try
+ {
+ // Wait for devices if requested
+ if (!WaitForDeviceConnect(parsedArgs.WaitForDevicesPeriod))
+ {
+ return 1;
+ }
+
+ // Start mapping
+ Logging.WriteLine("Starting instrument mapping...");
+ StartCapture();
+
+ // Intercept Ctrl+C
+ bool keepGoing = true;
+ // Only send this message to the console, doesn't make sense in logs
+ Console.WriteLine("Press Ctrl+C to stop mapping and exit.");
+ Console.CancelKeyPress += (sender, eventArgs) =>
+ {
+ eventArgs.Cancel = true;
+ keepGoing = false;
+ };
+
+ // Wait for Ctrl+C or timeout period
+ var timer = Stopwatch.StartNew();
+ while (keepGoing)
+ {
+ if (timer.Elapsed.Seconds >= parsedArgs.TimeoutPeriod)
+ {
+ Logging.WriteLine("Program timeout reached, stopping.");
+ break;
+ }
+
+ Thread.Sleep(100);
+ }
+
+ Logging.WriteLine("Stopping instrument mapping...");
+ StopCapture();
+ return 0;
+ }
+ finally
+ {
+ Uninitialize();
+ }
+ }
+
+ public static string GetVersion()
+ {
+ var version = Assembly.GetEntryAssembly().GetName().Version;
+ return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
+ }
+
+ public static string GetExecutableName()
+ {
+ string executablePath = Assembly.GetEntryAssembly().Location;
+ return Path.GetFileName(executablePath);
+ }
+
+ private static bool WaitForDeviceConnect(int? timeoutPeriod)
+ {
+ if (!timeoutPeriod.HasValue)
+ {
+ return true;
+ }
+
+ if (deviceCount == 0)
+ {
+ int timeout = timeoutPeriod.Value;
+ Logging.WriteLine($"Waiting up to {timeout} seconds for devices to be detected...");
+
+ int lastSeenSeconds = -1;
+ var waitTimer = Stopwatch.StartNew();
+ while (deviceCount == 0 && waitTimer.Elapsed.Seconds < timeout)
+ {
+ // Display elapsed time
+ if (waitTimer.Elapsed.Seconds != lastSeenSeconds)
+ {
+ Console.CursorLeft = 0;
+ Console.Write($"{waitTimer.Elapsed.Seconds}/{timeout}s");
+ lastSeenSeconds = waitTimer.Elapsed.Seconds;
+ }
+
+ Thread.Sleep(100);
+ }
+
+ // Clear elapsed time
+ Console.CursorLeft = 0;
+ Console.Write(new string(' ', 40));
+ Console.CursorLeft = 0;
+
+ if (deviceCount == 0)
+ {
+ Logging.WriteLine("No devices detected within timeout period, exiting.");
+ return false;
+ }
+ }
+
+ Logging.WriteLine("Device found, starting mapping.");
+ return true;
+ }
+
+ private static bool Initialize()
+ {
+ // Initialize the appropriate virtual controller driver
+ if (BackendSettings.MapperMode == MappingMode.ViGEmBus || BackendSettings.MapperMode == MappingMode.RPCS3)
+ {
+ if (!ViGEmInstance.TryInitialize())
+ {
+ Logging.WriteLine("Error: Failed to initialize ViGEmBus driver. Please ensure it is installed.");
+ return false;
+ }
+
+ Logging.WriteLine("ViGEmBus driver initialized successfully.");
+ }
+ else if (BackendSettings.MapperMode == MappingMode.vJoy)
+ {
+ if (!vJoyInstance.Enabled)
+ {
+ Logging.WriteLine("Error: vJoy driver not found or disabled. Please ensure it is installed.");
+ return false;
+ }
+
+ if (vJoyInstance.GetAvailableDeviceCount() <= 0)
+ {
+ Logging.WriteLine("Error: No vJoy devices available. Please ensure they are configured correctly.");
+ return false;
+ }
+
+ Logging.WriteLine("vJoy driver initialized successfully.");
+ }
+
+ // Initialize backends
+ GameInputBackend.DeviceCountChanged += OnDeviceCountChanged;
+ if (!GameInputBackend.Initialize())
+ {
+ Logging.WriteLine("Warning: GameInput backend failed to initialize.");
+ }
+ else
+ {
+ Logging.WriteLine("GameInput backend initialized successfully.");
+ }
+
+ WinUsbBackend.DeviceCountChanged += OnDeviceCountChanged;
+ if (!WinUsbBackend.Initialize())
+ {
+ Logging.WriteLine("Warning: WinUSB backend failed to initialize.");
+ }
+ else
+ {
+ Logging.WriteLine("WinUSB backend initialized successfully.");
+ }
+
+ if (!GameInputBackend.Initialized && !WinUsbBackend.Initialized)
+ {
+ Logging.WriteLine("Error: All input backends failed to initialize.");
+ return false;
+ }
+
+ UpdateDeviceCount();
+ return true;
+ }
+
+ private static void Uninitialize()
+ {
+ StopCapture();
+
+ GameInputBackend.Uninitialize();
+ GameInputBackend.DeviceCountChanged -= OnDeviceCountChanged;
+
+ WinUsbBackend.Uninitialize();
+ WinUsbBackend.DeviceCountChanged -= OnDeviceCountChanged;
+
+ // Clean up
+ Logging.CloseAll();
+ ViGEmInstance.Dispose();
+ }
+
+ private static void StartCapture()
+ {
+ if (captureActive)
+ return;
+
+ WinUsbBackend.StartCapture();
+ GameInputBackend.StartCapture();
+ captureActive = true;
+ Logging.WriteLine("Instrument mapping is active.");
+ }
+
+ private static void StopCapture()
+ {
+ if (!captureActive)
+ return;
+
+ WinUsbBackend.StopCapture();
+ GameInputBackend.StopCapture();
+ captureActive = false;
+ Logging.WriteLine("Instrument mapping stopped.");
+ }
+
+ private static void OnDeviceCountChanged()
+ {
+ UpdateDeviceCount();
+ }
+
+ private static void UpdateDeviceCount()
+ {
+ deviceCount = GameInputBackend.DeviceCount + WinUsbBackend.DeviceCount;
+ }
+
+ private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
+ {
+ // Build log message all at once to ensure no other messages cut into it
+ var logMessage = new StringBuilder();
+ logMessage.AppendLine("-------------------");
+ logMessage.AppendLine("UNHANDLED EXCEPTION");
+ logMessage.AppendLine("-------------------");
+ logMessage.AppendLine(args.ExceptionObject?.ToString() ?? "(null error)");
+ Logging.WriteLine(logMessage.ToString());
+
+ // Only send this message to the console, doesn't make sense in logs
+ Console.WriteLine($"An error log was written to {Logging.MainLogPath}");
+
+ Uninitialize();
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.CLI/RB4InstrumentMapper.CLI.csproj b/RB4InstrumentMapper.CLI/RB4InstrumentMapper.CLI.csproj
new file mode 100644
index 0000000..ab33282
--- /dev/null
+++ b/RB4InstrumentMapper.CLI/RB4InstrumentMapper.CLI.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net472
+ Exe
+ x64
+ x64
+
+ true
+
+
+
+ Copyright © 2021
+ $(ResourcesDir)Icon.ico
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Dependencies/Nefarius.ViGEm.Client.dll b/RB4InstrumentMapper.Core/Dependencies/Nefarius.ViGEm.Client.dll
new file mode 100644
index 0000000..3ed226e
Binary files /dev/null and b/RB4InstrumentMapper.Core/Dependencies/Nefarius.ViGEm.Client.dll differ
diff --git a/RB4InstrumentMapper.Core/Dependencies/SharpGameInput.dll b/RB4InstrumentMapper.Core/Dependencies/SharpGameInput.dll
new file mode 100644
index 0000000..8ee718c
Binary files /dev/null and b/RB4InstrumentMapper.Core/Dependencies/SharpGameInput.dll differ
diff --git a/Dependencies/x64/vJoyInterface.dll b/RB4InstrumentMapper.Core/Dependencies/x64/vJoyInterface.dll
similarity index 100%
rename from Dependencies/x64/vJoyInterface.dll
rename to RB4InstrumentMapper.Core/Dependencies/x64/vJoyInterface.dll
diff --git a/Dependencies/x64/vJoyInterfaceWrap.dll b/RB4InstrumentMapper.Core/Dependencies/x64/vJoyInterfaceWrap.dll
similarity index 100%
rename from Dependencies/x64/vJoyInterfaceWrap.dll
rename to RB4InstrumentMapper.Core/Dependencies/x64/vJoyInterfaceWrap.dll
diff --git a/RB4InstrumentMapper.Core/Logging.Files.cs b/RB4InstrumentMapper.Core/Logging.Files.cs
new file mode 100644
index 0000000..79b2d21
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Logging.Files.cs
@@ -0,0 +1,268 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+
+namespace RB4InstrumentMapper.Core
+{
+ public static partial class Logging
+ {
+ ///
+ /// The file to log errors to.
+ ///
+ private static StreamWriter mainLog = null;
+ private static readonly object mainLock = new object();
+
+ ///
+ /// Gets whether or not the main log exists.
+ ///
+ public static bool MainLogExists => mainLog != null;
+
+ private static bool allowMainLogCreation = true;
+
+ ///
+ /// The current file to log packets to.
+ ///
+ private static StreamWriter packetLog = null;
+ private static readonly object packetLock = new object();
+
+ ///
+ /// Gets whether or not a packet log exists.
+ ///
+ public static bool PacketLogExists => packetLog != null;
+
+ ///
+ /// The path to the folder to write logs to.
+ ///
+ ///
+ /// Currently %USERPROFILE%\Documents\RB4InstrumentMapper\Logs
+ ///
+ public static readonly string LogFolderPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
+ "RB4InstrumentMapper",
+ "Logs"
+ );
+
+ ///
+ /// The path to the folder to write packet logs to.
+ ///
+ ///
+ /// Currently %USERPROFILE%\Documents\RB4InstrumentMapper\PacketLogs
+ ///
+ public static readonly string PacketLogFolderPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
+ "RB4InstrumentMapper",
+ "PacketLogs"
+ );
+
+ ///
+ /// The current path for the main log.
+ ///
+ public static string MainLogPath { get; private set; }
+
+ ///
+ /// The current path for the packet log.
+ ///
+ public static string PacketLogPath { get; private set; }
+
+ ///
+ /// Creates a log file path with the format of log_{yyyy-MM-dd_HH-mm-ss}.txt.
+ ///
+ private static string MakeDatedLogPath(string folderPath)
+ {
+ return Path.Combine(folderPath, $"log_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.txt");
+ }
+
+ ///
+ /// Creates a file stream at the specified path.
+ ///
+ private static StreamWriter CreateFileStream(string filePath)
+ {
+ try
+ {
+ // Create folder if it doesn't exist
+ string folderPath = Path.GetDirectoryName(filePath);
+ if (!string.IsNullOrEmpty(folderPath) && !Directory.Exists(folderPath))
+ {
+ Directory.CreateDirectory(folderPath);
+ }
+
+ return new StreamWriter(filePath);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Couldn't create log file at {filePath}");
+ Console.WriteLine(ex.GetFirstLine());
+ Debug.WriteLine(ex.ToString());
+ return null;
+ }
+ }
+
+ ///
+ /// Creates the main log file, optionally with the given file path.
+ ///
+ public static void CreateMainLog(string filePath = null)
+ {
+ lock (mainLock)
+ {
+ if (!allowMainLogCreation || mainLog != null)
+ return;
+
+ if (string.IsNullOrEmpty(filePath))
+ {
+ filePath = MakeDatedLogPath(LogFolderPath);
+ }
+
+ mainLog = CreateFileStream(filePath);
+ if (mainLog == null)
+ {
+ // Log could not be created, don't allow creating it again to prevent console spam
+ allowMainLogCreation = false;
+ return;
+ }
+
+ MainLogPath = filePath;
+ }
+
+ Console.WriteLine($"Created main log file at {filePath}");
+ }
+
+ ///
+ /// Creates a packet log file, optionally with the given file path.
+ ///
+ public static void CreatePacketLog(string filePath = null)
+ {
+ lock (packetLock)
+ {
+ if (packetLog != null)
+ return;
+
+ if (string.IsNullOrEmpty(filePath))
+ {
+ filePath = MakeDatedLogPath(PacketLogFolderPath);
+ }
+
+ packetLog = CreateFileStream(filePath);
+ if (packetLog == null)
+ return;
+
+ PacketLogPath = filePath;
+ }
+
+ Console.WriteLine($"Created packet log file at {filePath}");
+ }
+
+ ///
+ /// Writes a line to the log file.
+ ///
+ public static void Main_WriteLine(string text)
+ {
+ // Create log file if it hasn't been made yet
+ CreateMainLog();
+
+ lock (mainLock)
+ {
+ mainLog?.WriteLine(GetMessageHeader(text));
+ }
+ }
+
+ ///
+ /// Writes an exception, and any context, to the log.
+ ///
+ public static void Main_WriteException(Exception ex, string context = null)
+ {
+ // Create log file if it hasn't been made yet
+ CreateMainLog();
+
+ lock (mainLock)
+ {
+ mainLog?.WriteException(ex, context);
+ }
+ }
+
+ public static void Packet_WriteLine(string text)
+ {
+ // Don't create log file if it hasn't been made yet
+ // Packet log should be created manually
+ // CreatePacketLog();
+
+ lock (packetLock)
+ {
+ packetLog?.WriteLine(text);
+ }
+ }
+
+ ///
+ /// Closes the main log file.
+ ///
+ public static void CloseMainLog()
+ {
+ lock (mainLock)
+ {
+ mainLog?.Close();
+ mainLog = null;
+ MainLogPath = null;
+ }
+ }
+
+ ///
+ /// Closes the active packet log file.
+ ///
+ public static void ClosePacketLog()
+ {
+ lock (packetLock)
+ {
+ packetLog?.Close();
+ packetLog = null;
+ PacketLogPath = null;
+ }
+ }
+
+ ///
+ /// Closes all log files.
+ ///
+ public static void CloseAll()
+ {
+ CloseMainLog();
+ ClosePacketLog();
+ }
+
+ ///
+ /// Gets the first line of an exception.
+ ///
+ public static string GetFirstLine(this Exception ex)
+ {
+ if (ex == null)
+ return "(null exception)";
+
+ string message = ex.ToString();
+ int newLine = message.IndexOfAny(new[] { '\r', '\n' });
+ if (newLine != -1)
+ return message.Substring(0, newLine);
+ else
+ return message;
+ }
+
+ ///
+ /// Writes an exception + stack trace to a stream writer.
+ ///
+ public static void WriteException(this StreamWriter stream, Exception ex, string context = null)
+ {
+ stream.WriteLine(GetMessageHeader("EXCEPTION"));
+ stream.WriteLine("------------------------------");
+ // Prevent writing an empty line if context is not provided
+ if (context != null)
+ stream.WriteLine(context);
+ stream.WriteLine(ex);
+ stream.WriteLine("------------------------------");
+ }
+
+ ///
+ /// Gets a message header with a timestamp.
+ ///
+ private static string GetMessageHeader(string message)
+ {
+ return $"[{DateTime.Now:HH:mm:ss.fff}] {message}";
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Logging.cs b/RB4InstrumentMapper.Core/Logging.cs
new file mode 100644
index 0000000..c34312c
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Logging.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Diagnostics;
+
+namespace RB4InstrumentMapper.Core
+{
+ ///
+ /// Provides functionality for logging.
+ ///
+ public static partial class Logging
+ {
+ ///
+ /// Whether to print verbose messages to the console.
+ /// (They will always be written to the log file.)
+ ///
+ public static bool PrintVerbose { get; set; } = false;
+
+ ///
+ /// Prints the given message to the log, debug console, and standard output console.
+ ///
+ public static void WriteLine(string message)
+ {
+ Debug.WriteLine(message);
+ Main_WriteLine(message);
+ Console.WriteLine(message);
+ }
+
+ ///
+ /// Prints the given message to the log and debug console,
+ /// along with the standard output console if is enabled.
+ ///
+ public static void WriteLineVerbose(string message)
+ {
+ // Always log messages to debug/log
+ Debug.WriteLine(message);
+ Main_WriteLine(message);
+ if (!PrintVerbose)
+ return;
+
+ Console.WriteLine(message);
+ }
+
+ ///
+ /// Prints the given exception and message to the log, debug console, and standard output console.
+ ///
+ public static void WriteException(string message, Exception ex)
+ {
+ Debug.WriteLine(message);
+ Debug.WriteLine(ex);
+ Main_WriteException(ex, message);
+ Console.WriteLine(message);
+ Console.WriteLine(ex.GetFirstLine());
+ }
+
+ ///
+ /// Prints the given exception and message to the log and debug console,
+ /// along with the standard output console if is enabled.
+ ///
+ public static void WriteExceptionVerbose(string message, Exception ex)
+ {
+ // Always log errors to debug/log
+ Debug.WriteLine(message);
+ Debug.WriteLine(ex);
+ Main_WriteException(ex, message);
+
+ if (!PrintVerbose)
+ return;
+
+ Console.WriteLine(message);
+ Console.WriteLine(ex.GetFirstLine());
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/DeviceMapper.cs b/RB4InstrumentMapper.Core/Mapping/DeviceMapper.cs
new file mode 100644
index 0000000..16c34a5
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/DeviceMapper.cs
@@ -0,0 +1,96 @@
+using System;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// A mapper which maps inputs from a physical device to a virtual controller.
+ ///
+ internal abstract class DeviceMapper : IDisposable
+ {
+ protected readonly IBackendClient client;
+ protected readonly bool mapGuideButton;
+
+ protected bool disposed = false;
+
+ ///
+ /// Initializes a new device mapper with the given parent client,
+ /// and option of whether or not to map the guide button.
+ ///
+ public DeviceMapper(IBackendClient client)
+ {
+ this.client = client;
+
+ mapGuideButton = client.MapGuideButton;
+ }
+
+ ~DeviceMapper()
+ {
+ Dispose(false);
+ }
+
+ ///
+ /// Handles an incoming packet.
+ ///
+ public virtual XboxResult HandleMessage(byte command, ReadOnlySpan data)
+ {
+ CheckDisposed();
+ return OnMessageReceived(command, data);
+ }
+
+ ///
+ /// Handles a keystroke message.
+ ///
+ public virtual XboxResult HandleKeystroke(XboxKeystroke key)
+ {
+ CheckDisposed();
+
+ if (key.Keycode == XboxKeyCode.LeftWindows && mapGuideButton)
+ {
+ MapGuideButton(key.Pressed);
+ }
+
+ return XboxResult.Success;
+ }
+
+ protected abstract XboxResult OnMessageReceived(byte command, ReadOnlySpan data);
+ protected abstract void MapGuideButton(bool pressed);
+
+ public abstract void ResetReport();
+
+ ///
+ /// Disposes the mapper and any resources it uses.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ DisposeManagedResources();
+ }
+
+ DisposeUnmanagedResources();
+
+ disposed = true;
+ }
+
+ protected void CheckDisposed()
+ {
+ if (disposed)
+ throw new ObjectDisposedException("this");
+ }
+
+ protected virtual void DisposeManagedResources()
+ {
+ }
+
+ protected virtual void DisposeUnmanagedResources()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/MapperFactory.cs b/RB4InstrumentMapper.Core/Mapping/MapperFactory.cs
new file mode 100644
index 0000000..ad049e6
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/MapperFactory.cs
@@ -0,0 +1,252 @@
+using System;
+using System.Collections.Generic;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ public enum MappingMode
+ {
+ NotSet = 0,
+ ViGEmBus = 1,
+ vJoy = 2,
+ RPCS3 = 3,
+ }
+
+ ///
+ /// Creates a device mapper for a client.
+ ///
+ internal static class MapperFactory
+ {
+ private delegate DeviceMapper CreateMapper(IBackendClient client);
+
+ private static readonly Dictionary<(ushort vendorId, ushort productId), CreateMapper> hardwareIdLookup
+ = new Dictionary<(ushort, ushort), CreateMapper>()
+ {
+ // Guitars
+ { (0x0738, 0x4161), GetGuitarMapper }, // MadCatz Stratocaster
+ { (0x0E6F, 0x0170), GetGuitarMapper }, // PDP Jaguar
+ { (0x0E6F, 0x0248), GetGuitarMapper }, // PDP Riffmaster
+
+ // Drumkits
+ { (0x0738, 0x4262), GetDrumsMapper }, // MadCatz
+ { (0x0E6F, 0x0171), GetDrumsMapper }, // PDP
+
+ // Other
+ { (0x1430, 0x079B), GetGHLGuitarMapper }, // Guitar Hero Live guitar
+ { (0x0738, 0x4164), GetWirelessLegacyMapper }, // MadCatz Wireless Legacy adapter
+
+ // Gamepads
+ { (0x045E, 0x02DD), GetGamepadMapper }, // Microsoft 1st-revision gamepad
+ { (0x045E, 0x0B00), GetGamepadMapper }, // Microsoft Elite Series 2 gamepad
+ };
+
+ private static readonly Dictionary interfaceGuidLookup = new Dictionary()
+ {
+ { XboxDeviceGuids.MadCatzGuitar, GetGuitarMapper },
+ { XboxDeviceGuids.PdpGuitar, GetGuitarMapper },
+
+ { XboxDeviceGuids.MadCatzDrumkit, GetDrumsMapper },
+ { XboxDeviceGuids.PdpDrumkit, GetDrumsMapper },
+
+ { XboxDeviceGuids.ActivisionGuitarHeroLive, GetGHLGuitarMapper },
+
+ { XboxDeviceGuids.MadCatzLegacyWireless, GetWirelessLegacyMapper },
+
+ { XboxDeviceGuids.XboxGamepad, GetGamepadMapper },
+ };
+
+ // Interface GUIDs to ignore when more than one supported interface is found
+ private static readonly HashSet conflictIgnoredIds = new HashSet()
+ {
+ // GHL guitars list both a unique interface and the gamepad interface
+ XboxDeviceGuids.XboxGamepad,
+ };
+
+ private static CreateMapper GetByInterfaceIds(HashSet interfaceGuids)
+ {
+ // Get unique interface GUID
+ Guid interfaceGuid = default;
+ foreach (var guid in interfaceGuids)
+ {
+ if (!interfaceGuidLookup.ContainsKey(guid))
+ continue;
+
+ if (interfaceGuid != default)
+ {
+ // Ignore IDs known to have conflicts
+ if (conflictIgnoredIds.Contains(guid))
+ continue;
+
+ Logging.WriteLine($"More than one recognized interface found! Cannot get specific mapper, device will not be mapped.");
+ Logging.WriteLine($"Consider filing a GitHub issue with the GUIDs below if this device should be supported:");
+ foreach (var guid2 in interfaceGuids)
+ {
+ Logging.WriteLine($"- {guid2}");
+ }
+ return null;
+ }
+
+ interfaceGuid = guid;
+ }
+
+ if (interfaceGuid == default)
+ {
+ Logging.WriteLine($"Could not find any supported interface IDs! Device will not be mapped.");
+ Logging.WriteLine($"Consider filing a GitHub issue with the GUIDs below if this device should be supported:");
+ foreach (var guid2 in interfaceGuids)
+ {
+ Logging.WriteLine($"- {guid2}");
+ }
+ return null;
+ }
+
+ // Get mapper creation delegate for interface GUID
+ if (!interfaceGuidLookup.TryGetValue(interfaceGuid, out var func))
+ {
+ Logging.WriteLine($"Could not get a specific mapper for interface {interfaceGuid}! Device will not be mapped.");
+ Logging.WriteLine($"Consider filing a GitHub issue with the GUID above if this device should be supported.");
+ return null;
+ }
+
+ return func;
+ }
+
+ public static DeviceMapper GetByHardwareIds(IBackendClient client)
+ {
+ if (!hardwareIdLookup.TryGetValue((client.VendorId, client.ProductId), out var func))
+ {
+ // Verbose since hardware ID lookup is meant for GameInput,
+ // and we don't want to warn unnecessarily for devices that don't need to be handled
+ Logging.WriteLineVerbose($"Device with hardware IDs {client.VendorId:X4}:{client.ProductId:X4} is not recognized! Device will not be mapped.");
+ return new DummyMapper(client);
+ }
+
+ try
+ {
+ return func(client);
+ }
+ catch (Exception ex)
+ {
+ Logging.WriteException("Failed to create mapper for device!", ex);
+ return null;
+ }
+ }
+
+ public static DeviceMapper GetByInterfaceIds(IBackendClient client, HashSet interfaceGuids)
+ {
+ var func = GetByInterfaceIds(interfaceGuids);
+ if (func == null)
+ return new DummyMapper(client);
+
+ try
+ {
+ return func(client);
+ }
+ catch (Exception ex)
+ {
+ Logging.WriteException("Failed to create mapper for device!", ex);
+ return null;
+ }
+ }
+
+ private static DeviceMapper GetMapper(IBackendClient client, CreateMapper createViGEm, CreateMapper createvJoy,
+ CreateMapper createRpcs3)
+ {
+ DeviceMapper mapper;
+ bool devicesAvailable;
+
+ var mode = BackendSettings.MapperMode;
+ switch (mode)
+ {
+ case MappingMode.ViGEmBus:
+ mapper = ViGEmInstance.AreDevicesAvailable ? createViGEm(client) : null;
+ devicesAvailable = ViGEmInstance.AreDevicesAvailable;
+ break;
+
+ case MappingMode.RPCS3:
+ mapper = ViGEmInstance.AreDevicesAvailable ? createRpcs3(client) : null;
+ devicesAvailable = ViGEmInstance.AreDevicesAvailable;
+ break;
+
+ case MappingMode.vJoy:
+ mapper = vJoyInstance.AreDevicesAvailable ? createvJoy(client) : null;
+ devicesAvailable = vJoyInstance.AreDevicesAvailable;
+ break;
+
+ default:
+ throw new NotImplementedException($"Unhandled mapping mode {mode}!");
+ }
+
+ if (mapper != null)
+ {
+ Logging.WriteLine($"Created new {mapper.GetType().Name}");
+ if (!devicesAvailable)
+ Logging.WriteLine("Device limit reached, no new devices will be handled.");
+ }
+
+ return mapper;
+ }
+
+ public static DeviceMapper GetGamepadMapper(IBackendClient client)
+ {
+#if ENABLE_GAMEPAD_MAPPING
+ Logging.WriteLine("Warning: Gamepads are only supported for testing purposes. You will experience duplicate inputs in games.");
+ return GetMapper(client,
+ (c) => new GamepadViGEmMapper(c),
+ (c) => new GamepadvJoyMapper(c),
+ // No RPCS3 mapper, as this is for testing only
+ (c) => new GamepadViGEmMapper(c)
+ );
+#else
+ return null;
+#endif
+ }
+
+ public static DeviceMapper GetGuitarMapper(IBackendClient client)
+ {
+ const ushort RIFFMASTER_VENDOR_ID = 0x0E6F;
+ const ushort RIFFMASTER_PRODUCT_ID = 0x0248;
+
+ bool isRiffmaster = client.VendorId == RIFFMASTER_VENDOR_ID &&
+ client.ProductId == RIFFMASTER_PRODUCT_ID;
+
+ CreateMapper createViGEm;
+ if (isRiffmaster)
+ createViGEm = (c) => new RiffmasterViGEmMapper(c);
+ else
+ createViGEm = (c) => new GuitarViGEmMapper(c);
+
+ return GetMapper(client,
+ createViGEm,
+ (c) => new GuitarvJoyMapper(c),
+ (c) => new GuitarRPCS3Mapper(c)
+ );
+ }
+
+ public static DeviceMapper GetDrumsMapper(IBackendClient client) => GetMapper(client,
+ (c) => new DrumsViGEmMapper(c),
+ (c) => new DrumsvJoyMapper(c),
+ (c) => new DrumsRPCS3Mapper(c)
+ );
+
+ public static DeviceMapper GetGHLGuitarMapper(IBackendClient client) => GetMapper(client,
+ (c) => new GHLGuitarViGEmMapper(c),
+ (c) => new GHLGuitarvJoyMapper(c),
+ // No mapping differences between RPCS3 and ViGEm modes
+ (c) => new GHLGuitarViGEmMapper(c)
+ );
+
+ public static DeviceMapper GetWirelessLegacyMapper(IBackendClient client)
+ {
+ var mapper = new WirelessLegacyMapper(client);
+ Logging.WriteLine($"Created new {nameof(WirelessLegacyMapper)} mapper");
+ return mapper;
+ }
+
+ public static DeviceMapper GetFallbackMapper(IBackendClient client) => GetMapper(client,
+ (c) => new FallbackViGEmMapper(c),
+ (c) => new FallbackvJoyMapper(c),
+ (c) => new FallbackRPCS3Mapper(c)
+ );
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/DummyMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/DummyMapper.cs
new file mode 100644
index 0000000..8a63a50
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/DummyMapper.cs
@@ -0,0 +1,17 @@
+using System;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// A mapper which does nothing.
+ ///
+ internal class DummyMapper : DeviceMapper
+ {
+ public DummyMapper(IBackendClient client) : base(client) {}
+ public override void ResetReport() {}
+ protected override void MapGuideButton(bool pressed) {}
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ => XboxResult.Success;
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/RPCS3/DrumsRPCS3Mapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/RPCS3/DrumsRPCS3Mapper.cs
new file mode 100644
index 0000000..e806a44
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/RPCS3/DrumsRPCS3Mapper.cs
@@ -0,0 +1,97 @@
+using System;
+using Nefarius.ViGEm.Client.Targets;
+using Nefarius.ViGEm.Client.Targets.Xbox360;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps drumkit inputs to a ViGEmBus device, with RPCS3 compatibilty mappings.
+ ///
+ internal class DrumsRPCS3Mapper : ViGEmMapper
+ {
+ public DrumsRPCS3Mapper(IBackendClient client)
+ : base(client)
+ {
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxDrumInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ // The previous state of the yellow/blue cymbals
+ private int previousDpadCymbals;
+ // The current state of the d-pad mask from the hit yellow/blue cymbals
+ private int dpadMask;
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxDrumInput drumReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(device, drumReport, ref previousDpadCymbals, ref dpadMask);
+ return SubmitReport();
+ }
+
+ ///
+ /// Maps drumkit input data to an Xbox 360 controller.
+ ///
+ internal static void HandleReport(IXbox360Controller device, in XboxDrumInput report, ref int previousDpadCymbals, ref int dpadMask)
+ {
+ // Changes from the normal ViGEm mapping are based on the PS3 guitar report
+ // https://github.com/TheNathannator/PlasticBand/blob/main/Docs/Instruments/4-Lane%20Drums/PS3%20and%20Wii.md
+ // RPCS3 by default maps XInput gamepads 1:1 with PS3 ones,
+ // so we change the bindings here to make any rebinding unnecessary
+
+ // Menu and Options
+ var buttons = (XboxGamepadButton)report.Buttons;
+ device.SetButtonState(Xbox360Button.Start, (buttons & XboxGamepadButton.Menu) != 0);
+ device.SetButtonState(Xbox360Button.Back, (buttons & XboxGamepadButton.Options) != 0);
+
+ // Dpad
+ DrumsViGEmMapper.MapDpad_HardwareAccurate(device, report, ref previousDpadCymbals, ref dpadMask);
+
+ // Pads and cymbals
+ byte redPad = report.RedPad;
+ byte yellowPad = report.YellowPad;
+ byte bluePad = report.BluePad;
+ byte greenPad = report.GreenPad;
+
+ byte yellowCym = report.YellowCymbal;
+ byte blueCym = report.BlueCymbal;
+ byte greenCym = report.GreenCymbal;
+
+ // Color flags
+ device.SetButtonState(Xbox360Button.B, (redPad != 0) || ((buttons & XboxGamepadButton.B) != 0));
+ device.SetButtonState(Xbox360Button.Y, ((yellowPad | yellowCym) != 0) || ((buttons & XboxGamepadButton.Y) != 0));
+ device.SetButtonState(Xbox360Button.X, ((bluePad | blueCym) != 0) || ((buttons & XboxGamepadButton.X) != 0));
+ device.SetButtonState(Xbox360Button.A, ((greenPad | greenCym) != 0) || ((buttons & XboxGamepadButton.A) != 0));
+
+ // Pad flag
+ // Left stick click instead of right stick click
+ device.SetButtonState(Xbox360Button.LeftThumb,
+ (redPad | yellowPad | bluePad | greenPad) != 0);
+ // Cymbal flag
+ // Right stick click instead of right bumper
+ device.SetButtonState(Xbox360Button.RightThumb,
+ (yellowCym | blueCym | greenCym) != 0);
+
+ // Pedals
+ device.SetButtonState(Xbox360Button.LeftShoulder,
+ (report.Buttons & (ushort)XboxDrumButton.KickOne) != 0);
+ // Right bumper instead of left stick click
+ device.SetButtonState(Xbox360Button.RightShoulder,
+ (report.Buttons & (ushort)XboxDrumButton.KickTwo) != 0);
+
+ // No velocity axes since it's not feasible to map them
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/RPCS3/FallbackRPCS3Mapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/RPCS3/FallbackRPCS3Mapper.cs
new file mode 100644
index 0000000..5bced4c
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/RPCS3/FallbackRPCS3Mapper.cs
@@ -0,0 +1,72 @@
+using System;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// The RPCS3 mapper used when device type could not be determined. Maps based on report length.
+ ///
+ internal class FallbackRPCS3Mapper : ViGEmMapper
+ {
+ public FallbackRPCS3Mapper(IBackendClient client)
+ : base(client)
+ {
+ }
+
+ protected override unsafe XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxGuitarInput.CommandId:
+ // These have the same value
+ // case XboxDrumInput.CommandId:
+ // #if ENABLE_GAMEPAD_MAPPING
+ // case XboxGamepadInput.CommandId:
+ // #endif
+ return ParseInput(data);
+
+ case XboxGHLGuitarInput.CommandId:
+ // Deliberately limit to the exact size
+ if (data.Length != sizeof(XboxGHLGuitarInput) || !ParsingUtils.TryRead(data, out XboxGHLGuitarInput guitarReport))
+ return XboxResult.InvalidMessage;
+
+ // No mapping differences between RPCS3 and ViGEm modes
+ GHLGuitarViGEmMapper.HandleReport(device, guitarReport);
+ return SubmitReport();
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ // The previous state of the yellow/blue cymbals
+ private int previousDpadCymbals;
+ // The current state of the d-pad mask from the hit yellow/blue cymbals
+ private int dpadMask;
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (data.Length == sizeof(XboxGuitarInput) && ParsingUtils.TryRead(data, out XboxGuitarInput guitarReport))
+ {
+ GuitarRPCS3Mapper.HandleReport(device, guitarReport);
+ }
+ else if (data.Length == sizeof(XboxDrumInput) && ParsingUtils.TryRead(data, out XboxDrumInput drumReport))
+ {
+ DrumsRPCS3Mapper.HandleReport(device, drumReport, ref previousDpadCymbals, ref dpadMask);
+ }
+#if ENABLE_GAMEPAD_MAPPING
+ else if (data.Length == sizeof(XboxGamepadInput) && ParsingUtils.TryRead(data, out XboxGamepadInput gamepadReport))
+ {
+ GamepadViGEmMapper.HandleReport(device, gamepadReport);
+ }
+#endif
+ else
+ {
+ // Not handled
+ return XboxResult.Success;
+ }
+
+ return SubmitReport();
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/RPCS3/GuitarRPCS3Mapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/RPCS3/GuitarRPCS3Mapper.cs
new file mode 100644
index 0000000..474d2ea
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/RPCS3/GuitarRPCS3Mapper.cs
@@ -0,0 +1,86 @@
+using System;
+using Nefarius.ViGEm.Client.Targets;
+using Nefarius.ViGEm.Client.Targets.Xbox360;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps guitar inputs to a ViGEmBus device, with RPCS3 compatibilty mappings.
+ ///
+ internal class GuitarRPCS3Mapper : ViGEmMapper
+ {
+ public GuitarRPCS3Mapper(IBackendClient client)
+ : base(client)
+ {
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxGuitarInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxGuitarInput guitarReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(device, guitarReport);
+
+ // Send data
+ return SubmitReport();
+ }
+
+ ///
+ /// Maps guitar input data to an Xbox 360 controller.
+ ///
+ internal static void HandleReport(IXbox360Controller device, in XboxGuitarInput report)
+ {
+ // Changes from the normal ViGEm mapping are based on the PS3 guitar report
+ // https://github.com/TheNathannator/PlasticBand/blob/main/Docs/Instruments/5-Fret%20Guitar/Rock%20Band/PS3%20and%20Wii.md
+ // RPCS3 by default maps XInput gamepads 1:1 with PS3 ones,
+ // so we change the bindings here to make any rebinding unnecessary
+
+ // Menu and Options
+ var buttons = (XboxGamepadButton)report.Buttons;
+ device.SetButtonState(Xbox360Button.Start, (buttons & XboxGamepadButton.Menu) != 0);
+ device.SetButtonState(Xbox360Button.Back, (buttons & XboxGamepadButton.Options) != 0);
+
+ // Dpad
+ device.SetButtonState(Xbox360Button.Up, (buttons & XboxGamepadButton.DpadUp) != 0);
+ device.SetButtonState(Xbox360Button.Down, (buttons & XboxGamepadButton.DpadDown) != 0);
+ device.SetButtonState(Xbox360Button.Left, (buttons & XboxGamepadButton.DpadLeft) != 0);
+ device.SetButtonState(Xbox360Button.Right, (buttons & XboxGamepadButton.DpadRight) != 0);
+
+ // Frets
+ device.SetButtonState(Xbox360Button.A, report.Green);
+ device.SetButtonState(Xbox360Button.B, report.Red);
+ device.SetButtonState(Xbox360Button.Y, report.Yellow);
+ device.SetButtonState(Xbox360Button.X, report.Blue);
+ device.SetButtonState(Xbox360Button.LeftShoulder, report.Orange);
+
+ // Lower fret flag
+ // This uses the L2 button bit on PS3 guitars, which we can't set directly,
+ // so we set the trigger axis instead
+ device.SetSliderValue(Xbox360Slider.LeftTrigger, report.LowerFretsPressed ? byte.MaxValue : byte.MinValue);
+
+ // Whammy
+ device.SetAxisValue(Xbox360Axis.RightThumbX, report.WhammyBar.ScaleToInt16());
+ // Tilt
+ // Button instead of an axis
+ // TODO: The threshold here should probably be configurable/calibratable
+ device.SetButtonState(Xbox360Button.RightShoulder, report.Tilt >= 0xD0);
+ // Pickup Switch
+ // Right stick Y instead of left trigger
+ device.SetAxisValue(Xbox360Axis.RightThumbY,
+ GuitarViGEmMapper.CalculatePickupSwitch(report.PickupSwitch).ScaleToInt16());
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/DrumsViGEmMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/DrumsViGEmMapper.cs
new file mode 100644
index 0000000..74dbd28
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/DrumsViGEmMapper.cs
@@ -0,0 +1,227 @@
+using System;
+using Nefarius.ViGEm.Client.Targets;
+using Nefarius.ViGEm.Client.Targets.Xbox360;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps drumkit inputs to a ViGEmBus device.
+ ///
+ internal class DrumsViGEmMapper : ViGEmMapper
+ {
+ public DrumsViGEmMapper(IBackendClient client)
+ : base(client)
+ {
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxDrumInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ // The previous state of the yellow/blue cymbals
+ private int previousDpadCymbals;
+ // The current state of the d-pad mask from the hit yellow/blue cymbals
+ private int dpadMask;
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxDrumInput drumReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(device, drumReport, ref previousDpadCymbals, ref dpadMask);
+ return SubmitReport();
+ }
+
+ ///
+ /// Maps drumkit input data to an Xbox 360 controller.
+ ///
+ internal static void HandleReport(IXbox360Controller device, in XboxDrumInput report, ref int previousDpadCymbals, ref int dpadMask)
+ {
+ // Menu and Options
+ var buttons = (XboxGamepadButton)report.Buttons;
+ device.SetButtonState(Xbox360Button.Start, (buttons & XboxGamepadButton.Menu) != 0);
+ device.SetButtonState(Xbox360Button.Back, (buttons & XboxGamepadButton.Options) != 0);
+
+ if (BackendSettings.UseAccurateDrumMappings)
+ {
+ // Dpad
+ MapDpad_HardwareAccurate(device, report, ref previousDpadCymbals, ref dpadMask);
+
+ // Pads and cymbals
+ MapDrums_HardwareAccurate(device, report);
+ }
+ else
+ {
+ // Dpad
+ device.SetButtonState(Xbox360Button.Up, (buttons & XboxGamepadButton.DpadUp) != 0);
+ device.SetButtonState(Xbox360Button.Down, (buttons & XboxGamepadButton.DpadDown) != 0);
+ device.SetButtonState(Xbox360Button.Left, (buttons & XboxGamepadButton.DpadLeft) != 0);
+ device.SetButtonState(Xbox360Button.Right, (buttons & XboxGamepadButton.DpadRight) != 0);
+
+ // Pads and cymbals
+ MapDrums_Individual(device, report);
+ }
+ }
+
+ internal static void MapDpad_HardwareAccurate(IXbox360Controller device, in XboxDrumInput report, ref int previousDpadCymbals, ref int dpadMask)
+ {
+ const int yellowBit = 0x01;
+ const int blueBit = 0x02;
+
+ // Yellow and blue cymbal trigger d-pad up and down respectively on the RB2/3 kit we're emulating
+ // However, they only trigger one or the other, not both at the same time, so we need to mimic that
+ int cymbalMask = (report.YellowCymbal != 0 ? yellowBit : 0) | (report.BlueCymbal != 0 ? blueBit : 0);
+ if (cymbalMask != previousDpadCymbals)
+ {
+ if (cymbalMask == 0)
+ dpadMask = 0;
+
+ // This could probably be done more simply, but this works
+ if (dpadMask != 0)
+ {
+ // D-pad is already set
+ // Only remove the set value
+ if ((cymbalMask & yellowBit) == 0)
+ dpadMask &= ~yellowBit;
+ else if ((cymbalMask & blueBit) == 0)
+ dpadMask &= ~blueBit;
+ }
+
+ // Explicitly check this so that if the d-pad is cleared but the other cymbal is still active,
+ // it will get set to that cymbal's d-pad
+ if (dpadMask == 0)
+ {
+ // D-pad is not set
+ // If both cymbals are hit at the same time, yellow takes priority
+ if ((cymbalMask & yellowBit) != 0)
+ dpadMask |= yellowBit;
+ else if ((cymbalMask & blueBit) != 0)
+ dpadMask |= blueBit;
+ }
+
+ previousDpadCymbals = cymbalMask;
+ }
+
+ var buttons = (XboxGamepadButton)report.Buttons;
+ device.SetButtonState(Xbox360Button.Up, ((dpadMask & yellowBit) != 0) || ((buttons & XboxGamepadButton.DpadUp) != 0));
+ device.SetButtonState(Xbox360Button.Down, ((dpadMask & blueBit) != 0) || ((buttons & XboxGamepadButton.DpadDown) != 0));
+ device.SetButtonState(Xbox360Button.Left, (buttons & XboxGamepadButton.DpadLeft) != 0);
+ device.SetButtonState(Xbox360Button.Right, (buttons & XboxGamepadButton.DpadRight) != 0);
+ }
+
+ // Maps using the exact inputs an Xbox 360 RB drumkit sends
+ internal static void MapDrums_HardwareAccurate(IXbox360Controller device, in XboxDrumInput report)
+ {
+ // Pads and cymbals
+ byte redPad = report.RedPad;
+ byte yellowPad = report.YellowPad;
+ byte bluePad = report.BluePad;
+ byte greenPad = report.GreenPad;
+
+ byte yellowCym = report.YellowCymbal;
+ byte blueCym = report.BlueCymbal;
+ byte greenCym = report.GreenCymbal;
+
+ // Color flags
+ var buttons = (XboxGamepadButton)report.Buttons;
+ device.SetButtonState(Xbox360Button.B, (redPad != 0) || ((buttons & XboxGamepadButton.B) != 0));
+ device.SetButtonState(Xbox360Button.Y, ((yellowPad | yellowCym) != 0) || ((buttons & XboxGamepadButton.Y) != 0));
+ device.SetButtonState(Xbox360Button.X, ((bluePad | blueCym) != 0) || ((buttons & XboxGamepadButton.X) != 0));
+ device.SetButtonState(Xbox360Button.A, ((greenPad | greenCym) != 0) || ((buttons & XboxGamepadButton.A) != 0));
+
+ // Pad flag
+ device.SetButtonState(Xbox360Button.RightThumb,
+ (redPad | yellowPad | bluePad | greenPad) != 0);
+ // Cymbal flag
+ device.SetButtonState(Xbox360Button.RightShoulder,
+ (yellowCym | blueCym | greenCym) != 0);
+
+ // Pedals
+ device.SetButtonState(Xbox360Button.LeftShoulder,
+ (report.Buttons & (ushort)XboxDrumButton.KickOne) != 0);
+ device.SetButtonState(Xbox360Button.LeftThumb,
+ (report.Buttons & (ushort)XboxDrumButton.KickTwo) != 0);
+
+ // Velocities
+ device.SetAxisValue(
+ Xbox360Axis.LeftThumbX,
+ ByteToVelocity(redPad)
+ );
+ device.SetAxisValue(
+ Xbox360Axis.LeftThumbY,
+ ByteToVelocityNegative((byte)(yellowPad | yellowCym))
+ );
+ device.SetAxisValue(
+ Xbox360Axis.RightThumbX,
+ ByteToVelocity((byte)(bluePad | blueCym))
+ );
+ device.SetAxisValue(
+ Xbox360Axis.RightThumbY,
+ ByteToVelocityNegative((byte)(greenPad | greenCym))
+ );
+ }
+
+ // Maps with drums and cymbals fully separated
+ internal static void MapDrums_Individual(IXbox360Controller device, in XboxDrumInput report)
+ {
+ byte redPad = report.RedPad;
+ byte yellowPad = report.YellowPad;
+ byte bluePad = report.BluePad;
+ byte greenPad = report.GreenPad;
+
+ byte yellowCym = report.YellowCymbal;
+ byte blueCym = report.BlueCymbal;
+ byte greenCym = report.GreenCymbal;
+
+ // Pads/face buttons
+ var buttons = (XboxGamepadButton)report.Buttons;
+ device.SetButtonState(Xbox360Button.B, (redPad != 0) || ((buttons & XboxGamepadButton.B) != 0));
+ device.SetButtonState(Xbox360Button.Y, (yellowPad != 0) || ((buttons & XboxGamepadButton.Y) != 0));
+ device.SetButtonState(Xbox360Button.X, (bluePad != 0) || ((buttons & XboxGamepadButton.X) != 0));
+ device.SetButtonState(Xbox360Button.A, (greenPad != 0) || ((buttons & XboxGamepadButton.A) != 0));
+
+ // Cymbals
+ device.SetButtonState(Xbox360Button.LeftThumb, yellowCym != 0);
+ device.SetButtonState(Xbox360Button.RightThumb, blueCym != 0);
+ device.SetButtonState(Xbox360Button.RightShoulder, greenCym != 0);
+
+ // Pedals
+ device.SetButtonState(Xbox360Button.LeftShoulder,
+ (report.Buttons & (ushort)XboxDrumButton.KickOne) != 0);
+ // Left trigger for second kick, we're all out of available buttons at this point
+ device.SetSliderValue(Xbox360Slider.LeftTrigger,
+ (report.Buttons & (ushort)XboxDrumButton.KickTwo) != 0 ? byte.MaxValue : byte.MinValue);
+
+ // No velocities, too many to map to 5 axes
+ }
+
+ private static short ByteToVelocity(byte value)
+ {
+ // Scale the value to fill the byte
+ value = (byte)(value * 0x11);
+
+ // Bitwise invert to flip the value, then shift down one to exclude the sign bit
+ int scaled = ~value.ScaleToUInt16();
+ return (short)(scaled >> 1);
+ }
+
+ private static short ByteToVelocityNegative(byte value)
+ {
+ // Scale the value to fill the byte
+ value = (byte)(value * 0x11);
+
+ // Bitwise invert to flip the value, then shift down one to exclude the sign bit, then add our own
+ int scaled = ~value.ScaleToUInt16();
+ return (short)((scaled >> 1) | 0x8000);
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/FallbackViGEmMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/FallbackViGEmMapper.cs
new file mode 100644
index 0000000..8abb99a
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/FallbackViGEmMapper.cs
@@ -0,0 +1,71 @@
+using System;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// The ViGEmBus mapper used when device type could not be determined. Maps based on report length.
+ ///
+ internal class FallbackViGEmMapper : ViGEmMapper
+ {
+ public FallbackViGEmMapper(IBackendClient client)
+ : base(client)
+ {
+ }
+
+ protected override unsafe XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxGuitarInput.CommandId:
+ // These have the same value
+ // case XboxDrumInput.CommandId:
+ // #if ENABLE_GAMEPAD_MAPPING
+ // case XboxGamepadInput.CommandId:
+ // #endif
+ return ParseInput(data);
+
+ case XboxGHLGuitarInput.CommandId:
+ // Deliberately limit to the exact size
+ if (data.Length != sizeof(XboxGHLGuitarInput) || !ParsingUtils.TryRead(data, out XboxGHLGuitarInput guitarReport))
+ return XboxResult.InvalidMessage;
+
+ GHLGuitarViGEmMapper.HandleReport(device, guitarReport);
+ return SubmitReport();
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ // The previous state of the yellow/blue cymbals
+ private int previousDpadCymbals;
+ // The current state of the d-pad mask from the hit yellow/blue cymbals
+ private int dpadMask;
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (data.Length == sizeof(XboxGuitarInput) && ParsingUtils.TryRead(data, out XboxGuitarInput guitarReport))
+ {
+ GuitarViGEmMapper.HandleReport(device, guitarReport);
+ }
+ else if (data.Length == sizeof(XboxDrumInput) && ParsingUtils.TryRead(data, out XboxDrumInput drumReport))
+ {
+ DrumsViGEmMapper.HandleReport(device, drumReport, ref previousDpadCymbals, ref dpadMask);
+ }
+#if ENABLE_GAMEPAD_MAPPING
+ else if (data.Length == sizeof(XboxGamepadInput) && ParsingUtils.TryRead(data, out XboxGamepadInput gamepadReport))
+ {
+ GamepadViGEmMapper.HandleReport(device, gamepadReport);
+ }
+#endif
+ else
+ {
+ // Not handled
+ return XboxResult.Success;
+ }
+
+ return SubmitReport();
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/GHLGuitarViGEmMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/GHLGuitarViGEmMapper.cs
new file mode 100644
index 0000000..5ce452a
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/GHLGuitarViGEmMapper.cs
@@ -0,0 +1,105 @@
+using System;
+using Nefarius.ViGEm.Client.Targets;
+using Nefarius.ViGEm.Client.Targets.Xbox360;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps GHL guitar inputs to a ViGEmBus device.
+ ///
+ internal class GHLGuitarViGEmMapper : ViGEmMapper
+ {
+ private XboxGHLGuitarKeepAlive keepAlive;
+
+ public GHLGuitarViGEmMapper(IBackendClient client)
+ : base(client)
+ {
+ keepAlive = new XboxGHLGuitarKeepAlive(client);
+
+ // Set player LED
+ var setLeds = XboxGHLGuitarSetPlayerLeds.Create(GetPlayerLeds());
+ client.SendMessage(setLeds);
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxGHLGuitarInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxGHLGuitarInput guitarReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(device, guitarReport);
+ return SubmitReport();
+ }
+
+ private XboxGHLGuitarPlayerLeds GetPlayerLeds()
+ {
+ switch (userIndex)
+ {
+ case 1: return XboxGHLGuitarPlayerLeds.Player1;
+ case 2: return XboxGHLGuitarPlayerLeds.Player2;
+ case 3: return XboxGHLGuitarPlayerLeds.Player3;
+ case 4: return XboxGHLGuitarPlayerLeds.Player4;
+
+ default:
+ return XboxGHLGuitarPlayerLeds.All;
+ }
+ }
+
+ ///
+ /// Maps guitar input data to an Xbox 360 controller.
+ ///
+ internal static void HandleReport(IXbox360Controller device, in XboxGHLGuitarInput report)
+ {
+ // Menu buttons
+ device.SetButtonState(Xbox360Button.Start, report.Pause);
+ device.SetButtonState(Xbox360Button.Back, report.HeroPower);
+ device.SetButtonState(Xbox360Button.LeftThumb, report.GHTV);
+
+ // Dpad/strum
+ device.SetButtonState(Xbox360Button.Up, report.DpadUp | report.StrumUp);
+ device.SetButtonState(Xbox360Button.Down, report.DpadDown | report.StrumDown);
+ device.SetButtonState(Xbox360Button.Left, report.DpadLeft);
+ device.SetButtonState(Xbox360Button.Right, report.DpadRight);
+
+ short strum = report.StrumUp ? short.MaxValue
+ : report.StrumDown ? short.MinValue
+ : (short)0;
+ device.SetAxisValue(Xbox360Axis.LeftThumbY, strum);
+
+ // Frets
+ device.SetButtonState(Xbox360Button.A, report.Black1);
+ device.SetButtonState(Xbox360Button.B, report.Black2);
+ device.SetButtonState(Xbox360Button.Y, report.Black3);
+ device.SetButtonState(Xbox360Button.X, report.White1);
+ device.SetButtonState(Xbox360Button.LeftShoulder, report.White2);
+ device.SetButtonState(Xbox360Button.RightShoulder, report.White3);
+
+ // Whammy
+ // Swapped compared to other guitars; also rests at center instead of negative end
+ device.SetAxisValue(Xbox360Axis.RightThumbY, report.WhammyBar.ScaleToInt16());
+ // Tilt
+ // Swapped compared to other guitars
+ device.SetAxisValue(Xbox360Axis.RightThumbX, report.Tilt.ScaleToInt16());
+ }
+
+ protected override void DisposeManagedResources()
+ {
+ base.DisposeManagedResources();
+
+ keepAlive?.Dispose();
+ keepAlive = null;
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/GamepadViGEmMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/GamepadViGEmMapper.cs
new file mode 100644
index 0000000..e391067
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/GamepadViGEmMapper.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Diagnostics;
+using Nefarius.ViGEm.Client.Targets;
+using Nefarius.ViGEm.Client.Targets.Xbox360;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps gamepad inputs to a ViGEmBus device.
+ ///
+ internal class GamepadViGEmMapper : ViGEmMapper
+ {
+ private bool rumbling;
+ private Stopwatch rumbleCooldown = new Stopwatch();
+
+ public GamepadViGEmMapper(IBackendClient client)
+ : base(client)
+ {
+ rumbleCooldown.Start();
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxGamepadInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxGamepadInput gamepadReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(device, gamepadReport);
+
+ // Rumble, for output testing
+ float x = Math.Abs(gamepadReport.LeftStickX / 32768f);
+ float y = Math.Abs(gamepadReport.LeftStickY / 32768f);
+ float max = Math.Max(x, y);
+ if (max > 0.1f)
+ {
+ if (rumbleCooldown.ElapsedMilliseconds > 50)
+ {
+ rumbling = true;
+ byte left = (byte)(x * 255);
+ byte right = (byte)(y * 255);
+ client.SendMessage(XboxGamepadRumble.Create(left, right));
+ rumbleCooldown.Restart();
+ }
+ }
+ else if (rumbling)
+ {
+ rumbling = false;
+ client.SendMessage(XboxGamepadRumble.Create(0, 0));
+ }
+
+ return SubmitReport();
+ }
+
+ ///
+ /// Maps gamepad input data to an Xbox 360 controller.
+ ///
+ internal static void HandleReport(IXbox360Controller device, in XboxGamepadInput report)
+ {
+ // Face buttons
+ device.SetButtonState(Xbox360Button.A, report.A);
+ device.SetButtonState(Xbox360Button.B, report.B);
+ device.SetButtonState(Xbox360Button.X, report.X);
+ device.SetButtonState(Xbox360Button.Y, report.Y);
+
+ // Dpad
+ device.SetButtonState(Xbox360Button.Up, report.DpadUp);
+ device.SetButtonState(Xbox360Button.Down, report.DpadDown);
+ device.SetButtonState(Xbox360Button.Left, report.DpadLeft);
+ device.SetButtonState(Xbox360Button.Right, report.DpadRight);
+
+ // Dpad
+ device.SetButtonState(Xbox360Button.LeftShoulder, report.LeftBumper);
+ device.SetButtonState(Xbox360Button.RightShoulder, report.RightBumper);
+ device.SetButtonState(Xbox360Button.LeftThumb, report.LeftStickPress);
+ device.SetButtonState(Xbox360Button.RightThumb, report.RightStickPress);
+
+ // Menu and Options
+ device.SetButtonState(Xbox360Button.Start, report.Menu);
+ device.SetButtonState(Xbox360Button.Back, report.Options);
+
+ // Sticks
+ device.SetAxisValue(Xbox360Axis.LeftThumbX, report.LeftStickX);
+ device.SetAxisValue(Xbox360Axis.LeftThumbY, report.LeftStickY);
+ device.SetAxisValue(Xbox360Axis.RightThumbX, report.RightStickX);
+ device.SetAxisValue(Xbox360Axis.RightThumbY, report.RightStickY);
+
+ // Triggers
+ device.SetSliderValue(Xbox360Slider.LeftTrigger, (byte)(report.LeftTrigger >> 2));
+ device.SetSliderValue(Xbox360Slider.RightTrigger, (byte)(report.RightTrigger >> 2));
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/GuitarViGEmMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/GuitarViGEmMapper.cs
new file mode 100644
index 0000000..6368d53
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/GuitarViGEmMapper.cs
@@ -0,0 +1,87 @@
+using System;
+using Nefarius.ViGEm.Client.Targets;
+using Nefarius.ViGEm.Client.Targets.Xbox360;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps guitar inputs to a ViGEmBus device.
+ ///
+ internal class GuitarViGEmMapper : ViGEmMapper
+ {
+ public GuitarViGEmMapper(IBackendClient client)
+ : base(client)
+ {
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxGuitarInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxGuitarInput guitarReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(device, guitarReport);
+
+ // Send data
+ return SubmitReport();
+ }
+
+ ///
+ /// Maps guitar input data to an Xbox 360 controller.
+ ///
+ internal static void HandleReport(IXbox360Controller device, in XboxGuitarInput report)
+ {
+ // Menu and Options
+ var buttons = (XboxGamepadButton)report.Buttons;
+ device.SetButtonState(Xbox360Button.Start, (buttons & XboxGamepadButton.Menu) != 0);
+ device.SetButtonState(Xbox360Button.Back, (buttons & XboxGamepadButton.Options) != 0);
+
+ // Dpad
+ device.SetButtonState(Xbox360Button.Up, (buttons & XboxGamepadButton.DpadUp) != 0);
+ device.SetButtonState(Xbox360Button.Down, (buttons & XboxGamepadButton.DpadDown) != 0);
+ device.SetButtonState(Xbox360Button.Left, (buttons & XboxGamepadButton.DpadLeft) != 0);
+ device.SetButtonState(Xbox360Button.Right, (buttons & XboxGamepadButton.DpadRight) != 0);
+
+ // Frets
+ device.SetButtonState(Xbox360Button.A, report.Green);
+ device.SetButtonState(Xbox360Button.B, report.Red);
+ device.SetButtonState(Xbox360Button.Y, report.Yellow);
+ device.SetButtonState(Xbox360Button.X, report.Blue);
+ device.SetButtonState(Xbox360Button.LeftShoulder, report.Orange);
+
+ // Lower fret flag
+ device.SetButtonState(Xbox360Button.LeftThumb, report.LowerFretsPressed);
+
+ // Whammy
+ device.SetAxisValue(Xbox360Axis.RightThumbX, report.WhammyBar.ScaleToInt16());
+ // Tilt
+ device.SetAxisValue(Xbox360Axis.RightThumbY, report.Tilt.ScaleToInt16_Positive());
+ // Pickup Switch
+ device.SetSliderValue(Xbox360Slider.LeftTrigger, CalculatePickupSwitch(report.PickupSwitch));
+ }
+
+ internal static byte CalculatePickupSwitch(byte rawValue)
+ {
+ // Fix up value to be 1-5
+ rawValue >>= 4;
+ rawValue++;
+
+ // Divide the range of a byte into 5 equal ranges,
+ // and use the midpoints of those ranges as the final values
+ const byte rangeDivision = byte.MaxValue / 5;
+ return (byte)((rangeDivision * rawValue) - (rangeDivision / 2));
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/RiffmasterViGEmMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/RiffmasterViGEmMapper.cs
new file mode 100644
index 0000000..cc7f194
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/RiffmasterViGEmMapper.cs
@@ -0,0 +1,55 @@
+using System;
+using Nefarius.ViGEm.Client.Targets;
+using Nefarius.ViGEm.Client.Targets.Xbox360;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps Riffmaster guitar inputs to a ViGEmBus device.
+ ///
+ internal class RiffmasterViGEmMapper : ViGEmMapper
+ {
+ public RiffmasterViGEmMapper(IBackendClient client)
+ : base(client)
+ {
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxRiffmasterInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxRiffmasterInput guitarReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(device, guitarReport);
+
+ // Send data
+ return SubmitReport();
+ }
+
+ ///
+ /// Maps guitar input data to an Xbox 360 controller.
+ ///
+ internal static void HandleReport(IXbox360Controller device, in XboxRiffmasterInput report)
+ {
+ // Guitar inputs
+ GuitarViGEmMapper.HandleReport(device, report.Base);
+
+ // Joystick
+ device.SetAxisValue(Xbox360Axis.LeftThumbX, report.JoystickX);
+ device.SetAxisValue(Xbox360Axis.LeftThumbY, report.JoystickY);
+ device.SetButtonState(Xbox360Button.LeftThumb, report.JoystickClick | report.Base.LowerFretsPressed);
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/ViGEmMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/ViGEmMapper.cs
new file mode 100644
index 0000000..008f20f
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/ViGEm/ViGEmMapper.cs
@@ -0,0 +1,93 @@
+using Nefarius.ViGEm.Client.Targets;
+using Nefarius.ViGEm.Client.Targets.Xbox360;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// A mapper that maps to a ViGEmBus device.
+ ///
+ internal abstract class ViGEmMapper : DeviceMapper
+ {
+ ///
+ /// The device to map to.
+ ///
+ protected IXbox360Controller device;
+
+ ///
+ /// Whether or not the emulated Xbox 360 controller has connected fully.
+ ///
+ protected bool deviceConnected = false;
+
+ ///
+ /// The LED number for the emulated Xbox 360 controller.
+ ///
+ protected byte userIndex;
+
+ public ViGEmMapper(IBackendClient client)
+ : base(client)
+ {
+ device = ViGEmInstance.CreateDevice();
+ device.FeedbackReceived += DeviceConnected;
+ device.Connect();
+ device.AutoSubmitReport = false;
+ }
+
+ // Temporary event handler to ensure device connection
+ private void DeviceConnected(object sender, Xbox360FeedbackReceivedEventArgs args)
+ {
+ // Device has connected
+ deviceConnected = true;
+
+ // Log the user index
+ userIndex = args.LedNumber;
+ Logging.WriteLineVerbose($"Created new ViGEmBus device with user index {userIndex}");
+
+ // Unregister the event handler
+ (sender as IXbox360Controller).FeedbackReceived -= DeviceConnected;
+ }
+
+ protected override void MapGuideButton(bool pressed)
+ {
+ device.SetButtonState(Xbox360Button.Guide, pressed);
+ device.SubmitReport();
+ }
+
+ protected XboxResult SubmitReport()
+ {
+ if (!deviceConnected)
+ return XboxResult.Pending;
+
+ device.SubmitReport();
+ return XboxResult.Success;
+ }
+
+ public override void ResetReport()
+ {
+ try
+ {
+ device.ResetReport();
+ device.SubmitReport();
+ }
+ catch { }
+ }
+
+ protected override void DisposeManagedResources()
+ {
+ if (device != null)
+ {
+ // Reset report
+ ResetReport();
+
+ // Disconnect device
+ try
+ {
+ device.Disconnect();
+ }
+ catch { }
+ }
+
+ device = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/WirelessLegacyMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/WirelessLegacyMapper.cs
new file mode 100644
index 0000000..7f037a1
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/WirelessLegacyMapper.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps devices connected to a RB4 wireless legacy adapter to virtual controllers.
+ ///
+ internal class WirelessLegacyMapper : DeviceMapper
+ {
+ private struct SubMapper
+ {
+ public XboxWirelessLegacyDeviceType DeviceType;
+ public DeviceMapper Mapper;
+ }
+
+ // Mappers are not guaranteed to be created for each device, unknown subtypes will be ignored and have none
+ private readonly Dictionary mappers = new Dictionary();
+
+ public WirelessLegacyMapper(IBackendClient client)
+ : base(client)
+ {
+ client.SendMessage(XboxWirelessLegacyRequestDevices.RequestDevices);
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxWirelessLegacyInputHeader.CommandId:
+ return HandleInput(data);
+
+ case XboxWirelessLegacyDeviceConnect.CommandId:
+ return HandleConnection(data);
+
+ case XboxWirelessLegacyDeviceDisconnect.CommandId:
+ return HandleDisconnection(data);
+ }
+
+ return XboxResult.Success;
+ }
+
+ private unsafe XboxResult HandleInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxWirelessLegacyInputHeader header))
+ return XboxResult.InvalidMessage;
+
+ // Find the mapper for the given user index
+ byte userIndex = header.UserIndex;
+ if (!mappers.TryGetValue(userIndex, out var subMapper))
+ {
+ Logging.WriteLineVerbose($"Missing mapper for wireless legacy user index {userIndex}!");
+ return XboxResult.InvalidMessage;
+ }
+
+ // Verify the device type
+ if (subMapper.DeviceType != header.DeviceType)
+ {
+ Logging.WriteLineVerbose($"Wrong input type for wireless legacy user index {userIndex}! Expected {subMapper.DeviceType}, got {header.DeviceType}");
+ return XboxResult.InvalidMessage;
+ }
+
+ // Slice off the header and pass the rest of the data to the mapper
+ // The data afterwards has its own copy of the button mask, so we don't need to do anything else
+ data = data.Slice(sizeof(XboxWirelessLegacyInputHeader));
+ return subMapper.Mapper?.HandleMessage(XboxWirelessLegacyInputHeader.CommandId, data) ?? XboxResult.Success;
+ }
+
+ private XboxResult HandleConnection(ReadOnlySpan data)
+ {
+ if (!XboxWirelessLegacyDeviceConnect.TryParse(data, out var connect))
+ return XboxResult.InvalidMessage;
+
+ // Check if a mapper already exists for the given user index
+ byte userIndex = connect.UserIndex;
+ if (mappers.TryGetValue(userIndex, out var subMapper))
+ {
+ Logging.WriteLineVerbose($"Mapper already exists for legacy adapter user index {userIndex}! Overwriting.");
+ subMapper.Mapper?.Dispose();
+ }
+
+ // Add a new mapper for the device
+ mappers[userIndex] = new SubMapper() { DeviceType = connect.DeviceType, Mapper = GetMapperForDevice(connect) };
+ return XboxResult.Success;
+ }
+
+ private unsafe XboxResult HandleDisconnection(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxWirelessLegacyDeviceDisconnect disconnect))
+ return XboxResult.InvalidMessage;
+
+ // Find the mapper for the given user index
+ byte userIndex = disconnect.UserIndex;
+ if (!mappers.TryGetValue(userIndex, out var subMapper))
+ {
+ Logging.WriteLineVerbose($"Missing mapper for legacy adapter user index {userIndex}!");
+ return XboxResult.InvalidMessage;
+ }
+
+ // Remove the mapper
+ subMapper.Mapper?.Dispose();
+ mappers.Remove(userIndex);
+ return XboxResult.Success;
+ }
+
+ public override XboxResult HandleKeystroke(XboxKeystroke key)
+ {
+ foreach (var subMapper in mappers.Values)
+ {
+ var result = subMapper.Mapper.HandleKeystroke(key);
+ if (result != XboxResult.Success)
+ return result;
+ }
+
+ return XboxResult.Success;
+ }
+
+ // Handled by the override above
+ protected override void MapGuideButton(bool pressed) { }
+
+ public override void ResetReport()
+ {
+ foreach (var subMapper in mappers.Values)
+ {
+ subMapper.Mapper.ResetReport();
+ }
+ }
+
+ private DeviceMapper GetMapperForDevice(XboxWirelessLegacyDeviceConnect connect)
+ {
+ var type = connect.DeviceType;
+ switch (type)
+ {
+ case XboxWirelessLegacyDeviceType.Guitar:
+ return MapperFactory.GetGuitarMapper(client);
+
+ case XboxWirelessLegacyDeviceType.Drums:
+ return MapperFactory.GetDrumsMapper(client);
+
+ default:
+ Logging.WriteLine($"User index {connect.UserIndex + 1} on the legacy adapter has an unsupported device type {type} (XInput subtype {connect.XInputSubType})!");
+ Logging.WriteLine("If you think it should be supported, restart capture with packet logging to a file enabled, go through all of the inputs, and create a GitHub issue with the log file attached.");
+ return null;
+ }
+ }
+
+ protected override void DisposeManagedResources()
+ {
+ foreach (var subMapper in mappers.Values)
+ {
+ subMapper.Mapper.Dispose();
+ }
+
+ mappers.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/DrumsvJoyMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/DrumsvJoyMapper.cs
new file mode 100644
index 0000000..3fa5b33
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/DrumsvJoyMapper.cs
@@ -0,0 +1,73 @@
+using System;
+using RB4InstrumentMapper.Core.Parsing;
+using vJoyInterfaceWrap;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps drumkit inputs to a vJoy device.
+ ///
+ internal class DrumsvJoyMapper : vJoyMapper
+ {
+ public DrumsvJoyMapper(IBackendClient client)
+ : base(client)
+ {
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxDrumInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxDrumInput guitarReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(ref state, guitarReport);
+ return SubmitReport();
+ }
+
+ ///
+ /// Maps drumkit input data to a vJoy device.
+ ///
+ internal static void HandleReport(ref vJoy.JoystickState state, XboxDrumInput report)
+ {
+ // Menu and Options
+ var buttons = (XboxGamepadButton)report.Buttons;
+ state.SetButton(vJoyButton.Fifteen, (buttons & XboxGamepadButton.Menu) != 0);
+ state.SetButton(vJoyButton.Sixteen, (buttons & XboxGamepadButton.Options) != 0);
+
+ // D-pad
+ ParseDpad(ref state, buttons);
+
+ // Face buttons
+ state.SetButton(vJoyButton.Four, (buttons & XboxGamepadButton.A) != 0);
+ state.SetButton(vJoyButton.One, (buttons & XboxGamepadButton.B) != 0);
+ state.SetButton(vJoyButton.Three, (buttons & XboxGamepadButton.X) != 0);
+ state.SetButton(vJoyButton.Two, (buttons & XboxGamepadButton.Y) != 0);
+
+ // Pads
+ state.SetButton(vJoyButton.One, report.RedPad != 0);
+ state.SetButton(vJoyButton.Two, report.YellowPad != 0);
+ state.SetButton(vJoyButton.Three, report.BluePad != 0);
+ state.SetButton(vJoyButton.Four, report.GreenPad != 0);
+
+ // Cymbals
+ state.SetButton(vJoyButton.Six, report.YellowCymbal != 0);
+ state.SetButton(vJoyButton.Seven, report.BlueCymbal != 0);
+ state.SetButton(vJoyButton.Eight, report.GreenCymbal != 0);
+
+ // Kick pedals
+ state.SetButton(vJoyButton.Five, (report.Buttons & (ushort)XboxDrumButton.KickOne) != 0);
+ state.SetButton(vJoyButton.Nine, (report.Buttons & (ushort)XboxDrumButton.KickTwo) != 0);
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/FallbackvJoyMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/FallbackvJoyMapper.cs
new file mode 100644
index 0000000..4ce8b4a
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/FallbackvJoyMapper.cs
@@ -0,0 +1,66 @@
+using System;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// The vJoy mapper used when device type could not be determined. Maps based on report length.
+ ///
+ internal class FallbackvJoyMapper : vJoyMapper
+ {
+ public FallbackvJoyMapper(IBackendClient client)
+ : base(client)
+ {
+ }
+
+ protected override unsafe XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxGuitarInput.CommandId:
+ // These have the same value
+ // case XboxDrumInput.CommandId:
+ // #if ENABLE_GAMEPAD_MAPPING
+ // case XboxGamepadInput.CommandId:
+ // #endif
+ return ParseInput(data);
+
+ case XboxGHLGuitarInput.CommandId:
+ // Deliberately limit to the exact size
+ if (data.Length != sizeof(XboxGHLGuitarInput) || !ParsingUtils.TryRead(data, out XboxGHLGuitarInput guitarReport))
+ return XboxResult.InvalidMessage;
+
+ GHLGuitarvJoyMapper.HandleReport(ref state, guitarReport);
+ return XboxResult.Success;
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (data.Length == sizeof(XboxGuitarInput) && ParsingUtils.TryRead(data, out XboxGuitarInput guitarReport))
+ {
+ GuitarvJoyMapper.HandleReport(ref state, guitarReport);
+ }
+ else if (data.Length == sizeof(XboxDrumInput) && ParsingUtils.TryRead(data, out XboxDrumInput drumReport))
+ {
+ DrumsvJoyMapper.HandleReport(ref state, drumReport);
+ }
+#if ENABLE_GAMEPAD_MAPPING
+ else if (data.Length == sizeof(XboxGamepadInput) && ParsingUtils.TryRead(data, out XboxGamepadInput gamepadReport))
+ {
+ GamepadvJoyMapper.HandleReport(ref state, gamepadReport);
+ }
+#endif
+ else
+ {
+ // Not handled
+ return XboxResult.Success;
+ }
+
+ return SubmitReport();
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/GHLGuitarvJoyMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/GHLGuitarvJoyMapper.cs
new file mode 100644
index 0000000..0cbac72
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/GHLGuitarvJoyMapper.cs
@@ -0,0 +1,118 @@
+using System;
+using RB4InstrumentMapper.Core.Parsing;
+using vJoyInterfaceWrap;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps GHL guitar inputs to a vJoy device.
+ ///
+ internal class GHLGuitarvJoyMapper : vJoyMapper
+ {
+ private XboxGHLGuitarKeepAlive keepAlive;
+
+ public GHLGuitarvJoyMapper(IBackendClient client)
+ : base(client)
+ {
+ keepAlive = new XboxGHLGuitarKeepAlive(client);
+
+ // Set player LED
+ var setLeds = XboxGHLGuitarSetPlayerLeds.Create(GetPlayerLeds());
+ client.SendMessage(setLeds);
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxGHLGuitarInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxGHLGuitarInput guitarReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(ref state, guitarReport);
+ return SubmitReport();
+ }
+
+ private XboxGHLGuitarPlayerLeds GetPlayerLeds()
+ {
+ switch (deviceId)
+ {
+ case 1: return XboxGHLGuitarPlayerLeds.Player1;
+ case 2: return XboxGHLGuitarPlayerLeds.Player2;
+ case 3: return XboxGHLGuitarPlayerLeds.Player3;
+ case 4: return XboxGHLGuitarPlayerLeds.Player4;
+ case 5: return XboxGHLGuitarPlayerLeds.Player5;
+ case 6: return XboxGHLGuitarPlayerLeds.Player6;
+ case 7: return XboxGHLGuitarPlayerLeds.Player7;
+ case 8: return XboxGHLGuitarPlayerLeds.Player8;
+ case 9: return XboxGHLGuitarPlayerLeds.Player9;
+ case 10: return XboxGHLGuitarPlayerLeds.Player10;
+ case 11: return XboxGHLGuitarPlayerLeds.Player11;
+ case 12: return XboxGHLGuitarPlayerLeds.Player12;
+ case 13: return XboxGHLGuitarPlayerLeds.Player13;
+ case 14: return XboxGHLGuitarPlayerLeds.Player14;
+ case 15: return XboxGHLGuitarPlayerLeds.Player15;
+
+ // If someone connects this many devices it's their problem lol
+ case 16:
+ default:
+ return XboxGHLGuitarPlayerLeds.All;
+ }
+ }
+
+ ///
+ /// Maps GHL guitar input data to a vJoy device.
+ ///
+ internal static void HandleReport(ref vJoy.JoystickState state, XboxGHLGuitarInput report)
+ {
+ // Menu buttons
+ state.SetButton(vJoyButton.Fifteen, report.Pause);
+ state.SetButton(vJoyButton.Sixteen, report.HeroPower);
+ state.SetButton(vJoyButton.Twelve, report.GHTV);
+
+ // D-pad/strum
+ // It would be more efficient to directly map the GHL value to the vJoy value,
+ // but that doesn't account for the strum bar being on its own axis
+ XboxGamepadButton dpad = 0;
+ if (report.DpadUp | report.StrumUp) dpad |= XboxGamepadButton.DpadUp;
+ if (report.DpadDown | report.StrumDown) dpad |= XboxGamepadButton.DpadDown;
+ if (report.DpadLeft) dpad |= XboxGamepadButton.DpadLeft;
+ if (report.DpadRight) dpad |= XboxGamepadButton.DpadRight;
+ ParseDpad(ref state, dpad);
+
+ // Frets
+ state.SetButton(vJoyButton.One, report.Black1);
+ state.SetButton(vJoyButton.Two, report.Black2);
+ state.SetButton(vJoyButton.Three, report.Black3);
+ state.SetButton(vJoyButton.Four, report.White1);
+ state.SetButton(vJoyButton.Five, report.White2);
+ state.SetButton(vJoyButton.Six, report.White3);
+
+ // Whammy
+ // Value ranges from 128 (not pressed) to 255 (fully pressed)
+ byte whammy = (byte)((report.WhammyBar - 0x80) * 2);
+ SetAxis(ref state.AxisY, whammy);
+
+ // Tilt
+ // Value ranges from 0 to 255
+ SetAxis(ref state.AxisZ, report.Tilt);
+ }
+
+ protected override void DisposeManagedResources()
+ {
+ base.DisposeManagedResources();
+
+ keepAlive?.Dispose();
+ keepAlive = null;
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/GamepadvJoyMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/GamepadvJoyMapper.cs
new file mode 100644
index 0000000..b61420d
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/GamepadvJoyMapper.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Diagnostics;
+using RB4InstrumentMapper.Core.Parsing;
+using vJoyInterfaceWrap;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps gamepad inputs to a vJoy device.
+ ///
+ internal class GamepadvJoyMapper : vJoyMapper
+ {
+ private bool rumbling;
+ private Stopwatch rumbleCooldown = new Stopwatch();
+
+ public GamepadvJoyMapper(IBackendClient client)
+ : base(client)
+ {
+ rumbleCooldown.Start();
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxGamepadInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxGamepadInput gamepadReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(ref state, gamepadReport);
+
+ // Rumble, for output testing
+ short x = Math.Abs(gamepadReport.LeftStickX);
+ short y = Math.Abs(gamepadReport.LeftStickY);
+ short max = Math.Max(x, y);
+ if (max > (short.MaxValue / 10f))
+ {
+ if (rumbleCooldown.ElapsedMilliseconds > 50)
+ {
+ rumbling = true;
+ byte left = (byte)(x >> 8);
+ byte right = (byte)(y >> 8);
+ client.SendMessage(XboxGamepadRumble.Create(left, right));
+ rumbleCooldown.Restart();
+ }
+ }
+ else if (rumbling)
+ {
+ rumbling = false;
+ client.SendMessage(XboxGamepadRumble.Create(0, 0));
+ }
+
+ return SubmitReport();
+ }
+
+ ///
+ /// Maps gamepad input data to a vJoy device.
+ ///
+ internal static void HandleReport(ref vJoy.JoystickState state, XboxGamepadInput report)
+ {
+ // Buttons and axes are mapped the same way as they display in joy.cpl when used normally
+
+ // Buttons
+ state.SetButton(vJoyButton.One, report.A);
+ state.SetButton(vJoyButton.Two, report.B);
+ state.SetButton(vJoyButton.Three, report.X);
+ state.SetButton(vJoyButton.Four, report.Y);
+
+ state.SetButton(vJoyButton.Five, report.LeftBumper);
+ state.SetButton(vJoyButton.Six, report.RightBumper);
+
+ state.SetButton(vJoyButton.Seven, report.Options);
+ state.SetButton(vJoyButton.Eight, report.Menu);
+
+ state.SetButton(vJoyButton.Nine, report.LeftStickPress);
+ state.SetButton(vJoyButton.Ten, report.RightStickPress);
+
+ // D-pad
+ ParseDpad(ref state, (XboxGamepadButton)report.Buttons);
+
+ // Left stick
+ SetAxis(ref state.AxisX, report.LeftStickX);
+ SetAxisInverted(ref state.AxisY, report.LeftStickY);
+
+ // Triggers
+ // These are both combined into a single axis
+ int triggerAxis = (report.LeftTrigger - report.RightTrigger) * 0x20;
+ SetAxis(ref state.AxisZ, (short)triggerAxis);
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/GuitarvJoyMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/GuitarvJoyMapper.cs
new file mode 100644
index 0000000..0152301
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/GuitarvJoyMapper.cs
@@ -0,0 +1,72 @@
+using System;
+using RB4InstrumentMapper.Core.Parsing;
+using vJoyInterfaceWrap;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Maps guitar inputs to a vJoy device.
+ ///
+ internal class GuitarvJoyMapper : vJoyMapper
+ {
+ public GuitarvJoyMapper(IBackendClient client)
+ : base(client)
+ {
+ }
+
+ protected override XboxResult OnMessageReceived(byte command, ReadOnlySpan data)
+ {
+ switch (command)
+ {
+ case XboxGuitarInput.CommandId:
+ return ParseInput(data);
+
+ default:
+ return XboxResult.Success;
+ }
+ }
+
+ private unsafe XboxResult ParseInput(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxGuitarInput guitarReport))
+ return XboxResult.InvalidMessage;
+
+ HandleReport(ref state, guitarReport);
+ return SubmitReport();
+ }
+
+ ///
+ /// Maps guitar input data to a vJoy device.
+ ///
+ internal static void HandleReport(ref vJoy.JoystickState state, XboxGuitarInput report)
+ {
+ // Menu and Options
+ var buttons = (XboxGamepadButton)report.Buttons;
+ state.SetButton(vJoyButton.Fifteen, (buttons & XboxGamepadButton.Menu) != 0);
+ state.SetButton(vJoyButton.Sixteen, (buttons & XboxGamepadButton.Options) != 0);
+
+ // D-pad
+ ParseDpad(ref state, buttons);
+
+ state.SetButton(vJoyButton.One, report.Green);
+ state.SetButton(vJoyButton.Two, report.Red);
+ state.SetButton(vJoyButton.Three, report.Yellow);
+ state.SetButton(vJoyButton.Four, report.Blue);
+ state.SetButton(vJoyButton.Five, report.Orange);
+
+ // Whammy
+ // Value ranges from 0 (not pressed) to 255 (fully pressed)
+ SetAxis(ref state.AxisY, report.WhammyBar);
+
+ // Tilt
+ // Value ranges from 0 to 255
+ // It seems to have a threshold of around 0x70 though,
+ // after a certain point values will get floored to 0
+ SetAxis(ref state.AxisZ, report.Tilt);
+
+ // Pickup switch
+ // Reported values are 0x00, 0x10, 0x20, 0x30, and 0x40 (ranges from 0 to 64)
+ state.AxisX = report.PickupSwitch * 0x200;
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/vJoyMapper.cs b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/vJoyMapper.cs
new file mode 100644
index 0000000..5817e66
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/Mappers/vJoy/vJoyMapper.cs
@@ -0,0 +1,129 @@
+using vJoyInterfaceWrap;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// A mapper that maps to a vJoy device.
+ ///
+ internal abstract class vJoyMapper : DeviceMapper
+ {
+ protected vJoy.JoystickState state = new vJoy.JoystickState();
+ protected uint deviceId = 0;
+
+ public vJoyMapper(IBackendClient client)
+ : base(client)
+ {
+ deviceId = vJoyInstance.GetNextAvailableID();
+ if (deviceId == 0)
+ {
+ throw new vJoyException("No vJoy devices are available.");
+ }
+
+ if (!vJoyInstance.AcquireDevice(deviceId))
+ {
+ throw new vJoyException($"Could not claim vJoy device {deviceId}.");
+ }
+
+ state.bDevice = (byte)deviceId;
+ Logging.WriteLineVerbose($"Acquired vJoy device with ID of {deviceId}");
+ }
+
+ protected override void MapGuideButton(bool pressed)
+ {
+ state.SetButton(vJoyButton.Fourteen, pressed);
+ vJoyInstance.UpdateDevice(deviceId, ref state);
+ }
+
+ // vJoy axes range from 0x0000 to 0x8000, but are exposed as full ints for some reason
+ protected static void SetAxis(ref int axisField, byte value)
+ {
+ axisField = (value * 0x0101) >> 1;
+ }
+
+ protected static void SetAxis(ref int axisField, short value)
+ {
+ axisField = ((ushort)value ^ 0x8000) >> 1;
+ }
+
+ protected static void SetAxisInverted(ref int axisField, short value)
+ {
+ axisField = 0x8000 - (((ushort)value ^ 0x8000) >> 1);
+ }
+
+ ///
+ /// Parses the state of the d-pad.
+ ///
+ protected static void ParseDpad(ref vJoy.JoystickState state, XboxGamepadButton buttons)
+ {
+ vJoyHat direction;
+ if ((buttons & XboxGamepadButton.DpadUp) != 0)
+ {
+ if ((buttons & XboxGamepadButton.DpadLeft) != 0)
+ {
+ direction = vJoyHat.UpLeft;
+ }
+ else if ((buttons & XboxGamepadButton.DpadRight) != 0)
+ {
+ direction = vJoyHat.UpRight;
+ }
+ else
+ {
+ direction = vJoyHat.Up;
+ }
+ }
+ else if ((buttons & XboxGamepadButton.DpadDown) != 0)
+ {
+ if ((buttons & XboxGamepadButton.DpadLeft) != 0)
+ {
+ direction = vJoyHat.DownLeft;
+ }
+ else if ((buttons & XboxGamepadButton.DpadRight) != 0)
+ {
+ direction = vJoyHat.DownRight;
+ }
+ else
+ {
+ direction = vJoyHat.Down;
+ }
+ }
+ else
+ {
+ if ((buttons & XboxGamepadButton.DpadLeft) != 0)
+ {
+ direction = vJoyHat.Left;
+ }
+ else if ((buttons & XboxGamepadButton.DpadRight) != 0)
+ {
+ direction = vJoyHat.Right;
+ }
+ else
+ {
+ direction = vJoyHat.Neutral;
+ }
+ }
+
+ state.bHats = (uint)direction;
+ }
+
+ protected XboxResult SubmitReport()
+ {
+ vJoyInstance.UpdateDevice(deviceId, ref state);
+ return XboxResult.Success;
+ }
+
+ public override void ResetReport()
+ {
+ state.Reset();
+ vJoyInstance.UpdateDevice(deviceId, ref state);
+ }
+
+ protected override void DisposeUnmanagedResources()
+ {
+ // Free device
+ ResetReport();
+ vJoyInstance.ReleaseDevice(deviceId);
+ deviceId = 0;
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/ViGEm/ViGEmInstance.cs b/RB4InstrumentMapper.Core/Mapping/ViGEm/ViGEmInstance.cs
new file mode 100644
index 0000000..9cb615b
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/ViGEm/ViGEmInstance.cs
@@ -0,0 +1,77 @@
+using System;
+using Nefarius.ViGEm.Client;
+using Nefarius.ViGEm.Client.Targets;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Static ViGEmBus client.
+ ///
+ public static class ViGEmInstance
+ {
+ ///
+ /// Static ViGEmBus client.
+ ///
+ private static ViGEmClient client;
+
+ ///
+ /// Whether or not the ViGEmBus client has been initialized.
+ ///
+ public static bool Initialized => client != null;
+
+ ///
+ /// Whether or not new devices can be created.
+ ///
+ public static bool AreDevicesAvailable => Initialized && canCreateDevices;
+ private static bool canCreateDevices = false;
+
+ public static bool TryInitialize()
+ {
+ if (client != null)
+ return true;
+
+ try
+ {
+ client = new ViGEmClient();
+ canCreateDevices = true;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Logging.WriteException("Failed to create ViGEmBus client!", ex);
+ client = null;
+ canCreateDevices = false;
+ return false;
+ }
+ }
+
+ ///
+ /// Creates a new Xbox 360 device with the Xbox 360 Rock Band wireless instrument vendor/product IDs.
+ ///
+ public static IXbox360Controller CreateDevice()
+ {
+ if (!Initialized)
+ throw new ObjectDisposedException(nameof(client), "ViGEmBus client is disposed or not initialized yet!");
+
+ try
+ {
+ return client.CreateXbox360Controller(0x1BAD, 0x0719);
+ }
+ catch
+ {
+ canCreateDevices = false;
+ throw;
+ }
+ }
+ // Rock Band Guitar: USB\VID_1BAD&PID_0719&IG_00 XUSB\TYPE_00\SUB_86\VEN_1BAD\REV_0002
+ // Rock Band Drums: USB\VID_1BAD&PID_0719&IG_02 XUSB\TYPE_00\SUB_88\VEN_1BAD\REV_0002
+ // If subtype ID specification through ViGEmBus becomes possible at some point,
+ // the guitar should be subtype 6, and the drums should be subtype 8
+
+ public static void Dispose()
+ {
+ client?.Dispose();
+ client = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/XboxDeviceGuids.cs b/RB4InstrumentMapper.Core/Mapping/XboxDeviceGuids.cs
new file mode 100644
index 0000000..a0c9cf8
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/XboxDeviceGuids.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Xbox device interface GUIDs.
+ ///
+ internal static class XboxDeviceGuids
+ {
+ public static readonly Guid XboxInputDevice = Guid.Parse("9776FF56-9BFD-4581-AD45-B645BBA526D6");
+ public static readonly Guid XboxNavigationController = Guid.Parse("B8F31FE7-7386-40E9-A9F8-2F21263ACFB7");
+ public static readonly Guid XboxGamepad = Guid.Parse("082E402C-07DF-45E1-A5AB-A3127AF197B5");
+
+ public static readonly Guid MadCatzGuitar = Guid.Parse("0D2AE438-7F7D-4933-8693-30FC55018E77");
+ public static readonly Guid MadCatzDrumkit = Guid.Parse("06182893-CCE0-4B85-9271-0A10DBAB7E07");
+ public static readonly Guid MadCatzLegacyWireless = Guid.Parse("AF259D0F-76B0-4CDB-BFD1-CEA8C0A8F5EE");
+
+ public static readonly Guid PdpGuitar = Guid.Parse("1A266AF6-3A46-45E3-B9B6-0F2C0B2C1EBE");
+ public static readonly Guid PdpDrumkit = Guid.Parse("A503F9B0-955E-47C4-A2ED-B1336FA7703E");
+
+ public static readonly Guid ActivisionGuitarHeroLive = Guid.Parse("FD12FDD9-8E73-47C7-A231-96268C38009A");
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyDefinitions.cs b/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyDefinitions.cs
new file mode 100644
index 0000000..b5b8ce7
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyDefinitions.cs
@@ -0,0 +1,44 @@
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// vJoy button flag constants.
+ ///
+ public enum vJoyButton : uint
+ {
+ None = 0,
+ One = 1 << 0,
+ Two = 1 << 1,
+ Three = 1 << 2,
+ Four = 1 << 3,
+ Five = 1 << 4,
+ Six = 1 << 5,
+ Seven = 1 << 6,
+ Eight = 1 << 7,
+ Nine = 1 << 8,
+ Ten = 1 << 9,
+ Eleven = 1 << 10,
+ Twelve = 1 << 11,
+ Thirteen = 1 << 12,
+ Fourteen = 1 << 13,
+ Fifteen = 1 << 14,
+ Sixteen = 1 << 15
+ }
+
+ ///
+ /// vJoy PoV hat constants.
+ ///
+ public enum vJoyHat : uint
+ {
+ // vJoy continuous PoV hat values range from 0 to 35999 (measured in 1/100 of a degree).
+ // The value is measured clockwise, with up being 0.
+ Neutral = 0xFFFFFFFF,
+ Up = 0,
+ UpRight = 4500,
+ Right = 9000,
+ DownRight = 13500,
+ Down = 18000,
+ DownLeft = 22500,
+ Left = 27000,
+ UpLeft = 31500
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyException.cs b/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyException.cs
new file mode 100644
index 0000000..eac7ac2
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyException.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// A vJoy exception.
+ ///
+ class vJoyException : Exception
+ {
+ public vJoyException()
+ : base() {}
+
+ public vJoyException(string message)
+ : base(message) {}
+
+ public vJoyException(string message, Exception innerException)
+ : base(message, innerException) {}
+
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyExtensions.cs b/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyExtensions.cs
new file mode 100644
index 0000000..209a07c
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyExtensions.cs
@@ -0,0 +1,37 @@
+using System.Runtime.CompilerServices;
+using vJoyInterfaceWrap;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ public static class vJoyExtensions
+ {
+ ///
+ /// Sets the state of the specified button.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void SetButton(ref this vJoy.JoystickState state, vJoyButton button, bool set)
+ {
+ if (set)
+ {
+ state.Buttons |= (uint)button;
+ }
+ else
+ {
+ state.Buttons &= (uint)~button;
+ }
+ }
+
+ ///
+ /// Resets the values of this state.
+ ///
+ public static void Reset(ref this vJoy.JoystickState state)
+ {
+ // Only reset the values we use
+ state.Buttons = (uint)vJoyButton.None;
+ state.bHats = (uint)vJoyHat.Neutral;
+ state.AxisX = 0;
+ state.AxisY = 0;
+ state.AxisZ = 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyInstance.cs b/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyInstance.cs
new file mode 100644
index 0000000..d9379f2
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Mapping/vJoy/vJoyInstance.cs
@@ -0,0 +1,147 @@
+using System;
+using vJoyInterfaceWrap;
+
+namespace RB4InstrumentMapper.Core.Mapping
+{
+ ///
+ /// Provides functionality for logging.
+ ///
+ public static class vJoyInstance
+ {
+ private static readonly vJoy client = new vJoy();
+
+ public static bool Enabled => client.vJoyEnabled();
+
+ public static string Manufacturer => client.GetvJoyManufacturerString();
+ public static string Product => client.GetvJoyProductString();
+ public static string SerialNumber => client.GetvJoySerialNumberString();
+
+ public static bool AreDevicesAvailable => Enabled && GetNextAvailableID() > 0;
+
+ public static bool DriverMatch(out uint libraryVersion, out uint driverVersion)
+ {
+ libraryVersion = 0;
+ driverVersion = 0;
+ return client.DriverMatch(ref libraryVersion, ref driverVersion);
+ }
+
+ public static VjdStat GetDeviceStatus(uint deviceId) => client.GetVJDStatus(deviceId);
+
+ public static bool IsDeviceAvailable(uint deviceId)
+ {
+ return (GetDeviceStatus(deviceId) == VjdStat.VJD_STAT_FREE) && IsDeviceCompatible(deviceId);
+ }
+
+ public static bool IsDeviceCompatible(uint deviceId)
+ {
+ // Check that the vJoy device is configured correctly
+ int numButtons = client.GetVJDButtonNumber(deviceId);
+ int numContPov = client.GetVJDContPovNumber(deviceId);
+ bool xExists = client.GetVJDAxisExist(deviceId, HID_USAGES.HID_USAGE_X);
+ bool yExists = client.GetVJDAxisExist(deviceId, HID_USAGES.HID_USAGE_Y);
+ bool zExists = client.GetVJDAxisExist(deviceId, HID_USAGES.HID_USAGE_Z);
+
+ if (numButtons >= 16 && numContPov >= 1 &&
+ xExists && yExists && zExists
+ )
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Counts the number of available vJoy devices.
+ ///
+ public static int GetAvailableDeviceCount()
+ {
+ if (!Enabled)
+ {
+ return 0;
+ }
+
+ // Loop through vJoy IDs and populate list
+ int freeDeviceCount = 0;
+ for (uint id = 1; id <= 16; id++)
+ {
+ if (IsDeviceAvailable(id))
+ {
+ freeDeviceCount++;
+ }
+ }
+
+ switch (freeDeviceCount)
+ {
+ case 0:
+ Console.WriteLine($"No vJoy devices available! Please configure some in the Configure vJoy application.");
+ Console.WriteLine($"Devices must be configured with 16 or more buttons, 1 or more continuous POV hats, and have the X, Y, and Z axes.");
+ break;
+
+ case 1:
+ Console.WriteLine($"{freeDeviceCount} vJoy device available.");
+ break;
+
+ default:
+ Console.WriteLine($"{freeDeviceCount} vJoy devices available.");
+ break;
+ }
+
+ return freeDeviceCount;
+ }
+
+ ///
+ /// Gets the next available device ID.
+ ///
+ public static uint GetNextAvailableID()
+ {
+ // Get available devices
+ for (uint deviceId = 1; deviceId <= 16; deviceId++)
+ {
+ // Ensure device is available
+ if (IsDeviceAvailable(deviceId))
+ {
+ return deviceId;
+ }
+ }
+
+ // No devices available
+ return 0;
+ }
+
+ ///
+ /// Acquires a vJoy device.
+ ///
+ public static bool AcquireDevice(uint deviceId)
+ {
+ return client.AcquireVJD(deviceId);
+ }
+
+ ///
+ /// Releases a vJoy device.
+ ///
+ public static void ReleaseDevice(uint deviceId)
+ {
+ if (client.GetVJDStatus(deviceId) == VjdStat.VJD_STAT_OWN)
+ {
+ client.RelinquishVJD(deviceId);
+ }
+ }
+
+ public static void FreeAllDevices()
+ {
+ for (uint i = 1; i <= 16; i++)
+ {
+ ReleaseDevice(i);
+ }
+ }
+
+ ///
+ /// Acquires a vJoy device.
+ ///
+ public static bool UpdateDevice(uint deviceId, ref vJoy.JoystickState state)
+ {
+ return client.UpdateVJD(deviceId, ref state);
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/MappingSettings.cs b/RB4InstrumentMapper.Core/MappingSettings.cs
new file mode 100644
index 0000000..f1b888b
--- /dev/null
+++ b/RB4InstrumentMapper.Core/MappingSettings.cs
@@ -0,0 +1,57 @@
+using RB4InstrumentMapper.Core.Mapping;
+
+namespace RB4InstrumentMapper.Core
+{
+ ///
+ /// Settings for the parsing backend.
+ ///
+ public static class BackendSettings
+ {
+ private static uint _pollingFrequency = 60;
+
+ ///
+ /// The controller emulator to use.
+ ///
+ public static MappingMode MapperMode { get; set; } = MappingMode.NotSet;
+
+ ///
+ /// The rate at which to poll for inputs, in hertz.
+ ///
+ public static uint PollingFrequency
+ {
+ get => _pollingFrequency;
+ set => _pollingFrequency = ClampPollingFrequency(value);
+ }
+
+ ///
+ /// Whether to use hardware-accurate drum mappings (only applies to ViGEmBus mode).
+ ///
+ public static bool UseAccurateDrumMappings { get; set; } = false;
+
+ ///
+ /// Whether packets should be logged to the console.
+ ///
+ public static bool LogPackets { get; set; } = false;
+
+ ///
+ /// Clamps the polling frequency value to an acceptable range.
+ ///
+ public static uint ClampPollingFrequency(uint frequency)
+ {
+ // This upper bound *would* be 1000, but in testing the
+ // SleepTimer implementation can't do more than around 500hz
+ if (frequency > 500)
+ {
+ return 500;
+ }
+ else if (frequency < 60)
+ {
+ return 60;
+ }
+ else
+ {
+ return frequency;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/NativeMethods.txt b/RB4InstrumentMapper.Core/NativeMethods.txt
new file mode 100644
index 0000000..d5ac04a
--- /dev/null
+++ b/RB4InstrumentMapper.Core/NativeMethods.txt
@@ -0,0 +1,9 @@
+FILE_ACCESS_RIGHTS
+SYNCHRONIZATION_ACCESS_RIGHTS
+CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
+INFINITE
+
+CreateWaitableTimerEx
+SetWaitableTimer
+WaitForSingleObject
+CloseHandle
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Backends/GameInputBackend.cs b/RB4InstrumentMapper.Core/Parsing/Backends/GameInputBackend.cs
new file mode 100644
index 0000000..3e89d0e
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Backends/GameInputBackend.cs
@@ -0,0 +1,204 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading.Tasks;
+using SharpGameInput;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ public static class GameInputBackend
+ {
+ private static IGameInput gameInput;
+
+ private static bool inputsEnabled = false;
+
+ private static readonly ConcurrentDictionary devices
+ = new ConcurrentDictionary();
+
+ private static GameInputCallbackToken deviceCallbackToken;
+
+ public static int DeviceCount => devices.Count;
+
+ public static event Action DeviceCountChanged;
+
+ public static bool Initialized { get; private set; } = false;
+
+ public static bool Initialize()
+ {
+ if (Initialized)
+ return true;
+
+ if (!GameInput.Create(out gameInput, out int result))
+ {
+ Logging.WriteLine($"Failed to create GameInput instance: 0x{result:X8}");
+ return false;
+ }
+
+ if (!gameInput.RegisterDeviceCallback(
+ null,
+ GameInputKind.RawDeviceReport,
+ GameInputDeviceStatus.Connected,
+ GameInputEnumerationKind.AsyncEnumeration,
+ null,
+ OnDeviceStatusChange,
+ out deviceCallbackToken,
+ out result
+ ))
+ {
+ gameInput?.Dispose();
+ Logging.WriteLine($"Failed to register GameInput device callback: 0x{result:X8}");
+ return false;
+ }
+
+ Initialized = true;
+ DeviceCountChanged?.Invoke();
+ return true;
+ }
+
+ public static void Uninitialize()
+ {
+ if (!Initialized)
+ return;
+
+ deviceCallbackToken?.Unregister(1_000_000);
+ deviceCallbackToken = null;
+
+ foreach (var pair in devices)
+ {
+ pair.Key.Dispose();
+ pair.Value.Dispose();
+ }
+ devices.Clear();
+
+ DeviceCountChanged?.Invoke();
+
+ gameInput?.Dispose();
+ gameInput = null;
+
+ Initialized = false;
+ }
+
+ public static void Refresh()
+ {
+ if (!Initialized)
+ return;
+
+ deviceCallbackToken?.Unregister(1_000_000);
+ deviceCallbackToken = null;
+
+ foreach (var pair in devices)
+ {
+ pair.Key.Dispose();
+ pair.Value.Dispose();
+ }
+ devices.Clear();
+
+ if (!gameInput.RegisterDeviceCallback(
+ null,
+ GameInputKind.RawDeviceReport,
+ GameInputDeviceStatus.Connected,
+ GameInputEnumerationKind.AsyncEnumeration,
+ null,
+ OnDeviceStatusChange,
+ out deviceCallbackToken,
+ out int result
+ ))
+ {
+ Logging.WriteLine($"Failed to register GameInput device callback: 0x{result:X8}");
+ return;
+ }
+
+ DeviceCountChanged?.Invoke();
+ }
+
+ public static void StartCapture()
+ {
+ if (!Initialized)
+ return;
+
+ inputsEnabled = true;
+ foreach (var device in devices.Values)
+ {
+ device.EnableInputs(true);
+ }
+ }
+
+ public static void StopCapture()
+ {
+ if (!Initialized)
+ return;
+
+ inputsEnabled = false;
+ foreach (var device in devices.Values)
+ {
+ device.EnableInputs(false);
+ }
+ }
+
+ internal static async void QueueForRemoval(GameInputBackendDevice device)
+ {
+ // Force ourselves out of the device read thread/callback and defer to later
+ await Task.Yield();
+
+ if (!devices.TryRemove(device.Device, out _))
+ return;
+
+ Logging.WriteLine($"Removing GameInput device {DeviceInfoToString(device.Device.DeviceInfo)}");
+ device.Dispose();
+ DeviceCountChanged?.Invoke();
+ }
+
+ private static void OnDeviceStatusChange(
+ LightGameInputCallbackToken callbackToken,
+ object context,
+ LightIGameInputDevice device,
+ ulong timestamp,
+ GameInputDeviceStatus currentStatus,
+ GameInputDeviceStatus previousStatus
+ )
+ {
+ // Ignore if connection status hasn't changed
+ if ((currentStatus & GameInputDeviceStatus.Connected) == (previousStatus & GameInputDeviceStatus.Connected))
+ return;
+
+ ref readonly var info = ref device.DeviceInfo;
+
+ // We only cover Xbox One devices
+ if (info.deviceFamily != GameInputDeviceFamily.XboxOne && info.deviceFamily != GameInputDeviceFamily.Virtual)
+ return;
+
+ // We only support devices with raw reports
+ if ((info.supportedInput & GameInputKind.RawDeviceReport) == 0)
+ return;
+
+ if ((currentStatus & GameInputDeviceStatus.Connected) != 0)
+ {
+ var permaDevice = device.ToComPtr();
+ var backendDevice = new GameInputBackendDevice(gameInput, permaDevice);
+ backendDevice.EnableInputs(inputsEnabled);
+
+ if (devices.TryAdd(permaDevice, backendDevice))
+ {
+ Logging.WriteLine($"GameInput device {DeviceInfoToString(info)} connected");
+ DeviceCountChanged?.Invoke();
+ }
+ }
+ else
+ {
+ if (!devices.TryRemove(device.ToComPtr(), out var backendDevice))
+ return;
+
+ backendDevice.Dispose();
+ Logging.WriteLine($"GameInput device {DeviceInfoToString(info)} disconnected");
+ DeviceCountChanged?.Invoke();
+ }
+ }
+
+ private static unsafe string DeviceInfoToString(in GameInputDeviceInfo info)
+ {
+ if (info.displayName != null)
+ return $"{info.displayName->ToString()} ({info.deviceId})";
+ else
+ return $"{info.deviceId}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Backends/GameInputBackendDevice.cs b/RB4InstrumentMapper.Core/Parsing/Backends/GameInputBackendDevice.cs
new file mode 100644
index 0000000..80ae5af
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Backends/GameInputBackendDevice.cs
@@ -0,0 +1,295 @@
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using RB4InstrumentMapper.Core.Mapping;
+using RB4InstrumentMapper.Core.Utility;
+using SharpGameInput;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ internal class GameInputBackendDevice : IDisposable, IBackendClient
+ {
+ private const int E_NOTIMPL = unchecked((int)0x80004001);
+
+ private IGameInput gameInput;
+ private IGameInputDevice device;
+
+ private Thread readThread;
+ private EventWaitHandle threadStop = new EventWaitHandle(false, EventResetMode.ManualReset);
+ private volatile bool ioError = false;
+
+ private byte[] lastReport = Array.Empty();
+ private int lastReportLength = 0;
+
+ private bool inputsEnabled = false;
+
+ public ushort VendorId { get; }
+ public ushort ProductId { get; }
+
+ public bool MapGuideButton => false;
+
+ public IGameInputDevice Device => device;
+
+ public GameInputBackendDevice(IGameInput gameInput, IGameInputDevice device)
+ {
+ this.gameInput = gameInput.Duplicate();
+ this.device = device.Duplicate();
+
+ ref readonly var info = ref device.DeviceInfo;
+ VendorId = info.vendorId;
+ ProductId = info.productId;
+ }
+
+ // No finalizer, all resources are managed
+
+ public void Dispose()
+ {
+ inputsEnabled = false;
+
+ threadStop?.Set();
+ readThread?.Join();
+ readThread = null;
+
+ threadStop?.Dispose();
+ threadStop = null;
+
+ device?.Dispose();
+ device = null;
+
+ gameInput?.Dispose();
+ gameInput = null;
+ }
+
+ public void EnableInputs(bool enabled)
+ {
+ if (inputsEnabled == enabled)
+ return;
+
+ inputsEnabled = enabled;
+ if (enabled)
+ {
+ threadStop.Reset();
+ readThread = new Thread(ReadThread) { IsBackground = true };
+ readThread.Start();
+ }
+ else
+ {
+ threadStop.Set();
+ readThread.Join();
+ }
+ }
+
+ private unsafe void ReadThread()
+ {
+ using var deviceMapper = MapperFactory.GetByHardwareIds(this);
+ if (deviceMapper == null)
+ {
+ GameInputBackend.QueueForRemoval(this);
+ return;
+ }
+
+ if (gameInput.RegisterReadingCallback(
+ device, GameInputKind.RawDeviceReport, 0, null,
+ (token, context, reading, hasOverrunOccurred) =>
+ {
+ using (reading)
+ {
+ if (!HandleReading(reading, deviceMapper))
+ token.Stop();
+ }
+ },
+ out var readingToken, out int result
+ ))
+ {
+ threadStop.WaitOne(Timeout.Infinite);
+ readingToken.Unregister(1000000);
+ }
+ else
+ {
+ // RegisterReadingCallback is not implemented at the time of writing,
+ // so we fall back to polling on failure
+ if (result != E_NOTIMPL)
+ Logging.WriteLineVerbose($"Couldn't register reading callback, falling back to manual polling. Error result: 0x{result:X4}");
+ ReadThreaded(deviceMapper);
+ }
+ }
+
+ private unsafe void ReadThreaded(DeviceMapper mapper)
+ {
+ using var sleepTimer = new SleepTimer();
+
+ // We unfortunately can't rely on timestamp to determine state change,
+ // as guitar axis changes do not change the timestamp
+ // ulong lastTimestamp = 0;
+
+ while (!threadStop.WaitOne(0))
+ {
+ sleepTimer.Sleep(1 / (double) BackendSettings.PollingFrequency);
+
+ int hResult = gameInput.GetCurrentReading(GameInputKind.RawDeviceReport, device, out var reading);
+ if (hResult < 0)
+ {
+ if (hResult == (int)GameInputResult.ReadingNotFound)
+ continue;
+
+ if (hResult != (int)GameInputResult.DeviceDisconnected)
+ Logging.WriteLineVerbose($"Failed to get current reading: 0x{hResult:X8}");
+ break;
+ }
+
+ using (reading)
+ {
+ // // Ignore unchanged reports
+ // ulong timestamp = reading.GetTimestamp();
+ // if (lastTimestamp == timestamp)
+ // continue;
+ // lastTimestamp = timestamp;
+
+ if (!HandleReading(reading, mapper))
+ break;
+ }
+ }
+ }
+
+ private unsafe bool HandleReading(LightIGameInputReading reading, DeviceMapper mapper)
+ {
+ if (!reading.GetRawReport(out var rawReport))
+ {
+ Logging.WriteLineVerbose("Could not get raw report!");
+ return false;
+ }
+
+ using (rawReport)
+ {
+ const int maxStackSize = 64;
+
+ byte reportId = (byte)rawReport.ReportInfo.id;
+ int reportSize = (int)rawReport.GetRawDataSize();
+
+ byte[] poolBuffer = null;
+ Span data = reportSize > maxStackSize
+ ? (poolBuffer = ArrayPool.Shared.Rent(reportSize))
+ : stackalloc byte[maxStackSize];
+
+ try
+ {
+ fixed (byte* ptr = data)
+ {
+ int readSize = (int)rawReport.GetRawData((UIntPtr)data.Length, ptr);
+ data = data.Slice(0, Math.Min(readSize, data.Length));
+ }
+
+ // Compare with last report to determine if any inputs changed
+ // Necessary due to the timestamp not updating on guitar axis changes
+ if (data.SequenceEqual(lastReport.AsSpan(0, lastReportLength)))
+ return true;
+
+ if (lastReport.Length < data.Length)
+ lastReport = new byte[data.Length];
+ data.CopyTo(lastReport);
+ lastReportLength = data.Length;
+
+ PacketLogging.WritePacket(
+ new ReadOnlySpan(&reportId, sizeof(byte)),
+ data,
+ PacketDirection.In
+ );
+
+ var result = mapper.HandleMessage(reportId, data);
+ if (result == XboxResult.Disconnected)
+ return false;
+ }
+ finally
+ {
+ if (poolBuffer != null)
+ {
+ ArrayPool.Shared.Return(poolBuffer);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public XboxResult SendMessage(XboxMessage message)
+ {
+ return SendMessage(message.Header, message.Bytes);
+ }
+
+ public XboxResult SendMessage(XboxCommandHeader header)
+ {
+ return SendMessage(header, Span.Empty);
+ }
+
+ public unsafe XboxResult SendMessage(XboxCommandHeader header, ref T data)
+ where T : unmanaged
+ {
+ // Create a byte buffer for the given data
+ var writeBuffer = new Span(Unsafe.AsPointer(ref data), sizeof(T));
+ return SendMessage(header, writeBuffer);
+ }
+
+ public unsafe XboxResult SendMessage(XboxCommandHeader header, Span data)
+ {
+ PacketLogging.WritePacket(
+ new ReadOnlySpan(&header.CommandId, sizeof(byte)),
+ data,
+ PacketDirection.Out
+ );
+
+ if (ioError)
+ return XboxResult.Disconnected;
+
+ const int retryThreshold = 3;
+ for (int tryCount = 0; tryCount < retryThreshold; tryCount++,
+ Logging.WriteLineVerbose($"Error while sending report! (Attempt {tryCount})"))
+ {
+ int hResult = device.CreateRawDeviceReport(header.CommandId, GameInputRawDeviceReportKind.Output, out var report);
+ if (hResult < 0)
+ {
+ if (hResult == (int)GameInputResult.DeviceDisconnected)
+ return XboxResult.Disconnected;
+
+ Logging.WriteLineVerbose($"Failed to create raw report: 0x{hResult:X8}");
+ continue;
+ }
+
+ using (report)
+ {
+ if (!data.IsEmpty)
+ {
+ fixed (byte* ptr = data)
+ {
+ if (!report.SetRawData((UIntPtr)data.Length, ptr))
+ {
+ Logging.WriteLineVerbose("Failed to set raw report data!");
+ continue;
+ }
+ }
+ }
+
+ hResult = device.SendRawDeviceOutput(report);
+ if (hResult < 0)
+ {
+ // This call is not implemented as of the time of writing,
+ // ignore and treat as success
+ if (hResult == E_NOTIMPL)
+ return XboxResult.Success;
+
+ if (hResult == (int)GameInputResult.DeviceDisconnected)
+ return XboxResult.Disconnected;
+
+ Logging.WriteLineVerbose($"Failed to send raw report: 0x{hResult:X8}");
+ continue;
+ }
+
+ return XboxResult.Success;
+ }
+ }
+
+ ioError = true;
+ return XboxResult.Disconnected;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Backends/ReplayBackend.cs b/RB4InstrumentMapper.Core/Parsing/Backends/ReplayBackend.cs
new file mode 100644
index 0000000..ee28be6
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Backends/ReplayBackend.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using RB4InstrumentMapper.Core.Mapping;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Replays packet log files to help with debugging packet handling issues.
+ ///
+ ///
+ /// Since it's only really meant for debugging, this replayer has a number
+ /// of limitations and is not meant for general use:
+ /// - It can only handle one device at a time.
+ /// - Arrival and descriptor messages must be present in the log.
+ ///
+ public static class ReplayBackend
+ {
+ public static bool ReplayLog(string logPath)
+ {
+ if (!File.Exists(logPath))
+ {
+ Console.WriteLine($"File not found: {logPath}");
+ return false;
+ }
+
+ MappingMode mappingMode;
+ if (ViGEmInstance.TryInitialize())
+ {
+ mappingMode = MappingMode.ViGEmBus;
+ }
+ else if (vJoyInstance.Enabled)
+ {
+ mappingMode = MappingMode.vJoy;
+ }
+ else
+ {
+ Console.WriteLine("No controller emulators available! Please make sure ViGEmBus and/or vJoy is installed.");
+ return false;
+ }
+
+ BackendSettings.MapperMode = mappingMode;
+ Console.WriteLine($"Using mapping mode {mappingMode}");
+
+ string[] lines = File.ReadAllLines(logPath);
+ var previousSequence = new Dictionary>();
+ using var device = new XboxDevice(BackendType.Replay);
+ device.EnableInputs(true);
+ foreach (string line in lines)
+ {
+ // Remove any comments
+ int spanEnd = line.IndexOf("//");
+ if (spanEnd < 0)
+ spanEnd = line.Length;
+
+ var lineSpan = line.AsSpan().Slice(0, spanEnd).Trim();
+ if (lineSpan.IsEmpty)
+ continue;
+
+ if (!PacketLogging.TryParsePacket(lineSpan, out var headerBytes, out var data, out var direction) ||
+ !XboxCommandHeader.TryParse(headerBytes, out var header, out _))
+ {
+ Console.WriteLine($"Couldn't parse line: {line}");
+ Debugger.Break();
+ break;
+ }
+
+ // Skip packets that were sent from us
+ if (direction != PacketDirection.In)
+ {
+ Console.WriteLine($"Skipping direction-out line: {line}");
+ continue;
+ }
+
+ // Ensure correct header data length (for GameInput)
+ header.DataLength = data.Length;
+
+ // Set proper sequence ID if unspecified (for GameInput)
+ if (header.SequenceCount == 0)
+ {
+ if (!previousSequence.TryGetValue(header.Client, out var clientSequence))
+ {
+ clientSequence = new Dictionary();
+ previousSequence.Add(header.Client, clientSequence);
+ }
+
+ if (!clientSequence.TryGetValue(header.CommandId, out byte sequence) ||
+ sequence == 0xFF) // Sequence IDs of 0 are not valid
+ sequence = 0;
+
+ header.SequenceCount = ++sequence;
+ clientSequence[header.CommandId] = sequence;
+ }
+
+ Console.WriteLine($"Processing line: {line}");
+ var result = device.HandlePacket(header, data);
+ if (result != XboxResult.Success)
+ {
+ Console.WriteLine($"Error when handling line: {result}");
+ Debugger.Break();
+ break;
+ }
+ }
+
+ // Debug break before the device is disposed
+ Debugger.Break();
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Backends/WinUsbBackend.cs b/RB4InstrumentMapper.Core/Parsing/Backends/WinUsbBackend.cs
new file mode 100644
index 0000000..18d2b46
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Backends/WinUsbBackend.cs
@@ -0,0 +1,289 @@
+using System;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using Nefarius.Drivers.WinUSB;
+using Nefarius.Utilities.DeviceManagement.Extensions;
+using Nefarius.Utilities.DeviceManagement.PnP;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ public static class WinUsbBackend
+ {
+ private static readonly Guid WinUsbClassGuid = Guid.Parse("88BAE032-5A81-49F0-BC3D-A4FF138216D6");
+ private const string XGIP_COMPATIBLE_ID = @"USB\MS_COMP_XGIP10";
+
+ private static readonly DeviceNotificationListener watcher = new DeviceNotificationListener();
+ private static readonly ConcurrentDictionary devices = new ConcurrentDictionary();
+
+ private static bool inputsEnabled = false;
+
+ public static int DeviceCount => devices.Count;
+
+ public static event Action DeviceCountChanged;
+
+ public static bool Initialized { get; private set; } = false;
+
+ public static bool Initialize()
+ {
+ if (Initialized)
+ return true;
+
+ try
+ {
+ foreach (var deviceInfo in USBDevice.GetDevices(DeviceInterfaceIds.UsbDevice))
+ {
+ AddDevice(deviceInfo.DevicePath);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.WriteException("Failed to initialize WinUSB backend!", ex);
+ ClearDevices();
+ return false;
+ }
+
+ DeviceCountChanged?.Invoke();
+
+ watcher.DeviceArrived += DeviceArrived;
+ watcher.DeviceRemoved += DeviceRemoved;
+ watcher.StartListen(DeviceInterfaceIds.UsbDevice);
+
+ Initialized = true;
+ return true;
+ }
+
+ public static void Uninitialize()
+ {
+ if (!Initialized)
+ return;
+
+ watcher.StopListen();
+ watcher.DeviceArrived -= DeviceArrived;
+ watcher.DeviceRemoved -= DeviceRemoved;
+
+ ClearDevices();
+
+ Initialized = false;
+ }
+
+ private static void ClearDevices()
+ {
+ if (!Initialized)
+ return;
+
+ foreach (var devicePath in devices.Keys)
+ {
+ RemoveDevice(devicePath, remove: false);
+ }
+
+ devices.Clear();
+
+ DeviceCountChanged?.Invoke();
+ }
+
+ private static void DeviceArrived(DeviceEventArgs args)
+ {
+ AddDevice(args.SymLink);
+ }
+
+ private static void DeviceRemoved(DeviceEventArgs args)
+ {
+ RemoveDevice(args.SymLink);
+ }
+
+ private static void AddDevice(string devicePath)
+ {
+ if (!IsCompatibleDevice(devicePath))
+ return;
+
+ // Paths are case-insensitive
+ devicePath = devicePath.ToLowerInvariant();
+ var device = XboxWinUsbDevice.TryCreate(devicePath);
+ if (device == null)
+ return;
+
+ device.EnableInputs(inputsEnabled);
+ device.StartReading();
+ devices[devicePath] = device;
+
+ Logging.WriteLine($"USB device {devicePath} connected");
+ DeviceCountChanged?.Invoke();
+ }
+
+ private static void RemoveDevice(string devicePath, bool remove = true)
+ {
+ // Paths are case-insensitive
+ devicePath = devicePath.ToLowerInvariant();
+ if (!devices.TryGetValue(devicePath, out var device))
+ return;
+
+ device.Dispose();
+ if (remove)
+ devices.TryRemove(devicePath, out _);
+
+ Logging.WriteLine($"USB device {devicePath} disconnected");
+ DeviceCountChanged?.Invoke();
+ }
+
+ public static void StartCapture()
+ {
+ if (!Initialized)
+ return;
+
+ inputsEnabled = true;
+ foreach (var device in devices.Values)
+ {
+ device.EnableInputs(inputsEnabled);
+ }
+ }
+
+ public static void StopCapture()
+ {
+ if (!Initialized)
+ return;
+
+ inputsEnabled = false;
+ foreach (var device in devices.Values)
+ {
+ device.EnableInputs(inputsEnabled);
+ }
+ }
+
+ public static bool IsCompatibleDevice(string devicePath)
+ {
+ try
+ {
+ var device = PnPDevice.GetDeviceByInterfaceId(devicePath);
+ return IsCompatibleDevice(device);
+ }
+ catch (Exception ex)
+ {
+ Logging.WriteException("Failed to determine device compatibility!", ex);
+ return false;
+ }
+ }
+
+ public static bool IsCompatibleDevice(PnPDevice device)
+ {
+ try
+ {
+ // Only accept WinUSB devices, at least for now
+ var classGuid = device.GetProperty(DevicePropertyKey.Device_ClassGuid);
+ if (classGuid != WinUsbClassGuid)
+ return false;
+
+ // Check for the Xbox One compatible ID
+ if (!IsXGIPDevice(device))
+ return false;
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Logging.WriteException("Failed to determine device compatibility!", ex);
+ return false;
+ }
+ }
+
+ public static bool IsXGIPDevice(PnPDevice device)
+ {
+ // Check for the Xbox One compatible ID
+ var compatibleIds = device.GetProperty(DevicePropertyKey.Device_CompatibleIds);
+ foreach (string id in compatibleIds)
+ {
+ if (id == XGIP_COMPATIBLE_ID)
+ return true;
+ }
+
+ return false;
+ }
+
+ // WinUSB devices are exclusive-access, so we need a helper method to get already-initialized devices
+ public static USBDevice GetUsbDevice(string devicePath)
+ {
+ if (devices.TryGetValue(devicePath, out var device))
+ return device.UsbDevice;
+ return USBDevice.GetSingleDeviceByPath(devicePath);
+ }
+
+ public static bool SwitchDeviceToWinUSB(string instanceId)
+ {
+ try
+ {
+ var device = PnPDevice.GetDeviceByInstanceId(instanceId).ToUsbPnPDevice();
+ return SwitchDeviceToWinUSB(device);
+ }
+ catch (Exception ex)
+ {
+ // Verbose since this will be attempted twice, and the first attempt will always fail if we're not elevated
+ Logging.WriteExceptionVerbose($"Failed to switch device {instanceId} to WinUSB!", ex);
+ return false;
+ }
+ }
+
+ public static bool SwitchDeviceToWinUSB(UsbPnPDevice device)
+ {
+ try
+ {
+ if (!IsXGIPDevice(device))
+ {
+ Debug.Fail($"Device instance {device.InstanceId} is not an XGIP device!");
+ return false;
+ }
+
+ device.InstallNullDriver(out bool reboot);
+ if (reboot)
+ device.CyclePort();
+
+ device.InstallCustomDriver("winusb.inf", out reboot);
+ if (reboot)
+ device.CyclePort();
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ // Verbose since this will be attempted twice, and the first attempt will always fail if we're not elevated
+ Logging.WriteExceptionVerbose($"Failed to switch device {device.InstanceId} to WinUSB!", ex);
+ return false;
+ }
+ }
+
+ public static bool RevertDevice(string instanceId)
+ {
+ try
+ {
+ var device = PnPDevice.GetDeviceByInstanceId(instanceId).ToUsbPnPDevice();
+ return RevertDevice(device);
+ }
+ catch (Exception ex)
+ {
+ // Verbose since this will be attempted twice, and the first attempt will always fail if we're not elevated
+ Logging.WriteExceptionVerbose($"Failed to revert device {instanceId} to its original driver!", ex);
+ return false;
+ }
+ }
+
+ public static bool RevertDevice(UsbPnPDevice device)
+ {
+ try
+ {
+ device.InstallNullDriver(out bool reboot);
+ if (reboot)
+ device.CyclePort();
+
+ device.Uninstall(out reboot);
+ if (reboot)
+ device.CyclePort();
+
+ return Devcon.Refresh();
+ }
+ catch (Exception ex)
+ {
+ // Verbose since this will be attempted twice: once in-process, and once in a separate elevated process
+ Logging.WriteExceptionVerbose($"Failed to revert device {device.InstanceId} to its original driver!", ex);
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Backends/XboxWinUsbDevice.cs b/RB4InstrumentMapper.Core/Parsing/Backends/XboxWinUsbDevice.cs
new file mode 100644
index 0000000..3ca4847
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Backends/XboxWinUsbDevice.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Threading;
+using Nefarius.Drivers.WinUSB;
+using Nefarius.Utilities.DeviceManagement.PnP;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ internal class XboxWinUsbDevice : XboxDevice
+ {
+ private const byte XBOX_INTERFACE_CLASS = 0xFF; // Vendor-specific
+ private const byte XBOX_INTERFACE_SUB_CLASS = 0x47;
+ private const byte XBOX_INTERFACE_PROTOCOL = 0xD0;
+
+ public USBDevice UsbDevice { get; private set; }
+
+ private USBInterface mainInterface;
+
+ private Thread readThread;
+ private volatile bool readPackets = false;
+ private volatile bool ioError = false;
+
+ private volatile bool inputsEnabled = false;
+ private volatile bool previousInputsEnabled = false;
+
+ private XboxWinUsbDevice(USBDevice usb, USBInterface @interface)
+ : base(BackendType.Usb, mapGuide: true, @interface.OutPipe.MaximumPacketSize)
+ {
+ UsbDevice = usb;
+ mainInterface = @interface;
+ }
+
+ public static XboxWinUsbDevice TryCreate(string devicePath)
+ {
+ try
+ {
+ var usbDevice = USBDevice.GetSingleDeviceByPath(devicePath);
+ var mainInterface = FindMainInterface(usbDevice);
+ if (mainInterface == null)
+ {
+ usbDevice.Dispose();
+ return null;
+ }
+
+ return new XboxWinUsbDevice(usbDevice, mainInterface);
+ }
+ catch (Exception ex)
+ {
+ Logging.WriteException("Failed to create WinUSB device!", ex);
+ return null;
+ }
+ }
+
+ public void StartReading()
+ {
+ if (readPackets)
+ return;
+
+ ioError = false;
+ readPackets = true;
+ readThread = new Thread(ReadThread) { IsBackground = true };
+ readThread.Start();
+ }
+
+ public void StopReading()
+ {
+ // Abort in pipe
+ if (!ioError)
+ {
+ try
+ {
+ mainInterface.InPipe.Abort();
+ }
+ catch (Exception ex)
+ {
+ Logging.WriteExceptionVerbose($"Failed to abort read pipe!", ex);
+ }
+ }
+
+ // Reset device
+ SendMessage(XboxConfiguration.ResetDevice);
+
+ if (!readPackets)
+ return;
+
+ readPackets = false;
+ readThread.Join();
+ readThread = null;
+ }
+
+ public override void EnableInputs(bool enabled)
+ {
+ // Defer to read thread
+ inputsEnabled = enabled;
+ }
+
+ private void ReadThread()
+ {
+ Span readBuffer = stackalloc byte[mainInterface.InPipe.MaximumPacketSize];
+
+ while (readPackets)
+ {
+ // Read packet data
+ int bytesRead = ReadPacket(readBuffer);
+ if (bytesRead < 0)
+ break;
+
+ bool enabled = inputsEnabled;
+ if (enabled != previousInputsEnabled)
+ {
+ previousInputsEnabled = enabled;
+ base.EnableInputs(enabled);
+ }
+
+ // Process packet data
+ var packetData = readBuffer.Slice(0, bytesRead);
+ var result = HandleRawPacket(packetData);
+ switch (result)
+ {
+ case XboxResult.Success:
+ break;
+
+ case XboxResult.UnsupportedDevice:
+ SendMessage(XboxConfiguration.PowerOffDevice);
+ readPackets = false;
+ break;
+ }
+ }
+
+ readPackets = false;
+ }
+
+ private int ReadPacket(Span readBuffer)
+ {
+ if (ioError)
+ return -1;
+
+ const int retryThreshold = 3;
+ int retryCount = 0;
+
+ do
+ {
+ try
+ {
+ return mainInterface.InPipe.Read(readBuffer);
+ }
+ catch (Exception ex)
+ {
+ Logging.WriteExceptionVerbose($"Error while reading packet! (Attempt {retryCount + 1})", ex);
+ }
+ }
+ while (++retryCount < retryThreshold);
+
+ ioError = true;
+ return -1;
+ }
+
+ protected override XboxResult SendPacket(Span data)
+ {
+ if (ioError)
+ return XboxResult.Disconnected;
+
+ const int retryThreshold = 3;
+ int retryCount = 0;
+
+ do
+ {
+ try
+ {
+ mainInterface.OutPipe.Write(data);
+ return XboxResult.Success;
+ }
+ catch (Exception ex)
+ {
+ Logging.WriteExceptionVerbose($"Error while sending packet! (Attempt {retryCount + 1})", ex);
+ }
+ }
+ while (++retryCount < retryThreshold);
+
+ ioError = true;
+ return XboxResult.Disconnected;
+ }
+
+ private static USBInterface FindMainInterface(USBDevice device)
+ {
+ foreach (var iface in device.Interfaces)
+ {
+ // Ignore non-XGIP interfaces
+ if (iface.ClassValue != XBOX_INTERFACE_CLASS ||
+ iface.SubClass != XBOX_INTERFACE_SUB_CLASS ||
+ iface.Protocol != XBOX_INTERFACE_PROTOCOL)
+ continue;
+
+ // The main interface uses interrupt transfers
+ if (iface.InPipe?.TransferType != USBTransferType.Interrupt ||
+ iface.OutPipe?.TransferType != USBTransferType.Interrupt)
+ continue;
+
+ return iface;
+ }
+
+ return null;
+ }
+
+ protected override void ReleaseManagedResources()
+ {
+ base.ReleaseManagedResources();
+
+ if (readThread != null)
+ StopReading();
+
+ UsbDevice?.Dispose();
+ UsbDevice = null;
+ mainInterface = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/IBackendDevice.cs b/RB4InstrumentMapper.Core/Parsing/IBackendDevice.cs
new file mode 100644
index 0000000..48eb760
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/IBackendDevice.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ internal interface IBackendClient
+ {
+ ushort VendorId { get; }
+ ushort ProductId { get; }
+
+ bool MapGuideButton { get; }
+
+ XboxResult SendMessage(XboxMessage message);
+ XboxResult SendMessage(XboxCommandHeader header);
+ XboxResult SendMessage(XboxCommandHeader header, ref T data) where T : unmanaged;
+ XboxResult SendMessage(XboxCommandHeader header, Span data);
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/PacketLogging.cs b/RB4InstrumentMapper.Core/Parsing/PacketLogging.cs
new file mode 100644
index 0000000..3b39682
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/PacketLogging.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Diagnostics;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ internal enum PacketDirection
+ {
+ In,
+ Out,
+ }
+
+ internal static class PacketLogging
+ {
+ private const string inStr = "->";
+ private const string outStr = "<-";
+
+ public static void WriteLine(string message)
+ {
+ Debug.WriteLine(message);
+ Console.WriteLine(message);
+ Logging.Packet_WriteLine(message);
+ }
+
+ public static void WritePacket(ReadOnlySpan header, ReadOnlySpan data, PacketDirection direction)
+ {
+ if (!BackendSettings.LogPackets)
+ return;
+
+ var time = DateTime.Now;
+ int length = header.Length + data.Length;
+ string headerStr = ParsingUtils.ToHexString(header);
+ string dataStr = ParsingUtils.ToHexString(data);
+ string directionStr = direction == PacketDirection.In ? inStr : outStr;
+ WriteLine($"[{time:yyyy-MM-dd hh:mm:ss.fff}] [{length:D2}] {directionStr} {headerStr} | {dataStr}");
+ }
+
+ public static bool TryParsePacket(ReadOnlySpan input,
+ out byte[] header, out byte[] data, out PacketDirection direction)
+ {
+ header = Array.Empty();
+ data = Array.Empty();
+ direction = PacketDirection.In;
+ if (input.IsEmpty)
+ return false;
+
+ // Skip time and packet length
+ // For easier manual packet log construction, these are optional
+ int lastBracket = input.LastIndexOf(']');
+ if (lastBracket >= 0)
+ input = input.Slice(++lastBracket).TrimStart();
+
+ // Parse direction
+ // For easier manual packet log construction, this defaults to in
+ if (input.StartsWith(inStr.AsSpan()))
+ {
+ direction = PacketDirection.In;
+ input = input.Slice(inStr.Length).TrimStart();
+ }
+ else if (input.StartsWith(outStr.AsSpan()))
+ {
+ input = input.Slice(outStr.Length).TrimStart();
+ direction = PacketDirection.Out;
+ }
+
+ // Find header separator
+ int headerSeparator = input.LastIndexOf('|');
+ if (headerSeparator >= 0)
+ {
+ // Parse data (optional)
+ // Not all packets have data
+ var dataText = input.Slice(headerSeparator + 1);
+ if (!ParsingUtils.TryParseBytesFromHexString(dataText, out data))
+ return false;
+ input = input.Slice(0, headerSeparator);
+ }
+
+ // Parse header
+ if (!ParsingUtils.TryParseBytesFromHexString(input, out header))
+ return false;
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/Drums/XboxDrumInput.cs b/RB4InstrumentMapper.Core/Parsing/Packets/Drums/XboxDrumInput.cs
new file mode 100644
index 0000000..a43c913
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/Drums/XboxDrumInput.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Re-definitions for button flags that have specific meanings.
+ ///
+ [Flags]
+ internal enum XboxDrumButton : ushort
+ {
+ // Not used as these are for menu navigation purposes
+ // RedPad = GamepadButton.B,
+ // GreenPad = GamepadButton.A,
+ KickOne = XboxGamepadButton.LeftBumper,
+ KickTwo = XboxGamepadButton.RightBumper
+ }
+
+ ///
+ /// An input report from a drumkit.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxDrumInput
+ {
+ public const byte CommandId = 0x20;
+
+ public ushort Buttons;
+ private readonly ushort pads;
+ private readonly ushort cymbals;
+
+ public byte RedPad => (byte)((pads & 0x00F0) >> 4);
+ public byte YellowPad => (byte)(pads & 0x000F);
+ public byte BluePad => (byte)((pads & 0xF000) >> 12);
+ public byte GreenPad => (byte)((pads & 0x0F00) >> 8);
+
+ public byte YellowCymbal => (byte)((cymbals & 0x00F0) >> 4);
+ public byte BlueCymbal => (byte)(cymbals & 0x000F);
+ public byte GreenCymbal => (byte)((cymbals & 0xF000) >> 12);
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/GHLGuitar/XboxGHLGuitarInput.cs b/RB4InstrumentMapper.Core/Parsing/Packets/GHLGuitar/XboxGHLGuitarInput.cs
new file mode 100644
index 0000000..8f2d160
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/GHLGuitar/XboxGHLGuitarInput.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Button flags for GHL guitars.
+ ///
+ [Flags]
+ internal enum XboxGHLGuitarButton : ushort
+ {
+ White1 = 0x0001,
+ Black1 = 0x0002,
+ Black2 = 0x0004,
+ Black3 = 0x0008,
+ White2 = 0x0010,
+ White3 = 0x0020,
+
+ Select = 0x0100,
+ Start = 0x0200,
+ GHTV = 0x0400,
+
+ // Already handled by the guide button messages
+ // DpadCenter = 0x1000,
+ }
+
+ ///
+ /// D-pad states for GHL guitars.
+ ///
+ public enum XboxGHLGuitarDpad : byte
+ {
+ // vJoy continuous PoV hat values range from 0 to 35999 (measured in 1/100 of a degree).
+ // The value is measured clockwise, with up being 0.
+ Neutral = 0x0F,
+ Up = 0,
+ UpRight = 1,
+ Right = 2,
+ DownRight = 3,
+ Down = 4,
+ DownLeft = 5,
+ Left = 6,
+ UpLeft = 7
+ }
+
+ ///
+ /// An input report from a guitar.
+ ///
+ [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 27)]
+ internal struct XboxGHLGuitarInput
+ {
+ public const byte CommandId = 0x21;
+
+ public const byte StrumbarCenter = 0x80;
+
+ // For reference; PS3 stick axes put up as 0x00 and down as 0xFF
+ // public const byte StrumbarUp = 0x00;
+ // public const byte StrumbarDown = 0xFF;
+
+ [FieldOffset(0)]
+ public XboxGHLGuitarButton Buttons;
+
+ [FieldOffset(2)]
+ public XboxGHLGuitarDpad Dpad;
+
+ [FieldOffset(4)]
+ public byte StrumBar;
+
+ [FieldOffset(6)]
+ public byte WhammyBar;
+
+ [FieldOffset(19)]
+ public byte Tilt;
+
+ public bool Black1 => (Buttons & XboxGHLGuitarButton.Black1) != 0;
+ public bool Black2 => (Buttons & XboxGHLGuitarButton.Black2) != 0;
+ public bool Black3 => (Buttons & XboxGHLGuitarButton.Black3) != 0;
+ public bool White1 => (Buttons & XboxGHLGuitarButton.White1) != 0;
+ public bool White2 => (Buttons & XboxGHLGuitarButton.White2) != 0;
+ public bool White3 => (Buttons & XboxGHLGuitarButton.White3) != 0;
+
+ public bool HeroPower => (Buttons & XboxGHLGuitarButton.Select) != 0;
+ public bool Pause => (Buttons & XboxGHLGuitarButton.Start) != 0;
+ public bool GHTV => (Buttons & XboxGHLGuitarButton.GHTV) != 0;
+
+ // public bool DpadCenter => (Buttons & XboxGHLGuitarButton.DpadCenter) != 0;
+
+ public bool DpadUp => Dpad == XboxGHLGuitarDpad.Up || Dpad == XboxGHLGuitarDpad.UpLeft || Dpad == XboxGHLGuitarDpad.UpRight;
+ public bool DpadDown => Dpad == XboxGHLGuitarDpad.Down || Dpad == XboxGHLGuitarDpad.DownLeft || Dpad == XboxGHLGuitarDpad.DownRight;
+ public bool DpadLeft => Dpad == XboxGHLGuitarDpad.Left || Dpad == XboxGHLGuitarDpad.UpLeft || Dpad == XboxGHLGuitarDpad.DownLeft;
+ public bool DpadRight => Dpad == XboxGHLGuitarDpad.Right || Dpad == XboxGHLGuitarDpad.UpRight || Dpad == XboxGHLGuitarDpad.DownRight;
+
+ public bool StrumUp => StrumBar < StrumbarCenter;
+ public bool StrumDown => StrumBar > StrumbarCenter;
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/GHLGuitar/XboxGHLGuitarOutput.cs b/RB4InstrumentMapper.Core/Parsing/Packets/GHLGuitar/XboxGHLGuitarOutput.cs
new file mode 100644
index 0000000..aa57f3d
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/GHLGuitar/XboxGHLGuitarOutput.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Threading;
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal unsafe struct XboxGHLGuitarOutput
+ {
+ public const byte CommandId = 0x22;
+
+ public byte SubCommand;
+ public fixed byte Data[7];
+ }
+
+ [Flags]
+ internal enum XboxGHLGuitarPlayerLeds : byte
+ {
+ None = 0,
+
+ Led1 = 0x01,
+ Led2 = 0x02,
+ Led3 = 0x04,
+ Led4 = 0x08,
+
+ All = Led1 | Led2 | Led3 | Led4,
+
+ Player1 = Led1,
+ Player2 = Led2,
+ Player3 = Led3,
+ Player4 = Led4,
+ Player5 = Led1 | Led2,
+ Player6 = Led1 | Led3,
+ Player7 = Led1 | Led4,
+ Player8 = Led2 | Led3,
+ Player9 = Led2 | Led4,
+ Player10 = Led3 | Led4,
+ Player11 = Led1 | Led2 | Led3,
+ Player12 = Led1 | Led2 | Led4,
+ Player13 = Led1 | Led3 | Led4,
+ Player14 = Led2 | Led3 | Led4,
+ Player15 = All,
+
+ // If someone connects this many devices it's their problem that this is the same as 15 lol
+ Player16 = All,
+ }
+
+ internal static class XboxGHLGuitarSetPlayerLeds
+ {
+ public const byte SubCommand = 0x01;
+
+ public static unsafe XboxMessage Create(XboxGHLGuitarPlayerLeds leds)
+ {
+ var output = new XboxGHLGuitarOutput()
+ {
+ SubCommand = SubCommand,
+ };
+
+ output.Data[0] = 0x08;
+ output.Data[1] = (byte)leds;
+
+ return new XboxMessage()
+ {
+ Header = new XboxCommandHeader()
+ {
+ CommandId = XboxGHLGuitarOutput.CommandId,
+ Flags = XboxCommandFlags.None,
+ },
+ Data = output,
+ };
+ }
+ }
+
+ internal class XboxGHLGuitarKeepAlive : IDisposable
+ {
+ public const byte SubCommand = 0x02;
+ public const int SendPeriodMilliseconds = 8000;
+
+ public static readonly XboxMessage Message = CreateMessage();
+
+ private readonly IBackendClient client;
+ private readonly Timer sendTimer;
+
+ public unsafe XboxGHLGuitarKeepAlive(IBackendClient client)
+ {
+ this.client = client;
+ sendTimer = new Timer(SendKeepAlive, null, 0, SendPeriodMilliseconds);
+ }
+
+ ~XboxGHLGuitarKeepAlive()
+ {
+ Dispose(false);
+ }
+
+ private static unsafe XboxMessage CreateMessage()
+ {
+ var output = new XboxGHLGuitarOutput()
+ {
+ SubCommand = SubCommand,
+ };
+
+ // Unknown magic data
+ output.Data[0] = 0x08;
+ output.Data[1] = 0x0A;
+
+ return new XboxMessage()
+ {
+ Header = new XboxCommandHeader()
+ {
+ CommandId = XboxGHLGuitarOutput.CommandId,
+ Flags = XboxCommandFlags.None,
+ },
+ Data = output,
+ };
+ }
+
+ private void SendKeepAlive(object _) => client.SendMessage(Message);
+
+ ///
+ /// Disposes the mapper and any resources it uses.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ sendTimer.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/Gamepad/XboxGamepadInput.cs b/RB4InstrumentMapper.Core/Parsing/Packets/Gamepad/XboxGamepadInput.cs
new file mode 100644
index 0000000..40564ac
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/Gamepad/XboxGamepadInput.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Flag definitions for the buttons bytes.
+ ///
+ [Flags]
+ internal enum XboxGamepadButton : ushort
+ {
+ Sync = 0x0001,
+ // Unused = 0x0002,
+ Menu = 0x0004,
+ Options = 0x0008,
+ A = 0x0010,
+ B = 0x0020,
+ X = 0x0040,
+ Y = 0x0080,
+ DpadUp = 0x0100,
+ DpadDown = 0x0200,
+ DpadLeft = 0x0400,
+ DpadRight = 0x0800,
+ LeftBumper = 0x1000,
+ RightBumper = 0x2000,
+ LeftStickPress = 0x4000,
+ RightStickPress = 0x8000
+ }
+
+ ///
+ /// An input report from a gamepad.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxGamepadInput
+ {
+ public const byte CommandId = 0x20;
+
+ public const ushort TriggerMax = 0x03FF;
+
+ public bool A => (Buttons & (ushort)XboxGamepadButton.A) != 0;
+ public bool B => (Buttons & (ushort)XboxGamepadButton.B) != 0;
+ public bool X => (Buttons & (ushort)XboxGamepadButton.X) != 0;
+ public bool Y => (Buttons & (ushort)XboxGamepadButton.Y) != 0;
+
+ public bool DpadUp => (Buttons & (ushort)XboxGamepadButton.DpadUp) != 0;
+ public bool DpadDown => (Buttons & (ushort)XboxGamepadButton.DpadDown) != 0;
+ public bool DpadLeft => (Buttons & (ushort)XboxGamepadButton.DpadLeft) != 0;
+ public bool DpadRight => (Buttons & (ushort)XboxGamepadButton.DpadRight) != 0;
+
+ public bool LeftBumper => (Buttons & (ushort)XboxGamepadButton.LeftBumper) != 0;
+ public bool RightBumper => (Buttons & (ushort)XboxGamepadButton.RightBumper) != 0;
+ public bool LeftStickPress => (Buttons & (ushort)XboxGamepadButton.LeftStickPress) != 0;
+ public bool RightStickPress => (Buttons & (ushort)XboxGamepadButton.RightStickPress) != 0;
+
+ public bool Menu => (Buttons & (ushort)XboxGamepadButton.Menu) != 0;
+ public bool Options => (Buttons & (ushort)XboxGamepadButton.Options) != 0;
+
+ public ushort Buttons;
+ public ushort LeftTrigger;
+ public ushort RightTrigger;
+ public short LeftStickX;
+ public short LeftStickY;
+ public short RightStickX;
+ public short RightStickY;
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/Gamepad/XboxGamepadRumble.cs b/RB4InstrumentMapper.Core/Parsing/Packets/Gamepad/XboxGamepadRumble.cs
new file mode 100644
index 0000000..23605b7
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/Gamepad/XboxGamepadRumble.cs
@@ -0,0 +1,53 @@
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// An input report from a gamepad.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxGamepadRumble
+ {
+ public enum Flags : byte
+ {
+ RightRumble = 0x01,
+ LeftRumble = 0x02,
+ RightTrigger = 0x04,
+ LeftTrigger = 0x08,
+ }
+
+ public const byte CommandId = 0x09;
+
+ private byte unknown;
+ private Flags flags;
+
+ public byte leftTrigger;
+ public byte rightTrigger;
+ public byte leftRumble;
+ public byte rightRumble;
+
+ public byte duration;
+ public byte delay;
+ public byte repeat;
+
+ public static XboxMessage Create(byte left, byte right)
+ => new XboxMessage()
+ {
+ Header = new XboxCommandHeader()
+ {
+ CommandId = CommandId,
+ Flags = XboxCommandFlags.None,
+ },
+ Data = new XboxGamepadRumble()
+ {
+ flags = Flags.LeftRumble | Flags.RightRumble,
+ leftRumble = left,
+ rightRumble = right,
+
+ duration = 0xFF,
+ repeat = 0xEB,
+ },
+ };
+ }
+
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/Guitar/XboxGuitarInput.cs b/RB4InstrumentMapper.Core/Parsing/Packets/Guitar/XboxGuitarInput.cs
new file mode 100644
index 0000000..5fe85d5
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/Guitar/XboxGuitarInput.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Re-definitions for button flags that have specific meanings.
+ ///
+ [Flags]
+ internal enum XboxGuitarButton : ushort
+ {
+ StrumUp = XboxGamepadButton.DpadUp,
+ StrumDown = XboxGamepadButton.DpadDown,
+ GreenFret = XboxGamepadButton.A,
+ RedFret = XboxGamepadButton.B,
+ YellowFret = XboxGamepadButton.Y,
+ BlueFret = XboxGamepadButton.X,
+ OrangeFret = XboxGamepadButton.LeftBumper,
+ LowerFretFlag = XboxGamepadButton.LeftStickPress
+ }
+
+ ///
+ /// Flags used in and
+ ///
+ [Flags]
+ internal enum XboxGuitarFret : byte
+ {
+ Green = 0x01,
+ Red = 0x02,
+ Yellow = 0x04,
+ Blue = 0x08,
+ Orange = 0x10
+ }
+
+ ///
+ /// An input report from a guitar.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxGuitarInput
+ {
+ public const byte CommandId = 0x20;
+
+ public ushort Buttons;
+ public byte Tilt;
+ public byte WhammyBar;
+ public byte PickupSwitch;
+ public byte UpperFrets;
+ public byte LowerFrets;
+ private readonly byte unk1;
+ private readonly byte unk2;
+ private readonly byte unk3;
+
+ public bool Green => ((UpperFrets | LowerFrets) & (byte)XboxGuitarFret.Green) != 0;
+ public bool Red => ((UpperFrets | LowerFrets) & (byte)XboxGuitarFret.Red) != 0;
+ public bool Yellow => ((UpperFrets | LowerFrets) & (byte)XboxGuitarFret.Yellow) != 0;
+ public bool Blue => ((UpperFrets | LowerFrets) & (byte)XboxGuitarFret.Blue) != 0;
+ public bool Orange => ((UpperFrets | LowerFrets) & (byte)XboxGuitarFret.Orange) != 0;
+
+ public bool LowerFretsPressed => LowerFrets != 0;
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/Guitar/XboxRiffmasterInput.cs b/RB4InstrumentMapper.Core/Parsing/Packets/Guitar/XboxRiffmasterInput.cs
new file mode 100644
index 0000000..d610f5b
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/Guitar/XboxRiffmasterInput.cs
@@ -0,0 +1,25 @@
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// An input report from a Riffmaster guitar.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxRiffmasterInput
+ {
+ public const byte CommandId = 0x20;
+
+ public XboxGuitarInput Base;
+
+ public short JoystickX;
+ public short JoystickY;
+
+ private byte systemButtons;
+
+ public bool ShareButton => (systemButtons & 0x01) != 0;
+
+ public bool JoystickClick => (Base.Buttons & (ushort)XboxGamepadButton.LeftStickPress) != 0
+ && !Base.LowerFretsPressed; // Overlaps with the solo fret flag
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxAcknowledgement.cs b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxAcknowledgement.cs
new file mode 100644
index 0000000..1d75c36
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxAcknowledgement.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Acknowledges a prior command and provides info about current buffer allocations.
+ ///
+ ///
+ /// Used for communication reliability and error detection.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxAcknowledgement
+ {
+ public const byte CommandId = 0x01;
+
+ private byte unk1;
+ public byte InnerCommand;
+ public byte InnerFlags_Client;
+ public ushort BytesReceived;
+ private ushort unk2;
+ public ushort RemainingBuffer;
+
+ public XboxCommandFlags InnerFlags
+ {
+ get => (XboxCommandFlags)(InnerFlags_Client & 0xF0);
+ set => InnerFlags_Client = (byte)((byte)value & 0xF0 | InnerClient);
+ }
+
+ public byte InnerClient
+ {
+ get => (byte)(InnerFlags_Client & 0x0F);
+ set => InnerFlags_Client = (byte)((byte)InnerFlags | value & 0x0F);
+ }
+
+ public static (XboxCommandHeader header, XboxAcknowledgement acknowledge) FromMessage(XboxCommandHeader header,
+ ReadOnlySpan messageBuffer)
+ {
+ // The Xbox One driver seems to always send this for the inner flag
+ header.Flags = XboxCommandFlags.SystemCommand;
+
+ // Set acknowledgement data
+ var acknowledge = new XboxAcknowledgement()
+ {
+ unk1 = 0,
+ InnerCommand = header.CommandId,
+ InnerFlags_Client = header.Flags_Client,
+ unk2 = 0,
+ BytesReceived = (ushort)messageBuffer.Length,
+ };
+
+ // Set remaining header data (length is set when sending)
+ header.CommandId = CommandId;
+
+ return (header, acknowledge);
+ }
+
+ public static (XboxCommandHeader header, XboxAcknowledgement acknowledge) FromMessage(XboxCommandHeader header,
+ ReadOnlySpan messageBuffer, XboxChunkBuffer chunkBuffer)
+ {
+ var pair = FromMessage(header, messageBuffer);
+
+ if (chunkBuffer.Buffer != null)
+ {
+ pair.acknowledge.BytesReceived = (ushort)chunkBuffer.BytesUsed;
+ pair.acknowledge.RemainingBuffer = (ushort)chunkBuffer.BytesRemaining;
+ }
+
+ return pair;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxArrival.cs b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxArrival.cs
new file mode 100644
index 0000000..7237fc0
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxArrival.cs
@@ -0,0 +1,19 @@
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Indicates that a new device has connected and is awaiting initialization.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal readonly struct XboxArrival
+ {
+ public const byte CommandId = 0x02;
+
+ public readonly ulong SerialNumber;
+ public readonly ushort VendorId;
+ public readonly ushort ProductId;
+ private readonly ulong ignored1;
+ private readonly ulong ignored2;
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxAuthentication.cs b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxAuthentication.cs
new file mode 100644
index 0000000..c18697e
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxAuthentication.cs
@@ -0,0 +1,17 @@
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ internal static class XboxAuthentication
+ {
+ public const byte CommandId = 0x06;
+
+ public static readonly XboxMessage SuccessMessage = new XboxMessage()
+ {
+ Header = new XboxCommandHeader()
+ {
+ CommandId = CommandId,
+ Flags = XboxCommandFlags.SystemCommand,
+ },
+ Bytes = new byte[] { 0x01, 0x00 },
+ };
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxConfiguration.cs b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxConfiguration.cs
new file mode 100644
index 0000000..24ba45b
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxConfiguration.cs
@@ -0,0 +1,78 @@
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ internal enum XboxConfigurationCommand : byte
+ {
+ PowerOn = 0x00,
+ Sleep = 0x01,
+ PowerOff = 0x04,
+ WirelessPairing = 0x06,
+ Reset = 0x07,
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxConfiguration
+ {
+ public const byte CommandId = 0x05;
+
+ public static readonly XboxMessage PowerOnDevice = new XboxMessage()
+ {
+ Header = new XboxCommandHeader()
+ {
+ CommandId = CommandId,
+ Flags = XboxCommandFlags.SystemCommand,
+ },
+ Data = new XboxConfiguration()
+ {
+ SubCommand = XboxConfigurationCommand.PowerOn,
+ }
+ };
+
+ public static readonly XboxMessage PowerOffDevice = new XboxMessage()
+ {
+ Header = new XboxCommandHeader()
+ {
+ CommandId = CommandId,
+ Flags = XboxCommandFlags.SystemCommand,
+ },
+ Data = new XboxConfiguration()
+ {
+ SubCommand = XboxConfigurationCommand.PowerOff,
+ }
+ };
+
+ public static readonly XboxMessage ResetDevice = new XboxMessage()
+ {
+ Header = new XboxCommandHeader()
+ {
+ CommandId = CommandId,
+ Flags = XboxCommandFlags.SystemCommand,
+ },
+ Data = new XboxConfiguration()
+ {
+ SubCommand = XboxConfigurationCommand.Reset,
+ }
+ };
+
+ public XboxConfigurationCommand SubCommand;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxConfiguration
+ where TSub : unmanaged
+ {
+ public XboxConfigurationCommand SubCommand;
+ public TSub SubData;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal unsafe struct XboxWirelessPairing
+ {
+ public const XboxConfigurationCommand SubCommand = XboxConfigurationCommand.WirelessPairing;
+
+ private fixed byte pairingAddress[6];
+ public ushort countryCode;
+ private fixed byte unknown[6];
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxDescriptor.cs b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxDescriptor.cs
new file mode 100644
index 0000000..243bf5b
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxDescriptor.cs
@@ -0,0 +1,225 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// The descriptor data of an Xbox One device.
+ ///
+ ///
+ /// A large amount of the descriptor data is ignored, only data necessary for identifying device types is read.
+ ///
+ internal class XboxDescriptor
+ {
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ private struct Header
+ {
+ public ushort HeaderLength;
+ private int unk1;
+ private ulong unk2;
+ public ushort DataLength;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ private struct Offsets
+ {
+ public ushort CustomCommands;
+ public ushort FirmwareVersions;
+ public ushort AudioFormats;
+ public ushort InputCommands;
+ public ushort OutputCommands;
+ public ushort ClassNames;
+ public ushort InterfaceGuids;
+ public ushort HidDescriptor;
+ private ushort unk1;
+ private ushort unk2;
+ private ushort unk3;
+ }
+
+ public static readonly XboxMessage GetDescriptor = new XboxMessage()
+ {
+ Header = new XboxCommandHeader()
+ {
+ CommandId = CommandId,
+ Flags = XboxCommandFlags.SystemCommand,
+ },
+ // Header only, no data
+ };
+
+ public const byte CommandId = 0x04;
+
+ public HashSet InputCommands { get; private set; }
+ public HashSet OutputCommands { get; private set; }
+ public HashSet ClassNames { get; private set; }
+ public HashSet InterfaceGuids { get; private set; }
+
+ public static bool Parse(ReadOnlySpan data, out XboxDescriptor descriptor)
+ {
+ descriptor = new XboxDescriptor();
+ return descriptor.Parse(data);
+ }
+
+ private unsafe bool Parse(ReadOnlySpan data)
+ {
+ if (data.IsEmpty)
+ throw new ArgumentNullException(nameof(data));
+
+ // Descriptor header size
+ if (!ParsingUtils.TryRead(data, out ushort headerSize))
+ {
+ Debug.Fail($"Couldn't parse descriptor header size! Buffer size: {data.Length}, element size: {sizeof(ushort)}");
+ return false;
+ }
+
+ // Expecting a certain size
+ if (headerSize != sizeof(Header))
+ {
+ Debug.Fail($"Header size does not match expected size! Expected: {sizeof(Header)}, actual: {headerSize}");
+ return false;
+ }
+
+ // Descriptor header
+ if (!ParsingUtils.TryRead(data, out Header header))
+ {
+ Debug.Fail($"Couldn't parse descriptor header! Buffer size: {data.Length}, header size: {headerSize}");
+ return false;
+ }
+
+ // Verify buffer size
+ if (data.Length < header.DataLength)
+ {
+ Debug.Fail($"Buffer size is smaller than size listed in header! Buffer size: {data.Length}, listed size: {header.DataLength}");
+ return false;
+ }
+ Debug.Assert(data.Length == header.DataLength, $"Buffer size is not the same as size listed in header! Buffer size: {data.Length}, listed size: {header.DataLength}");
+ data = data.Slice(header.HeaderLength);
+
+ // Data offsets
+ if (!ParsingUtils.TryRead(data, out Offsets offsets))
+ {
+ Debug.Fail($"Couldn't parse descriptor offsets! Buffer size: {data.Length}, offsets size: {sizeof(Offsets)}");
+ return false;
+ }
+ // No slice, offsets are relative to the start of the offsets block
+
+ // Data elements
+ InputCommands = ParseUnique(data, offsets.InputCommands, nameof(InputCommands));
+ OutputCommands = ParseUnique(data, offsets.OutputCommands, nameof(OutputCommands));
+ ClassNames = ParseStrings(data, offsets.ClassNames, nameof(ClassNames));
+ InterfaceGuids = ParseUnique(data, offsets.InterfaceGuids, nameof(InterfaceGuids));
+
+ return true;
+ }
+
+ private static bool VerifyOffset(ReadOnlySpan buffer, ushort offset, int elementSize, out byte count, string elementName)
+ {
+ // Null offset means no elements are available
+ if (offset == 0)
+ {
+ count = 0;
+ return false;
+ }
+
+ // Ensure offset is within bounds
+ if (buffer.Length <= offset)
+ {
+ Debug.Fail($"Offset of {elementName} is greater than size of buffer! Offset: {offset}; Buffer size: {buffer.Length}");
+ count = 0;
+ return false;
+ }
+
+ // Get number of elements
+ count = buffer[offset];
+ // Zero count means no elements are available
+ if (count == 0)
+ {
+ return false;
+ }
+
+ // Element size of 0 is used for variable-length types like strings
+ if (elementSize == 0)
+ {
+ // Can't verify bounds here, everything else checks out so treat it as valid
+ return true;
+ }
+
+ // Ensure total size of elements is within bounds
+ var fromElements = buffer.Slice(offset);
+ int elementsSize = elementSize * count;
+ if (fromElements.Length < elementsSize)
+ {
+ Debug.Fail($"Size of {elementName} is greater than size of buffer from offset! Offset: {offset:X4}; Count: {count}; Element size: {elementSize}; Total length of elements: {elementsSize}; Buffer length from offset: {fromElements.Length}");
+ count = 0;
+ return false;
+ }
+
+ // Offset is valid
+ return true;
+ }
+
+ private static unsafe HashSet ParseUnique(ReadOnlySpan buffer, ushort offset, string elementName)
+ where T : unmanaged
+ {
+ if (!VerifyOffset(buffer, offset, sizeof(T), out byte count, elementName) || count == 0)
+ {
+ return null;
+ }
+
+ // Get data bounds
+ buffer = buffer.Slice(offset + sizeof(byte), count * sizeof(T));
+
+ // Get element data
+ var set = new HashSet();
+ var elements = MemoryMarshal.Cast(buffer);
+ foreach (var element in elements)
+ {
+ set.Add(element);
+ }
+
+ return set;
+ }
+
+ private static unsafe HashSet ParseStrings(ReadOnlySpan buffer, ushort offset, string elementName)
+ {
+ if (!VerifyOffset(buffer, offset, 0, out byte count, elementName) || count == 0)
+ {
+ return null;
+ }
+
+ var set = new HashSet();
+ buffer = buffer.Slice(offset + 1);
+ for (byte index = 0; index < count; index++)
+ {
+ // Get length
+ if (!ParsingUtils.TryRead(buffer, out ushort length))
+ {
+ set.TrimExcess();
+ break;
+ }
+ buffer = buffer.Slice(sizeof(ushort));
+
+ // Ensure length is within bounds
+ if (buffer.Length < length)
+ {
+ Debug.Fail($"Descriptor string length is greater than buffer size! Index: {index}; String length: {length}; Buffer size: {buffer.Length}");
+ set.TrimExcess();
+ break;
+ }
+
+ // Parse `length` bytes into a string
+ // Pointers are more efficient here, `char` is 2 bytes while these strings are 1-byte characters
+ fixed (byte* ptr = buffer)
+ {
+ sbyte* sPtr = (sbyte*)ptr;
+ var str = new string(sPtr, 0, length);
+ set.Add(str);
+ }
+ buffer = buffer.Slice(length);
+ }
+
+ return set;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxKeystroke.cs b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxKeystroke.cs
new file mode 100644
index 0000000..f3f7b70
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxKeystroke.cs
@@ -0,0 +1,37 @@
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Flags for keystroke events.
+ ///
+ internal enum XboxKeystrokeFlags : byte
+ {
+ Pressed = 0x01,
+ }
+
+ ///
+ /// Possible key codes.
+ ///
+ ///
+ /// These mirror those in the Win32 API; for brevity, only the ones used are defined here.
+ ///
+ internal enum XboxKeyCode : byte
+ {
+ LeftWindows = 0x5B, // Used for the guide button
+ }
+
+ ///
+ /// A keystroke event from a controller.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxKeystroke
+ {
+ public const byte CommandId = 0x07;
+
+ public XboxKeystrokeFlags Flags;
+ public XboxKeyCode Keycode;
+
+ public bool Pressed => (Flags & XboxKeystrokeFlags.Pressed) != 0;
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxLedControl.cs b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxLedControl.cs
new file mode 100644
index 0000000..8a5c716
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxLedControl.cs
@@ -0,0 +1,40 @@
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ internal enum XboxLedMode : byte
+ {
+ Off = 0x00,
+ On = 0x01,
+ BlinkFast = 0x02,
+ BlinkNormal = 0x03,
+ BlinkSlow = 0x04,
+ FadeSlow = 0x08,
+ FadeFast = 0x09,
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxLedControl
+ {
+ public const byte CommandId = 0x0a;
+
+ public static readonly XboxMessage EnableLed = new XboxMessage()
+ {
+ Header = new XboxCommandHeader()
+ {
+ CommandId = CommandId,
+ Flags = XboxCommandFlags.SystemCommand,
+ },
+ Data = new XboxLedControl()
+ {
+ Mode = XboxLedMode.On,
+ Brightness = 0x14
+ }
+ };
+
+ private byte unknown;
+
+ public XboxLedMode Mode;
+ public byte Brightness;
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxStatus.cs b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxStatus.cs
new file mode 100644
index 0000000..6511235
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/System/XboxStatus.cs
@@ -0,0 +1,48 @@
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Available types of batteries that can be used on a controller.
+ ///
+ internal enum XboxBatteryType : byte
+ {
+ Wired = 0,
+ Standard = 1,
+ ChargeKit = 2,
+ }
+
+ ///
+ /// The amount of battery remaining on the controller.
+ ///
+ internal enum XboxBatteryLevel : byte
+ {
+ Low = 0,
+ Medium = 1,
+ High = 2,
+ Full = 3,
+
+ Wired = Low,
+ }
+
+ ///
+ /// Provides information about a device's current status, such as battery type and level.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal readonly struct XboxStatus
+ {
+ public const byte CommandId = 0x03;
+
+ private readonly byte status;
+
+ // The GHL guitar does not send these bytes,
+ // so they are ignored since we don't recognize them anyways
+ // private readonly byte unk1;
+ // private readonly byte unk2;
+ // private readonly byte unk3;
+
+ public bool Connected => (status & 0b1100_0000) != 0;
+ public XboxBatteryType BatteryType => (XboxBatteryType)((status & 0b0000_1100) >> 2);
+ public XboxBatteryLevel BatteryLevel => (XboxBatteryLevel)(status & 0b0000_0011);
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyDeviceConnect.cs b/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyDeviceConnect.cs
new file mode 100644
index 0000000..cfd3586
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyDeviceConnect.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Possible device types for the wireless legacy adapter.
+ ///
+ internal enum XboxWirelessLegacyDeviceType : byte
+ {
+ Guitar = 1,
+ Drums = 2,
+ }
+
+ ///
+ /// Possible subtypes for XInput devices.
+ ///
+ internal enum XInputDeviceSubtype : byte
+ {
+ Unknown = 0,
+ Gamepad = 1,
+ Wheel = 2,
+ ArcadeStick = 3,
+ FlightStick = 4,
+ DancePad = 5,
+ Guitar = 6,
+ GuitarAlternate = 7,
+ Drums = 8,
+ GuitarBass = 11,
+ Keyboard = 15,
+ ArcadePad = 19,
+ Turntable = 23,
+ }
+
+ ///
+ /// Reports info about a device that has just connected to a wireless legacy adapter.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal unsafe struct XboxWirelessLegacyDeviceConnect
+ {
+ public const byte CommandId = 0x22;
+
+ public const int MinimumLength = 6;
+
+ public byte UserIndex;
+ public XboxWirelessLegacyDeviceType DeviceType;
+
+ private ushort vendorId; // big-endian
+ private byte unknown;
+ private byte xinputSubype;
+
+ private fixed char name[124];
+
+ public ushort VendorId => (ushort)((vendorId & 0xFF) << 8 | (vendorId & 0xFF00) >> 8);
+ public XInputDeviceSubtype XInputSubType => (XInputDeviceSubtype)(xinputSubype & 0x7F);
+
+ public static bool TryParse(ReadOnlySpan buffer, out XboxWirelessLegacyDeviceConnect connectInfo)
+ {
+ connectInfo = new XboxWirelessLegacyDeviceConnect();
+
+ if (buffer.Length < MinimumLength)
+ return false;
+
+ // Create a byte buffer reference and copy the message buffer into it
+ var writeBuffer = new Span(Unsafe.AsPointer(ref connectInfo), sizeof(XboxWirelessLegacyDeviceConnect));
+ if (buffer.Length > sizeof(XboxWirelessLegacyDeviceConnect))
+ buffer = buffer.Slice(0, sizeof(XboxWirelessLegacyDeviceConnect));
+
+ return buffer.TryCopyTo(writeBuffer);
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyDeviceDisconnect.cs b/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyDeviceDisconnect.cs
new file mode 100644
index 0000000..f23709c
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyDeviceDisconnect.cs
@@ -0,0 +1,15 @@
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Notifies when a device has disconnected from a wireless legacy adapter.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxWirelessLegacyDeviceDisconnect
+ {
+ public const byte CommandId = 0x23;
+
+ public byte UserIndex;
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyInput.cs b/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyInput.cs
new file mode 100644
index 0000000..5261ed7
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyInput.cs
@@ -0,0 +1,17 @@
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// The input report header used by the wireless legacy adapter.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxWirelessLegacyInputHeader
+ {
+ public const byte CommandId = 0x20;
+
+ public ushort Buttons;
+ public byte UserIndex;
+ public XboxWirelessLegacyDeviceType DeviceType;
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyRequestDevices.cs b/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyRequestDevices.cs
new file mode 100644
index 0000000..bbeb3f2
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/WirelessLegacy/XboxWirelessLegacyRequestDevices.cs
@@ -0,0 +1,20 @@
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Requests info for devices currently connected to a wireless legacy adapter.
+ ///
+ internal static class XboxWirelessLegacyRequestDevices
+ {
+ public const byte CommandId = 0x24;
+
+ public static readonly XboxMessage RequestDevices = new XboxMessage()
+ {
+ Header = new XboxCommandHeader()
+ {
+ CommandId = CommandId,
+ Flags = XboxCommandFlags.None,
+ },
+ // No data
+ };
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/XboxChunkBuffer.cs b/RB4InstrumentMapper.Core/Parsing/Packets/XboxChunkBuffer.cs
new file mode 100644
index 0000000..5659278
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/XboxChunkBuffer.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Diagnostics;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ internal class XboxChunkBuffer
+ {
+ public byte[] Buffer { get; private set; }
+ public int BytesUsed { get; private set; }
+ public int BytesRemaining => Buffer != null ? Buffer.Length - BytesUsed : 0;
+
+ public XboxResult ProcessChunk(ref XboxCommandHeader header, ref ReadOnlySpan chunkData)
+ {
+ int bufferIndex = header.ChunkIndex;
+
+ // Do nothing with chunks of length 0
+ if (bufferIndex <= 0)
+ {
+ // Chunked packets with a length of 0 are valid and have been observed with Elite controllers
+ bool emptySequence = bufferIndex == 0;
+ Debug.Assert(emptySequence, $"Negative buffer index {bufferIndex}!");
+ return emptySequence ? XboxResult.Success : XboxResult.InvalidMessage;
+ }
+
+ // Start of the chunk sequence
+ if (Buffer == null || (header.Flags & XboxCommandFlags.ChunkStart) != 0)
+ {
+ // Safety check
+ if ((header.Flags & XboxCommandFlags.ChunkStart) == 0)
+ {
+ // Some devices trigger this condition during authentication,
+ // so we don't fail if it's an auth packet
+ Debug.Assert(header.CommandId == XboxAuthentication.CommandId,
+ "Invalid chunk sequence start! No chunk buffer exists, expected a chunk start packet");
+ return XboxResult.InvalidMessage;
+ }
+
+ // Buffer index is the total size of the buffer on the starting packet
+ Buffer = new byte[bufferIndex];
+ bufferIndex = 0;
+ BytesUsed = 0;
+ }
+
+ // Validate sequence alignment
+ if (bufferIndex != BytesUsed)
+ {
+ // We don't fail here since this seems to be a consistent issue on devices it affects
+ // Debug.Fail("Invalid chunk sequence ordering! Buffer index is not aligned with the previous chunk");
+ return XboxResult.InvalidMessage;
+ }
+
+ // Buffer index equalling buffer length signals the end of the sequence
+ if (bufferIndex >= Buffer.Length)
+ {
+ // Safety checks
+ if (bufferIndex > Buffer.Length)
+ {
+ Debug.Fail("Invalid chunk sequence end! Buffer index is beyond the end of the chunk buffer");
+ return XboxResult.InvalidMessage;
+ }
+
+ if (chunkData.Length != 0)
+ {
+ Debug.Fail("Invalid chunk sequence end! Data was provided beyond the end of the buffer");
+ return XboxResult.InvalidMessage;
+ }
+
+ // Send off finished chunk buffer
+ chunkData = Buffer;
+ Buffer = null;
+ BytesUsed = 0;
+
+ // Update header
+ header.DataLength = chunkData.Length;
+ header.Flags &= ~(XboxCommandFlags.ChunkPacket | XboxCommandFlags.ChunkStart);
+ return XboxResult.Success;
+ }
+
+ // Verify chunk data bounds
+ if ((bufferIndex + chunkData.Length) > Buffer.Length)
+ {
+ Debug.Fail($"Invalid chunk sequence! Data was provided beyond the end of the buffer");
+ return XboxResult.InvalidMessage;
+ }
+
+ // Copy data to buffer
+ chunkData.CopyTo(Buffer.AsSpan(bufferIndex, chunkData.Length));
+ BytesUsed = bufferIndex + chunkData.Length;
+ return XboxResult.Pending;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/XboxCommandHeader.cs b/RB4InstrumentMapper.Core/Parsing/Packets/XboxCommandHeader.cs
new file mode 100644
index 0000000..60eee45
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/XboxCommandHeader.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+// TODO: Chunk headers need a minimum length of 6 bytes
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Command flag definitions.
+ ///
+ [Flags]
+ internal enum XboxCommandFlags : byte
+ {
+ None = 0,
+ NeedsAcknowledgement = 0x10,
+ SystemCommand = 0x20,
+ ChunkStart = 0x40,
+ ChunkPacket = 0x80
+ }
+
+ ///
+ /// Header data for a message.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct XboxCommandHeader
+ {
+ public const int MinimumByteLength = 4;
+
+ public const byte PrimaryClient = 0;
+
+ public byte CommandId;
+ public byte Flags_Client;
+ public byte SequenceCount;
+ public int DataLength;
+ public int ChunkIndex;
+
+ public XboxCommandFlags Flags
+ {
+ get => (XboxCommandFlags)(Flags_Client & 0xF0);
+ set => Flags_Client = (byte)((byte)value & 0xF0 | Client);
+ }
+
+ public byte Client
+ {
+ get => (byte)(Flags_Client & 0x0F);
+ set => Flags_Client = (byte)((byte)Flags | value & 0x0F);
+ }
+
+ public static bool TryParse(ReadOnlySpan data, out XboxCommandHeader header, out int bytesRead)
+ {
+ if (data.Length < MinimumByteLength)
+ {
+ // GameInput backend only logs the command ID for the header
+ if (data.Length == 1)
+ {
+ header = new XboxCommandHeader()
+ {
+ CommandId = data[0],
+ Client = PrimaryClient,
+ };
+ bytesRead = 1;
+ return true;
+ }
+
+ header = default;
+ bytesRead = 0;
+ return false;
+ }
+
+ // Command info
+ header = new XboxCommandHeader()
+ {
+ CommandId = data[0],
+ Flags_Client = data[1],
+ SequenceCount = data[2],
+ };
+ bytesRead = MinimumByteLength - 1;
+
+ // Message length
+ if (!DecodeLEB128(data.Slice(bytesRead), out int dataLength, out int byteLength))
+ {
+ return false;
+ }
+ header.DataLength = dataLength;
+ bytesRead += byteLength;
+
+ // Chunk index/length
+ if ((header.Flags & XboxCommandFlags.ChunkPacket) != 0)
+ {
+ if (!DecodeLEB128(data.Slice(bytesRead), out int chunkIndex, out byteLength))
+ {
+ return false;
+ }
+
+ header.ChunkIndex = chunkIndex;
+ bytesRead += byteLength;
+ }
+
+ return true;
+ }
+
+ public bool TryWriteToBuffer(Span buffer, out int bytesWritten)
+ {
+ bytesWritten = 0;
+ if (buffer.Length < GetByteLength())
+ return false;
+
+ // Command info
+ buffer[0] = CommandId;
+ buffer[1] = Flags_Client;
+ buffer[2] = SequenceCount;
+ bytesWritten += MinimumByteLength - 1;
+
+ // Message length
+ if (!EncodeLEB128(buffer.Slice(bytesWritten), DataLength, out int byteLength))
+ return false;
+
+ bytesWritten += byteLength;
+
+ // Chunk index/length
+ if ((Flags & XboxCommandFlags.ChunkPacket) != 0)
+ {
+ if (!EncodeLEB128(buffer.Slice(bytesWritten), ChunkIndex, out byteLength))
+ return false;
+
+ bytesWritten += byteLength;
+ }
+
+ return true;
+ }
+
+ public int GetByteLength()
+ {
+ int size = MinimumByteLength - 1;
+
+ // Data length
+ Span encodeBuffer = stackalloc byte[sizeof(int)];
+ bool success = EncodeLEB128(encodeBuffer, DataLength, out int length);
+ Debug.Assert(success, "Failed to get byte length for data length!");
+ size += length;
+
+ // Chunk index
+ if ((Flags & XboxCommandFlags.ChunkPacket) != 0)
+ {
+ success = EncodeLEB128(encodeBuffer, ChunkIndex, out length);
+ Debug.Assert(success, "Failed to get byte length for chunk index!");
+ size += length;
+ }
+
+ return size;
+ }
+
+ // https://en.wikipedia.org/wiki/LEB128
+ private static bool DecodeLEB128(ReadOnlySpan data, out int result, out int byteLength)
+ {
+ byteLength = 0;
+ result = 0;
+
+ if (data.IsEmpty)
+ {
+ return false;
+ }
+
+ // Decode variable-length length value
+ // Sequence length is limited to 4 bytes
+ byte value;
+ do
+ {
+ value = data[byteLength];
+ result |= (value & 0x7F) << (byteLength * 7);
+ byteLength++;
+ }
+ while ((value & 0x80) != 0 && byteLength < sizeof(int));
+
+ // Detect length sequences longer than 4 bytes
+ if ((value & 0x80) != 0)
+ {
+ Debug.Fail($"Variable-length value is greater than 4 bytes! Buffer: {ParsingUtils.ToHexString(data)}");
+ byteLength = 0;
+ result = 0;
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool EncodeLEB128(Span buffer, int value, out int byteLength)
+ {
+ byteLength = 0;
+ if (buffer.IsEmpty)
+ return false;
+
+ // Encode the given value
+ // Sequence length is limited to 4 bytes
+ byte result;
+ do
+ {
+ result = (byte)(value & 0x7F);
+ if (value > 0x7F)
+ {
+ result |= 0x80;
+ value >>= 7;
+ }
+
+ buffer[byteLength] = result;
+ byteLength++;
+ }
+ while (value > 0x7F && byteLength < sizeof(int));
+
+ // Detect values too large to encode
+ if (value > 0x7F)
+ {
+ Debug.Fail($"Value to encode ({value}) is greater than allowed!");
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/Packets/XboxMessage.cs b/RB4InstrumentMapper.Core/Parsing/Packets/XboxMessage.cs
new file mode 100644
index 0000000..ef01ed0
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/Packets/XboxMessage.cs
@@ -0,0 +1,45 @@
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ internal class XboxMessage
+ {
+ private XboxCommandHeader _header;
+ private byte[] _bytes;
+
+ public XboxCommandHeader Header
+ {
+ get => _header;
+ set
+ {
+ _header = value;
+ _header.DataLength = _bytes?.Length ?? 0;
+ }
+ }
+
+ public byte[] Bytes
+ {
+ get => _bytes;
+ set
+ {
+ _bytes = value;
+ _header.DataLength = _bytes?.Length ?? 0;
+ }
+ }
+ }
+
+ internal unsafe class XboxMessage : XboxMessage
+ where TData : unmanaged
+ {
+ public TData Data
+ {
+ get => MemoryMarshal.Read(Bytes);
+ set => MemoryMarshal.Write(Bytes, ref value);
+ }
+
+ public XboxMessage()
+ {
+ Bytes = new byte[sizeof(TData)];
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/ParsingUtils.cs b/RB4InstrumentMapper.Core/Parsing/ParsingUtils.cs
new file mode 100644
index 0000000..fe82a86
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/ParsingUtils.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// Helper functions for parsing.
+ ///
+ internal static class ParsingUtils
+ {
+ public static string ToHexString(ReadOnlySpan buffer)
+ {
+ const string characters = "0123456789ABCDEF";
+
+ if (buffer.IsEmpty)
+ return "";
+
+ Span stringBuffer = stackalloc char[buffer.Length * 3];
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ byte value = buffer[i];
+ int stringIndex = i * 3;
+ stringBuffer[stringIndex] = characters[(value & 0xF0) >> 4];
+ stringBuffer[stringIndex + 1] = characters[value & 0x0F];
+ stringBuffer[stringIndex + 2] = '-';
+ }
+ // Exclude last '-'
+ stringBuffer = stringBuffer.Slice(0, stringBuffer.Length - 1);
+
+ return stringBuffer.ToString();
+ }
+
+ public static bool TryParseBytesFromHexString(ReadOnlySpan input, out byte[] bytes)
+ {
+ bytes = null;
+ if (input.IsEmpty)
+ return false;
+
+ // Determine number of bytes based on character count
+ input = input.Trim(); // All whitespace must be removed for count to be correct
+ int charCount = input.Length + 1; // + 1 to account for removed '-'
+ int byteCount = Math.DivRem(charCount, 3, out int remainder);
+ if (remainder != 0)
+ return false;
+
+ bytes = new byte[byteCount];
+ for (int i = 0; i < byteCount; i++)
+ {
+ int inputIndex = i * 3;
+ if (!HexCharToNumber(input[inputIndex], out byte upper) ||
+ !HexCharToNumber(input[inputIndex + 1], out byte lower))
+ return false;
+
+ bytes[i] = (byte)((upper << 4) | (lower & 0x0F));
+
+ // Verify that '-' is present
+ int dashIndex = inputIndex + 2;
+ if (dashIndex < input.Length)
+ {
+ char dashChar = input[dashIndex];
+ if (dashChar != '-' && dashChar != ' ')
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool HexCharToNumber(char c, out byte b)
+ {
+ uint value = (uint)c - '0';
+ if (value > 9)
+ {
+ const uint AsciiLowercaseFlag = 0x20;
+ value = (c | AsciiLowercaseFlag) - 'a' + 0x0A;
+ if (value > 0x0F)
+ {
+ b = 0;
+ return false;
+ }
+ }
+
+ b = (byte)value;
+ return true;
+ }
+
+ // Re-implementation of MemoryMarshal.TryRead without the references check,
+ // since we have the unmanaged constraint
+ public static bool TryRead(ReadOnlySpan source, out T value)
+ where T : unmanaged
+ {
+ if (source.Length < Unsafe.SizeOf())
+ {
+ Unsafe.SkipInit(out value);
+ return false;
+ }
+
+ value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(source));
+ return true;
+ }
+
+ ///
+ /// Scales a byte to a short, starting from the negative end.
+ ///
+ public static short ScaleToInt16(this byte input)
+ {
+ return (short)(((input ^ 0x80) << 8) | input);
+ }
+
+ ///
+ /// Scales a byte to a short, starting from the negative end.
+ ///
+ public static short ScaleToInt16_Positive(this byte input)
+ {
+ return (short)((input << 7) | (input >> 1));
+ }
+
+ ///
+ /// Scales a byte to an unsigned short.
+ ///
+ public static ushort ScaleToUInt16(this byte input)
+ {
+ return (ushort)((input << 8) | input);
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Core/Parsing/XboxClient.cs b/RB4InstrumentMapper.Core/Parsing/XboxClient.cs
new file mode 100644
index 0000000..e2d6034
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/XboxClient.cs
@@ -0,0 +1,333 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using RB4InstrumentMapper.Core.Mapping;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ ///
+ /// A logical client on an Xbox device.
+ ///
+ internal class XboxClient : IDisposable, IBackendClient
+ {
+ ///
+ /// The parent device of the client.
+ ///
+ public XboxDevice Parent { get; }
+
+ ///
+ /// The arrival info of the client.
+ ///
+ public XboxArrival Arrival { get; private set; }
+
+ ///
+ /// The descriptor of the client.
+ ///
+ public XboxDescriptor Descriptor { get; private set; }
+
+ ///
+ /// The ID of the client.
+ ///
+ public byte ClientId { get; }
+
+ ushort IBackendClient.VendorId => Arrival.VendorId;
+ ushort IBackendClient.ProductId => Arrival.ProductId;
+
+ bool IBackendClient.MapGuideButton => Parent.MapGuideButton;
+
+ private DeviceMapper deviceMapper;
+
+ private int descriptorFailCount = 0;
+
+ private readonly Dictionary previousReceiveSequence = new Dictionary();
+ private readonly Dictionary previousSendSequence = new Dictionary();
+ private readonly Dictionary chunkBuffers = new Dictionary()
+ {
+ { XboxDescriptor.CommandId, new XboxChunkBuffer() },
+ };
+
+ public XboxClient(XboxDevice parent, byte clientId)
+ {
+ Parent = parent;
+ ClientId = clientId;
+ }
+
+ ~XboxClient()
+ {
+ Dispose(false);
+ }
+
+ ///
+ /// Parses command data from a packet.
+ ///
+ internal unsafe XboxResult HandleMessage(XboxCommandHeader header, ReadOnlySpan commandData)
+ {
+ // Verify packet length
+ if (header.DataLength != commandData.Length)
+ {
+ Debug.Fail($"Command header length does not match buffer length! Header: {header.DataLength} Buffer: {commandData.Length}");
+ return XboxResult.InvalidMessage;
+ }
+
+ // Ensure acknowledgement happens regardless of pending/failure
+ try
+ {
+ // Chunked packets
+ if ((header.Flags & XboxCommandFlags.ChunkPacket) != 0)
+ {
+ if (!chunkBuffers.TryGetValue(header.CommandId, out var chunkBuffer))
+ {
+ chunkBuffer = new XboxChunkBuffer();
+ chunkBuffers.Add(header.CommandId, chunkBuffer);
+ }
+
+ var chunkResult = chunkBuffer.ProcessChunk(ref header, ref commandData);
+ switch (chunkResult)
+ {
+ case XboxResult.Success:
+ break;
+ case XboxResult.Pending: // Chunk is unfinished
+ return chunkResult;
+ default: // Error handling the chunk
+ // Hack for descriptor read errors
+ if (header.CommandId == XboxDescriptor.CommandId)
+ {
+ descriptorFailCount++;
+ if (descriptorFailCount >= 3)
+ {
+ // Disconnect device after too many failed descriptors
+ return XboxResult.UnsupportedDevice;
+ }
+
+ var resendResult = SendMessage(XboxDescriptor.GetDescriptor);
+ if (resendResult != XboxResult.Success)
+ return resendResult;
+ }
+ return chunkResult;
+ }
+ }
+ }
+ finally
+ {
+ // Acknowledgement
+ if ((header.Flags & XboxCommandFlags.NeedsAcknowledgement) != 0)
+ {
+ SendAcknowledge(ref header, commandData);
+ }
+ }
+
+ // Don't handle the same packet twice
+ if (!previousReceiveSequence.TryGetValue(header.CommandId, out byte previousSequence))
+ previousSequence = 0;
+
+ if (header.SequenceCount == previousSequence)
+ return XboxResult.Success;
+ previousReceiveSequence[header.CommandId] = header.SequenceCount;
+
+ // Handle packet
+ return (header.Flags & XboxCommandFlags.SystemCommand) != 0
+ ? HandleSystemCommand(header.CommandId, commandData)
+ : HandleMapperCommand(header.CommandId, commandData);
+ }
+
+ private XboxResult HandleSystemCommand(byte commandId, ReadOnlySpan commandData)
+ {
+ switch (commandId)
+ {
+ case XboxArrival.CommandId:
+ return HandleArrival(commandData);
+
+ case XboxStatus.CommandId:
+ return HandleStatus(commandData);
+
+ case XboxDescriptor.CommandId:
+ return HandleDescriptor(commandData);
+
+ case XboxKeystroke.CommandId:
+ return HandleKeystroke(commandData);
+ }
+
+ return XboxResult.Success;
+ }
+
+ private XboxResult HandleMapperCommand(byte commandId, ReadOnlySpan commandData)
+ {
+ // Skip if inputs are disabled
+ if (!Parent.InputsEnabled)
+ return XboxResult.Success;
+
+ if (deviceMapper == null)
+ {
+ deviceMapper = MapperFactory.GetFallbackMapper(this);
+ if (deviceMapper == null)
+ {
+ // No more devices available, do nothing
+ return XboxResult.Success;
+ }
+
+ Logging.WriteLine("Warning: This device was not encountered during its initial connection! It will use the fallback mapper instead of one specific to its device interface.");
+ Logging.WriteLine("Reconnect it (or hit Start before connecting it) to ensure correct behavior.");
+ }
+
+ return deviceMapper.HandleMessage(commandId, commandData);
+ }
+
+ ///
+ /// Handles the arrival message of the device.
+ ///
+ private unsafe XboxResult HandleArrival(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxArrival arrival))
+ return XboxResult.InvalidMessage;
+
+ Logging.WriteLineVerbose($"New client connected with ID {arrival.SerialNumber:X12}");
+ Arrival = arrival;
+
+ // Kick off descriptor request
+ return SendMessage(XboxDescriptor.GetDescriptor);
+ }
+
+ ///
+ /// Handles the arrival message of the device.
+ ///
+ private unsafe XboxResult HandleStatus(ReadOnlySpan data)
+ {
+ if (!ParsingUtils.TryRead(data, out XboxStatus status))
+ return XboxResult.InvalidMessage;
+
+ if (!status.Connected)
+ return XboxResult.Disconnected;
+
+ return XboxResult.Success;
+ }
+
+ ///
+ /// Handles the Xbox One descriptor of the device.
+ ///
+ private XboxResult HandleDescriptor(ReadOnlySpan data)
+ {
+ if (!XboxDescriptor.Parse(data, out var descriptor))
+ return XboxResult.InvalidMessage;
+
+ Descriptor = descriptor;
+
+ if (Parent.InputsEnabled)
+ {
+ deviceMapper?.Dispose();
+ deviceMapper = MapperFactory.GetByInterfaceIds(this, Descriptor.InterfaceGuids);
+ }
+
+ // Send final set of initialization messages
+ Debug.Assert(Descriptor.OutputCommands.Contains(XboxConfiguration.CommandId));
+ var result = SendMessage(XboxConfiguration.PowerOnDevice);
+ if (result != XboxResult.Success)
+ return result;
+
+ if (Descriptor.OutputCommands.Contains(XboxLedControl.CommandId))
+ {
+ result = SendMessage(XboxLedControl.EnableLed);
+ if (result != XboxResult.Success)
+ return result;
+ }
+
+ if (Descriptor.OutputCommands.Contains(XboxAuthentication.CommandId))
+ {
+ // Authentication is not and will not be implemented, we just automatically pass all devices
+ result = SendMessage(XboxAuthentication.SuccessMessage);
+ if (result != XboxResult.Success)
+ return result;
+ }
+
+ return XboxResult.Success;
+ }
+
+ private unsafe XboxResult HandleKeystroke(ReadOnlySpan data)
+ {
+ if (data.Length % sizeof(XboxKeystroke) != 0)
+ return XboxResult.InvalidMessage;
+
+ // Multiple keystrokes can be sent in a single message
+ var keys = MemoryMarshal.Cast(data);
+ foreach (var key in keys)
+ {
+ deviceMapper?.HandleKeystroke(key);
+ }
+
+ return XboxResult.Success;
+ }
+
+ public unsafe XboxResult SendMessage(XboxMessage message)
+ {
+ return SendMessage(message.Header, message.Bytes);
+ }
+
+ public unsafe XboxResult SendMessage(XboxCommandHeader header)
+ {
+ SetUpHeader(ref header);
+ return Parent.SendMessage(header);
+ }
+
+ public unsafe XboxResult SendMessage(XboxCommandHeader header, ref T data)
+ where T : unmanaged
+ {
+ SetUpHeader(ref header);
+ return Parent.SendMessage(header, ref data);
+ }
+
+ public XboxResult SendMessage(XboxCommandHeader header, Span data)
+ {
+ SetUpHeader(ref header);
+ return Parent.SendMessage(header, data);
+ }
+
+ private XboxResult SendAcknowledge(ref XboxCommandHeader header, ReadOnlySpan commandData)
+ {
+ var (sendHeader, acknowledge) = chunkBuffers.TryGetValue(header.CommandId, out var chunkBuffer)
+ ? XboxAcknowledgement.FromMessage(header, commandData, chunkBuffer)
+ : XboxAcknowledgement.FromMessage(header, commandData);
+
+ // Don't go through SetUpHeader, we must preserve the sequence ID in the header
+ header.Client = ClientId;
+ header.Flags &= ~XboxCommandFlags.NeedsAcknowledgement;
+
+ return Parent.SendMessage(sendHeader, ref acknowledge);
+ }
+
+ private void SetUpHeader(ref XboxCommandHeader header)
+ {
+ header.Client = ClientId;
+
+ if (!previousSendSequence.TryGetValue(header.CommandId, out byte sequence) ||
+ sequence == 0xFF) // Sequence IDs of 0 are not valid
+ sequence = 0;
+
+ header.SequenceCount = ++sequence;
+ previousSendSequence[header.CommandId] = sequence;
+ }
+
+ public void EnableInputs(bool enabled)
+ {
+ deviceMapper?.Dispose();
+ deviceMapper = null;
+
+ if (enabled && Descriptor != null)
+ deviceMapper = MapperFactory.GetByInterfaceIds(this, Descriptor.InterfaceGuids);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ deviceMapper?.Dispose();
+ deviceMapper = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Parsing/XboxDevice.cs b/RB4InstrumentMapper.Core/Parsing/XboxDevice.cs
new file mode 100644
index 0000000..a26c364
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Parsing/XboxDevice.cs
@@ -0,0 +1,238 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace RB4InstrumentMapper.Core.Parsing
+{
+ internal enum BackendType
+ {
+ Pcap,
+ Usb,
+ Replay,
+ }
+
+ internal enum XboxResult
+ {
+ /// The packet was processed successfully.
+ Success,
+ /// More packet data is incoming and needs to be received.
+ Pending,
+ /// The device was disconnected.
+ Disconnected,
+ /// The packet contains an invalid message.
+ InvalidMessage,
+ /// The device being connected is not supported.
+ UnsupportedDevice,
+ }
+
+ ///
+ /// An Xbox device.
+ ///
+ internal class XboxDevice : IDisposable
+ {
+ ///
+ /// The clients currently on the device.
+ ///
+ private readonly Dictionary clients = new Dictionary();
+
+ private readonly int maxPacketSize;
+ private bool inputsEnabled = false;
+
+ public BackendType Backend { get; }
+ public bool MapGuideButton { get; }
+ public bool InputsEnabled => inputsEnabled;
+
+ public XboxDevice(BackendType backend) : this(backend, mapGuide: false, 0)
+ {
+ }
+
+ protected XboxDevice(BackendType backend, bool mapGuide, int maxPacket)
+ {
+ Backend = backend;
+ MapGuideButton = mapGuide;
+ maxPacketSize = maxPacket;
+ }
+
+ ~XboxDevice()
+ {
+ Dispose(false);
+ }
+
+ ///
+ /// Handles an incoming packet for this device.
+ ///
+ public unsafe XboxResult HandleRawPacket(ReadOnlySpan data)
+ {
+ // Some devices may send multiple messages in a single packet, placing them back-to-back
+ // The header length is very important in these scenarios, as it determines which bytes are part of the message
+ // and where the next message's header begins.
+ while (!data.IsEmpty)
+ {
+ // Command header
+ if (!XboxCommandHeader.TryParse(data, out var header, out int headerLength))
+ {
+ return XboxResult.InvalidMessage;
+ }
+ int messageLength = headerLength + header.DataLength;
+
+ // Verify bounds
+ if (data.Length < messageLength)
+ {
+ return XboxResult.InvalidMessage;
+ }
+
+ var headerData = data.Slice(0, headerLength);
+ var commandData = data.Slice(headerLength, header.DataLength);
+
+ // Debugging (if enabled)
+ PacketLogging.WritePacket(headerData, commandData, PacketDirection.In);
+
+ var result = HandlePacket(header, commandData);
+ if (result != XboxResult.Success)
+ return result;
+
+ // Progress to next message
+ data = data.Slice(messageLength);
+ }
+
+ return XboxResult.Success;
+ }
+
+ ///
+ /// Handles an incoming packet for this device.
+ ///
+ public unsafe XboxResult HandlePacket(XboxCommandHeader header, ReadOnlySpan commandData)
+ {
+ // No logging, no way to see the source data from here
+ // PacketLogging.WritePacket(headerData, commandData, PacketDirection.In);
+
+ if (!clients.TryGetValue(header.Client, out var client))
+ {
+ client = new XboxClient(this, header.Client);
+ clients.Add(header.Client, client);
+ }
+
+ var clientResult = client.HandleMessage(header, commandData);
+ switch (clientResult)
+ {
+ case XboxResult.Success:
+ case XboxResult.Pending:
+ break;
+ case XboxResult.UnsupportedDevice:
+ client.Dispose();
+ clients.Remove(header.Client);
+ if (header.Client == 0)
+ return clientResult;
+ break;
+ case XboxResult.Disconnected:
+ client.Dispose();
+ clients.Remove(header.Client);
+ Logging.WriteLineVerbose($"Client {client.Arrival.SerialNumber:X12} disconnected");
+ break;
+ default:
+ Logging.WriteLineVerbose($"Error handling message: {clientResult}");
+ break;
+ }
+
+ return XboxResult.Success;
+ }
+
+ internal unsafe XboxResult SendMessage(XboxMessage message)
+ {
+ return SendMessage(message.Header, message.Bytes);
+ }
+
+ internal unsafe XboxResult SendMessage(XboxCommandHeader header)
+ {
+ return SendMessage(header, Span.Empty);
+ }
+
+ internal unsafe XboxResult SendMessage(XboxCommandHeader header, ref T data)
+ where T : unmanaged
+ {
+ // Create a byte buffer for the given data
+ var writeBuffer = new Span(Unsafe.AsPointer(ref data), sizeof(T));
+ return SendMessage(header, writeBuffer);
+ }
+
+ // TODO: Span instead of ReadOnlySpan since the WinUSB lib doesn't use ReadOnlySpan for writing atm
+ internal XboxResult SendMessage(XboxCommandHeader header, Span data)
+ {
+ // For devices handled by Pcap and not over USB
+ if (maxPacketSize < XboxCommandHeader.MinimumByteLength)
+ return XboxResult.Success;
+
+ // Initialize lengths
+ header.DataLength = data.Length;
+ header.ChunkIndex = 0;
+ int packetLength = header.GetByteLength() + data.Length;
+
+ // Chunked messages
+ if (packetLength > maxPacketSize)
+ {
+ // Sending chunked messages isn't supported currently, as we never need to send one
+ Debug.Fail($"Message is too long! Max packet length: {maxPacketSize}, message size: {packetLength}");
+ return XboxResult.InvalidMessage;
+ }
+
+ // Create buffer and send it
+ Span packetBuffer = stackalloc byte[packetLength];
+ if (!header.TryWriteToBuffer(packetBuffer, out int bytesWritten) ||
+ !data.TryCopyTo(packetBuffer.Slice(bytesWritten)))
+ {
+ Debug.Fail("Failed to create packet buffer!");
+ return XboxResult.InvalidMessage;
+ }
+
+ PacketLogging.WritePacket(
+ packetBuffer.Slice(0, bytesWritten),
+ data,
+ PacketDirection.Out
+ );
+ return SendPacket(packetBuffer);
+ }
+
+ protected virtual XboxResult SendPacket(Span data)
+ {
+ // No-op by default, for Pcap
+ return XboxResult.Success;
+ }
+
+ public virtual void EnableInputs(bool enabled)
+ {
+ inputsEnabled = enabled;
+ foreach (var client in clients.Values)
+ {
+ client?.EnableInputs(enabled);
+ }
+ }
+
+ ///
+ /// Performs cleanup for the device.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ ReleaseManagedResources();
+ }
+ }
+
+ protected virtual void ReleaseManagedResources()
+ {
+ foreach (var client in clients.Values)
+ {
+ client.Dispose();
+ }
+
+ clients.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/RB4InstrumentMapper.Core.csproj b/RB4InstrumentMapper.Core/RB4InstrumentMapper.Core.csproj
new file mode 100644
index 0000000..e63c33e
--- /dev/null
+++ b/RB4InstrumentMapper.Core/RB4InstrumentMapper.Core.csproj
@@ -0,0 +1,55 @@
+
+
+
+ net472
+ 9
+ x64
+ x64
+
+ true
+
+
+
+ $(DefineConstants);ENABLE_GAMEPAD_MAPPING
+
+
+
+
+
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ Dependencies\x64\vJoyInterfaceWrap.dll
+
+
+ %(Filename)%(Extension)
+ PreserveNewest
+
+
+
+
+ False
+ Dependencies\Nefarius.ViGEm.Client.dll
+
+
+
+
+ False
+ Dependencies\SharpGameInput.dll
+
+
+
+
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Core/Utility/SleepTimer.cs b/RB4InstrumentMapper.Core/Utility/SleepTimer.cs
new file mode 100644
index 0000000..8e09dd8
--- /dev/null
+++ b/RB4InstrumentMapper.Core/Utility/SleepTimer.cs
@@ -0,0 +1,72 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Security;
+using Windows.Win32.Storage.FileSystem;
+using Windows.Win32.System.Threading;
+
+namespace RB4InstrumentMapper.Core.Utility
+{
+ using static PInvoke;
+
+ public unsafe class SleepTimer : IDisposable
+ {
+ private SafeFileHandle _timerHandle;
+
+ public SleepTimer()
+ {
+ _timerHandle = CreateWaitableTimerEx(
+ (SECURITY_ATTRIBUTES?)null,
+ null,
+ CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
+ (uint)FILE_ACCESS_RIGHTS.DELETE |
+ (uint)FILE_ACCESS_RIGHTS.SYNCHRONIZE |
+ (uint)SYNCHRONIZATION_ACCESS_RIGHTS.TIMER_MODIFY_STATE
+ );
+ if (_timerHandle == null || _timerHandle.IsInvalid)
+ {
+ int error = Marshal.GetLastWin32Error();
+ throw new Win32Exception(error, "Failed to create thread sleep timer");
+ }
+ }
+
+ public void Dispose()
+ {
+ _timerHandle?.Dispose();
+ _timerHandle = null;
+ }
+
+ public void Sleep(double seconds)
+ {
+ if (_timerHandle == null || _timerHandle.IsInvalid)
+ {
+ throw new ObjectDisposedException(nameof(_timerHandle));
+ }
+
+ if (seconds < 0)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ // This helps the actual wait time to be a little more accurate in testing
+ seconds *= 0.95;
+
+ long centiNanoSeconds = -(long)(seconds * 1000 * 1000 * 10);
+ if (!SetWaitableTimer(_timerHandle, centiNanoSeconds, 0, null, null, false))
+ {
+ int error = Marshal.GetLastWin32Error();
+ throw new Win32Exception(error);
+ }
+
+ var result = WaitForSingleObject(_timerHandle, INFINITE);
+ if (result != WAIT_EVENT.WAIT_OBJECT_0)
+ {
+ int error = Marshal.GetLastWin32Error();
+ throw new Win32Exception(error);
+ }
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.GUI/App.config b/RB4InstrumentMapper.GUI/App.config
new file mode 100644
index 0000000..58c1564
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/App.config
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -1
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ 60
+
+
+
+
\ No newline at end of file
diff --git a/App.xaml b/RB4InstrumentMapper.GUI/App.xaml
similarity index 57%
rename from App.xaml
rename to RB4InstrumentMapper.GUI/App.xaml
index df9ec07..8a0bbe1 100644
--- a/App.xaml
+++ b/RB4InstrumentMapper.GUI/App.xaml
@@ -1,8 +1,8 @@
-
+ xmlns:local="clr-namespace:RB4InstrumentMapper.GUI"
+ StartupUri="Windows/MainWindow.xaml">
diff --git a/RB4InstrumentMapper.GUI/App.xaml.cs b/RB4InstrumentMapper.GUI/App.xaml.cs
new file mode 100644
index 0000000..591e3e8
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/App.xaml.cs
@@ -0,0 +1,11 @@
+using System.Windows;
+
+namespace RB4InstrumentMapper.GUI
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/RB4InstrumentMapper.GUI/Program.cs b/RB4InstrumentMapper.GUI/Program.cs
new file mode 100644
index 0000000..df11390
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Program.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Diagnostics;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using RB4InstrumentMapper.Core;
+using RB4InstrumentMapper.Core.Parsing;
+using RB4InstrumentMapper.GUI.Properties;
+
+namespace RB4InstrumentMapper.GUI
+{
+ public class Program
+ {
+ private const string AutoStartOption = "--autostart";
+ private const string WinUsbOption = "--winusb";
+ private const string RevertOption = "--revert";
+ private const string ReplayOption = "--replay";
+
+ public static bool AutoStart { get; private set; } = false;
+
+ [STAThread]
+ public static int Main(string[] args)
+ {
+ AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
+
+ // Regular app startup
+ if (args.Length == 0)
+ {
+ App.Main();
+ return 0;
+ }
+
+ int exitCode;
+ switch (args[0])
+ {
+ case AutoStartOption:
+ {
+ AutoStart = true;
+ App.Main();
+ return 0;
+ }
+ case WinUsbOption:
+ {
+ if (args.Length < 2)
+ {
+ Console.WriteLine("Missing device instance ID argument");
+ return -1;
+ }
+
+ exitCode = WinUsbBackend.SwitchDeviceToWinUSB(args[1]) ? 0 : -1;
+ break;
+ }
+ case RevertOption:
+ {
+ if (args.Length < 2)
+ {
+ Console.WriteLine("Missing device instance ID argument");
+ return -1;
+ }
+
+ exitCode = WinUsbBackend.RevertDevice(args[1]) ? 0 : -1;
+ break;
+ }
+ case ReplayOption:
+ {
+ if (args.Length < 2)
+ {
+ Console.WriteLine("Missing log path argument");
+ return -1;
+ }
+
+ exitCode = ReplayBackend.ReplayLog(args[1]) ? 0 : -1;
+ break;
+ }
+ default:
+ {
+ Console.WriteLine($"Invalid option '{args[0]}'");
+ exitCode = -1;
+ break;
+ }
+ }
+
+ Logging.CloseAll();
+ return exitCode;
+ }
+
+ public static Task StartWinUsbProcess(string instanceId)
+ {
+ string args = $"{WinUsbOption} {instanceId}";
+ return RunElevated(args);
+ }
+
+ public static Task StartRevertProcess(string instanceId)
+ {
+ string args = $"{RevertOption} {instanceId}";
+ return RunElevated(args);
+ }
+
+ private static async Task RunElevated(string args)
+ {
+ string location = Assembly.GetEntryAssembly().Location;
+ var processInfo = new ProcessStartInfo()
+ {
+ Verb = "runas", // Run as admin
+ FileName = location,
+ Arguments = args,
+ CreateNoWindow = true,
+ };
+
+ try
+ {
+ var process = Process.Start(processInfo);
+ await Task.Run(process.WaitForExit);
+ return process.ExitCode == 0;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Failed to create elevated process!");
+ Debug.WriteLine(ex);
+ return false;
+ }
+ }
+
+ ///
+ /// Logs unhandled exceptions to a file and prompts the user with the exception message.
+ ///
+ private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
+ {
+ // Log message; built all at once to ensure no other messages cut into it.
+ var message = new StringBuilder();
+ message.AppendLine("-------------------");
+ message.AppendLine("UNHANDLED EXCEPTION");
+ message.AppendLine("-------------------");
+ message.AppendLine(args.ExceptionObject?.ToString() ?? "(null error)");
+ // Note for the check below: this will implicitly create the log
+ Logging.Main_WriteLine(message.ToString());
+
+ // MessageBox message
+ message.Clear();
+ message.AppendLine("An unhandled error has occured:");
+ message.AppendLine();
+ if (args.ExceptionObject is Exception ex)
+ {
+ message.AppendLine(ex.GetFirstLine());
+ }
+ else
+ {
+ message.AppendLine(args.ExceptionObject?.ToString() ?? "(null error)");
+ }
+ message.AppendLine();
+
+ // Use an alternate message if log couldn't be created
+ if (Logging.MainLogExists)
+ {
+ // Complete the message buffer
+ message.AppendLine("A log of the error has been created, do you want to open it?");
+
+ // Display message
+ var result = MessageBox.Show(message.ToString(), "Unhandled Error", MessageBoxButton.YesNo, MessageBoxImage.Error);
+ // If user requested to, open the log
+ if (result == MessageBoxResult.Yes)
+ {
+ Process.Start("explorer.exe", $"/select,\"{Logging.MainLogPath}\"").Dispose();
+ }
+ }
+ else
+ {
+ // Complete the message buffer
+ message.AppendLine("An error log was unable to be created.");
+
+ // Display message
+ MessageBox.Show(message.ToString(), "Unhandled Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+
+ // Close the log files
+ Logging.CloseAll();
+ // Save settings
+ Settings.Default.Save();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Properties/Settings.Designer.cs b/RB4InstrumentMapper.GUI/Properties/Settings.Designer.cs
similarity index 51%
rename from Properties/Settings.Designer.cs
rename to RB4InstrumentMapper.GUI/Properties/Settings.Designer.cs
index 899fee2..3f85848 100644
--- a/Properties/Settings.Designer.cs
+++ b/RB4InstrumentMapper.GUI/Properties/Settings.Designer.cs
@@ -8,11 +8,11 @@
//
//------------------------------------------------------------------------------
-namespace RB4InstrumentMapper.Properties {
+namespace RB4InstrumentMapper.GUI.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.9.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@@ -25,97 +25,61 @@ public static Settings Default {
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("")]
- public string currentPcapSelection {
+ [global::System.Configuration.DefaultSettingValueAttribute("-1")]
+ public int controllerDeviceType {
get {
- return ((string)(this["currentPcapSelection"]));
+ return ((int)(this["controllerDeviceType"]));
}
set {
- this["currentPcapSelection"] = value;
+ this["controllerDeviceType"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("")]
- public string currentGuitar1Selection {
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool switchDriverDisclaimerShown {
get {
- return ((string)(this["currentGuitar1Selection"]));
+ return ((bool)(this["switchDriverDisclaimerShown"]));
}
set {
- this["currentGuitar1Selection"] = value;
+ this["switchDriverDisclaimerShown"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("")]
- public string currentDrumSelection {
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool autoStart {
get {
- return ((string)(this["currentDrumSelection"]));
+ return ((bool)(this["autoStart"]));
}
set {
- this["currentDrumSelection"] = value;
+ this["autoStart"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("")]
- public string currentPacketDebugState {
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool accurateDrumMaps {
get {
- return ((string)(this["currentPacketDebugState"]));
+ return ((bool)(this["accurateDrumMaps"]));
}
set {
- this["currentPacketDebugState"] = value;
+ this["accurateDrumMaps"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("")]
- public string currentGuitar2Selection {
+ [global::System.Configuration.DefaultSettingValueAttribute("60")]
+ public uint pollingFrequency {
get {
- return ((string)(this["currentGuitar2Selection"]));
+ return ((uint)(this["pollingFrequency"]));
}
set {
- this["currentGuitar2Selection"] = value;
- }
- }
-
- [global::System.Configuration.UserScopedSettingAttribute()]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("")]
- public string currentGuitar1Id {
- get {
- return ((string)(this["currentGuitar1Id"]));
- }
- set {
- this["currentGuitar1Id"] = value;
- }
- }
-
- [global::System.Configuration.UserScopedSettingAttribute()]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("")]
- public string currentGuitar2Id {
- get {
- return ((string)(this["currentGuitar2Id"]));
- }
- set {
- this["currentGuitar2Id"] = value;
- }
- }
-
- [global::System.Configuration.UserScopedSettingAttribute()]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("")]
- public string currentDrumId {
- get {
- return ((string)(this["currentDrumId"]));
- }
- set {
- this["currentDrumId"] = value;
+ this["pollingFrequency"] = value;
}
}
}
diff --git a/RB4InstrumentMapper.GUI/Properties/Settings.settings b/RB4InstrumentMapper.GUI/Properties/Settings.settings
new file mode 100644
index 0000000..3a13497
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Properties/Settings.settings
@@ -0,0 +1,21 @@
+
+
+
+
+
+ -1
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ 60
+
+
+
\ No newline at end of file
diff --git a/RB4InstrumentMapper.GUI/RB4InstrumentMapper.GUI.csproj b/RB4InstrumentMapper.GUI/RB4InstrumentMapper.GUI.csproj
new file mode 100644
index 0000000..a7c6c05
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/RB4InstrumentMapper.GUI.csproj
@@ -0,0 +1,46 @@
+
+
+
+ RB4InstrumentMapper
+ net472
+ WinExe
+ x64
+ x64
+
+ true
+ true
+ RB4InstrumentMapper.GUI.Program
+ true
+
+
+
+ Copyright © 2021
+ $(ResourcesDir)Icon.ico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ True
+ Settings.settings
+
+
+
+
\ No newline at end of file
diff --git a/RB4InstrumentMapper.GUI/Windows/Controls/XboxUsbDeviceControl.xaml b/RB4InstrumentMapper.GUI/Windows/Controls/XboxUsbDeviceControl.xaml
new file mode 100644
index 0000000..b043a54
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Windows/Controls/XboxUsbDeviceControl.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RB4InstrumentMapper.GUI/Windows/Controls/XboxUsbDeviceControl.xaml.cs b/RB4InstrumentMapper.GUI/Windows/Controls/XboxUsbDeviceControl.xaml.cs
new file mode 100644
index 0000000..ce3542a
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Windows/Controls/XboxUsbDeviceControl.xaml.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using Nefarius.Utilities.DeviceManagement.PnP;
+using RB4InstrumentMapper.Core;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.GUI
+{
+ ///
+ /// Interaction logic for XboxUsbDeviceControl.xaml
+ ///
+ public partial class XboxUsbDeviceControl : UserControl
+ {
+ public event Action DriverSwitchStart;
+ public event Action DriverSwitchEnd;
+
+ public string DevicePath { get; }
+ public UsbPnPDevice PnpDevice { get; }
+ public bool IsWinUsb { get; }
+
+ public XboxUsbDeviceControl(string devicePath, UsbPnPDevice device, bool winusb)
+ {
+ DevicePath = devicePath;
+ PnpDevice = device;
+ IsWinUsb = winusb;
+
+ InitializeComponent();
+
+ bool disallowSwitch = false;
+ try
+ {
+ const string vidString = "VID_";
+ const string pidString = "PID_";
+
+ foreach (string id in PnpDevice.HardwareIds)
+ {
+ int vidIndex = id.IndexOf(vidString);
+ int pidIndex = id.IndexOf(pidString);
+ if (vidIndex < 0 || pidIndex < 0)
+ continue;
+
+ string vid = id.Substring(vidIndex + vidString.Length, 4);
+ string pid = id.Substring(pidIndex + pidString.Length, 4);
+ ushort vendorId = ushort.Parse(vid, NumberStyles.HexNumber);
+ ushort productId = ushort.Parse(pid, NumberStyles.HexNumber);
+
+ disallowSwitch = vendorId == 0x0E6F && productId == 0x0248;
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.Main_WriteException(ex, $"Failed to check VID/PID for device '{devicePath}'!");
+ }
+
+ if (IsWinUsb)
+ {
+ switchDriverButton.Content = "Revert Driver";
+ xboxIconImage.Visibility = Visibility.Hidden;
+ usbIconImage.Visibility = Visibility.Visible;
+
+ try
+ {
+ var usbDevice = WinUsbBackend.GetUsbDevice(devicePath);
+ manufacturerLabel.Content = usbDevice.Descriptor.Manufacturer;
+ nameLabel.Content = usbDevice.Descriptor.Product;
+ }
+ catch (Exception ex)
+ {
+ Logging.Main_WriteException(ex, $"Failed to get USB name/manufacturer for device '{devicePath}'!");
+ manufacturerLabel.Content = "(Failed to get manufacturer)";
+ nameLabel.Content = "(Failed to get name)";
+ }
+
+ if (disallowSwitch)
+ {
+ switchDriverButton.Content = "Please Revert";
+ }
+ }
+ else
+ {
+ switchDriverButton.Content = "Switch Driver";
+ xboxIconImage.Visibility = Visibility.Visible;
+ usbIconImage.Visibility = Visibility.Hidden;
+
+ try
+ {
+ manufacturerLabel.Content = PnpDevice.GetProperty(DevicePropertyKey.Device_Manufacturer);
+ nameLabel.Content = PnpDevice.GetProperty(DevicePropertyKey.NAME);
+ }
+ catch (Exception ex)
+ {
+ Logging.Main_WriteException(ex, $"Failed to get name/manufacturer for device '{devicePath}'!");
+ manufacturerLabel.Content = "(Failed to get manufacturer)";
+ nameLabel.Content = "(Failed to get name)";
+ }
+
+ if (disallowSwitch)
+ {
+ switchDriverButton.Content = "Do Not Switch";
+ switchDriverButton.IsEnabled = false;
+ }
+ }
+ }
+
+ public void Enable()
+ {
+ switchDriverButton.IsEnabled = true;
+ }
+
+ public void Disable()
+ {
+ switchDriverButton.IsEnabled = false;
+ }
+
+ private async void switchDriverButton_Clicked(object sender, RoutedEventArgs args)
+ {
+ DriverSwitchStart?.Invoke(this);
+ switchDriverProgress.Visibility = Visibility.Visible;
+
+ if (IsWinUsb)
+ await RevertDriver();
+ else
+ await SwitchToWinUSB();
+
+ switchDriverProgress.Visibility = Visibility.Hidden;
+ DriverSwitchEnd?.Invoke(this);
+ }
+
+ private async Task SwitchToWinUSB()
+ {
+ // Attempt normally in case we already have admin permissions
+ if (await Task.Run(() => WinUsbBackend.SwitchDeviceToWinUSB(PnpDevice)))
+ return;
+
+ // Otherwise, do it in a separate admin process
+ if (!await Program.StartWinUsbProcess(PnpDevice.InstanceId))
+ MessageBox.Show("Failed to switch device to WinUSB!", "Failed To Switch Driver", MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+
+ private async Task RevertDriver()
+ {
+ // Attempt normally in case we already have admin permissions
+ if (await Task.Run(() => WinUsbBackend.RevertDevice(PnpDevice)))
+ return;
+
+ // Otherwise, do it in a separate admin process
+ if (!await Program.StartRevertProcess(PnpDevice.InstanceId))
+ {
+ var response = MessageBox.Show(
+ "Failed to revert device to its original driver!\n\nDo you want to open the manual uninstallation guide?",
+ "Failed To Switch Driver",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Error
+ );
+
+ if (response == MessageBoxResult.Yes)
+ {
+ Process.Start("https://github.com/TheNathannator/RB4InstrumentMapper/blob/main/Docs/WinUSB/manual-winusb-install.md#remove-winusb");
+ }
+ }
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.GUI/Windows/Icons/USB.png b/RB4InstrumentMapper.GUI/Windows/Icons/USB.png
new file mode 100644
index 0000000..2d77c40
Binary files /dev/null and b/RB4InstrumentMapper.GUI/Windows/Icons/USB.png differ
diff --git a/RB4InstrumentMapper.GUI/Windows/Icons/Xbox.png b/RB4InstrumentMapper.GUI/Windows/Icons/Xbox.png
new file mode 100644
index 0000000..5a6eff9
Binary files /dev/null and b/RB4InstrumentMapper.GUI/Windows/Icons/Xbox.png differ
diff --git a/RB4InstrumentMapper.GUI/Windows/MainWindow.xaml b/RB4InstrumentMapper.GUI/Windows/MainWindow.xaml
new file mode 100644
index 0000000..53c6dd8
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Windows/MainWindow.xaml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RB4InstrumentMapper.GUI/Windows/MainWindow.xaml.cs b/RB4InstrumentMapper.GUI/Windows/MainWindow.xaml.cs
new file mode 100644
index 0000000..57816c7
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Windows/MainWindow.xaml.cs
@@ -0,0 +1,464 @@
+using System;
+using System.Configuration;
+using System.IO;
+using System.Reflection;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Threading;
+using RB4InstrumentMapper.Core;
+using RB4InstrumentMapper.Core.Mapping;
+using RB4InstrumentMapper.Core.Parsing;
+using RB4InstrumentMapper.GUI.Properties;
+
+namespace RB4InstrumentMapper.GUI
+{
+ public partial class MainWindow : Window
+ {
+ ///
+ /// Dispatcher to send changes to UI.
+ ///
+ private static Dispatcher uiDispatcher = null;
+
+ ///
+ /// Whether or not packet capture is active.
+ ///
+ private bool packetCaptureActive = false;
+
+ ///
+ /// Available controller emulation types.
+ ///
+ private enum ControllerType
+ {
+ None = -1,
+ vJoy = 0,
+ ViGEmBus = 1,
+ RPCS3 = 2
+ }
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ var version = Assembly.GetEntryAssembly().GetName().Version;
+ versionLabel.Content = $"v{version}";
+#if DEBUG
+ versionLabel.Content += " Debug";
+#endif
+
+ // Capture Dispatcher object for use in callbacks
+ uiDispatcher = Dispatcher;
+ }
+
+ ///
+ /// Called when the window loads.
+ ///
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ // Connect to console
+ var textboxConsole = new TextBoxWriter(messageConsole);
+ Console.SetOut(textboxConsole);
+
+ // Check for settings file corruption
+ try
+ {
+ // Settings validity *must* be checked via OpenExeConfiguration;
+ // querying a random setting instead will cause the corrupt data to become
+ // cached internally and require a program restart to clear out
+ ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
+ }
+ catch (ConfigurationErrorsException ex)
+ {
+ var result = MessageBox.Show(
+ "RB4InstrumentMapper has detected that your settings have become corrupted. " +
+ "This may be due to a crash or some other improper exiting of the program.\n\n" +
+ "To continue, your settings must be deleted and reset. Do you wish to continue?",
+ "Settings Corruption Detected",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning
+ );
+ if (result != MessageBoxResult.Yes)
+ {
+ Application.Current.Shutdown();
+ return;
+ }
+
+ File.Delete(ex.Filename);
+ Settings.Default.Reset();
+ Settings.Default.Save();
+ }
+
+ // Check for vJoy
+ bool vjoyFound = vJoyInstance.Enabled;
+ if (vjoyFound)
+ {
+ // Log vJoy driver attributes (Vendor Name, Product Name, Version Number)
+ Console.WriteLine($"vJoy found! - Vendor: {vJoyInstance.Manufacturer}, Product: {vJoyInstance.Product}, Version Number: {vJoyInstance.SerialNumber}");
+
+ if (vJoyInstance.GetAvailableDeviceCount() > 0)
+ {
+ vjoyDeviceTypeOption.IsEnabled = true;
+ }
+ else
+ {
+ Console.WriteLine("No vJoy devices found. vJoy selection will be unavailable.");
+ vjoyDeviceTypeOption.IsEnabled = false;
+ vjoyDeviceTypeOption.IsSelected = false;
+ }
+ }
+ else
+ {
+ Console.WriteLine("No vJoy driver found, or vJoy is disabled. vJoy selection will be unavailable.");
+ vjoyDeviceTypeOption.IsEnabled = false;
+ vjoyDeviceTypeOption.IsSelected = false;
+ }
+
+ // Check for ViGEmBus
+ bool vigemFound = ViGEmInstance.TryInitialize();
+ if (vigemFound)
+ {
+ Console.WriteLine("ViGEmBus found!");
+ vigemDeviceTypeOption.IsEnabled = true;
+ rpcs3DeviceTypeOption.IsEnabled = true;
+ }
+ else
+ {
+ Console.WriteLine("ViGEmBus not found. ViGEmBus selection will be unavailable.");
+ vigemDeviceTypeOption.IsEnabled = false;
+ vigemDeviceTypeOption.IsSelected = false;
+ rpcs3DeviceTypeOption.IsEnabled = false;
+ rpcs3DeviceTypeOption.IsSelected = false;
+ }
+
+ // Exit if neither ViGEmBus nor vJoy are installed
+ if (!vjoyFound && !vigemFound)
+ {
+ MessageBox.Show("No controller emulators found! Please install vJoy or ViGEmBus.\nThe program will now shut down.", "No Controller Emulators Found", MessageBoxButton.OK, MessageBoxImage.Error);
+ Application.Current.Shutdown();
+ return;
+ }
+
+ // Load backend settings
+ // Done after initializing virtual controller clients
+ LoadBackendSettings();
+
+ // Initialize backends
+ GameInputBackend.DeviceCountChanged += GameInputDeviceCountChanged;
+ GameInputBackend.Initialize();
+ SetGameInputInitialized(GameInputBackend.Initialized);
+
+ WinUsbBackend.DeviceCountChanged += WinUsbDeviceCountChanged;
+ WinUsbBackend.Initialize();
+ SetWinUsbInitialized(WinUsbBackend.Initialized);
+
+ // Auto-start capture if applicable
+ if ((Program.AutoStart || Settings.Default.autoStart) && startButton.IsEnabled)
+ {
+ StartCapture();
+ }
+ }
+
+ private void LoadBackendSettings()
+ {
+ SetDeviceType((ControllerType)Settings.Default.controllerDeviceType);
+ BackendSettings.UseAccurateDrumMappings = Settings.Default.accurateDrumMaps;
+ BackendSettings.PollingFrequency = Settings.Default.pollingFrequency;
+ }
+
+ ///
+ /// Called when the window has closed.
+ ///
+ private void Window_Closed(object sender, EventArgs e)
+ {
+ // Shut down
+ if (packetCaptureActive)
+ {
+ StopCapture();
+ }
+
+ GameInputBackend.Uninitialize();
+ GameInputBackend.DeviceCountChanged -= GameInputDeviceCountChanged;
+
+ WinUsbBackend.Uninitialize();
+ WinUsbBackend.DeviceCountChanged -= WinUsbDeviceCountChanged;
+
+ // Clean up
+ Settings.Default.Save();
+ Logging.CloseAll();
+ ViGEmInstance.Dispose();
+ }
+
+ private void GameInputDeviceCountChanged()
+ {
+ uiDispatcher.Invoke(() => gameInputDeviceCountLabel.Content = $"Count: {GameInputBackend.DeviceCount}");
+ }
+
+ private void WinUsbDeviceCountChanged()
+ {
+ uiDispatcher.Invoke(() => usbDeviceCountLabel.Content = $"Count: {WinUsbBackend.DeviceCount}");
+ }
+
+ ///
+ /// Configures the Pcap device and controller devices, and starts packet capture.
+ ///
+ private void StartCapture()
+ {
+ // Start capture in backends
+ WinUsbBackend.StartCapture();
+ GameInputBackend.StartCapture();
+ packetCaptureActive = true;
+
+ // Set window controls
+ usbConfigureDevicesButton.IsEnabled = false;
+
+ controllerDeviceTypeCombo.IsEnabled = false;
+
+ packetDebugCheckBox.IsEnabled = false;
+ packetLogCheckBox.IsEnabled = false;
+
+ settingsButton.IsEnabled = false;
+
+ startButton.Content = "Stop";
+
+ // Initialize packet log
+ if (packetLogCheckBox.IsEnabled && (packetLogCheckBox.IsChecked ?? false))
+ {
+ Logging.CreatePacketLog();
+ }
+ }
+
+ ///
+ /// Stops packet capture/mapping and resets Pcap/controller objects.
+ ///
+ private void StopCapture()
+ {
+ WinUsbBackend.StopCapture();
+ GameInputBackend.StopCapture();
+
+ // Store whether or not the packet log was created
+ bool doPacketLogMessage = Logging.PacketLogExists;
+ // Close packet log file
+ Logging.ClosePacketLog();
+
+ // Disable packet capture active flag
+ packetCaptureActive = false;
+
+ // Set window controls
+ usbConfigureDevicesButton.IsEnabled = true;
+
+ packetDebugCheckBox.IsEnabled = true;
+ packetLogCheckBox.IsEnabled = true;
+
+ settingsButton.IsEnabled = true;
+
+ controllerDeviceTypeCombo.IsEnabled = true;
+
+ startButton.Content = "Start";
+
+ // Force a refresh of the controller textbox
+ controllerDeviceTypeCombo_SelectionChanged(null, null);
+
+ Console.WriteLine("Stopped capture.");
+ if (doPacketLogMessage)
+ {
+ Console.WriteLine($"Packet log was written to {Logging.PacketLogPath}");
+ }
+ }
+
+ private void SetGameInputInitialized(bool enabled)
+ {
+ gameInputDeviceCountLabel.IsEnabled = enabled;
+ gameInputDeviceCountLabel.Content = enabled ? $"Count: {GameInputBackend.DeviceCount}" : "0";
+ gameInputRefreshButton.Content = enabled ? "Refresh" : "Initialize";
+ }
+
+ private void SetWinUsbInitialized(bool enabled)
+ {
+ usbDeviceCountLabel.IsEnabled = enabled;
+ usbDeviceCountLabel.Content = enabled ? $"Count: {WinUsbBackend.DeviceCount}" : "0";
+ usbConfigureDevicesButton.Content = enabled ? "Configure USB Devices" : "Initialize";
+ }
+
+ private void SetDeviceType(ControllerType type)
+ {
+ int typeInt = (int)type;
+ if (controllerDeviceTypeCombo.SelectedIndex != typeInt)
+ {
+ // Set device type selection to the correct thing
+ // Setting this fires off the handler, so we need to return
+ // and let the second call set things
+ controllerDeviceTypeCombo.SelectedIndex = typeInt;
+ return;
+ }
+
+ Settings.Default.controllerDeviceType = typeInt;
+
+ switch (type)
+ {
+ case ControllerType.vJoy:
+ if (vjoyDeviceTypeOption.IsEnabled && vJoyInstance.GetAvailableDeviceCount() > 0)
+ {
+ BackendSettings.MapperMode = MappingMode.vJoy;
+ }
+ else
+ {
+ // Reset device type selection
+ // Setting this fires off the handler again, no extra handling is needed
+ BackendSettings.MapperMode = MappingMode.NotSet;
+ controllerDeviceTypeCombo.SelectedIndex = -1;
+ return;
+ }
+ break;
+
+ case ControllerType.ViGEmBus:
+ if (vigemDeviceTypeOption.IsEnabled && ViGEmInstance.Initialized)
+ {
+ BackendSettings.MapperMode = MappingMode.ViGEmBus;
+ }
+ else
+ {
+ // Reset device type selection
+ // Setting this fires off the handler again, no extra handling is needed
+ BackendSettings.MapperMode = MappingMode.NotSet;
+ controllerDeviceTypeCombo.SelectedIndex = -1;
+ return;
+ }
+ break;
+
+ case ControllerType.RPCS3:
+ if (rpcs3DeviceTypeOption.IsEnabled && ViGEmInstance.Initialized)
+ {
+ BackendSettings.MapperMode = MappingMode.RPCS3;
+ }
+ else
+ {
+ // Reset device type selection
+ // Setting this fires off the handler again, no extra handling is needed
+ BackendSettings.MapperMode = MappingMode.NotSet;
+ controllerDeviceTypeCombo.SelectedIndex = -1;
+ return;
+ }
+ break;
+
+ case ControllerType.None:
+ BackendSettings.MapperMode = MappingMode.NotSet;
+ break;
+
+ default:
+ BackendSettings.MapperMode = MappingMode.NotSet;
+ controllerDeviceTypeCombo.SelectedIndex = -1;
+ break;
+ }
+
+ // Enable start button if a mapping mode is set
+ startButton.IsEnabled = BackendSettings.MapperMode != MappingMode.NotSet;
+ controllerDeviceTypeLabel.FontWeight = !startButton.IsEnabled ? FontWeights.Bold : FontWeights.Normal;
+ }
+
+ ///
+ /// Handles the click of the Start button.
+ ///
+ private void startButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (!packetCaptureActive)
+ {
+ StartCapture();
+ }
+ else
+ {
+ StopCapture();
+ }
+ }
+
+ ///
+ /// Handles the packet debug checkbox being checked/unchecked.
+ ///
+ private void packetDebugCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
+ {
+ BackendSettings.LogPackets = packetDebugCheckBox.IsChecked.GetValueOrDefault();
+ }
+
+ ///
+ /// Handles the verbose error checkbox being checked.
+ ///
+ private void verboseLogCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
+ {
+ Logging.PrintVerbose = verboseLogCheckBox.IsChecked.GetValueOrDefault();
+ }
+
+ ///
+ /// Handles the controller type setting being changed.
+ ///
+ private void controllerDeviceTypeCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ SetDeviceType((ControllerType)controllerDeviceTypeCombo.SelectedIndex);
+ }
+
+ ///
+ /// Handles the click of the Settings button.
+ ///
+ private void settingsButton_Click(object sender, RoutedEventArgs e)
+ {
+ var window = new SettingsWindow();
+
+ // Position relative to the top-right of the main window
+ window.Left = this.Left + (this.Width - window.Width - 50);
+ window.Top = this.Top + 50;
+
+ window.ShowDialog();
+
+ LoadBackendSettings();
+ }
+
+ ///
+ /// Handles the click of the GameInput Refresh button.
+ ///
+ private void gameInputRefreshButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (!GameInputBackend.Initialized)
+ {
+ GameInputBackend.Initialize();
+ SetGameInputInitialized(GameInputBackend.Initialized);
+ return;
+ }
+
+ GameInputBackend.Refresh();
+ }
+
+ ///
+ /// Handles the click of the USB Configure Devices button.
+ ///
+ private void usbConfigureDevicesButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (!WinUsbBackend.Initialized)
+ {
+ WinUsbBackend.Initialize();
+ SetWinUsbInitialized(GameInputBackend.Initialized);
+ return;
+ }
+
+ // Disable GameInput to prevent weird issues
+ // Otherwise, devices switched over aren't disconnected on the GameInput side,
+ // and devices reverted aren't picked up unless it's plugged in before RB4IM starts.
+ // Both require a restart of the PC or GameInput service to fix.
+ GameInputBackend.Uninitialize();
+
+ if (!Settings.Default.switchDriverDisclaimerShown)
+ {
+ MessageBox.Show(
+ "This driver switching process has proven itself to be unstable for some users. " +
+ "If you continue, and the procedure fails, your device may become stuck in a non-functional driver state that requires manual intervention to fix.\n\n" +
+ "You have been warned! Refer to the manual uninstallation instructions linked in this window if you run into trouble.",
+ "DISCLAIMER",
+ MessageBoxButton.OK,
+ MessageBoxImage.Warning
+ );
+ Settings.Default.switchDriverDisclaimerShown = true;
+ }
+
+ var window = new UsbDeviceListWindow();
+ window.ShowDialog();
+
+ GameInputBackend.Initialize();
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.GUI/Windows/SettingsWindow.xaml b/RB4InstrumentMapper.GUI/Windows/SettingsWindow.xaml
new file mode 100644
index 0000000..bb3b820
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Windows/SettingsWindow.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RB4InstrumentMapper.GUI/Windows/SettingsWindow.xaml.cs b/RB4InstrumentMapper.GUI/Windows/SettingsWindow.xaml.cs
new file mode 100644
index 0000000..1ada7c0
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Windows/SettingsWindow.xaml.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Windows;
+using System.Windows.Input;
+using RB4InstrumentMapper.Core;
+using RB4InstrumentMapper.GUI.Properties;
+
+namespace RB4InstrumentMapper.GUI
+{
+ ///
+ /// Interaction logic for SettingsWindow.xaml
+ ///
+ public partial class SettingsWindow : Window
+ {
+ private uint _pollingFrequency;
+
+ public SettingsWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ autoStartCheckBox.IsChecked = Settings.Default.autoStart;
+ accurateDrumMapsCheckBox.IsChecked = Settings.Default.accurateDrumMaps;
+ _pollingFrequency = BackendSettings.ClampPollingFrequency(Settings.Default.pollingFrequency);
+
+ pollingFrequencyTextBox.Text = _pollingFrequency.ToString();
+ }
+
+ private void Window_Closed(object sender, EventArgs e)
+ {
+ Settings.Default.Save();
+ }
+
+ private void Window_MouseDown(object sender, MouseButtonEventArgs args)
+ {
+ // Workaround for textboxes not getting unfocused when clicking the window,
+ // they only get unfocused when clicking another element
+ if (args.MouseDevice.DirectlyOver != Keyboard.FocusedElement)
+ {
+ Keyboard.ClearFocus();
+ }
+ }
+
+ private void saveButton_Click(object sender, RoutedEventArgs args)
+ {
+ // Changes to settings are only saved when hitting Save
+ Settings.Default.autoStart = autoStartCheckBox.IsChecked.GetValueOrDefault();
+ Settings.Default.accurateDrumMaps = accurateDrumMapsCheckBox.IsChecked.GetValueOrDefault();
+ Settings.Default.pollingFrequency = _pollingFrequency;
+
+ Close();
+ }
+
+ private void cancelButton_Click(object sender, RoutedEventArgs args)
+ {
+ Close();
+ }
+
+ private void pollingFrequencyTextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
+ {
+ if (!uint.TryParse(pollingFrequencyTextBox.Text, out uint value))
+ {
+ value = _pollingFrequency;
+ }
+
+ _pollingFrequency = BackendSettings.ClampPollingFrequency(value);
+ pollingFrequencyTextBox.Text = _pollingFrequency.ToString();
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.GUI/Windows/TextBoxWriter.cs b/RB4InstrumentMapper.GUI/Windows/TextBoxWriter.cs
new file mode 100644
index 0000000..dc6e02c
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Windows/TextBoxWriter.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Windows.Controls;
+using System.Windows.Threading;
+
+namespace RB4InstrumentMapper.GUI
+{
+ ///
+ /// A text writer which writes to a WPF textbox.
+ ///
+ ///
+ /// https://social.technet.microsoft.com/wiki/contents/articles/12347.wpfhowto-add-a-debugoutput-console-to-your-application.aspx
+ ///
+ public class TextBoxWriter : TextWriter
+ {
+ private const int MaxLines = 100;
+
+ private readonly object writeLock = new object();
+ private readonly StringBuilder currentLine = new StringBuilder();
+ private readonly Queue visibleLines = new Queue(MaxLines);
+
+ private readonly TextBox textBox;
+ private readonly Action updateText;
+ private volatile bool updateQueued;
+
+ public override Encoding Encoding => Encoding.Unicode;
+
+ public TextBoxWriter(TextBox output)
+ {
+ textBox = output;
+ updateText = UpdateText;
+ }
+
+ private void UpdateText()
+ {
+ lock (writeLock)
+ {
+ bool doScroll = Math.Abs(textBox.ExtentHeight - (textBox.VerticalOffset + textBox.ViewportHeight)) < (textBox.FontSize * 3);
+ textBox.Text = string.Join(Environment.NewLine, visibleLines);
+ if (doScroll)
+ textBox.ScrollToEnd();
+ updateQueued = false;
+ }
+ }
+
+ // Optimized path to avoid excess allocations and calls to Write(char)
+ public override void WriteLine(string value)
+ {
+ lock (writeLock)
+ {
+ currentLine.Append(value);
+ FlushLine();
+ }
+ }
+
+ public override void Write(char value)
+ {
+ lock (writeLock)
+ {
+ foreach (char c in CoreNewLine)
+ {
+ if (value == c)
+ {
+ FlushLine();
+ return;
+ }
+ }
+
+ currentLine.Append(value);
+ }
+ }
+
+ public override void Flush()
+ {
+ lock (writeLock)
+ {
+ FlushLine();
+ }
+ }
+
+ private void FlushLine()
+ {
+ // Ignore empty lines or duplicate updates
+ if (currentLine.Length < 1)
+ return;
+
+ visibleLines.Enqueue(currentLine.ToString());
+ while (visibleLines.Count > MaxLines)
+ visibleLines.Dequeue();
+
+ currentLine.Clear();
+
+ // Don't queue multiple updates at once
+ if (!updateQueued)
+ {
+ textBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, updateText);
+ updateQueued = true;
+ }
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.GUI/Windows/UsbDeviceListWindow.xaml b/RB4InstrumentMapper.GUI/Windows/UsbDeviceListWindow.xaml
new file mode 100644
index 0000000..0d16cd5
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Windows/UsbDeviceListWindow.xaml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+ These devices are handled by Windows through the GameInput backend.
+ They will not work in games that do not support them natively. Hit the
+ 'Switch Driver' button to have RB4InstrumentMapper provide support for
+ them through virtual controllers.
+
+
+
+
+
+
+
+
+ Manual installation instructions
+
+
+
+
+
+
+
+
+
+ These devices are handled by RB4InstrumentMapper through WinUSB.
+ RB4InstrumentMapper must be running for them to work. These devices
+ will also not work in games that already support them natively, unless
+ they also support Xbox 360 or generic input devices.
+
+
+
+
+
+
+
+
+ Manual uninstallation instructions
+
+
+
+
+
diff --git a/RB4InstrumentMapper.GUI/Windows/UsbDeviceListWindow.xaml.cs b/RB4InstrumentMapper.GUI/Windows/UsbDeviceListWindow.xaml.cs
new file mode 100644
index 0000000..2593dd8
--- /dev/null
+++ b/RB4InstrumentMapper.GUI/Windows/UsbDeviceListWindow.xaml.cs
@@ -0,0 +1,206 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Navigation;
+using Nefarius.Drivers.WinUSB;
+using Nefarius.Utilities.DeviceManagement.Extensions;
+using Nefarius.Utilities.DeviceManagement.PnP;
+using RB4InstrumentMapper.Core;
+using RB4InstrumentMapper.Core.Parsing;
+
+namespace RB4InstrumentMapper.GUI
+{
+ ///
+ /// Interaction logic for UsbDeviceListWindow.xaml
+ ///
+ public partial class UsbDeviceListWindow : Window
+ {
+ private readonly DeviceNotificationListener watcher = new DeviceNotificationListener();
+
+ private bool switchingDriver = false;
+
+ public UsbDeviceListWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void WindowLoaded(object sender, RoutedEventArgs e)
+ {
+ watcher.DeviceArrived += DeviceArrived;
+ watcher.DeviceRemoved += DeviceRemoved;
+ watcher.StartListen(DeviceInterfaceIds.UsbDevice);
+
+ Refresh();
+ }
+
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ e.Cancel = switchingDriver;
+ base.OnClosing(e);
+ }
+
+ private void WindowClosed(object sender, EventArgs e)
+ {
+ watcher.StopListen();
+ watcher.Dispose();
+ }
+
+ private void DriverSwitchStart(XboxUsbDeviceControl sender)
+ {
+ switchingDriver = true;
+ // This hides the entire title bar. Not the prettiest, but properly disabling
+ // the close button requires calling into native, and I don't wanna do that just for this window lol
+ WindowStyle = WindowStyle.None;
+
+ DisableAllEntries(winUsbDeviceList);
+ DisableAllEntries(xgipDeviceList);
+ }
+
+ private void DriverSwitchEnd(XboxUsbDeviceControl sender)
+ {
+ switchingDriver = false;
+ WindowStyle = WindowStyle.SingleBorderWindow;
+
+ // Bit redundant, but w/e lol
+ EnableAllEntries(winUsbDeviceList);
+ EnableAllEntries(xgipDeviceList);
+
+ Refresh();
+ }
+
+ private void EnableAllEntries(StackPanel devices)
+ {
+ foreach (XboxUsbDeviceControl entry in devices.Children)
+ {
+ entry.Enable();
+ }
+
+ devices.UpdateLayout();
+ }
+
+ private void DisableAllEntries(StackPanel devices)
+ {
+ foreach (XboxUsbDeviceControl entry in devices.Children)
+ {
+ entry.Disable();
+ }
+
+ devices.UpdateLayout();
+ }
+
+ private void Refresh()
+ {
+ RemoveAll(winUsbDeviceList);
+ RemoveAll(xgipDeviceList);
+
+ foreach (var deviceInfo in USBDevice.GetDevices(DeviceInterfaceIds.UsbDevice))
+ {
+ AddDevice(deviceInfo.DevicePath);
+ }
+ }
+
+ private void DeviceArrived(DeviceEventArgs args)
+ {
+ if (switchingDriver)
+ return;
+
+ Dispatcher.BeginInvoke(new Action(() => AddDevice(args.SymLink)));
+ }
+
+ private void DeviceRemoved(DeviceEventArgs args)
+ {
+ if (switchingDriver)
+ return;
+
+ Dispatcher.BeginInvoke(new Action(() => RemoveDevice(args.SymLink)));
+ }
+
+ private void AddDevice(string devicePath)
+ {
+ try
+ {
+ devicePath = devicePath.ToLower();
+ var pnpDevice = PnPDevice.GetDeviceByInterfaceId(devicePath).ToUsbPnPDevice();
+
+ if (WinUsbBackend.IsCompatibleDevice(pnpDevice))
+ {
+ var deviceControl = new XboxUsbDeviceControl(devicePath, pnpDevice, winusb: true);
+ deviceControl.DriverSwitchStart += DriverSwitchStart;
+ deviceControl.DriverSwitchEnd += DriverSwitchEnd;
+ winUsbDeviceList.Children.Add(deviceControl);
+ }
+ else if (WinUsbBackend.IsXGIPDevice(pnpDevice))
+ {
+ var deviceControl = new XboxUsbDeviceControl(devicePath, pnpDevice, winusb: false);
+ deviceControl.DriverSwitchStart += DriverSwitchStart;
+ deviceControl.DriverSwitchEnd += DriverSwitchEnd;
+ xgipDeviceList.Children.Add(deviceControl);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.Main_WriteException(ex, $"Failed to add device {devicePath} to USB device window!");
+ return;
+ }
+ }
+
+ private void RemoveDevice(string devicePath)
+ {
+ devicePath = devicePath.ToLower();
+
+ RemoveDevice(devicePath, winUsbDeviceList);
+ RemoveDevice(devicePath, xgipDeviceList);
+ }
+
+ private void RemoveDevice(string devicePath, StackPanel devices)
+ {
+ var children = devices.Children;
+ // By index, since we need to modify the list
+ for (int i = 0; i < children.Count; i++)
+ {
+ var entry = (XboxUsbDeviceControl)children[i];
+ if (entry.DevicePath == devicePath)
+ {
+ entry.DriverSwitchStart -= DriverSwitchStart;
+ entry.DriverSwitchEnd -= DriverSwitchEnd;
+
+ children.RemoveAt(i--);
+
+ // Don't break here, continue through everything to ensure removal
+ // For some reason, things don't get removed correctly otherwise
+ // break;
+ }
+ }
+
+ devices.UpdateLayout();
+ }
+
+ private void RemoveAll(StackPanel devices)
+ {
+ foreach (XboxUsbDeviceControl entry in devices.Children)
+ {
+ entry.DriverSwitchStart -= DriverSwitchStart;
+ entry.DriverSwitchEnd -= DriverSwitchEnd;
+ }
+
+ devices.Children.Clear();
+ devices.UpdateLayout();
+ }
+
+ private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ MessageBox.Show(
+ "Manually switching device drivers is for advanced users, and must be performed carefully. " +
+ "You are responsible for any problems that may arise from this process, and you are largely on your own if something bugs out.\n\n" +
+ "You have been warned!",
+ "DISCLAIMER",
+ MessageBoxButton.OK,
+ MessageBoxImage.Warning
+ );
+ Process.Start(e.Uri.AbsoluteUri);
+ e.Handled = true;
+ }
+ }
+}
diff --git a/RB4InstrumentMapper.Installer/Bundle.wxs b/RB4InstrumentMapper.Installer/Bundle.wxs
new file mode 100644
index 0000000..08d9f42
--- /dev/null
+++ b/RB4InstrumentMapper.Installer/Bundle.wxs
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RB4InstrumentMapper.Installer/RB4InstrumentMapper.Installer.wixproj b/RB4InstrumentMapper.Installer/RB4InstrumentMapper.Installer.wixproj
new file mode 100644
index 0000000..3cc7f73
--- /dev/null
+++ b/RB4InstrumentMapper.Installer/RB4InstrumentMapper.Installer.wixproj
@@ -0,0 +1,22 @@
+
+
+
+ RB4InstrumentMapperInstaller
+ Bundle
+ x64
+ x64
+
+
+
+
+
+
+
+
+
+
+
+ $(DefineConstants);ProgramVersion=$(Version);ResourcesDir=$(ResourcesDir);PkgMicrosoft_GameInput=$(PkgMicrosoft_GameInput)
+
+
+
\ No newline at end of file
diff --git a/RB4InstrumentMapper.Package/Product.wxs b/RB4InstrumentMapper.Package/Product.wxs
new file mode 100644
index 0000000..2a16d46
--- /dev/null
+++ b/RB4InstrumentMapper.Package/Product.wxs
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RB4InstrumentMapper.Package/RB4InstrumentMapper.Package.wixproj b/RB4InstrumentMapper.Package/RB4InstrumentMapper.Package.wixproj
new file mode 100644
index 0000000..e9e90a0
--- /dev/null
+++ b/RB4InstrumentMapper.Package/RB4InstrumentMapper.Package.wixproj
@@ -0,0 +1,28 @@
+
+
+
+ x64
+ x64
+
+ $(DefineConstants);ProgramVersion=$(Version);ResourcesDir=$(ResourcesDir)
+
+ ICE61
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/RB4InstrumentMapper.csproj b/RB4InstrumentMapper.csproj
deleted file mode 100644
index 1f5e682..0000000
--- a/RB4InstrumentMapper.csproj
+++ /dev/null
@@ -1,163 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {93041197-1E1B-4084-B1DD-C8363A588C48}
- WinExe
- RB4InstrumentMapper
- RB4InstrumentMapper
- v4.7.2
- 512
- {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- 4
- true
- true
-
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
- true
- bin\x64\Debug\
- DEBUG;TRACE
- full
- x64
- 7.3
- prompt
- true
-
-
- bin\x64\Release\
- TRACE
- true
- pdbonly
- x64
- 7.3
- prompt
- true
-
-
-
- packages\Nefarius.ViGEm.Client.1.17.178\lib\net452\Nefarius.ViGEm.Client.dll
-
-
- packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Base.dll
-
-
- packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Core.dll
-
-
- packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Core.Extensions.dll
-
-
- packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Packets.dll
-
-
- False
- Dependencies\x64\vJoyInterfaceWrap.dll
-
-
-
-
-
-
-
-
-
-
- 4.0
-
-
-
-
-
-
-
- MSBuild:Compile
- Designer
-
-
-
-
-
-
-
-
-
-
- MSBuild:Compile
- Designer
-
-
- App.xaml
- Code
-
-
-
- MainWindow.xaml
- Code
-
-
-
-
- Code
-
-
- True
- True
- Resources.resx
-
-
- True
- Settings.settings
- True
-
-
- ResXFileCodeGenerator
- Resources.Designer.cs
-
-
-
-
-
- SettingsSingleFileGenerator
- Settings.Designer.cs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- copy $(SolutionDir)Dependencies\x64\vJoyInterface.dll $(TargetDir).
-
-
\ No newline at end of file
diff --git a/RB4InstrumentMapper.sln b/RB4InstrumentMapper.sln
index bdccd7a..2bea222 100644
--- a/RB4InstrumentMapper.sln
+++ b/RB4InstrumentMapper.sln
@@ -1,50 +1,46 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.31729.503
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RB4InstrumentMapper", "RB4InstrumentMapper.csproj", "{93041197-1E1B-4084-B1DD-C8363A588C48}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RB4InstrumentMapper.Core", "RB4InstrumentMapper.Core\RB4InstrumentMapper.Core.csproj", "{B6FC7277-DC0B-4E36-8CEA-11E2D4E0A21F}"
EndProject
-Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "RB4InstrumentMapperInstaller", "Installer\RB4InstrumentMapperInstaller.wixproj", "{047562BB-6D63-4259-8E2E-0A5E834190B1}"
- ProjectSection(ProjectDependencies) = postProject
- {93041197-1E1B-4084-B1DD-C8363A588C48} = {93041197-1E1B-4084-B1DD-C8363A588C48}
- EndProjectSection
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RB4InstrumentMapper.CLI", "RB4InstrumentMapper.CLI\RB4InstrumentMapper.CLI.csproj", "{A5C36048-BF9B-40CD-8093-9ED1D70A2AC7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RB4InstrumentMapper.GUI", "RB4InstrumentMapper.GUI\RB4InstrumentMapper.GUI.csproj", "{AEEF817D-BB48-4298-B82B-90192F78089D}"
+EndProject
+Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "RB4InstrumentMapper.Package", "RB4InstrumentMapper.Package\RB4InstrumentMapper.Package.wixproj", "{A845D07C-443B-4A08-8766-2E0950A3E124}"
+EndProject
+Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "RB4InstrumentMapper.Installer", "RB4InstrumentMapper.Installer\RB4InstrumentMapper.Installer.wixproj", "{9E28F285-3EAD-4F6B-A72E-64C29ECCED07}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
- Debug|x86 = Debug|x86
- Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
- Release|x86 = Release|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Debug|Any CPU.ActiveCfg = Debug|x64
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Debug|Any CPU.Build.0 = Debug|x64
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Debug|x64.ActiveCfg = Debug|x64
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Debug|x64.Build.0 = Debug|x64
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Debug|x86.ActiveCfg = Debug|Any CPU
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Debug|x86.Build.0 = Debug|Any CPU
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Release|Any CPU.Build.0 = Release|Any CPU
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Release|x64.ActiveCfg = Release|x64
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Release|x64.Build.0 = Release|x64
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Release|x86.ActiveCfg = Release|Any CPU
- {93041197-1E1B-4084-B1DD-C8363A588C48}.Release|x86.Build.0 = Release|Any CPU
- {047562BB-6D63-4259-8E2E-0A5E834190B1}.Debug|Any CPU.ActiveCfg = Debug|x86
- {047562BB-6D63-4259-8E2E-0A5E834190B1}.Debug|x64.ActiveCfg = Debug|x86
- {047562BB-6D63-4259-8E2E-0A5E834190B1}.Debug|x86.ActiveCfg = Debug|x86
- {047562BB-6D63-4259-8E2E-0A5E834190B1}.Debug|x86.Build.0 = Debug|x86
- {047562BB-6D63-4259-8E2E-0A5E834190B1}.Release|Any CPU.ActiveCfg = Release|x86
- {047562BB-6D63-4259-8E2E-0A5E834190B1}.Release|x64.ActiveCfg = Release|x86
- {047562BB-6D63-4259-8E2E-0A5E834190B1}.Release|x86.ActiveCfg = Release|x86
- {047562BB-6D63-4259-8E2E-0A5E834190B1}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {77A2083E-D297-4C3D-91F3-481C8A9A4BCA}
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B6FC7277-DC0B-4E36-8CEA-11E2D4E0A21F}.Debug|x64.ActiveCfg = Debug|x64
+ {B6FC7277-DC0B-4E36-8CEA-11E2D4E0A21F}.Debug|x64.Build.0 = Debug|x64
+ {B6FC7277-DC0B-4E36-8CEA-11E2D4E0A21F}.Release|x64.ActiveCfg = Release|x64
+ {B6FC7277-DC0B-4E36-8CEA-11E2D4E0A21F}.Release|x64.Build.0 = Release|x64
+ {A5C36048-BF9B-40CD-8093-9ED1D70A2AC7}.Debug|x64.ActiveCfg = Debug|x64
+ {A5C36048-BF9B-40CD-8093-9ED1D70A2AC7}.Debug|x64.Build.0 = Debug|x64
+ {A5C36048-BF9B-40CD-8093-9ED1D70A2AC7}.Release|x64.ActiveCfg = Release|x64
+ {A5C36048-BF9B-40CD-8093-9ED1D70A2AC7}.Release|x64.Build.0 = Release|x64
+ {AEEF817D-BB48-4298-B82B-90192F78089D}.Debug|x64.ActiveCfg = Debug|x64
+ {AEEF817D-BB48-4298-B82B-90192F78089D}.Debug|x64.Build.0 = Debug|x64
+ {AEEF817D-BB48-4298-B82B-90192F78089D}.Release|x64.ActiveCfg = Release|x64
+ {AEEF817D-BB48-4298-B82B-90192F78089D}.Release|x64.Build.0 = Release|x64
+ {A845D07C-443B-4A08-8766-2E0950A3E124}.Debug|x64.ActiveCfg = Debug|x64
+ {A845D07C-443B-4A08-8766-2E0950A3E124}.Debug|x64.Build.0 = Debug|x64
+ {A845D07C-443B-4A08-8766-2E0950A3E124}.Release|x64.ActiveCfg = Release|x64
+ {A845D07C-443B-4A08-8766-2E0950A3E124}.Release|x64.Build.0 = Release|x64
+ {9E28F285-3EAD-4F6B-A72E-64C29ECCED07}.Debug|x64.ActiveCfg = Debug|x64
+ {9E28F285-3EAD-4F6B-A72E-64C29ECCED07}.Debug|x64.Build.0 = Debug|x64
+ {9E28F285-3EAD-4F6B-A72E-64C29ECCED07}.Release|x64.ActiveCfg = Release|x64
+ {9E28F285-3EAD-4F6B-A72E-64C29ECCED07}.Release|x64.Build.0 = Release|x64
EndGlobalSection
EndGlobal
diff --git a/README.es.md b/README.es.md
new file mode 100755
index 0000000..d5cc2d7
--- /dev/null
+++ b/README.es.md
@@ -0,0 +1,300 @@
+# RB4InstrumentMapper
+[](https://github.com/TheNathannator/RB4InstrumentMapper/blob/main/README.md)
+[](https://github.com/TheNathannator/RB4InstrumentMapper/blob/main/README.es.md)
+
+Un programa que interpreta y convierte datos de paquetes de instrumentos hechos para Xbox One a controles virtuales, para usar en juegos como [Clone Hero](https://clonehero.net/).
+
+
+
+Todos los instrumentos de Xbox One funcionan (guitarras/baterías de RB4, guitarras de GHL), y también el adaptador de RB4 inalámbrico para instrumentos anteriores (RB4 Wireless Legacy Adapter.)
+
+
+## Secciones
+
+- [Instalación](#instalación)
+ - [Configurando ViGEmBus](#configurando-vigembus)
+ - [Configurando vJoy](#configurando-vjoy)
+- [Usando guitarras y baterías de Rock Band 4](#usando-guitarras-y-bater%C3%ADas-de-rock-band-4)
+ - [Configurando](#configurando)
+ - [Stratocaster, Jaguar, Batería](#stratocaster-jaguar-bater%C3%ADa)
+ - [Riffmaster](#riffmaster)
+ - [Usando](#usando)
+ - [¿Problemas emparejando?](#problemas-emparejando)
+- [Usando guitarras de Guitar Hero Live o el adaptador de RB4 inalámbrico para instrumentos anteriores](#usando-guitarras-de-guitar-hero-live-o-el-adaptador-de-rb4-inal%C3%A1mbrico-para-instrumentos-anteriores-rb4-wireless-legacy-adapter)
+ - [Configurando](#configurando-1)
+ - [Usando](#usando-1)
+- [Configurando tus Instrumentos](#configurando-tus-instrumentos)
+ - [Clone Hero](#clone-hero)
+ - [YARG](#yarg)
+ - [GHWT: Definitive Edition](#ghwt-definitive-edition)
+ - [RPCS3](#rpcs3)
+- [Registro de Paquetes](#registro-de-paquetes)
+- [Registro de Errores](#registro-de-errores)
+- [Construyendo](#construyendo)
+- [Referencias](#referencias)
+- [Licencia](#licencia)
+
+## Instalación
+
+1. Descarga y instala RB4InstrumentMapper de [la pagina de publicaciones (Releases)](https://github.com/TheNathannator/RB4InstrumentMapper/releases).
+2. Descarga y configura [ViGEmBus](https://github.com/ViGEm/ViGEmBus/releases/latest) o [vJoy](https://github.com/jshafer817/vJoy/releases/latest) usando las guías abajo.
+ - ViGEmBus es recomendado porque es más fácil de usar y no requiere configuración adicional. vJoy es una opción alternativa por si tienes problemas con ViGEmBus.
+ - Los dos se pueden instalar al mismo tiempo, pero RB4InstrumentMapper solo va a usar uno.
+
+### Configurando ViGEmBus
+
+1. Descarga e instala [ViGEmBus](https://github.com/ViGEm/ViGEmBus/releases/latest).
+2. ¡Eso es todo!
+
+### Configurando vJoy
+
+1. Descarga e instala [vJoy](https://github.com/jshafer817/vJoy/releases/latest).
+2. Después de descargar, abre el menú de Inicio, busca la carpeta llamada `vJoy` y abre `Configure vJoy` dentro de esa carpeta.
+3. Configura un dispositivo (Device) para cada uno de tus instrumentos usando estas opciones:
+ - Number of Buttons: 16
+ - POV Hat Switch: Continuous, POVs: 1
+ - Axes: `X`, `Y`, `Z`
+
+ 
+
+4. Haz click en `Apply`
+5. ¡Eso es todo!
+
+---
+
+## Usando guitarras y baterías de Rock Band 4
+
+
+
+
+
+
+*RB4InstrumentMapper NO es necesario para usar guitarras en Fortnite*
+
+### Configurando
+
+#### Stratocaster, Jaguar, Batería
+
+
+
+
+
+Para usar guitarras y baterías inalámbrica de Rock Band 4, vas a necesitar un receptor para controles inalámbricos de Xbox One. Las dos versiones oficiales se ven así:
+
+
+
+
+- ¡*No* son iguales a los receptores inalámbricos de Xbox 360! Tienes que tener un receptor para controles inalámbricos de Xbox One (o solo "Xbox"), como los ejemplos arriba.
+- Los receptores que no son originales no se han probado y no recibirán soporte técnico.
+
+Adicionalmente, las guitarras Jaguar  requieren una actualización de firmware para conectarse a receptores para controles inalámbricos de Xbox One.
+
+
+- [Instrucciones (Inglés)](https://bit.ly/2UHzonU)
+- [Recargo de la Wiki de Clone Hero](https://wiki.clonehero.net/link/61), en caso de que el enlace de arriba caiga otra vez.
+
+#### Riffmaster
+
+
+
+Para usar la Riffmaster, vas a necesitar su receptor dedicado, en la foto abajo:
+
+
+
+### Usando
+
+1. En el menú desplegable de `Controller Emulation Mode`, selecciona el modo de emulación que quieres usar.
+
+ 
+
+2. Haz click en el botón de `Start` para empezar a leer datos que va a mandar tu instrumento.
+3. Conecta tus instrumentos. Van a estar detectados y funcionando hasta que presiones el botón de `Stop` o cierres el programa.
+ - Los instrumentos deben de estar conectados antes o después de presionar el botón de `Start`, el orden no importa.
+4. [Configura tus instrumentos en los juegos en los que los vas a usar](#configurando-tus-instrumentos)
+
+#### ¿Problemas emparejando?
+
+Es posible que guitarras/baterías no se emparejan correctamente solo usando el botón de emparejar. Esto incluye la batería de PDP y la guitarra Jaguar en ocasión. Sigue estos pasos para emparejar tu instrumento correctamente:
+1. Entra a `Configuración` de Windows > `Dispositivos` > `Bluetooth y otros dispositivos`
+2. Haz click en `Agregar Bluetooth u otro dispositivo` y elije `Otro`.
+3. Presiona y sostén el botón de emparejar hasta que el botón de Xbox parpadee rapido.
+4. Elije `Xbox compatible game controller` de la lista cuando aparezca.
+5. Si eso no funciona, reinicia tu computadora y trata otra vez.
+
+---
+
+## Usando guitarras de Guitar Hero Live o el adaptador de RB4 inalámbrico para instrumentos anteriores (RB4 Wireless Legacy Adapter)
+
+
+
+
+### Configurando
+
+Vas a necesitar instalar el controlador de WinUSB al receptor de Guitar Hero Live o el adaptador de RB4 inalámbrico para instrumentos anteriores (RB4 Wireless Legacy Adapter) antes de usarlos. Es posible usar RB4InstrumentMapper para instalarlo directamente, por el menú de `Configure Devices` en su menú principal:
+
+1. Haz click en `Configure Devices` abajo de eso.
+
+ 
+
+2. Encuentra el dispositivo que quieres usar en el menú del lado izquierdo.
+
+ 
+
+3. Haz click en `Switch Driver` y espera a que se cambie el controlador. El dispositivo que estás modificando va a salir en el lado derecho cuando se complete esto.
+
+ 
+
+ - Toma en cuenta que cualquier juego que tenga soporte directo para estos dispositivos no va funcionar hasta que borres el controlador de WinUSB. Haz click en el botón de `Revert Driver` para revertir tus cambios para poder usar el dispositivo directamente.
+
+Si tienes problemas con esta instalación, puedes tratar [instalar el controlador manualmente (en Ingles)](Docs/WinUSB/manual-winusb-install.md). Esto no es recomendado para la mayoría de los usuarios. Solo se debe usar esta alternativa si el menú de `Configure Devices` no funciona.
+- Esto también describe como [desinstalar manualmente (en Ingles)](Docs/WinUSB/manual-winusb-install.md#remove-winusb), en caso de que el dispositivo se quede con el controlador atorado y RB4InstrumentMapper pare de detectarlo.
+
+### Usando
+
+1. **¡Asegúrate que instalaste [el controlador de WinUSB](#configurando-1) en los dispositivos que quieres usar! ¡No serán detectados si no esta hecho!**
+2. En el menú desplegable de `Controller Emulation Mode`, selecciona el modo de emulación que quieres usar.
+
+ 
+
+3. Haz click en el botón de `Start` para empezar a leer datos que va a mandar tu instrumento.
+4. [Configura tus instrumentos en los juegos en los que los vas a usar](#configurando-tus-instrumentos).
+
+Se detectará de forma automática si se conectan/desconectan instrumentos, pero no se van a mandar datos a los juegos hasta que presiones el botón de `Start`.
+
+Si tienes problemas con el envío de botones, ya sean presiones o cosas raras, intenta reconectar el receptor/cable. Desafortunadamente, la detección de estos dos tipos de dispositivos no es completamente estable, así que es posible que no funcione perfectamente.
+
+---
+
+## Configurando tus instrumentos
+
+Ahora que RB4InstrumentMapper está listo y corriendo, puedes configurar los controles de tus instrumentos en los juegos que vas a jugar.
+
+### Clone Hero
+
+Si estás usando una guitarra en el modo ViGEmBus, Clone Hero va a mapear todo automáticamente. No debes de tener que hacer ajustes manuales, al menos que estés usando una guitarra de Guitar Hero Live. En ese caso, vas a necesitar cambiar la configuración de Whammy/Palanca de tremolo y de ladear.
+
+- La configuración automática también se va a aplicar a baterías, pero no va estar correcta porque esta diseñada para guitarras. Vas a necesitar cambiar la configuración que esta por defecto y ajustarla manualmente.
+
+Para baterías, modo vJoy, o si necesitas ajustar tus controles por otra razón:
+
+1. Presiona espacio en el menú principal.
+
+ 
+
+2. Haz click en Asignar Mando y presiona un botón para que se asigne.
+
+ 
+
+3. Haz click en las ranuras bajo la sección de `Mando` mapear todo en cada control.
+
+ 
+
+4. Repite para cada instrumento.
+5. Haz click en `Listo`.
+
+Si requieres mas ayuda mapeando tu instrumento, puedes preguntar por ayuda en [el Discord de Clone Hero](https://discord.gg/Hsn4Cgu).
+
+### YARG
+
+Consulta la [documentación oficial (en Ingles)](https://docs.yarg.in/en/profiles).
+
+### GHWT: Definitive Edition
+
+Consulta la [documentación oficial (en Ingles)](https://ghwt.de/wiki/#/wtde/binding?id=binding-controllers).
+
+### RPCS3
+
+1. Para usar en RPCS3, usa la opción de `ViGEmBus (RPCS3 compatibility)` bajo el menú desplegable de `Controller Emulation Mode`. Eso hace que RB4InstrumentMapper mande sus datos de tus instrumentos al emulador en una forma que no requiere hacer ajustes (o ajustes mínimos) en el emulador de RPCS3.
+
+ 
+
+2. Haz click en `Pads` en RPCS3.
+
+ 
+
+3. Cambiar `Handlers` a "XInput", selecciona tu instrumento bajo `Devices` y cambia `Device Class` a "Rock Band - Guitar" si estas usando una guitarra o 'Rock Band - Drums' si estas usando una batería.
+
+ 
+
+4. Repite para cada instrumento en las pestañas de Player.
+5. Haz click en `Listo`.
+
+## Problemas y soluciones
+
+### Preguntas frecuentes
+
+- "Veo un control de XInput después de pulsar Start, ¡pero ningún botón responde!"
+
+### Registro de errores
+
+En caso de que el programa falle, el error se va a registrar en un archivo dentro de la carpeta `RB4InstrumentMapper` > `Logs` que va estar localizada en la carpeta de tus Documentos. Asegúrate de incluirla cuando preguntes por ayuda o creas reportes de problemas con el programa.
+
+### Registro de paquetes
+
+Se puede usar RB4InstrumentMapper para registrar datos a un archivo por motivos de depuración. Para hacer esto, activa `Show Packets (for debugging)` y `Log packets to file` y luego haz click en el botón de `Start`. Los datos se van a guardar en tu carpeta de Documentos. Asegúrate de incluirla cuando preguntes por ayuda o creas reportes de problemas con la interpretación de datos.
+
+Toma en cuenta que estas opciones son solo para depurar. Dejarlas activadas puede causar problemas con el rendimiento del programa.
+
+### Construyendo
+
+Para construir este programa, vas a necesitar:
+
+- Visual Studio, o MSBuild/[el SDK de .NET](https://dotnet.microsoft.com/es-es/download) + tu editor de código preferido.
+- [WiX Toolset v4](https://wixtoolset.org/) si quieres construir el instalador.
+
+#### Con MSBuild
+
+Con MSBuild, usa los siguientes comandos en la carpeta del proyecto:
+
+```
+msbuild -t:Restore -verbosity:minimal
+msbuild "-p:Configuration=Release;Platform=x64" -verbosity:minimal
+```
+
+Esto resultara en los siguientes directorios:
+
+- Intérprete de órdenes: `RB4InstrumentMapper.CLI\bin\x64\Release\net472`
+- Interfaz gráfica de usuario: `RB4InstrumentMapper.GUI\bin\x64\Release\net472`
+- Instalador de interfaz gráfica de usuario+intérprete de órdenes: `RB4InstrumentMapper.Installer\bin\x64\Release`
+
+#### Con el SDK de .NET
+
+##### Manualmente
+
+Con el SDK de .NET, usa los siguientes comandos en la carpeta del proyecto:
+
+```
+dotnet build "-p:Configuration=Release;Platform=x64" -verbosity:minimal
+```
+
+Esto resultara en los siguientes directorios:
+
+- Intérprete de órdenes: `RB4InstrumentMapper.CLI\bin\x64\Release\net472`
+- Interfaz gráfica de usuario: `RB4InstrumentMapper.GUI\bin\x64\Release\net472`
+- Instalador de interfaz gráfica de usuario+intérprete de órdenes: `RB4InstrumentMapper.Installer\bin\x64\Release`
+
+##### Script de construcción
+
+El archivo de `publish.bat` esta proporcionado para crear lanzar nuevas versiones fácilmente. Esta producira el mismo resultado que una construcción manual, con estos resultados:
+
+- Intérprete de órdenes: `publish\RB4InstrumentMapper.CLI.zip`
+- Interfaz gráfica de usuario: `publish\RB4InstrumentMapper.GUI.zip`
+- Instalador de interfaz gráfica de usuario+intérprete de órdenes: `publish\RB4InstrumentMapper.Installer.exe`
+
+## Referencias
+
+Antecesores:
+
+- [Repositorio de GuitarSniffer](https://github.com/artman41/guitarsniffer)
+- [Repositorio de DrumSniffer](https://github.com/Dunkalunk/guitarsniffer)
+
+Datos de paquetes:
+
+- [Registros de paquetes de guitarra por GuitarSniffer](https://1drv.ms/f/s!AgQGk0OeTMLwhA-uDO9IQHEHqGhv)
+- Hoja de datos de paquetes para guitarras de GuitarSniffer: [Nuevos](https://docs.google.com/spreadsheets/d/1ITZUvRniGpfS_HV_rBpSwlDdGukc3GC1CeOe7SavQBo/edit?usp=sharing), [Viejos](https://1drv.ms/x/s!AgQGk0OeTMLwg3GBDXFUC3Erj4Wb)
+- [Codigo fuente de Javascript de rb4.app](https://rb4.app/js/app.js)
+- Investigación original, publicada en el [repositorio de documentación PlasticBand](https://github.com/TheNathannator/PlasticBand).
+
+## Licencia
+
+Este programa esta publicado con la licencia de MIT. Lee [LICENCIA (en Ingles)](LICENSE) para mas información.
diff --git a/README.md b/README.md
index b8a3dfe..7ce130b 100644
--- a/README.md
+++ b/README.md
@@ -1,113 +1,325 @@
# RB4InstrumentMapper
+[](https://github.com/TheNathannator/RB4InstrumentMapper/blob/main/README.md)
+[](https://github.com/TheNathannator/RB4InstrumentMapper/blob/main/README.es.md)
-A utility that connects up to three Xbox One Rock Band 4 instruments (2 guitars and 1 drumkit) for use in [Clone Hero](https://clonehero.net/).
+A program that maps packets from Xbox One instrument peripherals to virtual controllers, for use in games such as [Clone Hero](https://clonehero.net/).
-
+
-Almost all features for guitars and drums are supported.
+All Xbox One instruments are supported (RB4 guitars/drums, GHL guitar), along with the RB4 wireless legacy adapter.
+## Table of Contents
+- [Installation](#installation)
+ - [ViGEmBus Setup](#vigembus-setup)
+ - [vJoy Setup](#vjoy-setup)
+- [Using Rock Band 4 Guitars and Drums](#using-rock-band-4-guitars-and-drums)
+ - [Setup](#setup)
+ - [Stratocaster, Jaguar, Drums](#stratocaster-jaguar-drums)
+ - [Riffmaster](#riffmaster)
+ - [Usage](#usage)
+ - [Having Sync Issues?](#having-sync-issues)
+- [Using Guitar Hero Live Guitars and RB4 Wireless Legacy Adapters](#using-guitar-hero-live-guitars-and-rb4-wireless-legacy-adapters)
+ - [Setup](#setup-1)
+ - [Usage](#usage-1)
+- [Mapping your Controls](#mapping-your-controls)
+ - [Clone Hero](#clone-hero)
+ - [YARG](#yarg)
+ - [GHWT: Definitive Edition](#ghwt-definitive-edition)
+ - [RPCS3](#rpcs3)
+- [Troubleshooting](#troubleshooting)
+ - [FAQs](#faqs)
+ - [Packet Logs](#packet-logs)
+ - [Error Logs](#error-logs)
+- [Advanced Usage](#advanced-usage)
+ - [Command-Line Interface](#command-line-interface)
+ - [Building](#building)
+ - [With MSBuild](#with-msbuild)
+ - [With the .NET SDK](#with-the-net-sdk)
+ - [Manually](#manually)
+ - [Build Script](#build-script)
+- [References](#references)
+- [License](#license)
-## Requirements
+## Installation
-- Xbox One Wireless Receiver
-- Xbox One RB4 Guitars (up to 2)
- - For Jaguar guitars, you will need to install a [firmware update](https://bit.ly/2UHzonU).
-- Xbox One RB4 Drums
- - Note that PDP drums don't seem to work, they turn off after syncing to the receiver.
-- Windows 10 64-bit
-- Npcap in WinPcap compatibility mode, or WinPcap
-- USBPcap
-- vJoy or ViGEmBus
+1. Download and install RB4InstrumentMapper from [the Releases page](https://github.com/TheNathannator/RB4InstrumentMapper/releases).
+2. Download and set up [ViGEmBus](https://github.com/ViGEm/ViGEmBus/releases/latest) or [vJoy](https://github.com/jshafer817/vJoy/releases/latest) using the guides below.
+ - ViGEmBus is recommended, as it is significantly easier to use and requires no configuration. vJoy is supported as an alternative on the off-chance you run into issues with ViGEmBus.
+ - Both can be installed simultaneously if desired, however RB4InstrumentMapper will only use one of them at a time.
-## Installation
+### ViGEmBus Setup
-1. Install [WinPCap](https://www.winpcap.org/install/default.htm) (recommended), or [Npcap](https://nmap.org/npcap/#download) in WinPCap compatibility mode.
- - Make sure you only install one or the other. Installing both at the same time may cause issues.
-2. Install [USBPCap](https://desowin.org/usbpcap/).
-3. Install [ViGEmBus](https://github.com/ViGEm/ViGEmBus/releases/latest) (recommended) or [vJoy](https://github.com/jshafer817/vJoy/releases/latest).
- - If you installed vJoy, configure it:
- 1. Open your Start menu, find the `vJoy` folder, and open the `Configure vJoy` program inside it.
- 2. Configure devices 1, 2, and 3 with these settings:
- - Number of Buttons: 16
- - POV Hat Switch: Continuous, POVs: 1
- - Axes: `X`, `Y`, `Z`
- 3. Click Apply.\
- 
- - If you installed ViGEmBus, there's no configuration required. Outputs for guitars and drums will match that of their Xbox 360 counterparts.
-4. Restart your PC.
-5. Download the latest release from the [Releases tab](https://github.com/ferzkopp/RB4InstrumentMapper/releases/latest) and install it.
-6. Install Pcap.Net dependencies
- - [Microsoft Visual C++ 2010 Service Pack 1 Redistributable Package MFC Security Update](https://www.microsoft.com/en-us/download/details.aspx?id=26999)
- - [Visual C++ Redistributable Packages for Visual Studio 2013 ](https://www.microsoft.com/en-us/download/details.aspx?id=40784)
-
-## Usage
-
-1. Configure the selected Pcap device:
- - Click the `Auto-Detect Pcap` button and follow its instructions.
- - If that doesn't work, then if you installed WinPcap, try installing Npcap instead, or vice versa.
-2. Configure the selected controller device for each guitar and drumkit:
- - If you installed vJoy:
- - Pick one of the vJoy devices that you configured for each instrument you will be using.
- - If you installed ViGEmBus:
- - Pick the `ViGEmBus Device` item in the dropdown for each instrument you will be using. One emulated Xbox 360 controller will be created for each instrument that has this selected.
-3. Connect your instruments if you haven't yet.
-4. Assign the instrument ID for each instrument:
- - Click the `Auto-Detect ID` button next to each ID field.
- - Guitars should auto-detect automatically as they are constantly sending packets. Retry if duplicate IDs were detected (and rejected).
- - Drums require an action such as a button press on the instrument you are assigning within 2 seconds after 'Auto-Detect' was clicked.
-5. Click the Start button.
- - Note: launch *joy.cpl* to check Controller inputs.
-6. Map the controls for each instrument in Clone Hero:
- 1. Press Space on the main menu.
- 2. Click the Assign Controller button and do an action on the instrument to be assigned.
- 3. Click the slots in the Controller column to map the controls for one of the instruments.
- 4. Repeat for Player 2 and 3.
- 5. Click `Done`.
-
-Selections and IDs are saved and should persist across program sessions.
-
-## Error Logs
-
-In the case that the program crashes, an error log is saved to a `RB4InstrumentMapper` > `Logs` folder inside your Documents folder. Make sure to include it when creating an issue report for a crash or when getting help for a crash.
-
-## Other Versions
-
-A fork of this code with an updated interface, 64bit version and other improvements is available at: https://github.com/TheNathannator/RB4InstrumentMapper
+1. Download and install [ViGEmBus](https://github.com/ViGEm/ViGEmBus/releases/latest).
+2. That's all.
-## References
+### vJoy Setup
-- [GuitarSniffer repository](https://github.com/artman41/guitarsniffer)
-- [DrumSniffer repository](https://github.com/Dunkalunk/guitarsniffer)
+1. Download and install [vJoy](https://github.com/jshafer817/vJoy/releases/latest).
+2. After installing, open your Start menu, find the `vJoy` folder, and open the `Configure vJoy` program inside it.
+3. Configure one device for each one of your controllers, using these settings:
+ - Number of Buttons: 16
+ - POV Hat Switch: Continuous, POVs: 1
+ - Axes: `X`, `Y`, `Z`
-Packet Data:
+ 
-- [GuitarSniffer guitar packet logs](https://1drv.ms/f/s!AgQGk0OeTMLwhA-uDO9IQHEHqGhv)
-- GuitarSniffer guitar packet spreadsheets: [New](https://docs.google.com/spreadsheets/d/1ITZUvRniGpfS_HV_rBpSwlDdGukc3GC1CeOe7SavQBo/edit?usp=sharing), [Old](https://1drv.ms/x/s!AgQGk0OeTMLwg3GBDXFUC3Erj4Wb)
-- See [PacketFormats.md](PacketFormats.md) for a breakdown of the known packet data.
+4. Click Apply.
+5. You're all set up!
-## Build Tools
+---
-Compile was done with Visual Studio 2019 Community edition.
+## Using Rock Band 4 Guitars and Drums
-Installer was created using the following tools:
+
+
+
+
-- https://wixtoolset.org/
- - https://wixtoolset.org/releases/v3.11.2/stable
- - https://marketplace.visualstudio.com/items?itemName=WixToolset.WixToolsetVisualStudio2019Extension
- - https://marketplace.visualstudio.com/items?itemName=TomEnglert.Wax
+*Note that RB4InstrumentMapper is NOT required to use guitars in Fortnite!*
-## License
+### Setup
+
+#### Stratocaster, Jaguar, Drums
+
+
+
+
+
+For wireless Rock Band 4 guitars and drums, you will need an Xbox One wireless receiver. Both versions of the official one are shown here:
+
+
+
+
+- This is *not* the same as an Xbox 360 wireless receiver! You must get an Xbox One (or just "Xbox") receiver, as shown above.
+- Third-party receivers are untested, and they will not be deliberately supported.
+
+Additionally, Jaguar guitars  require a firmware update in order to connect to Xbox One receivers.
+
+- [Instructions](https://bit.ly/2UHzonU)
+- [Clone Hero wiki re-host](https://wiki.clonehero.net/link/61), in case the above link goes down again.
+
+#### Riffmaster
+
+
+
+For the Riffmaster, you will need its dedicated wireless dongle, pictured below:
+
+
+
+### Usage
+
+1. In the `Controller Emulation Mode` dropdown, select the controller emulation mode you want to use.
+
+ 
+
+2. Hit the `Start` button to begin reading inputs.
+3. Connect your instruments. They will be picked up and read automatically until you hit `Stop` or close the program.
+ - Instruments can be connected before or after hitting Start, the ordering doesn't matter.
+4. [Map your controls in the game you'll be playing](#mapping-your-controls).
+
+#### Having Sync Issues?
+
+Some guitars/drumkits might not sync properly when using just the sync button. This includes the PDP drumkit and occasionally the Jaguar guitar. Follow these steps to sync your device correctly:
+
+1. Go to Windows settings > Devices > Bluetooth & other devices
+2. Click `Add Bluetooth or other device` and pick the `Everything else` option.
+3. Press and hold the sync button until the Xbox button light flashes quickly.
+4. Select `Xbox compatible game controller` from the list once it appears.
+5. If that doesn't work, restart your PC and try again.
+
+---
+
+## Using Guitar Hero Live Guitars and RB4 Wireless Legacy Adapters
+
+
+
+
+### Setup
+
+You will need to install the WinUSB driver onto the Guitar Hero Live dongle or Rock Band 4 wireless legacy adapter before using it. RB4InstrumentMapper is capable of doing this directly, through the `Configure Devices` button on its main menu:
+
+1. Click the `Configure Devices` button under the `USB` group.
+
+ 
+
+2. Find the device you want to use on the left side of the menu.
+
+ 
+
+3. Click the `Switch Driver` button and wait for it to switch the driver. The device will show up on the right side of the menu once it's done.
+
+ 
+
+ - Note that games that natively support the device will no longer work directly with it until you uninstall the WinUSB driver. Hit the `Revert Driver` button on the device to do so, after which those games will work with it again.
+
+If you run into any issues with this process, you may try [installing the driver manually](Docs/WinUSB/manual-winusb-install.md). This is not recommended for normal use, it should only be used if the Configure Devices menu is not working.
+- This also covers [uninstalling manually](Docs/WinUSB/manual-winusb-install.md#remove-winusb), in case a device gets stuck with the driver installed and RB4InstrumentMapper stops picking it up.
+
+### Usage
+
+1. **Ensure you have [installed WinUSB](#setup-1) on the devices you want to use! They will not be recognized otherwise!**
+2. In the `Controller Emulation Mode` dropdown, select the controller emulation mode you want to use.
+
+ 
+
+3. Hit the `Start` button to begin reading inputs.
+4. [Map your controls in the game you'll be playing](#mapping-your-controls).
+
+Devices will be detected automatically as they are connected/removed, though they will not be read until you hit Start.
+
+If you are having issues with inputs not being registered or other general weirdness, try unplugging and replugging the dongle/cable. Unfortunately this backend is still finnicky, so things may not work the smoothest.
+
+---
+
+## Mapping your Controls
+
+Now that RB4InstrumentMapper is set up and running, map your controls for each instrument in the game you'll be playing.
+
+### Clone Hero
+
+If you're using a guitar in ViGEmBus mode, Clone Hero will automatically map your controls. You shouldn't need to do any manual mapping, unless you're using a Guitar Hero Live guitar, in which case you may need to swap your whammy and tilt bindings.
+
+- This automatic mapping will also apply to drumkits, however here the bindings will not be correct, as they are meant for guitars. You will need to remove the default bindings and do them manually.
+
+For drumkits, vJoy mode, or if you otherwise need to customize your controls:
+
+1. Press Space on the main menu.
+
+ 
+
+2. Click Assign Controller and press a button on the device for it to be assigned.
+
+ 
+
+3. Click the slots in the `Controller` column to map each of the controls.
-Copyright (c) 2021 Andreas Schiffler
+ 
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
-to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+4. Repeat for each connected device.
+5. Click `Done`.
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+If you require further help with mapping, you can ask in the [Clone Hero Discord server](https://discord.gg/Hsn4Cgu).
+
+### YARG
+
+Refer to the [official documentation](https://docs.yarg.in/en/profiles).
+
+### GHWT: Definitive Edition
+
+Refer to the [official documentation](https://ghwt.de/wiki/#/wtde/binding?id=binding-controllers).
+
+### RPCS3
+
+1. For RPCS3, use the `ViGEmBus (RPCS3 compatibility)` controller mode. This will change the mappings that RB4InstrumentMapper outputs so that little to no remapping is required to use instruments in RPCS3.
+
+ 
+
+2. Open the Gamepad Configuration Menu in RPCS3
+
+ 
+
+3. Change the `Handlers` to "XInput", pick your instrument under `Devices`, and set `Device Class` to "Rock Band - Guitar" if you're using a guitar or 'Rock Band - Drums' if you're using drums.
+
+ 
+
+4. Repeat for each connected device in the other Player tabs.
+5. Click `Done`.
+
+## Troubleshooting
+
+### FAQs
+
+- "I see an XInput gamepad after hitting Start, but no inputs respond!"
+
+If you haven't restarted your PC since installing ViGEmBus, do so now. Sometimes it doesn't fully work until after a reboot.
+
+### Error Logs
+
+In the case that the program crashes, an error log is saved to a `RB4InstrumentMapper` > `Logs` folder inside your Documents folder. Make sure to include it when getting help or creating an issue report for the crash.
+
+### Packet Logs
+
+RB4InstrumentMapper is capable of logging packets to a file for debugging purposes. To do so, enable both the `Show Packets (for debugging)` and `Log packets to file` checkboxes, then hit Start. Packet logs get saved to a `RB4InstrumentMapper` > `PacketLogs` folder inside your Documents folder. Make sure to include it when getting help or creating an issue report for packet parsing issues.
+
+Note that these settings are meant for debugging purposes only, leaving them enabled can reduce the performance of the program somewhat.
+
+## Advanced Usage
+
+### Command-Line Interface
+
+A command-line interface version of RB4InstrumentMapper is included alongside the GUI version. This is useful for scenarios where you want to run it without user interaction, such as a custom arcade cabinet or other startup automation scenarios.
+
+The CLI is available both standalone and alongside the installed version of the GUI. Additionaly, [an example startup script](cli_script_example.bat) is provided in the repository which can be quickly customized to suit specific needs.
+
+Example usage:
+
+```
+.\RB4InstrumentMapper.CLI.exe --mode vigem
+```
+
+For available options/arguments, run it without arguments or use the `--help` option.
+
+### Building
+
+To build this program, you will need:
+
+- Visual Studio, or MSBuild/[the .NET SDK](https://dotnet.microsoft.com/en-us/download).
+- [WiX Toolset v4](https://wixtoolset.org/) if you wish to build the installer.
+
+#### With MSBuild
+
+With MSBuild, use the following commands in the project folder:
+
+```
+msbuild -t:Restore -verbosity:minimal
+msbuild "-p:Configuration=Release;Platform=x64" -verbosity:minimal
+```
+
+This will result in the following build output paths:
+
+- CLI: `RB4InstrumentMapper.CLI\bin\x64\Release\net472`
+- GUI: `RB4InstrumentMapper.GUI\bin\x64\Release\net472`
+- GUI+CLI installer: `RB4InstrumentMapper.Installer\bin\x64\Release`
+
+#### With the .NET SDK
+
+##### Manually
+
+With the .NET SDK, use the following command in the project folder:
+
+```
+dotnet build "-p:Configuration=Release;Platform=x64" -verbosity:minimal
+```
+
+This will result in the following build output paths:
+
+- CLI: `RB4InstrumentMapper.CLI\bin\x64\Release\net472`
+- GUI: `RB4InstrumentMapper.GUI\bin\x64\Release\net472`
+- GUI+CLI installer: `RB4InstrumentMapper.Installer\bin\x64\Release`
+
+##### Build Script
+
+The `publish.bat` script is provided to help make publishing new releases easier. This script will produce the same output as a manual build, along with the following additional output:
+
+- Standalone CLI package: `publish\RB4InstrumentMapper.CLI.zip`
+- Standalone GUI package: `publish\RB4InstrumentMapper.GUI.zip`
+- GUI+CLI installer: `publish\RB4InstrumentMapper.Installer.exe`
+
+## References
+
+Predecessors:
+
+- [GuitarSniffer repository](https://github.com/artman41/guitarsniffer)
+- [DrumSniffer repository](https://github.com/Dunkalunk/guitarsniffer)
+
+Packet data:
+
+- [GuitarSniffer guitar packet logs](https://1drv.ms/f/s!AgQGk0OeTMLwhA-uDO9IQHEHqGhv)
+- GuitarSniffer guitar packet spreadsheets: [New](https://docs.google.com/spreadsheets/d/1ITZUvRniGpfS_HV_rBpSwlDdGukc3GC1CeOe7SavQBo/edit?usp=sharing), [Old](https://1drv.ms/x/s!AgQGk0OeTMLwg3GBDXFUC3Erj4Wb)
+- [rb4.app's Javascript source](https://rb4.app/js/app.js)
+- Original research, found in the [PlasticBand documentation repository](https://github.com/TheNathannator/PlasticBand).
+
+## License
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-IN THE SOFTWARE.
\ No newline at end of file
+This program is licensed under the MIT license. See [LICENSE](LICENSE) for details.
diff --git a/Installer/Resources/Icon.ico b/Resources/Icon.ico
similarity index 100%
rename from Installer/Resources/Icon.ico
rename to Resources/Icon.ico
diff --git a/Installer/Resources/Icon.xcf b/Resources/Icon.xcf
similarity index 100%
rename from Installer/Resources/Icon.xcf
rename to Resources/Icon.xcf
diff --git a/Installer/Resources/banner.bmp b/Resources/banner.bmp
similarity index 100%
rename from Installer/Resources/banner.bmp
rename to Resources/banner.bmp
diff --git a/Installer/Resources/dialog.bmp b/Resources/dialog.bmp
similarity index 100%
rename from Installer/Resources/dialog.bmp
rename to Resources/dialog.bmp
diff --git a/Installer/Resources/license.rtf b/Resources/license.rtf
similarity index 100%
rename from Installer/Resources/license.rtf
rename to Resources/license.rtf
diff --git a/cli_script_example.bat b/cli_script_example.bat
new file mode 100644
index 0000000..f319198
--- /dev/null
+++ b/cli_script_example.bat
@@ -0,0 +1,39 @@
+@echo off
+REM RB4InstrumentMapper CLI Launcher Example
+REM =======================================================
+REM This example script is intended to be run inside of the
+REM project source directory, with the CLI built already.
+
+REM Change to the directory where this script is located
+pushd /d "%~dp0"
+
+echo Starting RB4InstrumentMapper in CLI mode...
+echo.
+
+REM Configuration Parameters
+set MODE=vigem
+set TIMEOUT=0
+set WAIT_FOR_DEVICES=true
+set WAIT_TIMEOUT=30
+set LOG_FILE="%~dp0RB4InstrumentMapper_log.txt"
+
+REM Run the CLI version with the specified parameters
+echo Running with: --mode %MODE% --wait-for-devices %WAIT_TIMEOUT% --log-file %LOG_FILE%
+echo.
+
+REM Launch the CLI application
+RB4InstrumentMapper.CLI\bin\x64\Release\net472\RB4InstrumentMapper.CLI.exe --mode %MODE% --wait-for-devices %WAIT_TIMEOUT% --log-file %LOG_FILE% --accurate-drums --verbose
+
+REM Restore the previous working directory
+popd
+
+REM If we get here, check if the application exited with an error
+if %ERRORLEVEL% NEQ 0 (
+ echo.
+ echo RB4InstrumentMapper exited with error code: %ERRORLEVEL%
+ echo.
+ pause
+ exit /b %ERRORLEVEL%
+)
+
+exit /b 0
\ No newline at end of file
diff --git a/packages.config b/packages.config
deleted file mode 100644
index 9c8ba4a..0000000
--- a/packages.config
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/publish.bat b/publish.bat
new file mode 100644
index 0000000..4eaa947
--- /dev/null
+++ b/publish.bat
@@ -0,0 +1,56 @@
+@echo off
+
+echo Cleaning old build outputs
+dotnet clean "-p:Configuration=Debug;Platform=x64" -verbosity:minimal -consoleloggerparameters:NoSummary
+if %ERRORLEVEL% NEQ 0 goto error
+dotnet clean "-p:Configuration=Release;Platform=x64" -verbosity:minimal -consoleloggerparameters:NoSummary
+if %ERRORLEVEL% NEQ 0 goto error
+if exist "%~dp0\publish\*" (
+ del /q "%~dp0\publish\*"
+ if %ERRORLEVEL% NEQ 0 goto error
+)
+echo.
+
+REM Build projects
+
+REM echo Building standalone CLI
+REM dotnet build RB4InstrumentMapper.CLI\RB4InstrumentMapper.CLI.csproj "-p:Configuration=Release;Platform=x64" -verbosity:minimal -consoleloggerparameters:NoSummary
+REM if %ERRORLEVEL% NEQ 0 goto error
+REM echo.
+
+REM echo Building standalone GUI
+REM dotnet build RB4InstrumentMapper.GUI\RB4InstrumentMapper.GUI.csproj "-p:Configuration=Release;Platform=x64" -verbosity:minimal -consoleloggerparameters:NoSummary
+REM if %ERRORLEVEL% NEQ 0 goto error
+REM echo.
+
+echo Building CLI+GUI installer
+dotnet build RB4InstrumentMapper.Installer\RB4InstrumentMapper.Installer.wixproj "-p:Configuration=Release;Platform=x64" -verbosity:minimal -consoleloggerparameters:NoSummary
+if %ERRORLEVEL% NEQ 0 goto error
+echo.
+
+REM Copy files and build archives
+
+REM echo Packaging standalone CLI
+REM 7z a "%~dp0\publish\RB4InstrumentMapper.CLI-x64.zip" "%~dp0\RB4InstrumentMapper.CLI\bin\x64\Release\net472" -bb3 -bd -sse > nul
+REM if %ERRORLEVEL% NEQ 0 goto error
+REM 7z rn "%~dp0\publish\RB4InstrumentMapper.CLI-x64.zip" "net472" "RB4InstrumentMapper.CLI" -bb0 -bd > nul
+REM if %ERRORLEVEL% NEQ 0 goto error
+REM echo.
+
+REM echo Packaging standalone GUI
+REM 7z a "%~dp0\publish\RB4InstrumentMapper.GUI-x64.zip" "%~dp0\RB4InstrumentMapper.GUI\bin\x64\Release\net472" -bb3 -bd -sse > nul
+REM if %ERRORLEVEL% NEQ 0 goto error
+REM 7z rn "%~dp0\publish\RB4InstrumentMapper.GUI-x64.zip" "net472" "RB4InstrumentMapper.GUI" -bb0 -bd > nul
+REM if %ERRORLEVEL% NEQ 0 goto error
+REM echo.
+
+echo Copying installer package
+copy "%~dp0\RB4InstrumentMapper.Installer\bin\x64\Release\RB4InstrumentMapper.Installer.exe" "%~dp0\publish\RB4InstrumentMapperInstaller-x64.exe"
+if %ERRORLEVEL% NEQ 0 goto error
+
+exit
+
+:error
+echo.
+echo Build failed.
+exit