412 lines
14 KiB
C#
412 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
|
|
|
|
#pragma warning disable IDE0005
|
|
using Serilog = Meryel.Serilog;
|
|
using YamlDotNet = Meryel.UnityCodeAssist.YamlDotNet;
|
|
#pragma warning restore IDE0005
|
|
|
|
|
|
#nullable enable
|
|
|
|
|
|
namespace Meryel.UnityCodeAssist.Editor.Input
|
|
{
|
|
|
|
|
|
internal class UnityInputManager
|
|
{
|
|
//string yamlPath;
|
|
TextReader? reader;
|
|
InputManager? inputManager;
|
|
|
|
public void ReadFromText(string text)
|
|
{
|
|
reader = new StringReader(text);
|
|
ReadAux(false, out _);
|
|
}
|
|
|
|
public void ReadFromPath(string yamlPath)
|
|
{
|
|
|
|
switch (UnityEditor.EditorSettings.serializationMode)
|
|
{
|
|
case UnityEditor.SerializationMode.ForceText:
|
|
{
|
|
reader = new StreamReader(yamlPath);
|
|
ReadAux(false, out _);
|
|
}
|
|
break;
|
|
|
|
case UnityEditor.SerializationMode.ForceBinary:
|
|
{
|
|
// this approach will work for InputManager since its file size is small and limited
|
|
// but in the future, we may need to switch to reading binary files for big files
|
|
// like this https://github.com/Unity-Technologies/UnityDataTools
|
|
// or this https://github.com/SeriousCache/UABE
|
|
var converted = GetOrCreateConvertedFile(yamlPath);
|
|
if (!File.Exists(converted))
|
|
{
|
|
Serilog.Log.Warning("Temp file {TempFile} couldn't found for converted yaml input file. Auto Input Manager will not work!", converted);
|
|
return;
|
|
}
|
|
var rawLines = File.ReadLines(converted);
|
|
var yamlText = Text2Yaml.Convert(rawLines);
|
|
reader = new StringReader(yamlText);
|
|
ReadAux(false, out _);
|
|
}
|
|
break;
|
|
|
|
case UnityEditor.SerializationMode.Mixed:
|
|
{
|
|
reader = new StreamReader(yamlPath);
|
|
ReadAux(true, out var hasSemanticError);
|
|
if (hasSemanticError)
|
|
{
|
|
var converted = GetOrCreateConvertedFile(yamlPath);
|
|
if (!File.Exists(converted))
|
|
{
|
|
Serilog.Log.Warning("Temp file {TempFile} couldn't found for converted yaml input file. Auto Input Manager will not work!", converted);
|
|
return;
|
|
}
|
|
var rawLines = File.ReadLines(converted);
|
|
var yamlText = Text2Yaml.Convert(rawLines);
|
|
reader = new StringReader(yamlText);
|
|
ReadAux(false, out _);
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void ReadAux(bool canHaveSemanticError, out bool hasSemanticError)
|
|
{
|
|
hasSemanticError = false;
|
|
|
|
if (reader == null)
|
|
{
|
|
Serilog.Log.Warning($"{nameof(UnityInputManager)}.{nameof(reader)} is null");
|
|
return;
|
|
}
|
|
|
|
//var reader = new StreamReader(yamlPath);
|
|
var deserializer = new YamlDotNet.Serialization.DeserializerBuilder()
|
|
.WithTagMapping("tag:unity3d.com,2011:13", typeof(Class13Mapper))
|
|
.IgnoreUnmatchedProperties()
|
|
.Build();
|
|
//serializer.Settings.RegisterTagMapping("tag:unity3d.com,2011:13", typeof(Class13));
|
|
//serializer.Settings.ComparerForKeySorting = null;
|
|
Class13Mapper? result;
|
|
try
|
|
{
|
|
result = deserializer.Deserialize<Class13Mapper>(reader);
|
|
}
|
|
catch (YamlDotNet.Core.SemanticErrorException semanticErrorException)
|
|
{
|
|
Serilog.Log.Debug(semanticErrorException, "Couldn't parse InputManager.asset yaml file");
|
|
if (!canHaveSemanticError)
|
|
Serilog.Log.Error(semanticErrorException, "Couldn't parse InputManager.asset yaml file unexpectedly");
|
|
|
|
hasSemanticError = true;
|
|
return;
|
|
}
|
|
finally
|
|
{
|
|
reader.Close();
|
|
}
|
|
|
|
var inputManagerMapper = result?.InputManager;
|
|
if (inputManagerMapper == null)
|
|
{
|
|
Serilog.Log.Warning($"{nameof(inputManagerMapper)} is null");
|
|
return;
|
|
}
|
|
|
|
inputManager = new InputManager(inputManagerMapper);
|
|
}
|
|
|
|
|
|
public void SendData()
|
|
{
|
|
if (inputManager == null)
|
|
return;
|
|
|
|
var axisNames = inputManager.Axes.Select(a => a.Name!).Where(n => !string.IsNullOrEmpty(n)).Distinct().ToArray();
|
|
var axisInfos = axisNames.Select(a => inputManager.Axes.GetInfo(a)).ToArray();
|
|
if (!CreateBindingsMap(out var buttonKeys, out var buttonAxis))
|
|
return;
|
|
|
|
string[] joystickNames;
|
|
try
|
|
{
|
|
joystickNames = UnityEngine.Input.GetJoystickNames();
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
// Occurs if user have switched active Input handling to Input System package in Player Settings.
|
|
joystickNames = new string[0];
|
|
}
|
|
|
|
MQTTnetInitializer.Publisher?.SendInputManager(axisNames, axisInfos, buttonKeys, buttonAxis, joystickNames);
|
|
|
|
/*
|
|
MQTTnetInitializer.Publisher?.SendInputManager(
|
|
inputManager.Axes.Select(a => a.Name).Distinct().ToArray(),
|
|
inputManager.Axes.Select(a => a.positiveButton).ToArray(),
|
|
inputManager.Axes.Select(a => a.negativeButton).ToArray(),
|
|
inputManager.Axes.Select(a => a.altPositiveButton).ToArray(),
|
|
inputManager.Axes.Select(a => a.altNegativeButton).ToArray(),
|
|
UnityEngine.Input.GetJoystickNames()
|
|
);
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
bool CreateBindingsMap([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string[]? inputKeys, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string[]? inputAxis)
|
|
{
|
|
if (inputManager == null)
|
|
{
|
|
inputKeys = null;
|
|
inputAxis = null;
|
|
return false;
|
|
}
|
|
|
|
var dict = new Dictionary<string, string?>();
|
|
|
|
foreach (var axis in inputManager.Axes)
|
|
{
|
|
if (axis.altNegativeButton != null && !string.IsNullOrEmpty(axis.altNegativeButton))
|
|
dict[axis.altNegativeButton] = axis.Name;
|
|
}
|
|
foreach (var axis in inputManager.Axes)
|
|
{
|
|
if (axis.negativeButton != null && !string.IsNullOrEmpty(axis.negativeButton))
|
|
dict[axis.negativeButton] = axis.Name;
|
|
}
|
|
foreach (var axis in inputManager.Axes)
|
|
{
|
|
if (axis.altPositiveButton != null && !string.IsNullOrEmpty(axis.altPositiveButton))
|
|
dict[axis.altPositiveButton] = axis.Name;
|
|
}
|
|
foreach (var axis in inputManager.Axes)
|
|
{
|
|
if (axis.positiveButton != null && !string.IsNullOrEmpty(axis.positiveButton))
|
|
dict[axis.positiveButton] = axis.Name;
|
|
}
|
|
|
|
var keys = new string[dict.Count];
|
|
var values = new string[dict.Count];
|
|
dict.Keys.CopyTo(keys, 0);
|
|
dict.Values.CopyTo(values, 0);
|
|
|
|
inputKeys = keys;
|
|
inputAxis = values;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
static string GetOrCreateConvertedFile(string filePath)
|
|
{
|
|
var hash = GetMD5Hash(filePath);
|
|
var convertedPath = Path.Combine(Path.GetTempPath(), $"UCA_IM_{hash}.txt");
|
|
|
|
if (!File.Exists(convertedPath))
|
|
{
|
|
Serilog.Log.Debug("Converting binary to text format of {File} to {Target}", filePath, convertedPath);
|
|
var converter = new Binary2TextExec();
|
|
converter.Exec(filePath, convertedPath);
|
|
}
|
|
else
|
|
{
|
|
Serilog.Log.Debug("Converted file already exists at {Target}", convertedPath);
|
|
}
|
|
|
|
return convertedPath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a hash of the file using MD5.
|
|
/// </summary>
|
|
/// <param name="filePath"></param>
|
|
/// <returns></returns>
|
|
public static string GetMD5Hash(string filePath)
|
|
{
|
|
using var md5 = new MD5CryptoServiceProvider();
|
|
return GetHash(filePath, md5);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a hash of the file using MD5.
|
|
/// </summary>
|
|
/// <param name="filePath"></param>
|
|
/// <returns></returns>
|
|
public static string GetMD5Hash(Stream s)
|
|
{
|
|
using var md5 = new MD5CryptoServiceProvider();
|
|
return GetHash(s, md5);
|
|
}
|
|
|
|
private static string GetHash(string filePath, HashAlgorithm hasher)
|
|
{
|
|
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
return GetHash(fs, hasher);
|
|
}
|
|
|
|
private static string GetHash(Stream s, HashAlgorithm hasher)
|
|
{
|
|
var hash = hasher.ComputeHash(s);
|
|
var hashStr = Convert.ToBase64String(hash);
|
|
//return hashStr.TrimEnd('=');
|
|
var hashStrAlphaNumeric = System.Text.RegularExpressions.Regex.Replace(hashStr, "[^A-Za-z0-9]", "");
|
|
return hashStrAlphaNumeric;
|
|
}
|
|
|
|
}
|
|
|
|
public enum AxisType
|
|
{
|
|
KeyOrMouseButton = 0,
|
|
MouseMovement = 1,
|
|
JoystickAxis = 2
|
|
};
|
|
|
|
#pragma warning disable IDE1006
|
|
|
|
public class InputAxisMapper
|
|
{
|
|
public int serializedVersion { get; set; }
|
|
|
|
public string? m_Name { get; set; }
|
|
public string? descriptiveName { get; set; }
|
|
public string? descriptiveNegativeName { get; set; }
|
|
public string? negativeButton { get; set; }
|
|
public string? positiveButton { get; set; }
|
|
public string? altNegativeButton { get; set; }
|
|
public string? altPositiveButton { get; set; }
|
|
|
|
//public float gravity { get; set; }
|
|
//public float dead { get; set; }
|
|
//public float sensitivity { get; set; }
|
|
public string? gravity { get; set; }
|
|
public string? dead { get; set; }
|
|
public string? sensitivity { get; set; }
|
|
|
|
//public bool snap { get; set; }
|
|
public int snap { get; set; }
|
|
//public bool invert { get; set; }
|
|
public int invert { get; set; }
|
|
|
|
//public AxisType type { get; set; }
|
|
public int type { get; set; }
|
|
|
|
public int axis { get; set; }
|
|
public int joyNum { get; set; }
|
|
}
|
|
|
|
public class InputAxis
|
|
{
|
|
readonly InputAxisMapper map;
|
|
|
|
public InputAxis(InputAxisMapper map)
|
|
{
|
|
this.map = map;
|
|
}
|
|
|
|
public int SerializedVersion
|
|
{
|
|
get { return map.serializedVersion; }
|
|
set { map.serializedVersion = value; }
|
|
}
|
|
|
|
public string? Name => map.m_Name;
|
|
public string? descriptiveName => map.descriptiveName;
|
|
public string? descriptiveNegativeName => map.descriptiveNegativeName;
|
|
public string? negativeButton => map.negativeButton;
|
|
public string? positiveButton => map.positiveButton;
|
|
public string? altNegativeButton => map.altNegativeButton;
|
|
public string? altPositiveButton => map.altPositiveButton;
|
|
|
|
public float gravity => float.Parse(map.gravity);//**--format
|
|
public float dead => float.Parse(map.dead);//**--format
|
|
public float sensitivity => float.Parse(map.sensitivity);//**--format
|
|
|
|
public bool snap => map.snap != 0;
|
|
public bool invert => map.invert != 0;
|
|
|
|
public AxisType type => (AxisType)map.type;
|
|
|
|
public int axis => map.axis;
|
|
public int joyNum => map.joyNum;
|
|
}
|
|
|
|
public class InputManagerMapper
|
|
{
|
|
public int m_ObjectHideFlags { get; set; }
|
|
public int serializedVersion { get; set; }
|
|
public int m_UsePhysicalKeys { get; set; }
|
|
public List<InputAxisMapper>? m_Axes { get; set; }
|
|
}
|
|
|
|
#pragma warning restore IDE1006
|
|
|
|
public class InputManager
|
|
{
|
|
readonly InputManagerMapper map;
|
|
readonly List<InputAxis> axes;
|
|
|
|
public InputManager(InputManagerMapper map)
|
|
{
|
|
this.map = map;
|
|
this.axes = new List<InputAxis>();
|
|
|
|
if (map.m_Axes == null)
|
|
{
|
|
Serilog.Log.Warning($"map.m_Axes is null");
|
|
return;
|
|
}
|
|
|
|
foreach (var a in map.m_Axes)
|
|
this.axes.Add(new InputAxis(a));
|
|
}
|
|
|
|
public int ObjectHideFlags
|
|
{
|
|
get { return map.m_ObjectHideFlags; }
|
|
set { map.m_ObjectHideFlags = value; }
|
|
}
|
|
|
|
public int SerializedVersion
|
|
{
|
|
get { return map.serializedVersion; }
|
|
set { map.serializedVersion = value; }
|
|
}
|
|
|
|
public bool UsePhysicalKeys
|
|
{
|
|
get { return map.m_UsePhysicalKeys != 0; }
|
|
set { map.m_UsePhysicalKeys = value ? 1 : 0; }
|
|
}
|
|
|
|
/*public List<InputAxisMapper> Axes
|
|
{
|
|
get { return map.m_Axes; }
|
|
set { map.m_Axes = value; }
|
|
}*/
|
|
public List<InputAxis> Axes => axes;
|
|
}
|
|
|
|
public class Class13Mapper
|
|
{
|
|
public InputManagerMapper? InputManager { get; set; }
|
|
}
|
|
}
|