Commit 740de21f authored by nanahira's avatar nanahira

reverse ws

parent ecbc3fc0
Pipeline #43245 failed with stages
in 76 minutes and 33 seconds
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BotWrapper</RootNamespace>
<AssemblyName>Bot</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86'">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86'">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<StartupObject>BotWrapper.BotWrapper</StartupObject>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>WindBot.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="BotWrapper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="bot.conf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="WindBot.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BotWrapper</RootNamespace>
<AssemblyName>Bot</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86'">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86'">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<StartupObject>BotWrapper.BotWrapper</StartupObject>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>WindBot.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="BotWrapper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="bot.conf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="WindBot.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
-->
</Project>
\ No newline at end of file
using System;
using System.IO;
using System.Linq;
using System.Net;
using YGOSharp.Network;
using YGOSharp.Network.Enums;
using YGOSharp.Network.Utils;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using YGOSharp.Network;
using YGOSharp.Network.Enums;
using YGOSharp.Network.Utils;
namespace WindBot.Game
{
......@@ -17,60 +18,192 @@ namespace WindBot.Game
public string DeckCode;
public string Dialog;
public int Hand;
public bool Debug;
public bool _chat;
private string _serverHost;
private int _serverPort;
private short _proVersion;
private string _roomInfo;
private GameBehavior _behavior;
public GameClient(WindBotInfo Info)
{
Username = Info.Name;
Deck = Info.Deck;
public bool Debug;
public bool _chat;
private string _serverHost;
private int _serverPort;
private string _realIP;
private short _proVersion;
private string _roomInfo;
private readonly YGOClient _presetConnection;
private GameBehavior _behavior;
private enum ConnectionMode
{
Tcp,
WebSocket
}
private class ConnectionTarget
{
public ConnectionMode Mode;
public string ExternalHost;
public string TcpHost;
public int TcpPort;
public Uri WebSocketUri;
}
public GameClient(WindBotInfo Info, YGOClient presetConnection = null)
{
Username = Info.Name;
Deck = Info.Deck;
DeckFile = Info.DeckFile;
DeckCode = Info.DeckCode;
Dialog = Info.Dialog;
Hand = Info.Hand;
Debug = Info.Debug;
_chat = Info.Chat;
_serverHost = Info.Host;
_serverPort = Info.Port;
_roomInfo = Info.HostInfo;
_proVersion = (short)Info.Version;
}
public void Start()
{
Connection = new YGOClient();
_behavior = new GameBehavior(this);
Connection.Connected += OnConnected;
Connection.PacketReceived += OnPacketReceived;
IPAddress target_address;
try
{
target_address = IPAddress.Parse(_serverHost);
}
catch (System.Exception)
{
IPHostEntry _hostEntry = Dns.GetHostEntry(_serverHost);
target_address = _hostEntry.AddressList.FirstOrDefault(findIPv4 => findIPv4.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
}
Connection.Connect(target_address, _serverPort);
}
private void OnConnected()
{
BinaryWriter packet = GamePacketFactory.Create(CtosMessage.ExternalAddress);
packet.Write((UInt32)0); // real_ip, is always 0 in normal client
packet.WriteUnicodeAutoLength(_serverHost, 255);
Connection.Send(packet);
_chat = Info.Chat;
_serverHost = Info.Host;
_serverPort = Info.Port;
_realIP = Info.RealIP;
_roomInfo = Info.HostInfo;
_proVersion = (short)Info.Version;
_presetConnection = presetConnection;
}
public void Start()
{
bool shouldStartWsConnect = false;
WsClient wsClient = null;
IPAddress tcpAddress = null;
if (_presetConnection != null)
{
Connection = _presetConnection;
}
else
{
ConnectionTarget target = ResolveConnectionTarget();
_serverHost = target.ExternalHost;
if (target.Mode == ConnectionMode.WebSocket)
{
wsClient = new WsClient(target.WebSocketUri);
Connection = new YGOClient(wsClient);
shouldStartWsConnect = true;
}
else
{
_serverPort = target.TcpPort;
Connection = new YGOClient();
try
{
tcpAddress = IPAddress.Parse(target.TcpHost);
}
catch (System.Exception)
{
IPHostEntry hostEntry = Dns.GetHostEntry(target.TcpHost);
tcpAddress = hostEntry.AddressList.FirstOrDefault(findIPv4 => findIPv4.AddressFamily == AddressFamily.InterNetwork);
if (tcpAddress == null)
throw new Exception("Can't resolve an IPv4 address for host '" + target.TcpHost + "'.");
}
}
}
_behavior = new GameBehavior(this);
RegisterConnectionEvents();
if (shouldStartWsConnect)
{
wsClient.StartConnect();
return;
}
if (_presetConnection == null)
Connection.Connect(tcpAddress, _serverPort);
}
private void RegisterConnectionEvents()
{
Connection.Connected += OnConnected;
Connection.PacketReceived += OnPacketReceived;
}
private ConnectionTarget ResolveConnectionTarget()
{
if (string.IsNullOrEmpty(_serverHost))
throw new Exception("Host is empty.");
if (_serverHost.IndexOf("//", StringComparison.Ordinal) < 0)
{
return new ConnectionTarget
{
Mode = ConnectionMode.Tcp,
ExternalHost = _serverHost,
TcpHost = _serverHost,
TcpPort = _serverPort
};
}
Uri uri;
if (!Uri.TryCreate(_serverHost, UriKind.Absolute, out uri))
throw new Exception("Invalid Host URL: '" + _serverHost + "'.");
string scheme = uri.Scheme.ToLowerInvariant();
if (scheme == "tcp")
{
if (string.IsNullOrEmpty(uri.Host))
throw new Exception("Invalid TCP Host URL: '" + _serverHost + "'.");
int resolvedPort = _serverPort;
if (!uri.IsDefaultPort)
resolvedPort = uri.Port;
return new ConnectionTarget
{
Mode = ConnectionMode.Tcp,
ExternalHost = uri.Host,
TcpHost = uri.Host,
TcpPort = resolvedPort
};
}
if (scheme == "ws" || scheme == "wss")
{
return new ConnectionTarget
{
Mode = ConnectionMode.WebSocket,
ExternalHost = uri.Host,
WebSocketUri = uri
};
}
throw new Exception("Unsupported Host scheme '" + uri.Scheme + "'. Supported schemes: tcp, ws, wss.");
}
private uint ParseRealIpAsNetworkOrderInt32()
{
if (string.IsNullOrWhiteSpace(_realIP))
return 0;
string text = _realIP.Trim();
if (text.StartsWith("::ffff:", StringComparison.OrdinalIgnoreCase))
text = text.Substring("::ffff:".Length);
IPAddress parsedIp;
if (!IPAddress.TryParse(text, out parsedIp))
throw new Exception("Invalid RealIP: '" + _realIP + "'.");
if (parsedIp.AddressFamily == AddressFamily.InterNetworkV6 && parsedIp.IsIPv4MappedToIPv6)
parsedIp = parsedIp.MapToIPv4();
if (parsedIp.AddressFamily != AddressFamily.InterNetwork)
throw new Exception("RealIP must be an IPv4 address: '" + _realIP + "'.");
byte[] bytes = parsedIp.GetAddressBytes();
uint networkOrdered = ((uint)bytes[0] << 24) | ((uint)bytes[1] << 16) | ((uint)bytes[2] << 8) | bytes[3];
return unchecked((uint)IPAddress.NetworkToHostOrder((int)networkOrdered));
}
private void OnConnected()
{
BinaryWriter packet = GamePacketFactory.Create(CtosMessage.ExternalAddress);
packet.Write(ParseRealIpAsNetworkOrderInt32());
packet.WriteUnicodeAutoLength(_serverHost, 255);
Connection.Send(packet);
packet = GamePacketFactory.Create(CtosMessage.PlayerInfo);
packet.WriteUnicode(Username, 20);
......
......@@ -5,6 +5,7 @@ using System.Net;
using System.Web;
using WindBot.Game;
using WindBot.Game.AI;
using YGOSharp.Network;
using YGOSharp.OCGWrapper;
namespace WindBot
......@@ -72,7 +73,6 @@ namespace WindBot
Info.Name = Config.GetString("Name", Info.Name);
Info.Deck = Config.GetString("Deck", Info.Deck);
Info.DeckFile = Config.GetString("DeckFile", Info.DeckFile);
Info.DeckCode = Config.GetString("DeckCode", Info.DeckCode);
Info.Dialog = Config.GetString("Dialog", Info.Dialog);
Info.Host = Config.GetString("Host", Info.Host);
Info.Port = Config.GetInt("Port", Info.Port);
......@@ -81,40 +81,28 @@ namespace WindBot
Info.Hand = Config.GetInt("Hand", Info.Hand);
Info.Debug = Config.GetBool("Debug", Info.Debug);
Info.Chat = Config.GetBool("Chat", Info.Chat);
Info.DeckCode = Config.GetString("DeckCode", Info.DeckCode);
Info.RealIP = Config.GetString("RealIP", Info.RealIP);
Run(Info);
}
private static void RunAsServer(int ServerPort)
{
using (HttpListener MainServer = new HttpListener())
{
MainServer.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
MainServer.Prefixes.Add("http://+:" + ServerPort + "/");
MainServer.Start();
Logger.WriteLine("WindBot server start successed.");
Logger.WriteLine("HTTP GET http://127.0.0.1:" + ServerPort + "/?name=WindBot&host=127.0.0.1&port=7911 to call the bot.");
while (true)
ServerModeHost.Run(ServerPort, new ParameterizedThreadStart(Run), delegate (string rawUrl, out WindBotInfo Info, out string port)
{
#if !DEBUG
try
{
#endif
HttpListenerContext ctx = MainServer.GetContext();
WindBotInfo Info = new WindBotInfo();
string RawUrl = Path.GetFileName(ctx.Request.RawUrl);
Info = new WindBotInfo();
string RawUrl = Path.GetFileName(rawUrl);
Info.Name = HttpUtility.ParseQueryString(RawUrl).Get("name");
Info.Deck = HttpUtility.ParseQueryString(RawUrl).Get("deck");
Info.Host = HttpUtility.ParseQueryString(RawUrl).Get("host");
string port = HttpUtility.ParseQueryString(RawUrl).Get("port");
port = HttpUtility.ParseQueryString(RawUrl).Get("port");
if (port != null)
Info.Port = Int32.Parse(port);
string deckfile = HttpUtility.ParseQueryString(RawUrl).Get("deckfile");
if (deckfile != null)
Info.DeckFile = deckfile;
string deckcode = HttpUtility.ParseQueryString(RawUrl).Get("deckcode");
if (deckcode != null)
Info.DeckCode = deckcode;
string dialog = HttpUtility.ParseQueryString(RawUrl).Get("dialog");
if (dialog != null)
Info.Dialog = dialog;
......@@ -133,39 +121,13 @@ namespace WindBot
string chat = HttpUtility.ParseQueryString(RawUrl).Get("chat");
if (chat != null)
Info.Chat = bool.Parse(chat);
if (Info.Name == null || Info.Host == null || port == null)
{
ctx.Response.StatusCode = 400;
ctx.Response.Close();
}
else
{
#if !DEBUG
try
{
#endif
Thread workThread = new Thread(new ParameterizedThreadStart(Run));
workThread.Start(Info);
#if !DEBUG
}
catch (Exception ex)
{
Logger.WriteErrorLine("Start Thread Error: " + ex);
}
#endif
ctx.Response.StatusCode = 200;
ctx.Response.Close();
}
#if !DEBUG
}
catch (Exception ex)
{
Logger.WriteErrorLine("Parse Http Request Error: " + ex);
}
#endif
}
}
string deckcode = HttpUtility.ParseQueryString(RawUrl).Get("deckcode");
if (deckcode != null)
Info.DeckCode = deckcode;
string realIP = HttpUtility.ParseQueryString(RawUrl).Get("realip");
if (realIP != null)
Info.RealIP = realIP;
});
}
private static void Run(object o)
......@@ -175,8 +137,16 @@ namespace WindBot
{
//all errors will be catched instead of causing the program to crash.
#endif
WindBotInfo Info = (WindBotInfo)o;
GameClient client = new GameClient(Info);
WindBotInfo Info = o as WindBotInfo;
YGOClient presetConnection = null;
ServerRunRequest request = o as ServerRunRequest;
if (request != null)
{
Info = request.Info;
presetConnection = request.Connection;
}
GameClient client = (presetConnection == null) ? new GameClient(Info) : new GameClient(Info, presetConnection);
client.Start();
Logger.DebugWriteLine(client.Username + " started.");
while (client.Connection.IsConnected)
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using YGOSharp.Network;
namespace WindBot
{
internal class ServerRunRequest
{
public WindBotInfo Info;
public YGOClient Connection;
}
internal delegate void ParseServerInfoCallback(string rawUrl, out WindBotInfo info, out string port);
internal static class ServerModeHost
{
internal static void Run(int serverPort, ParameterizedThreadStart runCallback, ParseServerInfoCallback parseInfoCallback)
{
TcpListener mainServer = new TcpListener(IPAddress.Any, serverPort);
mainServer.Start();
while (true)
{
#if !DEBUG
try
{
#endif
Socket socket = mainServer.AcceptSocket();
ThreadPool.QueueUserWorkItem(delegate { HandleServerSocket(socket, runCallback, parseInfoCallback); });
#if !DEBUG
}
catch (Exception ex)
{
Logger.WriteErrorLine("Accept Socket Error: " + ex);
}
#endif
}
}
private static void HandleServerSocket(Socket socket, ParameterizedThreadStart runCallback, ParseServerInfoCallback parseInfoCallback)
{
bool handoffToBot = false;
try
{
string method;
string rawUrl;
Dictionary<string, string> headers;
if (!TryReadHttpRequest(socket, out method, out rawUrl, out headers))
return;
WindBotInfo info;
string port;
parseInfoCallback(rawUrl, out info, out port);
if (info == null)
info = new WindBotInfo();
string upgradeHeader = GetHeader(headers, "upgrade");
string connectionHeader = GetHeader(headers, "connection");
string webSocketKey = GetHeader(headers, "sec-websocket-key");
string webSocketVersion = GetHeader(headers, "sec-websocket-version");
bool isWebSocketRequest =
string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrEmpty(webSocketKey) &&
!string.IsNullOrEmpty(upgradeHeader) &&
upgradeHeader.Equals("websocket", StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrEmpty(connectionHeader) &&
connectionHeader.IndexOf("upgrade", StringComparison.OrdinalIgnoreCase) >= 0;
if (isWebSocketRequest)
{
if (info.Name == null)
info.Name = "WindBot";
if (info.Host == null)
{
IPEndPoint remote = socket.RemoteEndPoint as IPEndPoint;
info.Host = remote != null ? remote.Address.ToString() : "reverse-ws";
}
if (!string.IsNullOrEmpty(webSocketVersion) && webSocketVersion != "13")
{
WriteHttpResponse(socket, 426, "Upgrade Required", "Unsupported WebSocket Version");
return;
}
string acceptKey = ComputeWebSocketAccept(webSocketKey);
StringBuilder response = new StringBuilder();
response.Append("HTTP/1.1 101 Switching Protocols\r\n");
response.Append("Upgrade: websocket\r\n");
response.Append("Connection: Upgrade\r\n");
response.Append("Sec-WebSocket-Accept: ").Append(acceptKey).Append("\r\n\r\n");
byte[] responseBytes = Encoding.ASCII.GetBytes(response.ToString());
socket.Send(responseBytes);
ReverseWsSocketClient reverseWsClient = new ReverseWsSocketClient(socket);
YGOClient ygoClient = new YGOClient(reverseWsClient);
ServerRunRequest request = new ServerRunRequest();
request.Info = info;
request.Connection = ygoClient;
Thread workThread = new Thread(runCallback);
workThread.Start(request);
handoffToBot = true;
return;
}
bool canUseHostWithoutPort = false;
if (info.Host != null && info.Host.IndexOf("//", StringComparison.Ordinal) >= 0)
{
Uri uri;
if (Uri.TryCreate(info.Host, UriKind.Absolute, out uri))
{
string scheme = uri.Scheme.ToLowerInvariant();
canUseHostWithoutPort = (scheme == "ws" || scheme == "wss") || (scheme == "tcp" && !uri.IsDefaultPort);
}
}
if (info.Name == null || info.Host == null || (port == null && !canUseHostWithoutPort))
{
WriteHttpResponse(socket, 400, "Bad Request", "");
return;
}
#if !DEBUG
try
{
#endif
Thread normalWorkThread = new Thread(runCallback);
normalWorkThread.Start(info);
#if !DEBUG
}
catch (Exception ex)
{
Logger.WriteErrorLine("Start Thread Error: " + ex);
}
#endif
WriteHttpResponse(socket, 200, "OK", "");
#if !DEBUG
}
catch (Exception ex)
{
Logger.WriteErrorLine("Handle Socket Request Error: " + ex);
}
#endif
finally
{
if (!handoffToBot)
{
try
{
socket.Shutdown(SocketShutdown.Both);
}
catch
{
}
socket.Close();
}
}
}
private static bool TryReadHttpRequest(Socket socket, out string method, out string rawUrl, out Dictionary<string, string> headers)
{
method = null;
rawUrl = null;
headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
NetworkStream stream = new NetworkStream(socket, false);
MemoryStream headerBuffer = new MemoryStream();
int state = 0;
while (headerBuffer.Length < 65536)
{
int b = stream.ReadByte();
if (b < 0)
break;
headerBuffer.WriteByte((byte)b);
if (state == 0)
state = (b == '\r') ? 1 : 0;
else if (state == 1)
state = (b == '\n') ? 2 : 0;
else if (state == 2)
state = (b == '\r') ? 3 : 0;
else if (state == 3)
{
if (b == '\n')
break;
state = 0;
}
}
string requestText = Encoding.ASCII.GetString(headerBuffer.ToArray());
if (string.IsNullOrEmpty(requestText))
return false;
string[] lines = requestText.Split(new[] { "\r\n" }, StringSplitOptions.None);
if (lines.Length == 0 || string.IsNullOrWhiteSpace(lines[0]))
return false;
string[] requestLineParts = lines[0].Split(' ');
if (requestLineParts.Length < 3)
return false;
method = requestLineParts[0];
rawUrl = requestLineParts[1];
for (int i = 1; i < lines.Length; ++i)
{
string line = lines[i];
if (string.IsNullOrEmpty(line))
break;
int colon = line.IndexOf(':');
if (colon <= 0)
continue;
string key = line.Substring(0, colon).Trim();
string value = line.Substring(colon + 1).Trim();
headers[key] = value;
}
return true;
}
private static string GetHeader(Dictionary<string, string> headers, string key)
{
string value;
if (headers.TryGetValue(key, out value))
return value;
return null;
}
private static void WriteHttpResponse(Socket socket, int statusCode, string statusText, string body)
{
if (body == null)
body = "";
byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
StringBuilder builder = new StringBuilder();
builder.Append("HTTP/1.1 ").Append(statusCode).Append(' ').Append(statusText).Append("\r\n");
builder.Append("Connection: close\r\n");
builder.Append("Content-Type: text/plain; charset=utf-8\r\n");
builder.Append("Content-Length: ").Append(bodyBytes.Length).Append("\r\n\r\n");
byte[] headerBytes = Encoding.ASCII.GetBytes(builder.ToString());
socket.Send(headerBytes);
if (bodyBytes.Length > 0)
socket.Send(bodyBytes);
}
private static string ComputeWebSocketAccept(string secWebSocketKey)
{
string combined = secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
byte[] bytes = Encoding.ASCII.GetBytes(combined);
using (SHA1 sha1 = SHA1.Create())
{
return Convert.ToBase64String(sha1.ComputeHash(bytes));
}
}
}
}
This diff is collapsed.
......@@ -12,6 +12,7 @@ namespace WindBot
public string Host { get; set; }
public int Port { get; set; }
public string HostInfo { get; set; }
public string RealIP { get; set; }
public int Version { get; set; }
public int Hand { get; set; }
public bool Debug { get; set; }
......@@ -26,6 +27,7 @@ namespace WindBot
Host = "127.0.0.1";
Port = 7911;
HostInfo = "";
RealIP = null;
Version = 0x1362;
Hand = 0;
Debug = false;
......
......@@ -16,7 +16,7 @@ namespace YGOSharp.Network
protected int HeaderSize = 2;
protected bool IsHeaderSizeIncluded = false;
private NetworkClient _client;
private INetworkClient _client;
private List<byte> _receiveBuffer = new List<byte>();
private Queue<byte[]> _pendingPackets = new Queue<byte[]>();
......@@ -38,19 +38,20 @@ namespace YGOSharp.Network
get { return _client.RemoteIPAddress; }
}
public BinaryClient(NetworkClient client)
{
_client = client;
public BinaryClient(INetworkClient client)
{
_client = client;
client.Connected += Client_Connected;
client.Disconnected += Client_Disconnected;
client.DataReceived += Client_DataReceived;
if (_client.IsConnected)
{
_client.BeginReceive();
}
}
client.Disconnected += Client_Disconnected;
client.DataReceived += Client_DataReceived;
if (_client.IsConnected)
{
Client_Connected();
_client.BeginReceive();
}
}
public void Connect(IPAddress address, int port)
{
......
using System;
using System.Net;
using System.Net.Sockets;
namespace YGOSharp.Network
{
public interface INetworkClient
{
event Action Connected;
event Action<Exception> Disconnected;
event Action<byte[]> DataReceived;
bool IsConnected { get; }
IPAddress RemoteIPAddress { get; }
void Initialize(Socket socket);
void BeginConnect(IPAddress address, int port);
void BeginSend(byte[] data);
void BeginReceive();
void Close(Exception error = null);
}
}
......@@ -2,10 +2,10 @@
using System.Net;
using System.Net.Sockets;
namespace YGOSharp.Network
{
public class NetworkClient
{
namespace YGOSharp.Network
{
public class NetworkClient : INetworkClient
{
public event Action Connected;
public event Action<Exception> Disconnected;
public event Action<byte[]> DataReceived;
......
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Threading;
namespace YGOSharp.Network
{
public class ReverseWsClient : INetworkClient
{
public event Action Connected;
public event Action<Exception> Disconnected;
public event Action<byte[]> DataReceived;
public bool IsConnected { get; private set; }
public IPAddress RemoteIPAddress { get; private set; }
private readonly WebSocket _socket;
private int _receiveStarted;
private int _connectedRaised;
private bool _isClosed;
private readonly object _closeLock = new object();
public ReverseWsClient(WebSocket socket, IPAddress remoteIPAddress = null)
{
_socket = socket;
RemoteIPAddress = remoteIPAddress ?? IPAddress.None;
IsConnected = socket != null && socket.State == WebSocketState.Open;
}
public void Initialize(Socket socket)
{
throw new NotSupportedException("ReverseWsClient does not support Initialize(Socket).");
}
public void BeginConnect(IPAddress address, int port)
{
throw new NotSupportedException("ReverseWsClient does not support BeginConnect(IPAddress, int).");
}
public void BeginSend(byte[] data)
{
if (_isClosed)
return;
try
{
if (_socket.State != WebSocketState.Open)
{
Close();
return;
}
_socket.SendAsync(new ArraySegment<byte>(data), WebSocketMessageType.Binary, true, CancellationToken.None)
.GetAwaiter().GetResult();
}
catch (Exception ex)
{
Close(ex);
}
}
public void BeginReceive()
{
if (IsConnected && Interlocked.Exchange(ref _connectedRaised, 1) == 0)
Connected?.Invoke();
if (Interlocked.Exchange(ref _receiveStarted, 1) != 0)
return;
ThreadPool.QueueUserWorkItem(delegate { ReceiveLoop(); });
}
public void Close(Exception error = null)
{
lock (_closeLock)
{
if (_isClosed)
return;
_isClosed = true;
}
try
{
if (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.CloseReceived)
{
_socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "closed", CancellationToken.None)
.GetAwaiter().GetResult();
}
else
{
_socket.Abort();
}
}
catch
{
}
finally
{
IsConnected = false;
Disconnected?.Invoke(error);
}
}
private void ReceiveLoop()
{
byte[] buffer = new byte[4096];
while (!_isClosed)
{
try
{
WebSocketReceiveResult result;
using (MemoryStream stream = new MemoryStream())
{
do
{
result = _socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None)
.GetAwaiter().GetResult();
if (result.MessageType == WebSocketMessageType.Close)
{
Close();
return;
}
if (result.MessageType != WebSocketMessageType.Binary)
{
Close(new Exception("ReverseWsClient only supports binary frames."));
return;
}
if (result.Count > 0)
stream.Write(buffer, 0, result.Count);
}
while (!result.EndOfMessage);
DataReceived?.Invoke(stream.ToArray());
}
}
catch (Exception ex)
{
Close(ex);
return;
}
}
}
}
}
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace YGOSharp.Network
{
public class ReverseWsSocketClient : INetworkClient
{
public event Action Connected;
public event Action<Exception> Disconnected;
public event Action<byte[]> DataReceived;
public bool IsConnected { get; private set; }
public IPAddress RemoteIPAddress { get; private set; }
private readonly Socket _socket;
private readonly NetworkStream _stream;
private readonly object _sendLock = new object();
private readonly object _closeLock = new object();
private bool _isClosed;
private int _receiveStarted;
private int _connectedRaised;
private MemoryStream _fragmentBuffer;
private byte _fragmentOpcode;
public ReverseWsSocketClient(Socket socket)
{
_socket = socket;
_stream = new NetworkStream(socket, false);
IPEndPoint remote = socket.RemoteEndPoint as IPEndPoint;
RemoteIPAddress = remote != null ? remote.Address : IPAddress.None;
IsConnected = true;
}
public void Initialize(Socket socket)
{
throw new NotSupportedException("ReverseWsSocketClient does not support Initialize(Socket).");
}
public void BeginConnect(IPAddress address, int port)
{
throw new NotSupportedException("ReverseWsSocketClient does not support BeginConnect(IPAddress, int).");
}
public void BeginSend(byte[] data)
{
if (_isClosed)
return;
try
{
SendFrame(0x2, data ?? new byte[0]);
}
catch (Exception ex)
{
Close(ex);
}
}
public void BeginReceive()
{
if (IsConnected && Interlocked.Exchange(ref _connectedRaised, 1) == 0)
Connected?.Invoke();
if (Interlocked.Exchange(ref _receiveStarted, 1) != 0)
return;
ThreadPool.QueueUserWorkItem(delegate { ReceiveLoop(); });
}
public void Close(Exception error = null)
{
lock (_closeLock)
{
if (_isClosed)
return;
_isClosed = true;
}
IsConnected = false;
try
{
SendFrame(0x8, new byte[0]);
}
catch
{
}
try
{
_socket.Shutdown(SocketShutdown.Both);
}
catch
{
}
try
{
_socket.Close();
}
catch
{
}
Disconnected?.Invoke(error);
}
private void ReceiveLoop()
{
while (!_isClosed)
{
try
{
int b0 = _stream.ReadByte();
if (b0 < 0)
{
Close();
return;
}
int b1 = _stream.ReadByte();
if (b1 < 0)
{
Close();
return;
}
bool fin = (b0 & 0x80) != 0;
byte opcode = (byte)(b0 & 0x0F);
bool masked = (b1 & 0x80) != 0;
ulong payloadLength = (ulong)(b1 & 0x7F);
if (payloadLength == 126)
{
byte[] ext = ReadExact(2);
payloadLength = (ulong)((ext[0] << 8) | ext[1]);
}
else if (payloadLength == 127)
{
byte[] ext = ReadExact(8);
payloadLength = ((ulong)ext[0] << 56) |
((ulong)ext[1] << 48) |
((ulong)ext[2] << 40) |
((ulong)ext[3] << 32) |
((ulong)ext[4] << 24) |
((ulong)ext[5] << 16) |
((ulong)ext[6] << 8) |
ext[7];
}
if (payloadLength > int.MaxValue)
{
Close(new Exception("Reverse WS frame is too large."));
return;
}
byte[] maskKey = null;
if (masked)
maskKey = ReadExact(4);
byte[] payload = ReadExact((int)payloadLength);
if (masked && payloadLength > 0)
{
for (int i = 0; i < payload.Length; ++i)
payload[i] = (byte)(payload[i] ^ maskKey[i % 4]);
}
if (opcode == 0x8)
{
Close();
return;
}
if (opcode == 0x9)
{
SendFrame(0xA, payload);
continue;
}
if (opcode == 0xA)
continue;
if (opcode == 0x2)
{
if (fin)
{
DataReceived?.Invoke(payload);
}
else
{
_fragmentOpcode = opcode;
_fragmentBuffer = new MemoryStream();
_fragmentBuffer.Write(payload, 0, payload.Length);
}
continue;
}
if (opcode == 0x0)
{
if (_fragmentBuffer == null)
{
Close(new Exception("Invalid continuation frame."));
return;
}
_fragmentBuffer.Write(payload, 0, payload.Length);
if (fin)
{
if (_fragmentOpcode == 0x2)
DataReceived?.Invoke(_fragmentBuffer.ToArray());
_fragmentBuffer.Dispose();
_fragmentBuffer = null;
_fragmentOpcode = 0;
}
continue;
}
if (opcode == 0x1)
{
Close(new Exception("Reverse WS only supports binary frames."));
return;
}
}
catch (Exception ex)
{
Close(ex);
return;
}
}
}
private byte[] ReadExact(int count)
{
byte[] data = new byte[count];
int offset = 0;
while (offset < count)
{
int read = _stream.Read(data, offset, count - offset);
if (read <= 0)
throw new IOException("Socket closed.");
offset += read;
}
return data;
}
private void SendFrame(byte opcode, byte[] payload)
{
if (payload == null)
payload = new byte[0];
lock (_sendLock)
{
if (_isClosed)
return;
using (MemoryStream frame = new MemoryStream())
{
frame.WriteByte((byte)(0x80 | (opcode & 0x0F)));
int len = payload.Length;
if (len <= 125)
{
frame.WriteByte((byte)len);
}
else if (len <= ushort.MaxValue)
{
frame.WriteByte(126);
frame.WriteByte((byte)((len >> 8) & 0xFF));
frame.WriteByte((byte)(len & 0xFF));
}
else
{
frame.WriteByte(127);
ulong l = (ulong)len;
frame.WriteByte((byte)((l >> 56) & 0xFF));
frame.WriteByte((byte)((l >> 48) & 0xFF));
frame.WriteByte((byte)((l >> 40) & 0xFF));
frame.WriteByte((byte)((l >> 32) & 0xFF));
frame.WriteByte((byte)((l >> 24) & 0xFF));
frame.WriteByte((byte)((l >> 16) & 0xFF));
frame.WriteByte((byte)((l >> 8) & 0xFF));
frame.WriteByte((byte)(l & 0xFF));
}
if (len > 0)
frame.Write(payload, 0, len);
byte[] bytes = frame.ToArray();
_stream.Write(bytes, 0, bytes.Length);
_stream.Flush();
}
}
}
}
}
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Threading;
namespace YGOSharp.Network
{
public class WsClient : INetworkClient
{
public event Action Connected;
public event Action<Exception> Disconnected;
public event Action<byte[]> DataReceived;
public bool IsConnected { get; private set; }
public IPAddress RemoteIPAddress { get; private set; }
private readonly Uri _uri;
private readonly ClientWebSocket _socket;
private int _receiveStarted;
private bool _isClosed;
private readonly object _closeLock = new object();
public WsClient(Uri uri)
{
_uri = uri;
_socket = new ClientWebSocket();
RemoteIPAddress = IPAddress.None;
}
public void StartConnect()
{
ThreadPool.QueueUserWorkItem(delegate { ConnectInternal(); });
}
public void Initialize(Socket socket)
{
throw new NotSupportedException("WsClient does not support Initialize(Socket).");
}
public void BeginConnect(IPAddress address, int port)
{
throw new NotSupportedException("WsClient does not support BeginConnect(IPAddress, int).");
}
public void BeginSend(byte[] data)
{
if (_isClosed)
return;
try
{
if (_socket.State != WebSocketState.Open)
{
Close();
return;
}
_socket.SendAsync(new ArraySegment<byte>(data), WebSocketMessageType.Binary, true, CancellationToken.None)
.GetAwaiter().GetResult();
}
catch (Exception ex)
{
Close(ex);
}
}
public void BeginReceive()
{
if (Interlocked.Exchange(ref _receiveStarted, 1) != 0)
return;
ThreadPool.QueueUserWorkItem(delegate { ReceiveLoop(); });
}
public void Close(Exception error = null)
{
lock (_closeLock)
{
if (_isClosed)
return;
_isClosed = true;
}
try
{
if (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.CloseReceived)
{
_socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "closed", CancellationToken.None)
.GetAwaiter().GetResult();
}
else
{
_socket.Abort();
}
}
catch
{
}
finally
{
_socket.Dispose();
IsConnected = false;
Disconnected?.Invoke(error);
}
}
private void ConnectInternal()
{
try
{
_socket.ConnectAsync(_uri, CancellationToken.None).GetAwaiter().GetResult();
IsConnected = true;
try
{
IPAddress parsedIp;
if (IPAddress.TryParse(_uri.Host, out parsedIp))
RemoteIPAddress = parsedIp;
}
catch
{
}
Connected?.Invoke();
BeginReceive();
}
catch (Exception ex)
{
Close(ex);
}
}
private void ReceiveLoop()
{
byte[] buffer = new byte[4096];
while (!_isClosed)
{
try
{
WebSocketReceiveResult result;
using (MemoryStream stream = new MemoryStream())
{
do
{
result = _socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None)
.GetAwaiter().GetResult();
if (result.MessageType == WebSocketMessageType.Close)
{
Close();
return;
}
if (result.MessageType != WebSocketMessageType.Binary)
{
Close(new Exception("WsClient only supports binary frames."));
return;
}
if (result.Count > 0)
stream.Write(buffer, 0, result.Count);
}
while (!result.EndOfMessage);
DataReceived?.Invoke(stream.ToArray());
}
}
catch (Exception ex)
{
Close(ex);
return;
}
}
}
}
}
......@@ -5,15 +5,15 @@ namespace YGOSharp.Network
{
public class YGOClient : BinaryClient
{
public YGOClient()
: base(new NetworkClient())
{
}
public YGOClient(NetworkClient client)
: base(client)
{
}
public YGOClient()
: base(new NetworkClient())
{
}
public YGOClient(INetworkClient client)
: base(client)
{
}
public void Send(BinaryWriter writer)
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment