Native Aot (#10295)

* Protect models

* Json Context

* YAML AOT-friendly optimization

* Run the Test in AOT mode

* Remove TaskScheduler

* Clean build output

* Workflows
This commit is contained in:
DHR60
2026-11-01 12:29:06 +00:00
committed by GitHub
parent e2f4292321
commit 78b0b2224b
57 changed files with 617 additions and 273 deletions
+16
View File
@@ -29,6 +29,22 @@ jobs:
release_tag: ${{ inputs.release_tag }}
secrets: inherit
build-aot:
uses: ./.github/workflows/build.yml
with:
target: linux
isAot: true
release-aot-zip:
if: inputs.release_tag != ''
needs: build-aot
uses: ./.github/workflows/package-zip.yml
with:
target: linux
release_tag: ${{ inputs.release_tag }}
isAot: true
secrets: inherit
deb:
name: build and release deb x64 & arm64
if: (github.event_name == 'workflow_dispatch' && inputs.release_tag != '') || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
+17 -1
View File
@@ -28,7 +28,23 @@ jobs:
target: macos
release_tag: ${{ inputs.release_tag }}
secrets: inherit
build-aot:
uses: ./.github/workflows/build.yml
with:
target: macos
isAot: true
release-aot-zip:
if: inputs.release_tag != ''
needs: build-aot
uses: ./.github/workflows/package-zip.yml
with:
target: macos
release_tag: ${{ inputs.release_tag }}
isAot: true
secrets: inherit
dmg:
name: package and release macOS dmg
if: inputs.release_tag != ''
@@ -28,3 +28,19 @@ jobs:
target: windows-desktop
release_tag: ${{ inputs.release_tag }}
secrets: inherit
build-aot:
uses: ./.github/workflows/build.yml
with:
target: windows
isAot: true
release-aot-zip:
if: inputs.release_tag != ''
needs: build-aot
uses: ./.github/workflows/package-zip.yml
with:
target: windows-desktop
release_tag: ${{ inputs.release_tag }}
isAot: true
secrets: inherit
+54 -10
View File
@@ -10,6 +10,10 @@ on:
required: false
type: string
default: './v2rayN.Desktop/v2rayN.Desktop.csproj'
isAot:
required: false
type: boolean
default: false
jobs:
build:
@@ -27,7 +31,6 @@ jobs:
)
}}
env:
Output: "${{ github.workspace }}/${{ matrix.arch }}"
RID: |-
${{
case(
@@ -41,6 +44,7 @@ jobs:
${{
case(
inputs.target == 'windows', '-p:EnableWindowsTargeting=true',
inputs.target == 'linux', (matrix.arch == 'arm64' && '-p:LinkerFlavor=lld -p:ObjCopyName=llvm-objcopy' || ''),
''
)
}}
@@ -71,25 +75,65 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.1xx
- name: Build v2rayN
dotnet-version: 10.0.x
- name: Install dependencies
shell: bash
working-directory: ./v2rayN
run: dotnet publish $Project -c Release -r $RID -p:SelfContained=true $ExtOpt -o $Output
if: inputs.target == 'linux' && inputs.isAot
run: |
sudo apt-get update
sudo apt-get install -y clang zlib1g-dev build-essential
- name: Install dependencies cross-arch
shell: bash
if: inputs.target == 'linux' && inputs.isAot && matrix.arch == 'arm64'
run: |
sudo apt-get update
sudo apt-get install -y lld llvm binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross
- name: Set AOT Environment Variable
shell: bash
run: |
if [ "${{ inputs.isAot }}" = "true" ]; then
echo "DOTNET_AOT_ARG=-p:IsAot=true" >> $GITHUB_ENV
else
echo "DOTNET_AOT_ARG=-p:IsAot=false" >> $GITHUB_ENV
fi
- name: Set OUTPUT Environment Variable
shell: bash
run: |
RAW_PATH="${{ github.workspace }}/${{ matrix.arch }}${{ inputs.isAot && '-aot' || '' }}"
echo "OUTPUT=${RAW_PATH//\\//}" >> "$GITHUB_ENV"
- name: Build AmazTool
shell: bash
working-directory: ./v2rayN
run: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r $RID -p:SelfContained=true -p:PublishTrimmed=true $ExtOpt -o $Output
run: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r $RID -p:SelfContained=true -p:PublishTrimmed=true $ExtOpt ${{ env.DOTNET_AOT_ARG }} -o ${{ env.OUTPUT }}
- name: Build v2rayN WPF
shell: bash
if: inputs.project == './v2rayN/v2rayN.csproj'
working-directory: ./v2rayN
run: dotnet publish $Project -c Release -r $RID -p:SelfContained=true $ExtOpt -o ${{ env.OUTPUT }}
- name: Build v2rayN Avalonia
shell: bash
if: inputs.project == './v2rayN.Desktop/v2rayN.Desktop.csproj'
working-directory: ./v2rayN
run: dotnet publish $Project -c Release -r $RID -p:SelfContained=true $ExtOpt ${{ env.DOTNET_AOT_ARG }} -o ${{ env.OUTPUT }}
- name: Remove Debug Symbols
shell: bash
run: |
find ${{ matrix.arch }} -type f -name '*.pdb' -delete
find ${{ env.OUTPUT }} -type f -name '*.pdb' -delete
find ${{ env.OUTPUT }} -type f -name '*.debug' -delete
find ${{ env.OUTPUT }} -type f -name '*.dbg' -delete
find ${{ env.OUTPUT }} -type f -name '*.sym' -delete
find ${{ env.OUTPUT }} -name '*.dSYM' -exec rm -rf {} +
- name: Upload build artifacts
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.arch }}
path: ${{ matrix.arch }}
name: ${{ matrix.arch }}${{ inputs.isAot && '-aot' || '' }}
path: ${{ env.OUTPUT }}
+52 -13
View File
@@ -9,6 +9,10 @@ on:
target: # windows linux macos windows-desktop
required: true
type: string
isAot:
required: false
type: boolean
default: false
secrets:
GPG_PRIVATE_KEY:
required: true
@@ -43,27 +47,62 @@ jobs:
- name: Checkout
uses: actions/checkout@v7
- name: Set package variables
id: package-vars
shell: bash
env:
INPUT_TARGET: ${{ inputs.target }}
INPUT_IS_AOT: ${{ inputs.isAot }}
run: |
set -euo pipefail
package_dir="v2rayN-${Target}-${Arch}"
package_file="${package_dir}.zip"
build_artifact_name="${{ matrix.arch }}"
rename_suffix=""
if [[ "$INPUT_IS_AOT" == "true" ]]; then
build_artifact_name="${build_artifact_name}-aot"
rename_suffix="-aot"
fi
if [[ "$INPUT_TARGET" == "windows-desktop" ]]; then
rename_suffix="-desktop${rename_suffix}"
fi
final_package_file="${package_dir}${rename_suffix}.zip"
{
echo "build_artifact_name=${build_artifact_name}"
echo "package_dir=${package_dir}"
echo "package_file=${package_file}"
echo "final_package_file=${final_package_file}"
echo "rename_suffix=${rename_suffix}"
} >> "$GITHUB_OUTPUT"
- name: Restore build artifacts
uses: actions/download-artifact@v8
with:
name: ${{ matrix.arch }}
path: v2rayN-${{ env.Target }}-${{ env.Arch }}
name: ${{ steps.package-vars.outputs.build_artifact_name }}
path: ${{ steps.package-vars.outputs.package_dir }}
- name: Get v2rayN-core-bin
run: wget -nv -O v2rayN-$Target-$Arch.zip "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/v2rayN-$Target-$Arch.zip"
run: |
wget -nv -O "${{ steps.package-vars.outputs.package_file }}" "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/v2rayN-$Target-$Arch.zip"
- name: Package zip archive
run: 7z a -tZip v2rayN-$Target-$Arch.zip v2rayN-$Target-$Arch -mx1
shell: bash
run: |
7z a -tZip "${{ steps.package-vars.outputs.package_file }}" "${{ steps.package-vars.outputs.package_dir }}" -mx1
- name: Rename package archive
if: steps.package-vars.outputs.rename_suffix != ''
shell: bash
run: |
mv "${{ steps.package-vars.outputs.package_file }}" \
"${{ steps.package-vars.outputs.package_dir }}${{ steps.package-vars.outputs.rename_suffix }}.zip"
- name: Rename windows-desktop
if: inputs.target == 'windows-desktop'
run: mv "v2rayN-$Target-$Arch.zip" "v2rayN-$Target-$Arch-desktop.zip"
- name: Upload zip artifact
uses: actions/upload-artifact@v7
with:
name: release-${{ inputs.target }}-${{ matrix.arch }}
path: v2rayN*.zip
name: release-${{ inputs.target }}-${{ matrix.arch }}${{ inputs.isAot && '-aot' || '' }}
path: ${{ steps.package-vars.outputs.final_package_file }}
if-no-files-found: error
release:
@@ -75,7 +114,7 @@ jobs:
uses: ./.github/workflows/upload-sign.yml
with:
release_tag: ${{ inputs.release_tag }}
artifact_name: release-${{ inputs.target }}-${{ matrix.arch }}
artifact_name: release-${{ inputs.target }}-${{ matrix.arch }}${{ inputs.isAot && '-aot' || '' }}
file_pattern: '*.zip'
secrets:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
+20 -5
View File
@@ -5,16 +5,31 @@
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Update="Resx\Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<Compile Update="Resx\Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
<EmbeddedResource Update="Resx\Resource.resx">
<SubType>Designer</SubType>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resx\ResUI.zh-Hans.resx">
<SubType>Designer</SubType>
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resx\ResUI.zh-Hant.resx">
<SubType>Designer</SubType>
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
<Target Name="RemoveAotSatelliteOutputs" AfterTargets="ComputeFilesToPublish" Condition="'$(PublishAot)' == 'true'">
<ItemGroup>
<ResolvedFileToPublish Remove="@(ResolvedFileToPublish)"
Condition="$([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Contains('resources.dll'))" />
</ItemGroup>
</Target>
</Project>
+1 -1
View File
@@ -19,7 +19,7 @@ namespace AmazTool.Resx {
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resource {
+10
View File
@@ -29,4 +29,14 @@
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
</PropertyGroup>
<PropertyGroup>
<IsAot Condition="'$(IsAot)' == ''">false</IsAot>
</PropertyGroup>
<PropertyGroup Condition="'$(IsAot)' == 'true'">
<PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
<DefineConstants>$(DefineConstants);V2RAYN_IS_AOT</DefineConstants>
</PropertyGroup>
</Project>
+1 -2
View File
@@ -25,8 +25,7 @@
<PackageVersion Include="NLog" Version="6.2.1" />
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.4.1" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="TUnit" Version="1.75.0" />
<PackageVersion Include="TUnit" Version="1.73.19" />
<PackageVersion Include="TUnit.Assertions.Should" Version="1.72.16-beta" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
@@ -4,6 +4,7 @@
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
+121 -10
View File
@@ -7,40 +7,45 @@ public class JsonUtils
private static readonly JsonSerializerOptions _defaultDeserializeOptions = new()
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip
ReadCommentHandling = JsonCommentHandling.Skip,
TypeInfoResolver = AppJsonContext.Default,
};
private static readonly JsonSerializerOptions _defaultSerializeOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = AppJsonContext.Default,
};
private static readonly JsonSerializerOptions _defaultSerializeNoIndentedOptions = new()
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = AppJsonContext.Default,
};
private static readonly JsonSerializerOptions _nullValueSerializeOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = AppJsonContext.Default,
};
private static readonly JsonSerializerOptions _nullValueSerializeNoIndentedOptions = new()
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = AppJsonContext.Default,
};
private static readonly JsonDocumentOptions _defaultDocumentOptions = new()
{
CommentHandling = JsonCommentHandling.Skip
CommentHandling = JsonCommentHandling.Skip,
};
/// <summary>
@@ -109,7 +114,7 @@ public class JsonUtils
/// <param name="indented"></param>
/// <param name="nullValue"></param>
/// <returns></returns>
public static string Serialize(object? obj, bool indented = true, bool nullValue = false)
public static string Serialize<T>(T? obj, bool indented = true, bool nullValue = false)
{
var result = string.Empty;
try
@@ -140,7 +145,7 @@ public class JsonUtils
/// <param name="obj"></param>
/// <param name="options"></param>
/// <returns></returns>
public static string Serialize(object? obj, JsonSerializerOptions? options)
public static string SerializeCamelCase<T>(T? obj)
{
var result = string.Empty;
try
@@ -149,7 +154,14 @@ public class JsonUtils
{
return result;
}
result = JsonSerializer.Serialize(obj, options ?? _defaultSerializeOptions);
var options = new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
TypeInfoResolver = AppJsonContext.Default,
};
result = JsonSerializer.Serialize(obj, options);
}
catch (Exception ex)
{
@@ -162,9 +174,108 @@ public class JsonUtils
/// SerializeToNode
/// </summary>
/// <param name="obj"></param>
/// <param name="nullValue"></param>
/// <returns></returns>
public static JsonNode? SerializeToNode(object? obj, JsonSerializerOptions? options = null)
public static JsonNode? SerializeToNode<T>(T? obj, bool nullValue = false)
{
var options = nullValue ? _nullValueSerializeOptions : _defaultSerializeOptions;
return JsonSerializer.SerializeToNode(obj, options);
}
public static JsonArray? SerializeToArray(IEnumerable<string>? values)
{
if (values is null)
{
return null;
}
var array = new JsonArray();
foreach (var value in values)
{
var valueNode = JsonSerializer.SerializeToNode(value, _defaultSerializeOptions);
array.Add(valueNode);
}
return array;
}
}
[JsonSerializable(typeof(Config))]
[JsonSerializable(typeof(RulesItem))]
[JsonSerializable(typeof(ProfileItem))]
[JsonSerializable(typeof(List<RulesItem>))]
[JsonSerializable(typeof(List<ProfileItem>))]
[JsonSerializable(typeof(ProfileItemModel))]
[JsonSerializable(typeof(List<ProfileItemModel>))]
[JsonSerializable(typeof(RoutingTemplate))]
[JsonSerializable(typeof(DNSItem))]
[JsonSerializable(typeof(SimpleDNSItem))]
[JsonSerializable(typeof(IPAPIInfo))]
[JsonSerializable(typeof(SsSIP008))]
[JsonSerializable(typeof(List<SsServer>))]
[JsonSerializable(typeof(SsServer))]
[JsonSerializable(typeof(VmessQRCode))]
[JsonSerializable(typeof(ClashProxies))]
[JsonSerializable(typeof(ClashProviders))]
[JsonSerializable(typeof(ClashConnections))]
[JsonSerializable(typeof(ProtocolExtraItem))]
[JsonSerializable(typeof(TransportExtraItem))]
[JsonSerializable(typeof(TrafficItem))]
[JsonSerializable(typeof(V2rayMetricsVars))]
[JsonSerializable(typeof(V2rayMetricsVarsLink))]
[JsonSerializable(typeof(GitHubRelease))]
[JsonSerializable(typeof(List<GitHubRelease>))]
[JsonSerializable(typeof(ServerStatItem))]
[JsonSerializable(typeof(WebDavItem))]
[JsonSerializable(typeof(KeyEventItem))]
[JsonSerializable(typeof(SubItem))]
[JsonSerializable(typeof(SingboxConfig))]
[JsonSerializable(typeof(Dns4Sbox))]
[JsonSerializable(typeof(Inbound4Sbox))]
[JsonSerializable(typeof(List<Inbound4Sbox>))]
[JsonSerializable(typeof(Endpoints4Sbox))]
[JsonSerializable(typeof(List<Endpoints4Sbox>))]
[JsonSerializable(typeof(Outbound4Sbox))]
[JsonSerializable(typeof(List<Outbound4Sbox>))]
[JsonSerializable(typeof(Rule4Sbox))]
[JsonSerializable(typeof(Ruleset4Sbox))]
[JsonSerializable(typeof(List<Rule4Sbox>))]
[JsonSerializable(typeof(List<Ruleset4Sbox>))]
[JsonSerializable(typeof(V2rayConfig))]
[JsonSerializable(typeof(Dns4Ray))]
[JsonSerializable(typeof(DnsServer4Ray))]
[JsonSerializable(typeof(Inbounds4Ray))]
[JsonSerializable(typeof(List<Inbounds4Ray>))]
[JsonSerializable(typeof(Outbounds4Ray))]
[JsonSerializable(typeof(List<Outbounds4Ray>))]
[JsonSerializable(typeof(RulesItem4Ray))]
[JsonSerializable(typeof(List<RulesItem4Ray>))]
[JsonSerializable(typeof(Finalmask4Ray))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(long))]
[JsonSerializable(typeof(double))]
[JsonSerializable(typeof(decimal))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(Guid))]
[JsonSerializable(typeof(DateTime))]
[JsonSerializable(typeof(DateTimeOffset))]
[JsonSerializable(typeof(TimeSpan))]
[JsonSerializable(typeof(int?))]
[JsonSerializable(typeof(long?))]
[JsonSerializable(typeof(double?))]
[JsonSerializable(typeof(bool?))]
[JsonSerializable(typeof(DateTime?))]
[JsonSerializable(typeof(Guid?))]
[JsonSerializable(typeof(string[]))]
[JsonSerializable(typeof(int[]))]
[JsonSerializable(typeof(byte[]))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(List<int>))]
[JsonSerializable(typeof(Dictionary<string, string>))]
[JsonSerializable(typeof(Dictionary<string, object>))]
[JsonSerializable(typeof(object))]
[JsonSerializable(typeof(JsonElement))]
[JsonSerializable(typeof(JsonDocument))]
[JsonSerializable(typeof(JsonNode))]
[JsonSerializable(typeof(JsonObject))]
[JsonSerializable(typeof(JsonArray))]
internal partial class AppJsonContext : JsonSerializerContext;
+2 -3
View File
@@ -348,10 +348,9 @@ public class Utils
.ToList());
}
public static List<string> GetEnumNames<TEnum>() where TEnum : Enum
public static List<string> GetEnumNames<TEnum>() where TEnum : struct, Enum
{
return Enum.GetValues(typeof(TEnum))
.Cast<TEnum>()
return Enum.GetValues<TEnum>()
.Select(e => e.ToString())
.ToList();
}
+38 -21
View File
@@ -1,6 +1,5 @@
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.RepresentationModel;
namespace ServiceLib.Common;
@@ -13,45 +12,57 @@ public class YamlUtils
/// <summary>
/// Deserialize
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="str"></param>
/// <returns></returns>
public static T FromYaml<T>(string str)
public static YamlMappingNode FromYaml(string str)
{
var deserializer = new DeserializerBuilder()
.WithNamingConvention(PascalCaseNamingConvention.Instance)
.Build();
//var deserializer = new DeserializerBuilder()
// .WithNamingConvention(PascalCaseNamingConvention.Instance)
// .Build();
//try
//{
// var obj = deserializer.Deserialize<T>(str);
// return obj;
//}
//catch (Exception ex)
//{
// Logging.SaveLog(_tag, ex);
// return deserializer.Deserialize<T>("");
//}
try
{
var obj = deserializer.Deserialize<T>(str);
return obj;
var yaml = new YamlStream();
using var reader = new StringReader(str);
yaml.Load(reader);
var rootNode = (YamlMappingNode)yaml.Documents[0].RootNode;
return rootNode;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return deserializer.Deserialize<T>("");
return new YamlMappingNode();
}
}
/// <summary>
/// Serialize
/// </summary>
/// <param name="obj"></param>
/// <param name="node"></param>
/// <returns></returns>
public static string ToYaml(object? obj)
public static string ToYaml(YamlMappingNode? node)
{
var result = string.Empty;
if (obj == null)
if (node == null)
{
return result;
}
var serializer = new SerializerBuilder()
.WithNamingConvention(HyphenatedNamingConvention.Instance)
.Build();
try
{
result = serializer.Serialize(obj);
var doc = new YamlDocument(node);
var stream = new YamlStream(doc);
using var writer = new StringWriter();
stream.Save(writer, assignAnchors: false);
result = writer.ToString();
}
catch (Exception ex)
{
@@ -64,9 +75,15 @@ public class YamlUtils
{
try
{
var mergingParser = new MergingParser(new Parser(new StringReader(str)));
var obj = new DeserializerBuilder().Build().Deserialize(mergingParser);
return ToYaml(obj);
using var reader = new StringReader(str);
var parser = new Parser(reader);
var mergingParser = new MergingParser(parser);
var yamlStream = new YamlStream();
yamlStream.Load(mergingParser);
using var writer = new StringWriter();
yamlStream.Save(writer, assignAnchors: false);
return writer.ToString();
}
catch (Exception ex)
{
+1
View File
@@ -42,6 +42,7 @@ public class Global
public const string SingboxFakeIPFilterFileName = NamespaceSample + "singbox_fakeip_filter";
public const string ChromeRootCertFileName = NamespaceSample + "chrome_roots_pem";
public const string MozillaRootCertFileName = NamespaceSample + "mozilla_roots_pem";
public const string TaskXmlTemplateFileName = NamespaceSample + "TaskXmlTemplate";
public const string ChromeRootProvider = "chrome";
public const string MozillaRootProvider = "mozilla";
@@ -87,6 +87,7 @@ public static class AutoStartupHandler
[SupportedOSPlatform("windows")]
public static void AutoStartTaskService(string taskName, string fileName, string description)
{
#if false
if (taskName.IsNullOrEmpty())
{
return;
@@ -117,6 +118,78 @@ public static class AutoStartupHandler
task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName)));
taskService.RootFolder.RegisterTaskDefinition(taskName, task);
#endif
if (string.IsNullOrEmpty(taskName))
{
return;
}
RunSchtasks($"/Delete /TN \"{taskName}\" /F");
if (string.IsNullOrEmpty(fileName))
{
return;
}
var workingDir = Path.GetDirectoryName(fileName) ?? string.Empty;
var taskXml = EmbedUtils.GetEmbedText(Global.TaskXmlTemplateFileName);
taskXml = taskXml
.Replace("{{DESCRIPTION}}", System.Security.SecurityElement.Escape(description ?? ""))
.Replace("{{EXEC_PATH}}", System.Security.SecurityElement.Escape(fileName ?? ""))
.Replace("{{ARGUMENTS}}", string.Empty)
.Replace("{{WORKING_DIRECTORY}}", System.Security.SecurityElement.Escape(workingDir ?? ""));
var taskXmlFileName = Utils.GetTempPath($"{Utils.GetGuid(false)}.xml");
File.WriteAllText(taskXmlFileName, taskXml);
RunSchtasks(
"/Create",
"/TN", taskName,
"/XML", taskXmlFileName,
"/F"
);
}
private static bool RunSchtasks(params string[] arguments)
{
var psi = new ProcessStartInfo
{
FileName = "schtasks.exe",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
foreach (var argument in arguments)
{
psi.ArgumentList.Add(argument);
}
using var process = Process.Start(psi);
if (process == null)
{
return false;
}
process.WaitForExit();
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(output))
{
Logging.SaveLog($"RunSchtasks output: {output}");
}
if (!string.IsNullOrEmpty(error))
{
Logging.SaveLog($"RunSchtasks error: {error}");
}
return process.ExitCode == 0;
}
private static string GetAutoRunNameWindows()
+4 -24
View File
@@ -80,12 +80,7 @@ public class BaseFmt
{
var node = JsonUtils.ParseJson(item.Finalmask);
var finalmask = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
? JsonUtils.Serialize(node, indented: false, nullValue: true)
: item.Finalmask;
dicQuery.Add("fm", Utils.UrlEncode(finalmask));
}
@@ -154,12 +149,7 @@ public class BaseFmt
{
var node = JsonUtils.ParseJson(transport.XhttpExtra);
var extra = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
? JsonUtils.Serialize(node, indented: false, nullValue: true)
: transport.XhttpExtra;
dicQuery.Add("extra", UrlEncodeSafe(extra));
}
@@ -215,12 +205,7 @@ public class BaseFmt
{
var node = JsonUtils.ParseJson(finalmaskDecoded);
item.Finalmask = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
? JsonUtils.Serialize(node, indented: true, nullValue: true)
: finalmaskDecoded;
}
else
@@ -278,12 +263,7 @@ public class BaseFmt
var node = JsonUtils.ParseJson(xhttpExtra);
if (node != null)
{
xhttpExtra = JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
xhttpExtra = JsonUtils.Serialize(node, indented: true, nullValue: true);
}
}
+1 -6
View File
@@ -179,12 +179,7 @@ public class InnerFmt : BaseFmt
if (jsonObj.TryGetPropertyValue("CustomOutboundObj", out var customOutboundNode)
&& customOutboundNode is JsonObject customOutboundObj)
{
var customOutboundContent = JsonUtils.Serialize(customOutboundObj, new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
var customOutboundContent = JsonUtils.Serialize(customOutboundObj, indented: true, nullValue: true);
customOutboundFilePath = WriteAllText(customOutboundContent);
jsonObj.Remove("CustomOutboundObj");
}
+2 -12
View File
@@ -29,12 +29,7 @@ public class WireguardFmt : BaseFmt
{
var node = JsonUtils.ParseJson(finalmaskDecoded);
item.Finalmask = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
? JsonUtils.Serialize(node, indented: true, nullValue: true)
: finalmaskDecoded;
}
else
@@ -98,12 +93,7 @@ public class WireguardFmt : BaseFmt
{
var node = JsonUtils.ParseJson(item.Finalmask);
var finalmask = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
? JsonUtils.Serialize(node, indented: false, nullValue: true)
: item.Finalmask;
dicQuery.Add("fm", Utils.UrlEncode(finalmask));
}
@@ -133,7 +133,7 @@ public static class ProxySettingWindows
list.dwOptionCount = options.Length;
list.dwOptionError = 0;
var optSize = Marshal.SizeOf(typeof(InternetConnectionOption));
var optSize = Marshal.SizeOf<InternetConnectionOption>();
// make a pointer out of all that ...
var optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length); // !! remember to deallocate memory 4
// copy the array over into that spot in memory ...
@@ -207,17 +207,17 @@ public static class ProxySettingWindows
var entries = 0;
// attempt to query with 1 entry buffer
var rasEntryNames = new RASENTRYNAME[1];
var bufferSize = Marshal.SizeOf(typeof(RASENTRYNAME));
rasEntryNames[0].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME));
var bufferSize = Marshal.SizeOf<RASENTRYNAME>();
rasEntryNames[0].dwSize = Marshal.SizeOf<RASENTRYNAME>();
var result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries);
// increase buffer if the buffer is not large enough
if (result == (uint)ErrorCode.ERROR_BUFFER_TOO_SMALL)
{
rasEntryNames = new RASENTRYNAME[bufferSize / Marshal.SizeOf(typeof(RASENTRYNAME))];
rasEntryNames = new RASENTRYNAME[bufferSize / Marshal.SizeOf<RASENTRYNAME>()];
for (var i = 0; i < rasEntryNames.Length; i++)
{
rasEntryNames[i].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME));
rasEntryNames[i].dwSize = Marshal.SizeOf<RASENTRYNAME>();
}
result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries);
@@ -258,7 +258,7 @@ public static class ProxySettingWindows
static InternetConnectionOption()
{
Size = Marshal.SizeOf(typeof(InternetConnectionOption));
Size = Marshal.SizeOf<InternetConnectionOption>();
}
// Nested Types
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Configs;
[Serializable]
public class Config
{
#region property
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Configs;
[Serializable]
public class CoreBasicItem
{
public bool LogEnabled { get; set; }
@@ -22,7 +21,6 @@ public class CoreBasicItem
public bool EnableCacheFile4Sbox { get; set; } = true;
}
[Serializable]
public class InItem
{
public int LocalPort { get; set; }
@@ -38,7 +36,6 @@ public class InItem
public bool SecondLocalPortEnabled { get; set; }
}
[Serializable]
public class KcpItem
{
public int Mtu { get; set; }
@@ -54,7 +51,6 @@ public class KcpItem
public int MaxSendingWindow { get; set; }
}
[Serializable]
public class GrpcItem
{
public int? IdleTimeout { get; set; }
@@ -63,7 +59,6 @@ public class GrpcItem
public int? InitialWindowsSize { get; set; }
}
[Serializable]
public class GUIItem
{
public bool AutoRun { get; set; }
@@ -77,14 +72,12 @@ public class GUIItem
public string? RootCertProvider { get; set; }
}
[Serializable]
public class MsgUIItem
{
public string? MainMsgFilter { get; set; }
public bool? AutoRefresh { get; set; }
}
[Serializable]
public class UIItem
{
public bool EnableAutoAdjustMainLvColWidth { get; set; }
@@ -106,7 +99,6 @@ public class UIItem
public bool HideColumnIpInfo { get; set; }
}
[Serializable]
public class ConstItem
{
public string? SubConvertUrl { get; set; }
@@ -115,7 +107,6 @@ public class ConstItem
public string? RouteRulesTemplateSourceUrl { get; set; }
}
[Serializable]
public class KeyEventItem
{
public EGlobalHotkey EGlobalHotkey { get; set; }
@@ -129,7 +120,6 @@ public class KeyEventItem
public int? KeyCode { get; set; }
}
[Serializable]
public class CoreTypeItem
{
public EConfigType ConfigType { get; set; }
@@ -137,7 +127,6 @@ public class CoreTypeItem
public ECoreType CoreType { get; set; }
}
[Serializable]
public class TunModeItem
{
public bool EnableTun { get; set; }
@@ -153,7 +142,6 @@ public class TunModeItem
public string IPv6Address { get; set; }
}
[Serializable]
public class SpeedTestItem
{
public int SpeedTestTimeout { get; set; }
@@ -166,7 +154,6 @@ public class SpeedTestItem
public int? SpeedTestDelayInterval { get; set; }
}
[Serializable]
public class RoutingBasicItem
{
public string DomainStrategy { get; set; }
@@ -174,7 +161,6 @@ public class RoutingBasicItem
public string RoutingIndexId { get; set; }
}
[Serializable]
public class ColumnItem
{
public string Name { get; set; }
@@ -182,7 +168,6 @@ public class ColumnItem
public int Index { get; set; }
}
[Serializable]
public class Mux4RayItem
{
public int? Concurrency { get; set; }
@@ -190,7 +175,6 @@ public class Mux4RayItem
public string? XudpProxyUDP443 { get; set; }
}
[Serializable]
public class Mux4SboxItem
{
public string Protocol { get; set; }
@@ -198,7 +182,6 @@ public class Mux4SboxItem
public bool? Padding { get; set; }
}
[Serializable]
public class HysteriaItem
{
public int UpMbps { get; set; }
@@ -206,7 +189,6 @@ public class HysteriaItem
public int HopInterval { get; set; } = Global.Hysteria2DefaultHopInt;
}
[Serializable]
public class ClashUIItem
{
public bool EnableIPv6 { get; set; }
@@ -219,7 +201,6 @@ public class ClashUIItem
public List<ColumnItem> ConnectionsColumnItem { get; set; }
}
[Serializable]
public class SystemProxyItem
{
public ESysProxyType SysProxyType { get; set; }
@@ -230,7 +211,6 @@ public class SystemProxyItem
public string? CustomSystemProxyScriptPath { get; set; }
}
[Serializable]
public class WebDavItem
{
public string? Url { get; set; }
@@ -239,7 +219,6 @@ public class WebDavItem
public string? DirName { get; set; }
}
[Serializable]
public class CheckUpdateItem
{
public bool UpdateViaProxy { get; set; } = true;
@@ -247,7 +226,6 @@ public class CheckUpdateItem
public List<string>? CheckPreReleaseCoreTypes { get; set; }
}
[Serializable]
public class Fragment4RayItem
{
public string? Packets { get; set; }
@@ -262,7 +240,6 @@ public class Fragment4RayItem
// migration end
}
[Serializable]
public class WindowSizeItem
{
public string TypeName { get; set; }
@@ -270,7 +247,6 @@ public class WindowSizeItem
public int Height { get; set; }
}
[Serializable]
public class SimpleDNSItem
{
public bool? UseSystemHosts { get; set; }
@@ -293,7 +269,6 @@ public class SimpleDNSItem
public bool? EnableHappyEyeballs { get; set; }
}
[Serializable]
public class HappyEyeballs4RayItem
{
public int? TryDelayMs { get; set; }
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.CoreConfigs;
[Serializable]
public class CoreInfo
{
public ECoreType CoreType { get; set; }
@@ -44,7 +44,6 @@ public class Route4Sbox
public string? final { get; set; }
}
[Serializable]
public class Rule4Sbox
{
public string? outbound { get; set; }
@@ -103,7 +102,6 @@ public class Rule4Sbox
public bool? speculative { get; set; }
}
[Serializable]
public class Inbound4Sbox
{
public string type { get; set; }
@@ -232,7 +230,7 @@ public class Reality4Sbox
public class Transport4Sbox
{
public string? type { get; set; }
public object? host { get; set; }
public JsonNode? host { get; set; }
public string? path { get; set; }
public Headers4Sbox? headers { get; set; }
@@ -3,7 +3,7 @@ namespace ServiceLib.Models.CoreConfigs;
public class V2rayConfig
{
public Log4Ray log { get; set; }
public object dns { get; set; }
public JsonObject? dns { get; set; }
public FakeDns4Ray? fakedns { get; set; }
public List<Inbounds4Ray> inbounds { get; set; }
public List<Outbounds4Ray> outbounds { get; set; }
@@ -130,7 +130,7 @@ public class Outboundsettings4Ray
public string? secretKey { get; set; }
public object? address { get; set; }
public JsonNode? address { get; set; }
public int? port { get; set; }
@@ -142,7 +142,7 @@ public class Outboundsettings4Ray
public string? email { get; set; }
public object? headers { get; set; }
public JsonNode? headers { get; set; }
public List<WireguardPeer4Ray>? peers { get; set; }
@@ -199,8 +199,8 @@ public class Response4Ray
public class Dns4Ray
{
public Dictionary<string, object>? hosts { get; set; }
public List<object> servers { get; set; }
public Dictionary<string, JsonNode>? hosts { get; set; }
public List<JsonNode> servers { get; set; }
public bool? serveStale { get; set; }
public bool? enableParallelQuery { get; set; }
public string? queryStrategy { get; set; }
@@ -226,7 +226,6 @@ public class Routing4Ray
public List<BalancersItem4Ray>? balancers { get; set; }
}
[Serializable]
public class RulesItem4Ray
{
public string? type { get; set; }
@@ -321,7 +320,7 @@ public class StreamSettings4Ray
public HysteriaSettings4Ray? hysteriaSettings { get; set; }
public MasqueSettings4Ray? masqueSettings { get; set; }
public object? finalmask { get; set; }
public JsonObject? finalmask { get; set; }
public Sockopt4Ray? sockopt { get; set; }
}
@@ -363,9 +362,9 @@ public class Header4Ray
{
public string type { get; set; }
public object request { get; set; }
public JsonObject request { get; set; }
public object response { get; set; }
public JsonObject response { get; set; }
}
public class KcpSettings4Ray
@@ -411,7 +410,7 @@ public class XhttpSettings4Ray
public string? path { get; set; }
public string? host { get; set; }
public string? mode { get; set; }
public object? extra { get; set; }
public JsonObject? extra { get; set; }
}
public class HttpSettings4Ray
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Dto;
[Serializable]
public partial class ClashProxyModel : ReactiveObject
{
public string Name { get; set; }
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Dto;
[Serializable]
public partial class ProfileItemModel : ReactiveObject
{
public bool IsActive { get; set; }
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Dto;
[Serializable]
public class RoutingItemModel : RoutingItem
{
}
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Dto;
[Serializable]
public class RoutingTemplate
{
public string Version { get; set; }
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Dto;
[Serializable]
public class RulesItemModel : RulesItem
{
public string InboundTags { get; set; }
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Dto;
[Serializable]
public class ServerSpeedItem : ServerStatItem
{
public long ProxyUp { get; set; }
@@ -12,7 +11,6 @@ public class ServerSpeedItem : ServerStatItem
public long DirectDown { get; set; }
}
[Serializable]
public class TrafficItem
{
public ulong Up { get; set; }
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Dto;
[Serializable]
public class ServerTestItem
{
public string? IndexId { get; set; }
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Dto;
[Serializable]
public class SpeedTestResult
{
public string? IndexId { get; set; }
-1
View File
@@ -5,7 +5,6 @@ public class SsSIP008
public List<SsServer>? servers { get; set; }
}
[Serializable]
public class SsServer
{
public string? remarks { get; set; }
@@ -3,7 +3,6 @@ namespace ServiceLib.Models.Dto;
/// <summary>
/// https://github.com/2dust/v2rayN/wiki/
/// </summary>
[Serializable]
public class VmessQRCode
{
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Entities;
[Serializable]
public class DNSItem
{
[PrimaryKey]
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Entities;
[Serializable]
public class FullConfigTemplateItem
{
[PrimaryKey]
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Entities;
[Serializable]
public class ProfileExItem
{
[PrimaryKey]
@@ -1,7 +1,6 @@
namespace ServiceLib.Models.Entities;
[Obsolete("Use ProtocolExtraItem instead.")]
[Serializable]
public class ProfileGroupItem
{
[PrimaryKey]
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Entities;
[Serializable]
public class ProfileItem
{
private ProtocolExtraItem? _protocolExtraCache;
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Entities;
[Serializable]
public class RoutingItem
{
[PrimaryKey]
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Entities;
[Serializable]
public class RulesItem
{
public string Id { get; set; }
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Entities;
[Serializable]
public class ServerStatItem
{
[PrimaryKey]
@@ -1,6 +1,5 @@
namespace ServiceLib.Models.Entities;
[Serializable]
public class SubItem
{
[PrimaryKey]
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>{{DESCRIPTION}}</Description>
</RegistrationInfo>
<Triggers>
<LogonTrigger>
<Enabled>true</Enabled>
</LogonTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<LogonType>InteractiveToken</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Actions Context="Author">
<Exec>
<Command>{{EXEC_PATH}}</Command>
<Arguments>{{ARGUMENTS}}</Arguments>
<WorkingDirectory>{{WORKING_DIRECTORY}}</WorkingDirectory>
</Exec>
</Actions>
</Task>
+2 -2
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
@@ -18,7 +18,6 @@
<PackageReference Include="QRCoder" />
<PackageReference Include="CliWrap" />
<PackageReference Include="ZXing.Net.Bindings.SkiaSharp" />
<PackageReference Include="TaskScheduler" />
</ItemGroup>
<ItemGroup>
@@ -45,6 +44,7 @@
<EmbeddedResource Include="Sample\SampleTunRules" />
<EmbeddedResource Include="Sample\SingboxSampleClientConfig" />
<EmbeddedResource Include="Sample\SingboxSampleOutbound" />
<EmbeddedResource Include="Sample\TaskXmlTemplate" />
<EmbeddedResource Include="Sample\tun_singbox_dns" />
<EmbeddedResource Include="Sample\tun_singbox_inbound" />
<EmbeddedResource Include="Sample\tun_singbox_rules" />
@@ -61,10 +61,10 @@ public class CoreConfigClashService(Config config, bool isTunEnabled)
//YAML anchors
if (txtFile.Contains("<<:") && txtFile.Contains('*') && txtFile.Contains('&'))
{
txtFile = YamlUtils.PreprocessYaml(txtFile);
txtFile = YamlUtils.PreprocessYaml(txtFile) ?? "";
}
var fileContent = YamlUtils.FromYaml<Dictionary<string, object>>(txtFile);
var fileContent = YamlUtils.FromYaml(txtFile);
if (fileContent == null)
{
ret.Msg = ResUI.FailedConversionConfiguration;
@@ -72,29 +72,38 @@ public class CoreConfigClashService(Config config, bool isTunEnabled)
}
//mixed-port
fileContent["mixed-port"] = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
//fileContent["mixed-port"] = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
fileContent.Children[new YamlScalarNode("mixed-port")] = new YamlScalarNode(AppManager.Instance.GetLocalPort(EInboundProtocol.socks).ToString());
//log-level
fileContent["log-level"] = GetLogLevel(config.CoreBasicItem.Loglevel);
//fileContent["log-level"] = GetLogLevel(config.CoreBasicItem.Loglevel);
fileContent.Children[new YamlScalarNode("log-level")] = new YamlScalarNode(GetLogLevel(config.CoreBasicItem.Loglevel));
//external-controller
fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}";
fileContent.Remove("secret");
//fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}";
fileContent.Children[new YamlScalarNode("external-controller")] = new YamlScalarNode($"{Global.Loopback}:{AppManager.Instance.StatePort2}");
//fileContent.Remove("secret");
fileContent.Children.Remove(new YamlScalarNode("secret"));
//allow-lan
if (config.Inbound.First().AllowLANConn)
{
fileContent["allow-lan"] = "true";
fileContent["bind-address"] = "*";
//fileContent["allow-lan"] = "true";
fileContent.Children[new YamlScalarNode("allow-lan")] = new YamlScalarNode("true");
//fileContent["bind-address"] = "*";
fileContent.Children[new YamlScalarNode("bind-address")] = new YamlScalarNode("*");
}
else
{
fileContent["allow-lan"] = "false";
//fileContent["allow-lan"] = "false";
fileContent.Children[new YamlScalarNode("allow-lan")] = new YamlScalarNode("false");
}
//ipv6
fileContent["ipv6"] = config.ClashUIItem.EnableIPv6;
//fileContent["ipv6"] = config.ClashUIItem.EnableIPv6;
fileContent.Children[new YamlScalarNode("ipv6")] = new YamlScalarNode(config.ClashUIItem.EnableIPv6.ToString().ToLowerInvariant());
//mode
fileContent.TryAdd("mode", nameof(ERuleMode.Rule));
//fileContent.TryAdd("mode", nameof(ERuleMode.Rule));
fileContent.Children.TryAdd(new YamlScalarNode("mode"), new YamlScalarNode(nameof(ERuleMode.Rule)));
//enable tun mode
if (isTunEnabled)
@@ -102,10 +111,11 @@ public class CoreConfigClashService(Config config, bool isTunEnabled)
var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml);
if (tun.IsNotEmpty())
{
var tunContent = YamlUtils.FromYaml<Dictionary<string, object>>(tun);
var tunContent = YamlUtils.FromYaml(tun);
if (tunContent != null)
{
fileContent["tun"] = tunContent["tun"];
//fileContent["tun"] = tunContent["tun"];
fileContent.Children[new YamlScalarNode("tun")] = tunContent.Children[new YamlScalarNode("tun")];
}
}
}
@@ -120,27 +130,27 @@ public class CoreConfigClashService(Config config, bool isTunEnabled)
Logging.SaveLog($"{_tag}-Mixin", ex);
}
// Mihomo parses plain values such as 815458e4 as floats, so quote REALITY short IDs.
var originalRealityShortIds = new List<(Dictionary<object, object> RealityOptions, string ShortId)>();
if (fileContent.GetValueOrDefault("proxies") is List<object> proxies)
{
foreach (var proxy in proxies.OfType<Dictionary<object, object>>())
{
if (proxy.GetValueOrDefault("reality-opts") is Dictionary<object, object> realityOptions
&& realityOptions.GetValueOrDefault("short-id") is string shortId
&& !shortId.StartsWith(tagYamlStr2, StringComparison.Ordinal))
{
originalRealityShortIds.Add((realityOptions, shortId));
realityOptions["short-id"] = new YamlScalarNode(shortId) { Style = ScalarStyle.DoubleQuoted };
}
}
}
//// Mihomo parses plain values such as 815458e4 as floats, so quote REALITY short IDs.
//var originalRealityShortIds = new List<(Dictionary<object, object> RealityOptions, string ShortId)>();
//if (fileContent.GetValueOrDefault("proxies") is List<object> proxies)
//{
// foreach (var proxy in proxies.OfType<Dictionary<object, object>>())
// {
// if (proxy.GetValueOrDefault("reality-opts") is Dictionary<object, object> realityOptions
// && realityOptions.GetValueOrDefault("short-id") is string shortId
// && !shortId.StartsWith(tagYamlStr2, StringComparison.Ordinal))
// {
// originalRealityShortIds.Add((realityOptions, shortId));
// realityOptions["short-id"] = new YamlScalarNode(shortId) { Style = ScalarStyle.DoubleQuoted };
// }
// }
//}
var txtFileNew = YamlUtils.ToYaml(fileContent).Replace(tagYamlStr2, tagYamlStr3);
foreach (var (realityOptions, shortId) in originalRealityShortIds)
{
realityOptions["short-id"] = shortId;
}
//foreach (var (realityOptions, shortId) in originalRealityShortIds)
//{
// realityOptions["short-id"] = shortId;
//}
await File.WriteAllTextAsync(fileName, txtFileNew);
//check again
if (!File.Exists(fileName))
@@ -161,7 +171,7 @@ public class CoreConfigClashService(Config config, bool isTunEnabled)
}
}
private async Task MixinContent(Dictionary<string, object> fileContent, ProfileItem node)
private async Task MixinContent(YamlMappingNode fileContent, ProfileItem node)
{
if (!config.ClashUIItem.EnableMixinContent)
{
@@ -177,80 +187,116 @@ public class CoreConfigClashService(Config config, bool isTunEnabled)
var txtFile = await File.ReadAllTextAsync(Utils.GetConfigPath(Global.ClashMixinConfigFileName));
var mixinContent = YamlUtils.FromYaml<Dictionary<string, object>>(txtFile);
var mixinContent = YamlUtils.FromYaml(txtFile);
if (mixinContent == null)
{
return;
}
foreach (var item in mixinContent)
foreach (var (keyNode, valueNode) in mixinContent.Children)
{
if (!isTunEnabled && item.Key == "tun")
if (keyNode is not YamlScalarNode scalarKey)
{
continue;
}
var key = scalarKey.Value ?? "";
if (!isTunEnabled && key == "tun")
{
continue;
}
if (item.Key.StartsWith("prepend-")
|| item.Key.StartsWith("append-")
|| item.Key.StartsWith("removed-"))
if (key.StartsWith("prepend-")
|| key.StartsWith("append-")
|| key.StartsWith("removed-"))
{
ModifyContentMerge(fileContent, item.Key, item.Value);
ModifyContentMerge(fileContent, key, valueNode);
}
else
{
fileContent[item.Key] = item.Value;
fileContent.Children[keyNode] = valueNode;
}
}
return;
}
private void ModifyContentMerge(Dictionary<string, object> fileContent, string key, object value)
private void ModifyContentMerge(YamlMappingNode fileContent, YamlNode keyNode, YamlNode valueNode)
{
if (keyNode is not YamlScalarNode { Value: string key })
{
return;
}
var blPrepend = false;
var blRemoved = false;
string realKey;
if (key.StartsWith("prepend-"))
{
blPrepend = true;
key = key.Replace("prepend-", "");
realKey = key["prepend-".Length..];
}
else if (key.StartsWith("append-"))
{
blPrepend = false;
key = key.Replace("append-", "");
realKey = key["append-".Length..];
}
else if (key.StartsWith("removed-"))
{
blRemoved = true;
key = key.Replace("removed-", "");
realKey = key["removed-".Length..];
}
else
{
return;
}
if (!blRemoved && fileContent.TryAdd(key, value))
var targetKeyNode = new YamlScalarNode(realKey);
if (!blRemoved && fileContent.Children.TryAdd(targetKeyNode, valueNode))
{
return;
}
if (!fileContent.Children.TryGetValue(targetKeyNode, out var existingNode) ||
existingNode is not YamlSequenceNode lstOri ||
valueNode is not YamlSequenceNode lstValue)
{
return;
}
var lstOri = (List<object>)fileContent[key];
var lstValue = (List<object>)value;
if (blRemoved)
{
foreach (var item in lstValue)
var removePrefixes = lstValue.Children
.OfType<YamlScalarNode>()
.Select(s => s.Value)
.Where(v => !string.IsNullOrEmpty(v))
.ToList();
for (var i = lstOri.Children.Count - 1; i >= 0; i--)
{
lstOri.RemoveAll(t => t.ToString().StartsWith(item.ToString()));
if (lstOri.Children[i] is YamlScalarNode { Value: not null } itemScalar)
{
if (removePrefixes.Any(prefix => itemScalar.Value.StartsWith(prefix!)))
{
lstOri.Children.RemoveAt(i);
}
}
}
return;
}
if (blPrepend)
{
lstValue.Reverse();
lstValue.ForEach(item => lstOri.Insert(0, item));
// Insert in reverse order to maintain original sequence at index 0
for (var i = lstValue.Children.Count - 1; i >= 0; i--)
{
lstOri.Children.Insert(0, lstValue.Children[i]);
}
}
else
{
lstValue.ForEach(lstOri.Add);
foreach (var item in lstValue.Children)
{
lstOri.Children.Add(item);
}
}
}
@@ -525,7 +525,7 @@ public partial class CoreConfigSingboxService
transport.type = nameof(ETransport.http);
transport.host = transportExtra.Host.IsNullOrEmpty()
? null
: Utils.String2List(transportExtra.Host);
: JsonUtils.SerializeToArray(Utils.String2List(transportExtra.Host));
transport.path = transportExtra.Path.NullIfEmpty();
if (!useragentValue.IsNullOrEmpty())
{
@@ -17,14 +17,14 @@ public partial class CoreConfigV2rayService
}
// DNS routing
var dnsObj = JsonUtils.SerializeToNode(_coreConfig.dns);
if (dnsObj == null)
var dnsNode = JsonUtils.SerializeToNode(_coreConfig.dns);
if (dnsNode is not JsonObject dnsObj)
{
return;
}
dnsObj["tag"] = Global.DnsTag;
_coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(dnsObj));
_coreConfig.dns = dnsObj;
_coreConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
@@ -34,7 +34,7 @@ public partial class CoreConfigV2rayService
return;
}
var simpleDnsItem = context.SimpleDnsItem;
var dnsItem = _coreConfig.dns as Dns4Ray ?? new Dns4Ray();
var dnsItem = new Dns4Ray();
var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs;
//Outbound Freedom domainStrategy
@@ -117,7 +117,7 @@ public partial class CoreConfigV2rayService
balancerTag = finalRule.balancerTag,
});
_coreConfig.dns = dnsItem;
_coreConfig.dns = (JsonObject)JsonUtils.SerializeToNode(dnsItem);
}
catch (Exception ex)
{
@@ -317,7 +317,7 @@ public partial class CoreConfigV2rayService
if (!useDirectDns)
{
dnsItem.servers.AddRange(remoteDNSAddress);
dnsItem.servers.AddRange(JsonUtils.SerializeToArray(remoteDNSAddress));
}
else
{
@@ -326,8 +326,7 @@ public partial class CoreConfigV2rayService
var dnsServer = CreateDnsServer(dns, []);
dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}";
dnsServer.skipFallback = false;
dnsItem.servers.Add(JsonUtils.SerializeToNode(dnsServer,
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }));
dnsItem.servers.Add(JsonUtils.SerializeToNode(dnsServer));
}
}
return;
@@ -382,8 +381,7 @@ public partial class CoreConfigV2rayService
{
dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}";
}
var dnsServerNode = JsonUtils.SerializeToNode(dnsServer,
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
var dnsServerNode = JsonUtils.SerializeToNode(dnsServer);
dnsItem.servers.Add(dnsServerNode);
}
}
@@ -396,12 +394,12 @@ public partial class CoreConfigV2rayService
{
return;
}
dnsItem.hosts ??= new Dictionary<string, object>();
dnsItem.hosts ??= new Dictionary<string, JsonNode>();
if (simpleDNSItem.AddCommonHosts == true)
{
dnsItem.hosts = Global.PredefinedHosts.ToDictionary(
kvp => kvp.Key,
kvp => (object)kvp.Value
kvp => (JsonNode)JsonUtils.SerializeToArray(kvp.Value)
);
}
@@ -414,14 +412,14 @@ public partial class CoreConfigV2rayService
{
foreach (var host in systemHosts)
{
normalHost.TryAdd(host.Key, new List<string> { host.Value });
normalHost.TryAdd(host.Key, JsonUtils.SerializeToArray(new List<string> { host.Value }));
}
}
}
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts))
{
dnsItem.hosts[kvp.Key] = kvp.Value;
dnsItem.hosts[kvp.Key] = JsonUtils.SerializeToArray(kvp.Value);
}
}
@@ -449,8 +447,8 @@ public partial class CoreConfigV2rayService
}
}
var obj = JsonUtils.ParseJson(customDNS);
if (obj is null)
var node = JsonUtils.ParseJson(customDNS);
if (node is not JsonObject obj)
{
List<string> servers = [];
var arrDNS = customDNS.Split(',');
@@ -458,8 +456,7 @@ public partial class CoreConfigV2rayService
{
servers.Add(str);
}
obj = JsonUtils.ParseJson("{}");
obj["servers"] = JsonUtils.SerializeToNode(servers);
obj = new JsonObject { ["servers"] = JsonUtils.SerializeToNode(servers) };
}
// Append to dns settings
@@ -203,7 +203,7 @@ public partial class CoreConfigV2rayService
};
var setting = new Outboundsettings4Ray
{
address = Utils.String2List(protocolExtra.WgInterfaceAddress)?.Select(s => s.Trim()).ToList() ?? ["172.16.0.2/32"],
address = JsonUtils.SerializeToArray(Utils.String2List(protocolExtra.WgInterfaceAddress)?.Select(s => s.Trim()).ToList() ?? ["172.16.0.2/32"]),
secretKey = _node.Password,
reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(s => s.Trim()).Select(int.Parse).ToList(),
mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(),
@@ -428,7 +428,7 @@ public partial class CoreConfigV2rayService
}
kcpFinalmask.udp?.Reverse();
streamSettings.kcpSettings = kcpSettings;
streamSettings.finalmask = kcpFinalmask;
streamSettings.finalmask = (JsonObject)JsonUtils.SerializeToNode(kcpFinalmask);
break;
//ws
case nameof(ETransport.ws):
@@ -489,7 +489,7 @@ public partial class CoreConfigV2rayService
}
if (xhttpExtra.IsNotEmpty())
{
xhttpSettings.extra = JsonUtils.ParseJson(xhttpExtra);
xhttpSettings.extra = JsonUtils.ParseJson(xhttpExtra) as JsonObject;
}
streamSettings.xhttpSettings = xhttpSettings;
@@ -581,7 +581,7 @@ public partial class CoreConfigV2rayService
auth = _node.Password,
};
hy2Finalmask.udp?.Reverse();
streamSettings.finalmask = hy2Finalmask;
streamSettings.finalmask = (JsonObject)JsonUtils.SerializeToNode(hy2Finalmask);
break;
case "masque":
@@ -637,7 +637,7 @@ public partial class CoreConfigV2rayService
pathHttp = string.Join(",".AppendQuotes(), arrPath);
}
request = request.Replace("$requestPath$", $"{pathHttp.AppendQuotes()}");
rawSettings.header.request = JsonUtils.Deserialize<object>(request);
rawSettings.header.request = JsonUtils.SerializeToNode(request) as JsonObject;
streamSettings.rawSettings = rawSettings;
}
@@ -646,7 +646,7 @@ public partial class CoreConfigV2rayService
if (!_node.Finalmask.IsNullOrEmpty())
{
streamSettings.finalmask = JsonUtils.ParseJson(_node.Finalmask);
streamSettings.finalmask = JsonUtils.ParseJson(_node.Finalmask) as JsonObject;
}
}
catch (Exception ex)
@@ -837,10 +837,10 @@ public partial class CoreConfigV2rayService
protocol = "freedom",
streamSettings = new StreamSettings4Ray
{
finalmask = new Finalmask4Ray
finalmask = JsonUtils.SerializeToNode(new Finalmask4Ray
{
tcp = [fragmentMask],
},
}) as JsonObject,
},
};
+5 -1
View File
@@ -335,9 +335,13 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
//Check for avalonia desktop windows version
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "libHarfBuzzSharp.dll")))
{
return url?.Replace(".zip", "-desktop.zip");
url = url?.Replace(".zip", "-desktop.zip");
}
#if V2RAYN_IS_AOT
url = url?.Replace(".zip", "-aot.zip");
#endif
return url;
}
else if (Utils.IsLinux())
@@ -24,7 +24,7 @@ public sealed class WindowsJobService : IDisposable
try
{
var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
var length = Marshal.SizeOf<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>();
extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
@@ -448,7 +448,7 @@ public partial class ProfilesViewModel : MyReactiveObject
return null;
}
var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort);
var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort).ToList();
if (latest)
{
lstSelected.AddRange(await AppManager.Instance.GetProfileItemsOrderedByIndexIds(orderProfiles.Select(sp => sp?.IndexId)));
@@ -712,7 +712,7 @@ public partial class ProfilesViewModel : MyReactiveObject
actionType = ESpeedActionType.Realping;
}
lstSelected = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(ProfileItems?.OrderBy(t => t.Sort)));
lstSelected = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(ProfileItems?.OrderBy(t => t.Sort).ToList()));
}
else
{
@@ -191,13 +191,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
}
if (lst.Count > 0)
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
await SetClipboardDataInteraction.HandleSafe(JsonUtils.Serialize(lst, options));
await SetClipboardDataInteraction.HandleSafe(JsonUtils.SerializeCamelCase(lst));
}
}
@@ -69,7 +69,7 @@ public partial class QRCodeAvaloniaUtils
var bmi = new BITMAPINFO
{
biSize = Marshal.SizeOf(typeof(BITMAPINFO)),
biSize = Marshal.SizeOf<BITMAPINFO>(),
biWidth = width,
biHeight = -height,
biPlanes = 1,
+6
View File
@@ -0,0 +1,6 @@
<linker>
<assembly fullname="ServiceLib">
<type fullname="ServiceLib.Models.*" preserve="all" />
<type fullname="ServiceLib.Enums.*" preserve="all" />
</assembly>
</linker>
@@ -7,8 +7,6 @@ namespace v2rayN.Desktop.Views;
public partial class JsonEditor : UserControl
{
private static readonly JsonSerializerOptions SIndentedOptions = new() { WriteIndented = true };
private static readonly Lazy<IHighlightingDefinition> SHighlightingDark =
new(() => BuildHighlighting(dark: true), isThreadSafe: true);
@@ -88,7 +86,7 @@ public partial class JsonEditor : UserControl
try
{
var obj = JsonUtils.ParseJson(Editor.Text);
Editor.Text = JsonUtils.Serialize(obj, SIndentedOptions);
Editor.Text = JsonUtils.Serialize(obj, indented: true, nullValue: true);
}
catch
{
+4 -1
View File
@@ -55,5 +55,8 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<TrimmerRootDescriptor Include="TrimmerRoots.xml" />
</ItemGroup>
</Project>