island rework

This commit is contained in:
DismissedLight
2024-07-24 16:43:17 +08:00
parent 4ef65a2811
commit 45724801ee
15 changed files with 392 additions and 104 deletions

View File

@@ -0,0 +1,51 @@
using System;
using System.Text.Json;
namespace Snap.Hutao.Test.IncomingFeature;
[TestClass]
public class UnlockerIslandFunctionOffsetTest
{
private static readonly JsonSerializerOptions Options = new()
{
WriteIndented = true,
};
[TestMethod]
public void GenerateJson()
{
UnlockerIslandConfigurationWrapper wrapper = new()
{
Oversea = new()
{
FunctionOffsetFieldOfView = 0x00000000_01688E60,
FunctionOffsetTargetFrameRate = 0x00000000_018834D0,
FunctionOffsetFog = 0x00000000_00FB2AD0,
},
Chinese = new()
{
FunctionOffsetFieldOfView = 0x00000000_01684560,
FunctionOffsetTargetFrameRate = 0x00000000_0187EBD0,
FunctionOffsetFog = 0x00000000_00FAE1D0,
},
};
Console.WriteLine(JsonSerializer.Serialize(wrapper, Options));
}
private sealed class UnlockerIslandConfigurationWrapper
{
public required UnlockerIslandConfiguration Oversea { get; set; }
public required UnlockerIslandConfiguration Chinese { get; set; }
}
private sealed class UnlockerIslandConfiguration
{
public required uint FunctionOffsetFieldOfView { get; set; }
public required uint FunctionOffsetTargetFrameRate { get; set; }
public required uint FunctionOffsetFog { get; set; }
}
}

View File

@@ -41,8 +41,9 @@ internal sealed partial class SettingEntry
public const string LaunchScreenHeight = "Launch.ScreenHeight";
public const string LaunchIsScreenHeightEnabled = "Launch.IsScreenHeightEnabled";
public const string LaunchUnlockFps = "Launch.UnlockFps";
public const string LaunchUnlockerKind = "Launch.UnlockerKind";
public const string LaunchTargetFps = "Launch.TargetFps";
public const string LaunchTargetFov = "Launch.TargetFov";
public const string LaunchDisableFog = "Launch.DisableFog";
public const string LaunchMonitor = "Launch.Monitor";
public const string LaunchIsMonitorEnabled = "Launch.IsMonitorEnabled";
public const string LaunchIsUseCloudThirdPartyMobile = "Launch.IsUseCloudThirdPartyMobile";
@@ -51,6 +52,9 @@ internal sealed partial class SettingEntry
public const string LaunchUseBetterGenshinImpactAutomation = "Launch.UseBetterGenshinImpactAutomation";
public const string LaunchSetDiscordActivityWhenPlaying = "Launch.SetDiscordActivityWhenPlaying";
[Obsolete("不再区分解锁器类型,统一使用注入")]
public const string LaunchUnlockerKind = "Launch.UnlockerKind";
[Obsolete("不再支持多开")]
public const string MultipleInstances = "Launch.MultipleInstances";

View File

@@ -0,0 +1,37 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Service.Game.Unlocker.Island;
using Snap.Hutao.Web;
using System.Net.Http;
using System.Net.Http.Json;
namespace Snap.Hutao.Service.Feature;
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IFeatureService))]
[HttpClient(HttpClientConfiguration.Default)]
internal sealed partial class FeatureService : IFeatureService
{
private readonly IServiceScopeFactory serviceScopeFactory;
public async ValueTask<IslandFeature?> GetIslandFeatureAsync()
{
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
IHttpClientFactory httpClientFactory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>();
using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(FeatureService)))
{
try
{
return await httpClient.GetFromJsonAsync<IslandFeature>(HutaoEndpoints.Feature("UnlockerIsland")).ConfigureAwait(false);
}
catch
{
return default;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Service.Game.Unlocker.Island;
namespace Snap.Hutao.Service.Feature;
internal interface IFeatureService
{
ValueTask<IslandFeature?> GetIslandFeatureAsync();
}

View File

@@ -41,6 +41,8 @@ internal sealed class LaunchOptions : DbStoreOptions
private bool? unlockFps;
private NameDescriptionValue<GameFpsUnlockerKind>? unlockerKind;
private int? targetFps;
private float? targetFov;
private bool? disableFog;
private NameValue<int>? monitor;
private bool? isMonitorEnabled;
private bool? isUseCloudThirdPartyMobile;
@@ -169,40 +171,17 @@ internal sealed class LaunchOptions : DbStoreOptions
set => SetOption(ref unlockFps, SettingEntry.LaunchUnlockFps, value);
}
public List<NameDescriptionValue<GameFpsUnlockerKind>> UnlockerKinds { get; } =
[
new(SH.ServiceGameLaunchUnlockerKindLegacyName, SH.ServiceGameLaunchUnlockerKindLegacyDescription, GameFpsUnlockerKind.Legacy),
new(SH.ServiceGameLaunchUnlockerKindIslandName, SH.ServiceGameLaunchUnlockerKindIslandDescription, GameFpsUnlockerKind.Island),
];
public NameDescriptionValue<GameFpsUnlockerKind> UnlockerKind
{
get
{
return GetOption(ref unlockerKind, SettingEntry.LaunchUnlockerKind, name => GetKind(name, UnlockerKinds), UnlockerKinds[0]);
static NameDescriptionValue<GameFpsUnlockerKind> GetKind(string name, List<NameDescriptionValue<GameFpsUnlockerKind>> unlockerKinds)
{
GameFpsUnlockerKind kind = Enum.Parse<GameFpsUnlockerKind>(name);
return unlockerKinds.Single(entry => entry.Value == kind);
}
}
set
{
if (value is not null)
{
SetOption(ref unlockerKind, SettingEntry.LaunchUnlockerKind, value, selected => selected.Value.ToString());
}
}
}
public int TargetFps
{
get => GetOption(ref targetFps, SettingEntry.LaunchTargetFps, primaryScreenFps);
set => SetOption(ref targetFps, SettingEntry.LaunchTargetFps, value);
}
public float TargetFov
{
get=> GetOption(ref targetFov, SettingEntry.LaunchTargetFov, 45f);
}
public List<NameValue<int>> Monitors { get; } = [];
[AllowNull]

View File

@@ -1,27 +1,46 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Service.AvatarInfo.Factory.Builder;
using Snap.Hutao.Service.Feature;
using Snap.Hutao.Service.Game.Unlocker.Island;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.LibraryLoader;
using Snap.Hutao.Win32.System.Threading;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using static Snap.Hutao.Win32.ConstValues;
using static Snap.Hutao.Win32.Kernel32;
using static Snap.Hutao.Win32.Macros;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Service.Game.Unlocker;
internal abstract class GameFpsUnlocker : IGameFpsUnlocker
internal sealed class GameFpsUnlocker : IGameFpsUnlocker
{
private const string IslandEnvironmentName = "4F3E8543-40F7-4808-82DC-21E48A6037A7";
private readonly LaunchOptions launchOptions;
private readonly GameFpsUnlockerContext context = new();
private readonly IFeatureService featureService;
public GameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess, in UnlockOptions options, IProgress<GameFpsUnlockerContext> progress)
private readonly GameFpsUnlockerContext context = new();
private readonly string dataFolderIslandPath;
private IslandFunctionOffsets? offsets;
public GameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess, IProgress<GameFpsUnlockerContext> progress)
{
launchOptions = serviceProvider.GetRequiredService<LaunchOptions>();
featureService = serviceProvider.GetRequiredService<IFeatureService>();
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
dataFolderIslandPath = Path.Combine(runtimeOptions.DataFolder, "Snap.Hutao.UnlockerIsland.dll");
context.GameProcess = gameProcess;
context.AllAccess = OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_ALL_ACCESS, false, (uint)gameProcess.Id);
context.Options = options;
context.Progress = progress;
context.Logger = serviceProvider.GetRequiredService<ILogger<GameFpsUnlocker>>();
}
@@ -29,31 +48,91 @@ internal abstract class GameFpsUnlocker : IGameFpsUnlocker
public async ValueTask<bool> UnlockAsync(CancellationToken token = default)
{
HutaoException.ThrowIfNot(context.IsUnlockerValid, "This Unlocker is invalid");
(FindModuleResult result, RequiredRemoteModule remoteModule) = await GameProcessModule.FindModuleAsync(context).ConfigureAwait(false);
HutaoException.ThrowIfNot(result != FindModuleResult.TimeLimitExeeded, SH.ServiceGameUnlockerFindModuleTimeLimitExeeded);
HutaoException.ThrowIfNot(result != FindModuleResult.NoModuleFound, SH.ServiceGameUnlockerFindModuleNoModuleFound);
using (RequiredLocalModule localModule = LoadRequiredLocalModule(context.Options.GameFileSystem))
if (await featureService.GetIslandFeatureAsync().ConfigureAwait(false) is not { } feature)
{
GameFpsAddress.UnsafeFindFpsAddress(context, remoteModule, localModule);
return false;
}
offsets = string.Equals(GameConstants.GenshinImpactProcessName, context.GameProcess.ProcessName, StringComparison.OrdinalIgnoreCase)
? feature.Oversea
: feature.Chinese;
context.Report();
return context.FpsAddress != 0U;
return true;
}
public ValueTask PostUnlockAsync(CancellationToken token = default)
public async ValueTask PostUnlockAsync(CancellationToken token = default)
{
return PostUnlockOverrideAsync(context, launchOptions, context.Logger, token);
if (offsets is null)
{
return;
}
try
{
File.Copy(InstalledLocation.GetAbsolutePath("Snap.Hutao.UnlockerIsland.dll"), dataFolderIslandPath, true);
}
catch
{
context.Logger.LogError("Failed to copy island file.");
return;
}
try
{
using (MemoryMappedFile file = MemoryMappedFile.CreateOrOpen(IslandEnvironmentName, 1024))
{
using (MemoryMappedViewAccessor accessor = file.CreateViewAccessor())
{
nint handle = accessor.SafeMemoryMappedViewHandle.DangerousGetHandle();
InitializeIslandEnvironment(handle, offsets);
InitializeIsland(context.GameProcess);
await context.GameProcess.WaitForExitAsync().ConfigureAwait(false);
}
}
}
finally
{
context.Logger.LogInformation("Exit PostUnlockAsync");
}
}
protected abstract ValueTask PostUnlockOverrideAsync(GameFpsUnlockerContext context, LaunchOptions launchOptions, ILogger logger, CancellationToken token = default);
private static RequiredLocalModule LoadRequiredLocalModule(GameFileSystem gameFileSystem)
private unsafe void InitializeIslandEnvironment(nint handle, IslandFunctionOffsets offsets, LaunchOptions options)
{
LOAD_LIBRARY_FLAGS flags = LOAD_LIBRARY_FLAGS.LOAD_LIBRARY_AS_IMAGE_RESOURCE;
HMODULE executaleAddress = LoadLibraryExW(gameFileSystem.GameFilePath, default, flags);
IslandEnvironment* pIslandEnvironment = (IslandEnvironment*)handle;
pIslandEnvironment->FunctionOffsetFieldOfView = offsets.FunctionOffsetFieldOfView;
pIslandEnvironment->FunctionOffsetTargetFrameRate = offsets.FunctionOffsetTargetFrameRate;
pIslandEnvironment->FunctionOffsetFog = offsets.FunctionOffsetFog;
pIslandEnvironment->TargetFrameRate = options.TargetFps;
}
return new(executaleAddress);
private unsafe void InitializeIsland(Process gameProcess)
{
HANDLE hModule = default;
try
{
hModule = NativeLibrary.Load(dataFolderIslandPath);
nint pIslandGetWindowHook = NativeLibrary.GetExport((nint)(hModule & ~0x3L), "IslandGetWindowHook");
HOOKPROC hookProc = default;
((delegate* unmanaged[Stdcall]<HOOKPROC*, HRESULT>)pIslandGetWindowHook)(&hookProc);
SpinWait.SpinUntil(() => gameProcess.MainWindowHandle is not 0);
uint threadId = GetWindowThreadProcessId(gameProcess.MainWindowHandle, default);
HHOOK hHook = SetWindowsHookExW(WINDOWS_HOOK_ID.WH_GETMESSAGE, hookProc, (HINSTANCE)hModule, threadId);
if (hHook.Value is 0)
{
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(GetLastError()));
}
if (!PostThreadMessageW(threadId, WM_NULL, default, default))
{
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(GetLastError()));
}
}
finally
{
NativeLibrary.Free(hModule);
}
}
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using System.Diagnostics;
namespace Snap.Hutao.Service.Game.Unlocker;
@@ -10,18 +9,10 @@ internal sealed class GameFpsUnlockerContext
{
public string Description { get; set; } = default!;
public FindModuleResult FindModuleResult { get; set; }
public bool IsUnlockerValid { get; set; } = true;
public nuint FpsAddress { get; set; }
public UnlockOptions Options { get; set; }
public Process GameProcess { get; set; } = default!;
public HANDLE AllAccess { get; set; }
public IProgress<GameFpsUnlockerContext> Progress { get; set; } = default!;
public ILogger Logger { get; set; } = default!;

View File

@@ -7,9 +7,15 @@ namespace Snap.Hutao.Service.Game.Unlocker.Island;
internal struct IslandEnvironment
{
public nuint Address;
public int Value;
public nuint Reserved1;
public int Reserved2;
public IslandState State;
public WIN32_ERROR LastError;
public int Reserved;
public int Reserved3;
public float FieldOfView;
public float TargetFrameRate;
public bool DisableFog;
public nuint FunctionOffsetFieldOfView;
public nuint FunctionOffsetTargetFrameRate;
public nuint FunctionOffsetFog;
}

View File

@@ -7,9 +7,15 @@ namespace Snap.Hutao.Service.Game.Unlocker.Island;
internal struct IslandEnvironmentView
{
public nuint Address;
public int Value;
public nuint Reserved1;
public int Reserved2;
public IslandState State;
public WIN32_ERROR LastError;
public int Reserved;
public int Reserved3;
public float FieldOfView;
public float TargetFrameRate;
public bool DisableFog;
public nuint FunctionOffsetFieldOfView;
public nuint FunctionOffsetTargetFrameRate;
public nuint FunctionOffsetFog;
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game.Unlocker.Island;
internal sealed class IslandFeature
{
public required IslandFunctionOffsets Oversea { get; set; }
public required IslandFunctionOffsets Chinese { get; set; }
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game.Unlocker.Island;
internal sealed class IslandFunctionOffsets
{
public required uint FunctionOffsetFieldOfView { get; set; }
public required uint FunctionOffsetTargetFrameRate { get; set; }
public required uint FunctionOffsetFog { get; set; }
}

View File

@@ -320,7 +320,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Snap.Hutao.UnlockerIsland" Version="1.0.10">
<PackageReference Include="Snap.Hutao.UnlockerIsland" Version="1.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -1,8 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
#define IS_ALPHA_BUILD
using Snap.Hutao.Core;
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Web.Hutao.Response;
@@ -24,7 +22,7 @@ internal sealed partial class HutaoInfrastructureClient
public async ValueTask<HutaoResponse<StaticResourceSizeInformation>> GetStaticSizeAsync(CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(HutaoEndpoints.StaticSize)
.SetRequestUri(HutaoEndpoints.StaticSize())
.Get();
HutaoResponse<StaticResourceSizeInformation>? resp = await builder.SendAsync<HutaoResponse<StaticResourceSizeInformation>>(httpClient, logger, token).ConfigureAwait(false);
@@ -34,7 +32,7 @@ internal sealed partial class HutaoInfrastructureClient
public async ValueTask<HutaoResponse<IPInformation>> GetIPInformationAsync(CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(HutaoEndpoints.Ip)
.SetRequestUri(HutaoEndpoints.Ip())
.Get();
HutaoResponse<IPInformation>? resp = await builder.SendAsync<HutaoResponse<IPInformation>>(httpClient, logger, token).ConfigureAwait(false);
@@ -43,12 +41,7 @@ internal sealed partial class HutaoInfrastructureClient
public async ValueTask<HutaoResponse<HutaoPackageInformation>> GetHutaoVersionInfomationAsync(CancellationToken token = default)
{
string url
#if IS_ALPHA_BUILD
= HutaoEndpoints.PatchAlphaSnapHutao(Core.Setting.LocalSetting.Get(Core.Setting.SettingKeys.AlphaBuildUseCNPatchEndpoint, false));
#else
= HutaoEndpoints.PatchSnapHutao;
#endif
string url = HutaoEndpoints.PatchSnapHutao();
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(url)
@@ -62,7 +55,7 @@ internal sealed partial class HutaoInfrastructureClient
public async ValueTask<HutaoResponse<YaeVersionInformation>> GetYaeVersionInformationAsync(CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(HutaoEndpoints.PatchYaeAchievement)
.SetRequestUri(HutaoEndpoints.PatchYaeAchievement())
.Get();
HutaoResponse<YaeVersionInformation>? resp = await builder.SendAsync<HutaoResponse<YaeVersionInformation>>(httpClient, logger, token).ConfigureAwait(false);

View File

@@ -19,17 +19,17 @@ internal sealed partial class HutaoWallpaperClient
public ValueTask<Response<Wallpaper>> GetBingWallpaperAsync(CancellationToken token = default)
{
return GetWallpaperAsync(HutaoEndpoints.WallpaperBing, token);
return GetWallpaperAsync(HutaoEndpoints.WallpaperBing(), token);
}
public ValueTask<Response<Wallpaper>> GetLauncherWallpaperAsync(CancellationToken token = default)
{
return GetWallpaperAsync(HutaoEndpoints.WallpaperGenshinLauncher, token);
return GetWallpaperAsync(HutaoEndpoints.WallpaperGenshinLauncher(), token);
}
public ValueTask<Response<Wallpaper>> GetTodayWallpaperAsync(CancellationToken token = default)
{
return GetWallpaperAsync(HutaoEndpoints.WallpaperToday, token);
return GetWallpaperAsync(HutaoEndpoints.WallpaperToday(), token);
}
private async ValueTask<Response<Wallpaper>> GetWallpaperAsync(string url, CancellationToken token = default)

View File

@@ -3,12 +3,13 @@
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hutao.GachaLog;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Web;
[SuppressMessage("", "SA1201")]
[SuppressMessage("", "SA1203")]
internal static class HutaoEndpoints
internal static partial class HutaoEndpoints
{
#region HomaAPI
@@ -107,32 +108,77 @@ internal static class HutaoEndpoints
#region Infrasturcture
public static string Enka(in PlayerUid uid)
{
return $"{ApiSnapGenshinEnka}/{uid}";
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/enka/{uid}",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/enka/{uid}",
_ => $"{ApiSnapGenshin}/enka/{uid}",
};
}
public static string EnkaPlayerInfo(in PlayerUid uid)
{
return $"{ApiSnapGenshinEnka}/{uid}/info";
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/enka/{uid}/info",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/enka/{uid}/info",
_ => $"{ApiSnapGenshin}/enka/{uid}/info",
};
}
public const string Ip = $"{ApiSnapGenshin}/ip";
public static string Ip()
{
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/ip",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/ip",
_ => $"{ApiSnapGenshin}/ip",
};
}
#region Feature
public static string Feature(string name)
{
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/client/{name}.json",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/client/{name}.json",
_ => $"{ApiSnapGenshin}/client/{name}.json",
};
}
#endregion
#region Metadata
public static string Metadata(string locale, string fileName)
{
return $"{ApiSnapGenshinMetadata}/Genshin/{locale}/{fileName}";
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/metadata/Genshin/{locale}/{fileName}",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/metadata/Genshin/{locale}/{fileName}",
_ => $"{ApiSnapGenshin}/metadata/Genshin/{locale}/{fileName}",
};
}
#endregion
#region Patch
public const string PatchYaeAchievement = $"{ApiSnapGenshinPatch}/yae";
public const string PatchSnapHutao = $"{ApiSnapGenshinPatch}/hutao";
public static string PatchAlphaSnapHutao(bool isCN)
public static string PatchYaeAchievement()
{
return isCN
? $"{ApiAlphaSnapGenshin}/cn/patch/hutao"
: $"{ApiAlphaSnapGenshin}/global/patch/hutao";
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/patch/yae",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/patch/yae",
_ => $"{ApiAlphaSnapGenshin}/patch/yae",
};
}
public static string PatchSnapHutao()
{
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/patch/hutao",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/patch/hutao",
_ => $"{ApiAlphaSnapGenshin}/patch/hutao",
};
}
#endregion
@@ -143,34 +189,95 @@ internal static class HutaoEndpoints
public static string StaticRaw(string category, string fileName)
{
return $"{ApiSnapGenshinStaticRaw}/{category}/{fileName}";
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/static/raw/{category}/{fileName}",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/static/raw/{category}/{fileName}",
_ => $"{ApiSnapGenshin}/static/raw/{category}/{fileName}",
};
}
public static string StaticZip(string fileName)
{
return $"{ApiSnapGenshinStaticZip}/{fileName}.zip";
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/static/zip/{fileName}.zip",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/static/zip/{fileName}.zip",
_ => $"{ApiSnapGenshin}/static/zip/{fileName}.zip",
};
}
public const string StaticSize = $"{ApiSnapGenshin}/static/size";
public static string StaticSize()
{
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/static/size",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/static/size",
_ => $"{ApiSnapGenshin}/static/size",
};
}
#endregion
#region Wallpaper
public const string WallpaperBing = $"{ApiSnapGenshin}/wallpaper/bing";
public static string WallpaperBing()
{
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/wallpaper/bing",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/wallpaper/bing",
_ => $"{ApiSnapGenshin}/wallpaper/bing",
};
}
public const string WallpaperGenshinLauncher = $"{ApiSnapGenshin}/wallpaper/hoyoplay";
public static string WallpaperGenshinLauncher()
{
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/wallpaper/genshinlauncher",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/wallpaper/genshinlauncher",
_ => $"{ApiSnapGenshin}/wallpaper/genshinlauncher",
};
}
public const string WallpaperToday = $"{ApiSnapGenshin}/wallpaper/today";
public static string WallpaperToday()
{
return Kind switch
{
ApiKind.AlphaCN => $"{ApiAlphaSnapGenshin}/cn/wallpaper/today",
ApiKind.AlphaOS => $"{ApiAlphaSnapGenshin}/global/wallpaper/today",
_ => $"{ApiSnapGenshin}/wallpaper/today",
};
}
#endregion
#endregion
private const string ApiSnapGenshin = "https://api.snapgenshin.com";
private const string HomaSnapGenshin = "https://homa.snapgenshin.com";
}
internal static partial class HutaoEndpoints
{
private enum ApiKind
{
AlphaCN,
AlphaOS,
Formal,
}
private static ApiKind Kind
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if IS_ALPHA_BUILD
return Core.Setting.LocalSetting.Get(Core.Setting.SettingKeys.AlphaBuildUseCNPatchEndpoint, false) ? ApiKind.AlphaCN : ApiKind.AlphaOS;
#else
return ApiKind.Formal;
#endif
}
}
private const string ApiAlphaSnapGenshin = "https://api-alpha.snapgenshin.cn";
private const string ApiSnapGenshin = "https://api.snapgenshin.com";
private const string ApiSnapGenshinMetadata = $"{ApiSnapGenshin}/metadata";
private const string ApiSnapGenshinPatch = $"{ApiSnapGenshin}/patch";
private const string ApiSnapGenshinStaticRaw = $"{ApiSnapGenshin}/static/raw";
private const string ApiSnapGenshinStaticZip = $"{ApiSnapGenshin}/static/zip";
private const string ApiSnapGenshinEnka = $"{ApiSnapGenshin}/enka";
private const string HomaSnapGenshin = "https://homa.snapgenshin.com";
}