first commit

This commit is contained in:
Stefano Rossi 2025-07-12 18:59:18 +02:00
commit aa44d3ad70
Signed by: chadmin
GPG key ID: 9EFA2130646BC893
1585 changed files with 277994 additions and 0 deletions

View file

@ -0,0 +1,141 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using UnityEditor;
using UnityEngine;
#nullable enable
//namespace UTJ.UnityCommandLineTools
namespace Meryel.UnityCodeAssist.Editor.Input
{
// <summary>
// bin2textをUnityEditorから実行する為のClass
// programed by Katsumasa.Kimura
// </summary>
public class Binary2TextExec : EditorToolExec
{
public Binary2TextExec() : base("binary2text") { }
// <summary>
// bin2text filePath outPath options
// /summary>
public int Exec(string filePath, string outPath, string options)
{
var args = string.Format(@"""{0}"" ""{1}"" {2}", filePath, outPath, options);
return Exec(args);
}
public int Exec(string filePath, string outPath, bool detailed = false, bool largeBinaryHashOnly = false, bool hexFloat = false)
{
//var args = string.Format(@"""{0}"" ""{1}"" {2}", filePath, outPath, options);
var args = string.Format(@"""{0}"" ""{1}""", filePath, outPath);
if (detailed)
args += " -detailed";
if (largeBinaryHashOnly)
args += " -largebinaryhashonly";
if (hexFloat)
args += " -hexfloat";
return Exec(args);
}
}
// <summary>
// UnityEditorに含まれるコマンドラインツールを実行する為の基底Class
// programed by Katsumasa.Kimura
//</summary>
public class EditorToolExec
{
// <value>
// UnityEditorがインストールされているディレクトリへのパス
// </value>
protected string mEditorPath;
// <value>
// Toolsディレクトリへのパス
// </value>
protected string mToolsPath;
// <value>
// 実行ファイル名
// </value>
protected string mExecFname;
// <value>
// 実行ファイルへのフルパス
// </value>
protected string mExecFullPath;
// <value>
// 実行結果のOUTPUT
// </value>
private string? mOutput;
// <value>
// 実行結果のOUTPUT
// </value>
public string? Output
{
get { return mOutput; }
}
// <summary>
// コンストラクタ
// <param>
// mExecFname : 実行ファイル名
// </param>
// /summary>
public EditorToolExec(string mExecFname)
{
mEditorPath = Path.GetDirectoryName(EditorApplication.applicationPath);
mToolsPath = Path.Combine(mEditorPath, @"Data/Tools");
this.mExecFname = mExecFname;
//var files = Directory.GetFiles(mToolsPath, mExecFname, SearchOption.AllDirectories);
var files = Directory.GetFiles(mEditorPath, mExecFname + "*", SearchOption.AllDirectories);
if (files.Length == 0)
Serilog.Log.Error("{App} app couldn't be found at {Path}", mExecFname, mEditorPath);
mExecFullPath = files[0];
}
// <summary>
// コマンドラインツールを実行する
// <param>
// arg : コマンドラインツールに渡す引数
// </param>
// </summary>
public int Exec(string arg)
{
int exitCode = -1;
try
{
using var process = new Process();
process.StartInfo.FileName = mExecFullPath;
process.StartInfo.Arguments = arg;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
mOutput = process.StandardOutput.ReadToEnd();
process.WaitForExit();
exitCode = process.ExitCode;
process.Close();
}
catch (Exception e)
{
//UnityEngine.Debug.Log(e);
Serilog.Log.Error(e, "Exception while running process at {Scope}.{Location}", nameof(EditorToolExec), nameof(Exec));
}
return exitCode;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6db78598a7c20a141975035612031328
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,111 @@
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
#nullable enable
namespace Meryel.UnityCodeAssist.Editor.Input
{
public class InputManagerMonitor
{
private static readonly Lazy<InputManagerMonitor> _instance = new Lazy<InputManagerMonitor>(() => new InputManagerMonitor());
public static InputManagerMonitor Instance => _instance.Value;
//UnityInputManager inputManager;
readonly string inputManagerFilePath;
DateTime previousTagManagerLastWrite;
public InputManagerMonitor()
{
EditorApplication.update += Update;
inputManagerFilePath = CommonTools.GetInputManagerFilePath();
previousTagManagerLastWrite = System.IO.File.GetLastWriteTime(inputManagerFilePath);
}
void Update()
{
var currentInputManagerLastWrite = System.IO.File.GetLastWriteTime(inputManagerFilePath);
if (currentInputManagerLastWrite != previousTagManagerLastWrite)
{
previousTagManagerLastWrite = currentInputManagerLastWrite;
Bump();
}
}
public void Bump()
{
Serilog.Log.Debug("InputMonitor {Event}", nameof(Bump));
var inputManager = new UnityInputManager();
inputManager.ReadFromPath(inputManagerFilePath);
inputManager.SendData();
}
}
public static partial class Extensions
{
public static string GetInfo(this List<InputAxis> axes, string? name)
{
if (name == null || string.IsNullOrEmpty(name))
return string.Empty;
//axis.descriptiveName
var axesWithName = axes.Where(a => a.Name == name);
int threshold = 80;
var sb = new System.Text.StringBuilder();
foreach (var axis in axesWithName)
if (!string.IsNullOrEmpty(axis.descriptiveName))
sb.Append($"{axis.descriptiveName} ");
if (sb.Length > threshold)
return sb.ToString();
foreach (var axis in axesWithName)
if (!string.IsNullOrEmpty(axis.descriptiveNegativeName))
sb.Append($"{axis.descriptiveNegativeName} ");
if (sb.Length > threshold)
return sb.ToString();
foreach (var axis in axesWithName)
if (!string.IsNullOrEmpty(axis.positiveButton))
sb.Append($"[{axis.positiveButton}] ");
if (sb.Length > threshold)
return sb.ToString();
foreach (var axis in axesWithName)
if (!string.IsNullOrEmpty(axis.altPositiveButton))
sb.Append($"{{{axis.altPositiveButton}}} ");
if (sb.Length > threshold)
return sb.ToString();
foreach (var axis in axesWithName)
if (!string.IsNullOrEmpty(axis.negativeButton))
sb.Append($"-[{axis.negativeButton}] ");
if (sb.Length > threshold)
return sb.ToString();
foreach (var axis in axesWithName)
if (!string.IsNullOrEmpty(axis.altNegativeButton))
sb.Append($"-{{{axis.altNegativeButton}}} ");
return sb.ToString();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: abc0210fcf4b7cb46baab11d1db6fdb0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,210 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
#nullable enable
namespace Meryel.UnityCodeAssist.Editor.Input
{
public class Text2Yaml
{
public static string Convert(IEnumerable<string> textLines)
{
var sb = new StringBuilder();
var stack = new Stack<(string typeName, string identifier, int indentation)>();
sb.AppendLine(@"%YAML 1.1");
sb.AppendLine(@"%TAG !u! tag:unity3d.com,2011:");
sb.AppendLine(@"--- !u!13 &1");
sb.AppendLine(@"InputManager:");
var regexIndentation = new Regex("^\\s*");
var regexString = new Regex("^(\\s+)(\\w+)\\s+\"([a-zA-Z0-9_ ]*)\"\\s+\\(string\\)$");
var regexValue = new Regex("^(\\s+)(\\w+)\\s+(-?[0-9.]*)\\s+\\(((bool)|(int)|(float)|(unsigned int))\\)$");
var regexType = new Regex("^(\\s+)(\\w+)\\s+\\((\\w+)\\)$");
var regexVectorSize = new Regex("(\\s+)size\\s+(\\d)+\\s+\\(int\\)");
//var regexVectorData = new Regex("(\\s+)data \\(InputAxis\\)"); // remove InputAxis to make it more generic
string curTextLine;
var curTextLineNo = 3;
var textIndentation = 1;
var indentationPrefix = new string(' ', textIndentation * 2);
stack.Push(("InputManager", "InputManager", textIndentation));
foreach (var line in textLines.Skip(4))
{
curTextLine = line;
curTextLineNo++;
// Skip empty lines
if (line.Length == 0)
continue;
// Check if type undeclared, scope goes down, indentation decrements
{
var indentationMatch = regexIndentation.Match(line);
if (indentationMatch.Success)
{
var indentation = indentationMatch.Groups[0].Value.Length;
if (indentation > textIndentation)
Error($"indentation({indentation}) > textIndentation({textIndentation})");
while (indentation < textIndentation)
{
stack.Pop();
textIndentation--;
var typeIndentation = textIndentation;
if (stack.TryPeek(out var curType2))
typeIndentation = curType2.indentation;
else if (line.Length > 0)
Error("stack empty at type undeclaration");
indentationPrefix = new string(' ', typeIndentation * 2);
}
}
else
{
Error($"{nameof(regexIndentation)} failed");
}
}
// Skip size field of vectors
if (stack.TryPeek(out var curType1) && curType1.typeName == "vector")
{
var vectorSizeMatch = regexVectorSize.Match(line);
if (vectorSizeMatch.Success)
{
continue;
}
}
// Read string fields
{
var stringMatch = regexString.Match(line);
if (stringMatch.Success)
{
AddLine(stringMatch.Groups[2] + ": " + stringMatch.Groups[3]);
continue;
}
}
// Read bool/int/float/unsignedInt fields
{
var valueMatch = regexValue.Match(line);
if (valueMatch.Success)
{
AddLine(valueMatch.Groups[2] + ": " + valueMatch.Groups[3]);
continue;
}
}
// Check if new type declared, scope goes up, indentation increases
{
var typeMatch = regexType.Match(line);
if (typeMatch.Success)
{
var identifier = typeMatch.Groups[2].Value;
var typeName = typeMatch.Groups[3].Value;
var isVectorData = false;
if (stack.TryPeek(out var curType2) && curType2.typeName == "vector" && identifier == "data")
isVectorData = true;
var typeIndentation = textIndentation;
if (stack.TryPeek(out var curType3))
typeIndentation = curType3.indentation;
else if (line.Length > 0)
Error("stack empty at type declaration");
if (!isVectorData)
{
AddLine(typeMatch.Groups[2] + ":");
}
else
{
var customIndentation = typeIndentation - 1;
if (customIndentation < 0)
Error($"customIndentation({customIndentation}) < 0");
var customIndentationPrefix = new string(' ', customIndentation * 2);
AddLine("- serializedVersion: 3", customIndentationPrefix);
}
textIndentation++;
typeIndentation++;
if (isVectorData)
typeIndentation--;
stack.Push((typeName, identifier, typeIndentation));
indentationPrefix = new string(' ', typeIndentation * 2);
continue;
}
}
Error("line failed to match all cases");
}
return sb.ToString();
void AddLine(string line, string? customIndentationPrefix = null)
{
string suffix;
if (stack.TryPeek(out var top))
suffix = $" # {textIndentation}, {top.indentation}, {top.typeName} {top.identifier}";
else
suffix = $" # {textIndentation}, nil";
if (customIndentationPrefix != null)
sb.AppendLine(customIndentationPrefix + line + suffix);
else
sb.AppendLine(indentationPrefix + line + suffix);
}
void Error(string message)
{
var errorMessage = $"Text2Yaml error '{message}' at lineNo: {curTextLineNo}, line: '{curTextLine}' at {Environment.StackTrace}";
//throw new Exception(errorMessage);
Serilog.Log.Warning(errorMessage);
}
}
}
public static partial class Extensions
{
public static bool TryPeek<T>(this Stack<T> stack, /*[MaybeNullWhen(false)]*/ out T result)
{
if (stack.Count > 0)
{
result = stack.Peek();
return true;
}
else
{
result = default!;
return false;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d662105b3c16a894aa31647bdbd740ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,405 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
#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];
}
NetMQInitializer.Publisher?.SendInputManager(axisNames, axisInfos, buttonKeys, buttonAxis, joystickNames);
/*
NetMQInitializer.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; }
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 94bc173b04900674ba9cf4b722f8f8d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: