enhanced config and keybindings
This commit is contained in:
parent
9dbf8306af
commit
2796c5bb1e
26
3074.csproj
26
3074.csproj
|
@ -72,15 +72,21 @@
|
||||||
<DependentUpon>App.xaml</DependentUpon>
|
<DependentUpon>App.xaml</DependentUpon>
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Config.cs" />
|
<Compile Include="Data\Config.cs" />
|
||||||
<Compile Include="HotkeyManager.cs" />
|
<Compile Include="Utils\OnEvent.cs" />
|
||||||
<Compile Include="OnEvent.cs" />
|
<Compile Include="Utils\VirtualKey.cs" />
|
||||||
<Compile Include="VirtualKey.cs" />
|
<Compile Include="Windows\ConfigWindow.xaml.cs">
|
||||||
</ItemGroup>
|
<DependentUpon>ConfigWindow.xaml</DependentUpon>
|
||||||
<ItemGroup>
|
</Compile>
|
||||||
<Compile Include="OverlayWindow.xaml.cs">
|
<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>
|
<DependentUpon>OverlayWindow.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
<Compile Include="Tsf.cs" />
|
<Compile Include="Tsf.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs">
|
<Compile Include="Properties\AssemblyInfo.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
|
@ -92,9 +98,13 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include=".editorconfig" />
|
<Content Include=".editorconfig" />
|
||||||
|
<Content Include="manual_zh.md" />
|
||||||
|
<Content Include="README.md" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Include="OverlayWindow.xaml" />
|
<Page Include="Windows\ConfigWindow.xaml" />
|
||||||
|
<Page Include="Windows\Dialog\KeybindingDialog.xaml" />
|
||||||
|
<Page Include="Windows\OverlayWindow.xaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"/>
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"/>
|
||||||
</Project>
|
</Project>
|
4
App.xaml
4
App.xaml
|
@ -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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:_3074"
|
xmlns:local="clr-namespace:tsf_3074"
|
||||||
Startup="App_OnStartup"
|
Startup="App_OnStartup"
|
||||||
DispatcherUnhandledException="App_OnDispatcherUnhandledException">
|
DispatcherUnhandledException="App_OnDispatcherUnhandledException">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
|
|
|
@ -3,8 +3,11 @@ using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using tsf_3074.Data;
|
||||||
|
using tsf_3074.Window;
|
||||||
|
using tsf_3074.Windows.Dialog;
|
||||||
|
|
||||||
namespace _3074
|
namespace tsf_3074
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interaction logic for App.xaml
|
/// Interaction logic for App.xaml
|
||||||
|
@ -14,6 +17,7 @@ namespace _3074
|
||||||
private void App_OnStartup(object sender, StartupEventArgs e)
|
private void App_OnStartup(object sender, StartupEventArgs e)
|
||||||
{
|
{
|
||||||
SetCurrentProcessExplicitAppUserModelID("com.squirrel.Tsf");
|
SetCurrentProcessExplicitAppUserModelID("com.squirrel.Tsf");
|
||||||
|
Config.Instance.ToString();
|
||||||
Tsf.FFullGame.ToString();
|
Tsf.FFullGame.ToString();
|
||||||
var window = new OverlayWindow();
|
var window = new OverlayWindow();
|
||||||
window.Show();
|
window.Show();
|
||||||
|
|
36
Config.cs
36
Config.cs
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
71
Tsf.cs
71
Tsf.cs
|
@ -1,9 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Windows.Input;
|
||||||
using NetLimiter.Service;
|
using NetLimiter.Service;
|
||||||
|
using tsf_3074.Data;
|
||||||
|
|
||||||
namespace _3074
|
namespace tsf_3074
|
||||||
{
|
{
|
||||||
public static class Tsf
|
public static class Tsf
|
||||||
{
|
{
|
||||||
|
@ -28,12 +30,23 @@ namespace _3074
|
||||||
uint fs = 0, uint vk = 0)
|
uint fs = 0, uint vk = 0)
|
||||||
{
|
{
|
||||||
var cli = GetClient();
|
var cli = GetClient();
|
||||||
var name = GetFilterNameFor(portName);
|
var name = GetFilterNameFor(portName, upload);
|
||||||
var filter = cli.Filters.FirstOrDefault(f => f.Name == name) ??
|
var filter = cli.Filters.FirstOrDefault(f => f.Name == name) ??
|
||||||
cli.AddFilter(new Filter(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);
|
return new TsfFilter(name, filter, cli, upload, 1, fs, vk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +59,13 @@ namespace _3074
|
||||||
{
|
{
|
||||||
Functions = { new FFPathEqual(Config.Instance.Destiny2Path) }
|
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);
|
return new TsfFilter(name, filter, cli, false, 811, fs, vk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,11 +94,20 @@ namespace _3074
|
||||||
private readonly NLClient _client;
|
private readonly NLClient _client;
|
||||||
private readonly bool _upload;
|
private readonly bool _upload;
|
||||||
private readonly uint _limitSize; // the limit rate, normally is 1, only FG is 800 or 811
|
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;
|
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 event OnEvent OnStateChanged = delegate { };
|
||||||
|
|
||||||
public static readonly List<TsfFilter> AllFilters = new List<TsfFilter>();
|
public static readonly List<TsfFilter> AllFilters = new List<TsfFilter>();
|
||||||
|
@ -94,32 +123,48 @@ namespace _3074
|
||||||
_fs = fs;
|
_fs = fs;
|
||||||
_vk = vk;
|
_vk = vk;
|
||||||
|
|
||||||
GetLimitRule();
|
InitLimitRule();
|
||||||
|
|
||||||
AllFilters.Add(this);
|
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)
|
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, () =>
|
manager.RegisterHotkey(_fs, _vk, () =>
|
||||||
{
|
{
|
||||||
Toggle();
|
Toggle();
|
||||||
var currState = GetLimitRule().IsEnabled ? "On" : "Off";
|
Console.WriteLine($"Updated {_name} to {GetState()}");
|
||||||
Console.WriteLine($"Updated [{_name}] {currState}");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Rule GetLimitRule()
|
private void InitLimitRule()
|
||||||
{
|
{
|
||||||
if (_rule != null) return _rule;
|
if (_rule != null) return;
|
||||||
var dir = _upload ? RuleDir.Out : RuleDir.In;
|
var dir = _upload ? RuleDir.Out : RuleDir.In;
|
||||||
_rule = _client.Rules.FirstOrDefault(r => r.FilterId == _filter.Id && r.Dir == dir) ??
|
_rule = _client.Rules.FirstOrDefault(r => r.FilterId == _filter.Id && r.Dir == dir) ??
|
||||||
_client.AddRule(_filter.Id, new LimitRule(dir, _limitSize) { IsEnabled = false });
|
_client.AddRule(_filter.Id, new LimitRule(dir, _limitSize) { IsEnabled = false });
|
||||||
return _rule;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Toggle()
|
public void Toggle()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
namespace _3074
|
namespace tsf_3074
|
||||||
{
|
{
|
||||||
public delegate void OnEvent();
|
public delegate void OnEvent();
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
namespace _3074
|
namespace tsf_3074
|
||||||
{
|
{
|
||||||
public static class VirtualKey
|
public static class VirtualKey
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,22 @@
|
||||||
public const uint ModShift = 0x0004;
|
public const uint ModShift = 0x0004;
|
||||||
public const uint ModWin = 0x0008;
|
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
|
// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||||
|
|
||||||
public const uint VkLButton = 0x01; // Left mouse button
|
public const uint VkLButton = 0x01; // Left mouse button
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
|
|
||||||
namespace _3074
|
namespace tsf_3074
|
||||||
{
|
{
|
||||||
public partial class HotkeyManager
|
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;
|
_window = window;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +21,10 @@ namespace _3074
|
||||||
private const int HotkeyIdStart = 9000;
|
private const int HotkeyIdStart = 9000;
|
||||||
private int _hotkeyId = HotkeyIdStart;
|
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)
|
public void RegisterHotkey(uint fsModifiers, uint vk, OnEvent onEvent)
|
||||||
{
|
{
|
||||||
var thisId = _hotkeyId++;
|
var thisId = _hotkeyId++;
|
||||||
|
@ -29,7 +32,7 @@ namespace _3074
|
||||||
_listener[thisId] = onEvent;
|
_listener[thisId] = onEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UnregisterAllHotkeys()
|
public void UnregisterAllHotkeys()
|
||||||
{
|
{
|
||||||
var helper = new WindowInteropHelper(_window);
|
var helper = new WindowInteropHelper(_window);
|
||||||
while (_hotkeyId >= HotkeyIdStart)
|
while (_hotkeyId >= HotkeyIdStart)
|
||||||
|
@ -38,12 +41,18 @@ namespace _3074
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the hook to the Overlay window
|
||||||
|
*/
|
||||||
public void OnSourceInitialized()
|
public void OnSourceInitialized()
|
||||||
{
|
{
|
||||||
_source = HwndSource.FromHwnd(new WindowInteropHelper(_window).Handle) ?? throw new Exception();
|
_source = HwndSource.FromHwnd(new WindowInteropHelper(_window).Handle) ?? throw new Exception();
|
||||||
_source.AddHook(HwndHook);
|
_source.AddHook(HwndHook);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the hook from Overlay window
|
||||||
|
*/
|
||||||
public void OnClosed()
|
public void OnClosed()
|
||||||
{
|
{
|
||||||
_source.RemoveHook(HwndHook);
|
_source.RemoveHook(HwndHook);
|
||||||
|
@ -51,6 +60,10 @@ namespace _3074
|
||||||
UnregisterAllHotkeys();
|
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)
|
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||||
{
|
{
|
||||||
if (msg != 0x0312) return IntPtr.Zero;
|
if (msg != 0x0312) return IntPtr.Zero;
|
|
@ -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>
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:_3074"
|
xmlns:window="clr-namespace:tsf_3074.Window"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="Thirty-Seventy-Four Overlay" Height="100" Width="120"
|
Title="Thirty-Seventy-Four Overlay" Height="100" Width="120"
|
||||||
Background="{x:Null}" ResizeMode="NoResize"
|
Background="{x:Null}" ResizeMode="NoResize"
|
||||||
AllowsTransparency="True"
|
AllowsTransparency="True"
|
||||||
WindowStyle="None" Topmost="True"
|
WindowStyle="None" Topmost="True"
|
||||||
WindowStartupLocation="Manual" Top="0" Left="0"
|
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 Margin="15">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition></ColumnDefinition>
|
<ColumnDefinition></ColumnDefinition>
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 联系到我。
|
Loading…
Reference in New Issue