Add sysbot logic for remote control

This commit is contained in:
Kurt
2020-03-31 19:22:42 -07:00
parent 7bcfe788a2
commit b3e8b1b1fb
7 changed files with 248 additions and 0 deletions

42
NHSE.Injection/Decoder.cs Normal file
View File

@@ -0,0 +1,42 @@
using System;
namespace NHSE.Injection
{
public static class Decoder
{
private static bool IsNum(char c) => (uint)(c - '0') <= 9;
private static bool IsHexUpper(char c) => (uint)(c - 'A') <= 5;
public static byte[] ConvertHexByteStringToBytes(byte[] bytes)
{
var dest = new byte[bytes.Length / 2];
for (int i = 0; i < dest.Length; i++)
{
int ofs = i * 2;
var _0 = (char)bytes[ofs + 0];
var _1 = (char)bytes[ofs + 1];
dest[i] = DecodeTuple(_0, _1);
}
return dest;
}
private static byte DecodeTuple(char _0, char _1)
{
byte result;
if (IsNum(_0))
result = (byte)((_0 - '0') << 4);
else if (IsHexUpper(_0))
result = (byte)((_0 - 'A' + 10) << 4);
else
throw new ArgumentOutOfRangeException(nameof(_0));
if (IsNum(_1))
result |= (byte)(_1 - '0');
else if (IsHexUpper(_1))
result |= (byte)(_1 - 'A' + 10);
else
throw new ArgumentOutOfRangeException(nameof(_1));
return result;
}
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
<LangVersion>8</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,27 @@
namespace NHSE.Injection
{
/// <summary>
/// Controller Buttons
/// </summary>
public enum SwitchButton
{
A,
B,
X,
Y,
RSTICK,
LSTICK,
L,
R,
ZL,
ZR,
PLUS,
MINUS,
DUP,
DDOWN,
DLEFT,
DRIGHT,
HOME,
CAPTURE,
}
}

View File

@@ -0,0 +1,70 @@
using System.Linq;
using System.Text;
namespace NHSE.Injection
{
/// <summary>
/// Encodes commands for a <see cref="SysBot"/> to be sent as a <see cref="byte"/> array.
/// </summary>
public static class SwitchCommand
{
private static readonly Encoding Encoder = Encoding.UTF8;
private static byte[] Encode(string command) => Encoder.GetBytes(command + "\r\n");
/// <summary>
/// Removes the virtual controller from the bot. Allows physical controllers to control manually.
/// </summary>
/// <returns>Encoded command bytes</returns>
public static byte[] DetachController() => Encode("detachController");
/// <summary>
/// Presses and releases a <see cref="SwitchButton"/> for 50ms.
/// </summary>
/// <param name="button">Button to click.</param>
/// <remarks>Press &amp; Release timing is performed by the console automatically.</remarks>
/// <returns>Encoded command bytes</returns>
public static byte[] Click(SwitchButton button) => Encode($"click {button}");
/// <summary>
/// Presses and does NOT release a <see cref="SwitchButton"/>.
/// </summary>
/// <param name="button">Button to hold.</param>
/// <returns>Encoded command bytes</returns>
public static byte[] Hold(SwitchButton button) => Encode($"press {button}");
/// <summary>
/// Releases the held <see cref="SwitchButton"/>.
/// </summary>
/// <param name="button">Button to release.</param>
/// <returns>Encoded command bytes</returns>
public static byte[] Release(SwitchButton button) => Encode($"release {button}");
/// <summary>
/// Sets the specified <see cref="stick"/> to the desired <see cref="x"/> and <see cref="y"/> positions.
/// </summary>
/// <returns>Encoded command bytes</returns>
public static byte[] SetStick(SwitchStick stick, int x, int y) => Encode($"setStick {stick} {x} {y}");
/// <summary>
/// Resets the specified <see cref="stick"/> to (0,0)
/// </summary>
/// <returns>Encoded command bytes</returns>
public static byte[] ResetStick(SwitchStick stick) => SetStick(stick, 0, 0);
/// <summary>
/// Requests the Bot to send <see cref="count"/> bytes from <see cref="offset"/>.
/// </summary>
/// <param name="offset">Address of the data</param>
/// <param name="count">Amount of bytes</param>
/// <returns>Encoded command bytes</returns>
public static byte[] Peek(uint offset, int count) => Encode($"peek 0x{offset:X8} {count}");
/// <summary>
/// Sends the Bot <see cref="data"/> to be written to <see cref="offset"/>.
/// </summary>
/// <param name="offset">Address of the data</param>
/// <param name="data">Data to write</param>
/// <returns>Encoded command bytes</returns>
public static byte[] Poke(uint offset, byte[] data) => Encode($"poke 0x{offset:X8} 0x{string.Concat(data.Select(z => $"{z:X2}"))}");
}
}

View File

@@ -0,0 +1,11 @@
namespace NHSE.Injection
{
/// <summary>
/// Controller Stick differentiation
/// </summary>
public enum SwitchStick
{
LEFT,
RIGHT,
}
}

83
NHSE.Injection/SysBot.cs Normal file
View File

@@ -0,0 +1,83 @@
using System.Net.Sockets;
using System.Threading;
namespace NHSE.Injection
{
public class SysBot
{
public string IP = "192.168.1.65";
public int Port = 6000;
public Socket Connection = new Socket(SocketType.Stream, ProtocolType.Tcp);
public bool Connected;
private readonly object _sync = new object();
public void Connect(string ip, int port)
{
IP = ip;
Port = port;
lock (_sync)
{
Connection = new Socket(SocketType.Stream, ProtocolType.Tcp);
Connection.Connect(IP, Port);
Connected = true;
}
}
public void Disconnect()
{
lock (_sync)
{
Connection.Disconnect(false);
Connected = false;
}
}
private int ReadInternal(byte[] buffer)
{
int br = Connection.Receive(buffer, 0, 1, SocketFlags.None);
while (buffer[br - 1] != (byte)'\n')
br += Connection.Receive(buffer, br, 1, SocketFlags.None);
return br;
}
private int SendInternal(byte[] buffer) => Connection.Send(buffer);
public int Read(byte[] buffer)
{
lock (_sync)
return ReadInternal(buffer);
}
public byte[] ReadBytes(uint offset, int length)
{
lock (_sync)
{
var cmd = SwitchCommand.Peek(offset, length);
SendInternal(cmd);
// give it time to push data back
Thread.Sleep((length / 256) + 100);
var buffer = new byte[(length * 2) + 1];
var _ = ReadInternal(buffer);
return Decoder.ConvertHexByteStringToBytes(buffer);
}
}
public void WriteBytes(byte[] data, uint offset)
{
lock (_sync)
{
SendInternal(SwitchCommand.Poke(offset, data));
// give it time to push data back
Thread.Sleep((data.Length / 256) + 100);
}
}
}
public enum InjectionType
{
Pouch,
}
}

View File

@@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHSE.Parsing", "NHSE.Parsin
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHSE.Tests", "NHSE.Tests\NHSE.Tests.csproj", "{AD3412A3-74EA-443D-BB07-B36E39D5166F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHSE.Injection", "NHSE.Injection\NHSE.Injection.csproj", "{319E810B-A516-480E-88BD-F648222344D3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -39,6 +41,10 @@ Global
{AD3412A3-74EA-443D-BB07-B36E39D5166F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD3412A3-74EA-443D-BB07-B36E39D5166F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD3412A3-74EA-443D-BB07-B36E39D5166F}.Release|Any CPU.Build.0 = Release|Any CPU
{319E810B-A516-480E-88BD-F648222344D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{319E810B-A516-480E-88BD-F648222344D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{319E810B-A516-480E-88BD-F648222344D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{319E810B-A516-480E-88BD-F648222344D3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE