Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1e55d56
盛趣协议网络代理支持
Errerer Aug 27, 2026
4e37252
代理设置窗口布局清理
Errerer Aug 27, 2026
a2ac76c
Merge branch 'CN' of https://github.com/Errerer/FFXIVQuickLauncherX i…
Errerer Aug 27, 2026
364a11a
Merge branch 'AtmoOmen:CN' into CN
Errerer Aug 28, 2026
0021966
修复代理设置窗口缺少本地组合框样式导致的打开崩溃
Errerer Aug 28, 2026
faf3893
代理配置支持多条目预设与激活选择
Errerer Aug 28, 2026
1cc7a8b
Merge branch 'AtmoOmen:CN' into CN
Errerer Aug 29, 2026
3fbc334
修复手动注入读取提权游戏进程主模块导致的拒绝访问
Errerer Aug 29, 2026
cab3e2f
Merge branch 'CN' of https://github.com/Errerer/FFXIVQuickLauncherX i…
Errerer Sep 12, 2026
5b3dfca
代理仅对盛趣域名生效并补全大区列表代理
Errerer Sep 12, 2026
2a682e7
代理配置迁移到 Roaming 目录并支持旧文件迁移
Errerer Sep 12, 2026
378b867
修复代理预设增删不落盘并补充 VM 回归测试
Errerer Sep 13, 2026
336edd1
Merge remote-tracking branch 'upstream/CN' into CN
Errerer Sep 13, 2026
7821f01
修复代理窗口切换预设时密码框串写问题
Errerer Sep 14, 2026
6ef109c
对齐上游代码风格: 字段前置、初始化器对齐与可读性拆分
Errerer Sep 14, 2026
28550d6
代理配置并入启动器主配置体系, 旧配置文件启动时自动迁移
Errerer Sep 15, 2026
a786f3c
代理设置窗口入口移入 SettingsWindowViewModel 并统一经由 DialogService 打开
Errerer Sep 15, 2026
ebdf4b5
复核修正: 迁移成功后仅删除导入源文件, 清理无效 using
Errerer Sep 15, 2026
7642055
按 review 二轮意见修正: 删除迁移与代理测试, 恢复重启生效语义, 支持清空密码, 端口框套深色样式, ProxyConfigSn…
Errerer Sep 17, 2026
47ed010
Merge branch 'AtmoOmen:CN' into CN
Errerer Oct 8, 2026
0d5c835
Merge branch 'AtmoOmen:CN' into CN
Errerer Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/XIVLauncher.Common.Http/ProxyConfigSnapshot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace XIVLauncher.Common.Http;

/// <summary>
/// 代理配置快照, 由应用设置层提供, 避免公共库依赖 UI 层类型
/// </summary>
public sealed record ProxyConfigSnapshot
(
string Type,
string Host,
int Port,
string? Username = null,
string? Password = null
)
{
public bool IsDisabled =>
string.IsNullOrWhiteSpace(Host)
|| Port is < 1 or > 65535
|| string.IsNullOrWhiteSpace(Type)
|| string.Equals(Type, "None", StringComparison.OrdinalIgnoreCase);
}
4 changes: 3 additions & 1 deletion src/XIVLauncher.Common.Http/XLHttpClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ public static HttpClient Create
(
TimeSpan connectTimeout,
int maxConnectionsPerServer,
DecompressionMethods automaticDecompression
DecompressionMethods automaticDecompression,
IWebProxy? proxy = null
)
{
var handler = new SocketsHttpHandler
{
UseProxy = true,
Proxy = proxy,
ConnectTimeout = connectTimeout,
MaxConnectionsPerServer = maxConnectionsPerServer,
EnableMultipleHttp2Connections = true,
Expand Down
85 changes: 85 additions & 0 deletions src/XIVLauncher.Common.Http/XLProxyProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.Net;
using Serilog;

namespace XIVLauncher.Common.Http;

/// <summary>
/// 启动器全局代理提供者, 由启动流程根据代理配置构建一次。
/// HTTP/HTTPS/SOCKS5 由 SocketsHttpHandler 原生支持。
/// </summary>
public static class XLProxyProvider
{
public static IWebProxy? Current { get; private set; }
Comment thread
AtmoOmen marked this conversation as resolved.
Resolved

public static void Apply(ProxyConfigSnapshot? config)
{
var webProxy = BuildWebProxy(config);
Current = webProxy is null ? null : new SdoScopedProxy(webProxy);

Log.Information
(
"[XLProxyProvider] 代理配置已应用 (仅对盛趣域名 *.sdo.com / *.jijiagames.com 生效): {Proxy}",
webProxy is WebProxy { Address: not null } proxy ? proxy.Address.ToString() : "无"
);
}

public static IWebProxy? BuildWebProxy(ProxyConfigSnapshot? config)
{
if (config is null || config.IsDisabled)
return null;

try
{
var scheme = config.Type.Trim().ToLowerInvariant() switch
{
"http" => "http",
"https" => "https",
"socks5" => "socks5",
_ => null
};

if (scheme == null)
{
Log.Warning("[XLProxyProvider] 不支持的代理类型: {ProxyType}", config.Type);
return null;
}

var proxy = new WebProxy($"{scheme}://{config.Host.Trim()}:{config.Port}");

if (!string.IsNullOrWhiteSpace(config.Username))
proxy.Credentials = new NetworkCredential(config.Username, config.Password);

return proxy;
}
catch (Exception ex)
{
Log.Warning(ex, "[XLProxyProvider] 构建代理配置失败: {ProxyHost}:{ProxyPort} ({ProxyType})", config.Host, config.Port, config.Type);
return null;
}
}

/// <summary>
/// 仅对盛趣域名 (*.sdo.com / *.jijiagames.com) 生效的代理包装:
/// 其余目标一律直连, 避免 GitHub / Dalamud / NuGet 等流量被错误导入代理。
/// </summary>
private sealed class SdoScopedProxy(IWebProxy inner) : IWebProxy
{
public ICredentials? Credentials
{
get => inner.Credentials;
set => inner.Credentials = value;
}

public Uri? GetProxy(Uri destination) =>
IsSdoHost(destination.Host) ? inner.GetProxy(destination) : destination;

public bool IsBypassed(Uri host) =>
!IsSdoHost(host.Host);

private static bool IsSdoHost(string host) =>
string.Equals(host, "sdo.com", StringComparison.OrdinalIgnoreCase)
|| host.EndsWith(".sdo.com", StringComparison.OrdinalIgnoreCase)
|| string.Equals(host, "jijiagames.com", StringComparison.OrdinalIgnoreCase)
|| host.EndsWith(".jijiagames.com", StringComparison.OrdinalIgnoreCase);
}
}
3 changes: 2 additions & 1 deletion src/XIVLauncher.Common/Game/Launcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Serilog;
using XIVLauncher.Common.Constant;
using XIVLauncher.Common.Game.Exceptions;
using XIVLauncher.Common.Http;
using XIVLauncher.Common.Util;

namespace XIVLauncher.Common.Game;
Expand All @@ -13,7 +14,7 @@ public partial class Launcher
public delegate Process? GameStarter(GameStartRequest request);

public RestartMonitor RestartMonitor { get; } = new();
public HttpClient MockHttpClient { get; } = new(new HttpClientHandler { UseCookies = true });
public HttpClient MockHttpClient { get; } = new(new HttpClientHandler { UseCookies = true, Proxy = XLProxyProvider.Current });

public FFXIVProcess? LaunchGame
(
Expand Down
4 changes: 3 additions & 1 deletion src/XIVLauncher.DCTravel/DCTravelClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Text.Json.Nodes;
using Serilog;
using XIVLauncher.Common.Constant;
using XIVLauncher.Common.Http;
using XIVLauncher.Login;
using XIVLauncher.Login.Workflow;

Expand Down Expand Up @@ -78,7 +79,8 @@ public DCTravelClient(string nSessionID)
CookieContainer = cookieContainer,
UseCookies = true,
AllowAutoRedirect = true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
Proxy = XLProxyProvider.Current
};
httpClient = new HttpClient(handler)
{
Expand Down
7 changes: 6 additions & 1 deletion src/XIVLauncher.GamePatchV3/GameFileDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Text;
using Serilog;
using XIVLauncher.Common.Constant;
using XIVLauncher.Common.Http;
using XIVLauncher.GamePatchV3.Integrity;
using XIVLauncher.GamePatchV3.Integrity.Models;

Expand All @@ -27,7 +28,11 @@ public class GameFileDownloader : IDisposable
private string dataVersion = null!;

public GameFileDownloader()
: this(new SocketsHttpHandler())
: this(new SocketsHttpHandler
{
UseProxy = true,
Proxy = XLProxyProvider.Current
})
{
}

Expand Down
11 changes: 10 additions & 1 deletion src/XIVLauncher.GamePatchV3/GamePatchMetadataClient.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Diagnostics;
using System.Net;
using System.Text.Json;
using Serilog;
using XIVLauncher.Common.Constant;
using XIVLauncher.Common.Http;
using XIVLauncher.GamePatchV3.Integrity.Models;
using XIVLauncher.GamePatchV3.Models;
using XIVLauncher.GamePatchV3.Update.Models;
Expand All @@ -15,7 +17,14 @@ public sealed class GamePatchMetadataClient : IDisposable
PropertyNameCaseInsensitive = true
};

private readonly HttpClient client = new();
private readonly HttpClient client = new
(
new SocketsHttpHandler
{
UseProxy = true,
Proxy = XLProxyProvider.Current
}
);

public void Dispose() =>
client.Dispose();
Expand Down
10 changes: 9 additions & 1 deletion src/XIVLauncher.GamePatchV3/Update/GamePatchInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Xml.Linq;
using Serilog;
using XIVLauncher.Common.Constant;
using XIVLauncher.Common.Http;
using XIVLauncher.GamePatchV3.Integrity.Models;
using XIVLauncher.GamePatchV3.Models;
using XIVLauncher.GamePatchV3.Update.Models;
Expand All @@ -25,7 +26,14 @@ public sealed class GamePatchInstaller : IDisposable
PropertyNameCaseInsensitive = true
};

private readonly HttpClient client = new()
private readonly HttpClient client = new
(
new SocketsHttpHandler
{
UseProxy = true,
Proxy = XLProxyProvider.Current
}
)
{
DefaultRequestVersion = HttpVersion.Version20,
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower
Expand Down
1 change: 1 addition & 0 deletions src/XIVLauncher.GamePatchV3/XIVLauncher.GamePatchV3.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\XIVLauncher.Common\XIVLauncher.Common.csproj"/>
<ProjectReference Include="..\XIVLauncher.Common.Http\XIVLauncher.Common.Http.csproj"/>
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="XIVLauncher.Test"/>
Expand Down
4 changes: 3 additions & 1 deletion src/XIVLauncher.Login/Channels/LoginChannelContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Serilog;
using XIVLauncher.Account.DeviceProfiles;
using XIVLauncher.Common.Constant;
using XIVLauncher.Common.Http;
using XIVLauncher.Common.Util;
using XIVLauncher.Login.Client;
using XIVLauncher.Login.Exceptions;
Expand All @@ -29,7 +30,8 @@ public LoginChannelContext(DeviceProfileSnapshot deviceProfile)
var loginHandler = new HttpClientHandler
{
UseCookies = true,
CookieContainer = loginCookies
CookieContainer = loginCookies,
Proxy = XLProxyProvider.Current
};

loginHttpClient = new HttpClient(loginHandler);
Expand Down
15 changes: 10 additions & 5 deletions src/XIVLauncher.Login/Models/LoginArea.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ namespace XIVLauncher.Login.Models;

public class LoginArea
{
private static readonly HttpClient Client = XLHttpClientFactory.Create(TimeSpan.FromSeconds(30), int.MaxValue, DecompressionMethods.None);

static LoginArea() =>
Client.Timeout = TimeSpan.FromSeconds(30);
private static readonly Lazy<HttpClient> Client = new
(
() =>
{
var client = XLHttpClientFactory.Create(TimeSpan.FromSeconds(30), int.MaxValue, DecompressionMethods.None, XLProxyProvider.Current);
client.Timeout = TimeSpan.FromSeconds(30);
return client;
}
);

[JsonProperty("Areaid")]
public string AreaID { get; set; } = null!;
Expand Down Expand Up @@ -45,7 +50,7 @@ public static async Task<LoginArea[]> Get()
request.Headers.Add("Accept", "*/*");
request.Headers.Add("Host", "ff.dorado.sdo.com");

using var response = await Client.SendAsync(request);
using var response = await Client.Value.SendAsync(request);
response.EnsureSuccessStatusCode();

var text = await response.Content.ReadAsStringAsync();
Expand Down
31 changes: 31 additions & 0 deletions src/XIVLauncher/Settings/LauncherSettingsV3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,19 @@ public bool KeepPatches

#endregion

#region 网络代理配置

/// <summary>
/// 启动器网络代理配置 (与主配置一并存储)
/// </summary>
public ProxySettings ProxySettings
{
get;
set => Set(ref field, value);
} = new();

#endregion

#region 账户与安全配置

/// <summary>
Expand Down Expand Up @@ -480,6 +493,7 @@ out Exception? exception
{
var json = File.ReadAllText(sourcePath, Encoding.UTF8);
settings = JsonSerializer.Deserialize<LauncherSettingsV3>(json, JsonOptions) ?? new LauncherSettingsV3();
settings.SanitizeProxySettings();
var migrated = settings.MigrateWeGamePath();
settings.Attach(attachPath);
if (migrated)
Expand All @@ -504,6 +518,23 @@ private bool MigrateWeGamePath()
return true;
}

/// <summary>
/// 加载后清理代理配置: 补齐缺失的配置对象并校验密码字段可解密
/// </summary>
private void SanitizeProxySettings()
{
ProxySettings ??= new ProxySettings();

foreach (var profile in ProxySettings.Profiles)
{
if (!string.IsNullOrWhiteSpace(profile.ProxyPasswordEncrypted) && profile.GetPassword() == null)
{
Log.Warning("[LauncherSettingsV3] 代理密码解密失败, 已清空密码字段: {ProfileName}", profile.DisplayName);
profile.ProxyPasswordEncrypted = string.Empty;
}
}
}

private static string? MoveBrokenConfig(string configPath)
{
if (!File.Exists(configPath))
Expand Down
Loading