├── unlockfps_gui ├── Assets │ └── icon.ico ├── AssemblyInfo.cs ├── FodyWeavers.xml ├── Converters │ ├── BooleanConverters.cs │ ├── FullPath2NameConverter.cs │ ├── Enum2ListConverter.cs │ ├── HasItemsConverter.cs │ └── DelegateConverter.cs ├── OSVersionExt │ ├── Win32API │ │ ├── IWin32API.cs │ │ ├── Win32ApiProvider.cs │ │ └── Win32ApiEnums.cs │ ├── VersionInfo.cs │ ├── Environment │ │ ├── IEnvironment.cs │ │ └── EnvironmentProvider.cs │ ├── MajorVersion10 │ │ ├── RegistryProviderDefault.cs │ │ └── MajorVersion10Properties.cs │ └── Registry │ │ └── IRegistry.cs ├── ViewModels │ └── ViewModelBase.cs ├── Views │ ├── AboutWindow.axaml.cs │ ├── AlertWindow.axaml.cs │ ├── AboutWindow.axaml │ ├── AlertWindow.axaml │ ├── MainWindow.axaml │ ├── InitializationWindow.axaml │ ├── SettingsWindow.axaml.cs │ └── MainWindow.axaml.cs ├── Utils │ ├── AssemblyAttributeUtil.cs │ ├── WineHelper.cs │ ├── ReflectionUtil.cs │ ├── ProcessUtils.cs │ └── ConsoleManager.cs ├── AppBuilderExtensions.cs ├── Program.cs ├── app.manifest ├── App.axaml ├── unlockfps_gui.csproj ├── WindowChromeExtensions.cs ├── App.axaml.cs └── Styles │ └── TabStyles.axaml ├── unlockfps_nc ├── Resources │ ├── icon.ico │ └── app.manifest ├── Model │ └── Config.cs ├── AboutForm.cs ├── unlockfps_nc.csproj ├── Program.cs ├── Service │ └── ConfigService.cs ├── MainForm.cs ├── AboutForm.Designer.cs ├── Utility │ └── ProcessUtils.cs ├── SetupForm.Designer.cs ├── AboutForm.resx ├── SettingsForm.cs ├── SetupForm.resx ├── SettingsForm.resx ├── SetupForm.cs └── MainForm.Designer.cs ├── unlockfps ├── FodyWeavers.xml ├── Properties │ └── launchSettings.json ├── GameConstants.cs ├── Logging │ ├── ILoggerFactory.cs │ ├── LogLevel.cs │ ├── ILogger.cs │ ├── LogManager.cs │ └── ConsoleLogger.cs ├── AssemblyInfo.cs ├── NativeMethods.txt ├── Utils │ ├── TaskUtils.cs │ ├── WineHelper.cs │ ├── NativeMethods.cs │ ├── Win32Window.cs │ └── ProcessUtils.cs ├── Config.cs ├── unlockfps.csproj ├── Services │ ├── ConfigService.cs │ └── ProcessService.cs ├── Program.cs ├── app.manifest └── FpsPatterns.cs ├── .github └── workflows │ └── publish.yml ├── unlockfps_nc.sln ├── .gitattributes ├── README.md └── .gitignore /unlockfps_gui/Assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Milkitic/genshin-fps-unlock-universal/HEAD/unlockfps_gui/Assets/icon.ico -------------------------------------------------------------------------------- /unlockfps_nc/Resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Milkitic/genshin-fps-unlock-universal/HEAD/unlockfps_nc/Resources/icon.ico -------------------------------------------------------------------------------- /unlockfps_gui/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: PropertyChanged.FilterType(@".*ViewModel")] 2 | [assembly: PropertyChanged.FilterType(@".*\.Config")] -------------------------------------------------------------------------------- /unlockfps/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /unlockfps/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "unlockfps": { 4 | "commandName": "Project", 5 | "commandLineArgs": "-m" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /unlockfps_gui/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /unlockfps/GameConstants.cs: -------------------------------------------------------------------------------- 1 | namespace UnlockFps; 2 | 3 | public static class GameConstants 4 | { 5 | public static string[] GameNames { get; } = ["YuanShen", "GenshinImpact"]; 6 | } -------------------------------------------------------------------------------- /unlockfps/Logging/ILoggerFactory.cs: -------------------------------------------------------------------------------- 1 | namespace UnlockFps.Logging; 2 | 3 | public interface ILoggerFactory 4 | { 5 | ILogger CreateLogger(string name); 6 | ILogger CreateLogger(); 7 | } -------------------------------------------------------------------------------- /unlockfps/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: PropertyChanged.FilterType(@".*ViewModel")] 2 | [assembly: PropertyChanged.FilterType(@".*\.Config")] 3 | [assembly: PropertyChanged.FilterType(@".*\.LaunchOptions")] -------------------------------------------------------------------------------- /unlockfps/Logging/LogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace UnlockFps.Logging; 2 | 3 | public enum LogLevel 4 | { 5 | Trace = 0, 6 | Debug = 1, 7 | Information = 2, 8 | Warning = 3, 9 | Error = 4, 10 | Critical = 5, 11 | None = 6, 12 | } -------------------------------------------------------------------------------- /unlockfps_gui/Converters/BooleanConverters.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Data.Converters; 2 | 3 | namespace UnlockFps.Gui.Converters; 4 | 5 | public static class BooleanConverters 6 | { 7 | public static readonly IValueConverter Not = 8 | new DelegateConverter((x, _, _, _) => !(bool)x, (x, _, _, _) => !(bool)x); 9 | } -------------------------------------------------------------------------------- /unlockfps_gui/OSVersionExt/Win32API/IWin32API.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace OSVersionExt.Win32API 7 | { 8 | public interface IWin32API 9 | { 10 | NTSTATUS RtlGetVersion(ref OSVERSIONINFOEX versionInfo); 11 | int GetSystemMetrics(SystemMetric smIndex); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /unlockfps/NativeMethods.txt: -------------------------------------------------------------------------------- 1 | CreateProcess 2 | DispatchMessage 3 | GetClassName 4 | GetForegroundWindow 5 | GetMessage 6 | GetProcessImageFileName 7 | GetWindowText 8 | GetWindowThreadProcessId 9 | ResumeThread 10 | SetWinEventHook 11 | TranslateMessage 12 | 13 | EVENT_SYSTEM_FOREGROUND 14 | WINEVENT_OUTOFCONTEXT 15 | WINEVENT_SKIPOWNPROCESS 16 | 17 | OpenProcess 18 | QueryFullProcessImageName 19 | 20 | EnumWindows -------------------------------------------------------------------------------- /unlockfps_gui/OSVersionExt/VersionInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | 7 | namespace OSVersionExt 8 | { 9 | public class VersionInfo 10 | { 11 | public Version Version { get; private set; } 12 | 13 | public VersionInfo(int major, int minor, int build) 14 | { 15 | this.Version = new Version(major, minor, build); 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /unlockfps_gui/OSVersionExt/Environment/IEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace OSVersionExt.Environment 7 | { 8 | public interface IEnvironment 9 | { 10 | /// 11 | /// Determines whether the current operating system is a 64-bit operating system. 12 | /// 13 | /// 14 | bool Is64BitOperatingSystem(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /unlockfps_nc/Resources/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /unlockfps_gui/OSVersionExt/MajorVersion10/RegistryProviderDefault.cs: -------------------------------------------------------------------------------- 1 | using OSVersionExt.Registry; 2 | 3 | namespace OSVersionExt.MajorVersion10 4 | { 5 | public class RegistryProviderDefault : IRegistry 6 | { 7 | public RegistryProviderDefault() 8 | { 9 | // NOP 10 | } 11 | public object GetValue(string keyName, string valueName, object defaultValue) 12 | { 13 | return Microsoft.Win32.Registry.GetValue(keyName, valueName, defaultValue); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /unlockfps/Logging/ILogger.cs: -------------------------------------------------------------------------------- 1 | namespace UnlockFps.Logging; 2 | 3 | public interface ILogger 4 | { 5 | void Log(LogLevel logLevel, string message); 6 | void LogInformation(string message); 7 | void LogDebug(string message); 8 | void LogError(string message); 9 | void LogWarning(string message); 10 | void LogInformation(Exception exception, string message); 11 | void LogDebug(Exception exception, string message); 12 | void LogError(Exception exception, string message); 13 | void LogWarning(Exception exception, string message); 14 | } 15 | 16 | public interface ILogger : ILogger 17 | { 18 | } -------------------------------------------------------------------------------- /unlockfps_gui/OSVersionExt/Environment/EnvironmentProvider.cs: -------------------------------------------------------------------------------- 1 | using OSVersionExt.Environment; 2 | 3 | 4 | namespace OSVersionExt 5 | { 6 | public class EnvironmentProvider : IEnvironment 7 | { 8 | public EnvironmentProvider() 9 | { 10 | // NOP 11 | } 12 | 13 | /// 14 | /// Determines whether the current operating system is a 64-bit operating system. 15 | /// 16 | /// true if the operating system is 64-bit; otherwise, false. 17 | public bool Is64BitOperatingSystem() 18 | { 19 | return System.Environment.Is64BitOperatingSystem; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /unlockfps_gui/Converters/FullPath2NameConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Avalonia.Data.Converters; 4 | 5 | namespace UnlockFps.Gui.Converters; 6 | 7 | internal sealed class FullPath2NameConverter : IValueConverter 8 | { 9 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 10 | { 11 | if (value is string s) 12 | { 13 | return System.IO.Path.GetFileName(s); 14 | } 15 | 16 | return value?.ToString(); 17 | } 18 | 19 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | } -------------------------------------------------------------------------------- /unlockfps_gui/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.CompilerServices; 3 | using ReactiveUI; 4 | 5 | namespace UnlockFps.Gui.ViewModels; 6 | 7 | public class ViewModelBase : ReactiveObject 8 | { 9 | protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) 10 | { 11 | this.RaisePropertyChanged(propertyName); 12 | } 13 | 14 | protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) 15 | { 16 | if (EqualityComparer.Default.Equals(field, value)) return false; 17 | field = value; 18 | OnPropertyChanged(propertyName); 19 | return true; 20 | } 21 | } -------------------------------------------------------------------------------- /unlockfps_gui/Views/AboutWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Avalonia.Controls; 3 | using Avalonia.Input; 4 | using UnlockFps.Gui.Utils; 5 | 6 | namespace UnlockFps.Gui.Views; 7 | 8 | public partial class AboutWindow : Window 9 | { 10 | public AboutWindow() 11 | { 12 | this.SetSystemChrome(); 13 | InitializeComponent(); 14 | Run_Version.Text = "v" + ReflectionUtil.GetInformationalVersion(); 15 | } 16 | 17 | private void HyperLink_OnTapped(object? sender, TappedEventArgs e) 18 | { 19 | if (sender is TextBlock { Text: { } text }) 20 | { 21 | Process.Start(new ProcessStartInfo(text) { UseShellExecute = true }); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /unlockfps_gui/Converters/Enum2ListConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using Avalonia.Data.Converters; 5 | 6 | namespace UnlockFps.Gui.Converters; 7 | 8 | internal sealed class Enum2ListConverter : IValueConverter 9 | { 10 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 11 | { 12 | if (parameter is Type t && t.IsSubclassOf(typeof(Enum))) 13 | return GetTypeList(t); 14 | if (value is Enum) 15 | return GetTypeList(value.GetType()); 16 | return value; 17 | } 18 | 19 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | 24 | private static object GetTypeList(Type t) 25 | { 26 | var list = Enum.GetValues(t).Cast().ToList(); 27 | return list; 28 | } 29 | } -------------------------------------------------------------------------------- /unlockfps_gui/OSVersionExt/Registry/IRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace OSVersionExt.Registry 7 | { 8 | public interface IRegistry 9 | { 10 | /// 11 | /// Retrieves the value associated with the specified name, in the specified registry key. 12 | /// If the name is not found in the specified key, returns a default value that you provide, or null if the specified key does not exist. 13 | /// 14 | /// The full registry path of the key, beginning with a valid registry root, such as "HKEY_CURRENT_USER". 15 | /// The name of the name/value pair. 16 | /// The value to return if valueName does not exist. 17 | /// 18 | object GetValue(string keyName, string valueName, object defaultValue); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /unlockfps/Utils/TaskUtils.cs: -------------------------------------------------------------------------------- 1 | namespace UnlockFps.Utils; 2 | 3 | public static class TaskUtils 4 | { 5 | public static bool TaskSleep(double milliseconds, CancellationTokenSource cts) 6 | { 7 | return TaskSleep(TimeSpan.FromMilliseconds(milliseconds), cts); 8 | } 9 | 10 | public static bool TaskSleep(double milliseconds, CancellationToken token) 11 | { 12 | return TaskSleep(TimeSpan.FromMilliseconds(milliseconds), token); 13 | } 14 | 15 | public static bool TaskSleep(TimeSpan delay, CancellationTokenSource cts) 16 | { 17 | return TaskSleep(delay, cts.Token); 18 | } 19 | 20 | public static bool TaskSleep(TimeSpan delay, in CancellationToken token) 21 | { 22 | try 23 | { 24 | Task.Delay(delay).Wait(token); 25 | } 26 | catch (TaskCanceledException) 27 | { 28 | return false; 29 | } 30 | catch (OperationCanceledException) 31 | { 32 | return false; 33 | } 34 | 35 | return true; 36 | } 37 | } -------------------------------------------------------------------------------- /unlockfps_gui/Views/AlertWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Interactivity; 4 | 5 | namespace UnlockFps.Gui.Views; 6 | 7 | public partial class AlertWindow : Window 8 | { 9 | public static readonly StyledProperty IsErrorProperty = 10 | AvaloniaProperty.Register(nameof(IsError), true); 11 | 12 | public bool IsError 13 | { 14 | get => GetValue(IsErrorProperty); 15 | set => SetValue(IsErrorProperty, value); 16 | } 17 | 18 | public static readonly StyledProperty TextProperty = 19 | AvaloniaProperty.Register(nameof(Text), "Info"); 20 | 21 | public string Text 22 | { 23 | get => GetValue(TextProperty); 24 | set => SetValue(TextProperty, value); 25 | } 26 | 27 | public AlertWindow() 28 | { 29 | this.SetSystemChrome(); 30 | DataContext = this; 31 | InitializeComponent(); 32 | } 33 | 34 | private void Button_OnClick(object? sender, RoutedEventArgs e) 35 | { 36 | Close(); 37 | } 38 | } -------------------------------------------------------------------------------- /unlockfps_gui/Utils/AssemblyAttributeUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Runtime.Loader; 5 | 6 | namespace UnlockFps.Gui.Utils; 7 | 8 | internal static class AssemblyAttributeUtil 9 | { 10 | public static T? GetAssemblyAttribute(out T? coreAttribute) where T : Attribute 11 | { 12 | var versionAsm = Assembly.GetCallingAssembly(); 13 | var entryAsm = Assembly.GetEntryAssembly(); 14 | var desiredEntryAsmName = versionAsm.GetName().Name?.Split('.')[0]; 15 | var desiredEntryAsm = 16 | AssemblyLoadContext.Default.Assemblies.FirstOrDefault(k => k.GetName().Name == desiredEntryAsmName); 17 | if (desiredEntryAsm != null) 18 | { 19 | versionAsm = desiredEntryAsm; 20 | } 21 | 22 | if (entryAsm == versionAsm) 23 | { 24 | coreAttribute = null; 25 | return versionAsm.GetCustomAttribute(); 26 | } 27 | else 28 | { 29 | coreAttribute = versionAsm.GetCustomAttribute(); 30 | return entryAsm?.GetCustomAttribute(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /unlockfps_nc/Model/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace unlockfps_nc.Model 8 | { 9 | public class Config 10 | { 11 | public string GamePath { get; set; } = ""; 12 | 13 | public bool AutoStart { get; set; } 14 | public bool AutoClose { get; set; } 15 | public bool PopupWindow { get; set; } 16 | public bool Fullscreen { get; set; } = true; 17 | public bool UseCustomRes { get; set; } 18 | public bool IsExclusiveFullscreen { get; set; } 19 | public bool StartMinimized { get; set; } 20 | public bool UsePowerSave { get; set; } 21 | public bool SuspendLoad { get; set; } 22 | public bool UseMobileUI { get; set; } 23 | 24 | public int FPSTarget { get; set; } = 120; 25 | public int CustomResX { get; set; } = 1920; 26 | public int CustomResY { get; set; } = 1080; 27 | public int MonitorNum { get; set; } = 1; 28 | public int Priority { get; set; } = 3; 29 | 30 | public List DllList { get; set; } = new(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /unlockfps_nc/AboutForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using System.Drawing; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | 12 | namespace unlockfps_nc 13 | { 14 | public partial class AboutForm : Form 15 | { 16 | public AboutForm() 17 | { 18 | InitializeComponent(); 19 | } 20 | 21 | private void LinkLabelSource_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 22 | { 23 | OpenLink("https://github.com/34736384/genshin-fps-unlock"); 24 | } 25 | 26 | private void LinkLabelIssues_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 27 | { 28 | OpenLink("https://github.com/34736384/genshin-fps-unlock/issues"); 29 | } 30 | 31 | private void OpenLink(string url) 32 | { 33 | var psi = new ProcessStartInfo 34 | { 35 | FileName = url, 36 | UseShellExecute = true 37 | }; 38 | 39 | Process.Start(psi); 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /unlockfps_gui/Converters/HasItemsConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Globalization; 4 | using System.Linq; 5 | using Avalonia; 6 | using Avalonia.Data.Converters; 7 | 8 | namespace UnlockFps.Gui.Converters; 9 | 10 | internal sealed class HasItemsConverter : IValueConverter 11 | { 12 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 13 | { 14 | if (value is IEnumerable enumerable) 15 | { 16 | var enumerator = enumerable.GetEnumerator(); 17 | var moveNext = enumerator.MoveNext(); 18 | if (enumerator is IDisposable disposable) 19 | { 20 | disposable.Dispose(); 21 | } 22 | 23 | return moveNext; 24 | } 25 | 26 | if (value is int i) 27 | { 28 | return i > 0; 29 | } 30 | 31 | return AvaloniaProperty.UnsetValue; 32 | } 33 | 34 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 35 | { 36 | throw new NotImplementedException(); 37 | } 38 | 39 | private static object GetTypeList(Type t) 40 | { 41 | var list = Enum.GetValues(t).Cast().ToList(); 42 | return list; 43 | } 44 | } -------------------------------------------------------------------------------- /unlockfps_gui/Utils/WineHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace UnlockFps.Gui.Utils; 6 | 7 | internal static partial class WineHelper 8 | { 9 | public static bool DetectWine( 10 | [NotNullWhen(true)] out string? version, 11 | [NotNullWhen(true)] out string? buildId) 12 | { 13 | try 14 | { 15 | version = GetVersion(); 16 | Console.WriteLine("Wine version: " + version); 17 | buildId = GetBuildId(); 18 | Console.WriteLine("Wine build id: " + buildId); 19 | return true; 20 | } 21 | catch (EntryPointNotFoundException) 22 | { 23 | version = null; 24 | buildId = null; 25 | return false; 26 | } 27 | catch (DllNotFoundException) 28 | { 29 | version = null; 30 | buildId = null; 31 | return false; 32 | } 33 | } 34 | 35 | [LibraryImport("ntdll", EntryPoint = "wine_get_version", StringMarshalling = StringMarshalling.Utf8)] 36 | private static partial string GetVersion(); 37 | 38 | [LibraryImport("ntdll", EntryPoint = "wine_get_build_id", StringMarshalling = StringMarshalling.Utf8)] 39 | private static partial string GetBuildId(); 40 | } -------------------------------------------------------------------------------- /unlockfps/Config.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.ComponentModel; 3 | 4 | namespace UnlockFps; 5 | 6 | public partial class LaunchOptions : INotifyPropertyChanged 7 | { 8 | public string? GamePath { get; set; } 9 | 10 | public bool IsWindowBorderless { get; set; } 11 | public bool Fullscreen { get; set; } = true; 12 | public bool IsExclusiveFullscreen { get; set; } 13 | public bool UseCustomResolution { get; set; } 14 | public int CustomResolutionX { get; set; } = 1920; 15 | public int CustomResolutionY { get; set; } = 1080; 16 | public bool UseMobileUI { get; set; } 17 | 18 | public int MonitorId { get; set; } = 1; 19 | 20 | public bool SuspendLoad { get; set; } 21 | public ObservableCollection DllList { get; set; } = new(); 22 | } 23 | 24 | public partial class Config : INotifyPropertyChanged 25 | { 26 | public LaunchOptions LaunchOptions { get; set; } = new(); 27 | 28 | public bool AutoLaunch { get; set; } 29 | public bool AutoClose { get; set; } 30 | public bool UsePowerSave { get; set; } 31 | public int FpsTarget { get; set; } = 120; 32 | public int FpsPowerSave { get; set; } = 10; 33 | public int ProcessPriority { get; set; } = 3; 34 | public bool ShowDebugConsole { get; set; } 35 | public bool WindowQueryUseEvent { get; set; } = true; 36 | } -------------------------------------------------------------------------------- /unlockfps_gui/AppBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.Media; 4 | using SkiaSharp; 5 | 6 | namespace UnlockFps.Gui; 7 | 8 | public static class AppBuilderExtensions 9 | { 10 | public static AppBuilder WithNativeFonts(this AppBuilder appBuilder, string? specificFontFamily = null) 11 | { 12 | string? familyName = null; 13 | 14 | if (specificFontFamily != null) 15 | { 16 | var family = SKFontManager.Default.MatchFamily(specificFontFamily); 17 | familyName = family?.FamilyName; 18 | } 19 | 20 | FontManagerOptions options = new(); 21 | 22 | if (familyName == null) 23 | { 24 | familyName = SKFontManager.Default.MatchCharacter('a')?.FamilyName; 25 | if (familyName == null) 26 | { 27 | Console.Error.WriteLine("Cannot find default font."); 28 | } 29 | } 30 | 31 | if (familyName != null) 32 | { 33 | options.DefaultFamilyName = familyName; 34 | 35 | var fontFallbacks = new FontFallback[] 36 | { 37 | new() { FontFamily = FontFamily.Parse(familyName) }, 38 | }; 39 | options.FontFallbacks = fontFallbacks; 40 | } 41 | 42 | return appBuilder.With(options); 43 | } 44 | } -------------------------------------------------------------------------------- /unlockfps_nc/unlockfps_nc.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | true 8 | enable 9 | Resources\icon.ico 10 | Resources\app.manifest 11 | x64 12 | False 13 | true 14 | 7.0 15 | 16 | 17 | 18 | full 19 | 20 | 21 | 22 | full 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /unlockfps_gui/Converters/DelegateConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Avalonia.Data.Converters; 4 | 5 | namespace UnlockFps.Gui.Converters; 6 | 7 | public class DelegateConverter : IValueConverter 8 | { 9 | public delegate object? ConvertDelegate(object? value, Type targetType, object? parameter, CultureInfo culture); 10 | 11 | public delegate object? ConvertBackDelegate(object? value, Type targetType, object? parameter, 12 | CultureInfo culture); 13 | 14 | private readonly ConvertDelegate _convert; 15 | private readonly ConvertBackDelegate? _convertBack; 16 | 17 | public DelegateConverter(ConvertDelegate convert, ConvertBackDelegate? convertBack = null) 18 | { 19 | _convert = convert ?? throw new ArgumentNullException(nameof(convert)); 20 | _convertBack = convertBack; 21 | } 22 | 23 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 24 | { 25 | return _convert(value, targetType, parameter, culture); 26 | } 27 | 28 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 29 | { 30 | if (_convertBack == null) 31 | throw new NotImplementedException($"ConvertBack() of {GetType().Name} is not implemented."); 32 | return _convertBack(value, targetType, parameter, culture); 33 | } 34 | } -------------------------------------------------------------------------------- /unlockfps_nc/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System.Runtime.InteropServices; 3 | using unlockfps_nc.Service; 4 | using unlockfps_nc.Utility; 5 | 6 | namespace unlockfps_nc 7 | { 8 | internal static class Program 9 | { 10 | private static IntPtr MutexHandle = IntPtr.Zero; 11 | public static IServiceProvider ServiceProvider { get; private set; } 12 | 13 | [STAThread] 14 | static void Main() 15 | { 16 | MutexHandle = Native.CreateMutex(IntPtr.Zero, true, @"GenshinFPSUnlocker"); 17 | if (Marshal.GetLastWin32Error() == 183) 18 | { 19 | MessageBox.Show(@"Another unlocker is already running.", @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 20 | return; 21 | } 22 | 23 | var services = new ServiceCollection(); 24 | services.AddTransient(); 25 | services.AddTransient(); 26 | services.AddTransient(); 27 | services.AddSingleton(); 28 | services.AddSingleton(); 29 | 30 | ServiceProvider = services.BuildServiceProvider(); 31 | 32 | ApplicationConfiguration.Initialize(); 33 | Application.Run(ServiceProvider.GetRequiredService()); 34 | } 35 | 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /unlockfps_gui/OSVersionExt/Win32API/Win32ApiProvider.cs: -------------------------------------------------------------------------------- 1 | using OSVersionExt.Win32API; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Security; 7 | using System.Text; 8 | 9 | namespace OSVersionExt 10 | { 11 | /// 12 | /// Win32 API Provider 13 | /// 14 | /// CLR wrapper https://github.com/microsoft/referencesource/blob/master/mscorlib/microsoft/win32/win32native.cs 15 | public class Win32ApiProvider : IWin32API 16 | { 17 | private const String NTDLL = "ntdll.dll"; 18 | private const String USER32 = "user32.dll"; 19 | 20 | [SecurityCritical] 21 | [DllImport(NTDLL, EntryPoint = "RtlGetVersion", SetLastError = true, CharSet = CharSet.Unicode)] 22 | internal static extern NTSTATUS ntdll_RtlGetVersion(ref OSVERSIONINFOEX versionInfo); 23 | 24 | [DllImport(USER32, EntryPoint = "GetSystemMetrics")] 25 | internal static extern int ntdll_GetSystemMetrics(SystemMetric smIndex); 26 | 27 | 28 | public NTSTATUS RtlGetVersion(ref OSVERSIONINFOEX versionInfo) 29 | { 30 | return ntdll_RtlGetVersion(ref versionInfo); 31 | } 32 | 33 | public int GetSystemMetrics(SystemMetric smIndex) 34 | { 35 | return ntdll_GetSystemMetrics(smIndex); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /unlockfps/Utils/WineHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Runtime.InteropServices; 3 | using UnlockFps.Logging; 4 | 5 | namespace UnlockFps.Utils; 6 | 7 | internal static partial class WineHelper 8 | { 9 | private static readonly ILogger Logger = LogManager.GetLogger(nameof(WineHelper)); 10 | 11 | public static bool DetectWine( 12 | [NotNullWhen(true)] out string? version, 13 | [NotNullWhen(true)] out string? buildId) 14 | { 15 | try 16 | { 17 | version = GetVersion(); 18 | Logger.LogInformation($"Wine version: {version}"); 19 | buildId = GetBuildId(); 20 | Logger.LogInformation($"Wine build id: {buildId}"); 21 | return true; 22 | } 23 | catch (EntryPointNotFoundException) 24 | { 25 | version = null; 26 | buildId = null; 27 | return false; 28 | } 29 | catch (DllNotFoundException) 30 | { 31 | version = null; 32 | buildId = null; 33 | return false; 34 | } 35 | } 36 | 37 | [LibraryImport("ntdll", EntryPoint = "wine_get_version", StringMarshalling = StringMarshalling.Utf8)] 38 | private static partial string GetVersion(); 39 | 40 | [LibraryImport("ntdll", EntryPoint = "wine_get_build_id", StringMarshalling = StringMarshalling.Utf8)] 41 | private static partial string GetBuildId(); 42 | } -------------------------------------------------------------------------------- /unlockfps_nc/Service/ConfigService.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using unlockfps_nc.Model; 8 | 9 | namespace unlockfps_nc.Service 10 | { 11 | public class ConfigService 12 | { 13 | private const string ConfigName = "fps_config.json"; 14 | 15 | public Config Config { get; private set; } = new(); 16 | 17 | public ConfigService() 18 | { 19 | Load(); 20 | Sanitize(); 21 | } 22 | 23 | private void Load() 24 | { 25 | if (!File.Exists(ConfigName)) 26 | return; 27 | 28 | var json = File.ReadAllText(ConfigName); 29 | Config = JsonConvert.DeserializeObject(json); 30 | } 31 | 32 | private void Sanitize() 33 | { 34 | Config.FPSTarget = Math.Clamp(Config.FPSTarget, 1, 420); 35 | Config.Priority = Math.Clamp(Config.Priority, 0, 5); 36 | Config.CustomResX = Math.Clamp(Config.CustomResX, 200, 7680); 37 | Config.CustomResY = Math.Clamp(Config.CustomResY, 200, 4320); 38 | Config.MonitorNum = Math.Clamp(Config.MonitorNum, 1, 100); 39 | } 40 | 41 | public void Save() 42 | { 43 | var json = JsonConvert.SerializeObject(Config, Formatting.Indented); 44 | File.WriteAllText(ConfigName, json); 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /unlockfps/Utils/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace UnlockFps.Utils; 4 | 5 | internal static partial class NativeMethods 6 | { 7 | public delegate void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); 8 | 9 | [LibraryImport("user32.dll")] 10 | public static partial nint GetForegroundWindow(); 11 | 12 | [LibraryImport("user32.dll")] 13 | public static partial uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 14 | 15 | [LibraryImport("kernel32.dll", SetLastError = true)] 16 | [return: MarshalAs(UnmanagedType.Bool)] 17 | public static partial bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, Span lpBuffer, int nSize, out int lpNumberOfBytesWritten); 18 | 19 | [LibraryImport("kernel32.dll", SetLastError = true)] 20 | [return: MarshalAs(UnmanagedType.Bool)] 21 | public static partial bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, Span lpBuffer, int nSize, out int lpNumberOfBytesRead); 22 | 23 | [LibraryImport("kernel32.dll", EntryPoint = "LoadLibraryExW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] 24 | public static partial nint LoadLibraryEx(string lpLibFileName, IntPtr hFile, uint dwFlags); 25 | 26 | [LibraryImport("user32.dll", SetLastError = true)] 27 | public static partial IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, 28 | WinEventProc lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); 29 | 30 | } -------------------------------------------------------------------------------- /unlockfps_gui/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Avalonia; 4 | using Avalonia.ReactiveUI; 5 | using UnlockFps.Gui.Utils; 6 | 7 | namespace UnlockFps.Gui; 8 | 9 | internal sealed class Program 10 | { 11 | // Initialization code. Don't use any Avalonia, third-party APIs or any 12 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 13 | // yet and stuff might break. 14 | [STAThread] 15 | public static void Main(string[] args) 16 | { 17 | using (new Mutex(true, @"GenshinFPSUnlocker", out var createdNew)) 18 | { 19 | DuplicatedInstance = !createdNew; 20 | BuildAvaloniaApp() 21 | .StartWithClassicDesktopLifetime(args); 22 | } 23 | } 24 | 25 | public static bool DuplicatedInstance { get; private set; } 26 | 27 | // Avalonia configuration, don't remove; also used by visual designer. 28 | public static AppBuilder BuildAvaloniaApp() 29 | { 30 | var appBuilder = AppBuilder.Configure() 31 | .UsePlatformDetect() 32 | .WithNativeFonts() 33 | .LogToTrace() 34 | .UseReactiveUI(); 35 | if (WineHelper.DetectWine(out _, out _)) 36 | { 37 | return appBuilder 38 | .With(new Win32PlatformOptions 39 | { 40 | CompositionMode = [Win32CompositionMode.RedirectionSurface], 41 | RenderingMode = [Win32RenderingMode.Software], 42 | OverlayPopups = true 43 | }); 44 | } 45 | else 46 | { 47 | return appBuilder; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /unlockfps/unlockfps.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | true 9 | app.manifest 10 | UnlockFps 11 | unlockfps_cli 12 | 13 | 14 | 15 | true 16 | 17 | true 18 | true 19 | true 20 | 21 | 22 | 23 | 24 | 25 | all 26 | 27 | 28 | 29 | all 30 | compile; runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | all 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /unlockfps/Logging/LogManager.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace UnlockFps.Logging; 4 | 5 | public static class LogManager 6 | { 7 | private static ILoggerFactory? _loggerFactory; 8 | public static void SetLoggerFactory(ILoggerFactory loggerFactory) => _loggerFactory = loggerFactory; 9 | public static ILogger GetLogger(string name) => _loggerFactory?.CreateLogger(name) ?? new ConsoleLogger(name); 10 | //public static void Info(string message) 11 | //{ 12 | // var name = GetClassName() ?? nameof(Logger); 13 | // if (_loggerFactory is null) Console.WriteLine(message); 14 | // else _loggerFactory.CreateLogger(name).LogInformation(message); 15 | //} 16 | 17 | //public static void Debug(string message) 18 | //{ 19 | // var name = GetClassName() ?? nameof(Logger); 20 | // if (_loggerFactory is null) Console.WriteLine(message); 21 | // else _loggerFactory.CreateLogger(name).LogDebug(message); 22 | //} 23 | 24 | //public static void Error(string message) 25 | //{ 26 | // var name = GetClassName() ?? nameof(Logger); 27 | // if (_loggerFactory is null) Console.WriteLine(message); 28 | // else _loggerFactory.CreateLogger(name).LogError(message); 29 | //} 30 | 31 | //public static void Warn(string message) 32 | //{ 33 | // var name = GetClassName() ?? nameof(Logger); 34 | // if (_loggerFactory is null) Console.WriteLine(message); 35 | // else _loggerFactory.CreateLogger(name).LogWarning(message); 36 | //} 37 | 38 | //private static string? GetClassName() 39 | //{ 40 | // var methodInfo = new StackTrace().GetFrame(2)?.GetMethod(); 41 | // return methodInfo?.ReflectedType?.Name; 42 | //} 43 | } -------------------------------------------------------------------------------- /unlockfps_gui/Views/AboutWindow.axaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | env: 3 | ProjectName: genshin-fps-unlock 4 | on: 5 | create: 6 | tags: 7 | - "v*.*.*" 8 | 9 | jobs: 10 | build: 11 | name: Build and Release 12 | if: ${{ StartsWith(github.ref, 'refs/tags/') }} 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: '0' 20 | submodules: 'recursive' 21 | 22 | - name: Setup .NET 23 | uses: actions/setup-dotnet@v1 24 | with: 25 | dotnet-version: 8.0.x 26 | 27 | - name: Publish 28 | if: ${{ success() }} 29 | shell: pwsh 30 | run: | 31 | echo ${{ github.ref }} 32 | dotnet publish unlockfps_gui --runtime win-x64 --configuration Release --output ci-publish-win64 33 | rm ./ci-publish-win64/*.pdb 34 | rm ./ci-publish-win64/unlockfps_cli.* 35 | 36 | - name: Get tag 37 | uses: dawidd6/action-get-tag@v1 38 | if: ${{ success() && startsWith(github.ref, 'refs/tags/') }} 39 | id: tag 40 | 41 | - name: Pack via 7z 42 | if: ${{ success() && startsWith(github.ref, 'refs/tags/') }} 43 | run: | 44 | mkdir -p ./ci-pack/ 45 | 7z a -mx9 -mfb=273 -ms -md=31 -myx=9 -mtm=- -mmt -mmtf -md=1536m -mmf=bt3 -mmc=10000 -mpb=0 -mlc=0 "./ci-pack/${{ env.ProjectName }}-${{ steps.tag.outputs.tag }}-win64.7z" "./ci-publish-win64/*" -x!"${{ env.ProjectName }}" -r 46 | 47 | - name: Create a new GitHub release if a new tag is pushed 48 | uses: softprops/action-gh-release@v1 49 | if: ${{ success() && startsWith(github.ref, 'refs/tags/') }} 50 | env: 51 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 52 | with: 53 | name: ${{ steps.tag.outputs.tag }} 54 | prerelease: true 55 | draft: false 56 | files: | 57 | ./ci-pack/${{ env.ProjectName }}-${{ steps.tag.outputs.tag }}-win64.7z -------------------------------------------------------------------------------- /unlockfps_gui/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | true/PM 30 | PerMonitorV2, PerMonitor 31 | 32 | 33 | 34 | 35 | 36 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /unlockfps_nc.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34330.188 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "unlockfps_nc", "unlockfps_nc\unlockfps_nc.csproj", "{AB706C74-000D-4A43-909C-D0EE906003B8}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "unlockfps_gui", "unlockfps_gui\unlockfps_gui.csproj", "{2E8443BA-176B-4586-A393-7B6A0EB07E40}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "unlockfps", "unlockfps\unlockfps.csproj", "{3487E54A-20D4-496E-8764-1BE6AB4CCD61}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {AB706C74-000D-4A43-909C-D0EE906003B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {AB706C74-000D-4A43-909C-D0EE906003B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {AB706C74-000D-4A43-909C-D0EE906003B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {AB706C74-000D-4A43-909C-D0EE906003B8}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {2E8443BA-176B-4586-A393-7B6A0EB07E40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {2E8443BA-176B-4586-A393-7B6A0EB07E40}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {2E8443BA-176B-4586-A393-7B6A0EB07E40}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {2E8443BA-176B-4586-A393-7B6A0EB07E40}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {3487E54A-20D4-496E-8764-1BE6AB4CCD61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {3487E54A-20D4-496E-8764-1BE6AB4CCD61}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {3487E54A-20D4-496E-8764-1BE6AB4CCD61}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {3487E54A-20D4-496E-8764-1BE6AB4CCD61}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {BA9973A2-2E7A-4D69-A824-D9A1B1CCF56E} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /unlockfps_gui/Views/AlertWindow.axaml: -------------------------------------------------------------------------------- 1 | 15 | 19 | 20 | 21 | 26 | 32 | 33 | 38 | 39 | 40 | 44 | 45 |