mirror of
https://github.com/jie65535/GrasscutterCommandGenerator.git
synced 2025-06-07 22:59:14 +08:00
Add HTTP(S) proxy (#208)
This commit is contained in:
parent
41c644f2af
commit
dce7e54675
202
Source/GrasscutterTools/Eavesdrop/Certifier.cs
Normal file
202
Source/GrasscutterTools/Eavesdrop/Certifier.cs
Normal file
@ -0,0 +1,202 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace Eavesdrop;
|
||||
|
||||
public sealed class Certifier : IDisposable
|
||||
{
|
||||
private readonly X509Store _rootStore, _myStore;
|
||||
private readonly IDictionary<string, X509Certificate2> _certificateCache;
|
||||
|
||||
public string Issuer { get; }
|
||||
public string CertificateAuthorityName { get; }
|
||||
|
||||
public DateTime NotAfter { get; set; }
|
||||
public DateTime NotBefore { get; set; }
|
||||
public int KeyLength { get; set; } = 1024;
|
||||
public bool IsCachingSignedCertificates { get; set; }
|
||||
|
||||
public X509Certificate2? Authority { get; private set; }
|
||||
|
||||
public Certifier()
|
||||
: this("Eavesdrop")
|
||||
{ }
|
||||
public Certifier(string issuer)
|
||||
: this(issuer, $"{issuer} Root Certificate Authority", StoreLocation.CurrentUser)
|
||||
{ }
|
||||
public Certifier(string issuer, string certificateAuthorityName)
|
||||
: this(issuer, certificateAuthorityName, StoreLocation.CurrentUser)
|
||||
{ }
|
||||
public Certifier(string issuer, string certificateAuthorityName, StoreLocation location)
|
||||
{
|
||||
_myStore = new X509Store(StoreName.My, location);
|
||||
_rootStore = new X509Store(StoreName.Root, location);
|
||||
_certificateCache = new Dictionary<string, X509Certificate2>();
|
||||
|
||||
NotBefore = DateTime.Now;
|
||||
NotAfter = NotBefore.AddMonths(1);
|
||||
|
||||
Issuer = issuer;
|
||||
CertificateAuthorityName = certificateAuthorityName;
|
||||
}
|
||||
|
||||
public bool CreateTrustedRootCertificate()
|
||||
{
|
||||
return (Authority = InstallCertificate(_rootStore, CertificateAuthorityName)) != null;
|
||||
}
|
||||
public bool DestroyTrustedRootCertificate()
|
||||
{
|
||||
return DestroyCertificates(_rootStore);
|
||||
}
|
||||
public bool ExportTrustedRootCertificate(string path)
|
||||
{
|
||||
X509Certificate2? rootCertificate = InstallCertificate(_rootStore, CertificateAuthorityName);
|
||||
|
||||
path = Path.GetFullPath(path);
|
||||
if (rootCertificate != null)
|
||||
{
|
||||
byte[] data = rootCertificate.Export(X509ContentType.Cert);
|
||||
File.WriteAllBytes(path, data);
|
||||
}
|
||||
return File.Exists(path);
|
||||
}
|
||||
|
||||
public X509Certificate2? GenerateCertificate(string certificateName)
|
||||
{
|
||||
return InstallCertificate(_myStore, certificateName);
|
||||
}
|
||||
public X509Certificate2 CreateCertificate(string subjectName, string alternateName)
|
||||
{
|
||||
using var rsa = RSA.Create(KeyLength);
|
||||
var certificateRequest = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
if (Authority == null)
|
||||
{
|
||||
certificateRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
|
||||
certificateRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(certificateRequest.PublicKey, false));
|
||||
|
||||
using X509Certificate2 certificate = certificateRequest.CreateSelfSigned(NotBefore.ToUniversalTime(), NotAfter.ToUniversalTime());
|
||||
|
||||
certificate.FriendlyName = alternateName;
|
||||
return new X509Certificate2(certificate.Export(X509ContentType.Pfx, string.Empty), string.Empty, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||
sanBuilder.AddDnsName(alternateName);
|
||||
|
||||
certificateRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||
certificateRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
|
||||
certificateRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(certificateRequest.PublicKey, false));
|
||||
|
||||
using X509Certificate2 certificate = certificateRequest.Create(Authority, Authority.NotBefore, Authority.NotAfter, Guid.NewGuid().ToByteArray());
|
||||
using X509Certificate2 certificateWithPrivateKey = certificate.CopyWithPrivateKey(rsa);
|
||||
|
||||
certificateWithPrivateKey.FriendlyName = alternateName;
|
||||
return new X509Certificate2(certificateWithPrivateKey.Export(X509ContentType.Pfx, string.Empty), string.Empty, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
|
||||
}
|
||||
}
|
||||
private X509Certificate2? InstallCertificate(X509Store store, string certificateName)
|
||||
{
|
||||
if (_certificateCache.TryGetValue(certificateName, out X509Certificate2? certificate))
|
||||
{
|
||||
if (DateTime.Now >= certificate.NotAfter)
|
||||
{
|
||||
_certificateCache.Remove(certificateName);
|
||||
}
|
||||
else return certificate;
|
||||
}
|
||||
lock (store)
|
||||
{
|
||||
try
|
||||
{
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
string subjectName = $"CN={certificateName}, O={Issuer}";
|
||||
|
||||
certificate = FindCertificates(store, subjectName)?[0];
|
||||
if (certificate != null && DateTime.Now >= certificate.NotAfter)
|
||||
{
|
||||
if (Authority == null)
|
||||
{
|
||||
DestroyCertificates();
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
}
|
||||
else
|
||||
{
|
||||
store.Remove(certificate);
|
||||
}
|
||||
certificate = null;
|
||||
}
|
||||
|
||||
if (certificate == null)
|
||||
{
|
||||
certificate = CreateCertificate(subjectName, certificateName);
|
||||
if (certificate != null)
|
||||
{
|
||||
if (store == _rootStore || IsCachingSignedCertificates)
|
||||
{
|
||||
store.Add(certificate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return certificate;
|
||||
}
|
||||
catch { return certificate = null; }
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
if (certificate != null && !_certificateCache.ContainsKey(certificateName))
|
||||
{
|
||||
_certificateCache.Add(certificateName, certificate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool DestroyCertificates(X509Store store)
|
||||
{
|
||||
lock (store)
|
||||
{
|
||||
try
|
||||
{
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindByIssuerName, Issuer, false);
|
||||
|
||||
store.RemoveRange(certificates);
|
||||
IEnumerable<string> subjectNames = certificates.Cast<X509Certificate2>().Select(c => c.GetNameInfo(X509NameType.SimpleName, false));
|
||||
|
||||
foreach (string subjectName in subjectNames)
|
||||
{
|
||||
if (!_certificateCache.ContainsKey(subjectName)) continue;
|
||||
_certificateCache.Remove(subjectName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch { return false; }
|
||||
finally { store.Close(); }
|
||||
}
|
||||
}
|
||||
public bool DestroyCertificates() => DestroyCertificates(_myStore) && DestroyCertificates(_rootStore);
|
||||
|
||||
private static X509Certificate2Collection? FindCertificates(X509Store store, string subjectName)
|
||||
{
|
||||
X509Certificate2Collection certificates = store.Certificates
|
||||
.Find(X509FindType.FindBySubjectDistinguishedName, subjectName, false);
|
||||
|
||||
return certificates.Count > 0 ? certificates : null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_myStore.Close();
|
||||
_rootStore.Close();
|
||||
|
||||
_myStore.Dispose();
|
||||
_rootStore.Dispose();
|
||||
}
|
||||
}
|
200
Source/GrasscutterTools/Eavesdrop/Eavesdropper.cs
Normal file
200
Source/GrasscutterTools/Eavesdrop/Eavesdropper.cs
Normal file
@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Eavesdrop.Network;
|
||||
|
||||
namespace Eavesdrop
|
||||
{
|
||||
public static class Eavesdropper
|
||||
{
|
||||
private static TcpListener _listener;
|
||||
private static readonly object _stateLock;
|
||||
|
||||
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
|
||||
|
||||
public static event AsyncEventHandler<RequestInterceptedEventArgs> RequestInterceptedAsync;
|
||||
private static async Task OnRequestInterceptedAsync(RequestInterceptedEventArgs e)
|
||||
{
|
||||
Task interceptedTask = RequestInterceptedAsync?.Invoke(null, e);
|
||||
if (interceptedTask != null)
|
||||
{
|
||||
await interceptedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public static event AsyncEventHandler<ResponseInterceptedEventArgs> ResponseInterceptedAsync;
|
||||
private static async Task OnResponseInterceptedAsync(ResponseInterceptedEventArgs e)
|
||||
{
|
||||
Task interceptedTask = ResponseInterceptedAsync?.Invoke(null, e);
|
||||
if (interceptedTask != null)
|
||||
{
|
||||
await interceptedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> Overrides { get; }
|
||||
public static bool IsRunning { get; private set; }
|
||||
public static Certifier Certifier { get; set; }
|
||||
|
||||
static Eavesdropper()
|
||||
{
|
||||
_stateLock = new object();
|
||||
|
||||
ServicePointManager.Expect100Continue = true;
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
|
||||
|
||||
Overrides = new List<string>();
|
||||
Certifier = new Certifier("Eavesdrop", "Eavesdrop Root Certificate Authority");
|
||||
}
|
||||
|
||||
public static void Terminate()
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
ResetMachineProxy();
|
||||
IsRunning = false;
|
||||
|
||||
if (_listener != null)
|
||||
{
|
||||
_listener.Stop();
|
||||
_listener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void Initiate(int port)
|
||||
{
|
||||
Initiate(port, Interceptors.Default);
|
||||
}
|
||||
public static void Initiate(int port, Interceptors interceptors)
|
||||
{
|
||||
Initiate(port, interceptors, true);
|
||||
}
|
||||
public static void Initiate(int port, Interceptors interceptors, bool setSystemProxy)
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
Terminate();
|
||||
|
||||
_listener = new TcpListener(IPAddress.Any, port);
|
||||
_listener.Start();
|
||||
|
||||
IsRunning = true;
|
||||
|
||||
Task.Factory.StartNew(InterceptRequestAsync, TaskCreationOptions.LongRunning);
|
||||
if (setSystemProxy)
|
||||
{
|
||||
SetMachineProxy(port, interceptors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task InterceptRequestAsync()
|
||||
{
|
||||
while (IsRunning && _listener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
TcpClient client = await _listener.AcceptTcpClientAsync().ConfigureAwait(false);
|
||||
Task handleClientAsync = HandleClientAsync(client);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleClientAsync(TcpClient client)
|
||||
{
|
||||
using var local = new EavesNode(Certifier, client);
|
||||
WebRequest request = await local.ReadRequestAsync().ConfigureAwait(false);
|
||||
if (request == null) return;
|
||||
|
||||
HttpContent requestContent = null;
|
||||
var requestArgs = new RequestInterceptedEventArgs(request);
|
||||
try
|
||||
{
|
||||
requestArgs.Content = requestContent = await local.ReadRequestContentAsync(request).ConfigureAwait(false);
|
||||
await OnRequestInterceptedAsync(requestArgs).ConfigureAwait(false);
|
||||
if (requestArgs.Cancel) return;
|
||||
|
||||
request = requestArgs.Request;
|
||||
if (requestArgs.Content != null)
|
||||
{
|
||||
await local.WriteRequestContentAsync(request, requestArgs.Content).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
requestContent?.Dispose();
|
||||
requestArgs.Content?.Dispose();
|
||||
}
|
||||
|
||||
WebResponse response = null;
|
||||
try { response = await request.GetResponseAsync().ConfigureAwait(false); }
|
||||
catch (WebException ex) { response = ex.Response; }
|
||||
catch (ProtocolViolationException)
|
||||
{
|
||||
response?.Dispose();
|
||||
response = null;
|
||||
}
|
||||
|
||||
if (response == null) return;
|
||||
HttpContent responseContent = null;
|
||||
var responseArgs = new ResponseInterceptedEventArgs(request, response);
|
||||
try
|
||||
{
|
||||
responseArgs.Content = responseContent = EavesNode.ReadResponseContent(response);
|
||||
await OnResponseInterceptedAsync(responseArgs).ConfigureAwait(false);
|
||||
if (responseArgs.Cancel) return;
|
||||
|
||||
await local.SendResponseAsync(responseArgs.Response, responseArgs.Content).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
response.Dispose();
|
||||
responseArgs.Response.Dispose();
|
||||
|
||||
responseContent?.Dispose();
|
||||
responseArgs.Content?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetMachineProxy()
|
||||
{
|
||||
INETOptions.Overrides.Clear();
|
||||
INETOptions.IsIgnoringLocalTraffic = false;
|
||||
|
||||
INETOptions.HTTPAddress = null;
|
||||
INETOptions.HTTPSAddress = null;
|
||||
INETOptions.IsProxyEnabled = false;
|
||||
|
||||
INETOptions.Save();
|
||||
}
|
||||
private static void SetMachineProxy(int port, Interceptors interceptors)
|
||||
{
|
||||
foreach (string @override in Overrides)
|
||||
{
|
||||
if (INETOptions.Overrides.Contains(@override)) continue;
|
||||
INETOptions.Overrides.Add(@override);
|
||||
}
|
||||
|
||||
string address = ("127.0.0.1:" + port);
|
||||
if (interceptors.HasFlag(Interceptors.HTTP))
|
||||
{
|
||||
INETOptions.HTTPAddress = address;
|
||||
}
|
||||
if (interceptors.HasFlag(Interceptors.HTTPS))
|
||||
{
|
||||
INETOptions.HTTPSAddress = address;
|
||||
}
|
||||
INETOptions.IsProxyEnabled = true;
|
||||
INETOptions.IsIgnoringLocalTraffic = true;
|
||||
|
||||
INETOptions.Save();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Eavesdrop
|
||||
{
|
||||
public class RequestInterceptedEventArgs : CancelEventArgs
|
||||
{
|
||||
private HttpWebRequest _httpRequest;
|
||||
|
||||
public HttpContent Content { get; set; }
|
||||
|
||||
private WebRequest _request;
|
||||
public WebRequest Request
|
||||
{
|
||||
get => _request;
|
||||
set
|
||||
{
|
||||
_request = value;
|
||||
_httpRequest = (value as HttpWebRequest);
|
||||
}
|
||||
}
|
||||
|
||||
public Uri Uri => Request?.RequestUri;
|
||||
public CookieContainer CookieContainer => _httpRequest?.CookieContainer;
|
||||
|
||||
public string Method
|
||||
{
|
||||
get => Request?.Method;
|
||||
set
|
||||
{
|
||||
if (Request != null)
|
||||
{
|
||||
Request.Method = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public IWebProxy Proxy
|
||||
{
|
||||
get => Request?.Proxy;
|
||||
set
|
||||
{
|
||||
if (Request != null)
|
||||
{
|
||||
Request.Proxy = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public string ContentType
|
||||
{
|
||||
get => Request?.ContentType;
|
||||
set
|
||||
{
|
||||
if (Request != null)
|
||||
{
|
||||
Request.ContentType = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public WebHeaderCollection Headers
|
||||
{
|
||||
get => Request?.Headers;
|
||||
set
|
||||
{
|
||||
if (Request != null)
|
||||
{
|
||||
Request.Headers = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RequestInterceptedEventArgs(WebRequest request)
|
||||
{
|
||||
Request = request;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Eavesdrop.Network;
|
||||
|
||||
namespace Eavesdrop
|
||||
{
|
||||
public class ResponseInterceptedEventArgs : CancelEventArgs
|
||||
{
|
||||
private WebResponse _response;
|
||||
public WebResponse Response
|
||||
{
|
||||
get => _response;
|
||||
set
|
||||
{
|
||||
_response = value;
|
||||
if (value is HttpWebResponse httpResponse)
|
||||
{
|
||||
CookieContainer = new CookieContainer();
|
||||
CookieContainer.Add(httpResponse.Cookies);
|
||||
}
|
||||
else CookieContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public WebRequest Request { get; }
|
||||
public Uri Uri => Response?.ResponseUri;
|
||||
|
||||
public HttpContent Content { get; set; }
|
||||
public CookieContainer CookieContainer { get; private set; }
|
||||
|
||||
public string ContentType
|
||||
{
|
||||
get => Response?.ContentType;
|
||||
set
|
||||
{
|
||||
if (Response != null)
|
||||
{
|
||||
Response.ContentType = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public WebHeaderCollection Headers
|
||||
{
|
||||
get => Response?.Headers;
|
||||
set
|
||||
{
|
||||
if (Response == null) return;
|
||||
foreach (string header in value.AllKeys)
|
||||
{
|
||||
Response.Headers[header] = value[header];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ResponseInterceptedEventArgs(WebRequest request, WebResponse response)
|
||||
{
|
||||
Request = request;
|
||||
Response = response;
|
||||
}
|
||||
}
|
||||
}
|
14
Source/GrasscutterTools/Eavesdrop/Interceptors.cs
Normal file
14
Source/GrasscutterTools/Eavesdrop/Interceptors.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Eavesdrop
|
||||
{
|
||||
[Flags]
|
||||
public enum Interceptors
|
||||
{
|
||||
None = 0,
|
||||
HTTP = 1,
|
||||
HTTPS = 2,
|
||||
|
||||
Default = (HTTP | HTTPS)
|
||||
}
|
||||
}
|
241
Source/GrasscutterTools/Eavesdrop/Internals/INETOptions.cs
Normal file
241
Source/GrasscutterTools/Eavesdrop/Internals/INETOptions.cs
Normal file
@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Eavesdrop
|
||||
{
|
||||
public static class INETOptions
|
||||
{
|
||||
private static readonly object _stateLock;
|
||||
private static readonly int _iNetOptionSize;
|
||||
private static readonly int _iNetPackageSize;
|
||||
private static readonly RegistryKey _proxyKey;
|
||||
|
||||
public static List<string> Overrides { get; }
|
||||
|
||||
public static string HTTPAddress { get; set; }
|
||||
public static string HTTPSAddress { get; set; }
|
||||
|
||||
public static bool IsProxyEnabled { get; set; }
|
||||
public static bool IsIgnoringLocalTraffic { get; set; }
|
||||
|
||||
static INETOptions()
|
||||
{
|
||||
_stateLock = new object();
|
||||
_iNetOptionSize = Marshal.SizeOf(typeof(INETOption));
|
||||
_iNetPackageSize = Marshal.SizeOf(typeof(INETPackage));
|
||||
_proxyKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Internet Settings", true);
|
||||
|
||||
Overrides = new List<string>();
|
||||
Load();
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
var options = new List<INETOption>(3);
|
||||
string joinedAddresses = (IsProxyEnabled ? GetJoinedAddresses() : null);
|
||||
string joinedOverrides = (IsProxyEnabled ? GetJoinedOverrides() : null);
|
||||
|
||||
var kind = ProxyKind.PROXY_TYPE_DIRECT;
|
||||
if (!string.IsNullOrWhiteSpace(joinedAddresses))
|
||||
{
|
||||
options.Add(new INETOption(OptionKind.INTERNET_PER_CONN_PROXY_SERVER, joinedAddresses));
|
||||
if (!string.IsNullOrWhiteSpace(joinedOverrides))
|
||||
{
|
||||
options.Add(new INETOption(OptionKind.INTERNET_PER_CONN_PROXY_BYPASS, joinedOverrides));
|
||||
}
|
||||
kind |= ProxyKind.PROXY_TYPE_PROXY;
|
||||
}
|
||||
options.Insert(0, new INETOption(OptionKind.INTERNET_PER_CONN_FLAGS, (int)kind));
|
||||
|
||||
var inetPackage = new INETPackage
|
||||
{
|
||||
_optionError = 0,
|
||||
_size = _iNetPackageSize,
|
||||
_connection = IntPtr.Zero,
|
||||
_optionCount = options.Count
|
||||
};
|
||||
|
||||
IntPtr optionsPtr = Marshal.AllocCoTaskMem(_iNetOptionSize * options.Count);
|
||||
for (int i = 0; i < options.Count; ++i)
|
||||
{
|
||||
var optionPtr = new IntPtr((IntPtr.Size == 4 ? optionsPtr.ToInt32() : optionsPtr.ToInt64()) + (i * _iNetOptionSize));
|
||||
Marshal.StructureToPtr(options[i], optionPtr, false);
|
||||
}
|
||||
inetPackage._optionsPtr = optionsPtr;
|
||||
|
||||
IntPtr iNetPackagePtr = Marshal.AllocCoTaskMem(_iNetPackageSize);
|
||||
Marshal.StructureToPtr(inetPackage, iNetPackagePtr, false);
|
||||
|
||||
int returnvalue = (NativeMethods.InternetSetOption(IntPtr.Zero, 75, iNetPackagePtr, _iNetPackageSize) ? -1 : 0);
|
||||
if (returnvalue == 0)
|
||||
{
|
||||
returnvalue = Marshal.GetLastWin32Error();
|
||||
}
|
||||
|
||||
Marshal.FreeCoTaskMem(optionsPtr);
|
||||
Marshal.FreeCoTaskMem(iNetPackagePtr);
|
||||
if (returnvalue > 0)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
NativeMethods.InternetSetOption(IntPtr.Zero, 39, iNetPackagePtr, _iNetPackageSize);
|
||||
NativeMethods.InternetSetOption(IntPtr.Zero, 37, iNetPackagePtr, _iNetPackageSize);
|
||||
}
|
||||
}
|
||||
public static void Load()
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
LoadAddresses();
|
||||
LoadOverrides();
|
||||
IsProxyEnabled = (_proxyKey.GetValue("ProxyEnable")?.ToString() == "1");
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadOverrides()
|
||||
{
|
||||
string proxyOverride = _proxyKey.GetValue("ProxyOverride")?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(proxyOverride)) return;
|
||||
|
||||
string[] overrides = proxyOverride.Split(';');
|
||||
foreach (string @override in overrides)
|
||||
{
|
||||
if (@override == "<local>")
|
||||
{
|
||||
IsIgnoringLocalTraffic = true;
|
||||
}
|
||||
else if (!Overrides.Contains(@override))
|
||||
{
|
||||
Overrides.Add(@override);
|
||||
}
|
||||
}
|
||||
}
|
||||
private static void LoadAddresses()
|
||||
{
|
||||
string proxyServer = _proxyKey.GetValue("ProxyServer")?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(proxyServer)) return;
|
||||
|
||||
string[] values = proxyServer.Split(';');
|
||||
foreach (string value in values)
|
||||
{
|
||||
string[] pair = value.Split('=');
|
||||
if (pair.Length != 2)
|
||||
{
|
||||
HTTPAddress = value;
|
||||
HTTPSAddress = value;
|
||||
return;
|
||||
}
|
||||
|
||||
string address = pair[1];
|
||||
string protocol = pair[0];
|
||||
switch (protocol)
|
||||
{
|
||||
case "http": HTTPAddress = address; break;
|
||||
case "https": HTTPSAddress = address; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetJoinedAddresses()
|
||||
{
|
||||
var addresses = new List<string>(2);
|
||||
if (!string.IsNullOrWhiteSpace(HTTPAddress))
|
||||
{
|
||||
addresses.Add("http=" + HTTPAddress);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(HTTPSAddress))
|
||||
{
|
||||
addresses.Add("https=" + HTTPSAddress);
|
||||
}
|
||||
return string.Join(";", addresses);
|
||||
}
|
||||
private static string GetJoinedOverrides()
|
||||
{
|
||||
var overrides = new List<string>(Overrides);
|
||||
if (IsIgnoringLocalTraffic)
|
||||
{
|
||||
overrides.Add("<local>");
|
||||
}
|
||||
return string.Join(";", overrides);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
private struct INETOption
|
||||
{
|
||||
private readonly OptionKind _kind;
|
||||
private readonly INETOptionValue _value;
|
||||
|
||||
public INETOption(OptionKind kind, int value)
|
||||
{
|
||||
_kind = kind;
|
||||
_value = CreateValue(value);
|
||||
}
|
||||
public INETOption(OptionKind kind, string value)
|
||||
{
|
||||
_kind = kind;
|
||||
_value = CreateValue(value);
|
||||
}
|
||||
|
||||
private static INETOptionValue CreateValue(int value)
|
||||
{
|
||||
return new INETOptionValue
|
||||
{
|
||||
_intValue = value
|
||||
};
|
||||
}
|
||||
private static INETOptionValue CreateValue(string value)
|
||||
{
|
||||
return new INETOptionValue
|
||||
{
|
||||
_stringPointer = Marshal.StringToHGlobalAuto(value)
|
||||
};
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct INETOptionValue
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int _intValue;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public IntPtr _stringPointer;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public System.Runtime.InteropServices.ComTypes.FILETIME _fileTime;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
private struct INETPackage
|
||||
{
|
||||
public int _size;
|
||||
public IntPtr _connection;
|
||||
public int _optionCount;
|
||||
public int _optionError;
|
||||
public IntPtr _optionsPtr;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum ProxyKind
|
||||
{
|
||||
PROXY_TYPE_DIRECT = 1,
|
||||
PROXY_TYPE_PROXY = 2,
|
||||
PROXY_TYPE_AUTO_PROXY_URL = 4,
|
||||
PROXY_TYPE_AUTO_DETECT = 8
|
||||
}
|
||||
private enum OptionKind
|
||||
{
|
||||
INTERNET_PER_CONN_FLAGS = 1,
|
||||
INTERNET_PER_CONN_PROXY_SERVER = 2,
|
||||
INTERNET_PER_CONN_PROXY_BYPASS = 3,
|
||||
INTERNET_PER_CONN_AUTOCONFIG_URL = 4
|
||||
}
|
||||
}
|
||||
}
|
12
Source/GrasscutterTools/Eavesdrop/Internals/NativeMethods.cs
Normal file
12
Source/GrasscutterTools/Eavesdrop/Internals/NativeMethods.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Eavesdrop
|
||||
{
|
||||
internal static class NativeMethods
|
||||
{
|
||||
[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
|
||||
}
|
||||
}
|
21
Source/GrasscutterTools/Eavesdrop/LICENSE.md
Normal file
21
Source/GrasscutterTools/Eavesdrop/LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ArachisH
|
||||
|
||||
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.
|
365
Source/GrasscutterTools/Eavesdrop/Network/EavesNode.cs
Normal file
365
Source/GrasscutterTools/Eavesdrop/Network/EavesNode.cs
Normal file
@ -0,0 +1,365 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Net.Security;
|
||||
using System.Globalization;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Authentication;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
//using BrotliSharpLib;
|
||||
|
||||
namespace Eavesdrop.Network
|
||||
{
|
||||
public class EavesNode : IDisposable
|
||||
{
|
||||
private SslStream _secureStream;
|
||||
private readonly TcpClient _client;
|
||||
private readonly Certifier _certifier;
|
||||
private static readonly Regex _responseCookieSplitter;
|
||||
|
||||
public bool IsSecure => (_secureStream != null);
|
||||
|
||||
static EavesNode()
|
||||
{
|
||||
_responseCookieSplitter = new Regex(",(?! )");
|
||||
}
|
||||
public EavesNode(Certifier certifier, TcpClient client)
|
||||
{
|
||||
_client = client;
|
||||
_certifier = certifier;
|
||||
|
||||
_client.NoDelay = true;
|
||||
}
|
||||
|
||||
public Task<HttpWebRequest> ReadRequestAsync()
|
||||
{
|
||||
return ReadRequestAsync(null);
|
||||
}
|
||||
private async Task<HttpWebRequest> ReadRequestAsync(Uri baseUri)
|
||||
{
|
||||
string method = null;
|
||||
var headers = new List<string>();
|
||||
string requestUrl = baseUri?.OriginalString;
|
||||
|
||||
string command = ReadNonBufferedLine();
|
||||
if (string.IsNullOrWhiteSpace(command)) return null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(command)) return null;
|
||||
string[] values = command.Split(' ');
|
||||
|
||||
method = values[0];
|
||||
requestUrl += values[1];
|
||||
while (_client.Connected)
|
||||
{
|
||||
string header = ReadNonBufferedLine();
|
||||
if (string.IsNullOrWhiteSpace(header)) break;
|
||||
|
||||
headers.Add(header);
|
||||
}
|
||||
|
||||
if (method == "CONNECT")
|
||||
{
|
||||
baseUri = new Uri("https://" + requestUrl);
|
||||
await SendResponseAsync(HttpStatusCode.OK).ConfigureAwait(false);
|
||||
|
||||
if (!SecureTunnel(baseUri.Host)) return null;
|
||||
return await ReadRequestAsync(baseUri).ConfigureAwait(false);
|
||||
}
|
||||
else return CreateRequest(method, headers, new Uri(requestUrl));
|
||||
}
|
||||
|
||||
public async Task<ByteArrayContent> ReadRequestContentAsync(WebRequest request)
|
||||
{
|
||||
byte[] payload = await GetPayload(GetStream(), request.ContentLength).ConfigureAwait(false);
|
||||
if (payload == null) return null;
|
||||
|
||||
//if (request.Headers[HttpRequestHeader.ContentEncoding] == "br")
|
||||
//{
|
||||
// request.Headers[HttpRequestHeader.ContentEncoding] = ""; // No longer encoded.
|
||||
// payload = Brotli.DecompressBuffer(payload, 0, payload.Length);
|
||||
//}
|
||||
return new ByteArrayContent(payload);
|
||||
}
|
||||
public async Task WriteRequestContentAsync(WebRequest request, HttpContent content)
|
||||
{
|
||||
byte[] payload = null;
|
||||
if (content is StreamContent streamContent)
|
||||
{
|
||||
// TODO:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else payload = await content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
|
||||
//if (request.Headers[HttpRequestHeader.ContentEncoding] == "br")
|
||||
//{
|
||||
// payload = Brotli.CompressBuffer(payload, 0, payload.Length);
|
||||
//}
|
||||
|
||||
request.ContentLength = payload.Length;
|
||||
using (Stream output = await request.GetRequestStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
await output.WriteAsync(payload, 0, payload.Length).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task SendResponseAsync(WebResponse response, HttpContent content)
|
||||
{
|
||||
string description = "OK";
|
||||
var status = HttpStatusCode.OK;
|
||||
if (response is HttpWebResponse httpResponse)
|
||||
{
|
||||
status = httpResponse.StatusCode;
|
||||
description = httpResponse.StatusDescription;
|
||||
}
|
||||
return SendResponseAsync(status, description, response.Headers, content);
|
||||
}
|
||||
public Task SendResponseAsync(HttpStatusCode status, string description = null)
|
||||
{
|
||||
return SendResponseAsync(status, (description ?? status.ToString()), null, null);
|
||||
}
|
||||
public async Task SendResponseAsync(HttpStatusCode status, string description, WebHeaderCollection headers, HttpContent content)
|
||||
{
|
||||
var headerBuilder = new StringBuilder();
|
||||
headerBuilder.AppendLine($"HTTP/{HttpVersion.Version10} {(int)status} {description}");
|
||||
if (headers != null)
|
||||
{
|
||||
foreach (string header in headers.AllKeys)
|
||||
{
|
||||
if (header == "Transfer-Encoding") continue;
|
||||
|
||||
string value = headers[header];
|
||||
if (string.IsNullOrWhiteSpace(value)) continue;
|
||||
|
||||
if (header.Equals("Set-Cookie", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (string setCookie in _responseCookieSplitter.Split(value))
|
||||
{
|
||||
headerBuilder.AppendLine($"{header}: {setCookie}");
|
||||
}
|
||||
}
|
||||
else headerBuilder.AppendLine($"{header}: {value}");
|
||||
}
|
||||
}
|
||||
headerBuilder.AppendLine();
|
||||
|
||||
byte[] headerData = Encoding.UTF8.GetBytes(headerBuilder.ToString());
|
||||
await GetStream().WriteAsync(headerData, 0, headerData.Length).ConfigureAwait(false);
|
||||
if (content != null)
|
||||
{
|
||||
// TODO: If the Content-Encoding header has been changed, re-compress while writing?
|
||||
Stream input = await content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
|
||||
int bytesRead = 0;
|
||||
var buffer = new byte[8192];
|
||||
do
|
||||
{
|
||||
bytesRead = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||
if (_client.Connected && bytesRead > 0)
|
||||
{
|
||||
await GetStream().WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
|
||||
}
|
||||
else return;
|
||||
}
|
||||
while (input.CanRead && _client.Connected);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream()
|
||||
{
|
||||
return ((Stream)_secureStream ?? _client.GetStream());
|
||||
}
|
||||
private StreamWriter WrapStreamWriter()
|
||||
{
|
||||
return new StreamWriter(GetStream(), Encoding.UTF8, 1024, true);
|
||||
}
|
||||
private StreamReader WrapStreamReader(int bufferSize = 1024)
|
||||
{
|
||||
return new StreamReader(GetStream(), Encoding.UTF8, true, bufferSize, true);
|
||||
}
|
||||
|
||||
private string ReadNonBufferedLine()
|
||||
{
|
||||
string line = string.Empty;
|
||||
try
|
||||
{
|
||||
using (var binaryInput = new BinaryReader(GetStream(), Encoding.UTF8, true))
|
||||
{
|
||||
do { line += binaryInput.ReadChar(); }
|
||||
while (!line.EndsWith("\r\n"));
|
||||
}
|
||||
}
|
||||
catch (EndOfStreamException) { line += "\r\n"; }
|
||||
return line.Substring(0, line.Length - 2);
|
||||
}
|
||||
private bool SecureTunnel(string host)
|
||||
{
|
||||
try
|
||||
{
|
||||
X509Certificate2 certificate = _certifier.GenerateCertificate(host);
|
||||
|
||||
_secureStream = new SslStream(GetStream());
|
||||
_secureStream.AuthenticateAsServer(certificate, false, SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
private IEnumerable<Cookie> GetCookies(string cookieHeader, string host)
|
||||
{
|
||||
foreach (string cookie in cookieHeader.Split(';'))
|
||||
{
|
||||
int nameEndIndex = cookie.IndexOf('=');
|
||||
if (nameEndIndex == -1) continue;
|
||||
|
||||
string name = cookie.Substring(0, nameEndIndex).Trim();
|
||||
string value = cookie.Substring(nameEndIndex + 1).Trim();
|
||||
|
||||
yield return new Cookie(name, value, "/", host);
|
||||
}
|
||||
}
|
||||
private HttpWebRequest CreateRequest(string method, List<string> headers, Uri requestUri)
|
||||
{
|
||||
HttpWebRequest request = WebRequest.CreateHttp(requestUri);
|
||||
request.ProtocolVersion = HttpVersion.Version10;
|
||||
request.CookieContainer = new CookieContainer();
|
||||
request.AllowAutoRedirect = false;
|
||||
request.KeepAlive = false;
|
||||
request.Method = method;
|
||||
request.Proxy = null;
|
||||
|
||||
foreach (string header in headers)
|
||||
{
|
||||
int delimiterIndex = header.IndexOf(':');
|
||||
if (delimiterIndex == -1) continue;
|
||||
|
||||
string name = header.Substring(0, delimiterIndex);
|
||||
string value = header.Substring(delimiterIndex + 2);
|
||||
switch (name.ToLower())
|
||||
{
|
||||
case "range":
|
||||
case "expect":
|
||||
case "keep-alive":
|
||||
case "connection":
|
||||
case "proxy-connection": break;
|
||||
|
||||
case "host": request.Host = value; break;
|
||||
case "accept": request.Accept = value; break;
|
||||
case "referer": request.Referer = value; break;
|
||||
case "user-agent": request.UserAgent = value; break;
|
||||
case "content-type": request.ContentType = value; break;
|
||||
|
||||
case "content-length":
|
||||
{
|
||||
request.ContentLength =
|
||||
long.Parse(value, CultureInfo.InvariantCulture);
|
||||
|
||||
break;
|
||||
}
|
||||
case "cookie":
|
||||
{
|
||||
foreach (Cookie cookie in GetCookies(value, request.Host))
|
||||
{
|
||||
try
|
||||
{
|
||||
request.CookieContainer.Add(cookie);
|
||||
}
|
||||
catch (CookieException) { }
|
||||
}
|
||||
request.Headers[name] = value;
|
||||
break;
|
||||
}
|
||||
case "if-modified-since":
|
||||
{
|
||||
request.IfModifiedSince = DateTime.Parse(
|
||||
value.Split(';')[0], CultureInfo.InvariantCulture);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "date":
|
||||
if (long.TryParse(value, out var timestamp))
|
||||
{
|
||||
request.Date = timestamp > 10_000_000_000L
|
||||
? DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime
|
||||
: DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Date = DateTime.Parse(value);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
request.Headers[name] = value; break;
|
||||
}
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
GetStream().Dispose();
|
||||
_client.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static StreamContent ReadResponseContent(WebResponse response)
|
||||
{
|
||||
if (response.ContentLength == 0)
|
||||
{
|
||||
response.GetResponseStream().Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
Stream input = response.GetResponseStream();
|
||||
//if (response is HttpWebResponse httpResponse && !string.IsNullOrWhiteSpace(httpResponse.ContentEncoding))
|
||||
//{
|
||||
// switch (httpResponse.ContentEncoding)
|
||||
// {
|
||||
// //case "br": input = new BrotliStream(input, CompressionMode.Decompress); break;
|
||||
// case "gzip": input = new GZipStream(input, CompressionMode.Decompress); break;
|
||||
// case "deflate": input = new DeflateStream(input, CompressionMode.Decompress); break;
|
||||
// }
|
||||
// response.Headers.Remove(HttpResponseHeader.ContentLength);
|
||||
// response.Headers.Remove(HttpResponseHeader.ContentEncoding);
|
||||
// response.Headers.Add(HttpResponseHeader.TransferEncoding, "chunked");
|
||||
//}
|
||||
return new StreamContent(input, response.ContentLength > 0 ? (int)response.ContentLength : 4096);
|
||||
}
|
||||
public static async Task<byte[]> GetPayload(Stream input, long length)
|
||||
{
|
||||
if (length < 1) return null;
|
||||
|
||||
int totalBytesRead = 0;
|
||||
int nullBytesReadCount = 0;
|
||||
var payload = new byte[length];
|
||||
do
|
||||
{
|
||||
int bytesLeft = (payload.Length - totalBytesRead);
|
||||
int bytesRead = await input.ReadAsync(payload, totalBytesRead, bytesLeft).ConfigureAwait(false);
|
||||
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
nullBytesReadCount = 0;
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
else if (++nullBytesReadCount >= 2) return null;
|
||||
}
|
||||
while (totalBytesRead != payload.Length);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
}
|
@ -40,6 +40,8 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>11</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
@ -49,6 +51,8 @@
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>11</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Resources\IconGrasscutter.ico</ApplicationIcon>
|
||||
@ -87,6 +91,14 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="DispatchServer\DispatchServerAPI.cs" />
|
||||
<Compile Include="DispatchServer\Model\ServerStatus.cs" />
|
||||
<Compile Include="Eavesdrop\Certifier.cs" />
|
||||
<Compile Include="Eavesdrop\Eavesdropper.cs" />
|
||||
<Compile Include="Eavesdrop\Event Args\RequestInterceptedEventArgs.cs" />
|
||||
<Compile Include="Eavesdrop\Event Args\ResponseInterceptedEventArgs.cs" />
|
||||
<Compile Include="Eavesdrop\Interceptors.cs" />
|
||||
<Compile Include="Eavesdrop\Internals\INETOptions.cs" />
|
||||
<Compile Include="Eavesdrop\Internals\NativeMethods.cs" />
|
||||
<Compile Include="Eavesdrop\Network\EavesNode.cs" />
|
||||
<Compile Include="Forms\FormActivityEditor.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@ -319,6 +331,7 @@
|
||||
<Compile Include="Utils\HttpHelper.cs" />
|
||||
<Compile Include="Utils\Logger.cs" />
|
||||
<Compile Include="Utils\GithubHelper.cs" />
|
||||
<Compile Include="Utils\ProxyHelper.cs" />
|
||||
<Compile Include="Utils\SparseSet.cs" />
|
||||
<Compile Include="Utils\ToggleParser.cs" />
|
||||
<Compile Include="Utils\UIUtil.cs" />
|
||||
@ -669,6 +682,7 @@
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
</Compile>
|
||||
<None Include="Eavesdrop\LICENSE.md" />
|
||||
<None Include="GrasscutterTools.licenseheader" />
|
||||
<None Include="GrasscutterTools.snk" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
|
@ -60,6 +60,7 @@
|
||||
this.TxtHost = new System.Windows.Forms.ComboBox();
|
||||
this.BtnQueryServerStatus = new System.Windows.Forms.Button();
|
||||
this.LblHost = new System.Windows.Forms.Label();
|
||||
this.BtnProxy = new System.Windows.Forms.Button();
|
||||
this.GrpServerStatus.SuspendLayout();
|
||||
this.GrpRemoteCommand.SuspendLayout();
|
||||
this.TPOpenCommandCheck.SuspendLayout();
|
||||
@ -286,8 +287,8 @@
|
||||
//
|
||||
resources.ApplyResources(this.TxtHost, "TxtHost");
|
||||
this.TxtHost.Name = "TxtHost";
|
||||
this.TxtHost.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TxtHost_KeyDown);
|
||||
this.TxtHost.SelectedIndexChanged += new System.EventHandler(this.TxtHost_SelectedIndexChanged);
|
||||
this.TxtHost.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TxtHost_KeyDown);
|
||||
//
|
||||
// BtnQueryServerStatus
|
||||
//
|
||||
@ -301,10 +302,18 @@
|
||||
resources.ApplyResources(this.LblHost, "LblHost");
|
||||
this.LblHost.Name = "LblHost";
|
||||
//
|
||||
// BtnProxy
|
||||
//
|
||||
resources.ApplyResources(this.BtnProxy, "BtnProxy");
|
||||
this.BtnProxy.Name = "BtnProxy";
|
||||
this.BtnProxy.UseVisualStyleBackColor = true;
|
||||
this.BtnProxy.Click += new System.EventHandler(this.BtnProxy_Click);
|
||||
//
|
||||
// PageOpenCommand
|
||||
//
|
||||
resources.ApplyResources(this, "$this");
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.BtnProxy);
|
||||
this.Controls.Add(this.LnkLinks);
|
||||
this.Controls.Add(this.LnkGOODHelp);
|
||||
this.Controls.Add(this.LnkInventoryKamera);
|
||||
@ -365,5 +374,6 @@
|
||||
private System.Windows.Forms.ComboBox TxtHost;
|
||||
private System.Windows.Forms.Button BtnQueryServerStatus;
|
||||
private System.Windows.Forms.Label LblHost;
|
||||
private System.Windows.Forms.Button BtnProxy;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Eavesdrop;
|
||||
|
||||
using GrasscutterTools.DispatchServer;
|
||||
using GrasscutterTools.DispatchServer.Model;
|
||||
using GrasscutterTools.Game;
|
||||
@ -47,6 +49,8 @@ namespace GrasscutterTools.Pages
|
||||
if (DesignMode) return;
|
||||
|
||||
InitServerRecords();
|
||||
if (!string.IsNullOrEmpty(Settings.Default.Host))
|
||||
TxtHost.Items.Add(Settings.Default.Host);
|
||||
TxtHost.Items.AddRange(ServerRecords.Select(it => it.Host).ToArray());
|
||||
|
||||
NUDRemotePlayerId.Value = Settings.Default.RemoteUid;
|
||||
@ -57,6 +61,14 @@ namespace GrasscutterTools.Pages
|
||||
TxtToken.Text = Settings.Default.TokenCache;
|
||||
Task.Delay(1000).ContinueWith(_ => ShowTipInRunButton?.Invoke(Resources.TokenRestoredFromCache));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(TxtHost.Text))
|
||||
{
|
||||
TxtHost.Items.Add("http://127.0.0.1:443");
|
||||
TxtHost.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
BtnProxy.Text = Resources.StartProxy;
|
||||
}
|
||||
|
||||
#region - 服务器记录 -
|
||||
@ -161,6 +173,19 @@ namespace GrasscutterTools.Pages
|
||||
Settings.Default.RemoteUid = NUDRemotePlayerId.Value;
|
||||
Settings.Default.Host = TxtHost.Text;
|
||||
Settings.Default.TokenCache = Common.OC?.Token;
|
||||
|
||||
try
|
||||
{
|
||||
Logger.I(TAG, "Stop Proxy");
|
||||
ProxyHelper.StopProxy();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
MessageBox.Show(ex.ToString(), Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
#endif
|
||||
Logger.E(TAG, "Stop Proxy Failed.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -240,6 +265,7 @@ namespace GrasscutterTools.Pages
|
||||
MessageBox.Show(ex.Message, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (isOcEnabled)
|
||||
{
|
||||
LblOpenCommandSupport.Text = "√";
|
||||
@ -252,6 +278,8 @@ namespace GrasscutterTools.Pages
|
||||
LblOpenCommandSupport.ForeColor = Color.Red;
|
||||
GrpRemoteCommand.Enabled = false;
|
||||
}
|
||||
|
||||
BtnProxy.Enabled = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -583,5 +611,44 @@ namespace GrasscutterTools.Pages
|
||||
}
|
||||
|
||||
#endregion - 导入存档 GOOD -
|
||||
|
||||
|
||||
#region - 代理 Porxy -
|
||||
|
||||
/// <summary>
|
||||
/// 点击代理按钮时触发
|
||||
/// </summary>
|
||||
private void BtnProxy_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 正在运行则关闭
|
||||
if (ProxyHelper.IsRunning)
|
||||
{
|
||||
ProxyHelper.StopProxy();
|
||||
BtnProxy.Text = Resources.StartProxy;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 创建根证书并检查信任
|
||||
if (!ProxyHelper.CheckAndCreateCertifier())
|
||||
{
|
||||
MessageBox.Show("必须先信任根证书才能继续", Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动代理
|
||||
ProxyHelper.StartProxy(Common.OC.Host);
|
||||
BtnProxy.Text = Resources.StopProxy;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.E(TAG, "Start Proxy failed.", ex);
|
||||
MessageBox.Show(ex.Message, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -151,7 +151,7 @@
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>LnkLinks.ZOrder" xml:space="preserve">
|
||||
<value>0</value>
|
||||
<value>1</value>
|
||||
</data>
|
||||
<data name="LnkGOODHelp.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>None</value>
|
||||
@ -184,7 +184,7 @@
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>LnkGOODHelp.ZOrder" xml:space="preserve">
|
||||
<value>1</value>
|
||||
<value>2</value>
|
||||
</data>
|
||||
<data name="LnkInventoryKamera.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>None</value>
|
||||
@ -214,7 +214,7 @@
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>LnkInventoryKamera.ZOrder" xml:space="preserve">
|
||||
<value>2</value>
|
||||
<value>3</value>
|
||||
</data>
|
||||
<data name="LblGOODHelp.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>None</value>
|
||||
@ -244,7 +244,7 @@
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>LblGOODHelp.ZOrder" xml:space="preserve">
|
||||
<value>3</value>
|
||||
<value>4</value>
|
||||
</data>
|
||||
<data name="ButtonOpenGOODImport.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>None</value>
|
||||
@ -274,7 +274,7 @@
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>ButtonOpenGOODImport.ZOrder" xml:space="preserve">
|
||||
<value>4</value>
|
||||
<value>5</value>
|
||||
</data>
|
||||
<data name="LblHostTip.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>None</value>
|
||||
@ -307,7 +307,7 @@
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>LblHostTip.ZOrder" xml:space="preserve">
|
||||
<value>5</value>
|
||||
<value>6</value>
|
||||
</data>
|
||||
<data name="GrpServerStatus.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>None</value>
|
||||
@ -514,7 +514,7 @@
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>GrpServerStatus.ZOrder" xml:space="preserve">
|
||||
<value>6</value>
|
||||
<value>7</value>
|
||||
</data>
|
||||
<data name="GrpRemoteCommand.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>None</value>
|
||||
@ -923,7 +923,7 @@
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>GrpRemoteCommand.ZOrder" xml:space="preserve">
|
||||
<value>7</value>
|
||||
<value>8</value>
|
||||
</data>
|
||||
<data name="TxtHost.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>None</value>
|
||||
@ -932,7 +932,7 @@
|
||||
<value>136, 34</value>
|
||||
</data>
|
||||
<data name="TxtHost.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>182, 23</value>
|
||||
<value>182, 25</value>
|
||||
</data>
|
||||
<data name="TxtHost.TabIndex" type="System.Int32, mscorlib">
|
||||
<value>2</value>
|
||||
@ -941,13 +941,13 @@
|
||||
<value>TxtHost</value>
|
||||
</data>
|
||||
<data name=">>TxtHost.Type" xml:space="preserve">
|
||||
<value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name=">>TxtHost.Parent" xml:space="preserve">
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>TxtHost.ZOrder" xml:space="preserve">
|
||||
<value>8</value>
|
||||
<value>9</value>
|
||||
</data>
|
||||
<data name="BtnQueryServerStatus.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>None</value>
|
||||
@ -977,7 +977,7 @@
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>BtnQueryServerStatus.ZOrder" xml:space="preserve">
|
||||
<value>9</value>
|
||||
<value>10</value>
|
||||
</data>
|
||||
<data name="LblHost.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>None</value>
|
||||
@ -1010,7 +1010,34 @@
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>LblHost.ZOrder" xml:space="preserve">
|
||||
<value>10</value>
|
||||
<value>11</value>
|
||||
</data>
|
||||
<data name="BtnProxy.Enabled" type="System.Boolean, mscorlib">
|
||||
<value>False</value>
|
||||
</data>
|
||||
<data name="BtnProxy.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>3, 3</value>
|
||||
</data>
|
||||
<data name="BtnProxy.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>120, 25</value>
|
||||
</data>
|
||||
<data name="BtnProxy.TabIndex" type="System.Int32, mscorlib">
|
||||
<value>11</value>
|
||||
</data>
|
||||
<data name="BtnProxy.Text" xml:space="preserve">
|
||||
<value>启动代理</value>
|
||||
</data>
|
||||
<data name=">>BtnProxy.Name" xml:space="preserve">
|
||||
<value>BtnProxy</value>
|
||||
</data>
|
||||
<data name=">>BtnProxy.Type" xml:space="preserve">
|
||||
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name=">>BtnProxy.Parent" xml:space="preserve">
|
||||
<value>$this</value>
|
||||
</data>
|
||||
<data name=">>BtnProxy.ZOrder" xml:space="preserve">
|
||||
<value>0</value>
|
||||
</data>
|
||||
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
@ -1022,6 +1049,6 @@
|
||||
<value>PageOpenCommand</value>
|
||||
</data>
|
||||
<data name=">>$this.Type" xml:space="preserve">
|
||||
<value>GrasscutterTools.Pages.BasePage, GrasscutterTools, Version=1.7.4.0, Culture=neutral, PublicKeyToken=de2b1c089621e923</value>
|
||||
<value>GrasscutterTools.Pages.BasePage, GrasscutterTools, Version=1.13.0.0, Culture=neutral, PublicKeyToken=de2b1c089621e923</value>
|
||||
</data>
|
||||
</root>
|
@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
|
||||
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
|
||||
//通过使用 "*",如下所示:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.12.2")]
|
||||
[assembly: AssemblyFileVersion("1.12.2")]
|
||||
[assembly: AssemblyVersion("1.13.0")]
|
||||
[assembly: AssemblyFileVersion("1.13.0")]
|
129
Source/GrasscutterTools/Properties/Resources.Designer.cs
generated
129
Source/GrasscutterTools/Properties/Resources.Designer.cs
generated
@ -112,6 +112,8 @@ namespace GrasscutterTools.Properties {
|
||||
///2020:3.5风花的呼吸
|
||||
///2021:3.6盛典与慧业
|
||||
///2022:3.7决战!召唤之巅!
|
||||
///2023:3.8清夏!乐园?大秘境!
|
||||
///2024:4.0机枢巧物前哨战
|
||||
///// 1.0
|
||||
///1001:海灯节
|
||||
///5001:元素烘炉(test)
|
||||
@ -120,10 +122,7 @@ namespace GrasscutterTools.Properties {
|
||||
///// 1.1
|
||||
///5004:映天之章
|
||||
///5005:元素烘炉
|
||||
///5006:佳肴尚温
|
||||
///5007:飞行挑战
|
||||
///5009:古闻之章(钟离传说-1)
|
||||
///50 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
///5006:佳 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string Activity {
|
||||
get {
|
||||
@ -195,6 +194,8 @@ namespace GrasscutterTools.Properties {
|
||||
///28:乐园遗落之花
|
||||
///29:水仙之梦
|
||||
///30:花海甘露之光
|
||||
///31:逐影猎人
|
||||
///32:黄金剧团
|
||||
///51:行者之心
|
||||
///52:勇士之心
|
||||
///53:守护之心
|
||||
@ -347,9 +348,9 @@ namespace GrasscutterTools.Properties {
|
||||
/// <summary>
|
||||
/// 查找类似 1002:神里绫华
|
||||
///1003:琴
|
||||
///1005:空
|
||||
///1005:男主
|
||||
///1006:丽莎
|
||||
///1007:荧
|
||||
///1007:女主
|
||||
///1014:芭芭拉
|
||||
///1015:凯亚
|
||||
///1016:迪卢克
|
||||
@ -392,13 +393,13 @@ namespace GrasscutterTools.Properties {
|
||||
///1058:八重神子
|
||||
///1059:鹿野院平藏
|
||||
///1060:夜兰
|
||||
///1061:绮良良
|
||||
///1062:埃洛伊
|
||||
///1063:申鹤
|
||||
///1064:云堇
|
||||
///1065:久岐忍
|
||||
///1066:神里绫人
|
||||
///1067: [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
///1067:柯莱
|
||||
///1068 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string Avatar {
|
||||
get {
|
||||
@ -407,8 +408,7 @@ namespace GrasscutterTools.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 1001:4
|
||||
///1002:5
|
||||
/// 查找类似 1002:5
|
||||
///1003:5
|
||||
///1005:5
|
||||
///1006:4
|
||||
@ -455,6 +455,7 @@ namespace GrasscutterTools.Properties {
|
||||
///1058:5
|
||||
///1059:4
|
||||
///1060:5
|
||||
///1061:4
|
||||
///1062:5
|
||||
///1063:5
|
||||
///1064:4
|
||||
@ -843,33 +844,32 @@ namespace GrasscutterTools.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 // 特殊能力
|
||||
///44000001:使用冲刺后会在原地留下一个n秒后爆炸的能量波
|
||||
///44000002:飞行挑战玩法:空中冲刺 前冲
|
||||
///44000003:飞行挑战玩法:空中冲刺 上冲1
|
||||
///44000004:飞行挑战玩法:空中冲刺 上冲2
|
||||
///44000005:使用冲刺后会在原地留下一个n秒后爆炸的能量波
|
||||
///44000006:范围内回复生命、能量、复活
|
||||
///44000007:使用冲刺后会在原地留下一个n秒后爆炸的能量波
|
||||
///44000008:没有敌人时提升输出
|
||||
///44000009:没有敌人时提升输出
|
||||
///44000010:范围伤害
|
||||
///44000100:备用02
|
||||
///44000101:吐泡泡子弹(远)
|
||||
///44000102:吐泡泡子弹(近)
|
||||
///44000103:备用02
|
||||
///44000104:备用02
|
||||
///44000105:捉迷藏能量球
|
||||
///44000106:捉迷藏-技能-引导
|
||||
///44000107:捉迷藏-技能-诱饵
|
||||
///44000108:捉迷藏-牢
|
||||
///44000109:捉迷藏-牢
|
||||
///44000110:羽球节
|
||||
///44000111:羽球节
|
||||
///44000112:羽球节
|
||||
///44000113:羽球节
|
||||
///44000114:羽球节
|
||||
///440001 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// 查找类似 // 机关装置
|
||||
///40000001:蒸汽桶炮
|
||||
///70220032:风车机关
|
||||
///70220033:风车机关02-永动风车机关
|
||||
///70290584:升降铁栅栏0.8倍版-升降铁栅栏
|
||||
///70310300:风元素交互 水方碑
|
||||
///70310301:火元素交互 水方碑
|
||||
///70320001:旋转喷火机关
|
||||
///70320002:单面喷火机关
|
||||
///70320003:地城循环点光源
|
||||
///70330086:雷元素溢出洞口
|
||||
///70330332:沙漠 火元素方碑-沙漠 火元素方碑
|
||||
///70330400:沙漠 雷元素方碑
|
||||
///70330401:沙漠 冰元素方碑
|
||||
///70330402:沙漠 风元素方碑
|
||||
///70330403:沙漠 水元素方碑
|
||||
///70330404:沙漠 草元素方碑
|
||||
///70330405:沙漠 岩元素方碑
|
||||
///70330441:海市蜃楼-火元素方碑
|
||||
///70350001:地城大门01-原始门(废弃)
|
||||
///70350002:地城大门02-地城大门 倒品 大
|
||||
///70350003:地城大门03-地城大门 倒品 小
|
||||
///70350004:升降铁栅栏
|
||||
///70350005:横向机关门
|
||||
///70350006:升降铁栅栏-升降铁栅栏 大
|
||||
///70350007:丘丘人升降 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string Gadget {
|
||||
get {
|
||||
@ -953,10 +953,7 @@ namespace GrasscutterTools.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 // Items
|
||||
///
|
||||
///
|
||||
///// 虚拟物品
|
||||
/// 查找类似 // 虚拟道具
|
||||
///101:角色经验
|
||||
///102:冒险阅历
|
||||
///105:好感经验
|
||||
@ -1004,7 +1001,8 @@ namespace GrasscutterTools.Properties {
|
||||
///147:节庆热度
|
||||
///148:营业收入
|
||||
///149:可用资金
|
||||
///150: [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
///150:巧策灵感
|
||||
///151:蘑菇宝钱 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string Item {
|
||||
get {
|
||||
@ -1370,6 +1368,7 @@ namespace GrasscutterTools.Properties {
|
||||
///6:层岩巨渊·地下矿区
|
||||
///7:三界路飨祭
|
||||
///9:金苹果群岛(2.8)
|
||||
///10:Penumbra_LevelStreaming
|
||||
///1001:移动平台性能测试(test)
|
||||
///1002:攀爬测试2
|
||||
///1003:TheBigWorld
|
||||
@ -1386,12 +1385,10 @@ namespace GrasscutterTools.Properties {
|
||||
///1018:Chateau
|
||||
///1019:洞天云海地城玩法测试(test)
|
||||
///1023:Level_Yurenzhong
|
||||
///1024:黑夜循环地城(test)
|
||||
///1024:突破:清扫遗迹中的魔物(test)
|
||||
///1030:TestIntercept_LiYue
|
||||
///1031:爬塔丘丘人模板(test)
|
||||
///1032:云海白盒测试(test)
|
||||
///1033:Indoor_Ly_Bank
|
||||
///1 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
///1032:云海白 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string Scene {
|
||||
get {
|
||||
@ -1490,6 +1487,24 @@ namespace GrasscutterTools.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 启动代理 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string StartProxy {
|
||||
get {
|
||||
return ResourceManager.GetString("StartProxy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 关闭代理 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string StopProxy {
|
||||
get {
|
||||
return ResourceManager.GetString("StopProxy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 任务已经启动,无法操作 的本地化字符串。
|
||||
/// </summary>
|
||||
@ -1574,6 +1589,9 @@ namespace GrasscutterTools.Properties {
|
||||
///11420:「一心传」名刀
|
||||
///11421:「一心传」名刀
|
||||
///11422:东花坊时雨
|
||||
///11424:狼牙
|
||||
///11425:海渊终曲
|
||||
///11426:灰河渡手
|
||||
///11501:风鹰剑
|
||||
///11502:天空之刃
|
||||
///11503:苍古自由之誓
|
||||
@ -1584,10 +1602,7 @@ namespace GrasscutterTools.Properties {
|
||||
///11511:圣显之钥
|
||||
///11512:裁叶萃光
|
||||
///12101:训练大剑
|
||||
///12201:佣兵重剑
|
||||
///12301:铁影阔剑
|
||||
///12302:沐浴龙血的剑
|
||||
///12 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
///12201: [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string Weapon {
|
||||
get {
|
||||
@ -1596,7 +1611,9 @@ namespace GrasscutterTools.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 11301:blue
|
||||
/// 查找类似 11101:blue
|
||||
///11201:blue
|
||||
///11301:blue
|
||||
///11302:blue
|
||||
///11303:blue
|
||||
///11304:blue
|
||||
@ -1623,17 +1640,15 @@ namespace GrasscutterTools.Properties {
|
||||
///11420:purple
|
||||
///11421:purple
|
||||
///11422:purple
|
||||
///11424:purple
|
||||
///11425:purple
|
||||
///11426:purple
|
||||
///11501:yellow
|
||||
///11502:yellow
|
||||
///11503:yellow
|
||||
///11504:yellow
|
||||
///11505:yellow
|
||||
///11509:yellow
|
||||
///11510:yellow
|
||||
///11511:yellow
|
||||
///11512:yellow
|
||||
///12301:blue
|
||||
///12302:bl [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
///11509:yell [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string WeaponColor {
|
||||
get {
|
||||
|
@ -366,4 +366,10 @@ Improvement suggestions have been submitted, please use caution to send emails t
|
||||
<data name="Gadget" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\en-us\Gadget.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
|
||||
</data>
|
||||
<data name="StartProxy" xml:space="preserve">
|
||||
<value>Start Proxy</value>
|
||||
</data>
|
||||
<data name="StopProxy" xml:space="preserve">
|
||||
<value>Stop Proxy</value>
|
||||
</data>
|
||||
</root>
|
@ -375,4 +375,10 @@
|
||||
<data name="PageHotKey" xml:space="preserve">
|
||||
<value>快捷键</value>
|
||||
</data>
|
||||
<data name="StartProxy" xml:space="preserve">
|
||||
<value>启动代理</value>
|
||||
</data>
|
||||
<data name="StopProxy" xml:space="preserve">
|
||||
<value>关闭代理</value>
|
||||
</data>
|
||||
</root>
|
@ -354,4 +354,10 @@
|
||||
<data name="Gadget" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\ru-ru\Gadget.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
|
||||
</data>
|
||||
<data name="StartProxy" xml:space="preserve">
|
||||
<value>Запустить прокси</value>
|
||||
</data>
|
||||
<data name="StopProxy" xml:space="preserve">
|
||||
<value>Остановить прокси</value>
|
||||
</data>
|
||||
</root>
|
@ -360,4 +360,10 @@
|
||||
<data name="Gadget" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\zh-tw\Gadget.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
|
||||
</data>
|
||||
<data name="StartProxy" xml:space="preserve">
|
||||
<value>啟動代理</value>
|
||||
</data>
|
||||
<data name="StopProxy" xml:space="preserve">
|
||||
<value>關閉代理</value>
|
||||
</data>
|
||||
</root>
|
@ -31,15 +31,26 @@ namespace GrasscutterTools.Utils
|
||||
|
||||
private static void Write(string message)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"{DateTime.Now:mm:ss.fff} {message}");
|
||||
#else
|
||||
if (IsSaveLogs)
|
||||
{
|
||||
Console.WriteLine($"{DateTime.Now:mm:ss.fff} {message}");
|
||||
File.AppendAllText(LogPath, $"{DateTime.Now:mm:ss.fff} {message}{Environment.NewLine}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void Write(string level, string tag, string message) => Write($"<{level}:{tag}> {message}");
|
||||
|
||||
//public static void Debug(string message) => Write("DEBUG", "Proxy", message);
|
||||
|
||||
//public static void Info(string info) => Write("INFO", "Proxy", info);
|
||||
|
||||
//public static void Error(Exception ex) => Write("ERROR", "Proxy", ex.ToString());
|
||||
|
||||
|
||||
public static void I(string tag, string info) => Write("INFO", tag, info);
|
||||
|
||||
public static void W(string tag, string message) => Write("WARR", tag, message);
|
||||
|
195
Source/GrasscutterTools/Utils/ProxyHelper.cs
Normal file
195
Source/GrasscutterTools/Utils/ProxyHelper.cs
Normal file
@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Grasscutter Tools
|
||||
* Copyright (C) 2023 jie65535
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**/
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Win32;
|
||||
using Eavesdrop;
|
||||
using System.Net;
|
||||
|
||||
namespace GrasscutterTools.Utils
|
||||
{
|
||||
internal static class ProxyHelper
|
||||
{
|
||||
private const string TAG = "Proxy";
|
||||
|
||||
#region - Windows API -
|
||||
|
||||
[DllImport("wininet.dll")]
|
||||
private static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
|
||||
private const int INTERNET_OPTION_SETTINGS_CHANGED = 39;
|
||||
private const int INTERNET_OPTION_REFRESH = 37;
|
||||
|
||||
#endregion
|
||||
|
||||
#region - System Proxy -
|
||||
|
||||
private const string RegKeyInternetSettings = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
|
||||
private const string RegProxyEnable = "ProxyEnable";
|
||||
private const string RegProxyServer = "ProxyServer";
|
||||
private const string RegProxyOverride = "ProxyOverride";
|
||||
|
||||
private const string PassBy =
|
||||
"localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*";
|
||||
|
||||
private static void FlushOs()
|
||||
{
|
||||
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
|
||||
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
|
||||
}
|
||||
|
||||
private static bool _isSetSystemProxy;
|
||||
|
||||
private static void SetSystemProxy(int proxyPort)
|
||||
{
|
||||
using (var reg = Registry.CurrentUser.OpenSubKey(RegKeyInternetSettings, true))
|
||||
{
|
||||
reg.SetValue(RegProxyServer, $"http=127.0.0.1:{proxyPort};https=127.0.0.1:{proxyPort}");
|
||||
reg.SetValue(RegProxyOverride, PassBy);
|
||||
reg.SetValue(RegProxyEnable, 1);
|
||||
}
|
||||
|
||||
_isSetSystemProxy = true;
|
||||
FlushOs();
|
||||
}
|
||||
|
||||
private static void CloseSystemProxy()
|
||||
{
|
||||
if (!_isSetSystemProxy) return;
|
||||
_isSetSystemProxy = false;
|
||||
|
||||
using (var reg = Registry.CurrentUser.OpenSubKey(RegKeyInternetSettings, true))
|
||||
reg.SetValue(RegProxyEnable, 0);
|
||||
FlushOs();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - GS Proxy Server -
|
||||
|
||||
private const string ProxyOverrides =
|
||||
"localhost;1*;" + //" 127.*;10.*;192.168.*;" +
|
||||
"*0;*1;*2;*3;*4;*5;*6;*7;*8;*9;" +
|
||||
"*a;*b;*c;*d;*e;*f;*g;*h;*i;*j;*k;*l;*n;*o;*p;*q;*r;*s;*t;*u;*v;*w;*x;*y;*z" +
|
||||
"*a.com;*b.com;*c.com;*d.com;*f.com;*g.com;*h.com;*i.com;*j.com;*k.com;*l.com;*m.com;*p.com;*q.com;*r.com;*s.com;*t.com;*u.com;*v.com;*w.com;*x.com;*y.com;*z.com;" +
|
||||
"*ae.com;*be.com;*ce.com;*de.com;*fe.com;*ge.com;*pe.com;*te.com;*me.com;*le.com;*ve.com;" +
|
||||
"*ao.com;*bo.com;*eo.com;*go.com;*ke.com;*oo.com;*so.com;*io.com" +
|
||||
"*an.com;*cn.com;*dn.com;*gn.com;*wn.com;*dn.com;*sn.com;*un.com;*in.com";
|
||||
//"*bing*;*google*;*live.com;*office.com;*weibo*;*yahoo*;*taobao*;*go.com;*csdn.com;*msn.com;*aliyun.com;*cdn.com;";
|
||||
//"*ttvnw*;*edge*;*microsoft*;*bing*;*google*;*discordapp*;*gstatic.com;*imgur.com;*hub.*;*gitlab.com;*googleapis.com;*facebook.com;*cloudfront.net;*gvt1.com;*jquery.com;*akamai.net;*ultra-rv.com;*youtube*;*ytimg*;*ggpht*;" +
|
||||
//"*baidu*;*qq*;*sohu*;*weibo*;*163*;*360*;*iqiyi*;*youku*;*bilibili*;*sogou*;*taobao*;*jd*;*zhihu*;*steam*;*ea.com;*csdn*;*.msn.*;*aliyun*;*cdn*;" +
|
||||
//"*twitter.com;*instagram.com;*wikipedia.org;*yahoo*;*xvideos.com;*whatsapp.com;*live.com;*netflix.com;*office.com;*tiktok.com;*reddit.com;*discord*;*twitch*;*duckduckgo.com";
|
||||
|
||||
private static string[] urls =
|
||||
{
|
||||
"hoyoverse.com",
|
||||
"mihoyo.com",
|
||||
"yuanshen.com",
|
||||
};
|
||||
|
||||
private static void StartGsProxyServer(int port)
|
||||
{
|
||||
if (Eavesdropper.IsRunning) return;
|
||||
Eavesdropper.Overrides.Clear();
|
||||
Eavesdropper.Overrides.AddRange(ProxyOverrides.Split(';'));
|
||||
Eavesdropper.RequestInterceptedAsync += EavesdropperOnRequestInterceptedAsync;
|
||||
Eavesdropper.Initiate(port);
|
||||
}
|
||||
|
||||
private static Task EavesdropperOnRequestInterceptedAsync(object sender, RequestInterceptedEventArgs e)
|
||||
{
|
||||
var url = e.Request.RequestUri.OriginalString;
|
||||
foreach (var mhy in urls)
|
||||
{
|
||||
var i = url.IndexOf(mhy, StringComparison.CurrentCultureIgnoreCase);
|
||||
if (i == -1) continue;
|
||||
var p = url.IndexOf('/', i + mhy.Length);
|
||||
var target = p >= 0 ? _gcDispatch + url.Substring(p) : _gcDispatch;
|
||||
e.Request = RedirectRequest(e.Request as HttpWebRequest, new Uri(target));
|
||||
Logger.I(TAG, $"Redirect to {e.Request.RequestUri}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Logger.I(TAG, "Direct " + e.Request.RequestUri);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static HttpWebRequest RedirectRequest(HttpWebRequest request, Uri newUri)
|
||||
{
|
||||
var newRequest = WebRequest.CreateHttp(newUri);
|
||||
newRequest.ProtocolVersion = request.ProtocolVersion;
|
||||
newRequest.CookieContainer = request.CookieContainer;
|
||||
newRequest.AllowAutoRedirect = request.AllowAutoRedirect;
|
||||
newRequest.KeepAlive = request.KeepAlive;
|
||||
newRequest.Method = request.Method;
|
||||
newRequest.Proxy = request.Proxy;
|
||||
foreach (var name in request.Headers.AllKeys)
|
||||
{
|
||||
switch (name.ToLower())
|
||||
{
|
||||
case "host" : newRequest.Host = request.Host; break;
|
||||
case "accept" : newRequest.Accept = request.Accept; break;
|
||||
case "referer" : newRequest.Referer = request.Referer; break;
|
||||
case "user-agent" : newRequest.UserAgent = request.UserAgent; break;
|
||||
case "content-type" : newRequest.ContentType = request.ContentType; break;
|
||||
case "content-length" : newRequest.ContentLength = request.ContentLength; break;
|
||||
case "if-modified-since": newRequest.IfModifiedSince = request.IfModifiedSince; break;
|
||||
case "date" : newRequest.Date = request.Date; break;
|
||||
default: newRequest.Headers[name] = request.Headers[name]; break;
|
||||
}
|
||||
}
|
||||
//newRequest.Headers = request.Headers;
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
private static void StopGsProxyServer()
|
||||
{
|
||||
Eavesdropper.Terminate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private const int ProxyServerPort = 8146;
|
||||
private static string _gcDispatch;
|
||||
public static void StartProxy(string gcDispatch)
|
||||
{
|
||||
_gcDispatch = gcDispatch.TrimEnd('/');
|
||||
Logger.I(TAG, "Start Proxy, redirect to " + _gcDispatch);
|
||||
StartGsProxyServer(ProxyServerPort);
|
||||
//SetSystemProxy(ProxyServerPort);
|
||||
}
|
||||
|
||||
public static void StopProxy()
|
||||
{
|
||||
Logger.I(TAG, "Stop Proxy");
|
||||
//CloseSystemProxy();
|
||||
StopGsProxyServer();
|
||||
}
|
||||
|
||||
public static bool CheckAndCreateCertifier()
|
||||
{
|
||||
Eavesdropper.Certifier = new Certifier("jie65535", "GrasscutterTools Root Certificate Authority");
|
||||
return Eavesdropper.Certifier.CreateTrustedRootCertificate();
|
||||
}
|
||||
|
||||
public static bool IsRunning => Eavesdropper.IsRunning;
|
||||
}
|
||||
}
|
13
Source/GrasscutterTools/Utils/SystemProxy.cs
Normal file
13
Source/GrasscutterTools/Utils/SystemProxy.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GrasscutterTools.Utils
|
||||
{
|
||||
public class SystemProxy
|
||||
{
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user