enhanced config and keybindings

This commit is contained in:
Taskeren 2024-04-06 03:41:56 +08:00
parent 9dbf8306af
commit 2796c5bb1e
No known key found for this signature in database
GPG Key ID: BC3749B9DFBE1F00
18 changed files with 507 additions and 148 deletions

View File

@ -72,15 +72,21 @@
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Config.cs" />
<Compile Include="HotkeyManager.cs" />
<Compile Include="OnEvent.cs" />
<Compile Include="VirtualKey.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="OverlayWindow.xaml.cs">
<Compile Include="Data\Config.cs" />
<Compile Include="Utils\OnEvent.cs" />
<Compile Include="Utils\VirtualKey.cs" />
<Compile Include="Windows\ConfigWindow.xaml.cs">
<DependentUpon>ConfigWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\Control\HotkeyManager.cs" />
<Compile Include="Windows\Dialog\KeybindingDialog.xaml.cs">
<DependentUpon>KeybindingDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\OverlayWindow.xaml.cs">
<DependentUpon>OverlayWindow.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Tsf.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
@ -92,9 +98,13 @@
</ItemGroup>
<ItemGroup>
<Content Include=".editorconfig" />
<Content Include="manual_zh.md" />
<Content Include="README.md" />
</ItemGroup>
<ItemGroup>
<Page Include="OverlayWindow.xaml" />
<Page Include="Windows\ConfigWindow.xaml" />
<Page Include="Windows\Dialog\KeybindingDialog.xaml" />
<Page Include="Windows\OverlayWindow.xaml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"/>
</Project>

View File

@ -1,7 +1,7 @@
<Application x:Class="_3074.App"
<Application x:Class="tsf_3074.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:_3074"
xmlns:local="clr-namespace:tsf_3074"
Startup="App_OnStartup"
DispatcherUnhandledException="App_OnDispatcherUnhandledException">
<Application.Resources>

View File

@ -3,8 +3,11 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Threading;
using tsf_3074.Data;
using tsf_3074.Window;
using tsf_3074.Windows.Dialog;
namespace _3074
namespace tsf_3074
{
/// <summary>
/// Interaction logic for App.xaml
@ -14,6 +17,7 @@ namespace _3074
private void App_OnStartup(object sender, StartupEventArgs e)
{
SetCurrentProcessExplicitAppUserModelID("com.squirrel.Tsf");
Config.Instance.ToString();
Tsf.FFullGame.ToString();
var window = new OverlayWindow();
window.Show();

View File

@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
namespace _3074
{
public static class Config
{
public static ConfigData Instance => _instance ?? ReadConfig();
private static ConfigData _instance;
private static ConfigData ReadConfig(string path = "config.json")
{
if (File.Exists(path))
{
return _instance = JsonConvert.DeserializeObject<ConfigData>(File.ReadAllText(path));
}
var defaults = new ConfigData
{
Destiny2Path = "Your Destiny 2 application path"
};
File.WriteAllText(path, JsonConvert.SerializeObject(defaults));
throw new Exception("Config is not set");
}
}
public class ConfigData
{
public string Destiny2Path;
}
}

79
Data/Config.cs Normal file
View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using Microsoft.Win32;
using Newtonsoft.Json;
namespace tsf_3074.Data
{
public static class Config
{
public static ConfigData Instance => _instance ?? ReadConfig();
private static ConfigData _instance;
private static ConfigData ReadConfig(string path = "config.json")
{
if (File.Exists(path))
{
return _instance = JsonConvert.DeserializeObject<ConfigData>(File.ReadAllText(path));
}
FindDestiny2(out var appPath);
if (appPath == "")
{
MessageBox.Show("You'd better to read the README document.");
Application.Current.Shutdown();
throw new Exception();
}
_instance = new ConfigData
{
Destiny2Path = appPath,
Hotkeys = new Dictionary<string, HotkeyPair>
{
{ "tsf_3074", new HotkeyPair { vk = VirtualKey.VkG, fs = VirtualKey.ModCtrl } },
{ "tsf_3074_ul", new HotkeyPair { vk = VirtualKey.VkH, fs = VirtualKey.ModCtrl } },
{ "tsf_7500", new HotkeyPair { vk = VirtualKey.VkT, fs = VirtualKey.ModCtrl } },
{ "tsf_fg", new HotkeyPair { vk = VirtualKey.VkJ, fs = VirtualKey.ModCtrl } },
{ "tsf_27k", new HotkeyPair { vk = VirtualKey.VkN, fs = VirtualKey.ModAlt } },
{ "tsf_30k", new HotkeyPair { vk = VirtualKey.VkB, fs = VirtualKey.ModAlt } },
}
};
File.WriteAllText(path, JsonConvert.SerializeObject(_instance));
return _instance;
}
public static void SaveConfig(string path = "config.json")
{
File.WriteAllText(path, JsonConvert.SerializeObject(_instance));
}
private static void FindDestiny2(out string appPath)
{
var dialog = new OpenFileDialog
{
Multiselect = false,
Title = "Select Destiny 2 Application",
Filter = "Destiny 2|destiny2.exe"
};
dialog.ShowDialog();
Console.WriteLine(dialog.FileName);
appPath = dialog.FileName;
}
}
public class ConfigData
{
public string Destiny2Path;
public Dictionary<string, HotkeyPair> Hotkeys;
}
public class HotkeyPair
{
public uint vk;
public uint fs;
}
}

View File

@ -1,78 +0,0 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
namespace _3074
{
public partial class OverlayWindow : Window
{
internal readonly HotkeyManager _hotkey;
public OverlayWindow()
{
InitializeComponent();
DataContext = new OverlayViewModel();
_hotkey = new HotkeyManager(this);
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
TsfFilter.AllFilters.ForEach(f => f.RegisterHotkey(_hotkey));
_hotkey.OnSourceInitialized();
}
protected override void OnClosed(EventArgs e)
{
_hotkey.OnClosed();
base.OnClosed(e);
}
}
public class OverlayViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string F3074 => Tsf.F3074.GetState();
public string F3074UL => Tsf.F3074UL.GetState();
public string F7500 => Tsf.F7500.GetState();
public string F27K => Tsf.F27K.GetState();
public string F30K => Tsf.F30K.GetState();
public string FFullGame => Tsf.FFullGame.GetState();
public OverlayViewModel()
{
Tsf.F3074.OnStateChanged += () => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(F3074)));
Tsf.F3074UL.OnStateChanged += () => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(F3074UL)));
Tsf.F7500.OnStateChanged += () => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(F7500)));
Tsf.F27K.OnStateChanged += () => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(F27K)));
Tsf.F30K.OnStateChanged += () => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(F30K)));
Tsf.FFullGame.OnStateChanged += () => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FFullGame)));
}
}
public class OnOrOffColorSelector : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is string str)) return Colors.Orange;
switch (str.ToLower())
{
case "on":
return Colors.GreenYellow;
case "off":
return Colors.Red;
default:
return Colors.BlueViolet;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new Exception();
}
}
}

1
README.md Normal file
View File

@ -0,0 +1 @@
# Thirty Seventy Four (3074)

71
Tsf.cs
View File

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using NetLimiter.Service;
using tsf_3074.Data;
namespace _3074
namespace tsf_3074
{
public static class Tsf
{
@ -28,12 +30,23 @@ namespace _3074
uint fs = 0, uint vk = 0)
{
var cli = GetClient();
var name = GetFilterNameFor(portName);
var name = GetFilterNameFor(portName, upload);
var filter = cli.Filters.FirstOrDefault(f => f.Name == name) ??
cli.AddFilter(new Filter(name)
{
Functions = { new FFRemotePortInRange(new PortRangeFilterValue(portStart, portEnd)) }
Functions =
{
new FFPathEqual(Config.Instance.Destiny2Path),
new FFRemotePortInRange(new PortRangeFilterValue(portStart, portEnd))
}
});
var hotkey = Config.Instance.Hotkeys[name];
if (hotkey != null)
{
vk = hotkey.vk;
fs = hotkey.fs;
}
return new TsfFilter(name, filter, cli, upload, 1, fs, vk);
}
@ -46,6 +59,13 @@ namespace _3074
{
Functions = { new FFPathEqual(Config.Instance.Destiny2Path) }
});
var hotkey = Config.Instance.Hotkeys[name];
if (hotkey != null)
{
vk = hotkey.vk;
fs = hotkey.fs;
}
return new TsfFilter(name, filter, cli, false, 811, fs, vk);
}
@ -74,11 +94,20 @@ namespace _3074
private readonly NLClient _client;
private readonly bool _upload;
private readonly uint _limitSize; // the limit rate, normally is 1, only FG is 800 or 811
private readonly uint _fs;
private readonly uint _vk;
private uint _fs;
private uint _vk;
public string Name => _name;
public uint FS => _fs;
public uint VK => _vk;
private Rule _rule;
/**
* Invoked every time the IsEnable is changed. (Maybe not actually changed, like from false to false.)
*/
public event OnEvent OnStateChanged = delegate { };
public static readonly List<TsfFilter> AllFilters = new List<TsfFilter>();
@ -94,32 +123,48 @@ namespace _3074
_fs = fs;
_vk = vk;
GetLimitRule();
InitLimitRule();
AllFilters.Add(this);
}
public void RegisterHotkey(HotkeyManager manager)
~TsfFilter()
{
AllFilters.Remove(this);
}
/**
* Set the keybindings. Need to reload the hotkey manager manually!
*/
public void SetHotkey(uint vk, uint fs)
{
_vk = vk;
_fs = fs;
}
/**
* Register the hotkey to the HotkeyManager.
*/
internal void RegisterHotkey(HotkeyManager manager)
{
if (_vk != 0)
{
Console.WriteLine($"Registering Hotkey for {_name}");
Console.WriteLine(
$"Registering Hotkey for {_name} {VirtualKey.GetModifierName(_fs)} + {KeyInterop.KeyFromVirtualKey((int)_vk)}");
manager.RegisterHotkey(_fs, _vk, () =>
{
Toggle();
var currState = GetLimitRule().IsEnabled ? "On" : "Off";
Console.WriteLine($"Updated [{_name}] {currState}");
Console.WriteLine($"Updated {_name} to {GetState()}");
});
}
}
private Rule GetLimitRule()
private void InitLimitRule()
{
if (_rule != null) return _rule;
if (_rule != null) return;
var dir = _upload ? RuleDir.Out : RuleDir.In;
_rule = _client.Rules.FirstOrDefault(r => r.FilterId == _filter.Id && r.Dir == dir) ??
_client.AddRule(_filter.Id, new LimitRule(dir, _limitSize) { IsEnabled = false });
return _rule;
}
public void Toggle()

View File

@ -1,4 +1,4 @@
namespace _3074
namespace tsf_3074
{
public delegate void OnEvent();
}

View File

@ -1,4 +1,4 @@
namespace _3074
namespace tsf_3074
{
public static class VirtualKey
{
@ -8,6 +8,22 @@
public const uint ModShift = 0x0004;
public const uint ModWin = 0x0008;
public static string GetModifierName(uint mod)
{
switch (mod)
{
case ModNone: return "None";
case ModAlt: return "Alt";
case ModCtrl: return "Ctrl";
case ModShift: return "Shift";
case ModCtrl | ModAlt: return "Ctrl + Alt";
case ModCtrl | ModShift: return "Ctrl + Shift";
case ModAlt | ModShift: return "Alt + Shift";
case ModCtrl | ModAlt | ModShift: return "Ctrl + Shift + Alt";
default: return "Unknown";
}
}
// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
public const uint VkLButton = 0x01; // Left mouse button

27
Windows/ConfigWindow.xaml Normal file
View File

@ -0,0 +1,27 @@
<Window x:Class="tsf_3074.Windows.ConfigWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:tsf_3074.Window"
mc:Ignorable="d"
Title="ConfigWindow" Height="250" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Grid.Row="0" Grid.Column="0" Click="ButtonBase_OnClick">3074</Button>
<Button Grid.Row="0" Grid.Column="1" Click="ButtonBase_OnClick">3074 UL</Button>
<Button Grid.Row="1" Grid.Column="0" Click="ButtonBase_OnClick">7500</Button>
<Button Grid.Row="1" Grid.Column="1" Click="ButtonBase_OnClick">27k</Button>
<Button Grid.Row="2" Grid.Column="0" Click="ButtonBase_OnClick">30k</Button>
<Button Grid.Row="2" Grid.Column="1" Click="ButtonBase_OnClick">FG</Button>
</Grid>
</Window>

View File

@ -0,0 +1,74 @@
using System;
using System.Windows;
using System.Windows.Input;
using tsf_3074.Windows.Dialog;
namespace tsf_3074.Windows
{
public partial class ConfigWindow : System.Windows.Window
{
// dont use this ctor,
// use #ShowConfigDialog instead!
public ConfigWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (!(e.Source is System.Windows.Controls.Button button)) return;
switch (button.Content)
{
case "3074":
SetHotkeyFor(Tsf.F3074);
break;
case "3074 UL":
SetHotkeyFor(Tsf.F3074UL);
break;
case "7500":
SetHotkeyFor(Tsf.F7500);
break;
case "27k":
SetHotkeyFor(Tsf.F27K);
break;
case "30k":
SetHotkeyFor(Tsf.F30K);
break;
case "FG":
SetHotkeyFor(Tsf.FFullGame);
break;
}
}
/**
* Use KeybindingDialog to receive a keybinding,
* and set the value to TsfFilter.
* When the Config window exits, the hotkeys should be reloaded.
*/
private static void SetHotkeyFor(TsfFilter filter)
{
KeybindingDialog.OpenKeybindingDialog(out var key, out var fs,
KeyInterop.KeyFromVirtualKey((int)filter.VK), filter.FS);
// don't change if escape is pressed
if (key == Key.Escape) return;
filter.SetHotkey((uint)KeyInterop.VirtualKeyFromKey(key), fs);
}
private static bool _showing;
/**
* Use this static method to ensure there is only 1 instance of Config window.
*/
public static void ShowConfigDialog()
{
if (_showing) return;
_showing = true;
var window = new ConfigWindow();
window.Closed += (sender, e) => { _showing = false; };
window.ShowDialog();
}
}
}

View File

@ -1,16 +1,15 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace _3074
namespace tsf_3074
{
public partial class HotkeyManager
{
private readonly Window _window;
private readonly System.Windows.Window _window;
public HotkeyManager(Window window)
public HotkeyManager(System.Windows.Window window)
{
_window = window;
}
@ -22,6 +21,10 @@ namespace _3074
private const int HotkeyIdStart = 9000;
private int _hotkeyId = HotkeyIdStart;
/**
* Register a hotkey with given modifiers and virtual keys.
* When the keybinding is pressed, the onEvent is invoked.
*/
public void RegisterHotkey(uint fsModifiers, uint vk, OnEvent onEvent)
{
var thisId = _hotkeyId++;
@ -29,7 +32,7 @@ namespace _3074
_listener[thisId] = onEvent;
}
private void UnregisterAllHotkeys()
public void UnregisterAllHotkeys()
{
var helper = new WindowInteropHelper(_window);
while (_hotkeyId >= HotkeyIdStart)
@ -38,12 +41,18 @@ namespace _3074
}
}
/**
* Add the hook to the Overlay window
*/
public void OnSourceInitialized()
{
_source = HwndSource.FromHwnd(new WindowInteropHelper(_window).Handle) ?? throw new Exception();
_source.AddHook(HwndHook);
}
/**
* Remove the hook from Overlay window
*/
public void OnClosed()
{
_source.RemoveHook(HwndHook);
@ -51,6 +60,10 @@ namespace _3074
UnregisterAllHotkeys();
}
/**
* The callback for keybinding being pressed.
* Don't change this function, use registerHotkey!
*/
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg != 0x0312) return IntPtr.Zero;

View File

@ -0,0 +1,14 @@
<Window x:Class="tsf_3074.Windows.Dialog.KeybindingDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:tsf_3074.Windows.Dialog"
mc:Ignorable="d"
Title="KeybindingDialog" Height="400" Width="600" d:DataContext="{d:DesignInstance local:KeybindingDialogView}"
KeyDown="KeybindingDialog_OnKeyDown"
Topmost="True">
<Grid>
<TextBlock Text="{Binding StatusText}" FontSize="32" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
</Grid>
</Window>

View File

@ -0,0 +1,84 @@
using System.ComponentModel;
using System.Windows.Input;
namespace tsf_3074.Windows.Dialog
{
public partial class KeybindingDialog : System.Windows.Window
{
private readonly KeybindingDialogView _data = new KeybindingDialogView();
public KeybindingDialog()
{
InitializeComponent();
DataContext = _data;
}
private void KeybindingDialog_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return || e.Key == Key.Escape)
{
Close();
return;
}
var ctrl = Keyboard.IsKeyDown(Key.LeftCtrl);
var alt = Keyboard.IsKeyDown(Key.LeftAlt);
var shift = Keyboard.IsKeyDown(Key.LeftShift);
var fs = ctrl ? VirtualKey.ModCtrl :
alt ? VirtualKey.ModAlt :
shift ? VirtualKey.ModShift : 0;
_data.SetKey(e.Key, fs);
}
public static void OpenKeybindingDialog(out Key key, out uint fs, Key initKey = Key.None, uint initFs = 0)
{
var dialog = new KeybindingDialog
{
_data =
{
Key = initKey,
Modifier = initFs
}
};
// wait for exit, and put the values to the ref fields
dialog.ShowDialog();
key = dialog._data.Key;
fs = dialog._data.Modifier;
}
}
public class KeybindingDialogView : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string StatusText { get; set; } = "Press keys";
internal Key Key { get; set; }
internal uint Modifier { get; set; }
public void SetKey(Key key, uint fsMod)
{
if (key == Key.LeftCtrl || key == Key.LeftAlt || key == Key.LeftShift || key == Key.System) return;
Key = key;
Modifier = fsMod;
if (fsMod != 0)
{
var mod = fsMod == VirtualKey.ModCtrl ? "Ctrl" :
fsMod == VirtualKey.ModAlt ? "Alt" :
fsMod == VirtualKey.ModShift ? "Shift" : "Unknown";
StatusText = $"{mod} + {Key}";
}
else
{
StatusText = $"{Key}";
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StatusText)));
}
}
}

View File

@ -1,16 +1,16 @@
<Window x:Class="_3074.OverlayWindow"
<Window x:Class="tsf_3074.Window.OverlayWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:_3074"
xmlns:window="clr-namespace:tsf_3074.Window"
mc:Ignorable="d"
Title="Thirty-Seventy-Four Overlay" Height="100" Width="120"
Background="{x:Null}" ResizeMode="NoResize"
AllowsTransparency="True"
WindowStyle="None" Topmost="True"
WindowStartupLocation="Manual" Top="0" Left="0"
FontFamily="Consolas" d:DataContext="{d:DesignInstance local:OverlayViewModel}">
FontFamily="Consolas" d:DataContext="{d:DesignInstance window:OverlayViewModel}">
<Grid Margin="15">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>

View File

@ -0,0 +1,82 @@
using System;
using System.ComponentModel;
using tsf_3074.Data;
using tsf_3074.Windows;
namespace tsf_3074.Window
{
public partial class OverlayWindow : System.Windows.Window
{
private readonly HotkeyManager _hotkey;
public OverlayWindow()
{
InitializeComponent();
DataContext = new OverlayViewModel();
_hotkey = new HotkeyManager(this);
}
private void ReloadAllHotkeys(bool firstTime = false)
{
if (!firstTime) _hotkey.UnregisterAllHotkeys();
TsfFilter.AllFilters.ForEach(f => f.RegisterHotkey(_hotkey));
// add additional global keybindings (Alt+C)
_hotkey.RegisterHotkey(VirtualKey.ModAlt, VirtualKey.VkC, () =>
{
Console.WriteLine("Configuration hotkey is pressed!");
_hotkey.UnregisterAllHotkeys(); // unregister all hotkeys to prevent the issue, which the taken hotkeys are not able to be read from the dialog.
ConfigWindow.ShowConfigDialog();
Console.WriteLine("Configuration dialog exited.");
Console.WriteLine("Saving configuration!");
TsfFilter.AllFilters.ForEach(f =>
{
Config.Instance.Hotkeys[f.Name] = new HotkeyPair { vk = f.VK, fs = f.FS };
});
Config.SaveConfig();
Console.WriteLine("Refreshing hotkeys!");
ReloadAllHotkeys();
});
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
ReloadAllHotkeys(true);
_hotkey.OnSourceInitialized();
}
protected override void OnClosed(EventArgs e)
{
_hotkey.OnClosed();
base.OnClosed(e);
}
}
public class OverlayViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string F3074 => Tsf.F3074.GetState();
public string F3074UL => Tsf.F3074UL.GetState();
public string F7500 => Tsf.F7500.GetState();
public string F27K => Tsf.F27K.GetState();
public string F30K => Tsf.F30K.GetState();
public string FFullGame => Tsf.FFullGame.GetState();
public OverlayViewModel()
{
Tsf.F3074.OnStateChanged +=
() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(F3074)));
Tsf.F3074UL.OnStateChanged +=
() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(F3074UL)));
Tsf.F7500.OnStateChanged +=
() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(F7500)));
Tsf.F27K.OnStateChanged += () => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(F27K)));
Tsf.F30K.OnStateChanged += () => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(F30K)));
Tsf.FFullGame.OnStateChanged += () =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FFullGame)));
}
}
}

24
manual_zh.md Normal file
View File

@ -0,0 +1,24 @@
# TSF 3074 使用手册
## 安装
1. 首先你需要安装 NetLimiter 5你可以从任何其他地方下载到。
2. 启动 TSF会有一个文件选择弹窗选择你的命运2文件destiny2.exe)。
3. 按下 <kbd>Alt + C</kbd> 打开快捷键设置窗口,你可以在这个窗口改绑按键。
4. 例如点击 *3074 UL* 按钮,会弹出按键读取窗口,按下任意按键,例如 <kbd>K</kbd>,然后按回车,窗口会关闭。
5. 所有按键改动完成后右上角X掉就会自动保存你的设置。
6. 然后就可以进游戏用了。
## FAQ
### 开不起来?有问题?
如果你遇到了疑难杂症,可以先看看目录下有没有以 `tsf-crash_` 开头的文件,把他发送给我。
### 开源?
为了避免被棒鸡检测特征,所以不公开分享源码,如果你希望为项目做贡献,可以联系我。
### 怎么找到作者?
你可以通过邮箱 r0yalist¥outlook.com 联系到我。