diff --git a/Assets/Scripts/Characters/CharactersController.cs b/Assets/Scripts/Characters/CharactersController.cs index 055d4da..179056e 100644 --- a/Assets/Scripts/Characters/CharactersController.cs +++ b/Assets/Scripts/Characters/CharactersController.cs @@ -207,7 +207,7 @@ namespace CharacterSystem try { - await PrepareWindowAsync(newIndex, oldIndex); + await PrepareTargetAsync(newIndex, oldIndex); } catch (Exception exception) { @@ -296,6 +296,47 @@ namespace CharacterSystem } } + private async Task PrepareTargetAsync(int centerIndex, int additionalIndex) + { + int generation = ++_loadGeneration; + SetDesiredWindow(centerIndex, additionalIndex); + + Task characterTask = EnsureCharacterLoadedAsync(centerIndex); + Task videoTask = EnsureVideoLoadedAsync(centerIndex); + _ = PreloadWindowRemainderAsync(centerIndex, generation); + + await Task.WhenAll(characterTask, videoTask); + } + + private async Task PreloadWindowRemainderAsync(int centerIndex, int generation) + { + int[] indicesToLoad = _desiredIndices + .Where(index => index != centerIndex) + .ToArray(); + List loadingTasks = new(); + + foreach (int characterIndex in indicesToLoad) + { + loadingTasks.Add(EnsureCharacterLoadedAsync(characterIndex)); + loadingTasks.Add(EnsureVideoLoadedAsync(characterIndex)); + } + + try + { + await Task.WhenAll(loadingTasks); + } + catch (Exception exception) + { + Debug.LogException(exception, this); + } + + if (generation == _loadGeneration) + { + ReleaseUndesiredCharacters(); + ReleaseUndesiredVideos(); + } + } + private async Task EnsureCharacterLoadedAsync(int characterIndex) { if (_loadedCharacters.TryGetValue(characterIndex, out LoadedCharacter loadedCharacter)) diff --git a/Assets/Scripts/Core/Bootstrap.cs b/Assets/Scripts/Core/Bootstrap.cs index f2fbb6b..d2d60ed 100644 --- a/Assets/Scripts/Core/Bootstrap.cs +++ b/Assets/Scripts/Core/Bootstrap.cs @@ -117,21 +117,12 @@ public class Bootstrap : MonoBehaviour vitrinaPort = device.Key; Debug.Log($"нашел витрину на порту, {vitrinaPort}"); } - else if (device.Value == ComPortScanner.DeviceType.ControllerV1) + else if (device.Value == ComPortScanner.DeviceType.Controller) { if (!controllerFound) { - Debug.Log("Нашел периферийник1 открываю."); - ComWrapper.Initialize(device.Key, boardVersion: 1); - controllerFound = true; - } - } - else if (device.Value == ComPortScanner.DeviceType.ControllerV2) - { - if (!controllerFound) - { - Debug.Log("Нашел периферийник2 открываю."); - ComWrapper.Initialize(device.Key, boardVersion: 2); + Debug.Log("Нашел периферийный контроллер, открываю."); + ComWrapper.Initialize(device.Key); controllerFound = true; } } diff --git a/Assets/Scripts/Gameplay/BackToMenuController.cs b/Assets/Scripts/Gameplay/BackToMenuController.cs index bdd4e17..efe91f3 100644 --- a/Assets/Scripts/Gameplay/BackToMenuController.cs +++ b/Assets/Scripts/Gameplay/BackToMenuController.cs @@ -18,7 +18,7 @@ public class BackToMenuController : MonoBehaviour private void Update() { - if (!_canExit || In.GetAxis(JoystickSide.Both, InteractMode.Discrete).y <= 0) + if (!_canExit || In.GetVerticalAxis(JoystickSide.Both, InteractMode.Discrete) <= 0) return; Hide(); diff --git a/Assets/Scripts/Gameplay/Player.cs b/Assets/Scripts/Gameplay/Player.cs index f306ea8..efe2ffa 100644 --- a/Assets/Scripts/Gameplay/Player.cs +++ b/Assets/Scripts/Gameplay/Player.cs @@ -26,6 +26,7 @@ public class Player : BasePlayer, IGamePlayer private int SelectedColumn => availableColumns[selectedColumnIndex]; + public JoystickSide ControlSide { get; set; } = JoystickSide.Left; public bool ControlsEnabled { get; set; } = true; #region Public Methods @@ -227,17 +228,14 @@ public class Player : BasePlayer, IGamePlayer private int GetHorizontalInput() { - return In.GetAxis(GetJoystickSide(), InteractMode.Discrete).x; + return In.GetHorizontalAxis(ControlSide, InteractMode.Discrete); } private bool IsActionPressed() { - return In.GetAction(GetJoystickSide(), InteractMode.Discrete); + return In.GetAction(ControlSide, InteractMode.Discrete); } - private JoystickSide GetJoystickSide() - => team == Team.Player1 ? JoystickSide.Left : JoystickSide.Right; - private void SetState(PlayerState newState) { state = newState; diff --git a/Assets/Scripts/Gameplay/TurnManager.cs b/Assets/Scripts/Gameplay/TurnManager.cs index 80e2cbe..a2acac3 100644 --- a/Assets/Scripts/Gameplay/TurnManager.cs +++ b/Assets/Scripts/Gameplay/TurnManager.cs @@ -52,7 +52,13 @@ public class TurnManager : MonoBehaviour } private void SetupPlayers() - => _currentPlayer = _player1; + { + _player1.ControlSide = _gameMode == GameMode.pve + ? JoystickSide.Both + : JoystickSide.Left; + _player2.ControlSide = JoystickSide.Right; + _currentPlayer = _player1; + } private void StartTurn(IGamePlayer player) { diff --git a/Assets/Scripts/Input/COMinput.cs b/Assets/Scripts/Input/COMinput.cs index b4ccca4..3f37943 100644 --- a/Assets/Scripts/Input/COMinput.cs +++ b/Assets/Scripts/Input/COMinput.cs @@ -277,8 +277,13 @@ public class COMinput : InputSource // Обновляет состояния инпута когда приходит новый ввод с платы. Дергается событием. private void UpdateInputState(string input) { - input = input.Substring(0, PIN_COUNT); - for (int i = 0; i < input.Length; i++) + if (string.IsNullOrEmpty(input) || input.Length < PIN_COUNT) + { + Debug.LogWarning($"Получен короткий пакет COM-ввода: {input ?? ""}"); + return; + } + + for (int i = 0; i < PIN_COUNT; i++) { // Получаем новое состояние пина bool newState = input[i] == POSITIVE; @@ -306,7 +311,7 @@ public class COMinput : InputSource char sensorPositive = ConfigManager.Config.SensorPositiveValue.ToString()[0]; - if (prizeSensorPin < input.Length && input[prizeSensorPin] == sensorPositive) + if (prizeSensorPin < PIN_COUNT && input[prizeSensorPin] == sensorPositive) { Debug.Log($"Считано положительное значени датчика приза ({sensorPositive}). Устанавливаю значение 1 в буфер"); PlayerPrefs.SetInt("PrizeGivenBuffer", 1); diff --git a/Assets/Scripts/Input/ComInputConfiguration.cs b/Assets/Scripts/Input/ComInputConfiguration.cs index d62be82..339a7e5 100644 --- a/Assets/Scripts/Input/ComInputConfiguration.cs +++ b/Assets/Scripts/Input/ComInputConfiguration.cs @@ -30,19 +30,19 @@ public sealed class ComInputConfiguration public ComJoystickConfiguration LeftJoystick { get; set; } = new() { Up = 0, - Right = 1, - Down = 2, - Left = 3, - Action = 4 + Right = 2, + Down = 3, + Left = 1, + Action = 8 }; [JsonProperty("rightJoystick")] public ComJoystickConfiguration RightJoystick { get; set; } = new() { - Up = 5, - Right = 6, - Down = 7, - Left = 8, + Up = 4, + Right = 7, + Down = 6, + Left = 5, Action = 9 }; diff --git a/Assets/Scripts/Input/ComSystem/BoardAuthentication.cs b/Assets/Scripts/Input/ComSystem/BoardAuthentication.cs index b8d5d30..f497405 100644 --- a/Assets/Scripts/Input/ComSystem/BoardAuthentication.cs +++ b/Assets/Scripts/Input/ComSystem/BoardAuthentication.cs @@ -2,10 +2,99 @@ using System; using System.Security.Cryptography; using System.Text; +public enum ControllerAuthenticationState +{ + NotStarted, + AwaitingControllerVersion, + ReadyToStartHandshake, + AwaitingChallenge, + AwaitingControllerProof, + AwaitingReady, + Ready, + Failed +} + +public sealed class ControllerAuthenticator +{ + private string _hostChallenge; + + public ControllerAuthenticationState State { get; private set; } + + public void BeginControllerReset() + { + _hostChallenge = null; + State = ControllerAuthenticationState.AwaitingControllerVersion; + } + + public bool AcceptControllerVersion(string line) + { + if (State != ControllerAuthenticationState.AwaitingControllerVersion + || !BoardAuthentication.IsControllerVersionLine(line)) + return false; + + State = ControllerAuthenticationState.ReadyToStartHandshake; + return true; + } + + public string StartHandshake() + { + if (State != ControllerAuthenticationState.ReadyToStartHandshake) + return null; + + _hostChallenge = BoardAuthentication.CreateChallengeHex(); + State = ControllerAuthenticationState.AwaitingChallenge; + return "07" + _hostChallenge; + } + + public string Handle(string line) + { + if (State == ControllerAuthenticationState.AwaitingChallenge + && BoardAuthentication.TryExtractHexPayload(line, "CHAL", out string controllerChallenge)) + { + State = ControllerAuthenticationState.AwaitingControllerProof; + return "08" + BoardAuthentication.EncryptHexBlock(controllerChallenge); + } + + if (State == ControllerAuthenticationState.AwaitingControllerProof + && BoardAuthentication.TryExtractHexPayload(line, "RAES", out string encryptedResponse)) + { + if (!BoardAuthentication.ValidateEncryptedResponse(_hostChallenge, encryptedResponse)) + { + Fail(); + return null; + } + + State = ControllerAuthenticationState.AwaitingReady; + return null; + } + + if (State == ControllerAuthenticationState.AwaitingReady + && string.Equals(line, "READY!", StringComparison.Ordinal)) + { + State = ControllerAuthenticationState.Ready; + } + + return null; + } + + public void Reset() + { + _hostChallenge = null; + State = ControllerAuthenticationState.NotStarted; + } + + public void Fail() + { + _hostChallenge = null; + State = ControllerAuthenticationState.Failed; + } +} + public static class BoardAuthentication { private const int _blockSizeBytes = 16; private const int _hexBlockLength = _blockSizeBytes * 2; + private const byte _keyObfuscationMask = 0x37; private static readonly byte[] _aesObfuscatedKey = { @@ -23,6 +112,10 @@ public static class BoardAuthentication return ToHex(challenge); } + public static bool IsControllerVersionLine(string line) + => !string.IsNullOrEmpty(line) + && line.IndexOf("ver_", StringComparison.OrdinalIgnoreCase) >= 0; + public static string EncryptHexBlock(string plainHex) { byte[] plain = ParseHexBlock(plainHex); @@ -32,7 +125,7 @@ public static class BoardAuthentication aes.BlockSize = 128; aes.Mode = CipherMode.ECB; aes.Padding = PaddingMode.None; - aes.Key = (byte[])_aesObfuscatedKey.Clone(); + aes.Key = GetAesKey(); using ICryptoTransform encryptor = aes.CreateEncryptor(); byte[] encrypted = encryptor.TransformFinalBlock(plain, 0, plain.Length); @@ -64,12 +157,9 @@ public static class BoardAuthentication if (!TryNormalizeHexBlock(responseHex, out string normalizedResponse)) return false; - string expectedResponse = EncryptHexBlock(challengeHex); - int difference = 0; - for (int i = 0; i < expectedResponse.Length; i++) - difference |= expectedResponse[i] ^ normalizedResponse[i]; - - return difference == 0; + byte[] expectedResponse = ParseHexBlock(EncryptHexBlock(challengeHex)); + byte[] receivedResponse = ParseHexBlock(normalizedResponse); + return CryptographicOperations.FixedTimeEquals(expectedResponse, receivedResponse); } private static byte[] ParseHexBlock(string value) @@ -105,6 +195,15 @@ public static class BoardAuthentication || value >= 'A' && value <= 'F' || value >= 'a' && value <= 'f'; + private static byte[] GetAesKey() + { + byte[] key = new byte[_aesObfuscatedKey.Length]; + for (int i = 0; i < key.Length; i++) + key[i] = (byte)(_aesObfuscatedKey[i] ^ _keyObfuscationMask); + + return key; + } + private static string ToHex(byte[] bytes) { StringBuilder builder = new(bytes.Length * 2); diff --git a/Assets/Scripts/Input/ComSystem/ComPortScanner.cs b/Assets/Scripts/Input/ComSystem/ComPortScanner.cs index f688326..2bdd5fc 100644 --- a/Assets/Scripts/Input/ComSystem/ComPortScanner.cs +++ b/Assets/Scripts/Input/ComSystem/ComPortScanner.cs @@ -6,30 +6,32 @@ using UnityEngine; public static class ComPortScanner { // Параметры соединения - private const int BaudRate = 115200; - private const int DataBits = 8; - private const int Parity = 0; // 0 = None - private const int StopBits = 1; // 1 = One - private const int ReadTimeoutMs = 5; - private const int WriteTimeoutMs = 10; + private const int _baudRate = 115200; + private const int _dataBits = 8; + private const int _parity = 0; // 0 = None + private const int _stopBits = 1; // 1 = One + private const int _readTimeoutMs = 5; + private const int _writeTimeoutMs = 10; // Команды устройств - private const string CmdPrize = "97"; // Для витрины и периферийника2 - private const string CmdControllerAuth = "07"; - private const int DeviceResponseTimeoutMs = 1000; + private const string _cmdPrize = "97"; + private const string _cmdControllerReset = "00"; + private const string _cmdControllerAuth = "07"; + private const int _deviceResponseTimeoutMs = 1000; + private const int _controllerRestartTimeoutMs = 20000; + private const int _authenticationResponseTimeoutMs = 3000; // Типы устройств public enum DeviceType { None, - ControllerV1, // Периферийник1 - ControllerV2, // Периферийник2 - PrizeShowcase // Витрина + Controller, + PrizeShowcase } // Префиксы ответов - private const string RolePrefix = "ROLE :"; // Периферийник2 и витрина на 97 - private const string PrizeRoleValue = "vitrina"; // Витрина: ROLE : vitrina + private const string _rolePrefix = "ROLE :"; + private const string _prizeRoleValue = "vitrina"; /// /// Сканирует все доступные COM-порты и возвращает словарь {порт = тип устройства} @@ -62,32 +64,22 @@ public static class ComPortScanner Debug.Log($"Проверка порта {port}"); string response97 = CheckDevice( port, - CmdPrize, - line => line.StartsWith(RolePrefix, StringComparison.Ordinal)); - if (response97 != null && response97.StartsWith(RolePrefix)) + _cmdPrize, + line => line.StartsWith(_rolePrefix, StringComparison.Ordinal)); + if (response97 != null && response97.StartsWith(_rolePrefix)) { Debug.Log($"Role: {response97}"); // Извлекаем значение после "ROLE : " - string roleValue = response97.Substring(RolePrefix.Length).Trim(); + string roleValue = response97.Substring(_rolePrefix.Length).Trim(); - // Если роль = "vitrina" - это витрина - if (string.Equals(roleValue, PrizeRoleValue, StringComparison.OrdinalIgnoreCase)) - { + if (string.Equals(roleValue, _prizeRoleValue, StringComparison.OrdinalIgnoreCase)) return DeviceType.PrizeShowcase; - } - else - { - return DeviceType.ControllerV2; - } + + return DeviceType.Controller; } - string computerChallenge = BoardAuthentication.CreateChallengeHex(); - string authResponse = CheckDevice( - port, - CmdControllerAuth + computerChallenge, - IsAuthenticationResponse); - if (authResponse != null) - return DeviceType.ControllerV1; + if (CheckControllerDevice(port)) + return DeviceType.Controller; return DeviceType.None; } @@ -134,7 +126,7 @@ public static class ComPortScanner /// private static string CheckDevice(string port, string command, Func isExpectedResponse) { - int handle = ComNativeWrapper.Serial_Open(port, BaudRate, DataBits, Parity, StopBits, 10, WriteTimeoutMs); + int handle = ComNativeWrapper.Serial_Open(port, _baudRate, _dataBits, _parity, _stopBits, 10, _writeTimeoutMs); if (handle <= 0) { return null; @@ -152,7 +144,7 @@ public static class ComPortScanner byte[] buffer = new byte[1024]; long deadline = DateTime.UtcNow.Ticks - + DeviceResponseTimeoutMs * TimeSpan.TicksPerMillisecond; + + _deviceResponseTimeoutMs * TimeSpan.TicksPerMillisecond; while (DateTime.UtcNow.Ticks < deadline) { int bytesRead = ComNativeWrapper.Serial_ReadLineUTF8_bytes(handle, buffer, buffer.Length); @@ -172,7 +164,71 @@ public static class ComPortScanner } } - private static bool IsAuthenticationResponse(string line) - => BoardAuthentication.TryExtractHexPayload(line, "CHAL", out _) - || BoardAuthentication.TryExtractHexPayload(line, "RAES", out _); + private static bool CheckControllerDevice(string port) + { + int handle = ComNativeWrapper.Serial_Open( + port, + _baudRate, + _dataBits, + _parity, + _stopBits, + _readTimeoutMs, + _writeTimeoutMs); + if (handle <= 0) + return false; + + try + { + ClearPortBuffer(handle); + + int resetWriteResult = ComNativeWrapper.Serial_WriteStringUTF8( + handle, + _cmdControllerReset, + 1); + if (resetWriteResult < 0) + return false; + + string versionLine = ReadExpectedLine( + handle, + BoardAuthentication.IsControllerVersionLine, + _controllerRestartTimeoutMs); + if (versionLine == null) + return false; + + string computerChallenge = BoardAuthentication.CreateChallengeHex(); + int writeResult = ComNativeWrapper.Serial_WriteStringUTF8( + handle, + _cmdControllerAuth + computerChallenge, + 1); + if (writeResult < 0) + return false; + + return ReadExpectedLine(handle, IsChallengeResponse, _authenticationResponseTimeoutMs) != null; + } + finally + { + ComNativeWrapper.Serial_Close(handle); + } + } + + private static string ReadExpectedLine(int handle, Func isExpectedResponse, int timeoutMs) + { + byte[] buffer = new byte[1024]; + long deadline = DateTime.UtcNow.Ticks + timeoutMs * TimeSpan.TicksPerMillisecond; + while (DateTime.UtcNow.Ticks < deadline) + { + int bytesRead = ComNativeWrapper.Serial_ReadLineUTF8_bytes(handle, buffer, buffer.Length); + if (bytesRead <= 0) + continue; + + string line = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim(); + if (!string.IsNullOrEmpty(line) && isExpectedResponse(line)) + return line; + } + + return null; + } + + private static bool IsChallengeResponse(string line) + => BoardAuthentication.TryExtractHexPayload(line, "CHAL", out _); } diff --git a/Assets/Scripts/Input/ComSystem/ComWrapper.cs b/Assets/Scripts/Input/ComSystem/ComWrapper.cs index 810fc34..3275b85 100644 --- a/Assets/Scripts/Input/ComSystem/ComWrapper.cs +++ b/Assets/Scripts/Input/ComSystem/ComWrapper.cs @@ -17,15 +17,12 @@ public static class ComWrapper public static event Action InputRecieved; private const string _requestInputCommand = "91"; - private const string _requestAuthCommand = "07"; - private const string _responseAuthCommand = "08"; - private const string _challengePrefix = "CHAL"; - private const string _encryptedResponsePrefix = "RAES"; - private const string _readyResponse = "READY!"; + private const string _controllerResetCommand = "00"; private const string _cabinetLightingOnCommand = "856001"; private const string _cabinetLightingOffCommand = "856000"; - private const int _authStepTimeoutMs = 3000; - private const int _authReadyTimeoutMs = 5000; + private const int _controllerRestartTimeoutMs = 20000; + private const int _authHandshakeTimeoutMs = 10000; + private const int _controllerInputBitCount = 26; private const int _inputIdleBeforePingMs = 60_000; private const int _keepAlivePollMs = 5000; private const int _reconnectRetryMs = 10_000; @@ -38,14 +35,12 @@ public static class ComWrapper private static int _lastInputTick; private static string _controllerPort; private static volatile bool _isAuthorized; + private static ILogger _logger; - public static int BoardVersion { get; private set; } + public static bool IsPortOpen => h > 0; - private static ILogger logger; - - public static void Initialize(string port, int boardVersion)//Добавить логгер в конструктор + public static void Initialize(string port) { - BoardVersion = boardVersion; _controllerPort = port; if (_lifetimeCts != null) return; @@ -70,7 +65,7 @@ public static class ComWrapper { int w = ComNativeWrapper.Serial_WriteStringUTF8(h, message, 1); } - catch { logger?.Log($"Ошибка отправки строкового сигнала. {message}"); } + catch { _logger?.Log($"Ошибка отправки строкового сигнала. {message}"); } } private static void StartReaderThread() @@ -143,7 +138,10 @@ public static class ComWrapper if (line == "COIN") { if (!_isAuthorized) + { + Send("10"); return; + } PaymentReceived?.Invoke(); Send("10"); @@ -157,7 +155,11 @@ public static class ComWrapper MarkInputActivity(); string rawInputString = line.Substring(5); - string inputString = BoardVersion == 2 ? DecimalToBinary(rawInputString) : rawInputString; + if (!TryConvertInput(rawInputString, out string inputString)) + { + Debug.LogWarning($"Некорректный пакет ввода от контроллера: {line}"); + return; + } InputRecieved?.Invoke(inputString); } @@ -196,45 +198,8 @@ public static class ComWrapper return tcs.Task; } - /// - /// Очищает буфер порта от накопившихся старых данных - /// - private static void FlushPortBuffer() - { - try - { - // Проверяем валидность handle - if (h <= 0) - { - return; - } - - // Используем локальный буфер - byte[] localBuf = new byte[1024]; - - int flushedCount = 0; - - // Читаем все накопившиеся данные из порта - int read; - while ((read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, localBuf, localBuf.Length)) > 0) - { - flushedCount++; - if (flushedCount > 100) break; // Защита от бесконечного цикла - } - - Log($"Очищено: {flushedCount} сообщений"); - } - catch (Exception e) - { - Log($"FlushPortBuffer exception: {e.Message}"); - } - } - - static Task WaitForPrefixAsync(string prefix, int timeoutMs, CancellationToken ct) => WaitForAsync(s => s.StartsWith(prefix, StringComparison.Ordinal), timeoutMs, ct); - static Task WaitForExactAsync(string expected, int timeoutMs, CancellationToken ct) - => WaitForAsync(s => string.Equals(s, expected, StringComparison.Ordinal), timeoutMs, ct); static void OnLine(string line) { @@ -296,67 +261,26 @@ public static class ComWrapper else { Log($"Открыл порт: {port}"); - FlushPortBuffer(); - } - - WaitAllCoins(); } - static void WaitAllCoins() + private static bool TryConvertInput(string decimalString, out string input) { - int count = 0; - int maxCount = 100; - long timeout = 3000; + input = null; + if (!uint.TryParse(decimalString, out uint decimalValue)) + return false; - byte[] bytes = new byte[1024]; - long startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - long lastCoinTime = startTime; - while (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - lastCoinTime < 250) - { - int w = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, bytes, bytes.Length); - if (w <= 0) continue; - if( Encoding.UTF8.GetString(bytes, 0, w) == "COIN") - { - lastCoinTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - Send("10"); - count++; - } - if (count >= maxCount) break; - if(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTime >= timeout) break; - } - Debug.Log($"Before game coins: {count}"); - } + uint maximumValue = (1u << _controllerInputBitCount) - 1u; + if (decimalValue > maximumValue) + return false; - /// - /// Преобразует строку с десятичным числом в двоичное представление. - /// Младшие биты слева, старшие справа (перевёрнутый порядок для соответствия индексам пинов). - /// - /// Строка с десятичным числом - /// Строка с двоичным представлением - private static string DecimalToBinary(string decimalString) - { - try - { - if (long.TryParse(decimalString, out long decimalValue)) - { - // Стандартное преобразование: старшие биты слева - string binary = Convert.ToString(decimalValue, 2); - - // Переворачиваем строку: младшие биты слева, старшие справа - char[] chars = binary.ToCharArray(); - Array.Reverse(chars); - return new string(chars); - } - - Debug.Log($"Не удалось распарсить значение INPUT: {decimalString}"); - return "11111111111111111111111111"; // 26 единиц по умолчанию - } - catch (Exception e) - { - Debug.Log($"Ошибка преобразования в двоичную систему: {e.Message}"); - return "11111111111111111111111111"; // 26 единиц по умолчанию - } + string binary = Convert + .ToString(decimalValue, 2) + .PadLeft(_controllerInputBitCount, '0'); + char[] bits = binary.ToCharArray(); + Array.Reverse(bits); + input = new string(bits); + return true; } @@ -364,45 +288,48 @@ public static class ComWrapper { using CancellationTokenSource authCts = CancellationTokenSource.CreateLinkedTokenSource(ct); CancellationToken authToken = authCts.Token; - string computerChallenge = BoardAuthentication.CreateChallengeHex(); + ControllerAuthenticator authenticator = new(); + authenticator.BeginControllerReset(); try { - Task controllerChallengeTask = WaitForAsync( - line => BoardAuthentication.TryExtractHexPayload(line, _challengePrefix, out _), - _authStepTimeoutMs, + Task versionTask = WaitForAsync( + BoardAuthentication.IsControllerVersionLine, + _controllerRestartTimeoutMs, authToken); - Send(_requestAuthCommand + computerChallenge); + Log("Перезагружаю контроллер перед авторизацией."); + Send(_controllerResetCommand); + string versionLine = await versionTask; + Log($"Контроллер готов: {versionLine}"); - string controllerChallengeLine = await controllerChallengeTask; - if (!BoardAuthentication.TryExtractHexPayload( - controllerChallengeLine, - _challengePrefix, - out string controllerChallenge)) + if (!authenticator.AcceptControllerVersion(versionLine)) return false; - Task encryptedResponseTask = WaitForAsync( - line => BoardAuthentication.TryExtractHexPayload(line, _encryptedResponsePrefix, out _), - _authStepTimeoutMs, - authToken); - Task readyTask = WaitForExactAsync( - _readyResponse, - _authReadyTimeoutMs, - authToken); - Send(_responseAuthCommand + BoardAuthentication.EncryptHexBlock(controllerChallenge)); + string authenticationRequest = authenticator.StartHandshake(); + if (authenticationRequest == null) + return false; - string encryptedResponseLine = await encryptedResponseTask; - if (!BoardAuthentication.TryExtractHexPayload( - encryptedResponseLine, - _encryptedResponsePrefix, - out string encryptedResponse) - || !BoardAuthentication.ValidateEncryptedResponse(computerChallenge, encryptedResponse)) + Task authenticationTask = WaitForAsync( + line => + { + string response = authenticator.Handle(line); + if (response != null) + Send(response); + + return authenticator.State == ControllerAuthenticationState.Ready + || authenticator.State == ControllerAuthenticationState.Failed; + }, + _authHandshakeTimeoutMs, + authToken); + Send(authenticationRequest); + await authenticationTask; + + if (authenticator.State != ControllerAuthenticationState.Ready) { Log("Контроллер не прошёл взаимную AES-проверку."); return false; } - await readyTask; Log("Авторизация платы полностью завершена по READY!."); return true; } @@ -412,6 +339,7 @@ public static class ComWrapper } catch (Exception exception) { + authenticator.Fail(); Log($"Ошибка авторизации платы: {exception.Message}"); return false; } @@ -576,5 +504,5 @@ public static class ComWrapper Send(_cabinetLightingOnCommand); } - private static void Log(string message) => logger?.Log($"CW{message}"); + private static void Log(string message) => _logger?.Log($"CW{message}"); } diff --git a/Assets/Scripts/Input/In.cs b/Assets/Scripts/Input/In.cs index fef0101..b266361 100644 --- a/Assets/Scripts/Input/In.cs +++ b/Assets/Scripts/Input/In.cs @@ -10,7 +10,7 @@ public class In : MonoBehaviour public void Initialize() { #if UNITY_EDITOR - source = new KeyboardInput(); + source = ComWrapper.IsPortOpen ? new COMinput() : new KeyboardInput(); #else source = new COMinput(); #endif @@ -21,35 +21,51 @@ public class In : MonoBehaviour /// Возвращает горизонтальную и вертикальную оси выбранного джойстика. public static Vector2Int GetAxis(JoystickSide side, InteractMode interactMode = InteractMode.Discrete) + => new( + GetHorizontalAxis(side, interactMode), + GetVerticalAxis(side, interactMode)); + + public static int GetHorizontalAxis( + JoystickSide side, + InteractMode interactMode = InteractMode.Discrete) { if (side == JoystickSide.Both) { - int horizontal = ToAxis( - IsActive(InSignal.left1, interactMode) || IsActive(InSignal.left2, interactMode), - IsActive(InSignal.right1, interactMode) || IsActive(InSignal.right2, interactMode)); - int vertical = ToAxis( - IsActive(InSignal.down1, interactMode) || IsActive(InSignal.down2, interactMode), - IsActive(InSignal.up1, interactMode) || IsActive(InSignal.up2, interactMode)); - - return new Vector2Int(horizontal, vertical); + return ToAxis( + IsEitherActive(InSignal.left1, InSignal.left2, interactMode), + IsEitherActive(InSignal.right1, InSignal.right2, interactMode)); } bool isLeft = side == JoystickSide.Left; InSignal left = isLeft ? InSignal.left1 : InSignal.left2; InSignal right = isLeft ? InSignal.right1 : InSignal.right2; + + return ToAxis(IsActive(left, interactMode), IsActive(right, interactMode)); + } + + public static int GetVerticalAxis( + JoystickSide side, + InteractMode interactMode = InteractMode.Discrete) + { + if (side == JoystickSide.Both) + { + return ToAxis( + IsEitherActive(InSignal.down1, InSignal.down2, interactMode), + IsEitherActive(InSignal.up1, InSignal.up2, interactMode)); + } + + bool isLeft = side == JoystickSide.Left; InSignal down = isLeft ? InSignal.down1 : InSignal.down2; InSignal up = isLeft ? InSignal.up1 : InSignal.up2; - return new Vector2Int( - ToAxis(IsActive(left, interactMode), IsActive(right, interactMode)), - ToAxis(IsActive(down, interactMode), IsActive(up, interactMode))); + return ToAxis(IsActive(down, interactMode), IsActive(up, interactMode)); } /// Возвращает состояние кнопки Action выбранного джойстика. public static bool GetAction(JoystickSide side, InteractMode interactMode = InteractMode.Discrete) { if (side == JoystickSide.Both) - return IsActive(InSignal.action1, interactMode) || IsActive(InSignal.action2, interactMode); + return IsEitherActive(InSignal.action1, InSignal.action2, interactMode); InSignal action = side == JoystickSide.Left ? InSignal.action1 : InSignal.action2; return IsActive(action, interactMode); @@ -77,6 +93,16 @@ public class In : MonoBehaviour private static bool IsActive(InSignal signal, InteractMode interactMode) => interactMode == InteractMode.Discrete ? GetButtonDown(signal) : GetButton(signal); + private static bool IsEitherActive( + InSignal first, + InSignal second, + InteractMode interactMode) + { + bool firstActive = IsActive(first, interactMode); + bool secondActive = IsActive(second, interactMode); + return firstActive || secondActive; + } + private static int ToAxis(bool negative, bool positive) => (positive ? 1 : 0) - (negative ? 1 : 0); } @@ -98,4 +124,4 @@ public enum InteractMode { Discrete, Continious -} \ No newline at end of file +} diff --git a/Assets/Scripts/UI/Menu/Menu.cs b/Assets/Scripts/UI/Menu/Menu.cs index bdc577f..e31b3bb 100644 --- a/Assets/Scripts/UI/Menu/Menu.cs +++ b/Assets/Scripts/UI/Menu/Menu.cs @@ -48,7 +48,7 @@ public class Menu : MonoBehaviour, IControlable public void UpdateControls() { - int direction_horizontal = In.GetAxis(JoystickSide.Both, InteractMode.Discrete).x; + int direction_horizontal = In.GetHorizontalAxis(JoystickSide.Both, InteractMode.Discrete); menuPanelManager.SwitchPanel(direction_horizontal); bool action = In.GetAction(JoystickSide.Both, InteractMode.Discrete); diff --git a/Assets/Scripts/UI/Menu/Panels/MenuPanelsScroll.cs b/Assets/Scripts/UI/Menu/Panels/MenuPanelsScroll.cs index 115e58e..1553be7 100644 --- a/Assets/Scripts/UI/Menu/Panels/MenuPanelsScroll.cs +++ b/Assets/Scripts/UI/Menu/Panels/MenuPanelsScroll.cs @@ -56,7 +56,7 @@ public class MenuPanelsScroll : MenuPanel { if (!m_enabled || isTransitioning || BlocksNavigation) return; - int dir_v = -In.GetAxis(JoystickSide.Both, InteractMode.Discrete).y; + int dir_v = -In.GetVerticalAxis(JoystickSide.Both, InteractMode.Discrete); if (dir_v != 0) StartCoroutine(SwitchPanelCoroutine(dir_v)); } diff --git a/Assets/StreamingAssets/ComInputConfig.json b/Assets/StreamingAssets/ComInputConfig.json index cb07849..d889e69 100644 --- a/Assets/StreamingAssets/ComInputConfig.json +++ b/Assets/StreamingAssets/ComInputConfig.json @@ -1,16 +1,16 @@ { "leftJoystick": { "up": 0, - "right": 1, - "down": 2, - "left": 3, - "action": 4 + "right": 2, + "down": 3, + "left": 1, + "action": 8 }, "rightJoystick": { - "up": 5, - "right": 6, - "down": 7, - "left": 8, + "up": 4, + "right": 7, + "down": 6, + "left": 5, "action": 9 }, "serviceButton": 10 diff --git a/ProjectSettings/ScriptableBuildPipeline.json b/ProjectSettings/ScriptableBuildPipeline.json index dfa4f08..551b978 100644 --- a/ProjectSettings/ScriptableBuildPipeline.json +++ b/ProjectSettings/ScriptableBuildPipeline.json @@ -5,7 +5,7 @@ "threadedArchiving": true, "logCacheMiss": false, "logAssetWarnings": true, - "slimWriteResults": true, + "slimWriteResults": false, "maximumCacheSize": 20, "useDetailedBuildLog": false, "useV2Hasher": true,