diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..22e5606c
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,100 @@
+root = true
+# EditorConfig: http://EditorConfig.org
+# VS extension: https://marketplace.visualstudio.com/items?itemName = MadsKristensen.EditorConfig
+
+# 4 space indentation
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+
+# Microsoft .NET properties
+csharp_new_line_before_members_in_object_initializers = false
+csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
+csharp_style_expression_bodied_methods = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+dotnet_naming_rule.private_constants_rule.severity = suggestion
+dotnet_naming_rule.private_constants_rule.style = all_upper_style
+dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
+dotnet_naming_rule.private_static_readonly_rule.severity = suggestion
+dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style
+dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
+dotnet_naming_style.all_upper_style.capitalization = all_upper
+dotnet_naming_style.all_upper_style.word_separator = _
+dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
+dotnet_naming_style.lower_camel_case_style.required_prefix = _
+dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
+dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
+dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
+dotnet_style_qualification_for_event = false:warning
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_property = false:warning
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
+
+# CSharp code style settings:
+[*.cs]
+# Prefer "var" everywhere
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+
+# Prefer method-like constructs to have a block body
+csharp_style_expression_bodied_methods = true:suggestion
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_operators = true:suggestion
+
+# Prefer property-like constructs to have an expression-body
+csharp_style_expression_bodied_properties = true:suggestion
+csharp_style_expression_bodied_indexers = true:suggestion
+csharp_style_expression_bodied_accessors = true:none
+
+# Suggest more modern language features when available
+csharp_style_pattern_matching_over_is_with_cast_check = false:none
+csharp_style_pattern_matching_over_as_with_null_check = false:none
+csharp_style_inlined_variable_declaration = false:none
+csharp_style_throw_expression = false:none
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Newline settings
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = false
+csharp_new_line_before_members_in_anonymous_types = false
+
+# Dotnet code style settings:
+[*.{cs,vb}]
+# Sort using and Import directives with System.* appearing first
+dotnet_sort_system_directives_first = false
+# Avoid "this." and "Me." if not necessary
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_property = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_event = false:warning
+
+# Use language keywords instead of framework type names for type references
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+
+# Suggest more modern language features when available
+dotnet_style_object_initializer = true:silent
+dotnet_style_collection_initializer = true:silent
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+
+
+[*.{appxmanifest,asax,ascx,aspx,axml,build,config,cs,cshtml,csproj,css,dbml,discomap,dtd,htm,html,js,json,jsproj,jsx,lsproj,master,njsproj,nuspec,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,ts,tsx,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
+indent_style = space
+indent_size = 4
+tab_width = 4
diff --git a/Shadowsocks.Common/Models/IoCManager.cs b/Shadowsocks.Common/Models/IoCManager.cs
new file mode 100644
index 00000000..5c176290
--- /dev/null
+++ b/Shadowsocks.Common/Models/IoCManager.cs
@@ -0,0 +1,9 @@
+using SimpleInjector;
+
+namespace Shadowsocks.Common.Model
+{
+ public static class IoCManager
+ {
+ public static Container Container { get; } = new Container();
+ }
+}
diff --git a/Shadowsocks.Common/Shadowsocks.Common.csproj b/Shadowsocks.Common/Shadowsocks.Common.csproj
new file mode 100644
index 00000000..5fb972fe
--- /dev/null
+++ b/Shadowsocks.Common/Shadowsocks.Common.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netstandard2.1
+ clowwindy & community 2020
+ clowwindy & community 2020
+ Shadowsocks Common
+
+
+
+
+
+
+
+
+
+
diff --git a/shadowsocks-csharp/Util/SystemProxy/ProxyException.cs b/Shadowsocks.Common/SystemProxy/ProxyException.cs
similarity index 90%
rename from shadowsocks-csharp/Util/SystemProxy/ProxyException.cs
rename to Shadowsocks.Common/SystemProxy/ProxyException.cs
index 76e858cb..ec2866a3 100644
--- a/shadowsocks-csharp/Util/SystemProxy/ProxyException.cs
+++ b/Shadowsocks.Common/SystemProxy/ProxyException.cs
@@ -1,11 +1,7 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Runtime.Serialization;
-using System.Text;
-using System.Threading.Tasks;
-namespace Shadowsocks.Util.SystemProxy
+namespace Shadowsocks.Common.SystemProxy
{
enum ProxyExceptionType
{
diff --git a/shadowsocks-csharp/Controller/LoggerExtension.cs b/Shadowsocks.Common/Utilities/LoggerExtension.cs
similarity index 85%
rename from shadowsocks-csharp/Controller/LoggerExtension.cs
rename to Shadowsocks.Common/Utilities/LoggerExtension.cs
index 59bda840..9d6e1013 100644
--- a/shadowsocks-csharp/Controller/LoggerExtension.cs
+++ b/Shadowsocks.Common/Utilities/LoggerExtension.cs
@@ -1,151 +1,143 @@
-using System;
-using System.ComponentModel;
-using System.IO;
-using System.Net.Sockets;
-using System.Net;
-using System.Diagnostics;
-using System.Text;
-using Shadowsocks.Util.SystemProxy;
-
-namespace NLog
-{
- public static class LoggerExtension
- {
- // for key, iv, etc...
- public static void Dump(this Logger logger, string tag, ReadOnlySpan arr)
- {
- logger.Dump(tag, arr.ToArray(), arr.Length);
- }
- public static void Dump(this Logger logger, string tag, byte[] arr, int length = -1)
- {
- if (arr == null) logger.Trace($@"
-{tag}:
-(null)
-
-");
- if (length == -1) length = arr.Length;
-
- if (!logger.IsTraceEnabled) return;
- string hex = BitConverter.ToString(arr.AsSpan(0, Math.Min(arr.Length, length)).ToArray()).Replace("-", "");
- string content = $@"
-{tag}:
-{hex}
-
-";
- logger.Trace(content);
- }
- // for cipher and plain text, so we can use openssl to test
- public static void DumpBase64(this Logger logger, string tag, ReadOnlySpan arr)
- {
- logger.DumpBase64(tag, arr.ToArray(), arr.Length);
- }
- public static void DumpBase64(this Logger logger, string tag, byte[] arr, int length = -1)
- {
- if (arr == null) logger.Trace($@"
-{tag}:
-(null)
-
-");
- if (length == -1) length = arr.Length;
-
- if (!logger.IsTraceEnabled) return;
- string hex = Convert.ToBase64String(arr.AsSpan(0, Math.Min(arr.Length, length)).ToArray());
- string content = $@"
-{tag}:
-{hex}
-
-";
- logger.Trace(content);
- }
-
- public static void Debug(this Logger logger, EndPoint local, EndPoint remote, int len, string header = null, string tailer = null)
- {
- if (logger.IsDebugEnabled)
- {
- if (header == null && tailer == null)
- logger.Debug($"{local} => {remote} (size={len})");
- else if (header == null && tailer != null)
- logger.Debug($"{local} => {remote} (size={len}), {tailer}");
- else if (header != null && tailer == null)
- logger.Debug($"{header}: {local} => {remote} (size={len})");
- else
- logger.Debug($"{header}: {local} => {remote} (size={len}), {tailer}");
- }
- }
-
- public static void Debug(this Logger logger, Socket sock, int len, string header = null, string tailer = null)
- {
- if (logger.IsDebugEnabled)
- {
- logger.Debug(sock.LocalEndPoint, sock.RemoteEndPoint, len, header, tailer);
- }
- }
-
- public static void LogUsefulException(this Logger logger, Exception e)
- {
- // just log useful exceptions, not all of them
- if (e is SocketException)
- {
- SocketException se = (SocketException)e;
- if (se.SocketErrorCode == SocketError.ConnectionAborted)
- {
- // closed by browser when sending
- // normally happens when download is canceled or a tab is closed before page is loaded
- }
- else if (se.SocketErrorCode == SocketError.ConnectionReset)
- {
- // received rst
- }
- else if (se.SocketErrorCode == SocketError.NotConnected)
- {
- // The application tried to send or receive data, and the System.Net.Sockets.Socket is not connected.
- }
- else if (se.SocketErrorCode == SocketError.HostUnreachable)
- {
- // There is no network route to the specified host.
- }
- else if (se.SocketErrorCode == SocketError.TimedOut)
- {
- // The connection attempt timed out, or the connected host has failed to respond.
- }
- else
- {
- logger.Warn(e);
- }
- }
- else if (e is ObjectDisposedException)
- {
- }
- else if (e is Win32Exception)
- {
- var ex = (Win32Exception)e;
-
- // Win32Exception (0x80004005): A 32 bit processes cannot access modules of a 64 bit process.
- if ((uint)ex.ErrorCode != 0x80004005)
- {
- logger.Warn(e);
- }
- }
- else if (e is ProxyException)
- {
- var ex = (ProxyException)e;
- switch (ex.Type)
- {
- case ProxyExceptionType.FailToRun:
- case ProxyExceptionType.QueryReturnMalformed:
- case ProxyExceptionType.SysproxyExitError:
- logger.Error($"sysproxy - {ex.Type.ToString()}:{ex.Message}");
- break;
- case ProxyExceptionType.QueryReturnEmpty:
- case ProxyExceptionType.Unspecific:
- logger.Error($"sysproxy - {ex.Type.ToString()}");
- break;
- }
- }
- else
- {
- logger.Warn(e);
- }
- }
- }
-}
+using Shadowsocks.Common.SystemProxy;
+
+using System;
+using System.ComponentModel;
+using System.Net;
+using System.Net.Sockets;
+
+namespace NLog
+{
+ public static class LoggerExtension
+ {
+ // for key, iv, etc...
+ public static void Dump(this Logger logger, string tag, ReadOnlySpan arr)
+ {
+ logger.Dump(tag, arr.ToArray(), arr.Length);
+ }
+ public static void Dump(this Logger logger, string tag, byte[] arr, int length = -1)
+ {
+ if (arr == null) logger.Trace($@"
+{tag}:
+(null)
+
+");
+ if (length == -1) length = arr.Length;
+
+ if (!logger.IsTraceEnabled) return;
+ string hex = BitConverter.ToString(arr.AsSpan(0, Math.Min(arr.Length, length)).ToArray()).Replace("-", "");
+ string content = $@"
+{tag}:
+{hex}
+
+";
+ logger.Trace(content);
+ }
+ // for cipher and plain text, so we can use openssl to test
+ public static void DumpBase64(this Logger logger, string tag, ReadOnlySpan arr)
+ {
+ logger.DumpBase64(tag, arr.ToArray(), arr.Length);
+ }
+ public static void DumpBase64(this Logger logger, string tag, byte[] arr, int length = -1)
+ {
+ if (arr == null) logger.Trace($@"
+{tag}:
+(null)
+
+");
+ if (length == -1) length = arr.Length;
+
+ if (!logger.IsTraceEnabled) return;
+ string hex = Convert.ToBase64String(arr.AsSpan(0, Math.Min(arr.Length, length)).ToArray());
+ string content = $@"
+{tag}:
+{hex}
+
+";
+ logger.Trace(content);
+ }
+
+ public static void Debug(this Logger logger, EndPoint local, EndPoint remote, int len, string header = null, string tailer = null)
+ {
+ if (logger.IsDebugEnabled)
+ {
+ if (header == null && tailer == null)
+ logger.Debug($"{local} => {remote} (size={len})");
+ else if (header == null && tailer != null)
+ logger.Debug($"{local} => {remote} (size={len}), {tailer}");
+ else if (header != null && tailer == null)
+ logger.Debug($"{header}: {local} => {remote} (size={len})");
+ else
+ logger.Debug($"{header}: {local} => {remote} (size={len}), {tailer}");
+ }
+ }
+
+ public static void Debug(this Logger logger, Socket sock, int len, string header = null, string tailer = null)
+ {
+ if (logger.IsDebugEnabled)
+ logger.Debug(sock.LocalEndPoint, sock.RemoteEndPoint, len, header, tailer);
+
+ }
+
+ public static void LogUsefulException(this Logger logger, Exception e)
+ {
+ // just log useful exceptions, not all of them
+ if (e is SocketException se)
+ {
+ if (se.SocketErrorCode == SocketError.ConnectionAborted)
+ {
+ // closed by browser when sending
+ // normally happens when download is canceled or a tab is closed before page is loaded
+ }
+ else if (se.SocketErrorCode == SocketError.ConnectionReset)
+ {
+ // received rst
+ }
+ else if (se.SocketErrorCode == SocketError.NotConnected)
+ {
+ // The application tried to send or receive data, and the System.Net.Sockets.Socket is not connected.
+ }
+ else if (se.SocketErrorCode == SocketError.HostUnreachable)
+ {
+ // There is no network route to the specified host.
+ }
+ else if (se.SocketErrorCode == SocketError.TimedOut)
+ {
+ // The connection attempt timed out, or the connected host has failed to respond.
+ }
+ else
+ {
+ logger.Warn(e);
+ }
+ }
+ else if (e is ObjectDisposedException)
+ {
+ }
+ else if (e is Win32Exception ex)
+ {
+ // Win32Exception (0x80004005): A 32 bit processes cannot access modules of a 64 bit process.
+ if ((uint)ex.ErrorCode != 0x80004005)
+ logger.Warn(e);
+
+ }
+ else if (e is ProxyException pe)
+ {
+ switch (pe.Type)
+ {
+ case ProxyExceptionType.FailToRun:
+ case ProxyExceptionType.QueryReturnMalformed:
+ case ProxyExceptionType.SysproxyExitError:
+ logger.Error($"sysproxy - {pe.Type}:{pe.Message}");
+ break;
+ case ProxyExceptionType.QueryReturnEmpty:
+ case ProxyExceptionType.Unspecific:
+ logger.Error($"sysproxy - {pe.Type}");
+ break;
+ }
+ }
+ else
+ {
+ logger.Warn(e);
+ }
+ }
+ }
+}
diff --git a/shadowsocks-csharp/Encryption/AEAD/AEADAesGcmNativeEncryptor.cs b/Shadowsocks.Crypto/Crypto/AEAD/AEADAesGcmNativeCrypto.cs
similarity index 88%
rename from shadowsocks-csharp/Encryption/AEAD/AEADAesGcmNativeEncryptor.cs
rename to Shadowsocks.Crypto/Crypto/AEAD/AEADAesGcmNativeCrypto.cs
index 81cb3c34..b2552479 100644
--- a/shadowsocks-csharp/Encryption/AEAD/AEADAesGcmNativeEncryptor.cs
+++ b/Shadowsocks.Crypto/Crypto/AEAD/AEADAesGcmNativeCrypto.cs
@@ -1,12 +1,12 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Security.Cryptography;
-namespace Shadowsocks.Encryption.AEAD
+namespace Shadowsocks.Crypto.AEAD
{
- public class AEADAesGcmNativeEncryptor : AEADEncryptor
+ public class AEADAesGcmNativeCrypto : AEADCrypto
{
- public AEADAesGcmNativeEncryptor(string method, string password) : base(method, password)
+ public AEADAesGcmNativeCrypto(string method, string password) : base(method, password)
{
}
@@ -51,4 +51,4 @@ namespace Shadowsocks.Encryption.AEAD
return clen;
}
}
-}
\ No newline at end of file
+}
diff --git a/shadowsocks-csharp/Encryption/AEAD/AEADBouncyCastleEncryptor.cs b/Shadowsocks.Crypto/Crypto/AEAD/AEADBouncyCastleCrypto.cs
similarity index 91%
rename from shadowsocks-csharp/Encryption/AEAD/AEADBouncyCastleEncryptor.cs
rename to Shadowsocks.Crypto/Crypto/AEAD/AEADBouncyCastleCrypto.cs
index 950e4785..82433bbe 100644
--- a/shadowsocks-csharp/Encryption/AEAD/AEADBouncyCastleEncryptor.cs
+++ b/Shadowsocks.Crypto/Crypto/AEAD/AEADBouncyCastleCrypto.cs
@@ -1,16 +1,17 @@
-using System;
+using System;
using System.Collections.Generic;
+
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
-namespace Shadowsocks.Encryption.AEAD
+namespace Shadowsocks.Crypto.AEAD
{
- public class AEADBouncyCastleEncryptor : AEADEncryptor
+ public class AEADBouncyCastleCrypto : AEADCrypto
{
IAeadCipher aead;
bool enc;
- public AEADBouncyCastleEncryptor(string method, string password) : base(method, password)
+ public AEADBouncyCastleCrypto(string method, string password) : base(method, password)
{
aead = cipherFamily switch
{
diff --git a/shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs b/Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs
similarity index 88%
rename from shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs
rename to Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs
index 5e5f985d..b3dbd644 100644
--- a/shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs
+++ b/Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs
@@ -1,320 +1,322 @@
-using NLog;
-using Shadowsocks.Controller;
-using Shadowsocks.Encryption.Exception;
-using Shadowsocks.Encryption.Stream;
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Runtime.CompilerServices;
-using System.Text;
-
-namespace Shadowsocks.Encryption.AEAD
-{
- public abstract class AEADEncryptor : EncryptorBase
- {
- private static readonly Logger logger = LogManager.GetCurrentClassLogger();
- // We are using the same saltLen and keyLen
- private const string Info = "ss-subkey";
- private static readonly byte[] InfoBytes = Encoding.ASCII.GetBytes(Info);
-
- // every connection should create its own buffer
- private readonly byte[] buffer = new byte[65536];
- private int bufPtr = 0;
-
- public const int ChunkLengthBytes = 2;
- public const uint ChunkLengthMask = 0x3FFFu;
-
- protected CipherFamily cipherFamily;
- protected CipherInfo CipherInfo;
- protected static byte[] masterKey = Array.Empty();
- protected byte[] sessionKey = Array.Empty();
- protected int keyLen;
- protected int saltLen;
- protected int tagLen;
- protected int nonceLen;
-
- protected byte[] salt;
- protected byte[] nonce;
-
- // Is first packet
- protected bool saltReady;
-
- // Is first chunk(tcp request)
- protected bool tcpRequestSent;
-
- public AEADEncryptor(string method, string password)
- : base(method, password)
- {
- CipherInfo = GetCiphers()[method.ToLower()];
- cipherFamily = CipherInfo.Type;
- AEADCipherParameter parameter = (AEADCipherParameter)CipherInfo.CipherParameter;
- keyLen = parameter.KeySize;
- saltLen = parameter.SaltSize;
- tagLen = parameter.TagSize;
- nonceLen = parameter.NonceSize;
-
- InitKey(password);
-
- salt = new byte[saltLen];
- // Initialize all-zero nonce for each connection
- nonce = new byte[nonceLen];
- logger.Dump($"masterkey {instanceId}", masterKey, keyLen);
- logger.Dump($"nonce {instanceId}", nonce, keyLen);
- }
-
- protected abstract Dictionary GetCiphers();
-
- protected void InitKey(string password)
- {
- byte[] passbuf = Encoding.UTF8.GetBytes(password);
- // init master key
- if (masterKey == null)
- {
- masterKey = new byte[keyLen];
- }
-
- if (masterKey.Length != keyLen)
- {
- Array.Resize(ref masterKey, keyLen);
- }
-
- StreamEncryptor.LegacyDeriveKey(passbuf, masterKey, keyLen);
- // init session key
- sessionKey = new byte[keyLen];
- }
-
- public virtual void InitCipher(byte[] salt, bool isEncrypt)
- {
- this.salt = new byte[saltLen];
- Array.Copy(salt, this.salt, saltLen);
-
- CryptoUtils.HKDF(keyLen, masterKey, salt, InfoBytes).CopyTo(sessionKey, 0);
-
- logger.Dump($"salt {instanceId}", salt, saltLen);
- logger.Dump($"sessionkey {instanceId}", sessionKey, keyLen);
- }
-
- public abstract int CipherEncrypt(ReadOnlySpan plain, Span cipher);
- public abstract int CipherDecrypt(Span plain, ReadOnlySpan cipher);
-
- #region TCP
-
- [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.AggressiveOptimization)]
- public override int Encrypt(ReadOnlySpan plain, Span cipher)
- {
- // push data
- Span tmp = buffer.AsSpan(0, plain.Length + bufPtr);
- plain.CopyTo(tmp.Slice(bufPtr));
-
- int outlength = 0;
- if (!saltReady)
- {
- saltReady = true;
- // Generate salt
- byte[] saltBytes = RNG.GetBytes(saltLen);
- InitCipher(saltBytes, true);
- saltBytes.CopyTo(cipher);
- outlength = saltLen;
- }
-
- if (!tcpRequestSent)
- {
- tcpRequestSent = true;
-
- // read addr byte to encrypt
- int encAddrBufLength = ChunkEncrypt(tmp.Slice(0, AddressBufferLength), cipher.Slice(outlength));
- tmp = tmp.Slice(AddressBufferLength);
- outlength += encAddrBufLength;
- }
-
- // handle other chunks
- while (true)
- {
- // calculate next chunk size
- int bufSize = tmp.Length;
- if (bufSize <= 0)
- {
- return outlength;
- }
-
- int chunklength = (int)Math.Min(bufSize, ChunkLengthMask);
- // read next chunk
- int encChunkLength = ChunkEncrypt(tmp.Slice(0, chunklength), cipher.Slice(outlength));
- tmp = tmp.Slice(chunklength);
- outlength += encChunkLength;
-
- // check if we have enough space for outbuf
- // if not, keep buf for next run, at this condition, buffer is not empty
- if (outlength + TCPHandler.ChunkOverheadSize > TCPHandler.BufferSize)
- {
- logger.Debug("enc outbuf almost full, giving up");
-
- // write rest data to head of shared buffer
- tmp.CopyTo(buffer);
- bufPtr = tmp.Length;
-
- return outlength;
- }
- // check if buffer empty
- bufSize = tmp.Length;
- if (bufSize <= 0)
- {
- logger.Debug("No more data to encrypt, leaving");
- return outlength;
- }
- }
- }
-
- [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.AggressiveOptimization)]
- public override int Decrypt(Span plain, ReadOnlySpan cipher)
- {
- int outlength = 0;
- // drop all into buffer
- Span tmp = buffer.AsSpan(0, cipher.Length + bufPtr);
- cipher.CopyTo(tmp.Slice(bufPtr));
- int bufSize = tmp.Length;
-
- logger.Debug($"{instanceId} decrypt tcp, read salt: {!saltReady}");
- if (!saltReady)
- {
- // check if we get the leading salt
- if (bufSize <= saltLen)
- {
- // need more, write back cache
- tmp.CopyTo(buffer);
- bufPtr = tmp.Length;
- return outlength;
- }
- saltReady = true;
-
- byte[] salt = tmp.Slice(0, saltLen).ToArray();
- tmp = tmp.Slice(saltLen);
-
- InitCipher(salt, false);
- }
-
- // handle chunks
- while (true)
- {
- bufSize = tmp.Length;
- // check if we have any data
- if (bufSize <= 0)
- {
- logger.Trace("No data in buffer");
- return outlength;
- }
-
- // first get chunk length
- if (bufSize <= ChunkLengthBytes + tagLen)
- {
- // so we only have chunk length and its tag?
- // wait more
- logger.Trace($"{instanceId} not enough data to decrypt chunk. write {tmp.Length} byte back to buffer.");
- tmp.CopyTo(buffer);
- bufPtr = tmp.Length;
- return outlength;
- }
- logger.Trace($"{instanceId} try decrypt to offset {outlength}");
- int len = ChunkDecrypt(plain.Slice(outlength), tmp);
- if (len <= 0)
- {
- logger.Trace($"{instanceId} no chunk decrypted, write {tmp.Length} byte back to buffer.");
-
- // no chunk decrypted
- tmp.CopyTo(buffer);
- bufPtr = tmp.Length;
- return outlength;
- }
- logger.Trace($"{instanceId} decrypted {len} to offset {outlength}");
-
- // drop decrypted data
- tmp = tmp.Slice(ChunkLengthBytes + tagLen + len + tagLen);
- outlength += len;
-
- // logger.Debug("aead dec outlength " + outlength);
- if (outlength + 100 > TCPHandler.BufferSize)
- {
- logger.Trace($"{instanceId} output almost full, write {tmp.Length} byte back to buffer.");
- tmp.CopyTo(buffer);
- bufPtr = tmp.Length;
- return outlength;
- }
- bufSize = tmp.Length;
- // check if we already done all of them
- if (bufSize <= 0)
- {
- bufPtr = 0;
- logger.Debug($"{instanceId} no data in buffer, already all done");
- return outlength;
- }
- }
- }
-
- #endregion
-
- #region UDP
- [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.AggressiveOptimization)]
- public override int EncryptUDP(ReadOnlySpan plain, Span cipher)
- {
- RNG.GetSpan(cipher.Slice(0, saltLen));
- InitCipher(cipher.Slice(0, saltLen).ToArray(), true);
- return saltLen + CipherEncrypt(plain, cipher.Slice(saltLen));
- }
-
- [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.AggressiveOptimization)]
- public override int DecryptUDP(Span plain, ReadOnlySpan cipher)
- {
- InitCipher(cipher.Slice(0, saltLen).ToArray(), false);
- return CipherDecrypt(plain, cipher.Slice(saltLen));
- }
-
- #endregion
-
- [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.AggressiveOptimization)]
- private int ChunkEncrypt(ReadOnlySpan plain, Span cipher)
- {
- if (plain.Length > ChunkLengthMask)
- {
- logger.Error("enc chunk too big");
- throw new CryptoErrorException();
- }
-
- byte[] lenbuf = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)plain.Length));
- int cipherLenSize = CipherEncrypt(lenbuf, cipher);
- CryptoUtils.SodiumIncrement(nonce);
- int cipherDataSize = CipherEncrypt(plain, cipher.Slice(cipherLenSize));
- CryptoUtils.SodiumIncrement(nonce);
-
- return cipherLenSize + cipherDataSize;
- }
-
- [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.AggressiveOptimization)]
- private int ChunkDecrypt(Span plain, ReadOnlySpan cipher)
- {
- // try to dec chunk len
- byte[] chunkLengthByte = new byte[ChunkLengthBytes];
- CipherDecrypt(chunkLengthByte, cipher.Slice(0, ChunkLengthBytes + tagLen));
- ushort chunkLength = (ushort)IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(chunkLengthByte, 0));
- if (chunkLength > ChunkLengthMask)
- {
- // we get invalid chunk
- logger.Error($"{instanceId} Invalid chunk length: {chunkLength}");
- throw new CryptoErrorException();
- }
- // logger.Debug("Get the real chunk len:" + chunkLength);
- int bufSize = cipher.Length;
- if (bufSize < ChunkLengthBytes + tagLen /* we haven't remove them */+ chunkLength + tagLen)
- {
- logger.Debug($"{instanceId} need {ChunkLengthBytes + tagLen + chunkLength + tagLen}, but have {cipher.Length}");
- return 0;
- }
- CryptoUtils.SodiumIncrement(nonce);
- // we have enough data to decrypt one chunk
- // drop chunk len and its tag from buffer
- int len = CipherDecrypt(plain, cipher.Slice(ChunkLengthBytes + tagLen, chunkLength + tagLen));
- CryptoUtils.SodiumIncrement(nonce);
- logger.Trace($"{instanceId} decrypted {len} byte chunk used {ChunkLengthBytes + tagLen + chunkLength + tagLen} from {cipher.Length}");
- return len;
- }
- }
-}
\ No newline at end of file
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+using NLog;
+
+using Shadowsocks.Crypto.Exception;
+using Shadowsocks.Crypto.Stream;
+
+namespace Shadowsocks.Crypto.AEAD
+{
+ public abstract class AEADCrypto : CryptoBase
+ {
+ private static readonly Logger logger = LogManager.GetCurrentClassLogger();
+ // We are using the same saltLen and keyLen
+ private const string Info = "ss-subkey";
+ private static readonly byte[] InfoBytes = Encoding.ASCII.GetBytes(Info);
+
+ // every connection should create its own buffer
+ private readonly byte[] buffer = new byte[65536];
+ private int bufPtr = 0;
+
+ public const int ChunkLengthBytes = 2;
+ public const uint ChunkLengthMask = 0x3FFFu;
+
+ protected CipherFamily cipherFamily;
+ protected CipherInfo CipherInfo;
+ protected static byte[] masterKey = Array.Empty();
+ protected byte[] sessionKey = Array.Empty();
+ protected int keyLen;
+ protected int saltLen;
+ protected int tagLen;
+ protected int nonceLen;
+
+ protected byte[] salt;
+ protected byte[] nonce;
+
+ // Is first packet
+ protected bool saltReady;
+
+ // Is first chunk(tcp request)
+ protected bool tcpRequestSent;
+
+ public AEADCrypto(string method, string password)
+ : base(method, password)
+ {
+ CipherInfo = GetCiphers()[method.ToLower()];
+ cipherFamily = CipherInfo.Type;
+ AEADCipherParameter parameter = (AEADCipherParameter)CipherInfo.CipherParameter;
+ keyLen = parameter.KeySize;
+ saltLen = parameter.SaltSize;
+ tagLen = parameter.TagSize;
+ nonceLen = parameter.NonceSize;
+
+ InitKey(password);
+
+ salt = new byte[saltLen];
+ // Initialize all-zero nonce for each connection
+ nonce = new byte[nonceLen];
+
+ logger.Dump($"masterkey {instanceId}", masterKey, keyLen);
+ logger.Dump($"nonce {instanceId}", nonce, keyLen);
+ }
+
+ protected abstract Dictionary GetCiphers();
+
+ protected void InitKey(string password)
+ {
+ byte[] passbuf = Encoding.UTF8.GetBytes(password);
+ // init master key
+ if (masterKey == null)
+ {
+ masterKey = new byte[keyLen];
+ }
+
+ if (masterKey.Length != keyLen)
+ {
+ Array.Resize(ref masterKey, keyLen);
+ }
+
+ StreamCrypto.LegacyDeriveKey(passbuf, masterKey, keyLen);
+ // init session key
+ sessionKey = new byte[keyLen];
+ }
+
+ public virtual void InitCipher(byte[] salt, bool isEncrypt)
+ {
+ this.salt = new byte[saltLen];
+ Array.Copy(salt, this.salt, saltLen);
+
+ CryptoUtils.HKDF(keyLen, masterKey, salt, InfoBytes).CopyTo(sessionKey, 0);
+
+ logger.Dump($"salt {instanceId}", salt, saltLen);
+ logger.Dump($"sessionkey {instanceId}", sessionKey, keyLen);
+ }
+
+ public abstract int CipherEncrypt(ReadOnlySpan plain, Span cipher);
+ public abstract int CipherDecrypt(Span plain, ReadOnlySpan cipher);
+
+ #region TCP
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int Encrypt(ReadOnlySpan plain, Span cipher)
+ {
+ // push data
+ Span tmp = buffer.AsSpan(0, plain.Length + bufPtr);
+ plain.CopyTo(tmp.Slice(bufPtr));
+
+ int outlength = 0;
+ if (!saltReady)
+ {
+ saltReady = true;
+ // Generate salt
+ byte[] saltBytes = RNG.GetBytes(saltLen);
+ InitCipher(saltBytes, true);
+ saltBytes.CopyTo(cipher);
+ outlength = saltLen;
+ }
+
+ if (!tcpRequestSent)
+ {
+ tcpRequestSent = true;
+
+ // read addr byte to encrypt
+ int encAddrBufLength = ChunkEncrypt(tmp.Slice(0, AddressBufferLength), cipher.Slice(outlength));
+ tmp = tmp.Slice(AddressBufferLength);
+ outlength += encAddrBufLength;
+ }
+
+ // handle other chunks
+ while (true)
+ {
+ // calculate next chunk size
+ int bufSize = tmp.Length;
+ if (bufSize <= 0)
+ {
+ return outlength;
+ }
+
+ int chunklength = (int)Math.Min(bufSize, ChunkLengthMask);
+ // read next chunk
+ int encChunkLength = ChunkEncrypt(tmp.Slice(0, chunklength), cipher.Slice(outlength));
+ tmp = tmp.Slice(chunklength);
+ outlength += encChunkLength;
+
+ // check if we have enough space for outbuf
+ // if not, keep buf for next run, at this condition, buffer is not empty
+ if (outlength + TCPParameter.ChunkOverheadSize > TCPParameter.BufferSize)
+ {
+ logger.Debug("enc outbuf almost full, giving up");
+
+ // write rest data to head of shared buffer
+ tmp.CopyTo(buffer);
+ bufPtr = tmp.Length;
+
+ return outlength;
+ }
+ // check if buffer empty
+ bufSize = tmp.Length;
+ if (bufSize <= 0)
+ {
+ logger.Debug("No more data to encrypt, leaving");
+ return outlength;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int Decrypt(Span plain, ReadOnlySpan cipher)
+ {
+ int outlength = 0;
+ // drop all into buffer
+ Span tmp = buffer.AsSpan(0, cipher.Length + bufPtr);
+ cipher.CopyTo(tmp.Slice(bufPtr));
+ int bufSize = tmp.Length;
+
+ logger.Debug($"{instanceId} decrypt tcp, read salt: {!saltReady}");
+ if (!saltReady)
+ {
+ // check if we get the leading salt
+ if (bufSize <= saltLen)
+ {
+ // need more, write back cache
+ tmp.CopyTo(buffer);
+ bufPtr = tmp.Length;
+ return outlength;
+ }
+ saltReady = true;
+
+ byte[] salt = tmp.Slice(0, saltLen).ToArray();
+ tmp = tmp.Slice(saltLen);
+
+ InitCipher(salt, false);
+ }
+
+ // handle chunks
+ while (true)
+ {
+ bufSize = tmp.Length;
+ // check if we have any data
+ if (bufSize <= 0)
+ {
+ logger.Trace("No data in buffer");
+ return outlength;
+ }
+
+ // first get chunk length
+ if (bufSize <= ChunkLengthBytes + tagLen)
+ {
+ // so we only have chunk length and its tag?
+ // wait more
+ logger.Trace($"{instanceId} not enough data to decrypt chunk. write {tmp.Length} byte back to buffer.");
+ tmp.CopyTo(buffer);
+ bufPtr = tmp.Length;
+ return outlength;
+ }
+ logger.Trace($"{instanceId} try decrypt to offset {outlength}");
+ int len = ChunkDecrypt(plain.Slice(outlength), tmp);
+ if (len <= 0)
+ {
+ logger.Trace($"{instanceId} no chunk decrypted, write {tmp.Length} byte back to buffer.");
+
+ // no chunk decrypted
+ tmp.CopyTo(buffer);
+ bufPtr = tmp.Length;
+ return outlength;
+ }
+ logger.Trace($"{instanceId} decrypted {len} to offset {outlength}");
+
+ // drop decrypted data
+ tmp = tmp.Slice(ChunkLengthBytes + tagLen + len + tagLen);
+ outlength += len;
+
+ // logger.Debug("aead dec outlength " + outlength);
+ if (outlength + 100 > TCPParameter.BufferSize)
+ {
+ logger.Trace($"{instanceId} output almost full, write {tmp.Length} byte back to buffer.");
+ tmp.CopyTo(buffer);
+ bufPtr = tmp.Length;
+ return outlength;
+ }
+ bufSize = tmp.Length;
+ // check if we already done all of them
+ if (bufSize <= 0)
+ {
+ bufPtr = 0;
+ logger.Debug($"{instanceId} no data in buffer, already all done");
+ return outlength;
+ }
+ }
+ }
+
+ #endregion
+
+ #region UDP
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int EncryptUDP(ReadOnlySpan plain, Span cipher)
+ {
+ RNG.GetSpan(cipher.Slice(0, saltLen));
+ InitCipher(cipher.Slice(0, saltLen).ToArray(), true);
+ return saltLen + CipherEncrypt(plain, cipher.Slice(saltLen));
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int DecryptUDP(Span plain, ReadOnlySpan cipher)
+ {
+ InitCipher(cipher.Slice(0, saltLen).ToArray(), false);
+ return CipherDecrypt(plain, cipher.Slice(saltLen));
+ }
+
+ #endregion
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ private int ChunkEncrypt(ReadOnlySpan plain, Span cipher)
+ {
+ if (plain.Length > ChunkLengthMask)
+ {
+ logger.Error("enc chunk too big");
+ throw new CryptoErrorException();
+ }
+
+ byte[] lenbuf = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)plain.Length));
+ int cipherLenSize = CipherEncrypt(lenbuf, cipher);
+ CryptoUtils.SodiumIncrement(nonce);
+ int cipherDataSize = CipherEncrypt(plain, cipher.Slice(cipherLenSize));
+ CryptoUtils.SodiumIncrement(nonce);
+
+ return cipherLenSize + cipherDataSize;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ private int ChunkDecrypt(Span plain, ReadOnlySpan cipher)
+ {
+ // try to dec chunk len
+ byte[] chunkLengthByte = new byte[ChunkLengthBytes];
+ CipherDecrypt(chunkLengthByte, cipher.Slice(0, ChunkLengthBytes + tagLen));
+ ushort chunkLength = (ushort)IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(chunkLengthByte, 0));
+ if (chunkLength > ChunkLengthMask)
+ {
+ // we get invalid chunk
+ logger.Error($"{instanceId} Invalid chunk length: {chunkLength}");
+ throw new CryptoErrorException();
+ }
+ // logger.Debug("Get the real chunk len:" + chunkLength);
+ int bufSize = cipher.Length;
+ if (bufSize < ChunkLengthBytes + tagLen /* we haven't remove them */+ chunkLength + tagLen)
+ {
+ logger.Debug($"{instanceId} need {ChunkLengthBytes + tagLen + chunkLength + tagLen}, but have {cipher.Length}");
+ return 0;
+ }
+ CryptoUtils.SodiumIncrement(nonce);
+ // we have enough data to decrypt one chunk
+ // drop chunk len and its tag from buffer
+ int len = CipherDecrypt(plain, cipher.Slice(ChunkLengthBytes + tagLen, chunkLength + tagLen));
+ CryptoUtils.SodiumIncrement(nonce);
+ logger.Trace($"{instanceId} decrypted {len} byte chunk used {ChunkLengthBytes + tagLen + chunkLength + tagLen} from {cipher.Length}");
+ return len;
+ }
+ }
+}
diff --git a/shadowsocks-csharp/Encryption/AEAD/AEADNaClEncryptor.cs b/Shadowsocks.Crypto/Crypto/AEAD/AEADNaClCrypto.cs
similarity index 72%
rename from shadowsocks-csharp/Encryption/AEAD/AEADNaClEncryptor.cs
rename to Shadowsocks.Crypto/Crypto/AEAD/AEADNaClCrypto.cs
index 0634332f..f12b229b 100644
--- a/shadowsocks-csharp/Encryption/AEAD/AEADNaClEncryptor.cs
+++ b/Shadowsocks.Crypto/Crypto/AEAD/AEADNaClCrypto.cs
@@ -1,15 +1,16 @@
-using NaCl.Core;
-using NaCl.Core.Base;
using System;
using System.Collections.Generic;
-namespace Shadowsocks.Encryption.AEAD
+using NaCl.Core;
+using NaCl.Core.Base;
+
+namespace Shadowsocks.Crypto.AEAD
{
- public class AEADNaClEncryptor : AEADEncryptor
+ public class AEADNaClCrypto : AEADCrypto
{
SnufflePoly1305 enc;
- public AEADNaClEncryptor(string method, string password) : base(method, password)
+ public AEADNaClCrypto(string method, string password) : base(method, password)
{
}
@@ -43,16 +44,19 @@ namespace Shadowsocks.Encryption.AEAD
public override int CipherEncrypt(ReadOnlySpan plain, Span cipher)
{
- byte[] ct = enc.Encrypt(plain, null, nonce);
- ct.CopyTo(cipher);
- return ct.Length;
+ //byte[] ct = enc.Encrypt(plain, null, nonce);
+ //ct.CopyTo(cipher);
+ //return ct.Length;
+
+ throw new NotImplementedException();
}
public override int CipherDecrypt(Span plain, ReadOnlySpan cipher)
{
- byte[] pt = enc.Decrypt(cipher, null, nonce);
- pt.CopyTo(plain);
- return pt.Length;
+ //byte[] pt = enc.Decrypt(cipher, null, nonce);
+ //pt.CopyTo(plain);
+ //return pt.Length;
+ throw new NotImplementedException();
}
}
}
diff --git a/shadowsocks-csharp/Encryption/CipherInfo.cs b/Shadowsocks.Crypto/Crypto/CipherInfo.cs
similarity index 92%
rename from shadowsocks-csharp/Encryption/CipherInfo.cs
rename to Shadowsocks.Crypto/Crypto/CipherInfo.cs
index 210a4027..93e1d83c 100644
--- a/shadowsocks-csharp/Encryption/CipherInfo.cs
+++ b/Shadowsocks.Crypto/Crypto/CipherInfo.cs
@@ -1,6 +1,4 @@
-using Shadowsocks.Controller;
-
-namespace Shadowsocks.Encryption
+namespace Shadowsocks.Crypto
{
public enum CipherFamily
{
@@ -96,7 +94,9 @@ namespace Shadowsocks.Encryption
public override string ToString()
{
- return StandardState == CipherStandardState.InUse ? Name : $"{Name} ({I18N.GetString(StandardState.ToString().ToLower())})";
+ // TODO:
+ // return StandardState == CipherStandardState.InUse ? Name : $"{Name} ({I18N.GetString(StandardState.ToString().ToLower())})";
+ return "";
}
public string ToString(bool verbose)
{
@@ -108,4 +108,4 @@ namespace Shadowsocks.Encryption
return $"{Name} {StandardState} {CipherParameter}";
}
}
-}
\ No newline at end of file
+}
diff --git a/shadowsocks-csharp/Encryption/EncryptorBase.cs b/Shadowsocks.Crypto/Crypto/CryptoBase.cs
similarity index 83%
rename from shadowsocks-csharp/Encryption/EncryptorBase.cs
rename to Shadowsocks.Crypto/Crypto/CryptoBase.cs
index 7effb0c2..1fa95af9 100644
--- a/shadowsocks-csharp/Encryption/EncryptorBase.cs
+++ b/Shadowsocks.Crypto/Crypto/CryptoBase.cs
@@ -1,49 +1,48 @@
-using System;
-using System.Runtime.CompilerServices;
-
-namespace Shadowsocks.Encryption
-{
- public abstract class EncryptorBase : IEncryptor
- {
- private static int _currentId = 0;
-
- public const int MaxInputSize = 32768;
-
- public const int MAX_DOMAIN_LEN = 255;
- public const int ADDR_PORT_LEN = 2;
- public const int ADDR_ATYP_LEN = 1;
-
- public const int ATYP_IPv4 = 0x01;
- public const int ATYP_DOMAIN = 0x03;
- public const int ATYP_IPv6 = 0x04;
-
- public const int MD5Length = 16;
-
- // for debugging only, give it a number to trace data stream
- public readonly int instanceId;
-
- protected EncryptorBase(string method, string password)
- {
- instanceId = _currentId;
- _currentId++;
-
- Method = method;
- Password = password;
- }
-
- protected string Method;
- protected string Password;
-
- public override string ToString()
- {
- return $"{instanceId}({Method},{Password})";
- }
-
- public abstract int Encrypt(ReadOnlySpan plain, Span cipher);
- public abstract int Decrypt(Span plain, ReadOnlySpan cipher);
- public abstract int EncryptUDP(ReadOnlySpan plain, Span cipher);
- public abstract int DecryptUDP(Span plain, ReadOnlySpan cipher);
-
- public int AddressBufferLength { get; set; } = -1;
- }
-}
\ No newline at end of file
+using System;
+
+namespace Shadowsocks.Crypto
+{
+ public abstract class CryptoBase : ICrypto
+ {
+ private static int _currentId = 0;
+
+ public const int MaxInputSize = 32768;
+
+ public const int MAX_DOMAIN_LEN = 255;
+ public const int ADDR_PORT_LEN = 2;
+ public const int ADDR_ATYP_LEN = 1;
+
+ public const int ATYP_IPv4 = 0x01;
+ public const int ATYP_DOMAIN = 0x03;
+ public const int ATYP_IPv6 = 0x04;
+
+ public const int MD5Length = 16;
+
+ // for debugging only, give it a number to trace data stream
+ public readonly int instanceId;
+
+ protected CryptoBase(string method, string password)
+ {
+ instanceId = _currentId;
+ _currentId++;
+
+ Method = method;
+ Password = password;
+ }
+
+ protected string Method;
+ protected string Password;
+
+ public override string ToString()
+ {
+ return $"{instanceId}({Method},{Password})";
+ }
+
+ public abstract int Encrypt(ReadOnlySpan plain, Span cipher);
+ public abstract int Decrypt(Span plain, ReadOnlySpan cipher);
+ public abstract int EncryptUDP(ReadOnlySpan plain, Span cipher);
+ public abstract int DecryptUDP(Span plain, ReadOnlySpan cipher);
+
+ public int AddressBufferLength { get; set; } = -1;
+ }
+}
diff --git a/shadowsocks-csharp/Encryption/EncryptorFactory.cs b/Shadowsocks.Crypto/Crypto/CryptoFactory.cs
similarity index 74%
rename from shadowsocks-csharp/Encryption/EncryptorFactory.cs
rename to Shadowsocks.Crypto/Crypto/CryptoFactory.cs
index e3418257..01aaec72 100644
--- a/shadowsocks-csharp/Encryption/EncryptorFactory.cs
+++ b/Shadowsocks.Crypto/Crypto/CryptoFactory.cs
@@ -1,119 +1,123 @@
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-using System.Text;
-using Shadowsocks.Encryption.AEAD;
-using Shadowsocks.Encryption.Stream;
-
-namespace Shadowsocks.Encryption
-{
- public static class EncryptorFactory
- {
- public static string DefaultCipher = "chacha20-ietf-poly1305";
-
- private static readonly Dictionary _registeredEncryptors = new Dictionary();
- private static readonly Dictionary ciphers = new Dictionary();
- private static readonly Type[] ConstructorTypes = { typeof(string), typeof(string) };
-
- static EncryptorFactory()
- {
- foreach (var method in StreamPlainNativeEncryptor.SupportedCiphers())
- {
- if (!_registeredEncryptors.ContainsKey(method.Key))
- {
- ciphers.Add(method.Key, method.Value);
- _registeredEncryptors.Add(method.Key, typeof(StreamPlainNativeEncryptor));
- }
- }
- foreach (var method in StreamRc4NativeEncryptor.SupportedCiphers())
- {
- if (!_registeredEncryptors.ContainsKey(method.Key))
- {
- ciphers.Add(method.Key, method.Value);
- _registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeEncryptor));
- }
- }
- foreach (var method in StreamAesCfbBouncyCastleEncryptor.SupportedCiphers())
- {
- if (!_registeredEncryptors.ContainsKey(method.Key))
- {
- ciphers.Add(method.Key, method.Value);
- _registeredEncryptors.Add(method.Key, typeof(StreamAesCfbBouncyCastleEncryptor));
- }
- }
- foreach (var method in StreamChachaNaClEncryptor.SupportedCiphers())
- {
- if (!_registeredEncryptors.ContainsKey(method.Key))
- {
- ciphers.Add(method.Key, method.Value);
- _registeredEncryptors.Add(method.Key, typeof(StreamChachaNaClEncryptor));
- }
- }
-
-
- foreach (var method in AEADAesGcmNativeEncryptor.SupportedCiphers())
- {
- if (!_registeredEncryptors.ContainsKey(method.Key))
- {
- ciphers.Add(method.Key, method.Value);
- _registeredEncryptors.Add(method.Key, typeof(AEADAesGcmNativeEncryptor));
- }
- }
- foreach (var method in AEADNaClEncryptor.SupportedCiphers())
- {
- if (!_registeredEncryptors.ContainsKey(method.Key))
- {
- ciphers.Add(method.Key, method.Value);
- _registeredEncryptors.Add(method.Key, typeof(AEADNaClEncryptor));
- }
- }
- }
-
- public static IEncryptor GetEncryptor(string method, string password)
- {
- if (string.IsNullOrEmpty(method))
- {
- method = Model.Server.DefaultMethod;
- }
-
- method = method.ToLowerInvariant();
- bool ok = _registeredEncryptors.TryGetValue(method, out Type t);
- if (!ok)
- {
- t = _registeredEncryptors[DefaultCipher];
- }
-
- ConstructorInfo c = t?.GetConstructor(ConstructorTypes) ??
- throw new TypeLoadException("can't load constructor");
- if (c == null) throw new System.Exception("Invalid ctor");
- IEncryptor result = (IEncryptor)c.Invoke(new object[] { method, password });
- return result;
- }
-
- public static string DumpRegisteredEncryptor()
- {
- var sb = new StringBuilder();
- sb.Append(Environment.NewLine);
- sb.AppendLine("-------------------------");
- sb.AppendLine("Registered Encryptor Info");
- foreach (var encryptor in _registeredEncryptors)
- {
- sb.AppendLine($"{ciphers[encryptor.Key].ToString(true)} => {encryptor.Value.Name}");
- }
- // use ----- instead of =======, so when user paste it to Github, it won't became title
- sb.AppendLine("-------------------------");
- return sb.ToString();
- }
-
- public static CipherInfo GetCipherInfo(string name)
- {
- // TODO: Replace cipher when required not exist
- return ciphers[name];
- }
-
- public static IEnumerable ListAvaliableCiphers()
- {
- return ciphers.Values;
- }
- }
-}
\ No newline at end of file
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+using Shadowsocks.Common.Crypto;
+using Shadowsocks.Common.Model;
+using Shadowsocks.Crypto.AEAD;
+using Shadowsocks.Crypto.Stream;
+
+namespace Shadowsocks.Crypto
+{
+ public static class CryptoFactory
+ {
+ public static string DefaultCipher = "chacha20-ietf-poly1305";
+
+ private static readonly Dictionary _registeredEncryptors = new Dictionary();
+ private static readonly Dictionary ciphers = new Dictionary();
+ private static readonly Type[] ConstructorTypes = { typeof(string), typeof(string) };
+
+ static CryptoFactory()
+ {
+ foreach (var method in StreamPlainNativeCrypto.SupportedCiphers())
+ {
+ if (!_registeredEncryptors.ContainsKey(method.Key))
+ {
+ ciphers.Add(method.Key, method.Value);
+ _registeredEncryptors.Add(method.Key, typeof(StreamPlainNativeCrypto));
+ }
+ }
+ foreach (var method in StreamRc4NativeCrypto.SupportedCiphers())
+ {
+ if (!_registeredEncryptors.ContainsKey(method.Key))
+ {
+ ciphers.Add(method.Key, method.Value);
+ _registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeCrypto));
+ }
+ }
+ foreach (var method in StreamAesCfbBouncyCastleCrypto.SupportedCiphers())
+ {
+ if (!_registeredEncryptors.ContainsKey(method.Key))
+ {
+ ciphers.Add(method.Key, method.Value);
+ _registeredEncryptors.Add(method.Key, typeof(StreamAesCfbBouncyCastleCrypto));
+ }
+ }
+ foreach (var method in StreamChachaNaClCrypto.SupportedCiphers())
+ {
+ if (!_registeredEncryptors.ContainsKey(method.Key))
+ {
+ ciphers.Add(method.Key, method.Value);
+ _registeredEncryptors.Add(method.Key, typeof(StreamChachaNaClCrypto));
+ }
+ }
+
+
+ foreach (var method in AEADAesGcmNativeCrypto.SupportedCiphers())
+ {
+ if (!_registeredEncryptors.ContainsKey(method.Key))
+ {
+ ciphers.Add(method.Key, method.Value);
+ _registeredEncryptors.Add(method.Key, typeof(AEADAesGcmNativeCrypto));
+ }
+ }
+ foreach (var method in AEADNaClCrypto.SupportedCiphers())
+ {
+ if (!_registeredEncryptors.ContainsKey(method.Key))
+ {
+ ciphers.Add(method.Key, method.Value);
+ _registeredEncryptors.Add(method.Key, typeof(AEADNaClCrypto));
+ }
+ }
+ }
+
+ public static ICrypto GetEncryptor(string method, string password)
+ {
+ if (string.IsNullOrEmpty(method))
+ {
+ // todo
+ //method = IoCManager.Container.Resolve().GetDefaultMethod();
+ }
+
+ method = method.ToLowerInvariant();
+ bool ok = _registeredEncryptors.TryGetValue(method, out Type t);
+ if (!ok)
+ {
+ t = _registeredEncryptors[DefaultCipher];
+ }
+
+ ConstructorInfo c = t?.GetConstructor(ConstructorTypes) ??
+ throw new TypeLoadException("can't load constructor");
+ if (c == null) throw new System.Exception("Invalid ctor");
+ ICrypto result = (ICrypto)c.Invoke(new object[] { method, password });
+ return result;
+ }
+
+ public static string DumpRegisteredEncryptor()
+ {
+ var sb = new StringBuilder();
+ sb.Append(Environment.NewLine);
+ sb.AppendLine("-------------------------");
+ sb.AppendLine("Registered Encryptor Info");
+ foreach (var encryptor in _registeredEncryptors)
+ {
+ sb.AppendLine($"{ciphers[encryptor.Key].ToString(true)} => {encryptor.Value.Name}");
+ }
+ // use ----- instead of =======, so when user paste it to Github, it won't became title
+ sb.AppendLine("-------------------------");
+ return sb.ToString();
+ }
+
+ public static CipherInfo GetCipherInfo(string name)
+ {
+ // TODO: Replace cipher when required not exist
+ return ciphers[name];
+ }
+
+ public static IEnumerable ListAvaliableCiphers()
+ {
+ return ciphers.Values;
+ }
+ }
+}
diff --git a/shadowsocks-csharp/Encryption/Exception/CryptoException.cs b/Shadowsocks.Crypto/Crypto/Exception/CryptoException.cs
similarity index 84%
rename from shadowsocks-csharp/Encryption/Exception/CryptoException.cs
rename to Shadowsocks.Crypto/Crypto/Exception/CryptoException.cs
index 2871affc..bf05486c 100644
--- a/shadowsocks-csharp/Encryption/Exception/CryptoException.cs
+++ b/Shadowsocks.Crypto/Crypto/Exception/CryptoException.cs
@@ -1,17 +1,17 @@
-namespace Shadowsocks.Encryption.Exception
-{
- public class CryptoErrorException : System.Exception
- {
- public CryptoErrorException()
- {
- }
-
- public CryptoErrorException(string msg) : base(msg)
- {
- }
-
- public CryptoErrorException(string message, System.Exception innerException) : base(message, innerException)
- {
- }
- }
-}
\ No newline at end of file
+namespace Shadowsocks.Crypto.Exception
+{
+ public class CryptoErrorException : System.Exception
+ {
+ public CryptoErrorException()
+ {
+ }
+
+ public CryptoErrorException(string msg) : base(msg)
+ {
+ }
+
+ public CryptoErrorException(string message, System.Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/shadowsocks-csharp/Encryption/IEncryptor.cs b/Shadowsocks.Crypto/Crypto/ICrypto.cs
similarity index 79%
rename from shadowsocks-csharp/Encryption/IEncryptor.cs
rename to Shadowsocks.Crypto/Crypto/ICrypto.cs
index 060d57a7..48c5f210 100644
--- a/shadowsocks-csharp/Encryption/IEncryptor.cs
+++ b/Shadowsocks.Crypto/Crypto/ICrypto.cs
@@ -1,14 +1,14 @@
-using System;
-
-namespace Shadowsocks.Encryption
-{
- public interface IEncryptor
- {
- /* length == -1 means not used */
- int AddressBufferLength { set; get; }
- int Encrypt(ReadOnlySpan plain, Span cipher);
- int Decrypt(Span plain, ReadOnlySpan cipher);
- int EncryptUDP(ReadOnlySpan plain, Span cipher);
- int DecryptUDP(Span plain, ReadOnlySpan cipher);
- }
-}
+using System;
+
+namespace Shadowsocks.Crypto
+{
+ public interface ICrypto
+ {
+ /* length == -1 means not used */
+ int AddressBufferLength { set; get; }
+ int Encrypt(ReadOnlySpan plain, Span cipher);
+ int Decrypt(Span plain, ReadOnlySpan cipher);
+ int EncryptUDP(ReadOnlySpan plain, Span cipher);
+ int DecryptUDP(Span plain, ReadOnlySpan cipher);
+ }
+}
diff --git a/shadowsocks-csharp/Encryption/RNG.cs b/Shadowsocks.Crypto/Crypto/RNG.cs
similarity index 91%
rename from shadowsocks-csharp/Encryption/RNG.cs
rename to Shadowsocks.Crypto/Crypto/RNG.cs
index 276a9b1c..5f8711d3 100644
--- a/shadowsocks-csharp/Encryption/RNG.cs
+++ b/Shadowsocks.Crypto/Crypto/RNG.cs
@@ -1,50 +1,50 @@
-using System;
-using System.Security.Cryptography;
-
-namespace Shadowsocks.Encryption
-{
- public static class RNG
- {
- private static RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();
-
- public static void Reload()
- {
- _rng.Dispose();
- _rng = new RNGCryptoServiceProvider();
- }
-
- public static void GetSpan(Span span)
- {
- _rng.GetBytes(span);
- }
-
- public static Span GetSpan(int length)
- {
- Span span = new byte[length];
- _rng.GetBytes(span);
- return span;
- }
-
- public static byte[] GetBytes(int length)
- {
- byte[] buf = new byte[length];
- _rng.GetBytes(buf);
- return buf;
- }
-
- public static void GetBytes(byte[] buf, int len)
- {
- try
- {
- _rng.GetBytes(buf, 0, len);
- }
- catch
- {
- // the backup way
- byte[] tmp = new byte[len];
- _rng.GetBytes(tmp);
- Buffer.BlockCopy(tmp, 0, buf, 0, len);
- }
- }
- }
-}
\ No newline at end of file
+using System;
+using System.Security.Cryptography;
+
+namespace Shadowsocks.Crypto
+{
+ public static class RNG
+ {
+ private static RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();
+
+ public static void Reload()
+ {
+ _rng.Dispose();
+ _rng = new RNGCryptoServiceProvider();
+ }
+
+ public static void GetSpan(Span span)
+ {
+ _rng.GetBytes(span);
+ }
+
+ public static Span GetSpan(int length)
+ {
+ Span span = new byte[length];
+ _rng.GetBytes(span);
+ return span;
+ }
+
+ public static byte[] GetBytes(int length)
+ {
+ byte[] buf = new byte[length];
+ _rng.GetBytes(buf);
+ return buf;
+ }
+
+ public static void GetBytes(byte[] buf, int len)
+ {
+ try
+ {
+ _rng.GetBytes(buf, 0, len);
+ }
+ catch
+ {
+ // the backup way
+ byte[] tmp = new byte[len];
+ _rng.GetBytes(tmp);
+ Buffer.BlockCopy(tmp, 0, buf, 0, len);
+ }
+ }
+ }
+}
diff --git a/shadowsocks-csharp/Encryption/Stream/ExtendedCfbBlockCipher.cs b/Shadowsocks.Crypto/Crypto/Stream/ExtendedCfbBlockCipher.cs
similarity index 100%
rename from shadowsocks-csharp/Encryption/Stream/ExtendedCfbBlockCipher.cs
rename to Shadowsocks.Crypto/Crypto/Stream/ExtendedCfbBlockCipher.cs
diff --git a/shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs b/Shadowsocks.Crypto/Crypto/Stream/StreamAesBouncyCastleCrypto.cs
similarity index 89%
rename from shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs
rename to Shadowsocks.Crypto/Crypto/Stream/StreamAesBouncyCastleCrypto.cs
index 9e0cb8db..fde7bb9b 100644
--- a/shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs
+++ b/Shadowsocks.Crypto/Crypto/Stream/StreamAesBouncyCastleCrypto.cs
@@ -1,19 +1,19 @@
-using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
-namespace Shadowsocks.Encryption.Stream
+namespace Shadowsocks.Crypto.Stream
{
- public class StreamAesCfbBouncyCastleEncryptor : StreamEncryptor
+ public class StreamAesCfbBouncyCastleCrypto : StreamCrypto
{
readonly byte[] cfbBuf = new byte[MaxInputSize + 128];
int ptr = 0;
readonly ExtendedCfbBlockCipher b;
- public StreamAesCfbBouncyCastleEncryptor(string method, string password) : base(method, password)
+ public StreamAesCfbBouncyCastleCrypto(string method, string password) : base(method, password)
{
b = new ExtendedCfbBlockCipher(new AesEngine(), 128);
}
@@ -36,7 +36,7 @@ namespace Shadowsocks.Encryption.Stream
return cipher.Length;
}
- [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ [MethodImpl( MethodImplOptions.AggressiveInlining)]
private void CipherUpdate(ReadOnlySpan i, Span o)
{
Span ob = new byte[o.Length + 128];
diff --git a/shadowsocks-csharp/Encryption/Stream/StreamChachaNaClEncryptor.cs b/Shadowsocks.Crypto/Crypto/Stream/StreamChachaNaClCrypto.cs
similarity index 88%
rename from shadowsocks-csharp/Encryption/Stream/StreamChachaNaClEncryptor.cs
rename to Shadowsocks.Crypto/Crypto/Stream/StreamChachaNaClCrypto.cs
index 8346f171..3d007cc2 100644
--- a/shadowsocks-csharp/Encryption/Stream/StreamChachaNaClEncryptor.cs
+++ b/Shadowsocks.Crypto/Crypto/Stream/StreamChachaNaClCrypto.cs
@@ -1,11 +1,11 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using NaCl.Core;
-namespace Shadowsocks.Encryption.Stream
+namespace Shadowsocks.Crypto.Stream
{
- public class StreamChachaNaClEncryptor : StreamEncryptor
+ public class StreamChachaNaClCrypto : StreamCrypto
{
const int BlockSize = 64;
@@ -22,7 +22,7 @@ namespace Shadowsocks.Encryption.Stream
int remain = 0;
// increase counter manually...
int ic = 0;
- public StreamChachaNaClEncryptor(string method, string password) : base(method, password)
+ public StreamChachaNaClCrypto(string method, string password) : base(method, password)
{
}
@@ -36,7 +36,7 @@ namespace Shadowsocks.Encryption.Stream
return CipherUpdate(plain, cipher, true);
}
- [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CipherUpdate(ReadOnlySpan i, Span o, bool enc)
{
// about performance problem:
diff --git a/shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs b/Shadowsocks.Crypto/Crypto/Stream/StreamCrypto.cs
similarity index 88%
rename from shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs
rename to Shadowsocks.Crypto/Crypto/Stream/StreamCrypto.cs
index eacd25e6..f31485be 100644
--- a/shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs
+++ b/Shadowsocks.Crypto/Crypto/Stream/StreamCrypto.cs
@@ -1,179 +1,181 @@
-using NLog;
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Text;
-
-namespace Shadowsocks.Encryption.Stream
-{
- public abstract class StreamEncryptor : EncryptorBase
- {
- private static readonly Logger logger = LogManager.GetCurrentClassLogger();
-
- // shared by TCP decrypt UDP encrypt and decrypt
- protected static byte[] sharedBuffer = new byte[65536];
-
- // Is first packet
- protected bool ivReady;
-
- protected CipherFamily cipherFamily;
- protected CipherInfo CipherInfo;
- // long-time master key
- protected static byte[] key = Array.Empty();
- protected byte[] iv = Array.Empty();
- protected int keyLen;
- protected int ivLen;
-
- public StreamEncryptor(string method, string password)
- : base(method, password)
- {
- CipherInfo = GetCiphers()[method.ToLower()];
- cipherFamily = CipherInfo.Type;
- StreamCipherParameter parameter = (StreamCipherParameter)CipherInfo.CipherParameter;
- keyLen = parameter.KeySize;
- ivLen = parameter.IvSize;
-
- InitKey(password);
-
- logger.Dump($"key {instanceId}", key, keyLen);
- }
-
- protected abstract Dictionary GetCiphers();
-
- private void InitKey(string password)
- {
- byte[] passbuf = Encoding.UTF8.GetBytes(password);
- key ??= new byte[keyLen];
- if (key.Length != keyLen)
- {
- Array.Resize(ref key, keyLen);
- }
-
- LegacyDeriveKey(passbuf, key, keyLen);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void LegacyDeriveKey(byte[] password, byte[] key, int keylen)
- {
- byte[] result = new byte[password.Length + MD5Length];
- int i = 0;
- byte[] md5sum = Array.Empty();
- while (i < keylen)
- {
- if (i == 0)
- {
- md5sum = CryptoUtils.MD5(password);
- }
- else
- {
- Array.Copy(md5sum, 0, result, 0, MD5Length);
- Array.Copy(password, 0, result, MD5Length, password.Length);
- md5sum = CryptoUtils.MD5(result);
- }
- Array.Copy(md5sum, 0, key, i, Math.Min(MD5Length, keylen - i));
- i += MD5Length;
- }
- }
-
- protected virtual void InitCipher(byte[] iv, bool isEncrypt)
- {
- if (ivLen == 0)
- {
- return;
- }
-
- this.iv = new byte[ivLen];
- Array.Copy(iv, this.iv, ivLen);
- }
-
- protected abstract int CipherEncrypt(ReadOnlySpan plain, Span cipher);
- protected abstract int CipherDecrypt(Span plain, ReadOnlySpan cipher);
-
- #region TCP
- [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.AggressiveOptimization)]
- public override int Encrypt(ReadOnlySpan plain, Span cipher)
- {
- int cipherOffset = 0;
- logger.Trace($"{instanceId} encrypt TCP, generate iv: {!ivReady}");
- if (!ivReady)
- {
- // Generate IV
- byte[] ivBytes = RNG.GetBytes(ivLen);
- InitCipher(ivBytes, true);
- ivBytes.CopyTo(cipher);
- cipherOffset = ivLen;
- cipher = cipher.Slice(cipherOffset);
- ivReady = true;
- }
- int clen = CipherEncrypt(plain, cipher);
-
- logger.DumpBase64($"plain {instanceId}", plain);
- logger.DumpBase64($"cipher {instanceId}", cipher.Slice(0, clen));
- logger.Dump($"iv {instanceId}", iv, ivLen);
- return clen + cipherOffset;
- }
-
- private int recieveCtr = 0;
- [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.AggressiveOptimization)]
- public override int Decrypt(Span plain, ReadOnlySpan cipher)
- {
- logger.Trace($"{instanceId} decrypt TCP, read iv: {!ivReady}");
-
- int cipherOffset = 0;
- // is first packet, need read iv
- if (!ivReady)
- {
- // push to buffer in case of not enough data
- cipher.CopyTo(sharedBuffer.AsSpan(recieveCtr));
- recieveCtr += cipher.Length;
-
- // not enough data for read iv, return 0 byte data
- if (recieveCtr <= ivLen)
- {
- return 0;
- }
- // start decryption
- ivReady = true;
- if (ivLen > 0)
- {
- // read iv
- byte[] iv = sharedBuffer.AsSpan(0, ivLen).ToArray();
- InitCipher(iv, false);
- }
- else
- {
- InitCipher(Array.Empty(), false);
- }
- cipherOffset += ivLen;
- }
-
- // read all data from buffer
- int len = CipherDecrypt(plain, cipher.Slice(cipherOffset));
- logger.DumpBase64($"cipher {instanceId}", cipher.Slice(cipherOffset));
- logger.DumpBase64($"plain {instanceId}", plain.Slice(0, len));
- logger.Dump($"iv {instanceId}", iv, ivLen);
- return len;
- }
-
- #endregion
-
- #region UDP
- [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.AggressiveOptimization)]
- public override int EncryptUDP(ReadOnlySpan plain, Span cipher)
- {
- byte[] iv = RNG.GetBytes(ivLen);
- iv.CopyTo(cipher);
- InitCipher(iv, true);
- return ivLen + CipherEncrypt(plain, cipher.Slice(ivLen));
- }
-
- [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.AggressiveOptimization)]
- public override int DecryptUDP(Span plain, ReadOnlySpan cipher)
- {
- InitCipher(cipher.Slice(0, ivLen).ToArray(), false);
- return CipherDecrypt(plain, cipher.Slice(ivLen));
- }
-
- #endregion
- }
-}
\ No newline at end of file
+using NLog;
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Shadowsocks.Crypto.Stream
+{
+ public abstract class StreamCrypto : CryptoBase
+ {
+ private static readonly Logger logger = LogManager.GetCurrentClassLogger();
+
+ // shared by TCP decrypt UDP encrypt and decrypt
+ protected static byte[] sharedBuffer = new byte[65536];
+
+ // Is first packet
+ protected bool ivReady;
+
+ protected CipherFamily cipherFamily;
+ protected CipherInfo CipherInfo;
+ // long-time master key
+ protected static byte[] key = Array.Empty();
+ protected byte[] iv = Array.Empty();
+ protected int keyLen;
+ protected int ivLen;
+
+ public StreamCrypto(string method, string password)
+ : base(method, password)
+ {
+ CipherInfo = GetCiphers()[method.ToLower()];
+ cipherFamily = CipherInfo.Type;
+ StreamCipherParameter parameter = (StreamCipherParameter)CipherInfo.CipherParameter;
+ keyLen = parameter.KeySize;
+ ivLen = parameter.IvSize;
+
+ InitKey(password);
+
+ logger.Dump($"key {instanceId}", key, keyLen);
+ }
+
+ protected abstract Dictionary GetCiphers();
+
+ private void InitKey(string password)
+ {
+ byte[] passbuf = Encoding.UTF8.GetBytes(password);
+ key ??= new byte[keyLen];
+ if (key.Length != keyLen)
+ {
+ Array.Resize(ref key, keyLen);
+ }
+
+ LegacyDeriveKey(passbuf, key, keyLen);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void LegacyDeriveKey(byte[] password, byte[] key, int keylen)
+ {
+ byte[] result = new byte[password.Length + MD5Length];
+ int i = 0;
+ byte[] md5sum = Array.Empty();
+ while (i < keylen)
+ {
+ if (i == 0)
+ {
+ md5sum = CryptoUtils.MD5(password);
+ }
+ else
+ {
+ Array.Copy(md5sum, 0, result, 0, MD5Length);
+ Array.Copy(password, 0, result, MD5Length, password.Length);
+ md5sum = CryptoUtils.MD5(result);
+ }
+ Array.Copy(md5sum, 0, key, i, Math.Min(MD5Length, keylen - i));
+ i += MD5Length;
+ }
+ }
+
+ protected virtual void InitCipher(byte[] iv, bool isEncrypt)
+ {
+ if (ivLen == 0)
+ {
+ return;
+ }
+
+ this.iv = new byte[ivLen];
+ Array.Copy(iv, this.iv, ivLen);
+ }
+
+ protected abstract int CipherEncrypt(ReadOnlySpan plain, Span cipher);
+ protected abstract int CipherDecrypt(Span plain, ReadOnlySpan cipher);
+
+ #region TCP
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int Encrypt(ReadOnlySpan plain, Span cipher)
+ {
+ int cipherOffset = 0;
+ logger.Trace($"{instanceId} encrypt TCP, generate iv: {!ivReady}");
+ if (!ivReady)
+ {
+ // Generate IV
+ byte[] ivBytes = RNG.GetBytes(ivLen);
+ InitCipher(ivBytes, true);
+ ivBytes.CopyTo(cipher);
+ cipherOffset = ivLen;
+ cipher = cipher.Slice(cipherOffset);
+ ivReady = true;
+ }
+ int clen = CipherEncrypt(plain, cipher);
+
+ logger.DumpBase64($"plain {instanceId}", plain);
+ logger.DumpBase64($"cipher {instanceId}", cipher.Slice(0, clen));
+ logger.Dump($"iv {instanceId}", iv, ivLen);
+ return clen + cipherOffset;
+ }
+
+ private int recieveCtr = 0;
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int Decrypt(Span plain, ReadOnlySpan cipher)
+ {
+ logger.Trace($"{instanceId} decrypt TCP, read iv: {!ivReady}");
+
+ int cipherOffset = 0;
+ // is first packet, need read iv
+ if (!ivReady)
+ {
+ // push to buffer in case of not enough data
+ cipher.CopyTo(sharedBuffer.AsSpan(recieveCtr));
+ recieveCtr += cipher.Length;
+
+ // not enough data for read iv, return 0 byte data
+ if (recieveCtr <= ivLen)
+ {
+ return 0;
+ }
+ // start decryption
+ ivReady = true;
+ if (ivLen > 0)
+ {
+ // read iv
+ byte[] iv = sharedBuffer.AsSpan(0, ivLen).ToArray();
+ InitCipher(iv, false);
+ }
+ else
+ {
+ InitCipher(Array.Empty(), false);
+ }
+ cipherOffset += ivLen;
+ }
+
+ // read all data from buffer
+ int len = CipherDecrypt(plain, cipher.Slice(cipherOffset));
+
+ logger.DumpBase64($"cipher {instanceId}", cipher.Slice(cipherOffset));
+ logger.DumpBase64($"plain {instanceId}", plain.Slice(0, len));
+ logger.Dump($"iv {instanceId}", iv, ivLen);
+ return len;
+ }
+
+ #endregion
+
+ #region UDP
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int EncryptUDP(ReadOnlySpan plain, Span cipher)
+ {
+ byte[] iv = RNG.GetBytes(ivLen);
+ iv.CopyTo(cipher);
+ InitCipher(iv, true);
+ return ivLen + CipherEncrypt(plain, cipher.Slice(ivLen));
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int DecryptUDP(Span plain, ReadOnlySpan cipher)
+ {
+ InitCipher(cipher.Slice(0, ivLen).ToArray(), false);
+ return CipherDecrypt(plain, cipher.Slice(ivLen));
+ }
+
+ #endregion
+ }
+}
diff --git a/shadowsocks-csharp/Encryption/Stream/StreamPlainNativeEncryptor.cs b/Shadowsocks.Crypto/Crypto/Stream/StreamPlainNativeCrypto.cs
similarity index 82%
rename from shadowsocks-csharp/Encryption/Stream/StreamPlainNativeEncryptor.cs
rename to Shadowsocks.Crypto/Crypto/Stream/StreamPlainNativeCrypto.cs
index 8293db18..c4b02c0c 100644
--- a/shadowsocks-csharp/Encryption/Stream/StreamPlainNativeEncryptor.cs
+++ b/Shadowsocks.Crypto/Crypto/Stream/StreamPlainNativeCrypto.cs
@@ -1,12 +1,12 @@
-using System;
+using System;
using System.Collections.Generic;
-namespace Shadowsocks.Encryption.Stream
+namespace Shadowsocks.Crypto.Stream
{
- public class StreamPlainNativeEncryptor : StreamEncryptor
+ public class StreamPlainNativeCrypto : StreamCrypto
{
- public StreamPlainNativeEncryptor(string method, string password) : base(method, password)
+ public StreamPlainNativeCrypto(string method, string password) : base(method, password)
{
}
diff --git a/shadowsocks-csharp/Encryption/Stream/StreamRc4NativeEncryptor.cs b/Shadowsocks.Crypto/Crypto/Stream/StreamRc4NativeCrypto.cs
similarity index 92%
rename from shadowsocks-csharp/Encryption/Stream/StreamRc4NativeEncryptor.cs
rename to Shadowsocks.Crypto/Crypto/Stream/StreamRc4NativeCrypto.cs
index 41f55186..260a58ae 100644
--- a/shadowsocks-csharp/Encryption/Stream/StreamRc4NativeEncryptor.cs
+++ b/Shadowsocks.Crypto/Crypto/Stream/StreamRc4NativeCrypto.cs
@@ -1,14 +1,14 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
-namespace Shadowsocks.Encryption.Stream
+namespace Shadowsocks.Crypto.Stream
{
- public class StreamRc4NativeEncryptor : StreamEncryptor
+ public class StreamRc4NativeCrypto : StreamCrypto
{
byte[] realkey = new byte[256];
byte[] sbox = new byte[256];
- public StreamRc4NativeEncryptor(string method, string password) : base(method, password)
+ public StreamRc4NativeCrypto(string method, string password) : base(method, password)
{
}
@@ -94,7 +94,6 @@ namespace Shadowsocks.Encryption.Stream
return s;
}
- [MethodImpl(MethodImplOptions.AggressiveOptimization)]
private void RC4(Span s, Span data, int length)
{
for (int n = 0; n < length; n++)
diff --git a/Shadowsocks.Crypto/Crypto/TCPInfo.cs b/Shadowsocks.Crypto/Crypto/TCPInfo.cs
new file mode 100644
index 00000000..663cd7ef
--- /dev/null
+++ b/Shadowsocks.Crypto/Crypto/TCPInfo.cs
@@ -0,0 +1,20 @@
+using Shadowsocks.Crypto.AEAD;
+
+namespace Shadowsocks.Crypto
+{
+ public static class TCPParameter
+ {
+ // each recv size.
+ public const int RecvSize = 2048;
+
+ // overhead of one chunk, reserved for AEAD ciphers
+ // /* two tags */
+ public const int ChunkOverheadSize = 16 * 2 + AEADCrypto.ChunkLengthBytes;
+
+ // max chunk size
+ public const uint MaxChunkSize = AEADCrypto.ChunkLengthMask + AEADCrypto.ChunkLengthBytes + 16 * 2;
+
+ // In general, the ciphertext length, we should take overhead into account
+ public const int BufferSize = RecvSize + (int)MaxChunkSize + 32 /* max salt len */;
+ }
+}
diff --git a/Shadowsocks.Crypto/Shadowsocks.Crypto.csproj b/Shadowsocks.Crypto/Shadowsocks.Crypto.csproj
new file mode 100644
index 00000000..4c42fabc
--- /dev/null
+++ b/Shadowsocks.Crypto/Shadowsocks.Crypto.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netstandard2.1
+ clowwindy & community 2020
+ Shadowsocks Crypto
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/shadowsocks-csharp/Encryption/CryptoUtils.cs b/Shadowsocks.Crypto/Util/CryptoUtils.cs
similarity index 91%
rename from shadowsocks-csharp/Encryption/CryptoUtils.cs
rename to Shadowsocks.Crypto/Util/CryptoUtils.cs
index 6b50769f..12c93fa0 100644
--- a/shadowsocks-csharp/Encryption/CryptoUtils.cs
+++ b/Shadowsocks.Crypto/Util/CryptoUtils.cs
@@ -1,4 +1,4 @@
-using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
@@ -6,7 +6,7 @@ using System;
using System.Security.Cryptography;
using System.Threading;
-namespace Shadowsocks.Encryption
+namespace Shadowsocks.Crypto
{
public static class CryptoUtils
{
@@ -14,14 +14,14 @@ namespace Shadowsocks.Encryption
public static byte[] MD5(byte[] b)
{
- var hash = new byte[EncryptorBase.MD5Length];
+ var hash = new byte[CryptoBase.MD5Length];
Md5Hasher.Value.TryComputeHash(b, hash, out _);
return hash;
}
// currently useless, just keep api same
public static Span MD5(Span span)
{
- Span hash = new byte[EncryptorBase.MD5Length];
+ Span hash = new byte[CryptoBase.MD5Length];
Md5Hasher.Value.TryComputeHash(span, hash, out _);
return hash;
}
diff --git a/Shadowsocks.WPF/App.xaml b/Shadowsocks.WPF/App.xaml
new file mode 100644
index 00000000..b283b871
--- /dev/null
+++ b/Shadowsocks.WPF/App.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Shadowsocks.WPF/App.xaml.cs b/Shadowsocks.WPF/App.xaml.cs
new file mode 100644
index 00000000..36eff132
--- /dev/null
+++ b/Shadowsocks.WPF/App.xaml.cs
@@ -0,0 +1,20 @@
+using ReactiveUI;
+
+using Splat;
+
+using System.Reflection;
+using System.Windows;
+
+namespace Shadowsocks.WPF
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ public App()
+ {
+ Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetCallingAssembly());
+ }
+ }
+}
diff --git a/Shadowsocks.WPF/AssemblyInfo.cs b/Shadowsocks.WPF/AssemblyInfo.cs
new file mode 100644
index 00000000..8b5504ec
--- /dev/null
+++ b/Shadowsocks.WPF/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/shadowsocks-csharp/shadowsocks.ico b/Shadowsocks.WPF/Assets/shadowsocks.ico
old mode 100755
new mode 100644
similarity index 100%
rename from shadowsocks-csharp/shadowsocks.ico
rename to Shadowsocks.WPF/Assets/shadowsocks.ico
diff --git a/shadowsocks-csharp/Resources/ss128.pdn b/Shadowsocks.WPF/Assets/ss128.pdn
similarity index 100%
rename from shadowsocks-csharp/Resources/ss128.pdn
rename to Shadowsocks.WPF/Assets/ss128.pdn
diff --git a/shadowsocks-csharp/Resources/ss32.pdn b/Shadowsocks.WPF/Assets/ss32.pdn
similarity index 100%
rename from shadowsocks-csharp/Resources/ss32.pdn
rename to Shadowsocks.WPF/Assets/ss32.pdn
diff --git a/shadowsocks-csharp/Resources/ss32Fill.png b/Shadowsocks.WPF/Assets/ss32Fill.png
similarity index 100%
rename from shadowsocks-csharp/Resources/ss32Fill.png
rename to Shadowsocks.WPF/Assets/ss32Fill.png
diff --git a/shadowsocks-csharp/Resources/ss32In.png b/Shadowsocks.WPF/Assets/ss32In.png
similarity index 100%
rename from shadowsocks-csharp/Resources/ss32In.png
rename to Shadowsocks.WPF/Assets/ss32In.png
diff --git a/shadowsocks-csharp/Resources/ss32Out.png b/Shadowsocks.WPF/Assets/ss32Out.png
similarity index 100%
rename from shadowsocks-csharp/Resources/ss32Out.png
rename to Shadowsocks.WPF/Assets/ss32Out.png
diff --git a/shadowsocks-csharp/Resources/ss32Outline.png b/Shadowsocks.WPF/Assets/ss32Outline.png
similarity index 100%
rename from shadowsocks-csharp/Resources/ss32Outline.png
rename to Shadowsocks.WPF/Assets/ss32Outline.png
diff --git a/shadowsocks-csharp/Resources/ssw128.png b/Shadowsocks.WPF/Assets/ssw128.png
old mode 100755
new mode 100644
similarity index 100%
rename from shadowsocks-csharp/Resources/ssw128.png
rename to Shadowsocks.WPF/Assets/ssw128.png
diff --git a/Shadowsocks.WPF/FodyWeavers.xml b/Shadowsocks.WPF/FodyWeavers.xml
new file mode 100644
index 00000000..63fc1484
--- /dev/null
+++ b/Shadowsocks.WPF/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/Shadowsocks.WPF/FodyWeavers.xsd b/Shadowsocks.WPF/FodyWeavers.xsd
new file mode 100644
index 00000000..f3ac4762
--- /dev/null
+++ b/Shadowsocks.WPF/FodyWeavers.xsd
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/Shadowsocks.WPF/Localization/LocalizationProvider.cs b/Shadowsocks.WPF/Localization/LocalizationProvider.cs
new file mode 100644
index 00000000..a2625b92
--- /dev/null
+++ b/Shadowsocks.WPF/Localization/LocalizationProvider.cs
@@ -0,0 +1,15 @@
+using Shadowsocks.Common.Model;
+
+using System.Reflection;
+
+using WPFLocalizeExtension.Extensions;
+
+namespace Shadowsocks.WPF.Localization
+{
+ public class LocalizationProvider : ILocalizationProvider
+ {
+ private static readonly string CallingAssemblyName = Assembly.GetCallingAssembly().GetName().Name;
+
+ public T GetLocalizedValue(string key) => LocExtension.GetLocalizedValue($"{CallingAssemblyName}:Strings:{key}");
+ }
+}
diff --git a/shadowsocks-csharp/Localization/Strings.Designer.cs b/Shadowsocks.WPF/Localization/Strings.Designer.cs
similarity index 99%
rename from shadowsocks-csharp/Localization/Strings.Designer.cs
rename to Shadowsocks.WPF/Localization/Strings.Designer.cs
index 6aa6738a..3d7f18ef 100644
--- a/shadowsocks-csharp/Localization/Strings.Designer.cs
+++ b/Shadowsocks.WPF/Localization/Strings.Designer.cs
@@ -8,7 +8,7 @@
//
//------------------------------------------------------------------------------
-namespace Shadowsocks.Localization {
+namespace Shadowsocks.WPF.Localization {
using System;
diff --git a/shadowsocks-csharp/Localization/Strings.fr.resx b/Shadowsocks.WPF/Localization/Strings.fr.resx
similarity index 100%
rename from shadowsocks-csharp/Localization/Strings.fr.resx
rename to Shadowsocks.WPF/Localization/Strings.fr.resx
diff --git a/shadowsocks-csharp/Localization/Strings.ja.resx b/Shadowsocks.WPF/Localization/Strings.ja.resx
similarity index 100%
rename from shadowsocks-csharp/Localization/Strings.ja.resx
rename to Shadowsocks.WPF/Localization/Strings.ja.resx
diff --git a/shadowsocks-csharp/Localization/Strings.ko.resx b/Shadowsocks.WPF/Localization/Strings.ko.resx
similarity index 100%
rename from shadowsocks-csharp/Localization/Strings.ko.resx
rename to Shadowsocks.WPF/Localization/Strings.ko.resx
diff --git a/shadowsocks-csharp/Localization/Strings.resx b/Shadowsocks.WPF/Localization/Strings.resx
similarity index 100%
rename from shadowsocks-csharp/Localization/Strings.resx
rename to Shadowsocks.WPF/Localization/Strings.resx
diff --git a/shadowsocks-csharp/Localization/Strings.ru.resx b/Shadowsocks.WPF/Localization/Strings.ru.resx
similarity index 100%
rename from shadowsocks-csharp/Localization/Strings.ru.resx
rename to Shadowsocks.WPF/Localization/Strings.ru.resx
diff --git a/shadowsocks-csharp/Localization/Strings.zh-Hans.resx b/Shadowsocks.WPF/Localization/Strings.zh-Hans.resx
similarity index 100%
rename from shadowsocks-csharp/Localization/Strings.zh-Hans.resx
rename to Shadowsocks.WPF/Localization/Strings.zh-Hans.resx
diff --git a/shadowsocks-csharp/Localization/Strings.zh-Hant.resx b/Shadowsocks.WPF/Localization/Strings.zh-Hant.resx
similarity index 97%
rename from shadowsocks-csharp/Localization/Strings.zh-Hant.resx
rename to Shadowsocks.WPF/Localization/Strings.zh-Hant.resx
index 668c2b25..e52f7e3f 100644
--- a/shadowsocks-csharp/Localization/Strings.zh-Hant.resx
+++ b/Shadowsocks.WPF/Localization/Strings.zh-Hant.resx
@@ -1,189 +1,189 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- 密碼
-
-
- Port
-
-
- 類型
-
-
- 位址
-
-
- 逾時 (秒)
-
-
- 取消
-
-
- 確定
-
-
- 切換系統 Proxy 狀態
-
-
- 切換系統 Proxy 模式
-
-
- 切換區域網路共用
-
-
- 顯示記錄檔
-
-
- 切換上一個伺服器
-
-
- 切換下一個伺服器
-
-
- 註冊所有快速鍵
-
-
- 啟動時註冊快速鍵
-
-
- 更新
-
-
- 全部更新
-
-
- 新增
-
-
- 跳過版本
-
-
- 暫不更新
-
-
- 發現可用更新
-
-
- 複製鏈接
-
-
- 複製
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 密碼
+
+
+ Port
+
+
+ 類型
+
+
+ 位址
+
+
+ 逾時 (秒)
+
+
+ 取消
+
+
+ 確定
+
+
+ 切換系統 Proxy 狀態
+
+
+ 切換系統 Proxy 模式
+
+
+ 切換區域網路共用
+
+
+ 顯示記錄檔
+
+
+ 切換上一個伺服器
+
+
+ 切換下一個伺服器
+
+
+ 註冊所有快速鍵
+
+
+ 啟動時註冊快速鍵
+
+
+ 更新
+
+
+ 全部更新
+
+
+ 新增
+
+
+ 跳過版本
+
+
+ 暫不更新
+
+
+ 發現可用更新
+
+
+ 複製鏈接
+
+
+ 複製
+
\ No newline at end of file
diff --git a/shadowsocks-csharp/Properties/PublishProfiles/FolderProfile.pubxml b/Shadowsocks.WPF/Properties/PublishProfiles/FolderProfile.pubxml
similarity index 72%
rename from shadowsocks-csharp/Properties/PublishProfiles/FolderProfile.pubxml
rename to Shadowsocks.WPF/Properties/PublishProfiles/FolderProfile.pubxml
index 94842414..da619e3e 100644
--- a/shadowsocks-csharp/Properties/PublishProfiles/FolderProfile.pubxml
+++ b/Shadowsocks.WPF/Properties/PublishProfiles/FolderProfile.pubxml
@@ -1,12 +1,14 @@
-
+
-
-
- Release
- Any CPU
- bin\Release\netcoreapp3.1\publish\
- FileSystem
-
+-->
+
+
+ Debug
+ Any CPU
+ bin\Release\netcoreapp3.1\publish\
+ FileSystem
+ netcoreapp3.1
+ false
+
\ No newline at end of file
diff --git a/shadowsocks-csharp/Properties/PublishProfiles/win-x64.pubxml.user b/Shadowsocks.WPF/Properties/PublishProfiles/FolderProfile.pubxml.user
similarity index 100%
rename from shadowsocks-csharp/Properties/PublishProfiles/win-x64.pubxml.user
rename to Shadowsocks.WPF/Properties/PublishProfiles/FolderProfile.pubxml.user
diff --git a/shadowsocks-csharp/Properties/PublishProfiles/win-x64.pubxml b/Shadowsocks.WPF/Properties/PublishProfiles/win-x64.pubxml
similarity index 88%
rename from shadowsocks-csharp/Properties/PublishProfiles/win-x64.pubxml
rename to Shadowsocks.WPF/Properties/PublishProfiles/win-x64.pubxml
index 8975df70..0cb60638 100644
--- a/shadowsocks-csharp/Properties/PublishProfiles/win-x64.pubxml
+++ b/Shadowsocks.WPF/Properties/PublishProfiles/win-x64.pubxml
@@ -6,7 +6,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
Release
Any CPU
- bin\Release\netcoreapp3.1\win-x64\publish\
+ bin\Release\netcoreapp3.1\publish\
FileSystem
netcoreapp3.1
win-x64
diff --git a/shadowsocks-csharp/Properties/PublishProfiles/win-x86.pubxml.user b/Shadowsocks.WPF/Properties/PublishProfiles/win-x64.pubxml.user
similarity index 100%
rename from shadowsocks-csharp/Properties/PublishProfiles/win-x86.pubxml.user
rename to Shadowsocks.WPF/Properties/PublishProfiles/win-x64.pubxml.user
diff --git a/shadowsocks-csharp/Properties/PublishProfiles/win-x86.pubxml b/Shadowsocks.WPF/Properties/PublishProfiles/win-x86.pubxml
similarity index 88%
rename from shadowsocks-csharp/Properties/PublishProfiles/win-x86.pubxml
rename to Shadowsocks.WPF/Properties/PublishProfiles/win-x86.pubxml
index b25dd23a..4e6685ac 100644
--- a/shadowsocks-csharp/Properties/PublishProfiles/win-x86.pubxml
+++ b/Shadowsocks.WPF/Properties/PublishProfiles/win-x86.pubxml
@@ -6,7 +6,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
Release
Any CPU
- bin\Release\netcoreapp3.1\win-x86\publish\
+ bin\Release\netcoreapp3.1\publish\
FileSystem
netcoreapp3.1
win-x86
diff --git a/shadowsocks-csharp/Properties/PublishProfiles/FolderProfile.pubxml.user b/Shadowsocks.WPF/Properties/PublishProfiles/win-x86.pubxml.user
similarity index 93%
rename from shadowsocks-csharp/Properties/PublishProfiles/FolderProfile.pubxml.user
rename to Shadowsocks.WPF/Properties/PublishProfiles/win-x86.pubxml.user
index ede65b29..312c6e3b 100644
--- a/shadowsocks-csharp/Properties/PublishProfiles/FolderProfile.pubxml.user
+++ b/Shadowsocks.WPF/Properties/PublishProfiles/win-x86.pubxml.user
@@ -1,6 +1,6 @@
-
+
-
-
+-->
+
+
\ No newline at end of file
diff --git a/Shadowsocks.WPF/Resource.Designer.cs b/Shadowsocks.WPF/Resource.Designer.cs
new file mode 100644
index 00000000..b599894d
--- /dev/null
+++ b/Shadowsocks.WPF/Resource.Designer.cs
@@ -0,0 +1,133 @@
+//------------------------------------------------------------------------------
+//
+// 此代码由工具生成。
+// 运行时版本:4.0.30319.42000
+//
+// 对此文件的更改可能会导致不正确的行为,并且如果
+// 重新生成代码,这些更改将会丢失。
+//
+//------------------------------------------------------------------------------
+
+namespace Shadowsocks.WPF {
+ using System;
+
+
+ ///
+ /// 一个强类型的资源类,用于查找本地化的字符串等。
+ ///
+ // 此类是由 StronglyTypedResourceBuilder
+ // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+ // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+ // (以 /str 作为命令选项),或重新生成 VS 项目。
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resource {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resource() {
+ }
+
+ ///
+ /// 返回此类使用的缓存的 ResourceManager 实例。
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Shadowsocks.WPF.Resource", typeof(Resource).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// 重写当前线程的 CurrentUICulture 属性
+ /// 重写当前线程的 CurrentUICulture 属性。
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// 查找 System.Byte[] 类型的本地化资源。
+ ///
+ internal static byte[] ss128 {
+ get {
+ object obj = ResourceManager.GetObject("ss128", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// 查找 System.Byte[] 类型的本地化资源。
+ ///
+ internal static byte[] ss32 {
+ get {
+ object obj = ResourceManager.GetObject("ss32", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap ss32Fill {
+ get {
+ object obj = ResourceManager.GetObject("ss32Fill", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap ss32In {
+ get {
+ object obj = ResourceManager.GetObject("ss32In", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap ss32Out {
+ get {
+ object obj = ResourceManager.GetObject("ss32Out", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap ss32Outline {
+ get {
+ object obj = ResourceManager.GetObject("ss32Outline", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap ssw128 {
+ get {
+ object obj = ResourceManager.GetObject("ssw128", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+ }
+}
diff --git a/shadowsocks-csharp/Properties/Resources.resx b/Shadowsocks.WPF/Resource.resx
old mode 100755
new mode 100644
similarity index 72%
rename from shadowsocks-csharp/Properties/Resources.resx
rename to Shadowsocks.WPF/Resource.resx
index 3a394f0e..136f7759
--- a/shadowsocks-csharp/Properties/Resources.resx
+++ b/Shadowsocks.WPF/Resource.resx
@@ -1,157 +1,142 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
-
- ..\Data\abp.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312
-
-
- ..\data\dlc.dat;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- ..\Data\i18n.csv;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
-
-
- ..\data\nlog.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
-
-
- ..\data\privoxy_conf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
-
-
- ..\data\privoxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- ..\Resources\ss32Fill.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
-
-
- ..\Resources\ss32In.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
-
-
- ..\Resources\ss32Out.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
-
-
- ..\Resources\ss32Outline.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
-
-
- ..\Resources\ssw128.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
-
-
- ..\data\user-rule.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ assets\ss128.pdn;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ assets\ss32.pdn;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ assets\ss32fill.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ assets\ss32in.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ assets\ss32out.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ assets\ss32outline.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ assets\ssw128.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
\ No newline at end of file
diff --git a/Shadowsocks.WPF/Shadowsocks.WPF.csproj b/Shadowsocks.WPF/Shadowsocks.WPF.csproj
new file mode 100644
index 00000000..c8dcf349
--- /dev/null
+++ b/Shadowsocks.WPF/Shadowsocks.WPF.csproj
@@ -0,0 +1,70 @@
+
+
+
+ Exe
+ netcoreapp3.1
+ true
+ clowwindy & community 2020
+ Shadowsocks.WPF
+ Shadowsocks WPF GUI
+ 5.0.0.0
+ 1.0.0
+ Assets\shadowsocks.ico
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Strings.resx
+
+
+ True
+ True
+ Resource.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Strings.Designer.cs
+
+
+ ResXFileCodeGenerator
+ Resource.Designer.cs
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Shadowsocks.WPF/Shadowsocks.WPF.csproj.user b/Shadowsocks.WPF/Shadowsocks.WPF.csproj.user
new file mode 100644
index 00000000..93cbea4a
--- /dev/null
+++ b/Shadowsocks.WPF/Shadowsocks.WPF.csproj.user
@@ -0,0 +1,21 @@
+
+
+
+ <_LastSelectedProfileId>D:\workspaces\repos\shadowsocks-windows\Shadowsocks.WPF\Properties\PublishProfiles\win-x64.pubxml
+
+
+
+ Designer
+
+
+
+
+ Code
+
+
+
+
+ Designer
+
+
+
\ No newline at end of file
diff --git a/shadowsocks-csharp/ViewModels/ForwardProxyViewModel.cs b/Shadowsocks.WPF/ViewModels/ForwardProxyViewModel.cs
similarity index 99%
rename from shadowsocks-csharp/ViewModels/ForwardProxyViewModel.cs
rename to Shadowsocks.WPF/ViewModels/ForwardProxyViewModel.cs
index f901cac8..607a2e71 100644
--- a/shadowsocks-csharp/ViewModels/ForwardProxyViewModel.cs
+++ b/Shadowsocks.WPF/ViewModels/ForwardProxyViewModel.cs
@@ -9,7 +9,7 @@ using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
-namespace Shadowsocks.ViewModels
+namespace Shadowsocks.WPF.ViewModels
{
public class ForwardProxyViewModel : ReactiveValidationObject
{
diff --git a/shadowsocks-csharp/ViewModels/HotkeysViewModel.cs b/Shadowsocks.WPF/ViewModels/HotkeysViewModel.cs
similarity index 99%
rename from shadowsocks-csharp/ViewModels/HotkeysViewModel.cs
rename to Shadowsocks.WPF/ViewModels/HotkeysViewModel.cs
index 141d2cf9..fd10fb9b 100644
--- a/shadowsocks-csharp/ViewModels/HotkeysViewModel.cs
+++ b/Shadowsocks.WPF/ViewModels/HotkeysViewModel.cs
@@ -7,7 +7,7 @@ using System.Reactive;
using System.Text;
using System.Windows.Input;
-namespace Shadowsocks.ViewModels
+namespace Shadowsocks.WPF.ViewModels
{
public class HotkeysViewModel : ReactiveObject
{
diff --git a/Shadowsocks.WPF/ViewModels/MainWindowViewModel.cs b/Shadowsocks.WPF/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 00000000..042a5d34
--- /dev/null
+++ b/Shadowsocks.WPF/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,14 @@
+using ReactiveUI;
+using Shadowsocks.Controller.Service;
+
+using System;
+
+namespace Shadowsocks.WPF.ViewModels
+{
+ public class MainWindowViewModel : ReactiveObject
+ {
+ public MainWindowViewModel()
+ {
+ }
+ }
+}
diff --git a/shadowsocks-csharp/ViewModels/OnlineConfigViewModel.cs b/Shadowsocks.WPF/ViewModels/OnlineConfigViewModel.cs
similarity index 99%
rename from shadowsocks-csharp/ViewModels/OnlineConfigViewModel.cs
rename to Shadowsocks.WPF/ViewModels/OnlineConfigViewModel.cs
index f803b351..2c4d1b5d 100644
--- a/shadowsocks-csharp/ViewModels/OnlineConfigViewModel.cs
+++ b/Shadowsocks.WPF/ViewModels/OnlineConfigViewModel.cs
@@ -15,7 +15,7 @@ using System.Reactive.Linq;
using System.Text;
using System.Windows;
-namespace Shadowsocks.ViewModels
+namespace Shadowsocks.WPF.ViewModels
{
public class OnlineConfigViewModel : ReactiveValidationObject
{
diff --git a/shadowsocks-csharp/ViewModels/ServerSharingViewModel.cs b/Shadowsocks.WPF/ViewModels/ServerSharingViewModel.cs
similarity index 98%
rename from shadowsocks-csharp/ViewModels/ServerSharingViewModel.cs
rename to Shadowsocks.WPF/ViewModels/ServerSharingViewModel.cs
index f1375690..9ddfbd84 100644
--- a/shadowsocks-csharp/ViewModels/ServerSharingViewModel.cs
+++ b/Shadowsocks.WPF/ViewModels/ServerSharingViewModel.cs
@@ -10,7 +10,7 @@ using System.Reactive;
using System.Windows;
using System.Windows.Media.Imaging;
-namespace Shadowsocks.ViewModels
+namespace Shadowsocks.WPF.ViewModels
{
public class ServerSharingViewModel : ReactiveObject
{
diff --git a/shadowsocks-csharp/ViewModels/VersionUpdatePromptViewModel.cs b/Shadowsocks.WPF/ViewModels/VersionUpdatePromptViewModel.cs
similarity index 97%
rename from shadowsocks-csharp/ViewModels/VersionUpdatePromptViewModel.cs
rename to Shadowsocks.WPF/ViewModels/VersionUpdatePromptViewModel.cs
index 0ae9f2d1..ab90d196 100644
--- a/shadowsocks-csharp/ViewModels/VersionUpdatePromptViewModel.cs
+++ b/Shadowsocks.WPF/ViewModels/VersionUpdatePromptViewModel.cs
@@ -3,7 +3,7 @@ using ReactiveUI;
using Shadowsocks.Controller;
using System.Reactive;
-namespace Shadowsocks.ViewModels
+namespace Shadowsocks.WPF.ViewModels
{
public class VersionUpdatePromptViewModel : ReactiveObject
{
diff --git a/shadowsocks-csharp/Views/ForwardProxyView.xaml b/Shadowsocks.WPF/Views/ForwardProxyView.xaml
similarity index 96%
rename from shadowsocks-csharp/Views/ForwardProxyView.xaml
rename to Shadowsocks.WPF/Views/ForwardProxyView.xaml
index 0c6a9319..c0754de0 100644
--- a/shadowsocks-csharp/Views/ForwardProxyView.xaml
+++ b/Shadowsocks.WPF/Views/ForwardProxyView.xaml
@@ -1,12 +1,12 @@
/// Interaction logic for ForwardProxyView.xaml
diff --git a/shadowsocks-csharp/Views/HotkeysView.xaml b/Shadowsocks.WPF/Views/HotkeysView.xaml
similarity index 96%
rename from shadowsocks-csharp/Views/HotkeysView.xaml
rename to Shadowsocks.WPF/Views/HotkeysView.xaml
index 57527b2c..a01d5f83 100644
--- a/shadowsocks-csharp/Views/HotkeysView.xaml
+++ b/Shadowsocks.WPF/Views/HotkeysView.xaml
@@ -1,12 +1,12 @@
/// Interaction logic for HotkeysView.xaml
diff --git a/Shadowsocks.WPF/Views/MainWindow.xaml b/Shadowsocks.WPF/Views/MainWindow.xaml
new file mode 100644
index 00000000..110aa42a
--- /dev/null
+++ b/Shadowsocks.WPF/Views/MainWindow.xaml
@@ -0,0 +1,21 @@
+
+
+
diff --git a/Shadowsocks.WPF/Views/MainWindow.xaml.cs b/Shadowsocks.WPF/Views/MainWindow.xaml.cs
new file mode 100644
index 00000000..2bf713d8
--- /dev/null
+++ b/Shadowsocks.WPF/Views/MainWindow.xaml.cs
@@ -0,0 +1,17 @@
+using ReactiveUI;
+
+using Shadowsocks.WPF.ViewModels;
+
+namespace Shadowsocks.WPF.Views
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : ReactiveWindow
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/shadowsocks-csharp/Views/OnlineConfigView.xaml b/Shadowsocks.WPF/Views/OnlineConfigView.xaml
similarity index 93%
rename from shadowsocks-csharp/Views/OnlineConfigView.xaml
rename to Shadowsocks.WPF/Views/OnlineConfigView.xaml
index c024c036..6ba7ec58 100644
--- a/shadowsocks-csharp/Views/OnlineConfigView.xaml
+++ b/Shadowsocks.WPF/Views/OnlineConfigView.xaml
@@ -1,12 +1,12 @@
/// Interaction logic for OnlineConfigView.xaml
diff --git a/shadowsocks-csharp/Views/ServerSharingView.xaml b/Shadowsocks.WPF/Views/ServerSharingView.xaml
similarity index 92%
rename from shadowsocks-csharp/Views/ServerSharingView.xaml
rename to Shadowsocks.WPF/Views/ServerSharingView.xaml
index b037c08d..a812a4c8 100644
--- a/shadowsocks-csharp/Views/ServerSharingView.xaml
+++ b/Shadowsocks.WPF/Views/ServerSharingView.xaml
@@ -1,12 +1,12 @@
/// Interaction logic for ServerSharingView.xaml
diff --git a/shadowsocks-csharp/Views/VersionUpdatePromptView.xaml b/Shadowsocks.WPF/Views/VersionUpdatePromptView.xaml
similarity index 92%
rename from shadowsocks-csharp/Views/VersionUpdatePromptView.xaml
rename to Shadowsocks.WPF/Views/VersionUpdatePromptView.xaml
index cfd75b02..dabcc5d5 100644
--- a/shadowsocks-csharp/Views/VersionUpdatePromptView.xaml
+++ b/Shadowsocks.WPF/Views/VersionUpdatePromptView.xaml
@@ -1,12 +1,12 @@
/// Interaction logic for VersionUpdatePromptView.xaml
diff --git a/Shadowsocks/Assets/NLog.config b/Shadowsocks/Assets/NLog.config
new file mode 100644
index 00000000..d955c871
--- /dev/null
+++ b/Shadowsocks/Assets/NLog.config
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shadowsocks-csharp/Data/abp.js b/Shadowsocks/Assets/abp.js
similarity index 96%
rename from shadowsocks-csharp/Data/abp.js
rename to Shadowsocks/Assets/abp.js
index 1d5bcdcf..243607bd 100644
--- a/shadowsocks-csharp/Data/abp.js
+++ b/Shadowsocks/Assets/abp.js
@@ -1,810 +1,810 @@
-/* eslint-disable */
-// Was generated by gfwlist2pac in precise mode
-// https://github.com/clowwindy/gfwlist2pac
-
-// 2019-10-06: More 'javascript' way to interaction with main program
-// 2019-02-08: Updated to support shadowsocks-windows user rules.
-
-var proxy = __PROXY__;
-var userrules = [];
-var rules = [];
-
-// convert to abp grammar
-for (var i = 0; i < __RULES__.length; i++) {
- var s = __RULES__[i];
- if (s.substring(0, 2) == "||") s += "^";
- rules.push(s);
-}
-
-for (var i = 0; i < __USERRULES__.length; i++) {
- var s = __USERRULES__[i];
- if (s.substring(0, 2) == "||") s += "^";
- userrules.push(s);
-}
-
-/*
-* This file is part of Adblock Plus ,
-* Copyright (C) 2006-2014 Eyeo GmbH
-*
-* Adblock Plus is free software: you can redistribute it and/or modify
-* it under the terms of the GNU General Public License version 3 as
-* published by the Free Software Foundation.
-*
-* Adblock Plus 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 General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with Adblock Plus. If not, see .
-*/
-
-function createDict()
-{
- var result = {};
- result.__proto__ = null;
- return result;
-}
-
-function getOwnPropertyDescriptor(obj, key)
-{
- if (obj.hasOwnProperty(key))
- {
- return obj[key];
- }
- return null;
-}
-
-function extend(subclass, superclass, definition)
-{
- if (Object.__proto__)
- {
- definition.__proto__ = superclass.prototype;
- subclass.prototype = definition;
- }
- else
- {
- var tmpclass = function(){}, ret;
- tmpclass.prototype = superclass.prototype;
- subclass.prototype = new tmpclass();
- subclass.prototype.constructor = superclass;
- for (var i in definition)
- {
- if (definition.hasOwnProperty(i))
- {
- subclass.prototype[i] = definition[i];
- }
- }
- }
-}
-
-function Filter(text)
-{
- this.text = text;
- this.subscriptions = [];
-}
-Filter.prototype = {
- text: null,
- subscriptions: null,
- toString: function()
- {
- return this.text;
- }
-};
-Filter.knownFilters = createDict();
-Filter.elemhideRegExp = /^([^\/\*\|\@"!]*?)#(\@)?(?:([\w\-]+|\*)((?:\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\))*)|#([^{}]+))$/;
-Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)?$/;
-Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/;
-Filter.fromText = function(text)
-{
- if (text in Filter.knownFilters)
- {
- return Filter.knownFilters[text];
- }
- var ret;
- if (text.charAt(0) == "!")
- {
- ret = new CommentFilter(text);
- }
- else
- {
- ret = RegExpFilter.fromText(text);
- }
- Filter.knownFilters[ret.text] = ret;
- return ret;
-};
-
-function InvalidFilter(text, reason)
-{
- Filter.call(this, text);
- this.reason = reason;
-}
-extend(InvalidFilter, Filter, {
- reason: null
-});
-
-function CommentFilter(text)
-{
- Filter.call(this, text);
-}
-extend(CommentFilter, Filter, {
-});
-
-function ActiveFilter(text, domains)
-{
- Filter.call(this, text);
- this.domainSource = domains;
-}
-extend(ActiveFilter, Filter, {
- domainSource: null,
- domainSeparator: null,
- ignoreTrailingDot: true,
- domainSourceIsUpperCase: false,
- getDomains: function()
- {
- var prop = getOwnPropertyDescriptor(this, "domains");
- if (prop)
- {
- return prop;
- }
- var domains = null;
- if (this.domainSource)
- {
- var source = this.domainSource;
- if (!this.domainSourceIsUpperCase)
- {
- source = source.toUpperCase();
- }
- var list = source.split(this.domainSeparator);
- if (list.length == 1 && (list[0]).charAt(0) != "~")
- {
- domains = createDict();
- domains[""] = false;
- if (this.ignoreTrailingDot)
- {
- list[0] = list[0].replace(/\.+$/, "");
- }
- domains[list[0]] = true;
- }
- else
- {
- var hasIncludes = false;
- for (var i = 0; i < list.length; i++)
- {
- var domain = list[i];
- if (this.ignoreTrailingDot)
- {
- domain = domain.replace(/\.+$/, "");
- }
- if (domain == "")
- {
- continue;
- }
- var include;
- if (domain.charAt(0) == "~")
- {
- include = false;
- domain = domain.substr(1);
- }
- else
- {
- include = true;
- hasIncludes = true;
- }
- if (!domains)
- {
- domains = createDict();
- }
- domains[domain] = include;
- }
- domains[""] = !hasIncludes;
- }
- this.domainSource = null;
- }
- return this.domains;
- },
- sitekeys: null,
- isActiveOnDomain: function(docDomain, sitekey)
- {
- if (this.getSitekeys() && (!sitekey || this.getSitekeys().indexOf(sitekey.toUpperCase()) < 0))
- {
- return false;
- }
- if (!this.getDomains())
- {
- return true;
- }
- if (!docDomain)
- {
- return this.getDomains()[""];
- }
- if (this.ignoreTrailingDot)
- {
- docDomain = docDomain.replace(/\.+$/, "");
- }
- docDomain = docDomain.toUpperCase();
- while (true)
- {
- if (docDomain in this.getDomains())
- {
- return this.domains[docDomain];
- }
- var nextDot = docDomain.indexOf(".");
- if (nextDot < 0)
- {
- break;
- }
- docDomain = docDomain.substr(nextDot + 1);
- }
- return this.domains[""];
- },
- isActiveOnlyOnDomain: function(docDomain)
- {
- if (!docDomain || !this.getDomains() || this.getDomains()[""])
- {
- return false;
- }
- if (this.ignoreTrailingDot)
- {
- docDomain = docDomain.replace(/\.+$/, "");
- }
- docDomain = docDomain.toUpperCase();
- for (var domain in this.getDomains())
- {
- if (this.domains[domain] && domain != docDomain && (domain.length <= docDomain.length || domain.indexOf("." + docDomain) != domain.length - docDomain.length - 1))
- {
- return false;
- }
- }
- return true;
- }
-});
-
-function RegExpFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys)
-{
- ActiveFilter.call(this, text, domains, sitekeys);
- if (contentType != null)
- {
- this.contentType = contentType;
- }
- if (matchCase)
- {
- this.matchCase = matchCase;
- }
- if (thirdParty != null)
- {
- this.thirdParty = thirdParty;
- }
- if (sitekeys != null)
- {
- this.sitekeySource = sitekeys;
- }
- if (regexpSource.length >= 2 && regexpSource.charAt(0) == "/" && regexpSource.charAt(regexpSource.length - 1) == "/")
- {
- var regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), this.matchCase ? "" : "i");
- this.regexp = regexp;
- }
- else
- {
- this.regexpSource = regexpSource;
- }
-}
-extend(RegExpFilter, ActiveFilter, {
- domainSourceIsUpperCase: true,
- length: 1,
- domainSeparator: "|",
- regexpSource: null,
- getRegexp: function()
- {
- var prop = getOwnPropertyDescriptor(this, "regexp");
- if (prop)
- {
- return prop;
- }
- var source = this.regexpSource.replace(/\*+/g, "*").replace(/\^\|$/, "^").replace(/\W/g, "\\$&").replace(/\\\*/g, ".*").replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x7F]|$)").replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?").replace(/^\\\|/, "^").replace(/\\\|$/, "$").replace(/^(\.\*)/, "").replace(/(\.\*)$/, "");
- var regexp = new RegExp(source, this.matchCase ? "" : "i");
- this.regexp = regexp;
- return regexp;
- },
- contentType: 2147483647,
- matchCase: false,
- thirdParty: null,
- sitekeySource: null,
- getSitekeys: function()
- {
- var prop = getOwnPropertyDescriptor(this, "sitekeys");
- if (prop)
- {
- return prop;
- }
- var sitekeys = null;
- if (this.sitekeySource)
- {
- sitekeys = this.sitekeySource.split("|");
- this.sitekeySource = null;
- }
- this.sitekeys = sitekeys;
- return this.sitekeys;
- },
- matches: function(location, contentType, docDomain, thirdParty, sitekey)
- {
- if (this.getRegexp().test(location) && this.isActiveOnDomain(docDomain, sitekey))
- {
- return true;
- }
- return false;
- }
-});
-RegExpFilter.prototype["0"] = "#this";
-RegExpFilter.fromText = function(text)
-{
- var blocking = true;
- var origText = text;
- if (text.indexOf("@@") == 0)
- {
- blocking = false;
- text = text.substr(2);
- }
- var contentType = null;
- var matchCase = null;
- var domains = null;
- var sitekeys = null;
- var thirdParty = null;
- var collapse = null;
- var options;
- var match = text.indexOf("$") >= 0 ? Filter.optionsRegExp.exec(text) : null;
- if (match)
- {
- options = match[1].toUpperCase().split(",");
- text = match.input.substr(0, match.index);
- for (var _loopIndex6 = 0; _loopIndex6 < options.length; ++_loopIndex6)
- {
- var option = options[_loopIndex6];
- var value = null;
- var separatorIndex = option.indexOf("=");
- if (separatorIndex >= 0)
- {
- value = option.substr(separatorIndex + 1);
- option = option.substr(0, separatorIndex);
- }
- option = option.replace(/-/, "_");
- if (option in RegExpFilter.typeMap)
- {
- if (contentType == null)
- {
- contentType = 0;
- }
- contentType |= RegExpFilter.typeMap[option];
- }
- else if (option.charAt(0) == "~" && option.substr(1) in RegExpFilter.typeMap)
- {
- if (contentType == null)
- {
- contentType = RegExpFilter.prototype.contentType;
- }
- contentType &= ~RegExpFilter.typeMap[option.substr(1)];
- }
- else if (option == "MATCH_CASE")
- {
- matchCase = true;
- }
- else if (option == "~MATCH_CASE")
- {
- matchCase = false;
- }
- else if (option == "DOMAIN" && typeof value != "undefined")
- {
- domains = value;
- }
- else if (option == "THIRD_PARTY")
- {
- thirdParty = true;
- }
- else if (option == "~THIRD_PARTY")
- {
- thirdParty = false;
- }
- else if (option == "COLLAPSE")
- {
- collapse = true;
- }
- else if (option == "~COLLAPSE")
- {
- collapse = false;
- }
- else if (option == "SITEKEY" && typeof value != "undefined")
- {
- sitekeys = value;
- }
- else
- {
- return new InvalidFilter(origText, "Unknown option " + option.toLowerCase());
- }
- }
- }
- if (!blocking && (contentType == null || contentType & RegExpFilter.typeMap.DOCUMENT) && (!options || options.indexOf("DOCUMENT") < 0) && !/^\|?[\w\-]+:/.test(text))
- {
- if (contentType == null)
- {
- contentType = RegExpFilter.prototype.contentType;
- }
- contentType &= ~RegExpFilter.typeMap.DOCUMENT;
- }
- try
- {
- if (blocking)
- {
- return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys, collapse);
- }
- else
- {
- return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys);
- }
- }
- catch (e)
- {
- return new InvalidFilter(origText, e);
- }
-};
-RegExpFilter.typeMap = {
- OTHER: 1,
- SCRIPT: 2,
- IMAGE: 4,
- STYLESHEET: 8,
- OBJECT: 16,
- SUBDOCUMENT: 32,
- DOCUMENT: 64,
- XBL: 1,
- PING: 1,
- XMLHTTPREQUEST: 2048,
- OBJECT_SUBREQUEST: 4096,
- DTD: 1,
- MEDIA: 16384,
- FONT: 32768,
- BACKGROUND: 4,
- POPUP: 268435456,
- ELEMHIDE: 1073741824
-};
-RegExpFilter.prototype.contentType &= ~ (RegExpFilter.typeMap.ELEMHIDE | RegExpFilter.typeMap.POPUP);
-
-function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys, collapse)
-{
- RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys);
- this.collapse = collapse;
-}
-extend(BlockingFilter, RegExpFilter, {
- collapse: null
-});
-
-function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys)
-{
- RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys);
-}
-extend(WhitelistFilter, RegExpFilter, {
-});
-
-function Matcher()
-{
- this.clear();
-}
-Matcher.prototype = {
- filterByKeyword: null,
- keywordByFilter: null,
- clear: function()
- {
- this.filterByKeyword = createDict();
- this.keywordByFilter = createDict();
- },
- add: function(filter)
- {
- if (filter.text in this.keywordByFilter)
- {
- return;
- }
- var keyword = this.findKeyword(filter);
- var oldEntry = this.filterByKeyword[keyword];
- if (typeof oldEntry == "undefined")
- {
- this.filterByKeyword[keyword] = filter;
- }
- else if (oldEntry.length == 1)
- {
- this.filterByKeyword[keyword] = [oldEntry, filter];
- }
- else
- {
- oldEntry.push(filter);
- }
- this.keywordByFilter[filter.text] = keyword;
- },
- remove: function(filter)
- {
- if (!(filter.text in this.keywordByFilter))
- {
- return;
- }
- var keyword = this.keywordByFilter[filter.text];
- var list = this.filterByKeyword[keyword];
- if (list.length <= 1)
- {
- delete this.filterByKeyword[keyword];
- }
- else
- {
- var index = list.indexOf(filter);
- if (index >= 0)
- {
- list.splice(index, 1);
- if (list.length == 1)
- {
- this.filterByKeyword[keyword] = list[0];
- }
- }
- }
- delete this.keywordByFilter[filter.text];
- },
- findKeyword: function(filter)
- {
- var result = "";
- var text = filter.text;
- if (Filter.regexpRegExp.test(text))
- {
- return result;
- }
- var match = Filter.optionsRegExp.exec(text);
- if (match)
- {
- text = match.input.substr(0, match.index);
- }
- if (text.substr(0, 2) == "@@")
- {
- text = text.substr(2);
- }
- var candidates = text.toLowerCase().match(/[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g);
- if (!candidates)
- {
- return result;
- }
- var hash = this.filterByKeyword;
- var resultCount = 16777215;
- var resultLength = 0;
- for (var i = 0, l = candidates.length; i < l; i++)
- {
- var candidate = candidates[i].substr(1);
- var count = candidate in hash ? hash[candidate].length : 0;
- if (count < resultCount || count == resultCount && candidate.length > resultLength)
- {
- result = candidate;
- resultCount = count;
- resultLength = candidate.length;
- }
- }
- return result;
- },
- hasFilter: function(filter)
- {
- return filter.text in this.keywordByFilter;
- },
- getKeywordForFilter: function(filter)
- {
- if (filter.text in this.keywordByFilter)
- {
- return this.keywordByFilter[filter.text];
- }
- else
- {
- return null;
- }
- },
- _checkEntryMatch: function(keyword, location, contentType, docDomain, thirdParty, sitekey)
- {
- var list = this.filterByKeyword[keyword];
- for (var i = 0; i < list.length; i++)
- {
- var filter = list[i];
- if (filter == "#this")
- {
- filter = list;
- }
- if (filter.matches(location, contentType, docDomain, thirdParty, sitekey))
- {
- return filter;
- }
- }
- return null;
- },
- matchesAny: function(location, contentType, docDomain, thirdParty, sitekey)
- {
- var candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
- if (candidates === null)
- {
- candidates = [];
- }
- candidates.push("");
- for (var i = 0, l = candidates.length; i < l; i++)
- {
- var substr = candidates[i];
- if (substr in this.filterByKeyword)
- {
- var result = this._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey);
- if (result)
- {
- return result;
- }
- }
- }
- return null;
- }
-};
-
-function CombinedMatcher()
-{
- this.blacklist = new Matcher();
- this.whitelist = new Matcher();
- this.resultCache = createDict();
-}
-CombinedMatcher.maxCacheEntries = 1000;
-CombinedMatcher.prototype = {
- blacklist: null,
- whitelist: null,
- resultCache: null,
- cacheEntries: 0,
- clear: function()
- {
- this.blacklist.clear();
- this.whitelist.clear();
- this.resultCache = createDict();
- this.cacheEntries = 0;
- },
- add: function(filter)
- {
- if (filter instanceof WhitelistFilter)
- {
- this.whitelist.add(filter);
- }
- else
- {
- this.blacklist.add(filter);
- }
- if (this.cacheEntries > 0)
- {
- this.resultCache = createDict();
- this.cacheEntries = 0;
- }
- },
- remove: function(filter)
- {
- if (filter instanceof WhitelistFilter)
- {
- this.whitelist.remove(filter);
- }
- else
- {
- this.blacklist.remove(filter);
- }
- if (this.cacheEntries > 0)
- {
- this.resultCache = createDict();
- this.cacheEntries = 0;
- }
- },
- findKeyword: function(filter)
- {
- if (filter instanceof WhitelistFilter)
- {
- return this.whitelist.findKeyword(filter);
- }
- else
- {
- return this.blacklist.findKeyword(filter);
- }
- },
- hasFilter: function(filter)
- {
- if (filter instanceof WhitelistFilter)
- {
- return this.whitelist.hasFilter(filter);
- }
- else
- {
- return this.blacklist.hasFilter(filter);
- }
- },
- getKeywordForFilter: function(filter)
- {
- if (filter instanceof WhitelistFilter)
- {
- return this.whitelist.getKeywordForFilter(filter);
- }
- else
- {
- return this.blacklist.getKeywordForFilter(filter);
- }
- },
- isSlowFilter: function(filter)
- {
- var matcher = filter instanceof WhitelistFilter ? this.whitelist : this.blacklist;
- if (matcher.hasFilter(filter))
- {
- return !matcher.getKeywordForFilter(filter);
- }
- else
- {
- return !matcher.findKeyword(filter);
- }
- },
- matchesAnyInternal: function(location, contentType, docDomain, thirdParty, sitekey)
- {
- var candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
- if (candidates === null)
- {
- candidates = [];
- }
- candidates.push("");
- var blacklistHit = null;
- for (var i = 0, l = candidates.length; i < l; i++)
- {
- var substr = candidates[i];
- if (substr in this.whitelist.filterByKeyword)
- {
- var result = this.whitelist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey);
- if (result)
- {
- return result;
- }
- }
- if (substr in this.blacklist.filterByKeyword && blacklistHit === null)
- {
- blacklistHit = this.blacklist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey);
- }
- }
- return blacklistHit;
- },
- matchesAny: function(location, docDomain)
- {
- var key = location + " " + docDomain + " ";
- if (key in this.resultCache)
- {
- return this.resultCache[key];
- }
- var result = this.matchesAnyInternal(location, 0, docDomain, null, null);
- if (this.cacheEntries >= CombinedMatcher.maxCacheEntries)
- {
- this.resultCache = createDict();
- this.cacheEntries = 0;
- }
- this.resultCache[key] = result;
- this.cacheEntries++;
- return result;
- }
-};
-
-var userrulesMatcher = new CombinedMatcher();
-var defaultMatcher = new CombinedMatcher();
-
-var direct = 'DIRECT;';
-
-for (var i = 0; i < userrules.length; i++) {
- userrulesMatcher.add(Filter.fromText(userrules[i]));
-}
-
-for (var i = 0; i < rules.length; i++) {
- defaultMatcher.add(Filter.fromText(rules[i]));
-}
-
-function FindProxyForURL(url, host) {
- if (userrulesMatcher.matchesAny(url, host) instanceof BlockingFilter) {
- return proxy;
- }
- if (userrulesMatcher.matchesAny(url, host) instanceof WhitelistFilter) {
- return direct;
- }
- // Hack for Geosite, it provides a whitelist...
- if (defaultMatcher.matchesAny(url, host) instanceof WhitelistFilter) {
- return direct;
- }
- if (defaultMatcher.matchesAny(url, host) instanceof BlockingFilter) {
- return proxy;
- }
- return direct;
-}
+/* eslint-disable */
+// Was generated by gfwlist2pac in precise mode
+// https://github.com/clowwindy/gfwlist2pac
+
+// 2019-10-06: More 'javascript' way to interaction with main program
+// 2019-02-08: Updated to support shadowsocks-windows user rules.
+
+var proxy = __PROXY__;
+var userrules = [];
+var rules = [];
+
+// convert to abp grammar
+for (var i = 0; i < __RULES__.length; i++) {
+ var s = __RULES__[i];
+ if (s.substring(0, 2) == "||") s += "^";
+ rules.push(s);
+}
+
+for (var i = 0; i < __USERRULES__.length; i++) {
+ var s = __USERRULES__[i];
+ if (s.substring(0, 2) == "||") s += "^";
+ userrules.push(s);
+}
+
+/*
+* This file is part of Adblock Plus ,
+* Copyright (C) 2006-2014 Eyeo GmbH
+*
+* Adblock Plus is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License version 3 as
+* published by the Free Software Foundation.
+*
+* Adblock Plus 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with Adblock Plus. If not, see .
+*/
+
+function createDict()
+{
+ var result = {};
+ result.__proto__ = null;
+ return result;
+}
+
+function getOwnPropertyDescriptor(obj, key)
+{
+ if (obj.hasOwnProperty(key))
+ {
+ return obj[key];
+ }
+ return null;
+}
+
+function extend(subclass, superclass, definition)
+{
+ if (Object.__proto__)
+ {
+ definition.__proto__ = superclass.prototype;
+ subclass.prototype = definition;
+ }
+ else
+ {
+ var tmpclass = function(){}, ret;
+ tmpclass.prototype = superclass.prototype;
+ subclass.prototype = new tmpclass();
+ subclass.prototype.constructor = superclass;
+ for (var i in definition)
+ {
+ if (definition.hasOwnProperty(i))
+ {
+ subclass.prototype[i] = definition[i];
+ }
+ }
+ }
+}
+
+function Filter(text)
+{
+ this.text = text;
+ this.subscriptions = [];
+}
+Filter.prototype = {
+ text: null,
+ subscriptions: null,
+ toString: function()
+ {
+ return this.text;
+ }
+};
+Filter.knownFilters = createDict();
+Filter.elemhideRegExp = /^([^\/\*\|\@"!]*?)#(\@)?(?:([\w\-]+|\*)((?:\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\))*)|#([^{}]+))$/;
+Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)?$/;
+Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/;
+Filter.fromText = function(text)
+{
+ if (text in Filter.knownFilters)
+ {
+ return Filter.knownFilters[text];
+ }
+ var ret;
+ if (text.charAt(0) == "!")
+ {
+ ret = new CommentFilter(text);
+ }
+ else
+ {
+ ret = RegExpFilter.fromText(text);
+ }
+ Filter.knownFilters[ret.text] = ret;
+ return ret;
+};
+
+function InvalidFilter(text, reason)
+{
+ Filter.call(this, text);
+ this.reason = reason;
+}
+extend(InvalidFilter, Filter, {
+ reason: null
+});
+
+function CommentFilter(text)
+{
+ Filter.call(this, text);
+}
+extend(CommentFilter, Filter, {
+});
+
+function ActiveFilter(text, domains)
+{
+ Filter.call(this, text);
+ this.domainSource = domains;
+}
+extend(ActiveFilter, Filter, {
+ domainSource: null,
+ domainSeparator: null,
+ ignoreTrailingDot: true,
+ domainSourceIsUpperCase: false,
+ getDomains: function()
+ {
+ var prop = getOwnPropertyDescriptor(this, "domains");
+ if (prop)
+ {
+ return prop;
+ }
+ var domains = null;
+ if (this.domainSource)
+ {
+ var source = this.domainSource;
+ if (!this.domainSourceIsUpperCase)
+ {
+ source = source.toUpperCase();
+ }
+ var list = source.split(this.domainSeparator);
+ if (list.length == 1 && (list[0]).charAt(0) != "~")
+ {
+ domains = createDict();
+ domains[""] = false;
+ if (this.ignoreTrailingDot)
+ {
+ list[0] = list[0].replace(/\.+$/, "");
+ }
+ domains[list[0]] = true;
+ }
+ else
+ {
+ var hasIncludes = false;
+ for (var i = 0; i < list.length; i++)
+ {
+ var domain = list[i];
+ if (this.ignoreTrailingDot)
+ {
+ domain = domain.replace(/\.+$/, "");
+ }
+ if (domain == "")
+ {
+ continue;
+ }
+ var include;
+ if (domain.charAt(0) == "~")
+ {
+ include = false;
+ domain = domain.substr(1);
+ }
+ else
+ {
+ include = true;
+ hasIncludes = true;
+ }
+ if (!domains)
+ {
+ domains = createDict();
+ }
+ domains[domain] = include;
+ }
+ domains[""] = !hasIncludes;
+ }
+ this.domainSource = null;
+ }
+ return this.domains;
+ },
+ sitekeys: null,
+ isActiveOnDomain: function(docDomain, sitekey)
+ {
+ if (this.getSitekeys() && (!sitekey || this.getSitekeys().indexOf(sitekey.toUpperCase()) < 0))
+ {
+ return false;
+ }
+ if (!this.getDomains())
+ {
+ return true;
+ }
+ if (!docDomain)
+ {
+ return this.getDomains()[""];
+ }
+ if (this.ignoreTrailingDot)
+ {
+ docDomain = docDomain.replace(/\.+$/, "");
+ }
+ docDomain = docDomain.toUpperCase();
+ while (true)
+ {
+ if (docDomain in this.getDomains())
+ {
+ return this.domains[docDomain];
+ }
+ var nextDot = docDomain.indexOf(".");
+ if (nextDot < 0)
+ {
+ break;
+ }
+ docDomain = docDomain.substr(nextDot + 1);
+ }
+ return this.domains[""];
+ },
+ isActiveOnlyOnDomain: function(docDomain)
+ {
+ if (!docDomain || !this.getDomains() || this.getDomains()[""])
+ {
+ return false;
+ }
+ if (this.ignoreTrailingDot)
+ {
+ docDomain = docDomain.replace(/\.+$/, "");
+ }
+ docDomain = docDomain.toUpperCase();
+ for (var domain in this.getDomains())
+ {
+ if (this.domains[domain] && domain != docDomain && (domain.length <= docDomain.length || domain.indexOf("." + docDomain) != domain.length - docDomain.length - 1))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+});
+
+function RegExpFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys)
+{
+ ActiveFilter.call(this, text, domains, sitekeys);
+ if (contentType != null)
+ {
+ this.contentType = contentType;
+ }
+ if (matchCase)
+ {
+ this.matchCase = matchCase;
+ }
+ if (thirdParty != null)
+ {
+ this.thirdParty = thirdParty;
+ }
+ if (sitekeys != null)
+ {
+ this.sitekeySource = sitekeys;
+ }
+ if (regexpSource.length >= 2 && regexpSource.charAt(0) == "/" && regexpSource.charAt(regexpSource.length - 1) == "/")
+ {
+ var regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), this.matchCase ? "" : "i");
+ this.regexp = regexp;
+ }
+ else
+ {
+ this.regexpSource = regexpSource;
+ }
+}
+extend(RegExpFilter, ActiveFilter, {
+ domainSourceIsUpperCase: true,
+ length: 1,
+ domainSeparator: "|",
+ regexpSource: null,
+ getRegexp: function()
+ {
+ var prop = getOwnPropertyDescriptor(this, "regexp");
+ if (prop)
+ {
+ return prop;
+ }
+ var source = this.regexpSource.replace(/\*+/g, "*").replace(/\^\|$/, "^").replace(/\W/g, "\\$&").replace(/\\\*/g, ".*").replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x7F]|$)").replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?").replace(/^\\\|/, "^").replace(/\\\|$/, "$").replace(/^(\.\*)/, "").replace(/(\.\*)$/, "");
+ var regexp = new RegExp(source, this.matchCase ? "" : "i");
+ this.regexp = regexp;
+ return regexp;
+ },
+ contentType: 2147483647,
+ matchCase: false,
+ thirdParty: null,
+ sitekeySource: null,
+ getSitekeys: function()
+ {
+ var prop = getOwnPropertyDescriptor(this, "sitekeys");
+ if (prop)
+ {
+ return prop;
+ }
+ var sitekeys = null;
+ if (this.sitekeySource)
+ {
+ sitekeys = this.sitekeySource.split("|");
+ this.sitekeySource = null;
+ }
+ this.sitekeys = sitekeys;
+ return this.sitekeys;
+ },
+ matches: function(location, contentType, docDomain, thirdParty, sitekey)
+ {
+ if (this.getRegexp().test(location) && this.isActiveOnDomain(docDomain, sitekey))
+ {
+ return true;
+ }
+ return false;
+ }
+});
+RegExpFilter.prototype["0"] = "#this";
+RegExpFilter.fromText = function(text)
+{
+ var blocking = true;
+ var origText = text;
+ if (text.indexOf("@@") == 0)
+ {
+ blocking = false;
+ text = text.substr(2);
+ }
+ var contentType = null;
+ var matchCase = null;
+ var domains = null;
+ var sitekeys = null;
+ var thirdParty = null;
+ var collapse = null;
+ var options;
+ var match = text.indexOf("$") >= 0 ? Filter.optionsRegExp.exec(text) : null;
+ if (match)
+ {
+ options = match[1].toUpperCase().split(",");
+ text = match.input.substr(0, match.index);
+ for (var _loopIndex6 = 0; _loopIndex6 < options.length; ++_loopIndex6)
+ {
+ var option = options[_loopIndex6];
+ var value = null;
+ var separatorIndex = option.indexOf("=");
+ if (separatorIndex >= 0)
+ {
+ value = option.substr(separatorIndex + 1);
+ option = option.substr(0, separatorIndex);
+ }
+ option = option.replace(/-/, "_");
+ if (option in RegExpFilter.typeMap)
+ {
+ if (contentType == null)
+ {
+ contentType = 0;
+ }
+ contentType |= RegExpFilter.typeMap[option];
+ }
+ else if (option.charAt(0) == "~" && option.substr(1) in RegExpFilter.typeMap)
+ {
+ if (contentType == null)
+ {
+ contentType = RegExpFilter.prototype.contentType;
+ }
+ contentType &= ~RegExpFilter.typeMap[option.substr(1)];
+ }
+ else if (option == "MATCH_CASE")
+ {
+ matchCase = true;
+ }
+ else if (option == "~MATCH_CASE")
+ {
+ matchCase = false;
+ }
+ else if (option == "DOMAIN" && typeof value != "undefined")
+ {
+ domains = value;
+ }
+ else if (option == "THIRD_PARTY")
+ {
+ thirdParty = true;
+ }
+ else if (option == "~THIRD_PARTY")
+ {
+ thirdParty = false;
+ }
+ else if (option == "COLLAPSE")
+ {
+ collapse = true;
+ }
+ else if (option == "~COLLAPSE")
+ {
+ collapse = false;
+ }
+ else if (option == "SITEKEY" && typeof value != "undefined")
+ {
+ sitekeys = value;
+ }
+ else
+ {
+ return new InvalidFilter(origText, "Unknown option " + option.toLowerCase());
+ }
+ }
+ }
+ if (!blocking && (contentType == null || contentType & RegExpFilter.typeMap.DOCUMENT) && (!options || options.indexOf("DOCUMENT") < 0) && !/^\|?[\w\-]+:/.test(text))
+ {
+ if (contentType == null)
+ {
+ contentType = RegExpFilter.prototype.contentType;
+ }
+ contentType &= ~RegExpFilter.typeMap.DOCUMENT;
+ }
+ try
+ {
+ if (blocking)
+ {
+ return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys, collapse);
+ }
+ else
+ {
+ return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys);
+ }
+ }
+ catch (e)
+ {
+ return new InvalidFilter(origText, e);
+ }
+};
+RegExpFilter.typeMap = {
+ OTHER: 1,
+ SCRIPT: 2,
+ IMAGE: 4,
+ STYLESHEET: 8,
+ OBJECT: 16,
+ SUBDOCUMENT: 32,
+ DOCUMENT: 64,
+ XBL: 1,
+ PING: 1,
+ XMLHTTPREQUEST: 2048,
+ OBJECT_SUBREQUEST: 4096,
+ DTD: 1,
+ MEDIA: 16384,
+ FONT: 32768,
+ BACKGROUND: 4,
+ POPUP: 268435456,
+ ELEMHIDE: 1073741824
+};
+RegExpFilter.prototype.contentType &= ~ (RegExpFilter.typeMap.ELEMHIDE | RegExpFilter.typeMap.POPUP);
+
+function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys, collapse)
+{
+ RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys);
+ this.collapse = collapse;
+}
+extend(BlockingFilter, RegExpFilter, {
+ collapse: null
+});
+
+function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys)
+{
+ RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys);
+}
+extend(WhitelistFilter, RegExpFilter, {
+});
+
+function Matcher()
+{
+ this.clear();
+}
+Matcher.prototype = {
+ filterByKeyword: null,
+ keywordByFilter: null,
+ clear: function()
+ {
+ this.filterByKeyword = createDict();
+ this.keywordByFilter = createDict();
+ },
+ add: function(filter)
+ {
+ if (filter.text in this.keywordByFilter)
+ {
+ return;
+ }
+ var keyword = this.findKeyword(filter);
+ var oldEntry = this.filterByKeyword[keyword];
+ if (typeof oldEntry == "undefined")
+ {
+ this.filterByKeyword[keyword] = filter;
+ }
+ else if (oldEntry.length == 1)
+ {
+ this.filterByKeyword[keyword] = [oldEntry, filter];
+ }
+ else
+ {
+ oldEntry.push(filter);
+ }
+ this.keywordByFilter[filter.text] = keyword;
+ },
+ remove: function(filter)
+ {
+ if (!(filter.text in this.keywordByFilter))
+ {
+ return;
+ }
+ var keyword = this.keywordByFilter[filter.text];
+ var list = this.filterByKeyword[keyword];
+ if (list.length <= 1)
+ {
+ delete this.filterByKeyword[keyword];
+ }
+ else
+ {
+ var index = list.indexOf(filter);
+ if (index >= 0)
+ {
+ list.splice(index, 1);
+ if (list.length == 1)
+ {
+ this.filterByKeyword[keyword] = list[0];
+ }
+ }
+ }
+ delete this.keywordByFilter[filter.text];
+ },
+ findKeyword: function(filter)
+ {
+ var result = "";
+ var text = filter.text;
+ if (Filter.regexpRegExp.test(text))
+ {
+ return result;
+ }
+ var match = Filter.optionsRegExp.exec(text);
+ if (match)
+ {
+ text = match.input.substr(0, match.index);
+ }
+ if (text.substr(0, 2) == "@@")
+ {
+ text = text.substr(2);
+ }
+ var candidates = text.toLowerCase().match(/[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g);
+ if (!candidates)
+ {
+ return result;
+ }
+ var hash = this.filterByKeyword;
+ var resultCount = 16777215;
+ var resultLength = 0;
+ for (var i = 0, l = candidates.length; i < l; i++)
+ {
+ var candidate = candidates[i].substr(1);
+ var count = candidate in hash ? hash[candidate].length : 0;
+ if (count < resultCount || count == resultCount && candidate.length > resultLength)
+ {
+ result = candidate;
+ resultCount = count;
+ resultLength = candidate.length;
+ }
+ }
+ return result;
+ },
+ hasFilter: function(filter)
+ {
+ return filter.text in this.keywordByFilter;
+ },
+ getKeywordForFilter: function(filter)
+ {
+ if (filter.text in this.keywordByFilter)
+ {
+ return this.keywordByFilter[filter.text];
+ }
+ else
+ {
+ return null;
+ }
+ },
+ _checkEntryMatch: function(keyword, location, contentType, docDomain, thirdParty, sitekey)
+ {
+ var list = this.filterByKeyword[keyword];
+ for (var i = 0; i < list.length; i++)
+ {
+ var filter = list[i];
+ if (filter == "#this")
+ {
+ filter = list;
+ }
+ if (filter.matches(location, contentType, docDomain, thirdParty, sitekey))
+ {
+ return filter;
+ }
+ }
+ return null;
+ },
+ matchesAny: function(location, contentType, docDomain, thirdParty, sitekey)
+ {
+ var candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
+ if (candidates === null)
+ {
+ candidates = [];
+ }
+ candidates.push("");
+ for (var i = 0, l = candidates.length; i < l; i++)
+ {
+ var substr = candidates[i];
+ if (substr in this.filterByKeyword)
+ {
+ var result = this._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey);
+ if (result)
+ {
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+};
+
+function CombinedMatcher()
+{
+ this.blacklist = new Matcher();
+ this.whitelist = new Matcher();
+ this.resultCache = createDict();
+}
+CombinedMatcher.maxCacheEntries = 1000;
+CombinedMatcher.prototype = {
+ blacklist: null,
+ whitelist: null,
+ resultCache: null,
+ cacheEntries: 0,
+ clear: function()
+ {
+ this.blacklist.clear();
+ this.whitelist.clear();
+ this.resultCache = createDict();
+ this.cacheEntries = 0;
+ },
+ add: function(filter)
+ {
+ if (filter instanceof WhitelistFilter)
+ {
+ this.whitelist.add(filter);
+ }
+ else
+ {
+ this.blacklist.add(filter);
+ }
+ if (this.cacheEntries > 0)
+ {
+ this.resultCache = createDict();
+ this.cacheEntries = 0;
+ }
+ },
+ remove: function(filter)
+ {
+ if (filter instanceof WhitelistFilter)
+ {
+ this.whitelist.remove(filter);
+ }
+ else
+ {
+ this.blacklist.remove(filter);
+ }
+ if (this.cacheEntries > 0)
+ {
+ this.resultCache = createDict();
+ this.cacheEntries = 0;
+ }
+ },
+ findKeyword: function(filter)
+ {
+ if (filter instanceof WhitelistFilter)
+ {
+ return this.whitelist.findKeyword(filter);
+ }
+ else
+ {
+ return this.blacklist.findKeyword(filter);
+ }
+ },
+ hasFilter: function(filter)
+ {
+ if (filter instanceof WhitelistFilter)
+ {
+ return this.whitelist.hasFilter(filter);
+ }
+ else
+ {
+ return this.blacklist.hasFilter(filter);
+ }
+ },
+ getKeywordForFilter: function(filter)
+ {
+ if (filter instanceof WhitelistFilter)
+ {
+ return this.whitelist.getKeywordForFilter(filter);
+ }
+ else
+ {
+ return this.blacklist.getKeywordForFilter(filter);
+ }
+ },
+ isSlowFilter: function(filter)
+ {
+ var matcher = filter instanceof WhitelistFilter ? this.whitelist : this.blacklist;
+ if (matcher.hasFilter(filter))
+ {
+ return !matcher.getKeywordForFilter(filter);
+ }
+ else
+ {
+ return !matcher.findKeyword(filter);
+ }
+ },
+ matchesAnyInternal: function(location, contentType, docDomain, thirdParty, sitekey)
+ {
+ var candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
+ if (candidates === null)
+ {
+ candidates = [];
+ }
+ candidates.push("");
+ var blacklistHit = null;
+ for (var i = 0, l = candidates.length; i < l; i++)
+ {
+ var substr = candidates[i];
+ if (substr in this.whitelist.filterByKeyword)
+ {
+ var result = this.whitelist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey);
+ if (result)
+ {
+ return result;
+ }
+ }
+ if (substr in this.blacklist.filterByKeyword && blacklistHit === null)
+ {
+ blacklistHit = this.blacklist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey);
+ }
+ }
+ return blacklistHit;
+ },
+ matchesAny: function(location, docDomain)
+ {
+ var key = location + " " + docDomain + " ";
+ if (key in this.resultCache)
+ {
+ return this.resultCache[key];
+ }
+ var result = this.matchesAnyInternal(location, 0, docDomain, null, null);
+ if (this.cacheEntries >= CombinedMatcher.maxCacheEntries)
+ {
+ this.resultCache = createDict();
+ this.cacheEntries = 0;
+ }
+ this.resultCache[key] = result;
+ this.cacheEntries++;
+ return result;
+ }
+};
+
+var userrulesMatcher = new CombinedMatcher();
+var defaultMatcher = new CombinedMatcher();
+
+var direct = 'DIRECT;';
+
+for (var i = 0; i < userrules.length; i++) {
+ userrulesMatcher.add(Filter.fromText(userrules[i]));
+}
+
+for (var i = 0; i < rules.length; i++) {
+ defaultMatcher.add(Filter.fromText(rules[i]));
+}
+
+function FindProxyForURL(url, host) {
+ if (userrulesMatcher.matchesAny(url, host) instanceof BlockingFilter) {
+ return proxy;
+ }
+ if (userrulesMatcher.matchesAny(url, host) instanceof WhitelistFilter) {
+ return direct;
+ }
+ // Hack for Geosite, it provides a whitelist...
+ if (defaultMatcher.matchesAny(url, host) instanceof WhitelistFilter) {
+ return direct;
+ }
+ if (defaultMatcher.matchesAny(url, host) instanceof BlockingFilter) {
+ return proxy;
+ }
+ return direct;
+}
diff --git a/shadowsocks-csharp/Data/dlc.dat b/Shadowsocks/Assets/dlc.dat
similarity index 100%
rename from shadowsocks-csharp/Data/dlc.dat
rename to Shadowsocks/Assets/dlc.dat
diff --git a/shadowsocks-csharp/Data/privoxy.exe.gz b/Shadowsocks/Assets/privoxy.exe.gz
similarity index 100%
rename from shadowsocks-csharp/Data/privoxy.exe.gz
rename to Shadowsocks/Assets/privoxy.exe.gz
diff --git a/shadowsocks-csharp/Data/privoxy_conf.txt b/Shadowsocks/Assets/privoxy_conf.txt
similarity index 96%
rename from shadowsocks-csharp/Data/privoxy_conf.txt
rename to Shadowsocks/Assets/privoxy_conf.txt
index bef1c726..e6529288 100644
--- a/shadowsocks-csharp/Data/privoxy_conf.txt
+++ b/Shadowsocks/Assets/privoxy_conf.txt
@@ -1,8 +1,8 @@
-listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__
-toggle 0
-logfile ss_privoxy.log
-show-on-task-bar 0
-activity-animation 0
-forward-socks5 / __SOCKS_HOST__:__SOCKS_PORT__ .
-max-client-connections 2048
-hide-console
+listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__
+toggle 0
+logfile ss_privoxy.log
+show-on-task-bar 0
+activity-animation 0
+forward-socks5 / __SOCKS_HOST__:__SOCKS_PORT__ .
+max-client-connections 2048
+hide-console
diff --git a/shadowsocks-csharp/Data/user-rule.txt b/Shadowsocks/Assets/user-rule.txt
old mode 100755
new mode 100644
similarity index 98%
rename from shadowsocks-csharp/Data/user-rule.txt
rename to Shadowsocks/Assets/user-rule.txt
index 8a7fab80..14bfe023
--- a/shadowsocks-csharp/Data/user-rule.txt
+++ b/Shadowsocks/Assets/user-rule.txt
@@ -1,2 +1,2 @@
-! Put user rules line by line in this file.
-! See https://adblockplus.org/en/filter-cheatsheet
+! Put user rules line by line in this file.
+! See https://adblockplus.org/en/filter-cheatsheet
diff --git a/Shadowsocks/Resource.Designer.cs b/Shadowsocks/Resource.Designer.cs
new file mode 100644
index 00000000..ff15eaa2
--- /dev/null
+++ b/Shadowsocks/Resource.Designer.cs
@@ -0,0 +1,153 @@
+//------------------------------------------------------------------------------
+//
+// 此代码由工具生成。
+// 运行时版本:4.0.30319.42000
+//
+// 对此文件的更改可能会导致不正确的行为,并且如果
+// 重新生成代码,这些更改将会丢失。
+//
+//------------------------------------------------------------------------------
+
+namespace Shadowsocks {
+ using System;
+
+
+ ///
+ /// 一个强类型的资源类,用于查找本地化的字符串等。
+ ///
+ // 此类是由 StronglyTypedResourceBuilder
+ // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+ // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+ // (以 /str 作为命令选项),或重新生成 VS 项目。
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resource {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resource() {
+ }
+
+ ///
+ /// 返回此类使用的缓存的 ResourceManager 实例。
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Shadowsocks.Resource", typeof(Resource).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// 重写当前线程的 CurrentUICulture 属性
+ /// 重写当前线程的 CurrentUICulture 属性。
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// 查找类似 /* eslint-disable */
+ ///// Was generated by gfwlist2pac in precise mode
+ ///// https://github.com/clowwindy/gfwlist2pac
+ ///
+ ///// 2019-10-06: More 'javascript' way to interaction with main program
+ ///// 2019-02-08: Updated to support shadowsocks-windows user rules.
+ ///
+ ///var proxy = __PROXY__;
+ ///var userrules = [];
+ ///var rules = [];
+ ///
+ ///// convert to abp grammar
+ ///for (var i = 0; i < __RULES__.length; i++) {
+ /// var s = __RULES__[i];
+ /// if (s.substring(0, 2) == "||") s += "^";
+ /// rules.push(s);
+ ///}
+ ///
+ ///for (var i = 0; i < [字符串的其余部分被截断]"; 的本地化字符串。
+ ///
+ internal static string ABP_JS {
+ get {
+ return ResourceManager.GetString("ABP_JS", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找 System.Byte[] 类型的本地化资源。
+ ///
+ internal static byte[] DLC_DAT {
+ get {
+ object obj = ResourceManager.GetObject("DLC_DAT", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// 查找类似 <?xml version="1.0" encoding="utf-8" ?>
+ ///<!-- Warning: Configuration may reset after shadowsocks upgrade. -->
+ ///<!-- If you messed it up, delete this file and Shadowsocks will create a new one. -->
+ ///<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ /// <targets>
+ /// <!-- This line is managed by Shadowsocks. Do not modify it unless you know what you are doing.-->
+ /// <target name="file" xsi:type="File" fileName="ss_win_temp\shadowsocks [字符串的其余部分被截断]"; 的本地化字符串。
+ ///
+ internal static string NLOG_CONFIG {
+ get {
+ return ResourceManager.GetString("NLOG_CONFIG", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__
+ ///toggle 0
+ ///logfile ss_privoxy.log
+ ///show-on-task-bar 0
+ ///activity-animation 0
+ ///forward-socks5 / __SOCKS_HOST__:__SOCKS_PORT__ .
+ ///max-client-connections 2048
+ ///hide-console
+ /// 的本地化字符串。
+ ///
+ internal static string PRIVOXY_CONF {
+ get {
+ return ResourceManager.GetString("PRIVOXY_CONF", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找 System.Byte[] 类型的本地化资源。
+ ///
+ internal static byte[] PRIVOXY_EXE {
+ get {
+ object obj = ResourceManager.GetObject("PRIVOXY_EXE", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// 查找类似 ! Put user rules line by line in this file.
+ ///! See https://adblockplus.org/en/filter-cheatsheet
+ /// 的本地化字符串。
+ ///
+ internal static string USER_RULE {
+ get {
+ return ResourceManager.GetString("USER_RULE", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Shadowsocks/Resource.resx b/Shadowsocks/Resource.resx
new file mode 100644
index 00000000..04be637b
--- /dev/null
+++ b/Shadowsocks/Resource.resx
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ assets\abp.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312
+
+
+ assets\dlc.dat;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ assets\nlog.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312
+
+
+ assets\privoxy_conf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312
+
+
+ assets\privoxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ assets\user-rule.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
+
+
\ No newline at end of file
diff --git a/Shadowsocks/Shadowsocks.csproj b/Shadowsocks/Shadowsocks.csproj
new file mode 100644
index 00000000..eb331c1c
--- /dev/null
+++ b/Shadowsocks/Shadowsocks.csproj
@@ -0,0 +1,31 @@
+
+
+
+ netstandard2.1
+ clowwindy & community 2020
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resource.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resource.Designer.cs
+
+
+
+
diff --git a/shadowsocks-csharp/Data/NLog.config b/shadowsocks-csharp/Data/NLog.config
deleted file mode 100644
index 05509cff..00000000
--- a/shadowsocks-csharp/Data/NLog.config
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/shadowsocks-csharp/FodyWeavers.xml b/shadowsocks-csharp/FodyWeavers.xml
deleted file mode 100644
index 39c0beec..00000000
--- a/shadowsocks-csharp/FodyWeavers.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/shadowsocks-csharp/FodyWeavers.xsd b/shadowsocks-csharp/FodyWeavers.xsd
deleted file mode 100644
index 91102829..00000000
--- a/shadowsocks-csharp/FodyWeavers.xsd
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks
-
-
-
-
- A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.
-
-
-
-
- A list of unmanaged 32 bit assembly names to include, delimited with line breaks.
-
-
-
-
- A list of unmanaged 64 bit assembly names to include, delimited with line breaks.
-
-
-
-
- The order of preloaded assemblies, delimited with line breaks.
-
-
-
-
-
- This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.
-
-
-
-
- Controls if .pdbs for reference assemblies are also embedded.
-
-
-
-
- Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.
-
-
-
-
- As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.
-
-
-
-
- Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.
-
-
-
-
- Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.
-
-
-
-
- A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |
-
-
-
-
- A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.
-
-
-
-
- A list of unmanaged 32 bit assembly names to include, delimited with |.
-
-
-
-
- A list of unmanaged 64 bit assembly names to include, delimited with |.
-
-
-
-
- The order of preloaded assemblies, delimited with |.
-
-
-
-
-
-
-
-
- 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
-
-
-
-
- A comma-separated list of error codes that can be safely ignored in assembly verification.
-
-
-
-
- 'false' to turn off automatic generation of the XML Schema file.
-
-
-
-
-
\ No newline at end of file
diff --git a/shadowsocks-csharp/Localization/LocalizationProvider.cs b/shadowsocks-csharp/Localization/LocalizationProvider.cs
deleted file mode 100644
index 0677bce1..00000000
--- a/shadowsocks-csharp/Localization/LocalizationProvider.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Reflection;
-using WPFLocalizeExtension.Extensions;
-
-namespace Shadowsocks.Localization
-{
- public static class LocalizationProvider
- {
- public static T GetLocalizedValue(string key)
- {
- return LocExtension.GetLocalizedValue(Assembly.GetCallingAssembly().GetName().Name + ":Strings:" + key);
- }
- }
-}
diff --git a/shadowsocks-csharp/Properties/Resources.Designer.cs b/shadowsocks-csharp/Properties/Resources.Designer.cs
deleted file mode 100644
index 2c64d698..00000000
--- a/shadowsocks-csharp/Properties/Resources.Designer.cs
+++ /dev/null
@@ -1,244 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace Shadowsocks.Properties {
- using System;
-
-
- ///
- /// A strongly-typed resource class, for looking up localized strings, etc.
- ///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- public class Resources {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources() {
- }
-
- ///
- /// Returns the cached ResourceManager instance used by this class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- public static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Shadowsocks.Properties.Resources", typeof(Resources).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- public static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
-
- ///
- /// Looks up a localized string similar to /* eslint-disable */
- ///// Was generated by gfwlist2pac in precise mode
- ///// https://github.com/clowwindy/gfwlist2pac
- ///
- ///// 2019-10-06: More 'javascript' way to interaction with main program
- ///// 2019-02-08: Updated to support shadowsocks-windows user rules.
- ///
- ///var proxy = __PROXY__;
- ///var userrules = [];
- ///var rules = [];
- ///
- ///// convert to abp grammar
- ///for (var i = 0; i < __RULES__.length; i++) {
- /// var s = __RULES__[i];
- /// if (s.substring(0, 2) == "||") s += "^";
- /// rules.push(s);
- ///}
- ///
- ///for (var i = 0; i < [rest of string was truncated]";.
- ///
- public static string abp_js {
- get {
- return ResourceManager.GetString("abp_js", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized resource of type System.Byte[].
- ///
- public static byte[] dlc_dat {
- get {
- object obj = ResourceManager.GetObject("dlc_dat", resourceCulture);
- return ((byte[])(obj));
- }
- }
-
- ///
- /// Looks up a localized string similar to en,ru-RU,zh-CN,zh-TW,ja,ko,fr
- ///#Restart program to apply translation,,,,,,
- ///#This is comment line,,,,,,
- ///#Always keep language name at head of file,,,,,,
- ///#Language name is output in log,,,,,,
- ///"#You can find it by search ""Current language is:""",,,,,,
- ///#Please use UTF-8 with BOM encoding so we can edit it in Excel,,,,,,
- ///,,,,,,
- ///Shadowsocks,Shadowsocks,Shadowsocks,Shadowsocks,Shadowsocks,Shadowsocks,Shadowsocks
- ///,,,,,,
- ///#Menu,,,,,,
- ///,,,,,,
- ///System Proxy,Системный прокси-сервер,系统代理,系統代理,システムプロキシ,시스템 프록시,P [rest of string was truncated]";.
- ///
- public static string i18n_csv {
- get {
- return ResourceManager.GetString("i18n_csv", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8" ?>
- ///<!-- Warning: Configuration may reset after shadowsocks upgrade. -->
- ///<!-- If you messed it up, delete this file and Shadowsocks will create a new one. -->
- ///<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- /// <targets>
- /// <!-- This line is managed by Shadowsocks. Do not modify it unless you know what you are doing.-->
- /// <target name="file" xsi:type="File" fileName="ss_win_temp\shadowsocks.log" writ [rest of string was truncated]";.
- ///
- public static string NLog_config {
- get {
- return ResourceManager.GetString("NLog_config", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__
- ///toggle 0
- ///logfile ss_privoxy.log
- ///show-on-task-bar 0
- ///activity-animation 0
- ///forward-socks5 / __SOCKS_HOST__:__SOCKS_PORT__ .
- ///max-client-connections 2048
- ///hide-console
- ///.
- ///
- public static string privoxy_conf {
- get {
- return ResourceManager.GetString("privoxy_conf", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized resource of type System.Byte[].
- ///
- public static byte[] privoxy_exe {
- get {
- object obj = ResourceManager.GetObject("privoxy_exe", resourceCulture);
- return ((byte[])(obj));
- }
- }
-
- ///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
- ///
- public static System.Drawing.Bitmap ss32Fill {
- get {
- object obj = ResourceManager.GetObject("ss32Fill", resourceCulture);
- return ((System.Drawing.Bitmap)(obj));
- }
- }
-
- ///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
- ///
- public static System.Drawing.Bitmap ss32In {
- get {
- object obj = ResourceManager.GetObject("ss32In", resourceCulture);
- return ((System.Drawing.Bitmap)(obj));
- }
- }
-
- ///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
- ///
- public static System.Drawing.Bitmap ss32Out {
- get {
- object obj = ResourceManager.GetObject("ss32Out", resourceCulture);
- return ((System.Drawing.Bitmap)(obj));
- }
- }
-
- ///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
- ///
- public static System.Drawing.Bitmap ss32Outline {
- get {
- object obj = ResourceManager.GetObject("ss32Outline", resourceCulture);
- return ((System.Drawing.Bitmap)(obj));
- }
- }
-
- ///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
- ///
- public static System.Drawing.Bitmap ssw128 {
- get {
- object obj = ResourceManager.GetObject("ssw128", resourceCulture);
- return ((System.Drawing.Bitmap)(obj));
- }
- }
-
- ///
- /// Looks up a localized resource of type System.Byte[].
- ///
- public static byte[] sysproxy_exe {
- get {
- object obj = ResourceManager.GetObject("sysproxy_exe", resourceCulture);
- return ((byte[])(obj));
- }
- }
-
- ///
- /// Looks up a localized resource of type System.Byte[].
- ///
- public static byte[] sysproxy64_exe {
- get {
- object obj = ResourceManager.GetObject("sysproxy64_exe", resourceCulture);
- return ((byte[])(obj));
- }
- }
-
- ///
- /// Looks up a localized string similar to ! Put user rules line by line in this file.
- ///! See https://adblockplus.org/en/filter-cheatsheet
- ///.
- ///
- public static string user_rule {
- get {
- return ResourceManager.GetString("user_rule", resourceCulture);
- }
- }
- }
-}
diff --git a/shadowsocks-csharp/Properties/Settings.Designer.cs b/shadowsocks-csharp/Properties/Settings.Designer.cs
deleted file mode 100644
index 4cc88b29..00000000
--- a/shadowsocks-csharp/Properties/Settings.Designer.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// 此代码由工具生成。
-// 运行时版本:4.0.30319.42000
-//
-// 对此文件的更改可能会导致不正确的行为,并且如果
-// 重新生成代码,这些更改将会丢失。
-//
-//------------------------------------------------------------------------------
-
-namespace Shadowsocks.Properties {
-
-
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")]
- internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
-
- private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
-
- public static Settings Default {
- get {
- return defaultInstance;
- }
- }
-
- [global::System.Configuration.UserScopedSettingAttribute()]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("600")]
- public int LogViewerWidth {
- get {
- return ((int)(this["LogViewerWidth"]));
- }
- set {
- this["LogViewerWidth"] = value;
- }
- }
-
- [global::System.Configuration.UserScopedSettingAttribute()]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("400")]
- public int LogViewerHeight {
- get {
- return ((int)(this["LogViewerHeight"]));
- }
- set {
- this["LogViewerHeight"] = value;
- }
- }
-
- [global::System.Configuration.UserScopedSettingAttribute()]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("True")]
- public bool LogViewerMaximized {
- get {
- return ((bool)(this["LogViewerMaximized"]));
- }
- set {
- this["LogViewerMaximized"] = value;
- }
- }
-
- [global::System.Configuration.UserScopedSettingAttribute()]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("0")]
- public int LogViewerTop {
- get {
- return ((int)(this["LogViewerTop"]));
- }
- set {
- this["LogViewerTop"] = value;
- }
- }
-
- [global::System.Configuration.UserScopedSettingAttribute()]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("0")]
- public int LogViewerLeft {
- get {
- return ((int)(this["LogViewerLeft"]));
- }
- set {
- this["LogViewerLeft"] = value;
- }
- }
- }
-}
diff --git a/shadowsocks-csharp/Properties/Settings.settings b/shadowsocks-csharp/Properties/Settings.settings
deleted file mode 100644
index e6469363..00000000
--- a/shadowsocks-csharp/Properties/Settings.settings
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
- 600
-
-
- 400
-
-
- True
-
-
- 0
-
-
- 0
-
-
-
\ No newline at end of file
diff --git a/shadowsocks-windows.sln b/shadowsocks-windows.sln
index 806e8434..5f4391ec 100644
--- a/shadowsocks-windows.sln
+++ b/shadowsocks-windows.sln
@@ -12,6 +12,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShadowsocksTest", "test\Sha
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7BD54B24-BBD7-4DD8-99EF-DAEE6684B673}"
ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
.gitignore = .gitignore
appveyor.yml = appveyor.yml
appveyor.yml.obsolete = appveyor.yml.obsolete
@@ -23,6 +24,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks.Common", "Shadowsocks.Common\Shadowsocks.Common.csproj", "{61AF0616-8E90-46C4-908A-E182A5001AB5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks.Crypto", "Shadowsocks.Crypto\Shadowsocks.Crypto.csproj", "{AD8974F4-EFDC-4EC5-A147-54CD4934D295}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks", "Shadowsocks\Shadowsocks.csproj", "{85C2FC93-91F0-454F-AF0A-522FCD20827A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks.WPF", "Shadowsocks.WPF\Shadowsocks.WPF.csproj", "{EA1FB2D4-B5A7-47A6-B097-2F4D29E23010}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -37,6 +46,22 @@ Global
{45913187-0685-4903-B250-DCEF0479CD86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45913187-0685-4903-B250-DCEF0479CD86}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45913187-0685-4903-B250-DCEF0479CD86}.Release|Any CPU.Build.0 = Release|Any CPU
+ {61AF0616-8E90-46C4-908A-E182A5001AB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {61AF0616-8E90-46C4-908A-E182A5001AB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {61AF0616-8E90-46C4-908A-E182A5001AB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {61AF0616-8E90-46C4-908A-E182A5001AB5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AD8974F4-EFDC-4EC5-A147-54CD4934D295}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AD8974F4-EFDC-4EC5-A147-54CD4934D295}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AD8974F4-EFDC-4EC5-A147-54CD4934D295}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AD8974F4-EFDC-4EC5-A147-54CD4934D295}.Release|Any CPU.Build.0 = Release|Any CPU
+ {85C2FC93-91F0-454F-AF0A-522FCD20827A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {85C2FC93-91F0-454F-AF0A-522FCD20827A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {85C2FC93-91F0-454F-AF0A-522FCD20827A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {85C2FC93-91F0-454F-AF0A-522FCD20827A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EA1FB2D4-B5A7-47A6-B097-2F4D29E23010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EA1FB2D4-B5A7-47A6-B097-2F4D29E23010}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EA1FB2D4-B5A7-47A6-B097-2F4D29E23010}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EA1FB2D4-B5A7-47A6-B097-2F4D29E23010}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE