mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 11:33:06 +00:00
TODO: Needs a LimelightCryptoProvider implementation for each platform to work. Untested (and probably broken) on Android. Needs more testing in general, especially in corner cases.
113 lines
3.0 KiB
Java
113 lines
3.0 KiB
Java
package com.limelight.nvstream.input;
|
|
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.net.InetAddress;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.Socket;
|
|
import java.security.InvalidAlgorithmParameterException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.Arrays;
|
|
|
|
import javax.crypto.Cipher;
|
|
import javax.crypto.NoSuchPaddingException;
|
|
import javax.crypto.SecretKey;
|
|
import javax.crypto.spec.IvParameterSpec;
|
|
|
|
public class NvController {
|
|
|
|
public final static int PORT = 35043;
|
|
|
|
public final static int CONTROLLER_TIMEOUT = 3000;
|
|
|
|
private InetAddress host;
|
|
private Socket s;
|
|
private OutputStream out;
|
|
private Cipher riCipher;
|
|
|
|
|
|
private final static byte[] ENCRYPTED_HEADER = new byte[] {0x00, 0x00, 0x00, 0x20};
|
|
|
|
public NvController(InetAddress host, SecretKey riKey)
|
|
{
|
|
this.host = host;
|
|
try {
|
|
// This cipher is guaranteed to be supported
|
|
this.riCipher = Cipher.getInstance("AES/CBC/NoPadding");
|
|
this.riCipher.init(Cipher.ENCRYPT_MODE, riKey, new IvParameterSpec(new byte[16]));
|
|
} catch (NoSuchAlgorithmException e) {
|
|
e.printStackTrace();
|
|
} catch (NoSuchPaddingException e) {
|
|
e.printStackTrace();
|
|
} catch (InvalidKeyException e) {
|
|
e.printStackTrace();
|
|
} catch (InvalidAlgorithmParameterException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public void initialize() throws IOException
|
|
{
|
|
s = new Socket();
|
|
s.connect(new InetSocketAddress(host, PORT), CONTROLLER_TIMEOUT);
|
|
s.setTcpNoDelay(true);
|
|
out = s.getOutputStream();
|
|
}
|
|
|
|
public void close()
|
|
{
|
|
try {
|
|
s.close();
|
|
} catch (IOException e) {}
|
|
}
|
|
|
|
private byte[] encryptAesInputData(byte[] data) throws Exception {
|
|
// Input data is rounded to units of 32 bytes
|
|
byte[] blockRoundedData = Arrays.copyOf(data, 32);
|
|
return riCipher.update(blockRoundedData);
|
|
}
|
|
|
|
private void sendPacket(InputPacket packet) throws IOException {
|
|
out.write(ENCRYPTED_HEADER);
|
|
byte[] encryptedInput;
|
|
try {
|
|
encryptedInput = encryptAesInputData(packet.toWire());
|
|
} catch (Exception e) {
|
|
// Should never happen
|
|
e.printStackTrace();
|
|
return;
|
|
}
|
|
out.write(encryptedInput);
|
|
out.flush();
|
|
}
|
|
|
|
public void sendControllerInput(short buttonFlags, byte leftTrigger, byte rightTrigger,
|
|
short leftStickX, short leftStickY, short rightStickX, short rightStickY) throws IOException
|
|
{
|
|
sendPacket(new ControllerPacket(buttonFlags, leftTrigger,
|
|
rightTrigger, leftStickX, leftStickY,
|
|
rightStickX, rightStickY));
|
|
}
|
|
|
|
public void sendMouseButtonDown(byte mouseButton) throws IOException
|
|
{
|
|
sendPacket(new MouseButtonPacket(true, mouseButton));
|
|
}
|
|
|
|
public void sendMouseButtonUp(byte mouseButton) throws IOException
|
|
{
|
|
sendPacket(new MouseButtonPacket(false, mouseButton));
|
|
}
|
|
|
|
public void sendMouseMove(short deltaX, short deltaY) throws IOException
|
|
{
|
|
sendPacket(new MouseMovePacket(deltaX, deltaY));
|
|
}
|
|
|
|
public void sendKeyboardInput(short keyMap, byte keyDirection, byte modifier) throws IOException
|
|
{
|
|
sendPacket(new KeyboardPacket(keyMap, keyDirection, modifier));
|
|
}
|
|
}
|