diff --git a/docs/guia-acesso-remoto-pt-br.md b/docs/guia-acesso-remoto-pt-br.md index 53232b5685..f903c9bdf3 100644 --- a/docs/guia-acesso-remoto-pt-br.md +++ b/docs/guia-acesso-remoto-pt-br.md @@ -21,6 +21,10 @@ Por quê: - O gateway conecta no OpenKore via socket Unix (`--socket`) **ou** via TCP (`--kore-host` + `--kore-port`). - O fluxo padrão do projeto usa caminhos Unix (`/etc/...`) e serviço `systemd`. +> Importante: para funcionar, o endpoint precisa falar o protocolo de console do OpenKore (`set active/input`). Nem toda porta TCP do OpenKore (ex.: `XKore_port`) é compatível com esse protocolo. +> No Windows nativo, para fluxo simples, use só `tools/run-remote.cmd SEU_TOKEN start.exe` (ou `tkstart.exe`, etc.). +> Esse comando único abre OpenKore + gateway e expõe o endpoint TCP compatível. + Isso significa: - **Linux nativo**: cenário recomendado e documentado. @@ -250,6 +254,8 @@ perl tools/remote_gateway.pl \ ``` > Nesse modo, use `--kore-host` + `--kore-port` juntos. +> Importante: `XKore_port` no `config.txt` não garante compatibilidade com o protocolo de console (`set active/input`) usado pelo gateway. +> Se a porta não for compatível, o `/health` ficará com `connected=false` e `/commands` retornará `503 core_unavailable`. --- diff --git a/docs/guia-acesso-remoto-windows-nativo.md b/docs/guia-acesso-remoto-windows-nativo.md index e6c652ce85..96f7b2cb71 100644 --- a/docs/guia-acesso-remoto-windows-nativo.md +++ b/docs/guia-acesso-remoto-windows-nativo.md @@ -99,10 +99,37 @@ python -m json.tool .\config\gateway-users.json | Out-Null; Write-Host "JSON OK" Você precisa saber host/porta do endpoint TCP. -Exemplo deste guia: +### Pré-requisito crítico -- host: `127.0.0.1` -- porta: `2350` +O `remote_gateway.pl` só consegue falar com um endpoint que implemente o protocolo de console do OpenKore (`set active` / `input`). +Esse endpoint é nativo da **Interface Socket** (arquivo `console.socket`). + +Em outras palavras: deixar apenas `XKore_port` configurado não abre automaticamente esse protocolo para o gateway. + +⚠️ **Importante:** no OpenKore padrão, `XKore_port` (ex.: `2350`) **não é automaticamente** o endpoint de console remoto esperado pelo `remote_gateway.pl` (`set active/input`). + +### Fluxo único (simples, recomendado) + +> No Windows, ao usar `--interface=Socket`, o OpenKore agora usa TCP por padrão em `127.0.0.1:2350` se `OPENKORE_SOCKET_TCP_PORT` não estiver definido. + +Use **só** este arquivo: + +```text +tools\run-remote.cmd UM_TOKEN_LONGO_E_ALEATORIO start.exe +``` + +Ele já: +- abre **uma janela separada** para OpenKore com `--interface=Socket` usando o launcher que **você** informar (`start.exe`, `tkstart.exe`, etc.); +- configura endpoint TCP de console em `127.0.0.1:2350`; +- abre **outra janela separada** para o gateway em `127.0.0.1:18085`. + +Valide: + +```powershell +Test-NetConnection 127.0.0.1 -Port 2350 +``` + +Se `TcpTestSucceeded` for `False`, o gateway vai subir com `/health`, mas ficará com `connected=false` e retornará `503 core_unavailable` em `/commands`. --- @@ -114,6 +141,24 @@ No PowerShell, dentro de `C:\openkore`: perl .\tools\remote_gateway.pl --kore-host 127.0.0.1 --kore-port 2350 --listen-host 127.0.0.1 --listen-port 18085 --command-token "UM_TOKEN_LONGO_E_ALEATORIO" --audit-file ".\\logs\\gateway_audit.jsonl" --command-rate-limit 30 --command-rate-window 60 --auth-enabled --users-file ".\\config\\gateway-users.json" --token-ttl 900 --session-file ".\\data\\gateway_sessions.json" ``` +> Se quiser manter nativo no Windows, prefira abrir o endpoint com `OPENKORE_SOCKET_TCP_PORT` + `--interface=Socket` (bloco acima). + +### Modo ultra simples (recomendado) + +Sem digitar vários comandos: execute **um arquivo**: + +```text +tools\run-remote.cmd UM_TOKEN_LONGO_E_ALEATORIO +``` + +Esse `.cmd`: +- abre OpenKore (launcher detectado automaticamente) com `--interface=Socket`; +- configura `OPENKORE_SOCKET_TCP_HOST=127.0.0.1` e `OPENKORE_SOCKET_TCP_PORT=2350`; +- inicia o gateway em `127.0.0.1:18085` **sem depender** de `start-gateway.ps1`. +- o OpenKore é aberto pelo launcher normal (`start.exe`/`tkstart.exe`/etc), como no fluxo de duplo clique. + +> Se seu objetivo é simplicidade, use somente `tools\run-remote.cmd` e ignore os outros scripts auxiliares. + --- ## 7) Testar pelo Windows (browser + PowerShell) @@ -158,21 +203,16 @@ Se seu Windows não aceita SSH inbound, use VPN/Tailscale/ZeroTier e exponha ape ## 9) Inicialização automática no Windows -Use o script pronto: `scripts/start-gateway.ps1`. - -Ele detecta automaticamente a raiz do OpenKore quando executado de dentro do repositório. -Também existe atalho em `tools/start-gateway.ps1`. - -Exemplo manual (PowerShell): +Para manter simples, agende **um comando só**: ```powershell -powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\start-gateway.ps1 -KoreHost "127.0.0.1" -KorePort 2350 -ListenHost "127.0.0.1" -ListenPort 18085 -CommandToken "UM_TOKEN_LONGO_E_ALEATORIO" +powershell -NoProfile -ExecutionPolicy Bypass -Command "& { cd 'C:\openkore'; .\tools\run-remote.cmd UM_TOKEN_LONGO_E_ALEATORIO }" ``` Depois crie uma tarefa no **Task Scheduler** chamando: ```powershell -powershell -NoProfile -ExecutionPolicy Bypass -File C:\openkore\scripts\start-gateway.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -Command "& { cd 'C:\openkore'; .\tools\run-remote.cmd UM_TOKEN_LONGO_E_ALEATORIO }" ``` --- @@ -180,6 +220,14 @@ powershell -NoProfile -ExecutionPolicy Bypass -File C:\openkore\scripts\start-ga ## 10) Troubleshooting comum (Windows nativo) - **Gateway não inicia com erro de conexão**: host/porta TCP do OpenKore incorretos. +- **`Status conectado ao OpenKore: False` + erro `core_unavailable (503)` na UI**: + - O gateway está de pé, mas **não conseguiu abrir sessão com o endpoint do OpenKore**. + - Confira no PowerShell se a porta realmente aceita TCP: + ```powershell + Test-NetConnection 127.0.0.1 -Port 2350 + ``` + - Se `TcpTestSucceeded` vier `False`, a porta configurada em `--kore-port` está incorreta/inativa. + - Atenção: `XKore_port` pode existir no `config.txt` e **mesmo assim não ser** o endpoint que fala o protocolo de console (`set active/input`) esperado pelo `remote_gateway.pl`. - **porta 18085 não abre no Windows**: confirme que gateway está rodando e bound em `127.0.0.1`. - **401/403**: revisar usuário/senha/token/header. - **sem eventos**: OpenKore não está acessível no endpoint TCP configurado. @@ -191,4 +239,4 @@ powershell -NoProfile -ExecutionPolicy Bypass -File C:\openkore\scripts\start-ga ```powershell perl .\src\test\unittests.pl RemoteGatewaySmokeTest bash .\tools\check_gateway_release.sh -``` \ No newline at end of file +``` diff --git a/src/Interface/Console/Simple.pm b/src/Interface/Console/Simple.pm index 07330b2d02..c922cf1f99 100644 --- a/src/Interface/Console/Simple.pm +++ b/src/Interface/Console/Simple.pm @@ -31,17 +31,29 @@ use Globals qw(%consoleColors); use Interface; use base qw(Interface); use I18N qw(UTF8ToString); -use Utils::Unix; + +BEGIN { + if ($^O ne 'MSWin32') { + require Utils::Unix; + Utils::Unix->import(); + } +} sub new { my $class = shift; binmode STDOUT; STDOUT->autoflush(0); - return bless {}, $class; + my $self = bless {}, $class; + if ($^O eq 'MSWin32') { + if (open(my $conin, '<', 'CONIN$')) { + $self->{conin} = $conin; + } + } + return $self; } sub DESTROY { - print STDOUT Utils::Unix::getColor('default'); + print STDOUT Utils::Unix::getColor('default') if $^O ne 'MSWin32'; STDOUT->flush; } @@ -49,23 +61,26 @@ sub getInput { my ($self, $timeout) = @_; my $line; my $bits; + my $in = $self->{conin} || \*STDIN; + my $fd = fileno($in); + return undef if !defined $fd; if ($timeout < 0) { my $done; while (!$done) { $bits = ''; - vec($bits, fileno(STDIN), 1) = 1; + vec($bits, $fd, 1) = 1; if (select($bits, undef, undef, 1) > 0) { - $line = ; + $line = <$in>; $done = 1; } } } else { $bits = ''; - vec($bits, fileno(STDIN), 1) = 1; + vec($bits, $fd, 1) = 1; if (select($bits, undef, undef, $timeout) > 0) { - $line = ; + $line = <$in>; } } @@ -79,10 +94,13 @@ sub getInput { sub writeOutput { my ($self, $type, $message, $domain) = @_; - my ($code, $reset) = ( - Utils::Unix::getColorForMessage(\%consoleColors, $type, $domain), - Utils::Unix::getColor('reset'), - ); + my ($code, $reset) = ('', ''); + if ($^O ne 'MSWin32') { + ($code, $reset) = ( + Utils::Unix::getColorForMessage(\%consoleColors, $type, $domain), + Utils::Unix::getColor('reset'), + ); + } $message =~ s/\n/$reset\n$code/sg; $message = $code.$message.$reset; diff --git a/src/Interface/Socket.pm b/src/Interface/Socket.pm index 41b84e4914..3d403a9be6 100644 --- a/src/Interface/Socket.pm +++ b/src/Interface/Socket.pm @@ -56,7 +56,7 @@ use Interface; use Translation; use base qw(Interface); use Utils qw(timeOut); -use Interface::Console::Simple; +use Interface::Console; use constant MAX_LOG_ENTRIES => 5000; @@ -66,7 +66,7 @@ sub new { my (%self, $f); $self{server} = new Interface::Socket::Server(); - $self{console} = new Interface::Console::Simple(); + $self{console} = new Interface::Console(); open($f, ">:utf8", "$Settings::logs_folder/console.log"); $self{consoleLogFile} = $f; $self{logEntryCount} = 0; @@ -169,6 +169,9 @@ package Interface::Socket::Server; use strict; use IO::Socket::UNIX; +use IO::Socket::INET; +use Socket (); +use Errno qw(EADDRINUSE); use Base::Server; use base qw(Base::Server); use Settings; @@ -184,31 +187,64 @@ sub new { my ($class) = @_; my $socket_file = "$Settings::logs_folder/console.socket"; my $pid_file = "$Settings::logs_folder/openkore.pid"; - my $socket = new IO::Socket::UNIX( - Local => $socket_file, - Type => SOCK_STREAM, - Listen => 5 - ); - if (!$socket && $! == 98) { - $socket = new IO::Socket::UNIX( - Peer => $socket_file, - Type => SOCK_STREAM + my $tcp_host = $ENV{OPENKORE_SOCKET_TCP_HOST} || '127.0.0.1'; + my $unix_socket_supported = eval { Socket::pack_sockaddr_un($socket_file); 1 }; + my $tcp_port_env = $ENV{OPENKORE_SOCKET_TCP_PORT}; + my $default_tcp_port = (!$unix_socket_supported || $^O eq 'MSWin32') ? 2350 : 0; + my $tcp_port = ($tcp_port_env && $tcp_port_env =~ /^\d+$/ && $tcp_port_env > 0) + ? $tcp_port_env + : $default_tcp_port; + my $use_tcp = ($tcp_port =~ /^\d+$/ && $tcp_port > 0); + my ($socket, $endpoint_desc); + + if ($use_tcp) { + $socket = new IO::Socket::INET( + LocalAddr => $tcp_host, + LocalPort => $tcp_port, + Proto => 'tcp', + Type => SOCK_STREAM, + Listen => 5, + ReuseAddr => 1 ); if (!$socket) { - unlink($socket_file); + print STDERR "Cannot listen at '$tcp_host:$tcp_port': $!\n"; + exit 1; + } + $endpoint_desc = "$tcp_host:" . $socket->sockport; + print "Interface::Socket TCP endpoint listening at $endpoint_desc\n"; + } else { + $socket = new IO::Socket::UNIX( + Local => $socket_file, + Type => SOCK_STREAM, + Listen => 5 + ); + if (!$socket && $! == EADDRINUSE) { $socket = new IO::Socket::UNIX( - Local => $socket_file, - Type => SOCK_STREAM, - Listen => 5 + Peer => $socket_file, + Type => SOCK_STREAM ); - } else { - print STDERR "There is already an OpenKore instance listening at '$socket_file'.\n"; + if (!$socket) { + unlink($socket_file); + $socket = new IO::Socket::UNIX( + Local => $socket_file, + Type => SOCK_STREAM, + Listen => 5 + ); + } else { + print STDERR "There is already an OpenKore instance listening at '$socket_file'.\n"; + exit 1; + } + } + if (!$socket) { + if ($^O eq 'MSWin32') { + print STDERR "Cannot listen at UNIX socket '$socket_file' on Windows: $!\n"; + print STDERR "Set OPENKORE_SOCKET_TCP_PORT (example: 2350) to use TCP console endpoint.\n"; + } else { + print STDERR "Cannot listen at '$socket_file': $!\n"; + } exit 1; } - } - if (!$socket) { - print STDERR "Cannot listen at '$socket_file': $!\n"; - exit 1; + $endpoint_desc = $socket_file; } my $f; @@ -216,7 +252,7 @@ sub new { print $f $$; close($f); } else { - unlink $socket_file; + unlink $socket_file if !$use_tcp; print STDERR "Cannot write to PID file '$pid_file'.\n"; exit 1; } @@ -228,11 +264,13 @@ sub new { $self->{messages} = []; $self->{inputs} = []; $self->{socket_file} = $socket_file; + $self->{use_tcp} = $use_tcp; + $self->{endpoint_desc} = $endpoint_desc; $self->{pid_file} = $pid_file; $self->{waitingForInput} = 0; $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = sub { - unlink $socket_file; + unlink $socket_file if !$use_tcp; unlink $pid_file; exit 2; }; @@ -242,7 +280,7 @@ sub new { sub DESTROY { my ($self) = @_; - unlink $self->{socket_file}; + unlink $self->{socket_file} if !$self->{use_tcp}; unlink $self->{pid_file}; $self->SUPER::DESTROY(); } @@ -362,4 +400,4 @@ sub onClientData { } } -1; \ No newline at end of file +1; diff --git a/tools/run-remote.cmd b/tools/run-remote.cmd new file mode 100644 index 0000000000..23bd4e1909 --- /dev/null +++ b/tools/run-remote.cmd @@ -0,0 +1,53 @@ +@echo off +setlocal ENABLEEXTENSIONS + +REM One-shot launcher: OpenKore Socket TCP + Remote Gateway (Windows). +REM Usage: +REM tools\run-remote.cmd YOUR_LONG_RANDOM_TOKEN start.exe +REM If launcher is omitted, defaults to start.exe. + +cd /d "%~dp0.." +set "ROOT=%CD%" +set "COMMAND_TOKEN=%~1" +if "%COMMAND_TOKEN%"=="" set "COMMAND_TOKEN=CHANGE_ME" +set "LAUNCHER_NAME=%~2" +if "%LAUNCHER_NAME%"=="" set "LAUNCHER_NAME=start.exe" + +set "OPENKORE_SOCKET_TCP_HOST=127.0.0.1" +set "OPENKORE_SOCKET_TCP_PORT=2350" + +if not exist "%ROOT%\logs" mkdir "%ROOT%\logs" >nul 2>&1 +if not exist "%ROOT%\data" mkdir "%ROOT%\data" >nul 2>&1 +if not exist "%ROOT%\config" mkdir "%ROOT%\config" >nul 2>&1 + +if not exist "%ROOT%\config\gateway-users.json" ( + if exist "%ROOT%\tools\gateway-users.example.json" ( + copy /Y "%ROOT%\tools\gateway-users.example.json" "%ROOT%\config\gateway-users.json" >nul + ) +) + +set "LAUNCHER=%ROOT%\%LAUNCHER_NAME%" +if not exist "%LAUNCHER%" goto :launcher_missing + +echo [1/3] Abrindo OpenKore em janela separada (interface do launcher)... +start "OpenKore Socket" cmd /k "cd /d ""%ROOT%"" && set OPENKORE_SOCKET_TCP_HOST=%OPENKORE_SOCKET_TCP_HOST% && set OPENKORE_SOCKET_TCP_PORT=%OPENKORE_SOCKET_TCP_PORT% && ""%LAUNCHER%"" --interface=Socket" + +echo [2/3] Aguardando 3s... +timeout /t 3 /nobreak >nul + +echo [3/3] Abrindo gateway em outra janela separada... +start "OpenKore Remote Gateway" cmd /k "cd /d ""%ROOT%"" && perl ""%ROOT%\tools\remote_gateway.pl"" --kore-host 127.0.0.1 --kore-port 2350 --listen-host 127.0.0.1 --listen-port 18085 --command-token ""%COMMAND_TOKEN%"" --audit-file ""%ROOT%\logs\gateway_audit.jsonl"" --command-rate-limit 30 --command-rate-window 60 --auth-enabled --users-file ""%ROOT%\config\gateway-users.json"" --token-ttl 900 --session-file ""%ROOT%\data\gateway_sessions.json""" + +echo. +echo Pronto. Janelas abertas separadamente: +echo - OpenKore (launcher/interface) +echo - Remote Gateway +echo UI Web: http://127.0.0.1:18085/ +exit /b 0 + +:launcher_missing +echo [ERRO] Launcher nao encontrado: %LAUNCHER_NAME% +echo Informe explicitamente no comando, por exemplo: +echo tools\run-remote.cmd SEU_TOKEN start.exe +echo tools\run-remote.cmd SEU_TOKEN tkstart.exe +exit /b 1 diff --git a/tools/start-gateway.ps1 b/tools/start-gateway.ps1 index a78963bf15..ba77f29769 100644 --- a/tools/start-gateway.ps1 +++ b/tools/start-gateway.ps1 @@ -57,6 +57,30 @@ function Ensure-Dir { } } +function Test-TcpEndpoint { + param( + [string]$TargetHost, + [int]$Port, + [int]$TimeoutMs = 800 + ) + + $client = New-Object System.Net.Sockets.TcpClient + try { + $iar = $client.BeginConnect($TargetHost, $Port, $null, $null) + if (-not $iar.AsyncWaitHandle.WaitOne($TimeoutMs, $false)) { + return $false + } + $client.EndConnect($iar) + return $true + } + catch { + return $false + } + finally { + try { $client.Close() } catch {} + } +} + $OpenKoreRoot = Resolve-OpenKoreRoot -UserPath $OpenKoreRoot $gatewayScript = Join-Path $OpenKoreRoot "tools\remote_gateway.pl" $configDir = Join-Path $OpenKoreRoot "config" @@ -106,6 +130,12 @@ if ($CommandToken -eq "CHANGE_ME") { Write-Warning "Você ainda está usando token padrão CHANGE_ME. Troque antes de produção." } +$koreEndpointReachable = Test-TcpEndpoint -TargetHost $KoreHost -Port $KorePort +if (-not $koreEndpointReachable) { + Write-Warning "Não foi possível conectar em $KoreHost`:$KorePort antes de iniciar o gateway." + Write-Warning "Se o /health mostrar connected=false, inicie o OpenKore com endpoint compatível: `$env:OPENKORE_SOCKET_TCP_PORT=`"$KorePort`"` + `--interface=Socket`." +} + $existingPid = $null if (Test-Path -LiteralPath $pidFile) { $existingPid = (Get-Content -LiteralPath $pidFile -ErrorAction SilentlyContinue | Select-Object -First 1) @@ -160,4 +190,4 @@ try { } finally { Pop-Location -} \ No newline at end of file +} diff --git a/tools/start-openkore-socket-tcp.cmd b/tools/start-openkore-socket-tcp.cmd new file mode 100644 index 0000000000..7cece73e7b --- /dev/null +++ b/tools/start-openkore-socket-tcp.cmd @@ -0,0 +1,9 @@ +@echo off +setlocal +set "SCRIPT_DIR=%~dp0" +powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%start-openkore-socket-tcp.ps1" %* +if errorlevel 1 ( + echo. + echo Falha ao iniciar stack remoto do OpenKore. + pause +) diff --git a/tools/start-openkore-socket-tcp.ps1 b/tools/start-openkore-socket-tcp.ps1 new file mode 100644 index 0000000000..e490bd1da5 --- /dev/null +++ b/tools/start-openkore-socket-tcp.ps1 @@ -0,0 +1,215 @@ +param( + [string]$OpenKoreRoot = "", + [string]$LauncherPath = "", + [string]$SocketHost = "127.0.0.1", + [int]$SocketPort = 2350, + [switch]$LaunchOpenKore = $false, + [switch]$StartGateway = $false, + [string]$GatewayListenHost = "127.0.0.1", + [int]$GatewayListenPort = 18085, + [string]$CommandToken = "CHANGE_ME" +) + +$ErrorActionPreference = "Stop" + +function Resolve-OpenKoreRoot { + param([string]$UserPath) + + if ($UserPath -and $UserPath.Trim() -ne "") { + if (Test-Path -LiteralPath $UserPath) { + return (Resolve-Path -LiteralPath $UserPath).Path + } + throw "OpenKoreRoot inválido (não encontrado): $UserPath" + } + + $scriptPath = $PSCommandPath + if (-not $scriptPath) { $scriptPath = $MyInvocation.PSCommandPath } + if (-not $scriptPath) { $scriptPath = $MyInvocation.MyCommand.Path } + if (-not $scriptPath) { $scriptPath = (Join-Path (Get-Location).Path "tools\start-openkore-socket-tcp.ps1") } + $scriptDir = Split-Path -Parent $scriptPath + $candidates = @() + $candidates += $scriptDir + try { $candidates += (Resolve-Path (Join-Path $scriptDir "..")).Path } catch {} + try { $candidates += (Resolve-Path (Join-Path $scriptDir "..\..")).Path } catch {} + $candidates += (Get-Location).Path + + foreach ($cand in ($candidates | Select-Object -Unique)) { + if (Test-Path -LiteralPath (Join-Path $cand "openkore.pl")) { + return $cand + } + } + + throw "Não foi possível detectar a raiz do OpenKore automaticamente. Use -OpenKoreRoot `"C:\caminho\openkore`"." +} + +function Ensure-Dir { + param([string]$Path) + if (-not (Test-Path -LiteralPath $Path)) { + New-Item -ItemType Directory -Path $Path -Force | Out-Null + } +} + +function Wait-TcpOpen { + param( + [string]$TargetHost, + [int]$Port, + [int]$TimeoutMs = 8000 + ) + + $start = Get-Date + while (((Get-Date) - $start).TotalMilliseconds -lt $TimeoutMs) { + $client = New-Object System.Net.Sockets.TcpClient + try { + $iar = $client.BeginConnect($TargetHost, $Port, $null, $null) + if ($iar.AsyncWaitHandle.WaitOne(250, $false)) { + $client.EndConnect($iar) + return $true + } + } + catch {} + finally { + try { $client.Close() } catch {} + } + Start-Sleep -Milliseconds 200 + } + return $false +} + +function Resolve-Launcher { + param( + [string]$Root, + [string]$UserLauncherPath + ) + + if ($UserLauncherPath -and $UserLauncherPath.Trim() -ne "") { + if (-not (Test-Path -LiteralPath $UserLauncherPath)) { + throw "LauncherPath não encontrado: $UserLauncherPath" + } + return (Resolve-Path -LiteralPath $UserLauncherPath).Path + } + + $candidates = @( + (Join-Path $Root "start.exe"), + (Join-Path $Root "wxstart.exe"), + (Join-Path $Root "winguistart.exe"), + (Join-Path $Root "tkstart.exe"), + (Join-Path $Root "vxstart.exe"), + (Join-Path $Root "openkore.pl") + ) + + foreach ($candidate in $candidates) { + if (Test-Path -LiteralPath $candidate) { + return (Resolve-Path -LiteralPath $candidate).Path + } + } + + throw "Nenhum launcher encontrado (start.exe/wxstart.exe/winguistart.exe/tkstart.exe/vxstart.exe/openkore.pl)." +} + +function Write-EnvLauncherCmd { + param( + [string]$Root, + [string]$TargetHost, + [int]$Port + ) + + $cmdPath = Join-Path $Root "tools\openkore-socket-env.cmd" + @( + "@echo off", + "setlocal", + "set `"OPENKORE_SOCKET_TCP_HOST=$TargetHost`"", + "set `"OPENKORE_SOCKET_TCP_PORT=$Port`"", + "if `"%~1`"==`"`" goto :done", + "start `"`" cmd /k %*", + ":done" + ) | Set-Content -LiteralPath $cmdPath -Encoding ASCII + + return $cmdPath +} + +if ($SocketPort -le 0 -or $SocketPort -gt 65535) { + throw "SocketPort inválida: $SocketPort" +} + +$OpenKoreRoot = Resolve-OpenKoreRoot -UserPath $OpenKoreRoot + +$logsDir = Join-Path $OpenKoreRoot "logs" +$dataDir = Join-Path $OpenKoreRoot "data" +$openkorePidFile = Join-Path $dataDir "openkore_socket_tcp.pid" +$openkoreOut = Join-Path $logsDir "openkore_socket_tcp_stdout.log" +$openkoreErr = Join-Path $logsDir "openkore_socket_tcp_stderr.log" +$gatewayScript = Join-Path $OpenKoreRoot "tools\start-gateway.ps1" +$alreadyRunning = $false + +Ensure-Dir -Path $logsDir +Ensure-Dir -Path $dataDir + +$envCmdPath = Write-EnvLauncherCmd -Root $OpenKoreRoot -TargetHost $SocketHost -Port $SocketPort +Write-Host "Arquivo atualizado: $envCmdPath" +Write-Host "Use assim: tools\\openkore-socket-env.cmd start.exe --interface=Socket (abre em nova janela interativa)" + +if (Test-Path -LiteralPath $openkorePidFile) { + $existingPid = (Get-Content -LiteralPath $openkorePidFile -ErrorAction SilentlyContinue | Select-Object -First 1) + if ($existingPid) { + $existingProc = Get-Process -Id $existingPid -ErrorAction SilentlyContinue + if ($existingProc) { + Write-Host "OpenKore já está em execução (PID=$existingPid)." + $alreadyRunning = $true + } + } +} + +if (-not $LaunchOpenKore) { + if ($StartGateway) { + Write-Warning "-StartGateway foi ignorado porque -LaunchOpenKore não foi usado." + } + Write-Host "Configuração pronta. O script não iniciou OpenKore automaticamente." + return +} + +$launcher = Resolve-Launcher -Root $OpenKoreRoot -UserLauncherPath $LauncherPath + +Push-Location $OpenKoreRoot +try { + $env:OPENKORE_SOCKET_TCP_HOST = $SocketHost + $env:OPENKORE_SOCKET_TCP_PORT = "$SocketPort" + if (-not $alreadyRunning) { + $launcherName = [System.IO.Path]::GetFileName($launcher).ToLowerInvariant() + $openkoreArgs = @("--interface=Socket") + $openkoreFile = $launcher + + if ($launcherName -eq "openkore.pl") { + $openkoreFile = "perl" + $openkoreArgs = @(".\openkore.pl", "--interface=Socket") + } + + $openkoreProc = Start-Process -FilePath $openkoreFile -ArgumentList $openkoreArgs -WorkingDirectory $OpenKoreRoot -RedirectStandardOutput $openkoreOut -RedirectStandardError $openkoreErr -PassThru + Set-Content -LiteralPath $openkorePidFile -Value $openkoreProc.Id -Encoding ASCII + + if (Wait-TcpOpen -TargetHost $SocketHost -Port $SocketPort -TimeoutMs 10000) { + Write-Host "OpenKore iniciado com '$launcherName' e endpoint TCP de console em $SocketHost`:$SocketPort (PID=$($openkoreProc.Id))." + } else { + Write-Warning "OpenKore iniciou (PID=$($openkoreProc.Id)), mas a porta $SocketHost`:$SocketPort não abriu a tempo." + Write-Warning "Confira logs: $openkoreOut e $openkoreErr" + } + } else { + if (Wait-TcpOpen -TargetHost $SocketHost -Port $SocketPort -TimeoutMs 2000) { + Write-Host "Endpoint TCP já estava ativo em $SocketHost`:$SocketPort. Reutilizando instância existente." + } else { + Write-Warning "OpenKore já estava aberto, mas a porta $SocketHost`:$SocketPort não está ativa." + Write-Warning "Feche e inicie pelo script para aplicar OPENKORE_SOCKET_TCP_* e --interface=Socket." + } + } + + if ($StartGateway) { + if (-not (Test-Path -LiteralPath $gatewayScript)) { + throw "Script do gateway não encontrado: $gatewayScript" + } + & $gatewayScript -OpenKoreRoot $OpenKoreRoot -KoreHost $SocketHost -KorePort $SocketPort -ListenHost $GatewayListenHost -ListenPort $GatewayListenPort -CommandToken $CommandToken + } else { + Write-Host "Gateway não iniciado (-StartGateway:`$false)." + } +} +finally { + Pop-Location +}