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>
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<LangVersion>11</LangVersion>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
@ -49,6 +51,8 @@
|
|||||||
<DefineConstants>TRACE</DefineConstants>
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<LangVersion>11</LangVersion>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ApplicationIcon>Resources\IconGrasscutter.ico</ApplicationIcon>
|
<ApplicationIcon>Resources\IconGrasscutter.ico</ApplicationIcon>
|
||||||
@ -87,6 +91,14 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="DispatchServer\DispatchServerAPI.cs" />
|
<Compile Include="DispatchServer\DispatchServerAPI.cs" />
|
||||||
<Compile Include="DispatchServer\Model\ServerStatus.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">
|
<Compile Include="Forms\FormActivityEditor.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@ -319,6 +331,7 @@
|
|||||||
<Compile Include="Utils\HttpHelper.cs" />
|
<Compile Include="Utils\HttpHelper.cs" />
|
||||||
<Compile Include="Utils\Logger.cs" />
|
<Compile Include="Utils\Logger.cs" />
|
||||||
<Compile Include="Utils\GithubHelper.cs" />
|
<Compile Include="Utils\GithubHelper.cs" />
|
||||||
|
<Compile Include="Utils\ProxyHelper.cs" />
|
||||||
<Compile Include="Utils\SparseSet.cs" />
|
<Compile Include="Utils\SparseSet.cs" />
|
||||||
<Compile Include="Utils\ToggleParser.cs" />
|
<Compile Include="Utils\ToggleParser.cs" />
|
||||||
<Compile Include="Utils\UIUtil.cs" />
|
<Compile Include="Utils\UIUtil.cs" />
|
||||||
@ -669,6 +682,7 @@
|
|||||||
<DependentUpon>Resources.resx</DependentUpon>
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<None Include="Eavesdrop\LICENSE.md" />
|
||||||
<None Include="GrasscutterTools.licenseheader" />
|
<None Include="GrasscutterTools.licenseheader" />
|
||||||
<None Include="GrasscutterTools.snk" />
|
<None Include="GrasscutterTools.snk" />
|
||||||
<None Include="Properties\Settings.settings">
|
<None Include="Properties\Settings.settings">
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
this.TxtHost = new System.Windows.Forms.ComboBox();
|
this.TxtHost = new System.Windows.Forms.ComboBox();
|
||||||
this.BtnQueryServerStatus = new System.Windows.Forms.Button();
|
this.BtnQueryServerStatus = new System.Windows.Forms.Button();
|
||||||
this.LblHost = new System.Windows.Forms.Label();
|
this.LblHost = new System.Windows.Forms.Label();
|
||||||
|
this.BtnProxy = new System.Windows.Forms.Button();
|
||||||
this.GrpServerStatus.SuspendLayout();
|
this.GrpServerStatus.SuspendLayout();
|
||||||
this.GrpRemoteCommand.SuspendLayout();
|
this.GrpRemoteCommand.SuspendLayout();
|
||||||
this.TPOpenCommandCheck.SuspendLayout();
|
this.TPOpenCommandCheck.SuspendLayout();
|
||||||
@ -286,8 +287,8 @@
|
|||||||
//
|
//
|
||||||
resources.ApplyResources(this.TxtHost, "TxtHost");
|
resources.ApplyResources(this.TxtHost, "TxtHost");
|
||||||
this.TxtHost.Name = "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.SelectedIndexChanged += new System.EventHandler(this.TxtHost_SelectedIndexChanged);
|
||||||
|
this.TxtHost.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TxtHost_KeyDown);
|
||||||
//
|
//
|
||||||
// BtnQueryServerStatus
|
// BtnQueryServerStatus
|
||||||
//
|
//
|
||||||
@ -301,10 +302,18 @@
|
|||||||
resources.ApplyResources(this.LblHost, "LblHost");
|
resources.ApplyResources(this.LblHost, "LblHost");
|
||||||
this.LblHost.Name = "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
|
// PageOpenCommand
|
||||||
//
|
//
|
||||||
resources.ApplyResources(this, "$this");
|
resources.ApplyResources(this, "$this");
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.Controls.Add(this.BtnProxy);
|
||||||
this.Controls.Add(this.LnkLinks);
|
this.Controls.Add(this.LnkLinks);
|
||||||
this.Controls.Add(this.LnkGOODHelp);
|
this.Controls.Add(this.LnkGOODHelp);
|
||||||
this.Controls.Add(this.LnkInventoryKamera);
|
this.Controls.Add(this.LnkInventoryKamera);
|
||||||
@ -365,5 +374,6 @@
|
|||||||
private System.Windows.Forms.ComboBox TxtHost;
|
private System.Windows.Forms.ComboBox TxtHost;
|
||||||
private System.Windows.Forms.Button BtnQueryServerStatus;
|
private System.Windows.Forms.Button BtnQueryServerStatus;
|
||||||
private System.Windows.Forms.Label LblHost;
|
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.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
using Eavesdrop;
|
||||||
|
|
||||||
using GrasscutterTools.DispatchServer;
|
using GrasscutterTools.DispatchServer;
|
||||||
using GrasscutterTools.DispatchServer.Model;
|
using GrasscutterTools.DispatchServer.Model;
|
||||||
using GrasscutterTools.Game;
|
using GrasscutterTools.Game;
|
||||||
@ -47,6 +49,8 @@ namespace GrasscutterTools.Pages
|
|||||||
if (DesignMode) return;
|
if (DesignMode) return;
|
||||||
|
|
||||||
InitServerRecords();
|
InitServerRecords();
|
||||||
|
if (!string.IsNullOrEmpty(Settings.Default.Host))
|
||||||
|
TxtHost.Items.Add(Settings.Default.Host);
|
||||||
TxtHost.Items.AddRange(ServerRecords.Select(it => it.Host).ToArray());
|
TxtHost.Items.AddRange(ServerRecords.Select(it => it.Host).ToArray());
|
||||||
|
|
||||||
NUDRemotePlayerId.Value = Settings.Default.RemoteUid;
|
NUDRemotePlayerId.Value = Settings.Default.RemoteUid;
|
||||||
@ -57,6 +61,14 @@ namespace GrasscutterTools.Pages
|
|||||||
TxtToken.Text = Settings.Default.TokenCache;
|
TxtToken.Text = Settings.Default.TokenCache;
|
||||||
Task.Delay(1000).ContinueWith(_ => ShowTipInRunButton?.Invoke(Resources.TokenRestoredFromCache));
|
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 - 服务器记录 -
|
#region - 服务器记录 -
|
||||||
@ -161,6 +173,19 @@ namespace GrasscutterTools.Pages
|
|||||||
Settings.Default.RemoteUid = NUDRemotePlayerId.Value;
|
Settings.Default.RemoteUid = NUDRemotePlayerId.Value;
|
||||||
Settings.Default.Host = TxtHost.Text;
|
Settings.Default.Host = TxtHost.Text;
|
||||||
Settings.Default.TokenCache = Common.OC?.Token;
|
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>
|
/// <summary>
|
||||||
@ -240,6 +265,7 @@ namespace GrasscutterTools.Pages
|
|||||||
MessageBox.Show(ex.Message, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show(ex.Message, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOcEnabled)
|
if (isOcEnabled)
|
||||||
{
|
{
|
||||||
LblOpenCommandSupport.Text = "√";
|
LblOpenCommandSupport.Text = "√";
|
||||||
@ -252,6 +278,8 @@ namespace GrasscutterTools.Pages
|
|||||||
LblOpenCommandSupport.ForeColor = Color.Red;
|
LblOpenCommandSupport.ForeColor = Color.Red;
|
||||||
GrpRemoteCommand.Enabled = false;
|
GrpRemoteCommand.Enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BtnProxy.Enabled = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -583,5 +611,44 @@ namespace GrasscutterTools.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion - 导入存档 GOOD -
|
#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>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>LnkLinks.ZOrder" xml:space="preserve">
|
<data name=">>LnkLinks.ZOrder" xml:space="preserve">
|
||||||
<value>0</value>
|
<value>1</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LnkGOODHelp.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
<data name="LnkGOODHelp.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
@ -184,7 +184,7 @@
|
|||||||
<value>$this</value>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>LnkGOODHelp.ZOrder" xml:space="preserve">
|
<data name=">>LnkGOODHelp.ZOrder" xml:space="preserve">
|
||||||
<value>1</value>
|
<value>2</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LnkInventoryKamera.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
<data name="LnkInventoryKamera.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
@ -214,7 +214,7 @@
|
|||||||
<value>$this</value>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>LnkInventoryKamera.ZOrder" xml:space="preserve">
|
<data name=">>LnkInventoryKamera.ZOrder" xml:space="preserve">
|
||||||
<value>2</value>
|
<value>3</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LblGOODHelp.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
<data name="LblGOODHelp.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
@ -244,7 +244,7 @@
|
|||||||
<value>$this</value>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>LblGOODHelp.ZOrder" xml:space="preserve">
|
<data name=">>LblGOODHelp.ZOrder" xml:space="preserve">
|
||||||
<value>3</value>
|
<value>4</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ButtonOpenGOODImport.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
<data name="ButtonOpenGOODImport.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
@ -274,7 +274,7 @@
|
|||||||
<value>$this</value>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>ButtonOpenGOODImport.ZOrder" xml:space="preserve">
|
<data name=">>ButtonOpenGOODImport.ZOrder" xml:space="preserve">
|
||||||
<value>4</value>
|
<value>5</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LblHostTip.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
<data name="LblHostTip.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
@ -307,7 +307,7 @@
|
|||||||
<value>$this</value>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>LblHostTip.ZOrder" xml:space="preserve">
|
<data name=">>LblHostTip.ZOrder" xml:space="preserve">
|
||||||
<value>5</value>
|
<value>6</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GrpServerStatus.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
<data name="GrpServerStatus.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
@ -514,7 +514,7 @@
|
|||||||
<value>$this</value>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>GrpServerStatus.ZOrder" xml:space="preserve">
|
<data name=">>GrpServerStatus.ZOrder" xml:space="preserve">
|
||||||
<value>6</value>
|
<value>7</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GrpRemoteCommand.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
<data name="GrpRemoteCommand.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
@ -923,7 +923,7 @@
|
|||||||
<value>$this</value>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>GrpRemoteCommand.ZOrder" xml:space="preserve">
|
<data name=">>GrpRemoteCommand.ZOrder" xml:space="preserve">
|
||||||
<value>7</value>
|
<value>8</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TxtHost.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
<data name="TxtHost.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
@ -932,7 +932,7 @@
|
|||||||
<value>136, 34</value>
|
<value>136, 34</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TxtHost.Size" type="System.Drawing.Size, System.Drawing">
|
<data name="TxtHost.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
<value>182, 23</value>
|
<value>182, 25</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TxtHost.TabIndex" type="System.Int32, mscorlib">
|
<data name="TxtHost.TabIndex" type="System.Int32, mscorlib">
|
||||||
<value>2</value>
|
<value>2</value>
|
||||||
@ -941,13 +941,13 @@
|
|||||||
<value>TxtHost</value>
|
<value>TxtHost</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>TxtHost.Type" xml:space="preserve">
|
<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>
|
||||||
<data name=">>TxtHost.Parent" xml:space="preserve">
|
<data name=">>TxtHost.Parent" xml:space="preserve">
|
||||||
<value>$this</value>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>TxtHost.ZOrder" xml:space="preserve">
|
<data name=">>TxtHost.ZOrder" xml:space="preserve">
|
||||||
<value>8</value>
|
<value>9</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="BtnQueryServerStatus.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
<data name="BtnQueryServerStatus.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
@ -977,7 +977,7 @@
|
|||||||
<value>$this</value>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>BtnQueryServerStatus.ZOrder" xml:space="preserve">
|
<data name=">>BtnQueryServerStatus.ZOrder" xml:space="preserve">
|
||||||
<value>9</value>
|
<value>10</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LblHost.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
<data name="LblHost.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
@ -1010,7 +1010,34 @@
|
|||||||
<value>$this</value>
|
<value>$this</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>LblHost.ZOrder" xml:space="preserve">
|
<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>
|
</data>
|
||||||
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
@ -1022,6 +1049,6 @@
|
|||||||
<value>PageOpenCommand</value>
|
<value>PageOpenCommand</value>
|
||||||
</data>
|
</data>
|
||||||
<data name=">>$this.Type" xml:space="preserve">
|
<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>
|
</data>
|
||||||
</root>
|
</root>
|
@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
|
|||||||
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
|
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
|
||||||
//通过使用 "*",如下所示:
|
//通过使用 "*",如下所示:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.12.2")]
|
[assembly: AssemblyVersion("1.13.0")]
|
||||||
[assembly: AssemblyFileVersion("1.12.2")]
|
[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风花的呼吸
|
///2020:3.5风花的呼吸
|
||||||
///2021:3.6盛典与慧业
|
///2021:3.6盛典与慧业
|
||||||
///2022:3.7决战!召唤之巅!
|
///2022:3.7决战!召唤之巅!
|
||||||
|
///2023:3.8清夏!乐园?大秘境!
|
||||||
|
///2024:4.0机枢巧物前哨战
|
||||||
///// 1.0
|
///// 1.0
|
||||||
///1001:海灯节
|
///1001:海灯节
|
||||||
///5001:元素烘炉(test)
|
///5001:元素烘炉(test)
|
||||||
@ -120,10 +122,7 @@ namespace GrasscutterTools.Properties {
|
|||||||
///// 1.1
|
///// 1.1
|
||||||
///5004:映天之章
|
///5004:映天之章
|
||||||
///5005:元素烘炉
|
///5005:元素烘炉
|
||||||
///5006:佳肴尚温
|
///5006:佳 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||||
///5007:飞行挑战
|
|
||||||
///5009:古闻之章(钟离传说-1)
|
|
||||||
///50 [字符串的其余部分被截断]"; 的本地化字符串。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Activity {
|
internal static string Activity {
|
||||||
get {
|
get {
|
||||||
@ -195,6 +194,8 @@ namespace GrasscutterTools.Properties {
|
|||||||
///28:乐园遗落之花
|
///28:乐园遗落之花
|
||||||
///29:水仙之梦
|
///29:水仙之梦
|
||||||
///30:花海甘露之光
|
///30:花海甘露之光
|
||||||
|
///31:逐影猎人
|
||||||
|
///32:黄金剧团
|
||||||
///51:行者之心
|
///51:行者之心
|
||||||
///52:勇士之心
|
///52:勇士之心
|
||||||
///53:守护之心
|
///53:守护之心
|
||||||
@ -347,9 +348,9 @@ namespace GrasscutterTools.Properties {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 1002:神里绫华
|
/// 查找类似 1002:神里绫华
|
||||||
///1003:琴
|
///1003:琴
|
||||||
///1005:空
|
///1005:男主
|
||||||
///1006:丽莎
|
///1006:丽莎
|
||||||
///1007:荧
|
///1007:女主
|
||||||
///1014:芭芭拉
|
///1014:芭芭拉
|
||||||
///1015:凯亚
|
///1015:凯亚
|
||||||
///1016:迪卢克
|
///1016:迪卢克
|
||||||
@ -392,13 +393,13 @@ namespace GrasscutterTools.Properties {
|
|||||||
///1058:八重神子
|
///1058:八重神子
|
||||||
///1059:鹿野院平藏
|
///1059:鹿野院平藏
|
||||||
///1060:夜兰
|
///1060:夜兰
|
||||||
///1061:绮良良
|
|
||||||
///1062:埃洛伊
|
///1062:埃洛伊
|
||||||
///1063:申鹤
|
///1063:申鹤
|
||||||
///1064:云堇
|
///1064:云堇
|
||||||
///1065:久岐忍
|
///1065:久岐忍
|
||||||
///1066:神里绫人
|
///1066:神里绫人
|
||||||
///1067: [字符串的其余部分被截断]"; 的本地化字符串。
|
///1067:柯莱
|
||||||
|
///1068 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Avatar {
|
internal static string Avatar {
|
||||||
get {
|
get {
|
||||||
@ -407,8 +408,7 @@ namespace GrasscutterTools.Properties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 1001:4
|
/// 查找类似 1002:5
|
||||||
///1002:5
|
|
||||||
///1003:5
|
///1003:5
|
||||||
///1005:5
|
///1005:5
|
||||||
///1006:4
|
///1006:4
|
||||||
@ -455,6 +455,7 @@ namespace GrasscutterTools.Properties {
|
|||||||
///1058:5
|
///1058:5
|
||||||
///1059:4
|
///1059:4
|
||||||
///1060:5
|
///1060:5
|
||||||
|
///1061:4
|
||||||
///1062:5
|
///1062:5
|
||||||
///1063:5
|
///1063:5
|
||||||
///1064:4
|
///1064:4
|
||||||
@ -843,33 +844,32 @@ namespace GrasscutterTools.Properties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 // 特殊能力
|
/// 查找类似 // 机关装置
|
||||||
///44000001:使用冲刺后会在原地留下一个n秒后爆炸的能量波
|
///40000001:蒸汽桶炮
|
||||||
///44000002:飞行挑战玩法:空中冲刺 前冲
|
///70220032:风车机关
|
||||||
///44000003:飞行挑战玩法:空中冲刺 上冲1
|
///70220033:风车机关02-永动风车机关
|
||||||
///44000004:飞行挑战玩法:空中冲刺 上冲2
|
///70290584:升降铁栅栏0.8倍版-升降铁栅栏
|
||||||
///44000005:使用冲刺后会在原地留下一个n秒后爆炸的能量波
|
///70310300:风元素交互 水方碑
|
||||||
///44000006:范围内回复生命、能量、复活
|
///70310301:火元素交互 水方碑
|
||||||
///44000007:使用冲刺后会在原地留下一个n秒后爆炸的能量波
|
///70320001:旋转喷火机关
|
||||||
///44000008:没有敌人时提升输出
|
///70320002:单面喷火机关
|
||||||
///44000009:没有敌人时提升输出
|
///70320003:地城循环点光源
|
||||||
///44000010:范围伤害
|
///70330086:雷元素溢出洞口
|
||||||
///44000100:备用02
|
///70330332:沙漠 火元素方碑-沙漠 火元素方碑
|
||||||
///44000101:吐泡泡子弹(远)
|
///70330400:沙漠 雷元素方碑
|
||||||
///44000102:吐泡泡子弹(近)
|
///70330401:沙漠 冰元素方碑
|
||||||
///44000103:备用02
|
///70330402:沙漠 风元素方碑
|
||||||
///44000104:备用02
|
///70330403:沙漠 水元素方碑
|
||||||
///44000105:捉迷藏能量球
|
///70330404:沙漠 草元素方碑
|
||||||
///44000106:捉迷藏-技能-引导
|
///70330405:沙漠 岩元素方碑
|
||||||
///44000107:捉迷藏-技能-诱饵
|
///70330441:海市蜃楼-火元素方碑
|
||||||
///44000108:捉迷藏-牢
|
///70350001:地城大门01-原始门(废弃)
|
||||||
///44000109:捉迷藏-牢
|
///70350002:地城大门02-地城大门 倒品 大
|
||||||
///44000110:羽球节
|
///70350003:地城大门03-地城大门 倒品 小
|
||||||
///44000111:羽球节
|
///70350004:升降铁栅栏
|
||||||
///44000112:羽球节
|
///70350005:横向机关门
|
||||||
///44000113:羽球节
|
///70350006:升降铁栅栏-升降铁栅栏 大
|
||||||
///44000114:羽球节
|
///70350007:丘丘人升降 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||||
///440001 [字符串的其余部分被截断]"; 的本地化字符串。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Gadget {
|
internal static string Gadget {
|
||||||
get {
|
get {
|
||||||
@ -953,10 +953,7 @@ namespace GrasscutterTools.Properties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 // Items
|
/// 查找类似 // 虚拟道具
|
||||||
///
|
|
||||||
///
|
|
||||||
///// 虚拟物品
|
|
||||||
///101:角色经验
|
///101:角色经验
|
||||||
///102:冒险阅历
|
///102:冒险阅历
|
||||||
///105:好感经验
|
///105:好感经验
|
||||||
@ -1004,7 +1001,8 @@ namespace GrasscutterTools.Properties {
|
|||||||
///147:节庆热度
|
///147:节庆热度
|
||||||
///148:营业收入
|
///148:营业收入
|
||||||
///149:可用资金
|
///149:可用资金
|
||||||
///150: [字符串的其余部分被截断]"; 的本地化字符串。
|
///150:巧策灵感
|
||||||
|
///151:蘑菇宝钱 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Item {
|
internal static string Item {
|
||||||
get {
|
get {
|
||||||
@ -1370,6 +1368,7 @@ namespace GrasscutterTools.Properties {
|
|||||||
///6:层岩巨渊·地下矿区
|
///6:层岩巨渊·地下矿区
|
||||||
///7:三界路飨祭
|
///7:三界路飨祭
|
||||||
///9:金苹果群岛(2.8)
|
///9:金苹果群岛(2.8)
|
||||||
|
///10:Penumbra_LevelStreaming
|
||||||
///1001:移动平台性能测试(test)
|
///1001:移动平台性能测试(test)
|
||||||
///1002:攀爬测试2
|
///1002:攀爬测试2
|
||||||
///1003:TheBigWorld
|
///1003:TheBigWorld
|
||||||
@ -1386,12 +1385,10 @@ namespace GrasscutterTools.Properties {
|
|||||||
///1018:Chateau
|
///1018:Chateau
|
||||||
///1019:洞天云海地城玩法测试(test)
|
///1019:洞天云海地城玩法测试(test)
|
||||||
///1023:Level_Yurenzhong
|
///1023:Level_Yurenzhong
|
||||||
///1024:黑夜循环地城(test)
|
///1024:突破:清扫遗迹中的魔物(test)
|
||||||
///1030:TestIntercept_LiYue
|
///1030:TestIntercept_LiYue
|
||||||
///1031:爬塔丘丘人模板(test)
|
///1031:爬塔丘丘人模板(test)
|
||||||
///1032:云海白盒测试(test)
|
///1032:云海白 [字符串的其余部分被截断]"; 的本地化字符串。
|
||||||
///1033:Indoor_Ly_Bank
|
|
||||||
///1 [字符串的其余部分被截断]"; 的本地化字符串。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Scene {
|
internal static string Scene {
|
||||||
get {
|
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>
|
||||||
/// 查找类似 任务已经启动,无法操作 的本地化字符串。
|
/// 查找类似 任务已经启动,无法操作 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1574,6 +1589,9 @@ namespace GrasscutterTools.Properties {
|
|||||||
///11420:「一心传」名刀
|
///11420:「一心传」名刀
|
||||||
///11421:「一心传」名刀
|
///11421:「一心传」名刀
|
||||||
///11422:东花坊时雨
|
///11422:东花坊时雨
|
||||||
|
///11424:狼牙
|
||||||
|
///11425:海渊终曲
|
||||||
|
///11426:灰河渡手
|
||||||
///11501:风鹰剑
|
///11501:风鹰剑
|
||||||
///11502:天空之刃
|
///11502:天空之刃
|
||||||
///11503:苍古自由之誓
|
///11503:苍古自由之誓
|
||||||
@ -1584,10 +1602,7 @@ namespace GrasscutterTools.Properties {
|
|||||||
///11511:圣显之钥
|
///11511:圣显之钥
|
||||||
///11512:裁叶萃光
|
///11512:裁叶萃光
|
||||||
///12101:训练大剑
|
///12101:训练大剑
|
||||||
///12201:佣兵重剑
|
///12201: [字符串的其余部分被截断]"; 的本地化字符串。
|
||||||
///12301:铁影阔剑
|
|
||||||
///12302:沐浴龙血的剑
|
|
||||||
///12 [字符串的其余部分被截断]"; 的本地化字符串。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Weapon {
|
internal static string Weapon {
|
||||||
get {
|
get {
|
||||||
@ -1596,7 +1611,9 @@ namespace GrasscutterTools.Properties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 11301:blue
|
/// 查找类似 11101:blue
|
||||||
|
///11201:blue
|
||||||
|
///11301:blue
|
||||||
///11302:blue
|
///11302:blue
|
||||||
///11303:blue
|
///11303:blue
|
||||||
///11304:blue
|
///11304:blue
|
||||||
@ -1623,17 +1640,15 @@ namespace GrasscutterTools.Properties {
|
|||||||
///11420:purple
|
///11420:purple
|
||||||
///11421:purple
|
///11421:purple
|
||||||
///11422:purple
|
///11422:purple
|
||||||
|
///11424:purple
|
||||||
|
///11425:purple
|
||||||
|
///11426:purple
|
||||||
///11501:yellow
|
///11501:yellow
|
||||||
///11502:yellow
|
///11502:yellow
|
||||||
///11503:yellow
|
///11503:yellow
|
||||||
///11504:yellow
|
///11504:yellow
|
||||||
///11505:yellow
|
///11505:yellow
|
||||||
///11509:yellow
|
///11509:yell [字符串的其余部分被截断]"; 的本地化字符串。
|
||||||
///11510:yellow
|
|
||||||
///11511:yellow
|
|
||||||
///11512:yellow
|
|
||||||
///12301:blue
|
|
||||||
///12302:bl [字符串的其余部分被截断]"; 的本地化字符串。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string WeaponColor {
|
internal static string WeaponColor {
|
||||||
get {
|
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">
|
<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>
|
<value>..\Resources\en-us\Gadget.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StartProxy" xml:space="preserve">
|
||||||
|
<value>Start Proxy</value>
|
||||||
|
</data>
|
||||||
|
<data name="StopProxy" xml:space="preserve">
|
||||||
|
<value>Stop Proxy</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -375,4 +375,10 @@
|
|||||||
<data name="PageHotKey" xml:space="preserve">
|
<data name="PageHotKey" xml:space="preserve">
|
||||||
<value>快捷键</value>
|
<value>快捷键</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StartProxy" xml:space="preserve">
|
||||||
|
<value>启动代理</value>
|
||||||
|
</data>
|
||||||
|
<data name="StopProxy" xml:space="preserve">
|
||||||
|
<value>关闭代理</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -354,4 +354,10 @@
|
|||||||
<data name="Gadget" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<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>
|
<value>..\Resources\ru-ru\Gadget.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StartProxy" xml:space="preserve">
|
||||||
|
<value>Запустить прокси</value>
|
||||||
|
</data>
|
||||||
|
<data name="StopProxy" xml:space="preserve">
|
||||||
|
<value>Остановить прокси</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -360,4 +360,10 @@
|
|||||||
<data name="Gadget" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<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>
|
<value>..\Resources\zh-tw\Gadget.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StartProxy" xml:space="preserve">
|
||||||
|
<value>啟動代理</value>
|
||||||
|
</data>
|
||||||
|
<data name="StopProxy" xml:space="preserve">
|
||||||
|
<value>關閉代理</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -31,15 +31,26 @@ namespace GrasscutterTools.Utils
|
|||||||
|
|
||||||
private static void Write(string message)
|
private static void Write(string message)
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
Console.WriteLine($"{DateTime.Now:mm:ss.fff} {message}");
|
||||||
|
#else
|
||||||
if (IsSaveLogs)
|
if (IsSaveLogs)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{DateTime.Now:mm:ss.fff} {message}");
|
Console.WriteLine($"{DateTime.Now:mm:ss.fff} {message}");
|
||||||
File.AppendAllText(LogPath, $"{DateTime.Now:mm:ss.fff} {message}{Environment.NewLine}");
|
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}");
|
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 I(string tag, string info) => Write("INFO", tag, info);
|
||||||
|
|
||||||
public static void W(string tag, string message) => Write("WARR", tag, message);
|
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