Added Rumble support (currently XInput only), changed protocol version, code cleanup

Updated the code conventions.xml!
This commit is contained in:
Maschell 2017-04-13 15:32:55 +02:00
parent f5f1d42d1b
commit 70b5da2206
19 changed files with 315 additions and 191 deletions

View File

@ -26,7 +26,7 @@
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/> <setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/> <setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/> <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/> <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>

View File

@ -11,28 +11,24 @@ import javax.swing.JOptionPane;
* <p> * <p>
* v[(2), 2015-11-13 13:00 UTC] * v[(2), 2015-11-13 13:00 UTC]
* <p> * <p>
* One static method call will start a new instance of *THIS* application in the console and will EXIT the current * One static method call will start a new instance of *THIS* application in the console and will EXIT the current instance. SO FAR ONLY WORKS ON WINDOWS! Users
* instance. SO FAR ONLY WORKS ON WINDOWS! Users of other systems need to assist here. The methods are all in place. * of other systems need to assist here. The methods are all in place.
*/ */
final public class AutoRunFromConsole { final public class AutoRunFromConsole {
final private static String FAILMESSAGE_TITLE = "Please run in console."; final private static String FAILMESSAGE_TITLE = "Please run in console.";
final private static String FAILMESSAGE_BODY = "This application must be run in the console (or \"command box\").\n\nIn there, you have to type:\n\njava -jar nameofprogram.jar"; final private static String FAILMESSAGE_BODY = "This application must be run in the console (or \"command box\").\n\nIn there, you have to type:\n\njava -jar nameofprogram.jar";
static void showFailMessageAndExit() { static void showFailMessageAndExit() {
JOptionPane.showMessageDialog(null, FAILMESSAGE_BODY, FAILMESSAGE_TITLE, JOptionPane.INFORMATION_MESSAGE); JOptionPane.showMessageDialog(null, FAILMESSAGE_BODY, FAILMESSAGE_TITLE, JOptionPane.INFORMATION_MESSAGE);
System.exit(0); System.exit(0);
} }
private enum OSType { private enum OSType {
UNDETERMINED, WINDOWS, LINUX, MACOS UNDETERMINED, WINDOWS, LINUX, MACOS
} }
private static OSType getOsType() { private static OSType getOsType() {
// final String osName = System.getProperty("os.name"); // final String osName = System.getProperty("os.name");
@ -54,60 +50,55 @@ final public class AutoRunFromConsole {
return OSType.UNDETERMINED; return OSType.UNDETERMINED;
} }
/** /**
* Checks if the program is currently running in console, and if not, starts the program from console and EXITS this * Checks if the program is currently running in console, and if not, starts the program from console and EXITS this instance of the program. Should be (one
* instance of the program. Should be (one of) the first calls in your program. * of) the first calls in your program.
* <p> * <p>
* This is the less safe variant of the method: To check if you're currently in the IDE, it just tries to find the * This is the less safe variant of the method: To check if you're currently in the IDE, it just tries to find the executable name and if it exists in the
* executable name and if it exists in the current path. This should word perfectly at all times in IntelliJ - I * current path. This should word perfectly at all times in IntelliJ - I don't know what values getExecutableName() returns inside Eclipse, but I suspect it
* don't know what values getExecutableName() returns inside Eclipse, but I suspect it will work just as well. * will work just as well.
* <p> * <p>
* It's also less safe because you can't give a fallback executable name, but I believe it should do the trick in * It's also less safe because you can't give a fallback executable name, but I believe it should do the trick in all situations.
* all situations.
* <p> * <p>
* If this is used on a system other than Windows, a message box is shown telling the user to start the program from * If this is used on a system other than Windows, a message box is shown telling the user to start the program from the console. BECAUSE I DON'T KNOW HOW
* the console. BECAUSE I DON'T KNOW HOW TO OPEN A CONSOLE ON OTHER SYSTEMS. SEE startExecutableInConsole(); * TO OPEN A CONSOLE ON OTHER SYSTEMS. SEE startExecutableInConsole();
*/ */
public static void runYourselfInConsole(final boolean stayOpenAfterEnd) { public static void runYourselfInConsole(final boolean stayOpenAfterEnd) {
runYourselfInConsole(false, stayOpenAfterEnd, null, null); runYourselfInConsole(false, stayOpenAfterEnd, null, null);
} }
/** /**
* Checks if the program is currently running in console, and if not, starts the program from console and EXITS this * Checks if the program is currently running in console, and if not, starts the program from console and EXITS this instance of the program. Should be (one
* instance of the program. Should be (one of) the first calls in your program. * of) the first calls in your program.
* <p> * <p>
* This is the safer variant of the method: The first command line argument GIVEN BY THE IDE'S RUN CONFIGURATION * This is the safer variant of the method: The first command line argument GIVEN BY THE IDE'S RUN CONFIGURATION should be "ide" (Case is ignored.), which
* should be "ide" (Case is ignored.), which this method will use to determine if it's running from the IDE. * this method will use to determine if it's running from the IDE.
* <p> * <p>
* It is also safer because you can give a fallback executable name in case getExecutableName() could not determine * It is also safer because you can give a fallback executable name in case getExecutableName() could not determine it.
* it.
* <p> * <p>
* Ultimately, it is safer because if the executable could not be determined, it shows a message box telling the * Ultimately, it is safer because if the executable could not be determined, it shows a message box telling the user to start the program from the console.
* user to start the program from the console.
* <p> * <p>
* You will probably never make use of this variant. It's meant to be a solution if all else seems to fail (e.g. * You will probably never make use of this variant. It's meant to be a solution if all else seems to fail (e.g. customer calls and you need a quick fix).
* customer calls and you need a quick fix).
* <p> * <p>
* If this is used on a system other than Windows, a message box is shown telling the user to start the program from * If this is used on a system other than Windows, a message box is shown telling the user to start the program from the console. BECAUSE I DON'T KNOW HOW
* the console. BECAUSE I DON'T KNOW HOW TO OPEN A CONSOLE ON OTHER SYSTEMS. SEE startExecutableInConsole(); * TO OPEN A CONSOLE ON OTHER SYSTEMS. SEE startExecutableInConsole();
* *
* @param psvmArguments The arguments given to the main method. * @param psvmArguments
* @param fallbackExecutableName Can be null. In case getExecutableName() can't determine the proper name, the * The arguments given to the main method.
* fallback is used. * @param fallbackExecutableName
* Can be null. In case getExecutableName() can't determine the proper name, the fallback is used.
*/ */
public static void runYourselfInConsole(final String[] psvmArguments, final String fallbackExecutableName, final boolean stayOpenAfterEnd) { public static void runYourselfInConsole(final String[] psvmArguments, final String fallbackExecutableName, final boolean stayOpenAfterEnd) {
runYourselfInConsole(true, stayOpenAfterEnd, psvmArguments, fallbackExecutableName); runYourselfInConsole(true, stayOpenAfterEnd, psvmArguments, fallbackExecutableName);
} }
/** /**
* see the other two methods * see the other two methods
*/ */
private static void runYourselfInConsole(final boolean useSaferApproach, final boolean stayOpenAfterEnd, final String[] psvmArguments, final String fallbackExecutableName) { private static void runYourselfInConsole(final boolean useSaferApproach, final boolean stayOpenAfterEnd, final String[] psvmArguments,
final String fallbackExecutableName) {
String executableName = getExecutableName(fallbackExecutableName); String executableName = getExecutableName(fallbackExecutableName);
@ -135,18 +126,18 @@ final public class AutoRunFromConsole {
System.exit(0); System.exit(0);
} }
/** /**
* Opens a console window and starts the Java executable there. * Opens a console window and starts the Java executable there.
* <p> * <p>
* If this is used on a system other than Windows, a message box is shown telling the user to start the program from * If this is used on a system other than Windows, a message box is shown telling the user to start the program from the console. BECAUSE I DON'T KNOW HOW
* the console. BECAUSE I DON'T KNOW HOW TO OPEN A CONSOLE ON OTHER SYSTEMS. * TO OPEN A CONSOLE ON OTHER SYSTEMS.
* *
* @param executableName the full file name of the executable (without path) * @param executableName
* @param stayOpenAfterEnd If true (and if someone can figure out the necessary parameters for other systems than * the full file name of the executable (without path)
* Windows), the console will not close once the executable has terminated. This is useful * @param stayOpenAfterEnd
* e.g. if you want to give some kind of bye bye message because you actually assumed that * If true (and if someone can figure out the necessary parameters for other systems than Windows), the console will not close once the
* people start the program from console manually. * executable has terminated. This is useful e.g. if you want to give some kind of bye bye message because you actually assumed that people start
* the program from console manually.
*/ */
private static void startExecutableInConsole(final String executableName, final boolean stayOpenAfterEnd) { private static void startExecutableInConsole(final String executableName, final boolean stayOpenAfterEnd) {
@ -157,9 +148,9 @@ final public class AutoRunFromConsole {
break; break;
case WINDOWS: case WINDOWS:
if (stayOpenAfterEnd) { if (stayOpenAfterEnd) {
launchString = "cmd /c start cmd /k java -jar \"" + executableName+"\""; // No, using /k directly here DOES NOT do the trick. launchString = "cmd /c start cmd /k java -jar \"" + executableName + "\""; // No, using /k directly here DOES NOT do the trick.
} else { } else {
launchString = "cmd /c start java -jar \"" + executableName+"\""; launchString = "cmd /c start java -jar \"" + executableName + "\"";
} }
break; break;
case LINUX: case LINUX:
@ -180,35 +171,31 @@ final public class AutoRunFromConsole {
} }
} }
/** /**
* @param args the args as given to PSVM * @param args
* @return whether the first command line argument was "ide" (ignoring case). Don't forget to change your IDE's run * the args as given to PSVM
* configuration accordingly. * @return whether the first command line argument was "ide" (ignoring case). Don't forget to change your IDE's run configuration accordingly.
*/ */
private static boolean isRunFromIDE(final String[] args) { private static boolean isRunFromIDE(final String[] args) {
return args != null && args.length > 0 && args[0].equalsIgnoreCase("ide"); return args != null && args.length > 0 && args[0].equalsIgnoreCase("ide");
} }
/** /**
* @return if System.console() is available. DOES NOT WORK properly from IDE, will return false then even though it * @return if System.console() is available. DOES NOT WORK properly from IDE, will return false then even though it should be true. Use isRunFromIDE or
* should be true. Use isRunFromIDE or other means additionally. * other means additionally.
*/ */
private static boolean isRunningInConsole() { private static boolean isRunningInConsole() {
return System.console() != null; return System.console() != null;
} }
/** /**
* @param fallbackExecutableName Can be null. In the very unlikely case this method can't determine the executable, * @param fallbackExecutableName
* the fallback will also be checked. But if the fallback also doesn't exist AS A FILE * Can be null. In the very unlikely case this method can't determine the executable, the fallback will also be checked. But if the fallback also
* in the CURRENT path, null will be returned regardless, even if you're sure that * doesn't exist AS A FILE in the CURRENT path, null will be returned regardless, even if you're sure that your fallback should be correct.
* your fallback should be correct. * @return the name of the running jar file, OR NULL if it could not be determined (which should be a certainty while in IDE, hence can be abused for
* @return the name of the running jar file, OR NULL if it could not be determined (which should be a certainty * determining that).
* while in IDE, hence can be abused for determining that).
*/ */
public static String getExecutableName(final String fallbackExecutableName) { public static String getExecutableName(final String fallbackExecutableName) {
@ -228,19 +215,16 @@ final public class AutoRunFromConsole {
} }
} }
// APPROACH 2 - QUERY SYSTEM PROPERTIES // APPROACH 2 - QUERY SYSTEM PROPERTIES
final Properties properties = System.getProperties(); final Properties properties = System.getProperties();
final String executableNameFromJavaClassPathProperty = properties.getProperty("java.class.path"); final String executableNameFromJavaClassPathProperty = properties.getProperty("java.class.path");
final String executableNameFromSunJavaCommandProperty = properties.getProperty("sun.java.command"); final String executableNameFromSunJavaCommandProperty = properties.getProperty("sun.java.command");
// System.out.println("\n\nexecutableNameFromClass:\n" + executableNameFromClass); // System.out.println("\n\nexecutableNameFromClass:\n" + executableNameFromClass);
// System.out.println("\n\nexecutableNameFromJavaClassPathProperty:\n" + executableNameFromJavaClassPathProperty); // System.out.println("\n\nexecutableNameFromJavaClassPathProperty:\n" + executableNameFromJavaClassPathProperty);
// System.out.println("\n\nexecutableNameFromSunJavaCommandProperty:\n" + executableNameFromSunJavaCommandProperty); // System.out.println("\n\nexecutableNameFromSunJavaCommandProperty:\n" + executableNameFromSunJavaCommandProperty);
// System.out.println("\n\nfallbackExecutableName:\n" + fallbackExecutableName); // System.out.println("\n\nfallbackExecutableName:\n" + fallbackExecutableName);
if (isThisProbablyTheExecutable(executableNameFromClass)) { if (isThisProbablyTheExecutable(executableNameFromClass)) {
return executableNameFromClass; return executableNameFromClass;
} }
@ -260,11 +244,10 @@ final public class AutoRunFromConsole {
return null; return null;
} }
/** /**
* @param candidateName suspected name of the running java executable * @param candidateName
* @return if name is not null, ends with ".jar" (Case is ignored.), and points to a FILE existing in the CURRENT * suspected name of the running java executable
* directory. * @return if name is not null, ends with ".jar" (Case is ignored.), and points to a FILE existing in the CURRENT directory.
*/ */
private static boolean isThisProbablyTheExecutable(final String candidateName) { private static boolean isThisProbablyTheExecutable(final String candidateName) {

View File

@ -26,7 +26,7 @@ public class Main {
Utilities.sleep(1000); Utilities.sleep(1000);
for(Controller c : ControllerManager.getAttachedControllers()){ for (Controller c : ControllerManager.getAttachedControllers()) {
c.setActive(true); c.setActive(true);
} }
@ -39,13 +39,14 @@ public class Main {
public void run() { public void run() {
while (true) { while (true) {
boolean attached = false; boolean attached = false;
for(Controller c : ControllerManager.getAttachedControllers()){ for (Controller c : ControllerManager.getAttachedControllers()) {
if(c.isActive()){ if (c.isActive()) {
attached = true; attached = true;
System.out.print(String.format("VID: %04X PID %04X", c.getVID(),c.getPID()) +" data: " + Utilities.ByteArrayToString(c.getLatestData()) + " | " ); System.out.print(String.format("VID: %04X PID %04X", c.getVID(), c.getPID()) + " data: "
+ Utilities.ByteArrayToString(c.getLatestData()) + " | ");
} }
} }
if(attached) System.out.print("\r"); if (attached) System.out.print("\r");
Utilities.sleep(15); Utilities.sleep(15);
} }
} }

View File

@ -120,6 +120,6 @@ public class HidController extends Controller {
} }
String name = getHidDevice().getProductString(); String name = getHidDevice().getProductString();
return String.format("%s (0x%04X:0x%04X) on %s",(name != null) ? name: "USB HID", getVID(),getPID(),getIdentifier()); return String.format("%s (0x%04X:0x%04X) on %s", (name != null) ? name : "USB HID", getVID(), getPID(), getIdentifier());
} }
} }

View File

@ -30,6 +30,6 @@ public class XInput13Controller extends XInputController {
@Override @Override
public String getInfoText() { public String getInfoText() {
return String.format("XInput 1.3 (0x%04X:0x%04X) on ", getVID(),getPID()) + getIdentifier(); return String.format("XInput 1.3 (0x%04X:0x%04X) on ", getVID(), getPID()) + getIdentifier();
} }
} }

View File

@ -30,6 +30,6 @@ public class XInput14Controller extends XInputController {
@Override @Override
public String getInfoText() { public String getInfoText() {
return String.format("XInput 1.4 (0x%04X:0x%04X) on ", getVID(),getPID()) + getIdentifier(); return String.format("XInput 1.4 (0x%04X:0x%04X) on ", getVID(), getPID()) + getIdentifier();
} }
} }

View File

@ -35,6 +35,7 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.java.Log; import lombok.extern.java.Log;
import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException;
import net.ash.HIDToVPADNetworkClient.util.Settings;
import net.ash.HIDToVPADNetworkClient.util.Utilities; import net.ash.HIDToVPADNetworkClient.util.Utilities;
@Log @Log
@ -116,6 +117,15 @@ public class XInputController extends Controller {
buttonState |= axesDataShoulderButtons << 16; buttonState |= axesDataShoulderButtons << 16;
data.putInt(axesData).putInt(buttonState); data.putInt(axesData).putInt(buttonState);
if (isRumble()) {
int strength = Settings.RUMBLE_STRENGTH;
if (strength < 0) strength = 0;
if (strength > 100) strength = 100;
int value = (int) (65535 * (Settings.RUMBLE_STRENGTH / 100.0));
device.setVibration(value, value);
} else {
device.setVibration(0, 0);
}
return (data.array()); return (data.array());
} }
return new byte[0]; return new byte[0];
@ -139,6 +149,6 @@ public class XInputController extends Controller {
@Override @Override
public String getInfoText() { public String getInfoText() {
return String.format("XInput (0x%04X:%0x04X) on ", getVID(),getPID()) + getIdentifier(); return String.format("XInput (0x%04X:%0x04X) on ", getVID(), getPID()) + getIdentifier();
} }
} }

View File

@ -207,7 +207,7 @@ public class GuiOptionsWindow extends JPanel {
cBox.setSelected(Settings.ControllerFiltering.getFilterState(type)); cBox.setSelected(Settings.ControllerFiltering.getFilterState(type));
} }
//I can't believe I didn't figure this out for GuiControllerList // I can't believe I didn't figure this out for GuiControllerList
@Override @Override
public Dimension getMaximumSize() { public Dimension getMaximumSize() {
return new Dimension(Integer.MAX_VALUE, getPreferredSize().height); return new Dimension(Integer.MAX_VALUE, getPreferredSize().height);
@ -256,10 +256,13 @@ public class GuiOptionsWindow extends JPanel {
private abstract class Tab extends JPanel { private abstract class Tab extends JPanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public abstract void updateTab(); public abstract void updateTab();
public Tab(LayoutManager l) { public Tab(LayoutManager l) {
super(l); super(l);
} }
public Tab() { public Tab() {
super(); super();
} }

View File

@ -72,7 +72,8 @@ public class HidManager {
public static boolean isGamepad(HidDevice info) { public static boolean isGamepad(HidDevice info) {
if (info == null) return false; if (info == null) return false;
short usage = info.getUsageID(); short usage = info.getUsageID();
return (info.getProductString().toLowerCase().contains("gamepad") || usage == 0x05 || usage == 0x04 || isNintendoController(info) || isPlaystationController(info)); return (info.getProductString().toLowerCase().contains("gamepad") || usage == 0x05 || usage == 0x04 || isNintendoController(info)
|| isPlaystationController(info));
} }
public static boolean isKeyboard(HidDevice info) { public static boolean isKeyboard(HidDevice info) {

View File

@ -73,7 +73,8 @@ class Hid4JavaHidDevice implements HidDevice {
@Override @Override
public String toString() { public String toString() {
return "Hid4JavaHidDevice [vid= " + getVendorId() + ", pid= " + getProductId() + ", usage= " + String.format("%04X:%04X", getUsagePage(), getUsageID()) + ", data=" + Arrays.toString(data) + "]"; return "Hid4JavaHidDevice [vid= " + getVendorId() + ", pid= " + getProductId() + ", usage= " + String.format("%04X:%04X", getUsagePage(), getUsageID())
+ ", data=" + Arrays.toString(data) + "]";
} }
@Override @Override

View File

@ -62,6 +62,7 @@ class PureJavaHidDevice implements HidDevice, InputReportListener {
} }
private static boolean hasShownUdevErrorMessage = false; private static boolean hasShownUdevErrorMessage = false;
@Override @Override
public boolean open() { public boolean open() {
boolean result = true; boolean result = true;
@ -118,7 +119,7 @@ class PureJavaHidDevice implements HidDevice, InputReportListener {
@Override @Override
public String toString() { public String toString() {
return "PureJavaHidDevice [vid= " + String.format("%04X", getVendorId()) + ", pid= " + String.format("%04X", getProductId()) + ", path= " + getPath().trim() return "PureJavaHidDevice [vid= " + String.format("%04X", getVendorId()) + ", pid= " + String.format("%04X", getProductId()) + ", path= "
+ ", usage= " + String.format("%04X:%04X", getUsagePage(), getUsageID()) + ", data=" + Arrays.toString(currentData) + "]"; + getPath().trim() + ", usage= " + String.format("%04X:%04X", getUsagePage(), getUsageID()) + ", data=" + Arrays.toString(currentData) + "]";
} }
} }

View File

@ -24,6 +24,7 @@ package net.ash.HIDToVPADNetworkClient.manager;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -195,9 +196,14 @@ public final class ControllerManager {
XInputDevice device; XInputDevice device;
try { try {
device = XInputDevice.getDeviceFor(i); device = XInputDevice.getDeviceFor(i);
try {
if (device.poll() && device.isConnected()) { // Check if it is this controller is connected if (device.poll() && device.isConnected()) { // Check if it is this controller is connected
result.put(XInputController.XINPUT_INDENTIFER + i, type); result.put(XInputController.XINPUT_INDENTIFER + i, type);
} }
} catch (BufferUnderflowException e) {
//
log.info("XInput error.");
}
} catch (XInputNotLoadedException e) { } catch (XInputNotLoadedException e) {
// This shouln't happen? // This shouln't happen?
e.printStackTrace(); e.printStackTrace();

View File

@ -74,6 +74,7 @@ public final class NetworkManager implements Runnable {
@Override @Override
public void run() { public void run() {
int i = 0; int i = 0;
new Thread(UDPServer.getInstance(), "UDP Server").start();
while (true) { while (true) {
proccessCommands(); proccessCommands();
Utilities.sleep(Settings.PROCESS_CMD_INTERVAL); Utilities.sleep(Settings.PROCESS_CMD_INTERVAL);

View File

@ -25,24 +25,29 @@ import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List; import java.util.List;
import lombok.extern.java.Log; import lombok.extern.java.Log;
import net.ash.HIDToVPADNetworkClient.controller.Controller;
import net.ash.HIDToVPADNetworkClient.manager.ActiveControllerManager; import net.ash.HIDToVPADNetworkClient.manager.ActiveControllerManager;
import net.ash.HIDToVPADNetworkClient.util.MessageBox; import net.ash.HIDToVPADNetworkClient.util.MessageBox;
import net.ash.HIDToVPADNetworkClient.util.MessageBoxManager; import net.ash.HIDToVPADNetworkClient.util.MessageBoxManager;
import net.ash.HIDToVPADNetworkClient.util.Utilities;
@Log @Log
final class Protocol { final class Protocol {
private static ProtocolVersion currentProtocol = ProtocolVersion.MY_VERSION; public static ProtocolVersion currentProtocol = ProtocolVersion.MY_VERSION;
static final int TCP_PORT = 8112; static final int TCP_PORT = 8112;
static final int UDP_PORT = 8113; static final int UDP_PORT = 8113;
static final int UDP_CLIENT_PORT = 8114;
static final byte TCP_HANDSHAKE_VERSION_1 = 0x12; static final byte TCP_HANDSHAKE_VERSION_1 = 0x12;
static final byte TCP_HANDSHAKE_VERSION_2 = 0x13; static final byte TCP_HANDSHAKE_VERSION_2 = 0x13;
static final byte TCP_HANDSHAKE_VERSION_3 = 0x14;
static final byte TCP_HANDSHAKE_ABORT = 0x30; static final byte TCP_HANDSHAKE_ABORT = 0x30;
static final byte TCP_HANDSHAKE = TCP_HANDSHAKE_VERSION_2; // default version. static final byte TCP_HANDSHAKE = TCP_HANDSHAKE_VERSION_3; // default version.
static final byte TCP_SAME_CLIENT = 0x20; static final byte TCP_SAME_CLIENT = 0x20;
static final byte TCP_NEW_CLIENT = 0x21; static final byte TCP_NEW_CLIENT = 0x21;
@ -58,6 +63,7 @@ final class Protocol {
static final byte TCP_CMD_ATTACH_CONFIG_NOT_FOUND = (byte) 0xE1; static final byte TCP_CMD_ATTACH_CONFIG_NOT_FOUND = (byte) 0xE1;
static final byte TCP_CMD_ATTACH_USERDATA_OKAY = (byte) 0xE8; static final byte TCP_CMD_ATTACH_USERDATA_OKAY = (byte) 0xE8;
static final byte TCP_CMD_ATTACH_USERDATA_BAD = (byte) 0xE9; static final byte TCP_CMD_ATTACH_USERDATA_BAD = (byte) 0xE9;
private static boolean showVersionInfo = false;
private Protocol() { private Protocol() {
} }
@ -125,6 +131,9 @@ final class Protocol {
} else if (wiiuProtocolVersion == ProtocolVersion.Version2) { } else if (wiiuProtocolVersion == ProtocolVersion.Version2) {
// We want to do version 2! // We want to do version 2!
tcpClient.send(ProtocolVersion.Version2.getVersionByte()); tcpClient.send(ProtocolVersion.Version2.getVersionByte());
} else if (wiiuProtocolVersion == ProtocolVersion.Version3) {
// We want to do version 3!
tcpClient.send(ProtocolVersion.Version3.getVersionByte());
} }
if (wiiuProtocolVersion == ProtocolVersion.Version1) { if (wiiuProtocolVersion == ProtocolVersion.Version1) {
@ -151,6 +160,14 @@ final class Protocol {
if (resultHandshake == HandshakeReturnCode.GOOD_HANDSHAKE) { if (resultHandshake == HandshakeReturnCode.GOOD_HANDSHAKE) {
ActiveControllerManager.getInstance().attachAllActiveControllers(); ActiveControllerManager.getInstance().attachAllActiveControllers();
log.info("Handshake was successful! Using protocol version " + (currentProtocol.getVersionByte() - TCP_HANDSHAKE_VERSION_1 + 1)); log.info("Handshake was successful! Using protocol version " + (currentProtocol.getVersionByte() - TCP_HANDSHAKE_VERSION_1 + 1));
if (currentProtocol.getVersionByte() != ProtocolVersion.MY_VERSION.versionByte) {
String message = "Using an old network protocol. You may want to update this network client and HIDtoVPAD for the best experience.";
log.info(message);
if (!showVersionInfo) {
MessageBoxManager.addMessageBox(message, MessageBox.MESSAGE_INFO);
showVersionInfo = true;
}
}
return resultHandshake; return resultHandshake;
} else { } else {
log.info("[TCP] Handshaking failed"); log.info("[TCP] Handshaking failed");
@ -160,8 +177,12 @@ final class Protocol {
} }
public enum ProtocolVersion { public enum ProtocolVersion {
MY_VERSION((byte) TCP_HANDSHAKE), Version1((byte) TCP_HANDSHAKE_VERSION_1), Version2((byte) TCP_HANDSHAKE_VERSION_2), Abort( Abort((byte) TCP_HANDSHAKE_ABORT),
(byte) TCP_HANDSHAKE_ABORT), UNKOWN((byte) 0x00); UNKOWN((byte) 0x00),
Version1((byte) TCP_HANDSHAKE_VERSION_1),
Version2((byte) TCP_HANDSHAKE_VERSION_2),
Version3((byte) TCP_HANDSHAKE_VERSION_3),
MY_VERSION((byte) TCP_HANDSHAKE);
private final byte versionByte; private final byte versionByte;
private ProtocolVersion(byte versionByte) { private ProtocolVersion(byte versionByte) {
@ -174,6 +195,8 @@ final class Protocol {
return ProtocolVersion.Version1; return ProtocolVersion.Version1;
case TCP_HANDSHAKE_VERSION_2: case TCP_HANDSHAKE_VERSION_2:
return ProtocolVersion.Version2; return ProtocolVersion.Version2;
case TCP_HANDSHAKE_VERSION_3:
return ProtocolVersion.Version3;
case TCP_HANDSHAKE_ABORT: case TCP_HANDSHAKE_ABORT:
return ProtocolVersion.Abort; return ProtocolVersion.Abort;
default: default:
@ -186,4 +209,24 @@ final class Protocol {
} }
} }
public static void parseUDPServerData(byte[] data) {
if (currentProtocol.versionByte >= TCP_HANDSHAKE_VERSION_3) {
ByteBuffer buffer = ByteBuffer.wrap(data);
buffer.order(ByteOrder.BIG_ENDIAN);
byte cmd = buffer.get();
if (cmd == 0x01) {
int handle = buffer.getInt();
byte rumble = buffer.get();
Controller c = ActiveControllerManager.getInstance().getControllerByHIDHandle(handle);
if (c != null) {
if (rumble == 0x01) {
c.startRumble();
} else {
c.stopRumble();
}
}
}
}
}
} }

View File

@ -0,0 +1,66 @@
/*******************************************************************************
* Copyright (c) 2017 Ash (QuarkTheAwesome) & Maschell
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*******************************************************************************/
package net.ash.HIDToVPADNetworkClient.network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Arrays;
import lombok.extern.java.Log;
@Log
public final class UDPServer implements Runnable {
private final DatagramSocket sock;
private UDPServer() throws SocketException {
sock = new DatagramSocket(Protocol.UDP_CLIENT_PORT);
}
static UDPServer getInstance() {
UDPServer result = null;
try {
result = new UDPServer();
} catch (Exception e) {
// handle?
}
return result;
}
@Override
public void run() {
log.info("UDPServer running.");
byte[] receiveData = new byte[1400];
while (true) {
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
try {
sock.receive(receivePacket);
} catch (IOException e) {
continue;
}
byte[] data = Arrays.copyOf(receivePacket.getData(), receivePacket.getLength());
Protocol.parseUDPServerData(data);
}
}
}

View File

@ -47,6 +47,8 @@ public final class Settings {
public static final int DETECT_CONTROLLER_ACTIVE_INTERVAL = 100; public static final int DETECT_CONTROLLER_ACTIVE_INTERVAL = 100;
public static final int RUMBLE_STRENGTH = 50; // in % TODO: Create setting for this.
public static boolean SCAN_AUTOMATICALLY_FOR_CONTROLLERS = true; public static boolean SCAN_AUTOMATICALLY_FOR_CONTROLLERS = true;
public static boolean DEBUG_UDP_OUTPUT = false; public static boolean DEBUG_UDP_OUTPUT = false;
@ -197,46 +199,50 @@ public final class Settings {
} }
public enum Platform { public enum Platform {
LINUX (0x1), WINDOWS (0x2), MAC_OS_X (0x4), UNKNOWN (0x8); LINUX(0x1), WINDOWS(0x2), MAC_OS_X(0x4), UNKNOWN(0x8);
private int mask; private int mask;
private Platform(int mask) { private Platform(int mask) {
this.mask = mask; this.mask = mask;
} }
} }
//TODO rename this to something less nonsensical // TODO rename this to something less nonsensical
public static class ControllerFiltering { public static class ControllerFiltering {
public static enum Type { public static enum Type {
HIDGAMEPAD (0, "HID Gamepads", Platform.LINUX.mask | Platform.WINDOWS.mask | Platform.MAC_OS_X.mask), HIDGAMEPAD(0, "HID Gamepads", Platform.LINUX.mask | Platform.WINDOWS.mask | Platform.MAC_OS_X.mask),
XINPUT (5, "XInput controllers", Platform.WINDOWS.mask), XINPUT(5, "XInput controllers", Platform.WINDOWS.mask),
HIDKEYBOARD (1, "HID Keyboards", Platform.LINUX.mask | Platform.MAC_OS_X.mask), HIDKEYBOARD(1, "HID Keyboards", Platform.LINUX.mask | Platform.MAC_OS_X.mask),
HIDMOUSE (2, "HID Mice", Platform.LINUX.mask), HIDMOUSE(2, "HID Mice", Platform.LINUX.mask),
HIDOTHER (3, "Other HIDs", Platform.LINUX.mask | Platform.WINDOWS.mask | Platform.MAC_OS_X.mask), HIDOTHER(3, "Other HIDs", Platform.LINUX.mask | Platform.WINDOWS.mask | Platform.MAC_OS_X.mask),
LINUX (4, "Linux controllers", Platform.LINUX.mask), LINUX(4, "Linux controllers", Platform.LINUX.mask),;
;
private int index; private int index;
@Getter private String name; @Getter private String name;
private int platforms; private int platforms;
private Type(int index, String name, int platforms) { private Type(int index, String name, int platforms) {
this.index = index; this.index = index;
this.name = name; this.name = name;
this.platforms = platforms; this.platforms = platforms;
} }
public boolean isSupportedOnPlatform() { public boolean isSupportedOnPlatform() {
return (platforms & getPlattform().mask) != 0; return (platforms & getPlattform().mask) != 0;
} }
} }
private static boolean[] filterStates = new boolean[Type.values().length]; private static boolean[] filterStates = new boolean[Type.values().length];
public static String getFilterStates() { public static String getFilterStates() {
return Arrays.toString(filterStates); return Arrays.toString(filterStates);
} }
public static void loadFilterStates(String newFilterStates) { public static void loadFilterStates(String newFilterStates) {
boolean[] newFilterStatesParsed = Utilities.stringToBoolArray(newFilterStates); boolean[] newFilterStatesParsed = Utilities.stringToBoolArray(newFilterStates);
if (newFilterStatesParsed.length != filterStates.length) { if (newFilterStatesParsed.length != filterStates.length) {
//TODO handle changes in filtering more gracefully // TODO handle changes in filtering more gracefully
log.warning("Number of controller filters in config does not match reality, using defaults..."); log.warning("Number of controller filters in config does not match reality, using defaults...");
setDefaultFilterStates(); setDefaultFilterStates();
} else { } else {
@ -247,9 +253,11 @@ public final class Settings {
public static void setFilterState(Type filter, boolean state) { public static void setFilterState(Type filter, boolean state) {
filterStates[filter.index] = state; filterStates[filter.index] = state;
} }
public static boolean getFilterState(Type filter) { public static boolean getFilterState(Type filter) {
return filterStates[filter.index]; return filterStates[filter.index];
} }
public static void setDefaultFilterStates() { public static void setDefaultFilterStates() {
filterStates[Type.HIDGAMEPAD.index] = true; filterStates[Type.HIDGAMEPAD.index] = true;
filterStates[Type.HIDKEYBOARD.index] = false; filterStates[Type.HIDKEYBOARD.index] = false;

View File

@ -82,8 +82,8 @@ public final class Utilities {
} }
/** /**
* Arrays.toString(boolean[]) in reverse. * Arrays.toString(boolean[]) in reverse. https://stackoverflow.com/questions/456367/
* https://stackoverflow.com/questions/456367/ *
* @param string * @param string
* @return array * @return array
*/ */
@ -96,7 +96,7 @@ public final class Utilities {
return result; return result;
} }
public static String getStringFromInputStream(InputStream is) throws IOException{ public static String getStringFromInputStream(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int length; int length;