2020-11-08 14:58:31 +01:00
#nullable enable
using System ;
2020-09-22 20:17:42 +02:00
using System.Collections.Generic ;
using System.IO ;
2020-11-09 09:22:44 +01:00
using System.Linq ;
2020-09-22 22:14:24 +02:00
using System.Text.RegularExpressions ;
2020-11-08 20:43:23 +01:00
using System.Threading ;
2020-11-09 09:22:44 +01:00
using System.Xml.Linq ;
2020-09-22 20:17:42 +02:00
namespace bindEDplugin
{
public class bindEDPlugin
{
2020-11-09 09:22:44 +01:00
private static dynamic? _VA ;
private static string? _pluginPath ;
2020-11-08 14:58:31 +01:00
private static readonly string _bindingsDir = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) , @"Frontier Developments\Elite Dangerous\Options\Bindings" ) ;
2020-11-09 09:22:44 +01:00
private static readonly Dictionary < string , int > _fileEventCount = new Dictionary < string , int > ( ) ;
private static FileSystemWatcher Watcher
{
get
{
if ( _watcher = = null )
{
_watcher = new FileSystemWatcher ( _bindingsDir ) ;
_watcher . NotifyFilter = NotifyFilters . LastWrite | NotifyFilters . FileName ;
_watcher . Changed + = ( source , EventArgs ) = > { FileChangedHandler ( EventArgs . Name ) ; } ;
_watcher . Created + = ( source , EventArgs ) = > { FileChangedHandler ( EventArgs . Name ) ; } ;
_watcher . Renamed + = ( source , EventArgs ) = > { FileChangedHandler ( EventArgs . Name ) ; } ;
}
return _watcher ! ;
}
}
private static FileSystemWatcher ? _watcher ;
private static string Layout
{
get = > _layout ? ? = _VA ? . GetText ( "bindED.layout#" ) ? ? "en-us" ;
set
{
_layout = value ;
KeyMap = null ;
}
}
private static string? _layout ;
private static Dictionary < string , int > ? KeyMap
{
get = > _keyMap ? ? = LoadKeyMap ( Layout ) ;
set
{
_keyMap = value ;
Binds = null ;
}
}
private static Dictionary < string , int > ? _keyMap ;
private static string? Preset
{
get = > _preset ? ? = DetectPreset ( ) ;
set
{
_preset = value ;
Binds = null ;
}
}
private static string? _preset ;
private static Dictionary < string , string > ? Binds
{
get = > _binds ? ? = ReadBinds ( DetectBindsFile ( Preset ) ) ;
set = > _binds = value ;
}
private static Dictionary < string , string > ? _binds ;
2020-09-22 20:17:42 +02:00
2020-11-08 12:41:09 +01:00
public static string VERSION = "3.0" ;
2020-09-22 20:17:42 +02:00
2020-09-22 20:35:52 +02:00
public static string VA_DisplayName ( ) = > $"bindED Plugin v{VERSION}-alterNERDtive" ;
2020-09-22 20:17:42 +02:00
2020-09-22 20:35:52 +02:00
public static string VA_DisplayInfo ( ) = > "bindED Plugin\r\n\r\n2016 VoiceAttack.com\r\n2020 alterNERDtive" ;
2020-09-22 20:17:42 +02:00
2020-09-22 20:35:52 +02:00
public static Guid VA_Id ( ) = > new Guid ( "{524B4B9A-3965-4045-A39A-A239BF6E2838}" ) ;
2020-11-08 14:58:31 +01:00
public static void VA_Init1 ( dynamic vaProxy )
{
_VA = vaProxy ;
2020-11-08 21:12:00 +01:00
_VA . TextVariableChanged + = new Action < string , string , string , Guid ? > ( TextVariableChanged ) ;
_pluginPath = Path . GetDirectoryName ( _VA . PluginPath ( ) ) ;
try
{
2020-11-09 09:22:44 +01:00
LoadBinds ( Binds ) ;
2020-11-08 21:12:00 +01:00
}
catch ( Exception e )
{
LogError ( e . Message ) ;
}
2020-11-09 09:22:44 +01:00
finally
{
Watcher . EnableRaisingEvents = true ;
}
2020-11-08 14:58:31 +01:00
}
2020-09-22 20:35:52 +02:00
2020-11-08 14:58:31 +01:00
public static void VA_Invoke1 ( dynamic vaProxy )
{
_VA = vaProxy ;
try
{
string context = _VA . Context . ToLower ( ) ;
if ( context = = "listbinds" )
{
2020-11-09 09:22:44 +01:00
ListBinds ( Binds , _VA . GetText ( "bindED.separator" ) ? ? "\r\n" ) ;
2020-11-08 14:58:31 +01:00
}
else if ( context = = "loadbinds" )
{
2020-11-09 09:22:44 +01:00
LoadBinds ( Binds ) ;
2020-11-08 14:58:31 +01:00
}
else
{
LogWarn ( "Invoking the plugin with no context / a .binds file as context is deprecated and will be removed in a future version. Please invoke the 'loadbinds' context instead." ) ;
2020-11-09 08:35:27 +01:00
LogWarn ( "Bindings are also read automatically on VoiceAttack start and there should be no need to do it explicitly." ) ;
2020-11-09 09:22:44 +01:00
LoadBinds ( Binds ) ;
2020-11-08 14:58:31 +01:00
}
}
catch ( Exception e )
{
LogError ( e . Message ) ;
}
}
2020-09-22 20:35:52 +02:00
public static void VA_StopCommand ( ) { }
public static void VA_Exit1 ( dynamic vaProxy ) { }
2020-11-08 21:12:00 +01:00
public static void TextVariableChanged ( string name , string from , string to , Guid ? internalID )
{
if ( name . Equals ( "bindED.layout#" ) )
{
LogInfo ( $"Keyboard layout changed to '{to}', reloading …" ) ;
2020-11-09 09:22:44 +01:00
Layout = to ;
LoadBinds ( Binds ) ;
2020-11-08 21:12:00 +01:00
}
}
2020-11-08 14:58:31 +01:00
private static void LogError ( string message )
{
_VA ! . WriteToLog ( $"ERROR | bindED: {message}" , "red" ) ;
}
2020-09-22 20:17:42 +02:00
2020-11-08 20:43:23 +01:00
private static void LogInfo ( string message )
{
_VA ! . WriteToLog ( $"INFO | bindED: {message}" , "blue" ) ;
}
2020-11-08 14:58:31 +01:00
private static void LogWarn ( string message )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
_VA ! . WriteToLog ( $"WARN | bindED: {message}" , "yellow" ) ;
}
2020-11-09 09:22:44 +01:00
public static void ListBinds ( Dictionary < string , string > ? binds , string separator )
{
_VA ! . SetText ( "~bindED.bindsList" , string . Join ( separator , binds ! . Keys ) ) ;
LogInfo ( "List of Elite binds saved to TXT variable '~bindED.bindsList'." ) ;
}
private static void LoadBinds ( Dictionary < string , string > ? binds )
{
foreach ( KeyValuePair < string , string > bind in binds ! )
{
_VA ! . SetText ( bind . Key , bind . Value ) ;
}
LogInfo ( $"Elite binds '{Preset}' for layout '{Layout}' loaded successfully." ) ;
}
2020-11-08 14:58:31 +01:00
private static Dictionary < String , int > LoadKeyMap ( string layout )
{
string mapFile = Path . Combine ( _pluginPath , $"EDMap-{layout.ToLower()}.txt" ) ;
if ( ! File . Exists ( mapFile ) )
{
throw new FileNotFoundException ( $"No map file for layout '{layout}' found." ) ;
}
Dictionary < string , int > map = new Dictionary < string , int > ( 256 ) ;
foreach ( String line in File . ReadAllLines ( mapFile , System . Text . Encoding . UTF8 ) )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
String [ ] arItem = line . Split ( ";" . ToCharArray ( ) , 2 , StringSplitOptions . RemoveEmptyEntries ) ;
if ( ( arItem . Count ( ) = = 2 ) & & ( ! String . IsNullOrWhiteSpace ( arItem [ 0 ] ) ) & & ( ! map . ContainsKey ( arItem [ 0 ] ) ) )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
ushort iKey ;
if ( ushort . TryParse ( arItem [ 1 ] , out iKey ) )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
if ( iKey > 0 & & iKey < 256 )
map . Add ( arItem [ 0 ] . Trim ( ) , iKey ) ;
2020-09-22 20:17:42 +02:00
}
}
}
2020-11-08 14:58:31 +01:00
if ( map . Count = = 0 )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
throw new Exception ( $"Map file for {layout} does not contain any elements." ) ;
2020-09-22 20:17:42 +02:00
}
2020-11-08 14:58:31 +01:00
return map ;
}
2020-09-22 20:17:42 +02:00
2020-11-08 14:58:31 +01:00
private static string DetectPreset ( )
{
string startFile = Path . Combine ( _bindingsDir , "StartPreset.start" ) ;
if ( ! File . Exists ( startFile ) )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
throw new FileNotFoundException ( "No 'StartPreset.start' file found. Please run Elite: Dangerous at least once, then restart VoiceAttack." ) ;
2020-09-22 20:17:42 +02:00
}
2020-11-08 14:58:31 +01:00
return File . ReadAllText ( startFile ) ;
}
2020-09-22 20:17:42 +02:00
2020-11-09 09:22:44 +01:00
private static string DetectBindsFile ( string? preset )
2020-11-08 14:58:31 +01:00
{
DirectoryInfo dirInfo = new DirectoryInfo ( _bindingsDir ) ;
2020-11-08 20:43:23 +01:00
FileInfo [ ] bindFiles = dirInfo . GetFiles ( ) . Where ( i = > Regex . Match ( i . Name , $@"^{preset}(\.3\.0)?\.binds$" ) . Success ) . OrderByDescending ( p = > p . LastWriteTime ) . ToArray ( ) ;
2020-09-22 20:17:42 +02:00
2020-11-08 14:58:31 +01:00
if ( bindFiles . Count ( ) = = 0 )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
throw new FileNotFoundException ( $"No bindings file found for preset '{preset}'. If this is a default preset, please change anything in Elite’ s controls options." ) ;
2020-09-22 20:17:42 +02:00
}
2020-11-08 12:41:09 +01:00
2020-11-08 14:58:31 +01:00
return bindFiles [ 0 ] . FullName ;
}
2020-11-08 12:41:09 +01:00
2020-11-08 14:58:31 +01:00
private static Dictionary < string , string > ReadBinds ( string file )
{
XElement rootElement ;
2020-11-08 12:41:09 +01:00
2020-11-08 14:58:31 +01:00
rootElement = XElement . Load ( file ) ;
Dictionary < string , string > binds = new Dictionary < string , string > ( 512 ) ;
if ( rootElement ! = null )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
foreach ( XElement c in rootElement . Elements ( ) . Where ( i = > i . Elements ( ) . Count ( ) > 0 ) )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
foreach ( var element in c . Elements ( ) . Where ( i = > i . HasAttributes ) )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
List < int > keys = new List < int > ( ) ;
if ( element . Name = = "Primary" )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
if ( element . Attribute ( "Device" ) . Value = = "Keyboard" & & ! String . IsNullOrWhiteSpace ( element . Attribute ( "Key" ) . Value ) & & element . Attribute ( "Key" ) . Value . StartsWith ( "Key_" ) )
{
foreach ( var modifier in element . Elements ( ) . Where ( i = > i . Name . LocalName = = "Modifier" ) )
{
2020-11-09 09:22:44 +01:00
if ( KeyMap ! . ContainsKey ( modifier . Attribute ( "Key" ) . Value ) )
keys . Add ( KeyMap [ modifier . Attribute ( "Key" ) . Value ] ) ;
2020-11-08 14:58:31 +01:00
}
2020-09-22 20:17:42 +02:00
2020-11-09 09:22:44 +01:00
if ( KeyMap ! . ContainsKey ( element . Attribute ( "Key" ) . Value ) )
keys . Add ( KeyMap [ element . Attribute ( "Key" ) . Value ] ) ;
2020-11-08 14:58:31 +01:00
}
}
if ( keys . Count = = 0 ) //nothing found in primary... look in secondary
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
if ( element . Name = = "Secondary" )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
if ( element . Attribute ( "Device" ) . Value = = "Keyboard" & & ! String . IsNullOrWhiteSpace ( element . Attribute ( "Key" ) . Value ) & & element . Attribute ( "Key" ) . Value . StartsWith ( "Key_" ) )
2020-09-22 20:17:42 +02:00
{
2020-11-08 14:58:31 +01:00
foreach ( var modifier in element . Elements ( ) . Where ( i = > i . Name . LocalName = = "Modifier" ) )
2020-09-22 20:17:42 +02:00
{
2020-11-09 09:22:44 +01:00
if ( KeyMap ! . ContainsKey ( modifier . Attribute ( "Key" ) . Value ) )
keys . Add ( KeyMap [ modifier . Attribute ( "Key" ) . Value ] ) ;
2020-09-22 20:17:42 +02:00
}
2020-11-09 09:22:44 +01:00
if ( KeyMap ! . ContainsKey ( element . Attribute ( "Key" ) . Value ) )
keys . Add ( KeyMap [ element . Attribute ( "Key" ) . Value ] ) ;
2020-09-22 20:17:42 +02:00
}
}
}
2020-11-08 14:58:31 +01:00
if ( keys . Count > 0 )
{
String strTextValue = String . Empty ;
foreach ( int key in keys )
strTextValue + = String . Format ( "[{0}]" , key ) ;
binds . Add ( $"ed{c.Name.LocalName}" , strTextValue ) ;
}
2020-09-22 20:17:42 +02:00
}
}
}
2020-11-08 14:58:31 +01:00
return binds ;
}
2020-11-08 20:43:23 +01:00
private static void FileChangedHandler ( string name )
{
// so apparently these events all fire twice … let’ s make sure we only handle it once.
if ( _fileEventCount . ContainsKey ( name ) )
{
_fileEventCount [ name ] + = 1 ;
}
else
{
_fileEventCount . Add ( name , 1 ) ;
}
if ( _fileEventCount [ name ] % 2 = = 0 )
{
try
{
// let’ s make semi-sure that the file isn’ t locked …
// FIXXME: solve this properly
Thread . Sleep ( 500 ) ;
if ( name = = "StartPreset.start" )
{
LogInfo ( "Controls preset changed, reloading …" ) ;
2020-11-09 09:22:44 +01:00
Preset = null ;
LoadBinds ( Binds ) ;
2020-11-08 20:43:23 +01:00
}
else if ( Regex . Match ( name , $@"{_preset}(\.3\.0)?\.binds$" ) . Success )
{
LogInfo ( $"Bindings file '{name}' has changed, reloading …" ) ;
2020-11-09 09:22:44 +01:00
Binds = null ;
LoadBinds ( Binds ) ;
2020-11-08 20:43:23 +01:00
}
}
catch ( Exception e )
{
LogError ( e . Message ) ;
}
}
2020-11-08 14:58:31 +01:00
}
2020-09-22 20:17:42 +02:00
}
}