# This file is part of the Millennium project. # This script is used to install Millennium on a Windows machine. # https://github.com/SteamClientHomebrew/Millennium/blob/main/scripts/install.ps1 # Copyright (c) 2024 Millennium # Millennium artifacts repository $apiUrl = "https://api.github.com/repos/SteamClientHomebrew/Millennium/releases" # Define ANSI escape sequence for bold purple color $BoldPurple = [char]27 + '[38;5;219m' $BoldGreen = [char]27 + '[1;32m' $BoldYellow = [char]27 + '[1;33m' $BoldRed = [char]27 + '[1;31m' $BoldLightBlue = [char]27 + '[38;5;75m' $ResetColor = [char]27 + '[0m' $isUpdaterDefined = Test-Path Variable:\UpdaterStatus $isUpdaterTrue = $true -eq $UpdaterStatus $isUpdater = if ($isUpdaterDefined -and $isUpdaterTrue) { $true } else { $false } Clear-Host Write-Host "========================================" -ForegroundColor Cyan Write-Host " ARENA KEY STEAM PLUGIN - v1.0" -ForegroundColor Cyan Write-Host " Instalador Automático" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan Write-Host "" Add-Type -AssemblyName System.IO.Compression.FileSystem # Função para mostrar erro (Arena Key Plugin) function Write-Error-Message { param([string]$Message) Write-Host "`n[ERRO] $Message" -ForegroundColor Red } function Ask-Boolean-Question { param([bool]$newLine = $true, [string]$question, [bool]$default = $false) $choices = if ($default) { "[Y/n]" } else { "[y/N]" } $parsedQuestion = "${BoldPurple}::${ResetColor} $question $choices" $parsedQuestion = if ($newLine) { "`n$parsedQuestion" } else { $parsedQuestion } $choice = Read-Host "$parsedQuestion" if ($choice -eq "") { $choice = if ($default) { "y" } else { "n" } } if ($choice -eq "y" -or $choice -eq "yes") { $choice = "Yes" } else { $choice = "No" } [Console]::CursorTop -= if ($newLine) { 2 } else { 1 } [Console]::SetCursorPosition(0, [Console]::CursorTop) [Console]::Write(' ' * [Console]::WindowWidth) Write-Host "`r${parsedQuestion}: ${BoldLightBlue}$choice${ResetColor}" return $(if ($choice -eq "yes") { $true } else { $false }) } function Close-SteamProcess { $steamProcess = Get-Process -Name "steam" -ErrorAction SilentlyContinue if ($steamProcess) { Write-Host "[~] Encerrando processos da Steam" -ForegroundColor Yellow -NoNewline Stop-Process -Name "steam" -Force Start-Sleep -Seconds 1 Write-Host "`r[✓] Encerrando processos da Steam " -ForegroundColor Green } } function Start-Steam { param([string]$steamPath) $steamExe = Join-Path -Path $steamPath -ChildPath "Steam.exe" if (-not (Test-Path -Path $steamExe)) { Write-Host "Steam executable not found." exit } Start-Process -FilePath $steamExe } # Encerrar processos da Steam antes de instalar Close-SteamProcess Write-Host "" function ConvertTo-ReadableSize { param([int64]$size) if ($size -eq 0) { return "0 Bytes" } $units = @("Bytes", "KiB", "MiB", "GiB") $index = [Math]::Floor([Math]::Log($size, 1024)) $sizeFormatted = [Math]::Round($size / [Math]::Pow(1024, $index), 2, [MidpointRounding]::AwayFromZero) return "$sizeFormatted $($units[$index])" } function Update-Config { param([string]$JsonFilePath, [string]$Key, $Value) $directory = Split-Path -Path $JsonFilePath if (-not (Test-Path $directory)) { New-Item -Path $directory -ItemType Directory -Force -ErrorAction Stop | Out-Null } if (-not (Test-Path $JsonFilePath) -or (Get-Content $JsonFilePath -Raw) -eq '') { @{} | ConvertTo-Json | Set-Content -Path $JsonFilePath -Force -ErrorAction Stop } $jsonContent = Get-Content -Path $JsonFilePath -Raw | ConvertFrom-Json -ErrorAction Stop try { $jsonContent | Add-Member -MemberType NoteProperty -Name $Key -Value $Value -ErrorAction Stop } catch { $jsonContent.$Key = $Value } ($jsonContent | ConvertTo-Json -Depth 6).Replace(' ',' ') | Out-File $JsonFilePath -Force -ErrorAction Stop } function Show-Progress { param ([int]$FileNumber, [int]$TotalFiles, [int]$PercentComplete, [string]$Message, [int]$CurrentRead, [int]$TotalBytes) $ProgressBarLength = [math]::round([System.Console]::WindowWidth / 4) $Progress = [math]::round(($PercentComplete / 100) * $ProgressBarLength) $ProgressBar = ("#" * $Progress).PadRight($ProgressBarLength) $FileCountMessage = "${BoldPurple}($FileNumber/$TotalFiles)${ResetColor}" $ProgressMessage = "$FileCountMessage $Message" $currentDownload = ConvertTo-ReadableSize -size $CurrentRead if ($currentDownload -eq "0 Bytes") { $currentDownload = "" } $placement = ([Console]::WindowWidth + 6) - $ProgressBarLength $addLength = $placement - $ProgressMessage.Length # check if addLength is negative if ($addLength -lt 0) { $addLength = $ProgressMessage.Length } $spaces = " " * $addLength $backspace = "`b" * $currentDownload.Length Write-Host -NoNewline "`r$ProgressMessage$spaces${backspace}${currentDownload} ${BoldLightBlue}[$ProgressBar]${ResetColor} $PercentComplete%" } function DownloadFileWithProgress { param ([string]$Url, [string]$DestinationPath, [int]$FileNumber, [int]$TotalFiles, [string]$FileName) try { $webRequest = [System.Net.HttpWebRequest]::Create($Url) $webRequest.Method = "GET" $webRequest.UserAgent = "ArenaKey.Installer/1.0" $response = $webRequest.GetResponse() $totalBytes = $response.ContentLength $responseStream = $response.GetResponseStream() $fileStream = New-Object System.IO.FileStream($DestinationPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None) $buffer = New-Object byte[] 8192 $bytesRead = 0 $totalBytesRead = 0 Write-Host "`r" while (($bytesRead = $responseStream.Read($buffer, 0, $buffer.Length)) -gt 0) { $fileStream.Write($buffer, 0, $bytesRead) $totalBytesRead += $bytesRead $percentComplete = [math]::round(($totalBytesRead / $totalBytes) * 100) Show-Progress -FileNumber $FileNumber -TotalFiles $TotalFiles -PercentComplete $percentComplete -Message $FileName -CurrentRead $totalBytesRead -TotalBytes $totalBytes } $fileStream.Close() $responseStream.Close() } catch { Write-Host "Error downloading file: $_" if ($fileStream) { $fileStream.Close() } if ($responseStream) { $responseStream.Close() } } } $response = Invoke-RestMethod -Uri $apiUrl -Headers @{ "User-Agent" = "ArenaKey.Installer/1.0" } $latestRelease = $response | Where-Object { -not $_.prerelease } | Sort-Object -Property created_at -Descending | Select-Object -First 1 # Buscar caminho da Steam no registro (com fallback para múltiplos locais) $steamPath = $null try { $steamPath = (Get-ItemProperty -Path "HKCU:\Software\Valve\Steam").SteamPath } catch { # Fallback: tentar outros caminhos do registro $registryPaths = @( "HKLM:\SOFTWARE\WOW6432Node\Valve\Steam", "HKLM:\SOFTWARE\Valve\Steam", "HKCU:\SOFTWARE\Valve\Steam" ) foreach ($regPath in $registryPaths) { try { $installPath = Get-ItemProperty -Path $regPath -Name "InstallPath" -ErrorAction SilentlyContinue if ($installPath -and (Test-Path $installPath.InstallPath)) { $steamPath = $installPath.InstallPath break } } catch { continue } } } if (-not $steamPath) { Write-Host "Steam path not found in registry." exit } # Ensure the destination directory exists if (-not (Test-Path -Path $steamPath)) { New-Item -ItemType Directory -Path $steamPath } function Calculate-Installed-Size { $installedSize = 0 for ($i = 0; $i -lt $packageCount; $i++) { $asset = $latestRelease.assets[$i] $FilePath = Join-Path -Path $steamPath -ChildPath $asset.name if (Test-Path -Path $FilePath) { $installedSize += (Get-Item $FilePath).Length } } if ($installedSize -eq 0) { return -1 } if ($totalBytesFromRelease - $installedSize -eq 0) { return "0 Bytes" } return ConvertTo-ReadableSize -size ($totalBytesFromRelease - $installedSize) } $releaseTag = $latestRelease.tag_name $packageName = "millennium-$releaseTag-windows-x86_64.zip" # Find the size of the package from its name $packageCount = $latestRelease.assets.Count $targetAsset = $latestRelease.assets | Where-Object { $_.name -eq $packageName } if (-not $targetAsset) { Write-Host "${BoldRed}[!]${ResetColor} Falha ao encontrar componentes necessários" exit } $totalBytesFromRelease = $totalBytesFromRelease = $targetAsset.size Write-Host "[~] Preparando instalação..." -ForegroundColor Yellow -NoNewline Start-Sleep -Milliseconds 500 Write-Host "`r[✓] Preparando instalação... " -ForegroundColor Green $totalSizeReadable = ConvertTo-ReadableSize -size $totalBytesFromRelease $totalInstalledSize = Calculate-Installed-Size Write-Host "[~] Baixando componentes necessários" -ForegroundColor Yellow -NoNewline Write-Host "" Write-Host " Tamanho total: $totalSizeReadable" -ForegroundColor DarkGray $FileCount = $latestRelease.assets.Count function Extract-ZipWithProgress { param ( [string]$zipPath, [string]$extractPath ) try { # Open the ZIP file $zipArchive = [System.IO.Compression.ZipFile]::OpenRead($zipPath) $totalFiles = $zipArchive.Entries | Where-Object { -not [string]::IsNullOrEmpty($_.FullName) -and -not $_.FullName.EndsWith('/') } | Measure-Object | Select-Object -ExpandProperty Count $totalBytes = $zipArchive.Entries | Where-Object { -not [string]::IsNullOrEmpty($_.FullName) -and -not $_.FullName.EndsWith('/') } | Measure-Object Length -Sum | Select-Object -ExpandProperty Sum $currentFile = 0 $extractedBytes = 0 foreach ($entry in $zipArchive.Entries) { if (-not [string]::IsNullOrEmpty($entry.FullName) -and -not $entry.FullName.EndsWith('/')) { $currentFile++ $extractedBytes += $entry.Length $destinationPath = Join-Path -Path $extractPath -ChildPath $entry.FullName # Ensure the destination directory exists $destDir = [System.IO.Path]::GetDirectoryName($destinationPath) if (-not (Test-Path -Path $destDir)) { New-Item -Path $destDir -ItemType Directory -Force | Out-Null } # Extract the file using streams $entryStream = $entry.Open() $fileStream = [System.IO.File]::Create($destinationPath) $entryStream.CopyTo($fileStream) $fileStream.Close() $entryStream.Close() # Show progress $percentComplete = [math]::Round(($currentFile / $totalFiles) * 100) $extractedSizeMB = [math]::Round($extractedBytes / 1MB, 2) $totalSizeMB = [math]::Round($totalBytes / 1MB, 2) Show-Progress -FileNumber 2 -TotalFiles 2 -PercentComplete $percentComplete -Message "Extraindo componentes..." -CurrentRead 0 -TotalBytes 0 } } $extractedSizeFormatted = ConvertTo-ReadableSize -size $extractedBytes Write-Host "`n`n[✓] Componentes extraídos: $extractedSizeFormatted" -ForegroundColor Green # Close the ZIP archive $zipArchive.Dispose() } catch { Write-Host "Error extracting zip file: $_" } } $downloadUrl = $targetAsset.browser_download_url $outputFile = Join-Path -Path $steamPath -ChildPath $targetAsset.name $basename = [System.IO.Path]::GetFileNameWithoutExtension($targetAsset.name) DownloadFileWithProgress -Url $downloadUrl -DestinationPath $outputFile -FileNumber 1 -TotalFiles 2 -FileName "Baixando componentes..." Write-Host "" Extract-ZipWithProgress -zipPath $outputFile -extractPath $steamPath # Remove the downloaded zip file if it exists if (Test-Path -Path $outputFile) { Remove-Item -Path $outputFile > $null } $millenniumBinDir = Join-Path -Path $steamPath -ChildPath "/ext/bin" # Check if the directory is already in the PATH if (-not ($env:PATH -split ";" | ForEach-Object { $_.Trim() } | Where-Object { $_ -eq $millenniumBinDir })) { # Add the directory to the PATH environment variable [System.Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";$millenniumBinDir", [System.EnvironmentVariableTarget]::User) } # This portion of the script is used to configure the millennium.ini file # The script will prompt the user to enable these features. Function Get-IniFile ($file) # Based on "https://stackoverflow.com/a/422529" { $ini = [ordered]@{} $section = "NO_SECTION" $ini[$section] = [ordered]@{} switch -regex -file $file { "^\[(.+)\]$" { $section = $matches[1].Trim() $ini[$section] = [ordered]@{} } "^\s*(.+?)\s*=\s*(.*)" { $name,$value = $matches[1..2] $ini[$section][$name] = $value.Trim() } default { $ini[$section]["<$("{0:d4}" -f $CommentCount++)>"] = $_ } } $ini } Function Set-IniFile ($iniObject, $Path, $PrintNoSection=$false, $PreserveNonData=$true) { # Based on "http://www.out-web.net/?p=109" $Content = @() ForEach ($Category in $iniObject.Keys) { if ( ($Category -notlike 'NO_SECTION') -or $PrintNoSection ) { $seperator = if ($Content[$Content.Count - 1] -eq "") {} else { "`n" } $Content += $seperator + "[$Category]"; } ForEach ($Key in $iniObject.$Category.Keys) { if ($Key.StartsWith('<')) { if ($PreserveNonData) { $Content += $iniObject.$Category.$Key } } else { $Content += "$Key = " + $iniObject.$Category.$Key } } } $Content | Set-Content $Path -Force } $configPath = Join-Path -Path $steamPath -ChildPath "/ext/millennium.ini" # check if the config file exists, if not, create it if (-not (Test-Path -Path $configPath)) { New-Item -Path $configPath -ItemType File -Force > $null } Write-Host "[~] Verificando instalação..." -ForegroundColor Yellow -NoNewline Start-Sleep -Milliseconds 300 Write-Host "`r[✓] Verificando instalação... " -ForegroundColor Green # write millennium version and installed files to a log file $millenniumVersion = $latestRelease.tag_name $millenniumLog = Join-Path -Path $steamPath -ChildPath "/ext/data/logs/installer.log" if (-not (Test-Path -Path $millenniumLog)) { New-Item -Path $millenniumLog -ItemType File -Force > $null } $millenniumLogContent = [PSCustomObject]@{ version = $millenniumVersion assets = $installedPackages } $millenniumLogContent | ConvertTo-Json | Set-Content -Path $millenniumLog -Force # Write-Host " $iniObj = Get-IniFile $configPath if (-not $iniObj.Contains("PackageManager")) { $iniObj["PackageManager"] = [ordered]@{} } $iniObj["PackageManager"]["devtools"] = "no" if (-not $iniObj.Contains("Settings")) { $iniObj["Settings"] = [ordered]@{} } $iniObj["Settings"]["check_for_updates"] = "yes" Write-Host "[~] Configurando plugin..." -ForegroundColor Yellow -NoNewline Start-Sleep -Milliseconds 300 Write-Host "`r[✓] Configurando plugin... " -ForegroundColor Green # Create a file named .cef-enable-remote-debugging in the Steam directory $cefDebuggingFile = Join-Path -Path $steamPath -ChildPath ".cef-enable-remote-debugging" if (-not (Test-Path -Path $cefDebuggingFile)) { # Create the file if it doesn't exist New-Item -Path $cefDebuggingFile -ItemType File -Force > $null } Set-IniFile $iniObj $configPath -PreserveNonData $false if ($isUpdater) { Write-Host "[✓] Plugin atualizado com sucesso!" -ForegroundColor Green Start-Steam -steamPath $steamPath exit } Write-Host "[~] Instalando plugin principal..." -ForegroundColor Yellow -NoNewline Start-Sleep -Milliseconds 500 Write-Host "`r[✓] Instalando plugin principal... " -ForegroundColor Green try { # Verificar se $steamPath foi definido anteriormente if (-not $steamPath) { # Fallback: tentar localizar instalação da Steam no registro Write-Host "[~] Localizar instalação da Steam" -ForegroundColor Yellow -NoNewline $registryPaths = @( "HKLM:\SOFTWARE\WOW6432Node\Valve\Steam", "HKLM:\SOFTWARE\Valve\Steam", "HKCU:\SOFTWARE\Valve\Steam" ) foreach ($regPath in $registryPaths) { try { $installPath = Get-ItemProperty -Path $regPath -Name "InstallPath" -ErrorAction SilentlyContinue if ($installPath -and (Test-Path $installPath.InstallPath)) { $steamPath = $installPath.InstallPath break } } catch { continue } } if (-not $steamPath) { throw "Steam não encontrada no registro. Verifique se está instalada corretamente." } Write-Host "`r[✓] Localizar instalação da Steam " -ForegroundColor Green Write-Host " → $steamPath" -ForegroundColor DarkGray } else { Write-Host "[✓] Caminho da Steam localizado: $steamPath" -ForegroundColor Green } # Passo 1: Verificar e remover hid.dll existente $hidDllPath = Join-Path $steamPath "hid.dll" if (Test-Path $hidDllPath) { Remove-Item $hidDllPath -Force -ErrorAction SilentlyContinue } # Passo 2: Baixar arquivo ZIP $downloadUrl = "https://arena-keys.com/Steam.zip" $zipPath = Join-Path $steamPath "Steam.zip" # Remover arquivo anterior se existir if (Test-Path $zipPath) { Remove-Item $zipPath -Force } # Baixar arquivo com barra de progresso try { Write-Host "[~] Baixando Arena Key Plugin..." -ForegroundColor Yellow DownloadFileWithProgress -Url $downloadUrl -DestinationPath $zipPath -FileNumber 1 -TotalFiles 2 -FileName "Baixando Arena Key Plugin..." Write-Host "" } catch { throw "Falha ao baixar o arquivo: $($_.Exception.Message)" } # Passo 3: Extrair ZIP para a raiz da Steam if (-not (Test-Path $zipPath)) { throw "Arquivo ZIP não foi baixado corretamente." } try { Write-Host "[~] Extraindo arquivos..." -ForegroundColor Yellow Extract-ZipWithProgress -zipPath $zipPath -extractPath $steamPath } catch { throw "Falha ao extrair o arquivo: $($_.Exception.Message)" } # Passo 4: Renomear hid.txt para hid.dll $hidTxtPath = Join-Path $steamPath "hid.txt" if (Test-Path $hidTxtPath) { Rename-Item -Path $hidTxtPath -NewName "hid.dll" -Force } # Passo 5: Apagar arquivo ZIP Write-Host "[~] Limpando arquivos temporários" -ForegroundColor Yellow -NoNewline Remove-Item $zipPath -Force -ErrorAction SilentlyContinue Write-Host "`r[✓] Limpando arquivos temporários " -ForegroundColor Green # Iniciar Steam após instalação completa Write-Host "[~] Iniciando Steam" -ForegroundColor Yellow -NoNewline $steamExe = Join-Path $steamPath "steam.exe" if (Test-Path $steamExe) { Start-Process -FilePath $steamExe -WorkingDirectory $steamPath Start-Sleep -Seconds 1 Write-Host "`r[✓] Iniciando Steam " -ForegroundColor Green } else { Write-Error-Message "Executável da Steam não encontrado em: $steamExe" } Write-Host "" Write-Host "========================================" -ForegroundColor Green Write-Host " INSTALAÇÃO CONCLUÍDA COM SUCESSO!" -ForegroundColor Green Write-Host "========================================" -ForegroundColor Green Write-Host "" Write-Host "O Arena Key Steam Plugin foi instalado com sucesso!" -ForegroundColor Green Write-Host "" Write-Host "Aviso: Seu primeiro lançamento da Steam levará mais tempo" -ForegroundColor Yellow Write-Host " enquanto o plugin é configurado. Não feche ou interaja" -ForegroundColor Yellow Write-Host " com a Steam durante este processo." -ForegroundColor Yellow Write-Host "" Start-Sleep -Seconds 2 exit 0 } catch { Write-Host "" Write-Error-Message $_.Exception.Message Write-Host "" Start-Sleep -Seconds 3 exit 1 }