View Raw # CCLS Games CLI Tool v2.0
# Complete Version with Setup, Search, Get functionality, and Logging
# Configuration
$baseUrl = "https://games.ccls.icu"
$cliApiUrl = "$baseUrl/CLI/api/2.0"
$settingsFolder = ".\settings"
$credentialsFile = "$settingsFolder\credentials.dat"
$settingsFile = "$settingsFolder\settings.json" # Using JSON instead of INI for simplicity
$logsFolder = ".\logs" # Logs folder path
# Script-level variables for progress display, logging, and credentials
$script:logFile = $null
$script:sessionStartTime = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$script:cachedCredentials = $null
$script:oversightEnabled = $false
$script:oversightData = $null
$script:userLoggingEnabled = $false
$script:userLoggingPath = $null
$script:userLoggingUserId = $null
$script:userLoggingSessionFile = $null
$script:dependenciesChecked = $false
$script:dependenciesSatisfied = $false
$script:libraryCache = $null
$script:libJsonPath = $null
# Ensure settings and logs directories exist
if (-not (Test-Path $settingsFolder)) {
New-Item -Path $settingsFolder -ItemType Directory | Out-Null
}
if (-not (Test-Path $logsFolder)) {
New-Item -Path $logsFolder -ItemType Directory | Out-Null
}
# Initialize log file for the current session
$script:logFile = Join-Path -Path $logsFolder -ChildPath "ccls_session_$($script:sessionStartTime).log"
"CCLS Games CLI Session started at $($script:sessionStartTime)" | Out-File -FilePath $script:logFile
# Background check to ensure script is named CLI.ps1 for update functionality
function Test-ScriptNaming {
try {
# Get the current script file information
$currentScriptPath = $null
# Try to get the script path using different methods
if ($PSCommandPath) {
$currentScriptPath = $PSCommandPath
} elseif ($MyInvocation.MyCommand.Path) {
$currentScriptPath = $MyInvocation.MyCommand.Path
} elseif ($script:MyInvocation.MyCommand.Path) {
$currentScriptPath = $script:MyInvocation.MyCommand.Path
}
if ($currentScriptPath) {
$currentFileName = Split-Path -Path $currentScriptPath -Leaf
$currentDirectory = Split-Path -Path $currentScriptPath -Parent
$expectedFileName = "CLI.ps1"
$expectedFilePath = Join-Path -Path $currentDirectory -ChildPath $expectedFileName
"Script naming check initiated" | Out-File -FilePath $script:logFile -Append
"Current script path: $currentScriptPath" | Out-File -FilePath $script:logFile -Append
"Current file name: $currentFileName" | Out-File -FilePath $script:logFile -Append
"Expected file name: $expectedFileName" | Out-File -FilePath $script:logFile -Append
if ($currentFileName -ne $expectedFileName) {
"Script name mismatch detected - renaming required" | Out-File -FilePath $script:logFile -Append
# Check if CLI.ps1 already exists in the directory
if (Test-Path $expectedFilePath) {
"Warning: CLI.ps1 already exists in directory" | Out-File -FilePath $script:logFile -Append
# Create a backup of the existing CLI.ps1
$backupName = "CLI_backup_$(Get-Date -Format 'yyyy-MM-dd_HH-mm-ss').ps1"
$backupPath = Join-Path -Path $currentDirectory -ChildPath $backupName
try {
Copy-Item -Path $expectedFilePath -Destination $backupPath -Force
"Created backup of existing CLI.ps1: $backupPath" | Out-File -FilePath $script:logFile -Append
} catch {
"Failed to create backup of existing CLI.ps1: $($_.Exception.Message)" | Out-File -FilePath $script:logFile -Append
}
}
# Rename the current script to CLI.ps1
try {
Copy-Item -Path $currentScriptPath -Destination $expectedFilePath -Force
"Successfully copied current script to CLI.ps1" | Out-File -FilePath $script:logFile -Append
# Remove the old file if it has a different name
if ($currentScriptPath -ne $expectedFilePath) {
Remove-Item -Path $currentScriptPath -Force
"Successfully removed old script file: $currentScriptPath" | Out-File -FilePath $script:logFile -Append
}
"Script renaming completed successfully" | Out-File -FilePath $script:logFile -Append
} catch {
"Failed to rename script file: $($_.Exception.Message)" | Out-File -FilePath $script:logFile -Append
"Update functionality may not work properly with current filename" | Out-File -FilePath $script:logFile -Append
}
} else {
"Script name is correct (CLI.ps1) - no action needed" | Out-File -FilePath $script:logFile -Append
}
} else {
"Could not determine current script path - skipping rename check" | Out-File -FilePath $script:logFile -Append
"Update functionality may be affected if script is not named CLI.ps1" | Out-File -FilePath $script:logFile -Append
}
} catch {
"Error during script naming check: $($_.Exception.Message)" | Out-File -FilePath $script:logFile -Append
}
}
# Run the script naming check
Test-ScriptNaming
function Write-CCLSHost {
param (
[Parameter(Position = 0)]
[string]$Message = "",
[Parameter()]
[System.ConsoleColor]$ForegroundColor = [System.ConsoleColor]::White,
[Parameter()]
[System.ConsoleColor]$BackgroundColor = [System.ConsoleColor]::Black,
[Parameter()]
[switch]$NoNewline,
[Parameter()]
[switch]$Log,
[Parameter()]
[switch]$NoConsole
)
# Check if the message is marked for removal
if ($Message.StartsWith('#')) {
# Don't show in console, but log it if needed
if ($Log) {
$cleanMessage = $Message.Substring(1).Trim()
if (-not [string]::IsNullOrWhiteSpace($cleanMessage)) {
$cleanMessage | Out-File -FilePath $script:logFile -Append
# Send to server logging if enabled
if ($script:userLoggingEnabled) {
Send-LogToServer -LogEntry $cleanMessage
}
}
}
return
}
if ($Message.StartsWith('/')) {
# Skip completely - don't log or display
return
}
# Handle empty lines
if ([string]::IsNullOrWhiteSpace($Message)) {
# Display in console if not suppressed
if (-not $NoConsole) {
Write-Host "" -NoNewline:$NoNewline
}
# But don't log empty lines
return
}
# Display in console if not suppressed
if (-not $NoConsole) {
Write-Host $Message -ForegroundColor $ForegroundColor -BackgroundColor $BackgroundColor -NoNewline:$NoNewline
}
# Log if requested
if ($Log) {
$plainMessage = $Message
$plainMessage | Out-File -FilePath $script:logFile -Append
# Send to server logging if enabled
if ($script:userLoggingEnabled) {
Send-LogToServer -LogEntry $plainMessage
}
}
}
function Initialize-Settings {
# Get version information using existing function (but don't show alerts here)
try {
$currentVersion = "2.1.5" # Static version, no need to call Test-VersionUpdate here
} catch {
# If version check fails, use a default version
$currentVersion = "2.1.5"
Write-CCLSHost "#Error getting version info, using default: $($_.Exception.Message)" -Log -NoConsole
}
if (-not (Test-Path $settingsFile)) {
# Create default settings file
$defaultSettings = @{
RememberLogin = $false
DownloadPath = ".\downloads"
TempDownloadPath = ".\tmp"
HasCompletedSetup = $false
Version = $currentVersion
DevMode = $false # Add DevMode parameter with default false
}
try {
$defaultSettings | ConvertTo-Json | Set-Content -Path $settingsFile
Write-CCLSHost "#Created new settings file with DevMode parameter" -Log -NoConsole
} catch {
Write-CCLSHost "#Error creating settings file: $($_.Exception.Message)" -Log -NoConsole
}
# Return settings without version change flag
return $defaultSettings
}
Initialize-StartupMessageDirectories
# Load settings
try {
$settingsContent = Get-Content -Path $settingsFile -Raw
$settings = $settingsContent | ConvertFrom-Json
} catch {
Write-CCLSHost "#Error loading settings file: $($_.Exception.Message)" -Log -NoConsole
# Return default settings if loading fails
return @{
RememberLogin = $false
DownloadPath = ".\downloads"
TempDownloadPath = ".\tmp"
HasCompletedSetup = $false
Version = $currentVersion
DevMode = $false
}
}
# Check if Version property exists, add it if not
if (-not (Get-Member -InputObject $settings -Name "Version" -MemberType Properties)) {
$settings | Add-Member -MemberType NoteProperty -Name "Version" -Value $currentVersion
try {
$settings | ConvertTo-Json | Set-Content -Path $settingsFile
Write-CCLSHost "#Added Version parameter to settings" -Log -NoConsole
} catch {
Write-CCLSHost "#Error updating settings with Version: $($_.Exception.Message)" -Log -NoConsole
}
}
# Check if DevMode property exists, add it if not
if (-not (Get-Member -InputObject $settings -Name "DevMode" -MemberType Properties)) {
$settings | Add-Member -MemberType NoteProperty -Name "DevMode" -Value $false
try {
$settings | ConvertTo-Json | Set-Content -Path $settingsFile
Write-CCLSHost "#Added DevMode parameter to existing settings" -Log -NoConsole
} catch {
Write-CCLSHost "#Error updating settings with DevMode: $($_.Exception.Message)" -Log -NoConsole
}
}
# Check if version has changed and update if needed
if ($settings.Version -ne $currentVersion) {
Write-CCLSHost "#Detected version change from $($settings.Version) to $currentVersion" -Log -NoConsole
# Store the old version
$oldVersion = $settings.Version
# Update to new version
$settings.Version = $currentVersion
try {
$settings | ConvertTo-Json | Set-Content -Path $settingsFile
} catch {
Write-CCLSHost "#Error updating version in settings: $($_.Exception.Message)" -Log -NoConsole
}
# Download latest Python downloader when CLI version changes
try {
$scriptLocation = if ($PSScriptRoot) { $PSScriptRoot } else { (Get-Location).Path }
$pythonScript = Join-Path -Path $scriptLocation -ChildPath "ccls_downloader.py"
$downloadUrl = "https://games.ccls.icu/CLI/api/main/ccls_downloader/1.2.0/ccls_downloader.py"
Write-CCLSHost "#Downloading updated Python downloader..." -Log -NoConsole
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "CCLS-CLI/2.0")
$webClient.DownloadFile($downloadUrl, $pythonScript)
Write-CCLSHost "#Python downloader updated successfully" -Log -NoConsole
} catch {
Write-CCLSHost "#Error downloading Python downloader: $($_.Exception.Message)" -Log -NoConsole
}
# Keep track that version has changed for later notification
$script:versionChanged = $true
$script:previousVersion = $oldVersion
}
return $settings
}
function Test-SystemRequirementsSilent {
$results = @{
PythonInstalled = $false
PythonVersion = $null
RequestsInstalled = $false
RequestsVersion = $null
SevenZipInstalled = $false
SevenZipVersion = $null
MissingDependencies = @()
}
# Check for Python
try {
$pythonResult = python --version 2>&1
if ($pythonResult -match "Python (\d+\.\d+\.\d+)") {
$results.PythonInstalled = $true
$results.PythonVersion = $matches[1]
}
} catch {
# Python not installed
}
# Check for Python requests library (only if Python is installed)
if ($results.PythonInstalled) {
try {
$requestsCheck = python -c "import requests; print('Installed (v{0})'.format(requests.__version__))" 2>&1
if ($requestsCheck -match "Installed \(v([\d\.]+)\)") {
$results.RequestsInstalled = $true
$results.RequestsVersion = $matches[1]
}
} catch {
# Requests not installed
}
}
# Check for 7-Zip
$systemPaths = @(
"C:\Program Files\7-Zip\7z.exe",
"${env:ProgramFiles(x86)}\7-Zip\7z.exe"
)
$scriptLocation = if ($PSScriptRoot) {
$PSScriptRoot
} else {
(Get-Location).Path
}
$localPath = Join-Path -Path $scriptLocation -ChildPath "7zip\7z.exe"
$allPaths = $systemPaths + $localPath
foreach ($path in $allPaths) {
if (Test-Path -Path $path) {
$results.SevenZipInstalled = $true
try {
$versionInfo = Get-Item $path | Select-Object -ExpandProperty VersionInfo
$results.SevenZipVersion = $versionInfo.ProductVersion
} catch {
$results.SevenZipVersion = "Unknown"
}
break
}
}
# Determine missing dependencies
if (-not $results.PythonInstalled) {
$results.MissingDependencies += "install python"
}
if (-not $results.RequestsInstalled) {
if ($results.PythonInstalled) {
$results.MissingDependencies += "install requests"
}
# Note: if Python is not installed, we don't add requests to missing dependencies
# because the user will get an error when trying to install requests without Python
}
if (-not $results.SevenZipInstalled) {
$results.MissingDependencies += "install 7zip"
}
return $results
}
# Function to check dependencies and block CLI usage if missing
function Test-RequiredDependencies {
if ($script:dependenciesChecked) {
return $script:dependenciesSatisfied
}
$script:dependenciesChecked = $true
$dependencyResults = Test-SystemRequirementsSilent
if ($dependencyResults.MissingDependencies.Count -eq 0) {
$script:dependenciesSatisfied = $true
Write-CCLSHost "#All required dependencies are satisfied" -Log -NoConsole
return $true
} else {
$script:dependenciesSatisfied = $false
Write-CCLSHost "CLI Tool is missing critical dependencies, run 'check' for more info" -ForegroundColor Red -Log
Write-CCLSHost "The script will remain in a broken state until dependency requirements are met" -ForegroundColor Red -Log
return $false
}
}
# Function to enforce dependency requirements for most commands
function Assert-DependenciesRequired {
param (
[string]$CommandName = "this command"
)
if (-not (Test-RequiredDependencies)) {
Write-CCLSHost "'$CommandName' is missing critical dependencies, run 'check' for more info" -ForegroundColor Red -Log
Write-CCLSHost "The script will remain in a broken state until dependency requirements are met" -ForegroundColor Red -Log
return $false
}
return $true
}
function Initialize-LibraryCache {
# Set the lib.json path
$script:libJsonPath = Join-Path -Path $settingsFolder -ChildPath "lib.json"
Write-CCLSHost "#Initializing library cache system..." -Log -NoConsole
# Check if lib.json exists
if (-not (Test-Path $script:libJsonPath)) {
Write-CCLSHost "#lib.json not found, creating initial cache..." -Log -NoConsole
Update-LibraryCache -Force
} else {
# Load existing cache and validate it
$script:libraryCache = Get-LibraryCache
if ($script:libraryCache -and (Test-LibraryCacheValid)) {
Write-CCLSHost "#Library cache loaded and validated successfully" -Log -NoConsole
} else {
Write-CCLSHost "#Library cache invalid or corrupted, rebuilding..." -Log -NoConsole
Update-LibraryCache -Force
}
}
}
# Load library cache from file
function Get-LibraryCache {
try {
if (Test-Path $script:libJsonPath) {
$cacheContent = Get-Content -Path $script:libJsonPath -Raw | ConvertFrom-Json
return $cacheContent
}
} catch {
Write-CCLSHost "#Error loading library cache: $($_.Exception.Message)" -Log -NoConsole
}
return $null
}
# Validate library cache against current file system
function Test-LibraryCacheValid {
if (-not $script:libraryCache) {
return $false
}
$settings = Initialize-Settings
$downloadPath = $settings.DownloadPath
# Check if download path exists
if (-not (Test-Path $downloadPath)) {
Write-CCLSHost "#Download path doesn't exist, cache invalid" -Log -NoConsole
return $false
}
# Get current folders in download directory
$currentFolders = Get-ChildItem -Path $downloadPath -Directory -ErrorAction SilentlyContinue
# Check if all cached games still exist
foreach ($cachedGame in $script:libraryCache.games) {
$gamePath = Join-Path -Path $downloadPath -ChildPath $cachedGame.folderName
if (-not (Test-Path $gamePath)) {
Write-CCLSHost "#Cached game '$($cachedGame.folderName)' no longer exists" -Log -NoConsole
return $false
}
# Check if JSON metadata files have been modified
if ($cachedGame.hasMetadata -and $cachedGame.gameId) {
$jsonPath = Join-Path -Path $gamePath -ChildPath "$($cachedGame.gameId).json"
if (Test-Path $jsonPath) {
$currentModified = (Get-Item $jsonPath).LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")
if ($cachedGame.jsonLastModified -ne $currentModified) {
Write-CCLSHost "#JSON metadata for '$($cachedGame.folderName)' has been modified" -Log -NoConsole
return $false
}
}
}
}
# Check if there are new folders not in cache
$cachedFolderNames = $script:libraryCache.games | ForEach-Object { $_.folderName }
foreach ($folder in $currentFolders) {
if ($folder.Name -notin $cachedFolderNames) {
Write-CCLSHost "#New folder '$($folder.Name)' found, not in cache" -Log -NoConsole
return $false
}
}
Write-CCLSHost "#Library cache validation successful" -Log -NoConsole
return $true
}
# Update library cache
function Update-LibraryCache {
param (
[switch]$Force
)
$settings = Initialize-Settings
$downloadPath = $settings.DownloadPath
Write-CCLSHost "#Updating library cache..." -Log -NoConsole
# Initialize cache structure
$cacheData = @{
lastUpdated = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
totalGames = 0
totalSize = 0
totalSizeFormatted = "0 B"
games = @()
}
# Check if download path exists
if (-not (Test-Path $downloadPath)) {
Write-CCLSHost "#Download path doesn't exist, creating empty cache" -Log -NoConsole
$script:libraryCache = $cacheData
Save-LibraryCache
return
}
# Get all folders in download directory
$gameFolders = Get-ChildItem -Path $downloadPath -Directory -ErrorAction SilentlyContinue
$totalSize = 0
Write-CCLSHost "#Scanning $($gameFolders.Count) game folders..." -Log -NoConsole
foreach ($folder in $gameFolders) {
Write-CCLSHost "#Processing folder: $($folder.Name)" -Log -NoConsole
$gameEntry = @{
folderName = $folder.Name
gameName = $folder.Name # Default to folder name
gameId = "Unknown"
localVersion = "Unknown"
localSize = 0
localSizeFormatted = "0 B"
localPath = $folder.FullName
hasMetadata = $false
isOutdated = $false
onlineVersion = "Unknown"
jsonLastModified = $null
}
# Calculate folder size
try {
$folderSize = Get-FolderSize -Path $folder.FullName
$gameEntry.localSize = $folderSize
$gameEntry.localSizeFormatted = Format-Size -Size $folderSize
$totalSize += $folderSize
} catch {
Write-CCLSHost "#Error calculating size for $($folder.Name): $($_.Exception.Message)" -Log -NoConsole
$gameEntry.localSizeFormatted = "Unknown"
}
# Look for JSON metadata files
$jsonFiles = Get-ChildItem -Path $folder.FullName -Filter "*.json" -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "^c[gb]\d{4}\.json$" }
if ($jsonFiles.Count -gt 0) {
$jsonFile = $jsonFiles[0]
$gameEntry.gameId = [System.IO.Path]::GetFileNameWithoutExtension($jsonFile.Name)
$gameEntry.hasMetadata = $true
$gameEntry.jsonLastModified = $jsonFile.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")
# Read game info from JSON
try {
$localGameInfo = Get-Content -Path $jsonFile.FullName -Raw | ConvertFrom-Json
if ($localGameInfo.name -and -not [string]::IsNullOrWhiteSpace($localGameInfo.name)) {
$gameEntry.gameName = $localGameInfo.name
}
if ($localGameInfo.version -and -not [string]::IsNullOrWhiteSpace($localGameInfo.version)) {
$gameEntry.localVersion = $localGameInfo.version
}
} catch {
Write-CCLSHost "#Error reading JSON metadata for $($gameEntry.gameId): $($_.Exception.Message)" -Log -NoConsole
}
}
# Add to cache
$cacheData.games += $gameEntry
}
# Update totals
$cacheData.totalGames = $cacheData.games.Count
$cacheData.totalSize = $totalSize
$cacheData.totalSizeFormatted = Format-Size -Size $totalSize
# Save cache
$script:libraryCache = $cacheData
Save-LibraryCache
Write-CCLSHost "#Library cache updated successfully. Total: $($cacheData.totalGames) games, $($cacheData.totalSizeFormatted)" -Log -NoConsole
}
# Save library cache to file
function Save-LibraryCache {
try {
$jsonContent = $script:libraryCache | ConvertTo-Json -Depth 10
Set-Content -Path $script:libJsonPath -Value $jsonContent -Encoding UTF8 -Force
Write-CCLSHost "#Library cache saved to: $script:libJsonPath" -Log -NoConsole
} catch {
Write-CCLSHost "#Error saving library cache: $($_.Exception.Message)" -Log -NoConsole
}
}
# Get game from cache by various identifiers
function Get-GameFromCache {
param (
[string]$Identifier,
[string]$SearchType = "folderName"
)
if (-not $script:libraryCache -or -not $script:libraryCache.games) {
return $null
}
switch ($SearchType.ToLower()) {
"id" {
return $script:libraryCache.games | Where-Object { $_.gameId -eq $Identifier }
}
"gamename" {
# Case-insensitive name search
return $script:libraryCache.games | Where-Object { $_.gameName.ToLower() -eq $Identifier.ToLower() }
}
"foldername" {
# Case-insensitive folder name search
return $script:libraryCache.games | Where-Object { $_.folderName.ToLower() -eq $Identifier.ToLower() }
}
default {
return $script:libraryCache.games | Where-Object { $_.folderName.ToLower() -eq $Identifier.ToLower() }
}
}
}
# Function to handle cache errors gracefully
function Handle-CacheError {
param (
[string]$Operation,
[System.Exception]$Exception
)
Write-CCLSHost "#Cache error during $Operation`: $($Exception.Message)" -Log -NoConsole
# If cache is corrupted, try to recreate it
if ($Operation -eq "load" -or $Operation -eq "validate") {
Write-CCLSHost "#Attempting to recreate corrupted cache..." -Log -NoConsole
try {
Update-LibraryCache -Force
} catch {
Write-CCLSHost "#Failed to recreate cache: $($_.Exception.Message)" -Log -NoConsole
}
}
}
# 6. Cache validation improvements - Add additional validation checks:
function Test-LibraryCacheIntegrity {
if (-not $script:libraryCache) {
return $false
}
# Check if cache structure is valid
$requiredProperties = @('lastUpdated', 'totalGames', 'totalSize', 'totalSizeFormatted', 'games')
foreach ($prop in $requiredProperties) {
if (-not (Get-Member -InputObject $script:libraryCache -Name $prop -MemberType Properties)) {
Write-CCLSHost "#Cache missing required property: $prop" -Log -NoConsole
return $false
}
}
# Check if games array is valid
if ($script:libraryCache.games -isnot [array] -and $script:libraryCache.totalGames -gt 0) {
Write-CCLSHost "#Cache games property is not an array but totalGames > 0" -Log -NoConsole
return $false
}
return $true
}
# 7. Performance optimization - Add size calculation caching:
function Get-CachedFolderSize {
param (
[string]$Path,
[string]$FolderName
)
# First try to get from cache
if ($script:libraryCache) {
$cachedGame = Get-GameFromCache -Identifier $FolderName -SearchType "foldername"
if ($cachedGame) {
return $cachedGame.localSize
}
}
# If not in cache, calculate and return
return Get-FolderSize -Path $Path
}
# 8. Debug and maintenance functions:
function Show-LibraryCacheStatus {
Write-CCLSHost "`nLibrary Cache Status" -ForegroundColor Green -Log
Write-CCLSHost "===================" -ForegroundColor Green -Log
if (-not $script:libraryCache) {
Write-CCLSHost "Cache Status: Not loaded" -ForegroundColor Red -Log
return
}
Write-CCLSHost "Cache Status: Loaded" -ForegroundColor Green -Log
Write-CCLSHost "Cache File: $script:libJsonPath" -Log
Write-CCLSHost "Last Updated: $($script:libraryCache.lastUpdated)" -Log
Write-CCLSHost "Total Games: $($script:libraryCache.totalGames)" -Log
Write-CCLSHost "Total Size: $($script:libraryCache.totalSizeFormatted)" -Log
if (Test-Path $script:libJsonPath) {
$cacheFileInfo = Get-Item $script:libJsonPath
$cacheFileSize = Format-Size -Size $cacheFileInfo.Length
Write-CCLSHost "Cache File Size: $cacheFileSize" -Log
Write-CCLSHost "Cache File Modified: $($cacheFileInfo.LastWriteTime)" -Log
}
# Validate cache
$isValid = Test-LibraryCacheValid
$isIntegrity = Test-LibraryCacheIntegrity
Write-CCLSHost "Cache Valid: $(if ($isValid) { 'Yes' } else { 'No' })" -ForegroundColor $(if ($isValid) { 'Green' } else { 'Red' }) -Log
Write-CCLSHost "Cache Integrity: $(if ($isIntegrity) { 'Good' } else { 'Corrupted' })" -ForegroundColor $(if ($isIntegrity) { 'Green' } else { 'Red' }) -Log
}
function Toggle-DevMode {
$settings = Initialize-Settings
if ($settings.DevMode) {
# Currently in DevMode, turn it off
$settings.DevMode = $false
Save-Settings -settings $settings
# Clear any stored or cached credentials
if (Test-Path $credentialsFile) {
Remove-Item -Path $credentialsFile -Force
Write-CCLSHost "#Removed stored credentials" -Log -NoConsole
}
# Clear cached credentials
$script:cachedCredentials = $null
# Clear console and show exit message
Clear-Host
Write-CCLSHost "Exited devmode" -ForegroundColor Green -Log
# Show the normal login interface
Write-CCLSHost "Hello and welcome to the CCLS Games CLI Tool" -ForegroundColor Green -Log
Write-CCLSHost "Before proceeding to using this software you will need to sign in." -Log
Write-CCLSHost "If you do not have an account already please go to $baseUrl/login.php?signup to register a new account." -Log
# Try auto-login if remember login is enabled
$loginResult = @{ Success = $false; DevMode = $false }
if ($settings.RememberLogin) {
$loginResult = Start-AutoLogin
}
# If auto-login failed or is disabled, do manual login
if (-not $loginResult.Success) {
$loginResult = Start-ManualLogin
}
# If login succeeded, show main interface
if ($loginResult.Success) {
# Normal login - show welcome message
Show-StartupMessage
Write-CCLSHost "Welcome to CCLS Games CLI Tool, $($loginResult.Username)!" -ForegroundColor Green -Log
# Check dependencies after successful login
Test-RequiredDependencies | Out-Null
# Check if version has changed and show notification
if ($script:versionChanged) {
$versionInfo = Test-VersionUpdate
$currentVersion = $versionInfo.CurrentVersion
Write-CCLSHost "Welcome to $currentVersion! Type 'changelog latest' to view the changes made." -ForegroundColor Green -Log
}
# Show appropriate message based on setup status
if ($settings.HasCompletedSetup) {
Write-CCLSHost "Type 'help' for a list of available commands." -ForegroundColor Cyan -Log
}
else {
Write-CCLSHost "ALERT, type command 'setup' to set critical values before downloading." -ForegroundColor Red -Log
}
Start-CommandInterface -username $loginResult.Username
}
else {
Write-CCLSHost "Login failed. Press any key to exit..." -ForegroundColor Red -Log
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
Exit
}
} else {
# Currently in normal mode, turn on DevMode
$settings.DevMode = $true
Save-Settings -settings $settings
# Clear any stored or cached credentials
if (Test-Path $credentialsFile) {
Remove-Item -Path $credentialsFile -Force
Write-CCLSHost "#Removed stored credentials" -Log -NoConsole
}
# Clear cached credentials
$script:cachedCredentials = $null
Write-CCLSHost "You are now in developer mode. 'get', 'search' and any other command that calls external API's will not work." -ForegroundColor Red -Log
}
}
function Read-UsernameWithDevModeDetection {
$devModeActivated = $false
Write-CCLSHost "Username: " -ForegroundColor Cyan -NoNewline -Log
# Use a simpler approach - check for Ctrl+Q using a different method
$username = ""
$ctrlQPressed = $false
try {
# Try to use ReadKey if available, otherwise fall back to Read-Host
if ($Host.UI.RawUI -and $Host.UI.RawUI.KeyAvailable -ne $null) {
# Custom input handling to detect Ctrl+Q
while ($true) {
if ([Console]::KeyAvailable) {
$keyInfo = [Console]::ReadKey($true)
# Check for Ctrl+Q
if (($keyInfo.Modifiers -band [ConsoleModifiers]::Control) -and
($keyInfo.Key -eq [ConsoleKey]::Q)) {
$ctrlQPressed = $true
# Don't show anything when Ctrl+Q is pressed
continue
}
# Handle Enter key
if ($keyInfo.Key -eq [ConsoleKey]::Enter) {
Write-Host "" # New line
break
}
# Handle Backspace
if ($keyInfo.Key -eq [ConsoleKey]::Backspace) {
if ($username.Length -gt 0) {
$username = $username.Substring(0, $username.Length - 1)
Write-Host "`b `b" -NoNewline
}
continue
}
# Handle regular characters
if ($keyInfo.KeyChar -match '[a-zA-Z0-9\s]' -and $keyInfo.KeyChar -ne "`0") {
$username += $keyInfo.KeyChar
Write-Host $keyInfo.KeyChar -NoNewline
}
} else {
Start-Sleep -Milliseconds 50
}
}
# Check if Ctrl+Q was pressed and username is "devmode"
if ($ctrlQPressed -and $username.ToLower().Trim() -eq "devmode") {
$devModeActivated = $true
}
} else {
# Fallback to simple Read-Host for environments that don't support advanced key handling
Write-CCLSHost "#Using fallback input method" -Log -NoConsole
$tempInput = Read-Host
# Check for special devmode activation pattern
# Allow users to type "ctrl+q devmode" as an alternative
if ($tempInput.ToLower().Trim() -eq "ctrl+q devmode" -or $tempInput.ToLower().Trim() -eq "devmode ctrl+q") {
$devModeActivated = $true
$username = "devmode"
} else {
$username = $tempInput
}
}
} catch {
Write-CCLSHost "#Error in key detection, falling back to Read-Host: $($_.Exception.Message)" -Log -NoConsole
# Ultimate fallback
$tempInput = Read-Host
if ($tempInput.ToLower().Trim() -eq "ctrl+q devmode" -or $tempInput.ToLower().Trim() -eq "devmode ctrl+q") {
$devModeActivated = $true
$username = "devmode"
} else {
$username = $tempInput
}
}
Write-CCLSHost "$username" -NoConsole -Log
return @{
Username = $username
DevModeActivated = $devModeActivated
}
}
# Save settings
function Save-Settings($settings) {
$settings | ConvertTo-Json | Set-Content -Path $settingsFile
}
# Store credentials securely
function Save-Credentials($username, $password) {
$credentials = @{
Username = $username
Password = $password
} | ConvertTo-Json
# Simple encryption (replace with more secure method if needed)
$securePassword = ConvertTo-SecureString -String $credentials -AsPlainText -Force
$encryptedText = ConvertFrom-SecureString -SecureString $securePassword
# Save to file
$encryptedText | Set-Content -Path $credentialsFile
}
# Load stored credentials
function Get-StoredCredentials {
if (Test-Path $credentialsFile) {
try {
$encryptedText = Get-Content -Path $credentialsFile
$securePassword = ConvertTo-SecureString -String $encryptedText
$credentials = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
)
return $credentials | ConvertFrom-Json
}
catch {
Write-CCLSHost "Error reading credentials: $($_.Exception.Message)" -ForegroundColor Red -Log
return $null
}
}
return $null
}
# Run setup process
function Start-Setup {
Write-CCLSHost "`n`nCCLS Games CLI Setup" -ForegroundColor Green -Log
Write-CCLSHost "=====================" -ForegroundColor Green -Log
Write-CCLSHost "Please configure the following settings:`n" -ForegroundColor Cyan -Log
$settings = Initialize-Settings
# Get game installation directory
$validPath = $false
while (-not $validPath) {
Write-CCLSHost "Set your games default installation directory: " -ForegroundColor Yellow -NoNewline -Log
$downloadPath = Read-Host
Write-CCLSHost "$downloadPath" -NoConsole -Log
if ([string]::IsNullOrWhiteSpace($downloadPath)) {
Write-CCLSHost "Please enter a valid directory path." -ForegroundColor Red -Log
}
else {
# Create directory if it doesn't exist
if (-not (Test-Path $downloadPath)) {
try {
New-Item -ItemType Directory -Path $downloadPath -Force | Out-Null
$validPath = $true
}
catch {
Write-CCLSHost "Error creating directory: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
else {
$validPath = $true
}
}
}
# Get temporary download directory
$validTempPath = $false
while (-not $validTempPath) {
Write-CCLSHost "Set the temporary directory of downloading files before they have finished downloading: " -ForegroundColor Yellow -NoNewline -Log
$tempDownloadPath = Read-Host
Write-CCLSHost "$tempDownloadPath" -NoConsole -Log
if ([string]::IsNullOrWhiteSpace($tempDownloadPath)) {
Write-CCLSHost "Please enter a valid directory path." -ForegroundColor Red -Log
}
else {
# Create directory if it doesn't exist
if (-not (Test-Path $tempDownloadPath)) {
try {
New-Item -ItemType Directory -Path $tempDownloadPath -Force | Out-Null
$validTempPath = $true
}
catch {
Write-CCLSHost "Error creating directory: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
else {
$validTempPath = $true
}
}
}
# Update settings
$settings.DownloadPath = $downloadPath
$settings.TempDownloadPath = $tempDownloadPath
$settings.HasCompletedSetup = $true
Save-Settings -settings $settings
Write-CCLSHost "`nGreat, you have now completed the setup. Type 'help' for a list of commands to get you started." -ForegroundColor Green -Log
}
# Validate username against server
function Test-Username($username) {
$params = @{
Uri = "$cliApiUrl/username_check.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $username
}
}
try {
$response = Invoke-RestMethod @params
return $response.exists
}
catch {
Write-CCLSHost "Error connecting to the server: $($_.Exception.Message)" -ForegroundColor Red -Log
return $false
}
}
# Validate password against server
function Test-Password($username, $password) {
$params = @{
Uri = "$cliApiUrl/password_check.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $username
password = $password
}
}
try {
$response = Invoke-RestMethod @params
return $response.success
}
catch {
Write-CCLSHost "Error connecting to the server: $($_.Exception.Message)" -ForegroundColor Red -Log
return $false
}
}
function Start-AutoLogin {
$credentials = Get-StoredCredentials
if ($credentials -ne $null) {
Write-CCLSHost "#Attempting to login with stored credentials..." -Log
if (Test-Password -username $credentials.Username -password $credentials.Password) {
Write-CCLSHost "#Auto-login successful!" -Log
# Cache the credentials for the session
$script:cachedCredentials = @{
Username = $credentials.Username
Password = $credentials.Password
}
# Initialize oversight system after successful auto-login (THIS WAS MISSING!)
$versionInfo = Test-VersionUpdate
$currentVersion = $versionInfo.CurrentVersion
$script:oversightData = Get-OversightData -Username $credentials.Username -CurrentVersion $currentVersion
if ($script:oversightData) {
$script:oversightEnabled = $true
if ($script:oversightData.user_logging) {
$script:userLoggingEnabled = $true
Initialize-UserLogging -Username $credentials.Username
}
}
Test-RequiredDependencies | Out-Null
return @{
Success = $true
Username = $credentials.Username
}
}
else {
Write-CCLSHost "Stored credentials are no longer valid." -ForegroundColor Yellow -Log
# Remove invalid credentials
if (Test-Path $credentialsFile) {
Remove-Item -Path $credentialsFile -Force
}
}
}
return @{
Success = $false
}
}
function Start-ManualLogin {
$maxAttempts = 3
$attempts = 0
while ($attempts -lt $maxAttempts) {
# Use the new function to detect Ctrl+Q + devmode
$usernameResult = Read-UsernameWithDevModeDetection
$username = $usernameResult.Username
$devModeActivated = $usernameResult.DevModeActivated
# Check if DevMode was activated and username is "devmode"
if ($devModeActivated -and $username.ToLower() -eq "devmode") {
Write-CCLSHost "#DevMode activation detected via Ctrl+Q + devmode" -Log -NoConsole
# Enable DevMode
$settings = Initialize-Settings
$settings.DevMode = $true
Save-Settings -settings $settings
# Clear any stored or cached credentials
if (Test-Path $credentialsFile) {
Remove-Item -Path $credentialsFile -Force
Write-CCLSHost "#Removed stored credentials for DevMode" -Log -NoConsole
}
# Clear cached credentials
$script:cachedCredentials = $null
Write-CCLSHost "You are in developer mode. 'get', 'search' and any other command that calls external API's will not work." -ForegroundColor Red -Log
return @{
Success = $true
Username = "Developer"
DevMode = $true
}
}
# Check if username exists (normal login flow)
Write-CCLSHost "#Checking username..." -Log
if (Test-Username -username $username) {
Write-CCLSHost "#Username found!" -Log
Write-CCLSHost "Password: " -ForegroundColor Cyan -NoNewline -Log
$password = Read-Host -AsSecureString
$passwordPlain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
)
Write-CCLSHost "********" -NoConsole -Log
Write-CCLSHost "#Validating password..." -Log
if (Test-Password -username $username -password $passwordPlain) {
Write-CCLSHost "#Login successful!" -Log
# Cache the credentials for the session
$script:cachedCredentials = @{
Username = $username
Password = $passwordPlain
}
# Initialize oversight system after successful login
$versionInfo = Test-VersionUpdate
$currentVersion = $versionInfo.CurrentVersion
$script:oversightData = Get-OversightData -Username $username -CurrentVersion $currentVersion
if ($script:oversightData) {
$script:oversightEnabled = $true
if ($script:oversightData.user_logging) {
$script:userLoggingEnabled = $true
Initialize-UserLogging -Username $username
}
}
# Ask if user wants to save credentials - no newline before this
Write-CCLSHost "Do you want to remember your login info for next time? (Y/N)" -ForegroundColor Yellow -Log
$rememberLogin = Read-Host
Write-CCLSHost "$rememberLogin" -NoConsole -Log
if ($rememberLogin.ToLower() -eq "y") {
Save-Credentials -username $username -password $passwordPlain
$settings = Initialize-Settings
$settings.RememberLogin = $true
Save-Settings -settings $settings
Write-CCLSHost "#Login information saved." -Log
}
return @{
Success = $true
Username = $username
DevMode = $false
}
}
else {
Write-CCLSHost "Incorrect password, please try again." -ForegroundColor Red -Log
$attempts++
}
}
else {
Write-CCLSHost "Username not found, please try again." -ForegroundColor Red -Log
$attempts++
}
}
Write-CCLSHost "Too many failed login attempts. Please try again later." -ForegroundColor Red -Log
return @{
Success = $false
DevMode = $false
}
}
# Startup Message System Functions
# Add these functions to your CLI.ps1 script
# Initialize startup message directories
function Initialize-StartupMessageDirectories {
$startMessageFolder = Join-Path -Path $settingsFolder -ChildPath "start-message"
$defaultMessageFolder = Join-Path -Path $startMessageFolder -ChildPath "default"
if (-not (Test-Path $startMessageFolder)) {
New-Item -Path $startMessageFolder -ItemType Directory -Force | Out-Null
Write-CCLSHost "#Created start-message directory" -Log -NoConsole
}
if (-not (Test-Path $defaultMessageFolder)) {
New-Item -Path $defaultMessageFolder -ItemType Directory -Force | Out-Null
Write-CCLSHost "#Created start-message/default directory" -Log -NoConsole
}
}
# Display startup message if enabled
function Show-StartupMessage {
$startMessageFolder = Join-Path -Path $settingsFolder -ChildPath "start-message"
$defaultMessageFolder = Join-Path -Path $startMessageFolder -ChildPath "default"
$enabledMessageFile = Join-Path -Path $defaultMessageFolder -ChildPath "template.json"
if (Test-Path $enabledMessageFile) {
try {
$templateContent = Get-Content -Path $enabledMessageFile -Raw | ConvertFrom-Json
Invoke-StartupMessageTemplate -Template $templateContent
}
catch {
Write-CCLSHost "#Error loading startup message template: $($_.Exception.Message)" -Log -NoConsole
}
}
}
# Execute startup message template
function Invoke-StartupMessageTemplate {
param (
[object]$Template
)
if ($Template.template) {
# Check if this is a dynamic template that needs data processing
if ($Template.template.type) {
switch ($Template.template.type) {
"installed_games" {
Show-InstalledGamesStartupMessage -Template $Template
return
}
"library_stats" {
Show-LibraryStatsStartupMessage -Template $Template
return
}
}
}
# Handle regular static templates
if ($Template.template.messages) {
foreach ($message in $Template.template.messages) {
$text = if ($message.text) { $message.text } else { "" }
$color = if ($message.color) { $message.color } else { "White" }
$newline = if ($message.newline -eq $false) { $false } else { $true }
try {
# Validate color name exists
$validColors = @("Black", "DarkBlue", "DarkGreen", "DarkCyan", "DarkRed", "DarkMagenta", "DarkYellow", "Gray", "DarkGray", "Blue", "Green", "Cyan", "Red", "Magenta", "Yellow", "White")
if ($validColors -contains $color) {
$consoleColor = [System.ConsoleColor]::$color
} else {
$consoleColor = [System.ConsoleColor]::White
}
if ($newline) {
Write-Host $text -ForegroundColor $consoleColor
# Also log it
Write-CCLSHost "#StartupMsg: $text" -Log -NoConsole
} else {
Write-Host $text -ForegroundColor $consoleColor -NoNewline
# Also log it
Write-CCLSHost "#StartupMsg: $text" -Log -NoConsole
}
}
catch {
# Fallback to simple Write-Host if there are any issues
if ($newline) {
Write-Host $text -ForegroundColor White
} else {
Write-Host $text -ForegroundColor White -NoNewline
}
}
}
}
}
}
function Show-InstalledGamesStartupMessage {
param (
[object]$Template
)
# Show header from template
if ($Template.template.messages) {
foreach ($message in $Template.template.messages) {
$text = if ($message.text) { $message.text } else { "" }
$color = if ($message.color) { $message.color } else { "White" }
Write-Host $text -ForegroundColor $color
Write-CCLSHost "#StartupMsg: $text" -Log -NoConsole
}
}
# Ensure cache is initialized
if (-not $script:libraryCache) {
Initialize-LibraryCache
}
try {
# Check if we have games in cache
if (-not $script:libraryCache.games -or $script:libraryCache.totalGames -eq 0) {
Write-Host "No games are currently installed." -ForegroundColor Yellow
Write-CCLSHost "#StartupMsg: No games are currently installed." -Log -NoConsole
Write-Host "" -ForegroundColor White
return
}
# Process games from cache
$installedGames = @()
foreach ($cachedGame in $script:libraryCache.games) {
$gameEntry = @{
Name = $cachedGame.gameName
Id = $cachedGame.gameId
LocalVersion = $cachedGame.localVersion
LocalSize = $cachedGame.localSizeFormatted
HasMetadata = $cachedGame.hasMetadata
IsOutdated = $false
}
$installedGames += $gameEntry
}
# Sort games: metadata first, then by name
if ($installedGames.Count -eq 1) {
$sortedGames = $installedGames
} else {
$sortedGames = $installedGames | Sort-Object @{Expression={-($_.HasMetadata -as [int])}}, Name
}
if ($sortedGames -isnot [array]) {
$sortedGames = @($sortedGames)
}
# Calculate column widths
$maxNameLength = 30
$maxIdLength = 15
$maxSizeLength = 12
$maxVersionLength = 15
if ($sortedGames.Count -gt 0) {
foreach ($game in $sortedGames) {
if ($game.Name.Length -gt $maxNameLength) { $maxNameLength = $game.Name.Length }
if (("ID: " + $game.Id).Length -gt $maxIdLength) { $maxIdLength = ("ID: " + $game.Id).Length }
if (("Size: " + $game.LocalSize).Length -gt $maxSizeLength) { $maxSizeLength = ("Size: " + $game.LocalSize).Length }
if (("Ver: " + $game.LocalVersion).Length -gt $maxVersionLength) { $maxVersionLength = ("Ver: " + $game.LocalVersion).Length }
}
}
$nameWidth = $maxNameLength + 2
$idWidth = $maxIdLength + 2
$sizeWidth = $maxSizeLength + 2
$versionWidth = $maxVersionLength + 2
# Display header
$headerLine = "Game Name".PadRight($nameWidth) + "ID".PadRight($idWidth) + "Size".PadRight($sizeWidth) + "Version"
Write-Host $headerLine -ForegroundColor Cyan
Write-CCLSHost "#StartupMsg: $headerLine" -Log -NoConsole
$separatorLine = ("-" * ($nameWidth - 1)) + " " + ("-" * ($idWidth - 1)) + " " + ("-" * ($sizeWidth - 1)) + " " + ("-" * ($versionWidth - 1))
Write-Host $separatorLine -ForegroundColor Cyan
Write-CCLSHost "#StartupMsg: $separatorLine" -Log -NoConsole
# Display games (limit to first 10 for startup)
$displayCount = [Math]::Min(10, $sortedGames.Count)
for ($i = 0; $i -lt $displayCount; $i++) {
$game = $sortedGames[$i]
$gameColor = if ($game.HasMetadata) { "White" } else { "Gray" }
$gameName = $game.Name.PadRight($nameWidth)
$gameId = ("ID: " + $game.Id).PadRight($idWidth)
$gameSize = ("Size: " + $game.LocalSize).PadRight($sizeWidth)
$gameVersion = "Ver: " + $game.LocalVersion
$gameLine = "$gameName$gameId$gameSize$gameVersion"
Write-Host $gameLine -ForegroundColor $gameColor
Write-CCLSHost "#StartupMsg: $gameLine" -Log -NoConsole
}
# Show summary
if ($sortedGames.Count -gt 10) {
$remainingCount = $sortedGames.Count - 10
$summaryLine = "`n... and $remainingCount more games. Use 'browse' to see all installed games."
Write-Host $summaryLine -ForegroundColor Yellow
Write-CCLSHost "#StartupMsg: $summaryLine" -Log -NoConsole
} else {
$summaryLine = "`nTotal: $($sortedGames.Count) games installed. Use 'browse' for detailed management."
Write-Host $summaryLine -ForegroundColor Green
Write-CCLSHost "#StartupMsg: $summaryLine" -Log -NoConsole
}
Write-Host "" -ForegroundColor White
} catch {
Write-Host "Error loading installed games information from cache." -ForegroundColor Red
Write-CCLSHost "#StartupMsg: Error loading installed games from cache: $($_.Exception.Message)" -Log -NoConsole
Write-Host "" -ForegroundColor White
}
}
function Show-LibraryStatsStartupMessage {
param (
[object]$Template
)
# Show header from template
if ($Template.template.messages) {
foreach ($message in $Template.template.messages) {
$text = if ($message.text) { $message.text } else { "" }
$color = if ($message.color) { $message.color } else { "White" }
Write-Host $text -ForegroundColor $color
Write-CCLSHost "#StartupMsg: $text" -Log -NoConsole
}
}
# Ensure cache is initialized
if (-not $script:libraryCache) {
Initialize-LibraryCache
}
# Get local statistics from cache
$installedCount = 0
$totalLocalSize = 0
$formattedLocalSize = "0 B"
if ($script:libraryCache) {
$installedCount = $script:libraryCache.totalGames
$totalLocalSize = $script:libraryCache.totalSize
$formattedLocalSize = $script:libraryCache.totalSizeFormatted
}
# Get online statistics (only if logged in and not in DevMode)
$onlineGamesCount = "N/A"
$onlineBundlesCount = "N/A"
$totalOnlineCount = "N/A"
$settings = Initialize-Settings
if ($null -ne $script:cachedCredentials -and
$null -ne $script:cachedCredentials.Username -and
$null -ne $script:cachedCredentials.Password -and
-not $settings.DevMode) {
try {
$params = @{
Uri = "$cliApiUrl/list.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
}
}
$response = Invoke-RestMethod @params -TimeoutSec 5
if ($response.success) {
$onlineGamesCount = $response.count
$onlineBundlesCount = $response.bundle_count
$totalOnlineCount = $onlineGamesCount + $onlineBundlesCount
}
} catch {
Write-CCLSHost "#StartupMsg: Could not fetch online statistics: $($_.Exception.Message)" -Log -NoConsole
# Keep N/A values
}
}
# Display statistics
Write-Host "" -ForegroundColor White
# Local Statistics
$localStatsLine = "Installed Games: $installedCount games ($formattedLocalSize total)"
Write-Host $localStatsLine -ForegroundColor Green
Write-CCLSHost "#StartupMsg: $localStatsLine" -Log -NoConsole
# Online Statistics
if ($totalOnlineCount -ne "N/A") {
$onlineStatsLine = "Available Online: $onlineGamesCount games, $onlineBundlesCount bundles ($totalOnlineCount total)"
Write-Host $onlineStatsLine -ForegroundColor Cyan
Write-CCLSHost "#StartupMsg: $onlineStatsLine" -Log -NoConsole
# Progress indicator
if ($installedCount -gt 0 -and $onlineGamesCount -ne "N/A") {
$percentage = [Math]::Round(($installedCount / $onlineGamesCount) * 100, 1)
$progressLine = "Library Progress: $percentage% of available games installed"
Write-Host $progressLine -ForegroundColor Yellow
Write-CCLSHost "#StartupMsg: $progressLine" -Log -NoConsole
}
} else {
$offlineNoteLine = "Online Statistics: Not available (offline mode or connection issue)"
Write-Host $offlineNoteLine -ForegroundColor Gray
Write-CCLSHost "#StartupMsg: $offlineNoteLine" -Log -NoConsole
}
Write-Host "" -ForegroundColor White
# Quick tips
if ($installedCount -eq 0) {
$tipLine = "Quick tip: Use 'search library' to browse available games"
Write-Host $tipLine -ForegroundColor Yellow
Write-CCLSHost "#StartupMsg: $tipLine" -Log -NoConsole
} else {
$tipLine = "Quick tip: Use 'browse' to manage your games or 'search library' to find new ones"
Write-Host $tipLine -ForegroundColor Yellow
Write-CCLSHost "#StartupMsg: $tipLine" -Log -NoConsole
}
Write-Host "" -ForegroundColor White
}
# Main startup message management function
function Manage-StartupMessage {
param (
[string]$Action,
[string]$TemplateName = ""
)
# Initialize directories if they don't exist
Initialize-StartupMessageDirectories
$startMessageFolder = Join-Path -Path $settingsFolder -ChildPath "start-message"
$defaultMessageFolder = Join-Path -Path $startMessageFolder -ChildPath "default"
$templatesUrl = "$baseUrl/CLI/api/main/startup_message/templates"
$listApiUrl = "$baseUrl/CLI/api/main/startup_message/list.php"
switch ($Action.ToLower()) {
"list" {
try {
Write-CCLSHost "Fetching available startup message templates..." -ForegroundColor Cyan -Log
$params = @{
Uri = $listApiUrl
Method = "GET"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
}
$response = Invoke-RestMethod @params
if ($response.success) {
Write-CCLSHost "`n==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "Available Startup Message Templates - $($response.count) found" -ForegroundColor Green -Log
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
if ($response.count -eq 0) {
Write-CCLSHost "No templates available on the server." -ForegroundColor Yellow -Log
} else {
# Calculate column widths safely
$maxNameLength = 20 # Default minimum
if ($response.templates.Count -gt 0) {
try {
$nameLengths = @()
foreach ($template in $response.templates) {
$nameLengths += ("'$($template.name)'").Length
}
if ($nameLengths.Count -gt 0) {
$maxCalc = ($nameLengths | Measure-Object -Maximum).Maximum
if ($maxCalc -gt $maxNameLength) {
$maxNameLength = $maxCalc
}
}
}
catch {
# Use default if calculation fails
$maxNameLength = 20
}
}
$nameWidth = $maxNameLength + 2
# Display header
$headerLine = "Template Name".PadRight($nameWidth) + "Author".PadRight(15) + "Version".PadRight(10) + "Description"
Write-CCLSHost $headerLine -ForegroundColor Cyan -Log
$separatorLine = ("-" * ($nameWidth - 1)) + " " + ("-" * 14) + " " + ("-" * 9) + " " + ("-" * 20)
Write-CCLSHost $separatorLine -ForegroundColor Cyan -Log
# Display each template
foreach ($template in $response.templates) {
$templateName = ("'$($template.name)'").PadRight($nameWidth)
$author = $template.author.PadRight(15)
$version = $template.version.PadRight(10)
$description = $template.description
$templateLine = "$templateName$author$version$description"
Write-CCLSHost $templateLine -ForegroundColor White -Log
}
Write-CCLSHost "`nUse 'sys start-message get [template-name]' to download a template" -ForegroundColor Yellow -Log
Write-CCLSHost "Note: Use the template filename (e.g., 'gradient', 'minimal') not the display name" -ForegroundColor Gray -Log
}
} else {
Write-CCLSHost "Error fetching templates: $($response.error)" -ForegroundColor Red -Log
}
}
catch {
Write-CCLSHost "Error connecting to template server: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
"get" {
if ([string]::IsNullOrWhiteSpace($TemplateName)) {
Write-CCLSHost "Template name is required. Use: sys start-message get [template-name]" -ForegroundColor Red -Log
return
}
try {
Write-CCLSHost "Downloading template '$TemplateName'..." -ForegroundColor Cyan -Log
$templateUrl = "$templatesUrl/$TemplateName.json"
$localPath = Join-Path -Path $startMessageFolder -ChildPath "$TemplateName.json"
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "CCLS-CLI/2.0")
$webClient.DownloadFile($templateUrl, $localPath)
if (Test-Path $localPath) {
Write-CCLSHost "Template '$TemplateName' downloaded successfully!" -ForegroundColor Green -Log
} else {
Write-CCLSHost "Failed to download template '$TemplateName'" -ForegroundColor Red -Log
}
}
catch {
Write-CCLSHost "Error downloading template: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
"enable" {
if ([string]::IsNullOrWhiteSpace($TemplateName)) {
Write-CCLSHost "Template name is required. Use: sys start-message enable [template-name]" -ForegroundColor Red -Log
return
}
$templatePath = Join-Path -Path $startMessageFolder -ChildPath "$TemplateName.json"
if (-not (Test-Path $templatePath)) {
Write-CCLSHost "Template '$TemplateName' not found. Download it first with: sys start-message get $TemplateName" -ForegroundColor Red -Log
return
}
try {
# Move current default template back to main folder if it exists
$currentDefaultPath = Join-Path -Path $defaultMessageFolder -ChildPath "template.json"
$currentDefaultInfoPath = Join-Path -Path $defaultMessageFolder -ChildPath "info.txt"
if (Test-Path $currentDefaultPath) {
if (Test-Path $currentDefaultInfoPath) {
$currentTemplateName = (Get-Content -Path $currentDefaultInfoPath -Raw).Trim()
if (-not [string]::IsNullOrWhiteSpace($currentTemplateName)) {
$backupPath = Join-Path -Path $startMessageFolder -ChildPath "$currentTemplateName.json"
if (Test-Path $backupPath) {
Remove-Item -Path $backupPath -Force
}
Move-Item -Path $currentDefaultPath -Destination $backupPath -Force
} else {
Remove-Item -Path $currentDefaultPath -Force
}
Remove-Item -Path $currentDefaultInfoPath -Force
} else {
Remove-Item -Path $currentDefaultPath -Force
}
}
# Copy new template to default folder
$newDefaultPath = Join-Path -Path $defaultMessageFolder -ChildPath "template.json"
$newInfoPath = Join-Path -Path $defaultMessageFolder -ChildPath "info.txt"
Copy-Item -Path $templatePath -Destination $newDefaultPath -Force
Set-Content -Path $newInfoPath -Value $TemplateName.Trim() -NoNewline -Force
Write-CCLSHost "Template '$TemplateName' enabled successfully!" -ForegroundColor Green -Log
Write-CCLSHost "The startup message will appear on your next login." -ForegroundColor Cyan -Log
}
catch {
Write-CCLSHost "Error enabling template: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
"get,enable" {
if ([string]::IsNullOrWhiteSpace($TemplateName)) {
Write-CCLSHost "Template name is required. Use: sys start-message get,enable [template-name]" -ForegroundColor Red -Log
return
}
# Download first
Manage-StartupMessage -Action "get" -TemplateName $TemplateName
# Then enable if download was successful
$templatePath = Join-Path -Path $startMessageFolder -ChildPath "$TemplateName.json"
if (Test-Path $templatePath) {
Manage-StartupMessage -Action "enable" -TemplateName $TemplateName
}
}
"try" {
if ([string]::IsNullOrWhiteSpace($TemplateName)) {
Write-CCLSHost "Template name is required. Use: sys start-message try [template-name]" -ForegroundColor Red -Log
return
}
$templatePath = Join-Path -Path $startMessageFolder -ChildPath "$TemplateName.json"
if (-not (Test-Path $templatePath)) {
Write-CCLSHost "Template '$TemplateName' not found. Download it first with: sys start-message get $TemplateName" -ForegroundColor Red -Log
return
}
try {
Write-CCLSHost "`nPreviewing template '$TemplateName':" -ForegroundColor Yellow -Log
$separatorLine = "=" * 50
Write-CCLSHost $separatorLine -ForegroundColor DarkGray -Log
$templateContent = Get-Content -Path $templatePath -Raw | ConvertFrom-Json
Invoke-StartupMessageTemplate -Template $templateContent
Write-CCLSHost $separatorLine -ForegroundColor DarkGray -Log
Write-CCLSHost "Preview complete. Use 'sys start-message enable $TemplateName' to set as active." -ForegroundColor Yellow -Log
}
catch {
Write-CCLSHost "Error previewing template: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
"view" {
$currentDefaultPath = Join-Path -Path $defaultMessageFolder -ChildPath "template.json"
$currentDefaultInfoPath = Join-Path -Path $defaultMessageFolder -ChildPath "info.txt"
if (Test-Path $currentDefaultPath) {
try {
$templateName = if (Test-Path $currentDefaultInfoPath) {
(Get-Content -Path $currentDefaultInfoPath -Raw).Trim()
} else {
"Unknown"
}
Write-CCLSHost "`nCurrent startup message (Template: $templateName):" -ForegroundColor Yellow -Log
$separatorLine = "=" * 50
Write-CCLSHost $separatorLine -ForegroundColor DarkGray -Log
$templateContent = Get-Content -Path $currentDefaultPath -Raw | ConvertFrom-Json
Invoke-StartupMessageTemplate -Template $templateContent
Write-CCLSHost $separatorLine -ForegroundColor DarkGray -Log
}
catch {
Write-CCLSHost "Error displaying current template: $($_.Exception.Message)" -ForegroundColor Red -Log
}
} else {
Write-CCLSHost "No startup message is currently enabled." -ForegroundColor Yellow -Log
Write-CCLSHost "Use 'sys start-message list' to see available templates." -ForegroundColor Cyan -Log
}
}
"default" {
$currentDefaultPath = Join-Path -Path $defaultMessageFolder -ChildPath "template.json"
$currentDefaultInfoPath = Join-Path -Path $defaultMessageFolder -ChildPath "info.txt"
if (Test-Path $currentDefaultPath) {
try {
# Move current template back to main folder
if (Test-Path $currentDefaultInfoPath) {
$currentTemplateName = (Get-Content -Path $currentDefaultInfoPath -Raw).Trim()
if (-not [string]::IsNullOrWhiteSpace($currentTemplateName)) {
$backupPath = Join-Path -Path $startMessageFolder -ChildPath "$currentTemplateName.json"
if (Test-Path $backupPath) {
Remove-Item -Path $backupPath -Force
}
Move-Item -Path $currentDefaultPath -Destination $backupPath -Force
} else {
Remove-Item -Path $currentDefaultPath -Force
}
Remove-Item -Path $currentDefaultInfoPath -Force
} else {
Remove-Item -Path $currentDefaultPath -Force
}
Write-CCLSHost "Startup message disabled. Default system messages will be shown." -ForegroundColor Green -Log
}
catch {
Write-CCLSHost "Error disabling startup message: $($_.Exception.Message)" -ForegroundColor Red -Log
}
} else {
Write-CCLSHost "No startup message is currently enabled." -ForegroundColor Yellow -Log
}
}
"del" {
if ([string]::IsNullOrWhiteSpace($TemplateName)) {
Write-CCLSHost "Template name is required. Use: sys start-message del [template-name]" -ForegroundColor Red -Log
return
}
$templatePath = Join-Path -Path $startMessageFolder -ChildPath "$TemplateName.json"
if (Test-Path $templatePath) {
try {
# Check if this is the currently enabled template
$currentDefaultInfoPath = Join-Path -Path $defaultMessageFolder -ChildPath "info.txt"
$currentlyEnabled = $false
if (Test-Path $currentDefaultInfoPath) {
$currentTemplateName = (Get-Content -Path $currentDefaultInfoPath -Raw).Trim()
if ($currentTemplateName -eq $TemplateName) {
$currentlyEnabled = $true
}
}
if ($currentlyEnabled) {
Write-CCLSHost "Cannot delete '$TemplateName' - it is currently enabled." -ForegroundColor Red -Log
Write-CCLSHost "Use 'sys start-message default' to disable it first, then try deleting again." -ForegroundColor Yellow -Log
return
}
Remove-Item -Path $templatePath -Force
Write-CCLSHost "Template '$TemplateName' deleted successfully!" -ForegroundColor Green -Log
}
catch {
Write-CCLSHost "Error deleting template: $($_.Exception.Message)" -ForegroundColor Red -Log
}
} else {
Write-CCLSHost "Template '$TemplateName' not found." -ForegroundColor Red -Log
}
}
"wipe" {
try {
Write-CCLSHost "WARNING: This will delete ALL downloaded startup message templates!" -ForegroundColor Red -Log
Write-CCLSHost "Are you sure you want to continue? (Y/N)" -ForegroundColor Yellow -Log
$confirmation = Read-Host
Write-CCLSHost "$confirmation" -NoConsole -Log
if ($confirmation.ToLower() -eq "y") {
# Disable current template first
Manage-StartupMessage -Action "default"
# Remove all template files
$templateFiles = Get-ChildItem -Path $startMessageFolder -Filter "*.json" -ErrorAction SilentlyContinue
foreach ($file in $templateFiles) {
Remove-Item -Path $file.FullName -Force
}
Write-CCLSHost "All startup message templates have been deleted!" -ForegroundColor Green -Log
} else {
Write-CCLSHost "Operation cancelled." -ForegroundColor Yellow -Log
}
}
catch {
Write-CCLSHost "Error wiping templates: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
default {
Write-CCLSHost "Unknown action. Available actions:" -ForegroundColor Red -Log
Write-CCLSHost " list - List available templates" -ForegroundColor Cyan -Log
Write-CCLSHost " get [name] - Download a template" -ForegroundColor Cyan -Log
Write-CCLSHost " enable [name] - Enable a downloaded template" -ForegroundColor Cyan -Log
Write-CCLSHost " get,enable [name] - Download and enable a template" -ForegroundColor Cyan -Log
Write-CCLSHost " try [name] - Preview a template" -ForegroundColor Cyan -Log
Write-CCLSHost " view - View current startup message" -ForegroundColor Cyan -Log
Write-CCLSHost " default - Disable startup message" -ForegroundColor Cyan -Log
Write-CCLSHost " del [name] - Delete a template" -ForegroundColor Cyan -Log
Write-CCLSHost " wipe - Delete all templates" -ForegroundColor Cyan -Log
}
}
}
# Updated Install-Utility function
function Install-Utility {
param (
[string]$UtilityName
)
# Handle different utilities
switch ($UtilityName.ToLower()) {
"7zip" {
Install7Zip
}
"requests" {
InstallPythonRequests
}
"python" {
Install-Python
}
default {
Write-CCLSHost "Unsupported utility: $UtilityName" -ForegroundColor Red -Log
Write-CCLSHost "Currently supported utilities: 7zip, requests, python" -ForegroundColor Green -Log
}
}
}
# Function to install 7-Zip (internal function for Install-Utility)
function Install7Zip {
# Determine the current script directory
$scriptLocation = if ($PSScriptRoot) {
# If running from a script, use its location
$PSScriptRoot
} else {
# If running in console, use current directory
(Get-Location).Path
}
# Target directory is the script location
$targetDir = $scriptLocation
# URLs for download - Updated for v2.0 API structure
$zipUrl = "$baseUrl/CLI/api/2.0/utilities/7zip/7zip.zip"
$zipPath = Join-Path -Path $targetDir -ChildPath "7zip.zip"
Write-CCLSHost "Starting 7-Zip installation..." -ForegroundColor Cyan -Log
try {
# Download the zip file
Write-CCLSHost "Downloading 7-Zip package from $zipUrl" -ForegroundColor Yellow -Log
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "CCLS-CLI/2.0")
$webClient.DownloadFile($zipUrl, $zipPath)
if (-not (Test-Path $zipPath)) {
Write-CCLSHost "Failed to download 7-Zip package." -ForegroundColor Red -Log
return
}
Write-CCLSHost "Download completed successfully." -ForegroundColor Green -Log
# Create temporary extraction directory
$tempExtractPath = Join-Path -Path $targetDir -ChildPath "7zip_temp"
if (Test-Path $tempExtractPath) {
Remove-Item -Path $tempExtractPath -Recurse -Force
}
New-Item -ItemType Directory -Path $tempExtractPath -Force | Out-Null
# Final 7zip directory
$finalPath = Join-Path -Path $targetDir -ChildPath "7zip"
if (Test-Path $finalPath) {
Remove-Item -Path $finalPath -Recurse -Force
}
# Extract the zip file to temp location
Write-CCLSHost "Extracting 7-Zip package..." -ForegroundColor Yellow -Log
$extractionSuccess = $false
try {
# Try to use built-in Expand-Archive
Expand-Archive -Path $zipPath -DestinationPath $tempExtractPath -Force
$extractionSuccess = $true
}
catch {
Write-CCLSHost "#Built-in extraction failed, trying alternate method: $($_.Exception.Message)" -ForegroundColor Yellow -Log -NoConsole
try {
# Alternative extraction method using .NET
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $tempExtractPath)
$extractionSuccess = $true
}
catch {
Write-CCLSHost "Extraction failed: $($_.Exception.Message)" -ForegroundColor Red -Log
$extractionSuccess = $false
}
}
# Fix nested folder issue if extraction was successful
if ($extractionSuccess) {
# Check if we have a nested 7zip folder
$nestedFolder = Join-Path -Path $tempExtractPath -ChildPath "7zip"
if (Test-Path $nestedFolder) {
# Move the contents from nested folder to final location
Write-CCLSHost "#Fixing folder structure..." -Log -NoConsole
Move-Item -Path $nestedFolder -Destination $targetDir -Force
$extractionSuccess = $true
} else {
# If no nested folder, just rename temp folder to final name
Rename-Item -Path $tempExtractPath -NewName "7zip"
$extractionSuccess = $true
}
}
# Clean up the downloaded zip file
if (Test-Path $zipPath) {
Remove-Item -Path $zipPath -Force
Write-CCLSHost "#Removed temporary download file." -ForegroundColor Gray -Log -NoConsole
}
# Clean up temp folder if it still exists
if (Test-Path $tempExtractPath) {
Remove-Item -Path $tempExtractPath -Recurse -Force
Write-CCLSHost "#Removed temporary extraction folder." -ForegroundColor Gray -Log -NoConsole
}
# Verify installation
$exePath = Join-Path -Path $finalPath -ChildPath "7z.exe"
if ($extractionSuccess -and (Test-Path $exePath)) {
Write-CCLSHost "7-Zip has been successfully installed to $finalPath" -ForegroundColor Green -Log
Write-CCLSHost "You can now use 7-Zip for file extraction." -ForegroundColor Green -Log
}
else {
Write-CCLSHost "7-Zip installation was not completed successfully." -ForegroundColor Red -Log
Write-CCLSHost "Please try again or manually install 7-Zip." -ForegroundColor Yellow -Log
}
}
catch {
Write-CCLSHost "Error during installation: $($_.Exception.Message)" -ForegroundColor Red -Log
Write-CCLSHost "7-Zip installation failed." -ForegroundColor Red -Log
}
}
# Function to install Python requests library (internal function for Install-Utility)
function InstallPythonRequests {
Write-CCLSHost "Checking for Python installation..." -ForegroundColor Cyan -Log
# First check if Python is installed
try {
$pythonResult = python --version 2>&1
if ($pythonResult -match "Python (\d+\.\d+\.\d+)") {
$pythonVersion = $matches[1]
Write-CCLSHost "Python detected (v$pythonVersion)" -ForegroundColor Green -Log
} else {
Write-CCLSHost "Python not detected" -ForegroundColor Red -Log
Write-CCLSHost "Please install Python first before installing the requests library." -ForegroundColor Yellow -Log
Write-CCLSHost "Download Python with 'install python' command or" -ForegroundColor Yellow -Log
Write-CCLSHost "Download Python from: https://www.python.org/downloads/ manually" -ForegroundColor Yellow -Log
return
}
} catch {
Write-CCLSHost "Python is not installed or not in the system PATH." -ForegroundColor Red -Log
Write-CCLSHost "Please install Python first before installing the requests library." -ForegroundColor Yellow -Log
Write-CCLSHost "Download Python with 'install python' command or" -ForegroundColor Yellow -Log
Write-CCLSHost "Download Python from: https://www.python.org/downloads/ manually" -ForegroundColor Yellow -Log
return
}
# Check if requests is already installed
Write-CCLSHost "Checking if requests library is already installed..." -ForegroundColor Cyan -Log
try {
$requestsCheck = python -c "import requests; print('Installed (v{0})'.format(requests.__version__))" 2>&1
if ($requestsCheck -match "Installed \(v([\d\.]+)\)") {
$requestsVersion = $matches[1]
Write-CCLSHost "The requests library is already installed (v$requestsVersion)" -ForegroundColor Green -Log
# Ask if user wants to upgrade
Write-CCLSHost "Do you want to upgrade to the latest version? (Y/N)" -ForegroundColor Yellow -Log
$upgradeConfirmation = Read-Host
Write-CCLSHost "$upgradeConfirmation" -NoConsole -Log
if ($upgradeConfirmation.ToLower() -ne "y") {
Write-CCLSHost "Installation skipped." -ForegroundColor Yellow -Log
return
}
# If yes, set upgrade flag
$upgradeFlag = "--upgrade"
} else {
# Not installed
$upgradeFlag = ""
}
} catch {
# Not installed or error checking
Write-CCLSHost "The requests library is not installed." -ForegroundColor Yellow -Log
$upgradeFlag = ""
}
Write-CCLSHost "Installing Python requests library..." -ForegroundColor Cyan -Log
try {
# Use Start-Process with -Wait to run pip in the background
$pipCommand = "pip"
$pipArgs = "install $upgradeFlag requests"
# Temporary files for output and error
$tempOutputFile = [System.IO.Path]::GetTempFileName()
$tempErrorFile = [System.IO.Path]::GetTempFileName()
Write-CCLSHost "#Running: $pipCommand $pipArgs" -Log -NoConsole
# Show progress indicator
Write-CCLSHost "Installing..." -NoNewline -ForegroundColor Yellow -Log
# Run pip in background and redirect output to temp files
$process = Start-Process -FilePath $pipCommand -ArgumentList $pipArgs -NoNewWindow -PassThru -RedirectStandardOutput $tempOutputFile -RedirectStandardError $tempErrorFile -Wait
# Read output and error from temp files
$output = Get-Content -Path $tempOutputFile -Raw
$error = Get-Content -Path $tempErrorFile -Raw
# Clean up temp files
Remove-Item -Path $tempOutputFile -Force -ErrorAction SilentlyContinue
Remove-Item -Path $tempErrorFile -Force -ErrorAction SilentlyContinue
Write-CCLSHost "" -Log # Newline after progress indicator
# Verify installation regardless of process exit code
# This is more reliable than checking process exit code
$installSuccess = $false
try {
$verifyResult = python -c "import requests; print('Installed (v{0})'.format(requests.__version__))" 2>&1
if ($verifyResult -match "Installed \(v([\d\.]+)\)") {
$installSuccess = $true
$newVersion = $matches[1]
}
} catch {
$installSuccess = $false
}
if ($installSuccess) {
# Check if output contains "Successfully installed" as additional confirmation
if (-not [string]::IsNullOrWhiteSpace($output) -and
($output -match "Successfully installed" -or $output -match "Requirement already satisfied")) {
Write-CCLSHost "Python requests library v$newVersion installed successfully!" -ForegroundColor Green -Log
# Show summary of what happened
if ($output -match "Requirement already satisfied") {
Write-CCLSHost "All requirements were already satisfied." -ForegroundColor Green -Log
} else {
Write-CCLSHost "Installation completed successfully." -ForegroundColor Green -Log
}
} else {
Write-CCLSHost "Python requests library appears to be installed (v$newVersion)" -ForegroundColor Green -Log
Write-CCLSHost "But the installation process reported unusual output." -ForegroundColor Yellow -Log
}
} else {
Write-CCLSHost "Failed to install Python requests library." -ForegroundColor Red -Log
if (-not [string]::IsNullOrWhiteSpace($error)) {
Write-CCLSHost "Error details:" -ForegroundColor Red -Log
Write-CCLSHost $error -ForegroundColor Red -Log
}
if (-not [string]::IsNullOrWhiteSpace($output)) {
Write-CCLSHost "Output details:" -ForegroundColor Yellow -Log
Write-CCLSHost $output -ForegroundColor Yellow -Log
}
}
} catch {
Write-CCLSHost "Error during installation: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
# Function to install Python
function Install-Python {
Write-CCLSHost "Starting Python installation process..." -ForegroundColor Cyan -Log
# Create a temporary directory for the download
$tempDir = [System.IO.Path]::GetTempPath()
$tempDownloadPath = Join-Path -Path $tempDir -ChildPath "python_installer.exe"
# Determine if system is 64-bit or 32-bit
$is64Bit = [Environment]::Is64BitOperatingSystem
# Get Python version info from our server
try {
Write-CCLSHost "Retrieving Python version information..." -ForegroundColor Yellow -Log
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "CCLS-CLI/2.0")
# Use the CCLS API endpoint to get Python version info - Updated for v2.0 API structure
$pythonInfoJson = $webClient.DownloadString("$cliApiUrl/install_python.php")
$pythonInfo = $pythonInfoJson | ConvertFrom-Json
# Use the appropriate URL based on system architecture
if ($is64Bit) {
$pythonUrl = $pythonInfo.windows_64bit_url
Write-CCLSHost "Using 64-bit installer for Python $($pythonInfo.version)" -ForegroundColor Green -Log
} else {
$pythonUrl = $pythonInfo.windows_32bit_url
Write-CCLSHost "Using 32-bit installer for Python $($pythonInfo.version)" -ForegroundColor Green -Log
}
} catch {
Write-CCLSHost "Error retrieving Python information from server: $($_.Exception.Message)" -ForegroundColor Red -Log
Write-CCLSHost "Installation cannot proceed. Please try again later." -ForegroundColor Red -Log
return
}
# Download the Python installer
try {
Write-CCLSHost "Starting download from $pythonUrl" -ForegroundColor Yellow -Log
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "CCLS-CLI/2.0")
Write-CCLSHost "Downloading Python installer to $tempDownloadPath..." -ForegroundColor Cyan -Log
Write-CCLSHost "Download in progress, this may take a few minutes depending on your connection speed..." -ForegroundColor Yellow -Log
# Use synchronous download with simple progress indication
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$webClient.DownloadFile($pythonUrl, $tempDownloadPath)
$sw.Stop()
if (Test-Path $tempDownloadPath) {
$fileSize = (Get-Item $tempDownloadPath).Length / 1MB
Write-CCLSHost "Download completed successfully! Downloaded $([Math]::Round($fileSize, 2)) MB in $([Math]::Round($sw.Elapsed.TotalSeconds, 1)) seconds." -ForegroundColor Green -Log
} else {
Write-CCLSHost "Download failed - file not found at expected location." -ForegroundColor Red -Log
return
}
} catch {
Write-CCLSHost "Error downloading Python installer: $($_.Exception.Message)" -ForegroundColor Red -Log
return
}
# Launch the installer
try {
Write-CCLSHost "Starting Python installer..." -ForegroundColor Green -Log
Write-CCLSHost "Please follow the on-screen instructions to complete the installation." -ForegroundColor Yellow -Log
Write-CCLSHost "Recommended options:" -ForegroundColor Yellow -Log
Write-CCLSHost " - Check 'Add Python to PATH' (IMPORTANT)" -ForegroundColor Yellow -Log
Write-CCLSHost " - Ensure 'pip' is selected in the optional features (IMPORTANT)" -ForegroundColor Yellow -Log
# Start the installer
$process = Start-Process -FilePath $tempDownloadPath -Wait -PassThru
# Handle different exit codes
if ($process.ExitCode -eq 0) {
Write-CCLSHost "Python installation process completed successfully." -ForegroundColor Green -Log
} elseif ($process.ExitCode -eq 1602) {
Write-CCLSHost "Installation was cancelled by the user." -ForegroundColor Yellow -Log
} else {
Write-CCLSHost "Python installer exited with code: $($process.ExitCode)" -ForegroundColor Yellow -Log
}
Write-CCLSHost "For the script to recognize that Python was installed, you will need to close the script and run it again." -ForegroundColor Red -Log
Write-CCLSHost "Then run the 'check' command to confirm the Python installation was successful." -ForegroundColor Red -Log
} catch {
Write-CCLSHost "Error launching Python installer: $($_.Exception.Message)" -ForegroundColor Red -Log
}
# Clean up the downloaded file
try {
if (Test-Path $tempDownloadPath) {
Remove-Item -Path $tempDownloadPath -Force
Write-CCLSHost "Cleaned up temporary installer file." -ForegroundColor Gray -Log -NoConsole
}
} catch {
Write-CCLSHost "Note: Could not remove temporary installer file: $($_.Exception.Message)" -ForegroundColor Yellow -Log -NoConsole
}
}
# Enhanced Search-Game function with game name search capability (FIXED)
function Search-Game($id) {
$settings = Initialize-Settings
if ($settings.DevMode) {
Write-CCLSHost "'search' is disabled while in Developer mode" -ForegroundColor Red -Log
return
}
if (-not (Assert-DependenciesRequired -CommandName "search")) { return }
# Check if this is a game name search request
if ($id -eq "game" -and $args.Count -gt 0) {
# This is actually a game name search, not an ID
$searchTerm = $args -join " "
Write-CCLSHost "#Attempting to search for games matching: '$searchTerm'" -Log -NoConsole
# Perform the game name search
$searchResult = Search-GameByName -SearchTerm $searchTerm
if ($null -eq $searchResult) {
# Search failed or was cancelled, error already displayed
return
}
# Update the ID to the selected one and continue with normal processing
$id = $searchResult
Write-CCLSHost "#Selected game ID: $id, proceeding with search..." -Log -NoConsole
}
# Validate ID format - now supports both cg0000 and cb0000 formats
if ($id -notmatch "^(cg|cb)\d{4}$") {
Write-CCLSHost "Invalid ID format. Please use format 'cg0000' for games or 'cb0000' for bundles." -ForegroundColor Red -Log
return
}
# Check if credentials are cached
if ($null -eq $script:cachedCredentials) {
Write-CCLSHost "Error: Not logged in. Please restart the application and log in." -ForegroundColor Red -Log
return
}
# Determine if this is a game or bundle
$isBundle = $id -match "^cb\d{4}$"
$itemType = if ($isBundle) { "bundle" } else { "game" }
try {
# Set up request parameters with credentials
$params = @{
Uri = "$cliApiUrl/search.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
id = $id
}
}
Write-CCLSHost "#Searching for $itemType information..." -Log
# Fetch data from server
try {
$response = Invoke-RestMethod @params
# Check if the request was successful
if (-not $response.success) {
Write-CCLSHost "Error: $($response.message)" -ForegroundColor Red -Log
return
}
$itemInfo = $response
}
catch {
Write-CCLSHost "Error fetching $itemType information: $($_.Exception.Message)" -ForegroundColor Red -Log
return
}
# Display item information in a formatted way
Write-CCLSHost "`n==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "$($itemType.ToUpper()) Information for $($itemInfo.name) ($($itemInfo.id))" -ForegroundColor Green -Log
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "`nDescription:" -ForegroundColor Cyan -Log
Write-CCLSHost $itemInfo.description -Log
if ($itemInfo.safety_score -or $itemInfo.safety_level) {
Write-CCLSHost "`nSafety:" -ForegroundColor Cyan -Log
if ($itemInfo.safety_score) { Write-CCLSHost "Score: $($itemInfo.safety_score)" -Log }
if ($itemInfo.safety_level) { Write-CCLSHost "Level: $($itemInfo.safety_level)" -Log }
}
Write-CCLSHost "`nDetails:" -ForegroundColor Cyan -Log
# Handle different size properties for game vs bundle
if ($isBundle) {
if ($itemInfo.zipped_size) { Write-CCLSHost "Zipped Size: $($itemInfo.zipped_size)" -Log }
if ($itemInfo.unzipped_size) { Write-CCLSHost "Unzipped Size: $($itemInfo.unzipped_size)" -Log }
if ($itemInfo.games_included) { Write-CCLSHost "Games Included: $($itemInfo.games_included)" -Log }
} else {
if ($itemInfo.size) { Write-CCLSHost "Size: $($itemInfo.size)" -Log }
}
if ($itemInfo.version -and $itemInfo.version -ne "") { Write-CCLSHost "Version: $($itemInfo.version)" -Log }
if (($itemInfo.online -and $itemInfo.online -ne "") -or
($itemInfo.steam -and $itemInfo.steam -ne "") -or
($itemInfo.epic -and $itemInfo.epic -ne "")) {
Write-CCLSHost "`nAvailability:" -ForegroundColor Cyan -Log
if ($itemInfo.online -and $itemInfo.online -ne "") { Write-CCLSHost "Online: $($itemInfo.online)" -Log }
if ($itemInfo.steam -and $itemInfo.steam -ne "") { Write-CCLSHost "Steam: $($itemInfo.steam)" -Log }
if ($itemInfo.epic -and $itemInfo.epic -ne "") { Write-CCLSHost "Epic: $($itemInfo.epic)" -Log }
}
if ($itemInfo.false_av -and $itemInfo.false_av -ne "") {
Write-CCLSHost "`nNote: " -ForegroundColor Yellow -NoNewline -Log
Write-CCLSHost "This $itemType may trigger false antivirus alerts: $($itemInfo.false_av)" -Log
}
# If it's a game, show system requirements
if (-not $isBundle -and $itemInfo.system_requirements) {
Write-CCLSHost "`nSystem Requirements:" -ForegroundColor Cyan -Log
if ($itemInfo.system_requirements.minimum) {
Write-CCLSHost "Minimum:" -ForegroundColor Yellow -Log
foreach ($prop in $itemInfo.system_requirements.minimum.PSObject.Properties) {
Write-CCLSHost " $($prop.Name): $($prop.Value)" -Log
}
}
if ($itemInfo.system_requirements.recommended) {
Write-CCLSHost "`nRecommended:" -ForegroundColor Yellow -Log
foreach ($prop in $itemInfo.system_requirements.recommended.PSObject.Properties) {
Write-CCLSHost " $($prop.Name): $($prop.Value)" -Log
}
}
}
# REMOVED: The "Press any key to return" prompt that was causing the extra keypress requirement
}
catch {
Write-CCLSHost "An error occurred while processing $itemType information: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
# Completely rewritten Search-GameByName function with simplified logic and "get {number}" support
function Search-GameByName {
param (
[string]$SearchTerm
)
# Check if credentials are cached
if ($null -eq $script:cachedCredentials) {
Write-CCLSHost "Error: Not logged in. Please restart the application and log in." -ForegroundColor Red -Log
return $null
}
try {
# Set up request parameters with credentials to get the game library
$params = @{
Uri = "$cliApiUrl/list.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
}
}
Write-CCLSHost "#Fetching game library for search..." -Log -NoConsole
# Fetch library from server
$response = Invoke-RestMethod @params
# Check if the request was successful
if (-not $response.success) {
Write-CCLSHost "Error fetching game library: $($response.message)" -ForegroundColor Red -Log
return $null
}
# Create a simple list to hold all items
$allItems = New-Object System.Collections.ArrayList
# Add games to the list
if ($response.games) {
foreach ($game in $response.games) {
$null = $allItems.Add(@{
Name = $game.name
Id = $game.id
Type = "Game"
})
}
}
# Add bundles to the list
if ($response.bundles) {
foreach ($bundle in $response.bundles) {
$null = $allItems.Add(@{
Name = $bundle.name
Id = $bundle.id
Type = "Bundle"
})
}
}
Write-CCLSHost "#Debug: Total items loaded: $($allItems.Count)" -Log -NoConsole
# Validate search term
$searchTermLower = $SearchTerm.ToLower().Trim()
if ($searchTermLower.Length -lt 2) {
Write-CCLSHost "Search term too short. Please use at least 2 characters." -ForegroundColor Red -Log
return $null
}
# Create results list
$results = New-Object System.Collections.ArrayList
# Search through all items
foreach ($item in $allItems) {
$itemNameLower = $item.Name.ToLower().Trim()
$score = 0
$matchType = ""
# Simple scoring logic
if ($itemNameLower -eq $searchTermLower) {
# Exact match
$score = 1000
$matchType = "Exact Match"
}
elseif ($itemNameLower.StartsWith($searchTermLower)) {
# Starts with search term
$score = 900
$matchType = "Starts With"
}
elseif ($itemNameLower.Contains($searchTermLower)) {
# Contains search term
$score = 800
$matchType = "Contains Term"
}
else {
# Check if all words in search term are present
$searchWords = $searchTermLower -split '\s+' | Where-Object { $_.Length -gt 0 }
if ($searchWords.Count -gt 1) {
$allWordsFound = $true
foreach ($word in $searchWords) {
if (-not $itemNameLower.Contains($word)) {
$allWordsFound = $false
break
}
}
if ($allWordsFound) {
$score = 700
$matchType = "All Words Match"
}
}
}
# Add to results if we have a match
if ($score -gt 0) {
$null = $results.Add(@{
Name = $item.Name
Id = $item.Id
Type = $item.Type
Score = $score
MatchType = $matchType
})
# Debug output for VR HOT specifically
if ($itemNameLower -eq "vr hot") {
Write-CCLSHost "#Debug: Added VR HOT with score $score and match type '$matchType'" -Log -NoConsole
}
}
}
Write-CCLSHost "#Debug: Total matches found: $($results.Count)" -Log -NoConsole
# Check if we have any results
if ($results.Count -eq 0) {
Write-CCLSHost "No games or bundles found matching '$SearchTerm'." -ForegroundColor Red -Log
Write-CCLSHost "Try using different search terms or use 'search library' to see all available items." -ForegroundColor Yellow -Log
return $null
}
# Sort results by score (manual sort to avoid PowerShell pipeline issues)
$sortedResults = New-Object System.Collections.ArrayList
# Convert to array and sort manually
$resultArray = $results.ToArray()
for ($i = 0; $i -lt $resultArray.Length - 1; $i++) {
for ($j = $i + 1; $j -lt $resultArray.Length; $j++) {
if ($resultArray[$j].Score -gt $resultArray[$i].Score) {
$temp = $resultArray[$i]
$resultArray[$i] = $resultArray[$j]
$resultArray[$j] = $temp
}
}
}
# Take only the top 20 results
$maxResults = [Math]::Min(20, $resultArray.Length)
for ($i = 0; $i -lt $maxResults; $i++) {
$null = $sortedResults.Add($resultArray[$i])
}
Write-CCLSHost "#Debug: Sorted results count: $($sortedResults.Count)" -Log -NoConsole
# Display search results
Write-CCLSHost "`n==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "Search Results for '$SearchTerm' ($($sortedResults.Count) found)" -ForegroundColor Green -Log
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
for ($i = 0; $i -lt $sortedResults.Count; $i++) {
$result = $sortedResults[$i]
$displayNumber = $i + 1
# Color code by type
$typeColor = if ($result.Type -eq "Game") { "White" } else { "Cyan" }
Write-CCLSHost "[$displayNumber] " -ForegroundColor Yellow -NoNewline -Log
Write-CCLSHost "$($result.Name) " -ForegroundColor $typeColor -NoNewline -Log
Write-CCLSHost "($($result.Id)) " -ForegroundColor Gray -NoNewline -Log
Write-CCLSHost "[$($result.Type)]" -ForegroundColor Green -Log
}
Write-CCLSHost "`nEnter the number to view info, or 'get [number]' to download (1-$($sortedResults.Count)), or press Enter to cancel:" -ForegroundColor Yellow -Log
# Get user input
while ($true) {
Write-CCLSHost "Selection: " -ForegroundColor Cyan -NoNewline -Log
$userInput = Read-Host
Write-CCLSHost "$userInput" -NoConsole -Log
# Check for empty input (cancel)
if ([string]::IsNullOrWhiteSpace($userInput)) {
Write-CCLSHost "Search cancelled." -ForegroundColor Yellow -Log
return $null
}
# Check if input matches "get [number]" pattern
if ($userInput.Trim() -match "^get\s+(\d+)$") {
$selectedNumber = [int]$matches[1]
# Validate number is in range
if ($selectedNumber -ge 1 -and $selectedNumber -le $sortedResults.Count) {
$selectedItem = $sortedResults[$selectedNumber - 1]
Write-CCLSHost "#Calling Get-Game function to install: $($selectedItem.Name) ($($selectedItem.Id))" -NoConsole -Log
# Call Get-Game function directly with the selected ID
Get-Game -id $selectedItem.Id
return $null # Return null since we're not returning an ID for search
} else {
Write-CCLSHost "Invalid selection. Please enter 'get [number]' where number is between 1 and $($sortedResults.Count)." -ForegroundColor Red -Log
}
}
# Try to parse the input as a number (original behavior)
else {
$selectedNumber = 0
if ([int]::TryParse($userInput.Trim(), [ref]$selectedNumber)) {
# Validate number is in range
if ($selectedNumber -ge 1 -and $selectedNumber -le $sortedResults.Count) {
$selectedItem = $sortedResults[$selectedNumber - 1]
Write-CCLSHost "Selected: $($selectedItem.Name) ($($selectedItem.Id))" -ForegroundColor Green -Log
return $selectedItem.Id
} else {
Write-CCLSHost "Invalid selection. Please enter a number between 1 and $($sortedResults.Count)." -ForegroundColor Red -Log
}
} else {
Write-CCLSHost "Invalid input. Please enter a number, 'get [number]', or press Enter to cancel." -ForegroundColor Red -Log
}
}
}
}
catch {
Write-CCLSHost "Error during game search: $($_.Exception.Message)" -ForegroundColor Red -Log
return $null
}
}
function Resolve-GameNameToId {
param (
[string]$GameName
)
# Check if credentials are cached
if ($null -eq $script:cachedCredentials) {
Write-CCLSHost "Error: Not logged in. Please restart the application and log in." -ForegroundColor Red -Log
return $null
}
try {
# Set up request parameters with credentials to get the game library
$params = @{
Uri = "$cliApiUrl/list.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
}
}
Write-CCLSHost "#Resolving game name '$GameName' to game ID..." -Log -NoConsole
# Fetch library from server
$response = Invoke-RestMethod @params
# Check if the request was successful
if (-not $response.success) {
Write-CCLSHost "Error fetching game library: $($response.message)" -ForegroundColor Red -Log
return $null
}
# Search for the game by name (case-insensitive)
$matchedGame = $null
$gameNameLower = $GameName.ToLower().Trim()
# First try exact match
foreach ($game in $response.games) {
if ($game.name.ToLower().Trim() -eq $gameNameLower) {
$matchedGame = $game
break
}
}
# If no exact match, try partial match
if ($null -eq $matchedGame) {
foreach ($game in $response.games) {
if ($game.name.ToLower().Contains($gameNameLower)) {
if ($null -eq $matchedGame) {
$matchedGame = $game
} else {
# Multiple matches found
Write-CCLSHost "Multiple games found matching '$GameName':" -ForegroundColor Yellow -Log
# Show all matches
$allMatches = @()
foreach ($g in $response.games) {
if ($g.name.ToLower().Contains($gameNameLower)) {
$allMatches += $g
Write-CCLSHost " - $($g.name) ($($g.id))" -ForegroundColor Yellow -Log
}
}
Write-CCLSHost "Please be more specific or use the game ID directly." -ForegroundColor Yellow -Log
return $null
}
}
}
}
if ($null -ne $matchedGame) {
Write-CCLSHost "#Found game: '$($matchedGame.name)' with ID: $($matchedGame.id)" -Log -NoConsole
return $matchedGame.id
} else {
Write-CCLSHost "Game '$GameName' not found in the library." -ForegroundColor Red -Log
Write-CCLSHost "Use 'search library' to see all available games." -ForegroundColor Yellow -Log
return $null
}
}
catch {
Write-CCLSHost "Error resolving game name: $($_.Exception.Message)" -ForegroundColor Red -Log
return $null
}
}
function Get-Game {
param (
[string]$id,
[switch]$SkipConfirmation
)
$settings = Initialize-Settings
if ($settings.DevMode) {
Write-CCLSHost "'get' is disabled while in Developer mode" -ForegroundColor Red -Log
return
}
if (-not (Assert-DependenciesRequired -CommandName "get")) { return }
# Check if this is a game name resolution request
if ($id -eq "game" -and $args.Count -gt 0) {
# This is actually a game name, not an ID
$gameName = $args -join " "
Write-CCLSHost "#Attempting to resolve game name: '$gameName'" -Log -NoConsole
# Resolve the game name to an ID
$resolvedId = Resolve-GameNameToId -GameName $gameName
if ($null -eq $resolvedId) {
# Resolution failed, error already displayed
return
}
# Update the ID to the resolved one and continue with normal processing
$id = $resolvedId
Write-CCLSHost "#Resolved to ID: $id, proceeding with download..." -Log -NoConsole
}
# Validate ID format - now supports both cg0000 and cb0000 formats
if ($id -notmatch "^(cg|cb)\d{4}$") {
Write-CCLSHost "Invalid ID format. Please use format 'cg0000' for games or 'cb0000' for bundles." -ForegroundColor Red -Log
return
}
# Check if credentials are cached
if ($script:cachedCredentials -ne $null -and ($null -eq $script:cachedCredentials.Username -or $null -eq $script:cachedCredentials.Password)) {
Write-CCLSHost "Error: Not logged in. Please restart the application and log in." -ForegroundColor Red -Log
return
}
# Determine if this is a game or bundle
$isBundle = $id -match "^cb\d{4}$"
$itemType = if ($isBundle) { "bundle" } else { "game" }
# Check if setup has been completed
$settings = Initialize-Settings
if (-not $settings.HasCompletedSetup) {
Write-CCLSHost "ALERT, you must run the 'setup' command before downloading." -ForegroundColor Red -Log
return
}
# Ensure download directories exist
if (-not (Test-Path $settings.DownloadPath)) {
try {
New-Item -ItemType Directory -Path $settings.DownloadPath -Force | Out-Null
}
catch {
Write-CCLSHost "Error creating download directory: $($_.Exception.Message)" -ForegroundColor Red -Log
return
}
}
if (-not (Test-Path $settings.TempDownloadPath)) {
try {
New-Item -ItemType Directory -Path $settings.TempDownloadPath -Force | Out-Null
}
catch {
Write-CCLSHost "Error creating temporary download directory: $($_.Exception.Message)" -ForegroundColor Red -Log
return
}
}
# Get game info and download information
try {
# First, fetch the detailed information about the game
$infoParams = @{
Uri = "$cliApiUrl/search.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
id = $id
}
}
Write-CCLSHost "#Fetching $itemType information..." -Log -NoConsole
try {
$itemInfo = Invoke-RestMethod @infoParams
if (-not $itemInfo.success) {
Write-CCLSHost "Error fetching $itemType information: $($itemInfo.message)" -ForegroundColor Red -Log
return
}
Write-CCLSHost "#Successfully fetched $itemType information for later use" -Log -NoConsole
}
catch {
Write-CCLSHost "Error fetching $itemType information: $($_.Exception.Message)" -ForegroundColor Red -Log
return
}
# Now fetch download information
$params = @{
Uri = "$cliApiUrl/get.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
id = $id
}
}
Write-CCLSHost "#Fetching $itemType download information..." -Log -NoConsole
$response = Invoke-RestMethod @params
if (-not $response.success) {
Write-CCLSHost "Error: $($response.message)" -ForegroundColor Red -Log
return
}
$itemName = $response.name
$itemId = $response.id
$downloadUrl = $response.download_url
$itemSize = $response.size
# Create download file name from URL with better handling of complex URLs
$fileName = $null
try {
# Try to extract filename from URL
$uri = [System.Uri]::new($downloadUrl)
# First try to get from the path
$pathFileName = Split-Path -Path $uri.AbsolutePath -Leaf
# Check if we have query parameters that might contain the filename
if ($uri.Query) {
$queryParams = [System.Web.HttpUtility]::ParseQueryString($uri.Query)
if ($queryParams["file"]) {
$fileName = $queryParams["file"]
}
}
# If no filename from query, use the path filename
if ([string]::IsNullOrEmpty($fileName) -and -not [string]::IsNullOrEmpty($pathFileName)) {
$fileName = $pathFileName
}
# If still no filename, generate one
if ([string]::IsNullOrEmpty($fileName)) {
$fileName = "$itemId.7z"
}
# Clean the filename of invalid characters
$invalidChars = [System.IO.Path]::GetInvalidFileNameChars()
foreach ($char in $invalidChars) {
$fileName = $fileName.Replace($char, '_')
}
# Ensure it has a .7z extension if no extension present
if (-not [System.IO.Path]::HasExtension($fileName)) {
$fileName += ".7z"
}
}
catch {
Write-CCLSHost "#Error parsing download URL for filename: $($_.Exception.Message)" -Log -NoConsole
$fileName = "$itemId.7z"
}
Write-CCLSHost "#Using filename: $fileName" -Log -NoConsole
$downloadPath = Join-Path -Path $settings.TempDownloadPath -ChildPath $fileName
# === PHASE 1: Check for existing CG file (regardless of folder name) ===
Write-CCLSHost "#Scanning all folders for existing $itemId.json file..." -Log -NoConsole
$existingCgFolder = $null
$allFolders = Get-ChildItem -Path $settings.DownloadPath -Directory -ErrorAction SilentlyContinue
foreach ($folder in $allFolders) {
$cgJsonPath = Join-Path -Path $folder.FullName -ChildPath "$itemId.json"
if (Test-Path $cgJsonPath) {
$existingCgFolder = $folder.FullName
Write-CCLSHost "#Found existing $itemId.json in folder: $($folder.Name)" -Log -NoConsole
break
}
}
$replaceExistingInstallation = $false
$cgFileBasedReplacement = $false
if ($existingCgFolder) {
# Found existing CG file - show warning and ask for replacement
$folderSize = Get-FolderSize -Path $existingCgFolder
$formattedSize = Format-Size -Size $folderSize
$folderName = Split-Path -Path $existingCgFolder -Leaf
Write-CCLSHost "WARNING: $itemType with ID $itemId already exists in installation directory:" -ForegroundColor Yellow -Log
Write-CCLSHost " - $existingCgFolder ($formattedSize)" -ForegroundColor Yellow -Log
if (-not $SkipConfirmation) {
Write-CCLSHost "DO YOU WISH TO REPLACE OLD $($itemType.ToUpper()) FILES WITH NEW ONES? (Y/N)" -ForegroundColor Yellow -Log
$replaceConfirmation = Read-Host
Write-CCLSHost "$replaceConfirmation" -NoConsole -Log
if ($replaceConfirmation.ToLower() -ne "y") {
Write-CCLSHost "Download cancelled. Existing installation will not be modified." -ForegroundColor Cyan -Log
return
}
$replaceExistingInstallation = $true
$cgFileBasedReplacement = $true
} else {
Write-CCLSHost "#SkipConfirmation enabled, will replace existing installation" -Log -NoConsole
$replaceExistingInstallation = $true
$cgFileBasedReplacement = $true
}
} else {
Write-CCLSHost "#No existing CG file found for $itemId - proceeding with fresh download" -Log -NoConsole
}
# Confirm download with user (only if we're not replacing based on CG file)
if (-not $cgFileBasedReplacement) {
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "$($itemType.ToUpper()) Download: $itemName ($itemId)" -ForegroundColor Green -Log
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "Size: $itemSize" -ForegroundColor Yellow -Log
if (-not $SkipConfirmation) {
Write-CCLSHost "Do you want to proceed with the download? (Y/N)" -ForegroundColor Yellow -Log
$confirmation = Read-Host
Write-CCLSHost "$confirmation" -NoConsole -Log
if ($confirmation.ToLower() -ne "y") {
Write-CCLSHost "Download cancelled by user." -ForegroundColor Yellow -Log
return
}
} else {
Write-CCLSHost "#Automatically proceeding with download (confirmation skipped)..." -ForegroundColor Green -Log -NoConsole
}
} else {
# Show download info for replacement
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "$($itemType.ToUpper()) Download: $itemName ($itemId)" -ForegroundColor Green -Log
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "Size: $itemSize" -ForegroundColor Yellow -Log
}
# === PHASE 2: Remove existing CG-based installation if needed ===
if ($cgFileBasedReplacement -and $existingCgFolder) {
Write-CCLSHost "Removing existing installation before downloading..." -ForegroundColor Cyan -Log
try {
if (Test-Path -Path $existingCgFolder) {
Remove-Item -Path $existingCgFolder -Recurse -Force
Write-CCLSHost " - Removed: $existingCgFolder" -ForegroundColor Green -Log
} else {
Write-CCLSHost " - Folder already removed: $existingCgFolder" -ForegroundColor Yellow -Log
}
} catch {
Write-CCLSHost " - Failed to remove: $existingCgFolder - $($_.Exception.Message)" -ForegroundColor Red -Log
Write-CCLSHost "Download cancelled due to removal failure." -ForegroundColor Red -Log
return
}
}
# Flag to track if download was successfully completed
$downloadSuccessful = $false
try {
# Try to invoke Python to check if it's available
$pythonVersion = python --version 2>&1
$pythonAvailable = $true
Write-CCLSHost "#Python detected: $pythonVersion" -Log -NoConsole
# Determine the actual location of the Python script
$scriptLocation = if ($PSScriptRoot) {
# If running from a script, use its location
$PSScriptRoot
} else {
# If running in console, use current directory
(Get-Location).Path
}
$pythonScript = Join-Path -Path $scriptLocation -ChildPath "ccls_downloader.py"
# Debug the script path
Write-CCLSHost "#Python script path: $pythonScript" -Log -NoConsole
# Check if Python downloader exists, download if missing
if (-not (Test-Path $pythonScript)) {
try {
Write-CCLSHost "#Python downloader not found, downloading..." -Log -NoConsole
$downloadUrl = "https://games.ccls.icu/CLI/api/main/ccls_downloader/1.0.0/ccls_downloader.py"
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "CCLS-CLI/2.0")
$webClient.DownloadFile($downloadUrl, $pythonScript)
Write-CCLSHost "#Python downloader downloaded successfully" -Log -NoConsole
} catch {
Write-CCLSHost "#Error downloading Python downloader: $($_.Exception.Message)" -Log -NoConsole
Write-CCLSHost "Python downloader not available. Please ensure all dependencies are properly installed." -ForegroundColor Red -Log
return
}
}
# If we have Python and the downloader script, use it
if ($pythonAvailable -and (Test-Path $pythonScript)) {
Write-CCLSHost "#Using Python high-speed downloader" -Log -NoConsole
# Convert the item size to bytes if possible (for progress calculation)
$sizeInBytes = $null
if ($itemSize -match "(\d+\.?\d*)\s*(GB|MB|KB|B)") {
$value = [double]$matches[1]
$unit = $matches[2]
switch ($unit) {
"GB" { $sizeInBytes = [math]::Round($value * 1GB) }
"MB" { $sizeInBytes = [math]::Round($value * 1MB) }
"KB" { $sizeInBytes = [math]::Round($value * 1KB) }
"B" { $sizeInBytes = [math]::Round($value) }
}
}
try {
# Build the command line with proper quoting
$argList = @()
$argList += "`"$pythonScript`""
$argList += "`"$downloadUrl`""
$argList += "`"$downloadPath`""
$argList += "`"$itemName`""
$argList += "`"$itemId`""
if ($sizeInBytes) {
$argList += "$sizeInBytes"
}
$commandLine = "python $($argList -join ' ')"
Write-CCLSHost "#Running command: $commandLine" -Log -NoConsole
# Run Python as a normal process without redirecting output
# This allows it to directly handle console output
$process = Start-Process python -ArgumentList $argList -NoNewWindow -PassThru -Wait
if ($process.ExitCode -ne 0) {
# Check if a partial file exists and delete it
if (Test-Path $downloadPath) {
Write-CCLSHost "Download was cancelled. Removing partial file..." -ForegroundColor Yellow -Log
try {
Remove-Item -Path $downloadPath -Force
Write-CCLSHost "Partial file removed successfully." -ForegroundColor Yellow -Log
} catch {
Write-CCLSHost "Note: Could not remove partial file: $($_.Exception.Message)" -ForegroundColor Yellow -Log
}
} else {
Write-CCLSHost "Download was cancelled." -ForegroundColor Yellow -Log
}
# Return early without attempting extraction
return
}
# If we got here, download was successful
$downloadSuccessful = $true
}
catch {
Write-CCLSHost "Error running Python downloader: $($_.Exception.Message)" -ForegroundColor Red -Log
Write-CCLSHost "Download failed. Please ensure all dependencies are properly installed." -ForegroundColor Red -Log
return
}
}
else {
Write-CCLSHost "Python downloader not available. Please install required dependencies." -ForegroundColor Red -Log
return
}
}
catch {
Write-CCLSHost "Python not found. Please install required dependencies." -ForegroundColor Red -Log
return
}
# === PHASE 3: Post-download extraction and JSON creation ===
if ($downloadSuccessful) {
Write-CCLSHost "#Download completed successfully, preparing for extraction..." -Log -NoConsole
Write-CCLSHost "#Starting robust extraction process..." -Log -NoConsole
try {
# Call the robust extraction function with temporary extraction
$extractionResult = Start-RobustExtraction -settings $settings -itemId $itemId -SkipConfirmation $SkipConfirmation
# After extraction, save the game info JSON to the correct location
if ($extractionResult.Success -and $extractionResult.FinalFolder) {
# Path for the info JSON file
$infoJsonPath = Join-Path -Path $extractionResult.FinalFolder -ChildPath "$itemId.json"
# Save item info to JSON file
try {
# Ensure the directory exists
if (-not (Test-Path $extractionResult.FinalFolder)) {
New-Item -ItemType Directory -Path $extractionResult.FinalFolder -Force | Out-Null
}
# Convert and save the JSON with proper formatting
$jsonContent = $itemInfo | ConvertTo-Json -Depth 10
Set-Content -Path $infoJsonPath -Value $jsonContent -Encoding UTF8 -Force
# Verify the file was created successfully
if (Test-Path $infoJsonPath) {
$fileSize = (Get-Item $infoJsonPath).Length
Write-CCLSHost "Game information saved successfully to: $infoJsonPath (Size: $fileSize bytes)" -ForegroundColor Green -Log
} else {
Write-CCLSHost "Warning: JSON file creation verification failed" -ForegroundColor Yellow -Log
}
Write-CCLSHost "#Updating library cache after successful game installation..." -Log -NoConsole
Update-LibraryCache -Force
} catch {
Write-CCLSHost "Error saving game information: $($_.Exception.Message)" -ForegroundColor Red -Log
Write-CCLSHost "Attempted path: $infoJsonPath" -ForegroundColor Red -Log
}
} else {
Write-CCLSHost "Warning: Could not determine final folder location for JSON file creation" -ForegroundColor Yellow -Log
}
}
catch {
Write-CCLSHost "An error occurred during extraction: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
}
catch {
Write-CCLSHost "An error occurred: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
# === ROBUST EXTRACTION FUNCTION WITH FIXED 7-ZIP PATH RESOLUTION ===
function Start-RobustExtraction {
param (
[object]$settings,
[string]$itemId,
[switch]$SkipConfirmation
)
$tempDownloadPath = $settings.TempDownloadPath
$downloadPath = $settings.DownloadPath
# Check if 7-Zip is installed with improved path resolution
$7zipPath = $null
$found = $false
# Define all possible 7-Zip locations
$7zipLocations = @(
"C:\Program Files\7-Zip\7z.exe",
"${env:ProgramFiles(x86)}\7-Zip\7z.exe"
)
# Add the local 7zip installation path with proper script location resolution
$scriptLocation = if ($PSScriptRoot) {
$PSScriptRoot
} else {
(Get-Location).Path
}
$local7zipPath = Join-Path -Path $scriptLocation -ChildPath "7zip\7z.exe"
$7zipLocations += $local7zipPath
# Check each location
foreach ($path in $7zipLocations) {
Write-CCLSHost "#Checking 7-Zip path: $path" -Log -NoConsole
if (Test-Path -Path $path) {
$7zipPath = $path
$found = $true
Write-CCLSHost "#Found 7-Zip at: $path" -Log -NoConsole
break
}
}
if (-not $found) {
Write-CCLSHost "Error: 7-Zip is not installed at any expected location." -ForegroundColor Red -Log
Write-CCLSHost "Checked locations:" -ForegroundColor Red -Log
foreach ($path in $7zipLocations) {
Write-CCLSHost " - $path" -ForegroundColor Red -Log
}
Write-CCLSHost "Please install 7-Zip using the 'install 7zip' command." -ForegroundColor Yellow -Log
Write-CCLSHost "Once you have installed 7zip run 'extract $itemId -y' to extract the game" -ForegroundColor Yellow -Log
return @{ Success = $false; FinalFolder = $null }
}
# Get all .7z files in the temp download path
$7zFiles = Get-ChildItem -Path $tempDownloadPath -Filter "*.7z" -ErrorAction SilentlyContinue
if ($7zFiles.Count -eq 0) {
Write-CCLSHost "No .7z files found in '$tempDownloadPath'." -ForegroundColor Yellow -Log
return @{ Success = $false; FinalFolder = $null }
}
# Generate random 16-bit hash for temporary extraction folder
$randomHash = -join ((0..15) | ForEach-Object { '{0:X}' -f (Get-Random -Maximum 16) })
$tempExtractionPath = Join-Path -Path $downloadPath -ChildPath ".temp-$randomHash"
Write-CCLSHost "#Creating temporary extraction directory: $tempExtractionPath" -Log -NoConsole
Write-CCLSHost "#Using 7-Zip executable: $7zipPath" -Log -NoConsole
try {
# Create temporary extraction directory
New-Item -ItemType Directory -Path $tempExtractionPath -Force | Out-Null
$overallSuccess = $true
$extractedFolders = @()
# Process each .7z file
foreach ($file in $7zFiles) {
$filePath = $file.FullName
# Extract the file to temporary directory
Write-CCLSHost "Extracting: $filePath to temporary location" -ForegroundColor Cyan -Log
Write-CCLSHost "#Running: `"$7zipPath`" x `"$filePath`" -o`"$tempExtractionPath`" -y" -Log -NoConsole
# Capture and suppress 7-Zip output, only show errors if extraction fails
try {
$extractionOutput = & "$7zipPath" x "$filePath" -o"$tempExtractionPath" -y 2>&1
$extractionSuccess = $?
Write-CCLSHost "#7-Zip exit code: $LASTEXITCODE" -Log -NoConsole
Write-CCLSHost "#7-Zip output: $($extractionOutput -join '; ')" -Log -NoConsole
if ($extractionSuccess -and $LASTEXITCODE -eq 0) {
Write-CCLSHost "Extraction successful. Deleting original file: $filePath" -ForegroundColor Green -Log
Remove-Item -Path $filePath -Force
} else {
Write-CCLSHost "Error extracting file. 7-Zip exit code: $LASTEXITCODE" -ForegroundColor Red -Log
if ($extractionOutput) {
Write-CCLSHost "7-Zip output: $($extractionOutput -join "`n")" -ForegroundColor Red -Log
}
Remove-Item -Path $filePath -Force
$overallSuccess = $false
}
} catch {
Write-CCLSHost "Error running 7-Zip: $($_.Exception.Message)" -ForegroundColor Red -Log
Write-CCLSHost "7-Zip path used: $7zipPath" -ForegroundColor Red -Log
Remove-Item -Path $filePath -Force
$overallSuccess = $false
}
}
if (-not $overallSuccess) {
# Clean up temp directory if extraction failed
if (Test-Path $tempExtractionPath) {
Remove-Item -Path $tempExtractionPath -Recurse -Force
}
return @{ Success = $false; FinalFolder = $null }
}
# Get the folders that were extracted to the temporary directory
$extractedItems = Get-ChildItem -Path $tempExtractionPath -ErrorAction SilentlyContinue
$extractedFolders = $extractedItems | Where-Object { $_.PSIsContainer }
if ($extractedFolders.Count -eq 0) {
Write-CCLSHost "No folders found in temporary extraction directory. Looking for files..." -ForegroundColor Yellow -Log
# If no folders, maybe files were extracted directly
$extractedFiles = $extractedItems | Where-Object { -not $_.PSIsContainer }
if ($extractedFiles.Count -gt 0) {
Write-CCLSHost "Files were extracted directly to temporary directory. Creating container folder..." -ForegroundColor Yellow -Log
# Move all files to a subfolder named after the item
$containerFolderName = "ExtractedGame"
$containerFolderPath = Join-Path -Path $tempExtractionPath -ChildPath $containerFolderName
New-Item -ItemType Directory -Path $containerFolderPath -Force | Out-Null
foreach ($file in $extractedFiles) {
Move-Item -Path $file.FullName -Destination $containerFolderPath -Force
}
# Update the extracted folders list
$extractedFolders = @(Get-Item $containerFolderPath)
}
}
if ($extractedFolders.Count -eq 0) {
Write-CCLSHost "No content found after extraction." -ForegroundColor Red -Log
# Clean up temp directory
if (Test-Path $tempExtractionPath) {
Remove-Item -Path $tempExtractionPath -Recurse -Force
}
return @{ Success = $false; FinalFolder = $null }
}
Write-CCLSHost "#Found $($extractedFolders.Count) folder(s) in temporary extraction directory" -Log -NoConsole
# Check for folder name conflicts in the main downloads directory
$conflictingFolders = @()
$existingFolders = Get-ChildItem -Path $downloadPath -Directory -ErrorAction SilentlyContinue
foreach ($extractedFolder in $extractedFolders) {
$extractedFolderName = $extractedFolder.Name
Write-CCLSHost "#Checking for conflicts with extracted folder: $extractedFolderName" -Log -NoConsole
# Check if a folder with the same name exists in the downloads directory
$conflictingFolder = $existingFolders | Where-Object { $_.Name -eq $extractedFolderName }
if ($conflictingFolder) {
# Found a conflict - check if it contains the same CG file
$conflictCgPath = Join-Path -Path $conflictingFolder.FullName -ChildPath "$itemId.json"
if (-not (Test-Path $conflictCgPath)) {
# Conflict found and it doesn't contain our CG file
$conflictingFolders += @{
Name = $extractedFolderName
Path = $conflictingFolder.FullName
Size = Get-FolderSize -Path $conflictingFolder.FullName
}
Write-CCLSHost "#Found conflicting folder without matching CG file: $($conflictingFolder.FullName)" -Log -NoConsole
} else {
Write-CCLSHost "#Found folder with same name but it contains our CG file - this should have been handled in Phase 1" -Log -NoConsole
}
}
}
# Handle conflicts if any were found
if ($conflictingFolders.Count -gt 0) {
Write-CCLSHost "Folder name conflict detected!" -ForegroundColor Yellow -Log
foreach ($conflict in $conflictingFolders) {
$formattedSize = Format-Size -Size $conflict.Size
Write-CCLSHost " - Folder '$($conflict.Name)' already exists ($formattedSize)" -ForegroundColor Yellow -Log
}
$shouldRemoveConflicts = $true
if (-not $SkipConfirmation) {
Write-CCLSHost "Do you wish to delete these conflicting folders and proceed with installation? (Y/N)" -ForegroundColor Yellow -Log
$conflictConfirmation = Read-Host
Write-CCLSHost "$conflictConfirmation" -NoConsole -Log
if ($conflictConfirmation.ToLower() -ne "y") {
$shouldRemoveConflicts = $false
}
} else {
Write-CCLSHost "#SkipConfirmation enabled, will remove conflicting folders automatically" -Log -NoConsole
}
if ($shouldRemoveConflicts) {
# Remove conflicting folders
foreach ($conflict in $conflictingFolders) {
try {
Remove-Item -Path $conflict.Path -Recurse -Force
Write-CCLSHost " - Removed conflicting folder: $($conflict.Path)" -ForegroundColor Green -Log
} catch {
Write-CCLSHost " - Failed to remove folder: $($conflict.Path) - $($_.Exception.Message)" -ForegroundColor Red -Log
# Clean up temp directory and return failure
if (Test-Path $tempExtractionPath) {
Remove-Item -Path $tempExtractionPath -Recurse -Force
}
return @{ Success = $false; FinalFolder = $null }
}
}
} else {
Write-CCLSHost "Installation cancelled due to unresolved conflicts." -ForegroundColor Yellow -Log
# Clean up temp directory
if (Test-Path $tempExtractionPath) {
Remove-Item -Path $tempExtractionPath -Recurse -Force
}
return @{ Success = $false; FinalFolder = $null }
}
} else {
Write-CCLSHost "#No folder name conflicts detected" -Log -NoConsole
}
# Move extracted folders from temporary directory to main downloads directory
$finalFolder = $null
foreach ($extractedFolder in $extractedFolders) {
$sourcePath = $extractedFolder.FullName
$destinationPath = Join-Path -Path $downloadPath -ChildPath $extractedFolder.Name
try {
Write-CCLSHost "Moving extracted folder from temporary location to: $destinationPath" -ForegroundColor Cyan -Log
Move-Item -Path $sourcePath -Destination $destinationPath -Force
# Set the final folder (use the first one if multiple)
if (-not $finalFolder) {
$finalFolder = $destinationPath
}
} catch {
Write-CCLSHost "Failed to move folder: $($_.Exception.Message)" -ForegroundColor Red -Log
# Try to clean up
if (Test-Path $tempExtractionPath) {
Remove-Item -Path $tempExtractionPath -Recurse -Force
}
return @{ Success = $false; FinalFolder = $null }
}
}
# Clean up temporary extraction directory
try {
if (Test-Path $tempExtractionPath) {
Remove-Item -Path $tempExtractionPath -Recurse -Force
Write-CCLSHost "#Cleaned up temporary extraction directory" -Log -NoConsole
}
} catch {
Write-CCLSHost "#Warning: Could not clean up temporary directory: $($_.Exception.Message)" -Log -NoConsole
}
Write-CCLSHost "#Robust extraction completed successfully." -Log -NoConsole
return @{
Success = $true
FinalFolder = $finalFolder
}
} catch {
Write-CCLSHost "Error during robust extraction: $($_.Exception.Message)" -ForegroundColor Red -Log
# Clean up temp directory if it exists
try {
if (Test-Path $tempExtractionPath) {
Remove-Item -Path $tempExtractionPath -Recurse -Force
}
} catch {
Write-CCLSHost "#Warning: Could not clean up temporary directory after error" -Log -NoConsole
}
return @{ Success = $false; FinalFolder = $null }
}
}
# Function to check system requirements
function Test-SystemRequirements {
Write-CCLSHost "Checking system requirements:" -ForegroundColor Cyan -Log
# Check for Python
Write-CCLSHost "Python : " -NoNewline -Log
try {
$pythonResult = python --version 2>&1
if ($pythonResult -match "Python (\d+\.\d+\.\d+)") {
$pythonVersion = $matches[1]
Write-CCLSHost "Installed (v$pythonVersion)" -ForegroundColor Green -Log
$pythonInstalled = $true
} else {
Write-CCLSHost "Not detected" -ForegroundColor Red -Log
$pythonInstalled = $false
}
} catch {
Write-CCLSHost "Not installed" -ForegroundColor Red -Log
$pythonInstalled = $false
}
# Check for Python requests library (only if Python is installed)
Write-CCLSHost "Python requests : " -NoNewline -Log
if ($pythonInstalled) {
try {
$requestsCheck = python -c "import requests; print('Installed (v{0})'.format(requests.__version__))" 2>&1
if ($requestsCheck -match "Installed \(v([\d\.]+)\)") {
$requestsVersion = $matches[1]
Write-CCLSHost "Installed (v$requestsVersion)" -ForegroundColor Green -Log
} else {
Write-CCLSHost "Not installed" -ForegroundColor Red -Log
}
} catch {
Write-CCLSHost "Not installed" -ForegroundColor Red -Log
}
} else {
Write-CCLSHost "Not applicable (Python not installed)" -ForegroundColor Yellow -Log
}
# Check for 7-Zip
Write-CCLSHost "7-Zip : " -NoNewline -Log
# Check system-wide 7-Zip installation
$systemPaths = @(
"C:\Program Files\7-Zip\7z.exe",
"${env:ProgramFiles(x86)}\7-Zip\7z.exe"
)
# Check local 7-Zip installation
$scriptLocation = if ($PSScriptRoot) {
$PSScriptRoot
} else {
(Get-Location).Path
}
$localPath = Join-Path -Path $scriptLocation -ChildPath "7zip\7z.exe"
# Combine all paths to check
$allPaths = $systemPaths + $localPath
$7zipInstalled = $false
$7zipLocation = ""
foreach ($path in $allPaths) {
if (Test-Path -Path $path) {
$7zipInstalled = $true
$7zipLocation = $path
break
}
}
if ($7zipInstalled) {
# Get 7-Zip version
try {
$versionInfo = Get-Item $7zipLocation | Select-Object -ExpandProperty VersionInfo
$7zipVersion = $versionInfo.ProductVersion
Write-CCLSHost "Installed (v$7zipVersion)" -ForegroundColor Green -Log
} catch {
Write-CCLSHost "Installed (unknown version)" -ForegroundColor Green -Log
}
} else {
Write-CCLSHost "Not installed" -ForegroundColor Red -Log
}
# Summary of checks
Write-CCLSHost "`nSystem Check Summary:" -ForegroundColor Cyan -Log
if (-not $pythonInstalled) {
Write-CCLSHost " - Python is not installed. Install it with 'install python' command" -ForegroundColor Yellow -Log
Write-CCLSHost " - Or download it manually from https://www.python.org/downloads/" -ForegroundColor Yellow -Log
Write-CCLSHost " Ensure 'Add Python to PATH' is checked in the installer (IMPORTANT)" -ForegroundColor Yellow -Log
Write-CCLSHost " Ensure 'pip' is selected in the optional features in the installer (IMPORTANT)" -ForegroundColor Yellow -Log
}
if ($pythonInstalled -and $requestsCheck -notmatch "Installed") {
Write-CCLSHost " - Python 'requests' library is not installed." -ForegroundColor Yellow -Log
Write-CCLSHost " Install with: install requests" -ForegroundColor Yellow -Log
}
if (-not $7zipInstalled) {
Write-CCLSHost " - 7-Zip is not installed. 7-Zip is required for extracting downloaded games." -ForegroundColor Yellow -Log
Write-CCLSHost " Install with: install 7zip" -ForegroundColor Yellow -Log
}
if ($pythonInstalled -and $requestsCheck -match "Installed" -and $7zipInstalled) {
Write-CCLSHost "All system requirements are met. Your system is ready to use all features." -ForegroundColor Green -Log
}
}
# Get-GamesList function that uses the cached credentials
function Get-GamesList {
$settings = Initialize-Settings
if ($settings.DevMode) {
Write-CCLSHost "'search library' is disabled while in Developer mode" -ForegroundColor Red -Log
return
}
# Check if credentials are cached
if ($null -eq $script:cachedCredentials) {
Write-CCLSHost "Error: Not logged in. Please restart the application and log in." -ForegroundColor Red -Log
return
}
if (-not (Assert-DependenciesRequired -CommandName "search library")) { return }
try {
# Set up request parameters with credentials
$params = @{
Uri = "$cliApiUrl/list.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
}
}
Write-CCLSHost "#Fetching game and bundle library..." -Log
# Fetch library from server
try {
$response = Invoke-RestMethod @params
# Check if the request was successful
if (-not $response.success) {
Write-CCLSHost "Error: $($response.message)" -ForegroundColor Red -Log
return
}
# Display game list
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "Game Library - $($response.count) games available" -ForegroundColor Green -Log
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
if ($response.count -eq 0) {
Write-CCLSHost "No games found in the library." -ForegroundColor Yellow -Log
}
else {
# Determine the maximum length for proper formatting
$maxNameLength = ($response.games | ForEach-Object { $_.name.Length } | Measure-Object -Maximum).Maximum
$maxIdLength = ($response.games | ForEach-Object { $_.id.Length } | Measure-Object -Maximum).Maximum
# Ensure minimum column widths
$nameColumnWidth = [Math]::Max($maxNameLength, 30)
$idColumnWidth = [Math]::Max($maxIdLength, 8)
# Create header
Write-CCLSHost "$("Game Name".PadRight($nameColumnWidth)) => $("CG Number".PadRight($idColumnWidth))" -ForegroundColor Cyan -Log
Write-CCLSHost "$("-" * $nameColumnWidth) => $("-" * $idColumnWidth)" -ForegroundColor Cyan -Log
# Print each game with proper formatting
foreach ($game in $response.games) {
Write-CCLSHost "$($game.name.PadRight($nameColumnWidth)) => $($game.id)" -ForegroundColor White -Log
}
}
# Display bundle list if any bundles exist
if ($response.bundle_count -gt 0) {
Write-CCLSHost "`n==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "Bundle Library - $($response.bundle_count) bundles available" -ForegroundColor Green -Log
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
# Determine the maximum length for proper formatting
$maxNameLength = ($response.bundles | ForEach-Object { $_.name.Length } | Measure-Object -Maximum).Maximum
$maxIdLength = ($response.bundles | ForEach-Object { $_.id.Length } | Measure-Object -Maximum).Maximum
# Ensure minimum column widths
$nameColumnWidth = [Math]::Max($maxNameLength, 30)
$idColumnWidth = [Math]::Max($maxIdLength, 8)
# Create header
Write-CCLSHost "$("Bundle Name".PadRight($nameColumnWidth)) => $("CB Number".PadRight($idColumnWidth))" -ForegroundColor Cyan -Log
Write-CCLSHost "$("-" * $nameColumnWidth) => $("-" * $idColumnWidth)" -ForegroundColor Cyan -Log
# Print each bundle with proper formatting
foreach ($bundle in $response.bundles) {
Write-CCLSHost "$($bundle.name.PadRight($nameColumnWidth)) => $($bundle.id)" -ForegroundColor White -Log
}
}
Write-CCLSHost "`nUse 'search [cgnumber/cbnumber]' to get detailed information about a specific game or bundle." -ForegroundColor Yellow -Log
}
catch {
Write-CCLSHost "Error fetching library: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
catch {
Write-CCLSHost "An error occurred while processing library: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
function Clear-ConsoleScreen {
# Log the clear command
Write-CCLSHost "#User cleared the console screen" -Log -NoConsole
# Use the PowerShell Clear-Host cmdlet to clear the console
Clear-Host
}
# Function to get folder size including subfolders
function Get-FolderSize {
param (
[string]$Path
)
$totalSize = 0
try {
# Get all files in the folder and subfolders
$files = Get-ChildItem -Path $Path -Recurse -File -ErrorAction SilentlyContinue
foreach ($file in $files) {
$totalSize += $file.Length
}
}
catch {
Write-CCLSHost "Error calculating folder size: $($_.Exception.Message)" -ForegroundColor Red -Log
}
return $totalSize
}
# Modified Get-InstalledGames function to use cache
function Get-InstalledGames {
param (
[switch]$Detailed
)
# Ensure cache is initialized
if (-not $script:libraryCache) {
Initialize-LibraryCache
}
# Check if we have any games
if (-not $script:libraryCache.games -or $script:libraryCache.totalGames -eq 0) {
Write-CCLSHost "`nNo games found in the library." -ForegroundColor Yellow -Log
return
}
# Header
Write-CCLSHost "`nInstalled Games" -ForegroundColor Green -Log
Write-CCLSHost "==============" -ForegroundColor Green -Log
# List each game from cache
foreach ($game in $script:libraryCache.games) {
if ($Detailed) {
$version = $game.localVersion
$isOutdated = $false
# Check for updates if we have credentials and metadata
if ($null -ne $script:cachedCredentials -and
$null -ne $script:cachedCredentials.Username -and
$null -ne $script:cachedCredentials.Password -and
$game.hasMetadata -and $game.gameId -ne "Unknown") {
try {
Write-CCLSHost "#Checking for updates for $($game.gameId)..." -Log -NoConsole
$params = @{
Uri = "$cliApiUrl/search.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
id = $game.gameId
}
}
$response = Invoke-RestMethod @params
if ($response.success -and $response.version) {
$latestVersion = $response.version
# Compare versions
if ($version -ne $latestVersion -and $version -ne "Unknown" -and $latestVersion -ne "") {
$isOutdated = $true
Write-CCLSHost "#Game $($game.folderName) is outdated. Local version: $version, Latest: $latestVersion" -Log -NoConsole
}
}
} catch {
Write-CCLSHost "#Error checking updates for $($game.gameId): $($_.Exception.Message)" -Log -NoConsole
}
}
# Display with size and version
$displayText = "$($game.folderName) - $($game.localSizeFormatted)"
if ($version -ne "Unknown") {
$displayText += " - Version:$version"
if ($isOutdated) {
Write-CCLSHost $displayText -NoNewline -Log
Write-CCLSHost " (OUTDATED)" -ForegroundColor Red -Log
} else {
Write-CCLSHost $displayText -Log
}
} else {
Write-CCLSHost $displayText -Log
}
} else {
# Simple list of names
Write-CCLSHost "$($game.folderName)" -Log
}
}
# Show totals if detailed view
if ($Detailed) {
Write-CCLSHost "`nTotal size: $($script:libraryCache.totalSizeFormatted)" -ForegroundColor Cyan -Log
Write-CCLSHost "Games count: $($script:libraryCache.totalGames)" -ForegroundColor Cyan -Log
}
}
# Modified Get-GameInfoByIdentifier function to use cache
function Get-GameInfoByIdentifier {
param (
[string]$Identifier,
[string]$SearchType = "foldername",
[switch]$Detailed,
[switch]$Tree
)
# Ensure cache is initialized
if (-not $script:libraryCache) {
Initialize-LibraryCache
}
# Find game in cache
$cachedGame = Get-GameFromCache -Identifier $Identifier -SearchType $SearchType
if (-not $cachedGame) {
Write-CCLSHost "Could not locate game matching $Identifier" -ForegroundColor Red -Log
return
}
# Use cached data
$gamePath = $cachedGame.localPath
$gameDisplayName = $cachedGame.folderName
$size = $cachedGame.localSize
$sizeFormatted = $cachedGame.localSizeFormatted
$version = $cachedGame.localVersion
$gameId = $cachedGame.gameId
$gameInfo = $null
$isOutdated = $false
# Load JSON info if available
if ($cachedGame.hasMetadata -and $gameId -ne "Unknown") {
try {
$jsonPath = Join-Path -Path $gamePath -ChildPath "$gameId.json"
if (Test-Path $jsonPath) {
$gameInfo = Get-Content -Path $jsonPath -Raw | ConvertFrom-Json
}
# Check for latest version from server if we have credentials
if ($null -ne $script:cachedCredentials -and
$null -ne $script:cachedCredentials.Username -and
$null -ne $script:cachedCredentials.Password) {
try {
$params = @{
Uri = "$cliApiUrl/search.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
id = $gameId
}
}
Write-CCLSHost "#Checking for updates for $gameId..." -Log -NoConsole
$response = Invoke-RestMethod @params
if ($response.success -and $response.version) {
$latestVersion = $response.version
# Compare versions
if ($version -ne $latestVersion -and $version -ne "Unknown" -and $latestVersion -ne "") {
$isOutdated = $true
Write-CCLSHost "#Game $gameDisplayName is outdated. Local version: $version, Latest: $latestVersion" -Log -NoConsole
}
}
} catch {
Write-CCLSHost "#Error checking updates: $($_.Exception.Message)" -Log -NoConsole
}
}
} catch {
Write-CCLSHost "#Error reading game info: $($_.Exception.Message)" -Log -NoConsole
}
}
# Header
Write-CCLSHost "`nGame Information: $gameDisplayName" -ForegroundColor Green -Log
Write-CCLSHost "=======================" -ForegroundColor Green -Log
# Always show basic information (using cached data)
Write-CCLSHost "Size: $sizeFormatted" -Log
if ($gameId -and $gameId -ne "Unknown") {
Write-CCLSHost "ID: $gameId" -Log
}
# Display version with outdated tag if needed
if ($version -ne "Unknown") {
Write-CCLSHost "Version: $version" -NoNewline -Log
if ($isOutdated) {
Write-CCLSHost " (OUTDATED)" -ForegroundColor Red -Log
} else {
Write-CCLSHost "" -Log # Just to add a newline
}
}
# Show detailed information if -d switch is specified
if ($Detailed -and $gameInfo) {
# Show description if available
if ($gameInfo.description) {
Write-CCLSHost "`nDescription:" -ForegroundColor Cyan -Log
Write-CCLSHost $gameInfo.description -Log
}
# Show safety info if available
if ($gameInfo.safety_score -or $gameInfo.safety_level) {
Write-CCLSHost "`nSafety:" -ForegroundColor Cyan -Log
if ($gameInfo.safety_score) { Write-CCLSHost "Score: $($gameInfo.safety_score)" -Log }
if ($gameInfo.safety_level) { Write-CCLSHost "Level: $($gameInfo.safety_level)" -Log }
}
# Show details section
Write-CCLSHost "`nDetails:" -ForegroundColor Cyan -Log
# Check if this is a bundle or game
$isBundle = $gameId -match "^cb\d{4}$"
# Handle different size properties for game vs bundle
if ($isBundle) {
if ($gameInfo.zipped_size) { Write-CCLSHost "Zipped Size: $($gameInfo.zipped_size)" -Log }
if ($gameInfo.unzipped_size) { Write-CCLSHost "Unzipped Size: $($gameInfo.unzipped_size)" -Log }
if ($gameInfo.games_included) { Write-CCLSHost "Games Included: $($gameInfo.games_included)" -Log }
} else {
if ($gameInfo.size) { Write-CCLSHost "Size: $($gameInfo.size)" -Log }
}
# Show availability info if any is available
if (($gameInfo.online -and $gameInfo.online -ne "") -or
($gameInfo.steam -and $gameInfo.steam -ne "") -or
($gameInfo.epic -and $gameInfo.epic -ne "")) {
Write-CCLSHost "`nAvailability:" -ForegroundColor Cyan -Log
if ($gameInfo.online -and $gameInfo.online -ne "") { Write-CCLSHost "Online: $($gameInfo.online)" -Log }
if ($gameInfo.steam -and $gameInfo.steam -ne "") { Write-CCLSHost "Steam: $($gameInfo.steam)" -Log }
if ($gameInfo.epic -and $gameInfo.epic -ne "") { Write-CCLSHost "Epic: $($gameInfo.epic)" -Log }
}
# Show false antivirus information if available
if ($gameInfo.false_av -and $gameInfo.false_av -ne "") {
Write-CCLSHost "`nNote: " -ForegroundColor Yellow -NoNewline -Log
Write-CCLSHost "This game may trigger false antivirus alerts: $($gameInfo.false_av)" -Log
}
# Show system requirements if available
if ($gameInfo.system_requirements) {
Write-CCLSHost "`nSystem Requirements:" -ForegroundColor Cyan -Log
if ($gameInfo.system_requirements.minimum) {
Write-CCLSHost "Minimum:" -ForegroundColor Yellow -Log
foreach ($prop in $gameInfo.system_requirements.minimum.PSObject.Properties) {
Write-CCLSHost " $($prop.Name): $($prop.Value)" -Log
}
}
if ($gameInfo.system_requirements.recommended) {
Write-CCLSHost "`nRecommended:" -ForegroundColor Yellow -Log
foreach ($prop in $gameInfo.system_requirements.recommended.PSObject.Properties) {
Write-CCLSHost " $($prop.Name): $($prop.Value)" -Log
}
}
}
}
# If tree view is requested, show file tree (this still needs to scan the actual directory)
if ($Tree) {
Write-CCLSHost "`nFile Structure (full directory tree):" -ForegroundColor Cyan -Log
$fileTree = Get-FullFileTree -Path $gamePath
foreach ($line in $fileTree) {
Write-CCLSHost $line -Log
}
}
}
# Modified Browse-Games function to use cache
function Browse-Games {
$settings = Initialize-Settings
if ($settings.DevMode) {
Write-CCLSHost "'browse' is disabled while in Developer mode" -ForegroundColor Red -Log
return
}
# Ensure cache is initialized
if (-not $script:libraryCache) {
Initialize-LibraryCache
}
# Check if we have any games
if (-not $script:libraryCache.games -or $script:libraryCache.totalGames -eq 0) {
Write-CCLSHost "No games are currently installed." -ForegroundColor Yellow -Log
return
}
Write-CCLSHost "#Using library cache for browse display" -Log -NoConsole
# Create sorted games list from cache
$sortedGames = @()
foreach ($cachedGame in $script:libraryCache.games) {
$gameEntry = @{
Name = $cachedGame.gameName
Id = $cachedGame.gameId
Type = if ($cachedGame.hasMetadata) { "Game" } else { "Unknown" }
LocalVersion = $cachedGame.localVersion
LocalSize = $cachedGame.localSizeFormatted
HasMetadata = $cachedGame.hasMetadata
IsOutdated = $false
OnlineVersion = "Unknown"
LocalPath = $cachedGame.localPath
}
# Check for updates if we have credentials and metadata
if ($null -ne $script:cachedCredentials -and
$null -ne $script:cachedCredentials.Username -and
$null -ne $script:cachedCredentials.Password -and
$cachedGame.hasMetadata -and $cachedGame.gameId -ne "Unknown") {
try {
Write-CCLSHost "#Checking online version for $($cachedGame.gameId)..." -Log -NoConsole
$infoParams = @{
Uri = "$cliApiUrl/search.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
id = $cachedGame.gameId
}
}
$onlineGameInfo = Invoke-RestMethod @infoParams
if ($onlineGameInfo.success -and $onlineGameInfo.version) {
$gameEntry.OnlineVersion = $onlineGameInfo.version
# Compare versions to determine if outdated
if ($gameEntry.LocalVersion -ne "Unknown" -and
$gameEntry.OnlineVersion -ne "Unknown" -and
$gameEntry.OnlineVersion -ne "" -and
$gameEntry.LocalVersion -ne $gameEntry.OnlineVersion) {
$gameEntry.IsOutdated = $true
Write-CCLSHost "#Game $($gameEntry.Name) is outdated. Local: $($gameEntry.LocalVersion), Online: $($gameEntry.OnlineVersion)" -Log -NoConsole
}
}
} catch {
Write-CCLSHost "#Error checking online version for $($cachedGame.gameId): $($_.Exception.Message)" -Log -NoConsole
}
}
$sortedGames += $gameEntry
}
# Sort games: Games with metadata first, then by name
if ($sortedGames.Count -eq 1) {
$sortedGames = $sortedGames
} else {
$sortedGames = $sortedGames | Sort-Object @{Expression={-($_.HasMetadata -as [int])}}, Name
}
# Ensure we have an array even with one item
if ($sortedGames -isnot [array]) {
$sortedGames = @($sortedGames)
}
# Display the browse interface (rest of the function remains the same)
Write-CCLSHost "`n==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "Installed Games Browser - $($sortedGames.Count) games installed" -ForegroundColor Green -Log
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
# [Rest of the Browse-Games function continues as before with the same display logic]
# The display code remains identical, it just uses the cached data instead of scanning folders
# Calculate column widths for formatting - handle single game case
$maxNameLength = 30
$maxIdLength = 15
$maxLocalSizeLength = 15
$maxLocalVersionLength = 20
$maxTypeLength = 12
# Only calculate max lengths if we have games
if ($sortedGames.Count -gt 0) {
$nameArray = @()
$idArray = @()
$sizeArray = @()
$versionArray = @()
$typeArray = @()
foreach ($game in $sortedGames) {
$nameArray += $game.Name.Length
$idArray += ("ID: " + $game.Id).Length
$sizeArray += ("Size: " + $game.LocalSize).Length
$versionText = "Version: " + $game.LocalVersion
if ($game.IsOutdated) {
$versionText += " (OUTDATED)"
}
$versionArray += $versionText.Length
if ($game.HasMetadata) {
$gameType = "Official"
} else {
$gameType = "Unknown"
}
$typeArray += ("Type: " + $gameType).Length
}
if ($nameArray.Count -gt 0) {
$maxNameLength = [Math]::Max(($nameArray | Measure-Object -Maximum).Maximum, 30)
$maxIdLength = [Math]::Max(($idArray | Measure-Object -Maximum).Maximum, 15)
$maxLocalSizeLength = [Math]::Max(($sizeArray | Measure-Object -Maximum).Maximum, 15)
$maxLocalVersionLength = [Math]::Max(($versionArray | Measure-Object -Maximum).Maximum, 20)
$maxTypeLength = [Math]::Max(($typeArray | Measure-Object -Maximum).Maximum, 12)
}
}
# Ensure minimum widths
$nameWidth = $maxNameLength
$idWidth = $maxIdLength
$localSizeWidth = $maxLocalSizeLength
$localVersionWidth = $maxLocalVersionLength
$typeWidth = $maxTypeLength
# Display header
Write-CCLSHost ("#".PadRight(3) +
"Game Name".PadRight($nameWidth + 2) +
"ID".PadRight($idWidth + 2) +
"Size".PadRight($localSizeWidth + 2) +
"Version".PadRight($localVersionWidth + 2) +
"Type") -ForegroundColor Cyan -Log
Write-CCLSHost ("-" * 3 + " " +
"-" * ($nameWidth + 1) + " " +
"-" * ($idWidth + 1) + " " +
"-" * ($localSizeWidth + 1) + " " +
"-" * ($localVersionWidth + 1) + " " +
"-" * ($typeWidth)) -ForegroundColor Cyan -Log
# Display each game
for ($i = 0; $i -lt $sortedGames.Count; $i++) {
$game = $sortedGames[$i]
$displayNumber = $i + 1
# Determine type and base color
if ($game.HasMetadata) {
$gameType = "Official"
$gameColor = "Green"
} else {
$gameType = "Unknown"
$gameColor = "Gray"
}
# Format the values with prefixes
$gameName = if ($game.Name) { $game.Name } else { "Unknown" }
$gameIdDisplay = if ($game.Id -and $game.Id -ne "Unknown") { "ID: " + $game.Id } else { "ID: Unknown" }
$gameSizeDisplay = if ($game.LocalSize) { "Size: " + $game.LocalSize } else { "Size: Unknown" }
$gameVersionDisplay = if ($game.LocalVersion) { "Version: " + $game.LocalVersion } else { "Version: Unknown" }
$gameTypeDisplay = "Type: $gameType"
# Start building the line
$line = "$displayNumber".PadRight(3) + "$gameName".PadRight($nameWidth + 2)
# Display game number, name, ID, and size normally
Write-CCLSHost $line -ForegroundColor $gameColor -NoNewline -Log
Write-CCLSHost $gameIdDisplay.PadRight($idWidth + 2) -ForegroundColor White -NoNewline -Log
Write-CCLSHost $gameSizeDisplay.PadRight($localSizeWidth + 2) -ForegroundColor White -NoNewline -Log
# Handle version with special coloring for outdated games
if ($game.IsOutdated) {
$outdatedVersionDisplay = $gameVersionDisplay + " (OUTDATED)"
Write-CCLSHost $outdatedVersionDisplay.PadRight($localVersionWidth + 2) -ForegroundColor Red -NoNewline -Log
} else {
if ($game.LocalVersion -eq "Unknown") {
Write-CCLSHost $gameVersionDisplay.PadRight($localVersionWidth + 2) -ForegroundColor Gray -NoNewline -Log
} else {
Write-CCLSHost $gameVersionDisplay.PadRight($localVersionWidth + 2) -ForegroundColor Green -NoNewline -Log
}
}
# Display type
Write-CCLSHost $gameTypeDisplay -ForegroundColor $gameColor -Log
}
# Show command help
Write-CCLSHost "`nCommands:" -ForegroundColor Yellow -Log
Write-CCLSHost " view [number] - View detailed information about a game" -ForegroundColor Cyan -Log
Write-CCLSHost " del [number] - Delete an installed game" -ForegroundColor Cyan -Log
Write-CCLSHost " update [number] - Check for updates and update if available" -ForegroundColor Cyan -Log
Write-CCLSHost " refresh [number] - Refresh online info for a specific game" -ForegroundColor Cyan -Log
Write-CCLSHost " [number] - Same as 'view [number]'" -ForegroundColor Cyan -Log
Write-CCLSHost " Press Enter to return to main menu" -ForegroundColor Cyan -Log
# Command input loop (rest of Browse-Games remains the same)
while ($true) {
Write-CCLSHost "`nBrowse> " -ForegroundColor Yellow -NoNewline -Log
$userInput = Read-Host
Write-CCLSHost "$userInput" -NoConsole -Log
# Check for empty input (exit)
if ([string]::IsNullOrWhiteSpace($userInput)) {
Write-CCLSHost "Returning to main menu..." -ForegroundColor Green -Log
break
}
# Parse the command
$inputParts = $userInput.Trim().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)
if ($inputParts.Count -eq 0) {
continue
}
$command = $inputParts[0].ToLower()
$gameNumber = $null
# Handle different command formats
if ($inputParts.Count -eq 1) {
# Single input - could be just a number or a command
if ([int]::TryParse($command, [ref]$gameNumber)) {
# It's just a number, treat as 'view'
$command = "view"
} else {
Write-CCLSHost "Invalid command. Please specify a game number (e.g., 'view 1', 'del 1', 'update 1')." -ForegroundColor Red -Log
continue
}
}
elseif ($inputParts.Count -eq 2) {
# Command with number
if (-not [int]::TryParse($inputParts[1], [ref]$gameNumber)) {
Write-CCLSHost "Invalid game number. Please enter a valid number." -ForegroundColor Red -Log
continue
}
}
else {
Write-CCLSHost "Invalid command format. Use: [command] [number]" -ForegroundColor Red -Log
continue
}
# Validate game number
if ($gameNumber -lt 1 -or $gameNumber -gt $sortedGames.Count) {
Write-CCLSHost "Invalid game number. Please enter a number between 1 and $($sortedGames.Count)." -ForegroundColor Red -Log
continue
}
# Get the selected game
$selectedGame = $sortedGames[$gameNumber - 1]
# Execute the command
switch ($command) {
"view" {
Write-CCLSHost "`nViewing details for: $($selectedGame.Name)" -ForegroundColor Green -Log
if ($selectedGame.HasMetadata) {
# Call the existing list function with detailed view using ID
Get-GameInfoByIdentifier -Identifier $selectedGame.Id -SearchType "id" -Detailed
} else {
# Manual installation - show basic info using cache data
Write-CCLSHost "`nGame Information: $($selectedGame.Name)" -ForegroundColor Green -Log
Write-CCLSHost "=======================" -ForegroundColor Green -Log
Write-CCLSHost "Size: $($selectedGame.LocalSize)" -Log
Write-CCLSHost "Location: $($selectedGame.LocalPath)" -Log
Write-CCLSHost "Type: Unknown Installation (No game metadata found)" -Log
Write-CCLSHost "`nNote: This appears to be a manually installed game without CCLS metadata." -ForegroundColor Yellow -Log
Write-CCLSHost "You can view the file structure but cannot update or get detailed info." -ForegroundColor Yellow -Log
}
}
"del" {
Write-CCLSHost "`nDeleting: $($selectedGame.Name)" -ForegroundColor Red -Log
if ($selectedGame.HasMetadata) {
# Use ID-based deletion for games with metadata
Remove-Game -Identifier $selectedGame.Id -SearchType "id"
} else {
# Use folder-based deletion for manual installations
Remove-Game -Identifier $selectedGame.Name -SearchType "gamedir"
}
Write-CCLSHost "`nReturning to browse menu..." -ForegroundColor Cyan -Log
break # Exit the browse loop to refresh the list
}
"update" {
if (-not $selectedGame.HasMetadata) {
Write-CCLSHost "Cannot update '$($selectedGame.Name)' - this appears to be an unknown installation." -ForegroundColor Yellow -Log
Write-CCLSHost "Unknown installations cannot be automatically updated." -ForegroundColor Yellow -Log
continue
}
# Check if credentials are available for online check
if ($null -eq $script:cachedCredentials) {
Write-CCLSHost "Cannot check for updates - not logged in." -ForegroundColor Red -Log
continue
}
Write-CCLSHost "`nChecking for updates for: $($selectedGame.Name) ($($selectedGame.Id))" -ForegroundColor Cyan -Log
# Use the existing search function to get online version
try {
$infoParams = @{
Uri = "$cliApiUrl/search.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
username = $script:cachedCredentials.Username
password = $script:cachedCredentials.Password
id = $selectedGame.Id
}
}
$onlineGameInfo = Invoke-RestMethod @infoParams
if ($onlineGameInfo.success) {
$onlineVersion = if ($onlineGameInfo.version -and $onlineGameInfo.version -ne "") { $onlineGameInfo.version } else { "Unknown" }
Write-CCLSHost "Local version: $($selectedGame.LocalVersion)" -ForegroundColor Yellow -Log
Write-CCLSHost "Online version: $onlineVersion" -ForegroundColor Yellow -Log
if ($selectedGame.LocalVersion -ne "Unknown" -and $onlineVersion -ne "Unknown") {
if ($selectedGame.LocalVersion -eq $onlineVersion) {
Write-CCLSHost "Game is already up to date!" -ForegroundColor Green -Log
} else {
Write-CCLSHost "Update available! Starting download..." -ForegroundColor Cyan -Log
# Call the existing get function to update
Get-Game -id $selectedGame.Id
}
} else {
Write-CCLSHost "Cannot determine if update is needed due to unknown version information." -ForegroundColor Yellow -Log
Write-CCLSHost "Would you like to re-download anyway? (Y/N)" -ForegroundColor Yellow -Log
$confirmation = Read-Host
Write-CCLSHost "$confirmation" -NoConsole -Log
if ($confirmation.ToLower() -eq "y") {
Get-Game -id $selectedGame.Id
}
}
} else {
Write-CCLSHost "Error checking for updates: $($onlineGameInfo.message)" -ForegroundColor Red -Log
}
}
catch {
Write-CCLSHost "Error checking for updates: $($_.Exception.Message)" -ForegroundColor Red -Log
}
Write-CCLSHost "`nReturning to browse menu..." -ForegroundColor Cyan -Log
}
"refresh" {
if (-not $selectedGame.HasMetadata) {
Write-CCLSHost "Cannot refresh info for '$($selectedGame.Name)' - this is an unknown installation." -ForegroundColor Yellow -Log
continue
}
# Check if credentials are available
if ($null -eq $script:cachedCredentials) {
Write-CCLSHost "Cannot refresh info - not logged in." -ForegroundColor Red -Log
continue
}
Write-CCLSHost "`nRefreshing info for: $($selectedGame.Name) ($($selectedGame.Id))" -ForegroundColor Cyan -Log
# Call the existing search function to show current online info
Search-Game -id $selectedGame.Id
}
default {
Write-CCLSHost "Unknown command '$command'. Available commands: view, del, update, refresh" -ForegroundColor Red -Log
}
}
}
}
function Get-FullFileTree {
param (
[string]$Path,
[string]$Indent = ""
)
$output = @()
try {
# Get items in the current directory
$items = Get-ChildItem -Path $Path -ErrorAction SilentlyContinue
foreach ($item in $items) {
if ($item.PSIsContainer) {
# It's a directory
$output += "$Indent|- $($item.Name) (folder)"
# Recursively get all subdirectories with no depth limit
$childOutput = Get-FullFileTree -Path $item.FullName -Indent "$Indent| "
if ($childOutput) {
$output += $childOutput
}
}
else {
# It's a file
$sizeFormatted = Format-Size -Size $item.Length
$output += "$Indent|- $($item.Name) ($sizeFormatted)"
}
}
}
catch {
$output += "$Indent Error accessing path: $($_.Exception.Message)"
}
return $output
}
# Modified Remove-Game function to update cache after deletion
function Remove-Game {
param (
[string]$Identifier,
[string]$SearchType = "gamedir",
[switch]$Force
)
# Get settings to find download directory
$settings = Initialize-Settings
$downloadPath = $settings.DownloadPath
$gamePath = $null
$gameDisplayName = $null
# Find the game based on search type
switch ($SearchType.ToLower()) {
"id" {
# Search by game ID (e.g., cg0055)
Write-CCLSHost "#Searching for game with ID: $Identifier" -Log -NoConsole
# Validate ID format
if ($Identifier -notmatch "^(cg|cb)\d{4}$") {
Write-CCLSHost "Invalid ID format. Please use format 'cg0000' for games or 'cb0000' for bundles." -ForegroundColor Red -Log
return
}
# Look for JSON file with this ID in any game folder
$foundFolder = $null
$allFolders = Get-ChildItem -Path $downloadPath -Directory -ErrorAction SilentlyContinue
foreach ($folder in $allFolders) {
$jsonFile = Join-Path -Path $folder.FullName -ChildPath "$Identifier.json"
if (Test-Path $jsonFile) {
$foundFolder = $folder.FullName
$gameDisplayName = $folder.Name
break
}
}
if ($null -eq $foundFolder) {
Write-CCLSHost "Game '$Identifier' not found in library" -ForegroundColor Red -Log
return
}
$gamePath = $foundFolder
}
"game" {
# Search by game name using fuzzy matching (same as get game command)
Write-CCLSHost "#Searching for game with name: $Identifier" -Log -NoConsole
# Use the same game resolution logic as the get command
$resolvedId = Resolve-GameNameToId -GameName $Identifier
if ($null -eq $resolvedId) {
Write-CCLSHost "Game '$Identifier' not found in library" -ForegroundColor Red -Log
return
}
# Now find the folder containing this ID
$allFolders = Get-ChildItem -Path $downloadPath -Directory -ErrorAction SilentlyContinue
$foundFolder = $null
foreach ($folder in $allFolders) {
$jsonFile = Join-Path -Path $folder.FullName -ChildPath "$resolvedId.json"
if (Test-Path $jsonFile) {
$foundFolder = $folder.FullName
$gameDisplayName = $folder.Name
break
}
}
if ($null -eq $foundFolder) {
Write-CCLSHost "Game '$Identifier' not found in library" -ForegroundColor Red -Log
return
}
$gamePath = $foundFolder
}
"gamedir" {
# Search by game folder name (original behavior)
Write-CCLSHost "#Searching for game folder: $Identifier" -Log -NoConsole
$gamePath = Join-Path -Path $downloadPath -ChildPath $Identifier
$gameDisplayName = $Identifier
# Check if game folder exists
if (-not (Test-Path $gamePath)) {
Write-CCLSHost "Game '$Identifier' not found in library" -ForegroundColor Red -Log
return
}
}
default {
Write-CCLSHost "Invalid search type specified." -ForegroundColor Red -Log
return
}
}
# Get game size for informational purposes (from cache if available, otherwise calculate)
$size = 0
$sizeFormatted = "Unknown"
if ($script:libraryCache) {
$cachedGame = Get-GameFromCache -Identifier $gameDisplayName -SearchType "foldername"
if ($cachedGame) {
$size = $cachedGame.localSize
$sizeFormatted = $cachedGame.localSizeFormatted
}
}
if ($sizeFormatted -eq "Unknown") {
$size = Get-FolderSize -Path $gamePath
$sizeFormatted = Format-Size -Size $size
}
# Try to get additional game info from JSON file if available
$gameInfo = $null
$gameId = $null
$gameName = $null
try {
# Look for any JSON file with game ID pattern
$jsonFiles = Get-ChildItem -Path $gamePath -Filter "*.json" | Where-Object { $_.Name -match "^c[gb]\d{4}\.json$" }
if ($jsonFiles.Count -gt 0) {
$jsonFile = $jsonFiles[0]
$gameId = [System.IO.Path]::GetFileNameWithoutExtension($jsonFile.Name)
$gameInfo = Get-Content -Path $jsonFile.FullName -Raw | ConvertFrom-Json
$gameName = $gameInfo.name
}
} catch {
Write-CCLSHost "#Could not read game info from JSON file" -Log -NoConsole
}
# Provide information about what will be deleted
Write-CCLSHost "`nGame Deletion Confirmation" -ForegroundColor Red -Log
Write-CCLSHost "=========================" -ForegroundColor Red -Log
if ($gameName -and $gameId) {
Write-CCLSHost "Game: $gameName ($gameId)" -Log
} else {
Write-CCLSHost "Game: $gameDisplayName" -Log
}
Write-CCLSHost "Location: $gamePath" -Log
Write-CCLSHost "Size: $sizeFormatted" -Log
# Show additional game info if available
if ($gameInfo) {
if ($gameInfo.version -and $gameInfo.version -ne "") {
Write-CCLSHost "Version: $($gameInfo.version)" -Log
}
if ($gameInfo.size -and $gameInfo.size -ne "") {
Write-CCLSHost "Original Size: $($gameInfo.size)" -Log
}
}
# If not forced, prompt for confirmation
if (-not $Force) {
Write-CCLSHost "`nWARNING: This will permanently delete the game and all its files!" -ForegroundColor Yellow -Log
$displayName = if ($gameName) { $gameName } else { $gameDisplayName }
Write-CCLSHost "Are you sure you want to delete '$displayName'? (Y/N)" -ForegroundColor Yellow -Log
$confirmation = Read-Host
Write-CCLSHost "$confirmation" -NoConsole -Log
if ($confirmation.ToLower() -ne "y") {
Write-CCLSHost "Deletion cancelled." -ForegroundColor Green -Log
return
}
} else {
Write-CCLSHost "#Force deletion enabled, skipping confirmation" -Log -NoConsole
}
# Proceed with deletion
try {
# Use Remove-Item with -Recurse to delete the game folder and all contents
Remove-Item -Path $gamePath -Recurse -Force
$displayName = if ($gameName) { $gameName } else { $gameDisplayName }
Write-CCLSHost "`nGame '$displayName' has been successfully deleted." -ForegroundColor Green -Log
Write-CCLSHost "Freed up $sizeFormatted of disk space." -ForegroundColor Green -Log
# Update library cache after successful deletion
Write-CCLSHost "#Updating library cache after game deletion..." -Log -NoConsole
Update-LibraryCache -Force
}
catch {
Write-CCLSHost "Error deleting game: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
function Test-VersionUpdate {
# Current version - update this when releasing new versions
$currentVersion = "2.1.5" # Updated to match your current version
# Check for oversight system override first
if ($script:oversightData -and $script:oversightData.version_override) {
Write-CCLSHost "#Oversight version override active" -Log -NoConsole
$latestVersion = $script:oversightData.version_override.version
# Create and return result
$result = [PSCustomObject]@{
CurrentVersion = $currentVersion
LatestVersion = $latestVersion
IsLatest = ($currentVersion -eq $latestVersion)
OverrideDownloadUrl = $script:oversightData.version_override.download_link
IsOverride = $true
}
return $result
}
try {
# Get the latest version from the server - Updated for v2.0 API structure
$params = @{
Uri = "$baseUrl/CLI/api/2.0/latest/latest.txt"
Method = "GET"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
}
Write-CCLSHost "#Checking for updates..." -Log -NoConsole
$latestVersion = Invoke-RestMethod @params
# Strip any whitespace or newlines
$latestVersion = $latestVersion.Trim()
# Create and return result without displaying it
$result = [PSCustomObject]@{
CurrentVersion = $currentVersion
LatestVersion = $latestVersion
IsLatest = ($currentVersion -eq $latestVersion)
IsOverride = $false
}
# Use Write-Output to return without console display
return $result
}
catch {
Write-CCLSHost "#Error checking for updates: $($_.Exception.Message)" -Log -NoConsole
# Create and return result without displaying it
$result = [PSCustomObject]@{
CurrentVersion = $currentVersion
LatestVersion = $null
IsLatest = $true # Assume latest if can't check to avoid unnecessary alerts
IsOverride = $false
}
return $result
}
}
function Show-VersionWarningIfNeeded {
param (
[switch]$ForceCheck
)
# Don't show warnings if we're in DevMode
$settings = Initialize-Settings
if ($settings.DevMode) {
return
}
# Only check version if oversight data is available or if forced
if ($script:oversightData -or $ForceCheck) {
$versionInfo = Test-VersionUpdate
if (-not $versionInfo.IsLatest -and $versionInfo.LatestVersion) {
Write-CCLSHost "ALERT, you are running $($versionInfo.CurrentVersion) run 'update' command to update to latest version $($versionInfo.LatestVersion)" -ForegroundColor Red -Log
}
}
}
function Get-OversightData {
param (
[string]$Username,
[string]$CurrentVersion
)
try {
# Set up request parameters for oversight API
$params = @{
Uri = "$baseUrl/CLI/api/oversight/oversight.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
action = "get_oversight_data"
username = $Username
current_version = $CurrentVersion
}
}
Write-CCLSHost "#Checking oversight system..." -Log -NoConsole
# Fetch oversight data from server
$response = Invoke-RestMethod @params
# Check if the request was successful
if ($response.success) {
Write-CCLSHost "#Oversight system data retrieved successfully" -Log -NoConsole
return $response
} else {
Write-CCLSHost "#Oversight system returned error: $($response.error)" -Log -NoConsole
return $null
}
}
catch {
Write-CCLSHost "#Oversight system not available or error occurred: $($_.Exception.Message)" -Log -NoConsole
return $null
}
}
function Convert-HexToConsoleColor {
param (
[string]$HexColor
)
# Remove # if present
$HexColor = $HexColor.TrimStart('#')
# Convert to RGB values
try {
$r = [Convert]::ToInt32($HexColor.Substring(0, 2), 16)
$g = [Convert]::ToInt32($HexColor.Substring(2, 2), 16)
$b = [Convert]::ToInt32($HexColor.Substring(4, 2), 16)
# Map to closest console color (simplified mapping)
if ($r -gt 200 -and $g -lt 100 -and $b -lt 100) { return "Red" }
elseif ($r -lt 100 -and $g -gt 200 -and $b -lt 100) { return "Green" }
elseif ($r -lt 100 -and $g -lt 100 -and $b -gt 200) { return "Blue" }
elseif ($r -gt 200 -and $g -gt 200 -and $b -lt 100) { return "Yellow" }
elseif ($r -gt 200 -and $g -lt 100 -and $b -gt 200) { return "Magenta" }
elseif ($r -lt 100 -and $g -gt 200 -and $b -gt 200) { return "Cyan" }
elseif ($r -gt 150 -and $g -gt 150 -and $b -gt 150) { return "White" }
elseif ($r -lt 100 -and $g -lt 100 -and $b -lt 100) { return "DarkGray" }
else { return "Gray" }
}
catch {
return "White" # Default fallback color
}
}
function Initialize-UserLogging {
param (
[string]$Username
)
if ($script:userLoggingEnabled) {
try {
# Create the session filename
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$script:userLoggingSessionFile = "ccls_session_$timestamp.log"
# Get user ID from oversight response
if ($script:oversightData -and $script:oversightData.user_logging -and $script:oversightData.user_id) {
$script:userLoggingUserId = $script:oversightData.user_id
Write-CCLSHost "#User logging enabled for user: $Username (ID: $($script:userLoggingUserId))" -Log -NoConsole
# Send initial log entry to server
Send-LogToServer -LogEntry "CCLS Games CLI Session started at $(Get-Date) for user $Username (ID: $($script:userLoggingUserId))"
} else {
Write-CCLSHost "#User logging not available - no user ID received" -Log -NoConsole
$script:userLoggingEnabled = $false
}
}
catch {
Write-CCLSHost "#Error setting up user logging: $($_.Exception.Message)" -Log -NoConsole
$script:userLoggingEnabled = $false
}
}
}
function Send-LogToServer {
param (
[string]$LogEntry
)
if (-not $script:userLoggingEnabled -or -not $script:userLoggingUserId -or -not $script:userLoggingSessionFile) {
return
}
try {
$params = @{
Uri = "$baseUrl/CLI/api/oversight/oversight.php"
Method = "POST"
Headers = @{
"User-Agent" = "CCLS-CLI/2.0"
}
Body = @{
action = "log_entry"
user_id = $script:userLoggingUserId
log_entry = $LogEntry
session_file = $script:userLoggingSessionFile
}
}
# Send asynchronously to avoid slowing down the CLI
$null = Start-Job -ScriptBlock {
param($requestParams)
try {
Invoke-RestMethod @requestParams
} catch {
# Silently fail to avoid disrupting the main application
}
} -ArgumentList $params
}
catch {
# Silently fail to avoid disrupting the main application
}
}
function Update-CliTool {
Write-CCLSHost "Checking for updates..." -ForegroundColor Cyan -Log
# Get version information and suppress automatic output
$versionInfo = Test-VersionUpdate
$currentVersion = $versionInfo.CurrentVersion
$latestVersion = $versionInfo.LatestVersion
# Make sure we have valid version information
if ([string]::IsNullOrWhiteSpace($latestVersion)) {
Write-CCLSHost "Error: Couldn't retrieve latest version information." -ForegroundColor Red -Log
Write-CCLSHost "Please check your internet connection and try again later." -ForegroundColor Red -Log
return
}
# Compare versions
if ($versionInfo.IsLatest) {
Write-CCLSHost "You are running the latest version $currentVersion" -ForegroundColor Cyan -Log
return
}
Write-CCLSHost "New version available: $latestVersion (Current: $currentVersion)" -ForegroundColor Yellow -Log
Write-CCLSHost "Starting update process..." -ForegroundColor Cyan -Log
try {
# Determine the current script directory
$scriptLocation = if ($PSScriptRoot) {
# If running from a script, use its location
$PSScriptRoot
} else {
# If running in console, use current directory
(Get-Location).Path
}
# Get the current script path
$scriptPath = Join-Path -Path $scriptLocation -ChildPath "CLI.ps1"
# Set the update path
$updatePath = Join-Path -Path $scriptLocation -ChildPath "update.ps1"
# Create backups directory if it doesn't exist
$backupsFolder = Join-Path -Path $scriptLocation -ChildPath "backups"
if (-not (Test-Path $backupsFolder)) {
New-Item -Path $backupsFolder -ItemType Directory -Force | Out-Null
Write-CCLSHost "#Created backups directory at $backupsFolder" -Log
}
# Check if oversight has a version override
if ($versionInfo.OverrideDownloadUrl) {
$cliDownloadUrl = $versionInfo.OverrideDownloadUrl
Write-CCLSHost "#Using oversight override download URL" -Log -NoConsole
} else {
$cliDownloadUrl = "$baseUrl/CLI/api/2.0/latest/CLI.ps1"
}
Write-CCLSHost "Downloading update to $updatePath..." -ForegroundColor Cyan -Log
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "CCLS-CLI/2.0")
$webClient.DownloadFile($cliDownloadUrl, $updatePath)
Write-CCLSHost "Download completed." -ForegroundColor Green -Log
# Ask for confirmation
Write-CCLSHost "Are you sure you want to update to version $latestVersion? (Y/N)" -ForegroundColor Yellow -Log
$confirmation = Read-Host
Write-CCLSHost "$confirmation" -NoConsole -Log
if ($confirmation.ToLower() -ne "y") {
Write-CCLSHost "Update cancelled." -ForegroundColor Yellow -Log
# Clean up downloaded file
if (Test-Path $updatePath) {
Remove-Item -Path $updatePath -Force
}
return
}
# Create a backup with timestamp in the backups folder
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$backupFileName = "CLI_v$($currentVersion)_$timestamp.ps1.bak"
$backupPath = Join-Path -Path $backupsFolder -ChildPath $backupFileName
Copy-Item -Path $scriptPath -Destination $backupPath -Force
Write-CCLSHost "Created backup at $backupPath" -ForegroundColor Cyan -Log
# Read the new CLI content
$newContent = Get-Content -Path $updatePath -Raw
# Replace the current script with the new content
Set-Content -Path $scriptPath -Value $newContent -Force
# Clean up the update file
Remove-Item -Path $updatePath -Force
Write-CCLSHost "Successfully downloaded version $latestVersion" -ForegroundColor Green -Log
# Log the update completion
if ($script:userLoggingEnabled) {
Send-LogToServer -LogEntry "CLI Tool updated from version $currentVersion to $latestVersion"
}
Write-CCLSHost "Restart the program to apply update." -ForegroundColor Yellow -Log
# Exit the tool to allow the user to restart with the new version
Write-CCLSHost "Press any key to exit..." -ForegroundColor Cyan -Log
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
Exit
}
catch {
Write-CCLSHost "Error during update process: $($_.Exception.Message)" -ForegroundColor Red -Log
Write-CCLSHost "Update failed. Please try again later." -ForegroundColor Red -Log
# Log the update failure
if ($script:userLoggingEnabled) {
Send-LogToServer -LogEntry "CLI Tool update failed: $($_.Exception.Message)"
}
}
}
function Show-Version {
# Get version information using the existing Test-VersionUpdate function
$versionInfo = Test-VersionUpdate
$currentVersion = $versionInfo.CurrentVersion
$latestVersion = $versionInfo.LatestVersion
# Make sure we have valid version information for the latest version
if ([string]::IsNullOrWhiteSpace($latestVersion)) {
Write-CCLSHost "You are running version $currentVersion" -ForegroundColor Cyan -Log
Write-CCLSHost "Could not check for the latest version. Please check your internet connection." -ForegroundColor Yellow -Log
return
}
# Compare versions
if ($versionInfo.IsLatest) {
# Running the latest version
Write-CCLSHost "You are running the latest version $currentVersion" -ForegroundColor Cyan -Log
} else {
# Running an outdated version
$overrideText = if ($versionInfo.IsOverride) { " (oversight override)" } else { "" }
Write-CCLSHost "You are running version " -ForegroundColor Red -NoNewline -Log
Write-CCLSHost "$currentVersion" -ForegroundColor Cyan -NoNewline -Log
Write-CCLSHost ", latest version " -ForegroundColor Red -NoNewline -Log
Write-CCLSHost "$latestVersion" -ForegroundColor Cyan -NoNewline -Log
Write-CCLSHost "$overrideText run 'update' to install the latest version." -ForegroundColor Red -Log
}
}
# Function to get all log files sorted by newest to oldest
function Get-LogFiles {
param (
[int]$Limit = -1 # -1 means no limit, show all
)
try {
# Get all log files from the logs folder
$logFiles = Get-ChildItem -Path $logsFolder -Filter "*.log" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending
if ($logFiles.Count -eq 0) {
return @()
}
# Apply limit if specified
if ($Limit -gt 0 -and $Limit -lt $logFiles.Count) {
$logFiles = $logFiles | Select-Object -First $Limit
}
return $logFiles
}
catch {
Write-CCLSHost "Error retrieving log files: $($_.Exception.Message)" -ForegroundColor Red -Log
return @()
}
}
# Function to display log file contents
function Show-LogFile {
param (
[string]$LogFileName
)
$logPath = Join-Path -Path $logsFolder -ChildPath $LogFileName
# Check if log file exists
if (-not (Test-Path $logPath)) {
Write-CCLSHost "Log file '$LogFileName' not found." -ForegroundColor Red -Log
return
}
try {
# Read file content line by line to avoid issues with large files
$logContent = Get-Content -Path $logPath -ErrorAction Stop
Write-CCLSHost "`nLog File Contents: $LogFileName" -ForegroundColor Green -Log
Write-CCLSHost "================================" -ForegroundColor Green -Log
# Display content line by line to avoid output issues
foreach ($line in $logContent) {
Write-CCLSHost $line -Log
}
Write-CCLSHost "`n[End of log file]" -ForegroundColor Gray -Log
}
catch {
Write-CCLSHost "Error reading log file: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
# Function to delete a log file
function Remove-LogFile {
param (
[string]$LogFileName,
[switch]$Force
)
$logPath = Join-Path -Path $logsFolder -ChildPath $LogFileName
# Check if log file exists
if (-not (Test-Path $logPath)) {
Write-CCLSHost "Log file '$LogFileName' not found." -ForegroundColor Red -Log
return
}
# Get file info for confirmation
$fileInfo = Get-Item $logPath
$fileSize = Format-Size -Size $fileInfo.Length
$lastModified = $fileInfo.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")
Write-CCLSHost "`nLog File Deletion Confirmation" -ForegroundColor Red -Log
Write-CCLSHost "=============================" -ForegroundColor Red -Log
Write-CCLSHost "File: $LogFileName" -Log
Write-CCLSHost "Size: $fileSize" -Log
Write-CCLSHost "Last Modified: $lastModified" -Log
if (-not $Force) {
Write-CCLSHost "`nAre you sure you want to delete this log file? (Y/N)" -ForegroundColor Yellow -Log
$confirmation = Read-Host
Write-CCLSHost "$confirmation" -NoConsole -Log
if ($confirmation.ToLower() -ne "y") {
Write-CCLSHost "Deletion cancelled." -ForegroundColor Green -Log
return
}
} else {
Write-CCLSHost "#Force deletion enabled, skipping confirmation" -Log -NoConsole
}
try {
Remove-Item -Path $logPath -Force
Write-CCLSHost "`nLog file '$LogFileName' has been successfully deleted." -ForegroundColor Green -Log
}
catch {
Write-CCLSHost "Error deleting log file: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
# Function to open a log file with default system application
function Open-LogFile {
param (
[string]$LogFileName
)
$logPath = Join-Path -Path $logsFolder -ChildPath $LogFileName
# Check if log file exists
if (-not (Test-Path $logPath)) {
Write-CCLSHost "Log file '$LogFileName' not found." -ForegroundColor Red -Log
return
}
try {
Write-CCLSHost "Opening log file '$LogFileName' with default application..." -ForegroundColor Cyan -Log
Start-Process -FilePath $logPath
Write-CCLSHost "Log file opened successfully." -ForegroundColor Green -Log
}
catch {
Write-CCLSHost "Error opening log file: $($_.Exception.Message)" -ForegroundColor Red -Log
Write-CCLSHost "The system may not have a default application associated with .log files." -ForegroundColor Yellow -Log
}
}
# Main log list browser function
function Show-LogBrowser {
param (
[int]$Limit = -1
)
# Get log files
$logFiles = Get-LogFiles -Limit $Limit
if ($logFiles.Count -eq 0) {
Write-CCLSHost "No log files found in $logsFolder" -ForegroundColor Yellow -Log
return
}
# Display header
$limitText = if ($Limit -gt 0) { " (showing latest $Limit)" } else { "" }
Write-CCLSHost "`n==========================================================" -ForegroundColor DarkGray -Log
Write-CCLSHost "Log Files Browser - $($logFiles.Count) files found$limitText" -ForegroundColor Green -Log
Write-CCLSHost "==========================================================" -ForegroundColor DarkGray -Log
# Calculate column widths
$maxNameLength = ($logFiles | ForEach-Object { $_.Name.Length } | Measure-Object -Maximum).Maximum
$nameWidth = [Math]::Max($maxNameLength, 30)
# Display header row
Write-CCLSHost ("#".PadRight(3) +
"File Name".PadRight($nameWidth + 2) +
"Size".PadRight(12) +
"Last Modified".PadRight(20)) -ForegroundColor Cyan -Log
Write-CCLSHost ("-" * 3 + " " +
"-" * ($nameWidth + 1) + " " +
"-" * 11 + " " +
"-" * 19) -ForegroundColor Cyan -Log
# Display each log file
for ($i = 0; $i -lt $logFiles.Count; $i++) {
$logFile = $logFiles[$i]
$displayNumber = $i + 1
$fileSize = Format-Size -Size $logFile.Length
$lastModified = $logFile.LastWriteTime.ToString("yyyy-MM-dd HH:mm")
# Format the line
$line = "$displayNumber".PadRight(3) +
"$($logFile.Name)".PadRight($nameWidth + 2) +
"$fileSize".PadRight(12) +
$lastModified
# Color code based on age (newer files are greener)
$age = (Get-Date) - $logFile.LastWriteTime
if ($age.TotalDays -lt 1) {
$color = "Green"
} elseif ($age.TotalDays -lt 7) {
$color = "Yellow"
} else {
$color = "White"
}
Write-CCLSHost $line -ForegroundColor $color -Log
}
# Show command help
Write-CCLSHost "`nCommands:" -ForegroundColor Yellow -Log
Write-CCLSHost " view [number/filename] - View contents of a log file" -ForegroundColor Cyan -Log
Write-CCLSHost " del [number/filename] - Delete a log file" -ForegroundColor Cyan -Log
Write-CCLSHost " open [number/filename] - Open log file with default application" -ForegroundColor Cyan -Log
Write-CCLSHost " [number] - Same as 'view [number]'" -ForegroundColor Cyan -Log
Write-CCLSHost " Press Enter to return to main menu" -ForegroundColor Cyan -Log
# Command input loop
while ($true) {
Write-CCLSHost "`nLogs> " -ForegroundColor Yellow -NoNewline -Log
$userInput = Read-Host
Write-CCLSHost "$userInput" -NoConsole -Log
# Check for empty input (exit)
if ([string]::IsNullOrWhiteSpace($userInput)) {
Write-CCLSHost "Returning to main menu..." -ForegroundColor Green -Log
break
}
# Parse the command
$inputParts = $userInput.Trim().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)
if ($inputParts.Count -eq 0) {
continue
}
$command = $inputParts[0].ToLower()
$fileIdentifier = $null
# Handle different command formats
if ($inputParts.Count -eq 1) {
# Single input - could be just a number or a command
$fileNumber = 0
if ([int]::TryParse($command, [ref]$fileNumber)) {
# It's just a number, treat as 'view'
$fileIdentifier = $fileNumber
$command = "view"
} else {
Write-CCLSHost "Invalid command. Please specify a file number or filename (e.g., 'view 1', 'del mylog.log', 'open 3')." -ForegroundColor Red -Log
continue
}
}
elseif ($inputParts.Count -eq 2) {
# Command with identifier
$fileIdentifier = $inputParts[1]
}
else {
Write-CCLSHost "Invalid command format. Use: [command] [number/filename]" -ForegroundColor Red -Log
continue
}
# Resolve file identifier to actual log file
$selectedLogFile = $null
$fileNumber = 0
if ([int]::TryParse($fileIdentifier, [ref]$fileNumber)) {
# It's a number
if ($fileNumber -lt 1 -or $fileNumber -gt $logFiles.Count) {
Write-CCLSHost "Invalid file number. Please enter a number between 1 and $($logFiles.Count)." -ForegroundColor Red -Log
continue
}
$selectedLogFile = $logFiles[$fileNumber - 1]
} else {
# It's a filename
$selectedLogFile = $logFiles | Where-Object { $_.Name -eq $fileIdentifier }
if (-not $selectedLogFile) {
Write-CCLSHost "Log file '$fileIdentifier' not found in the current list." -ForegroundColor Red -Log
continue
}
}
# Execute the command
switch ($command) {
"view" {
Write-CCLSHost "`nViewing log file: $($selectedLogFile.Name)" -ForegroundColor Green -Log
Show-LogFile -LogFileName $selectedLogFile.Name
}
"del" {
Write-CCLSHost "`nDeleting log file: $($selectedLogFile.Name)" -ForegroundColor Red -Log
Remove-LogFile -LogFileName $selectedLogFile.Name
# Refresh the log files list after deletion
Write-CCLSHost "`nRefreshing log list..." -ForegroundColor Cyan -Log
$logFiles = Get-LogFiles -Limit $Limit
if ($logFiles.Count -eq 0) {
Write-CCLSHost "No log files remaining. Returning to main menu..." -ForegroundColor Yellow -Log
break
}
}
"open" {
Write-CCLSHost "`nOpening log file: $($selectedLogFile.Name)" -ForegroundColor Cyan -Log
Open-LogFile -LogFileName $selectedLogFile.Name
}
default {
Write-CCLSHost "Unknown command '$command'. Available commands: view, del, open" -ForegroundColor Red -Log
}
}
}
}
# Function to display a specific version's changelog
function Show-Changelog {
param (
[string]$Version
)
# Handle special cases first
if ($Version -eq "list") {
# List all available changelogs using the dedicated endpoint
try {
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "CCLS-CLI/2.0")
$listUrl = "$baseUrl/CLI/api/2.0/changelogs_list.php"
$response = $webClient.DownloadString($listUrl)
# Parse the JSON response
$changelogVersions = $response | ConvertFrom-Json
Write-CCLSHost "Available Changelogs:" -ForegroundColor Green -Log
Write-CCLSHost "-------------------" -ForegroundColor Green -Log
if ($changelogVersions.Count -eq 0) {
Write-CCLSHost "No changelogs available." -ForegroundColor Yellow -Log
} else {
foreach ($version in $changelogVersions) {
Write-CCLSHost " $version" -Log
}
}
Write-CCLSHost "`nUse 'changelog [version]' to view a specific changelog" -ForegroundColor Cyan -Log
}
catch {
Write-CCLSHost "Error retrieving changelog list" -ForegroundColor Red -Log
}
return
}
elseif ($Version -eq "latest") {
# Show the latest version's changelog
try {
# Use existing Test-VersionUpdate function to get latest version
$versionInfo = Test-VersionUpdate
$latestVersion = $versionInfo.LatestVersion
if ([string]::IsNullOrWhiteSpace($latestVersion)) {
Write-CCLSHost "Error: Unable to determine latest version." -ForegroundColor Red -Log
return
}
# Now get that version's changelog
Show-Changelog -Version $latestVersion
}
catch {
Write-CCLSHost "Error retrieving latest version information" -ForegroundColor Red -Log
}
return
}
# For a specific version, first check if it exists in the available versions
try {
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "CCLS-CLI/2.0")
# Get the list of available versions first - Updated for v2.0 API structure
$listUrl = "$baseUrl/CLI/api/2.0/changelogs_list.php"
$response = $webClient.DownloadString($listUrl)
$availableVersions = $response | ConvertFrom-Json
# Check if the requested version is available
if ($availableVersions -contains $Version) {
# Version exists, fetch and display the changelog - Updated for v2.0 API structure
$changelogUrl = "https://games.ccls.icu/CLI/changelogs/$Version.txt"
$changelogContent = $webClient.DownloadString($changelogUrl)
Write-CCLSHost "Changelog for Version $Version" -ForegroundColor Green -Log
Write-CCLSHost "------------------------" -ForegroundColor Green -Log
Write-CCLSHost $changelogContent -Log
} else {
# Version doesn't exist in the available versions
Write-CCLSHost "Unknown version. Type 'changelog list' for a list of all versions." -ForegroundColor Red -Log
}
}
catch {
Write-CCLSHost "Unknown version. Type 'changelog list' for a list of all versions." -ForegroundColor Red -Log
}
}
function Refresh-LibraryCache {
Write-CCLSHost "Manually refreshing library cache..." -ForegroundColor Cyan -Log
try {
Update-LibraryCache -Force
Write-CCLSHost "Library cache refreshed successfully!" -ForegroundColor Green -Log
if ($script:libraryCache) {
Write-CCLSHost "Total games: $($script:libraryCache.totalGames)" -ForegroundColor Cyan -Log
Write-CCLSHost "Total size: $($script:libraryCache.totalSizeFormatted)" -ForegroundColor Cyan -Log
Write-CCLSHost "Last updated: $($script:libraryCache.lastUpdated)" -ForegroundColor Cyan -Log
}
} catch {
Write-CCLSHost "Error refreshing library cache: $($_.Exception.Message)" -ForegroundColor Red -Log
}
}
# Fixed Start-CommandInterface function (complete replacement)
function Start-CommandInterface($username) {
$running = $true
while ($running) {
Write-CCLSHost "CCLS>" -ForegroundColor Yellow -NoNewline -Log
$command = Read-Host
Write-CCLSHost "$command" -NoConsole -Log
# Check for empty input - if empty, just continue to next prompt
if ([string]::IsNullOrWhiteSpace($command)) {
continue
}
# Flag to track if a help command was processed
$helpProcessed = $false
# Flag to track if a base command was processed
$baseCommandProcessed = $false
# Process help commands first
switch -Regex ($command.ToLower()) {
# Add comprehensive help command
"^help\s+(-list-all|-a)$" {
Write-CCLSHost "CCLS Games CLI - Complete Command Reference" -ForegroundColor Green -Log
Write-CCLSHost "=======================================" -ForegroundColor Green -Log
Write-CCLSHost "`nBASIC COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "-------------" -ForegroundColor Yellow -Log
Write-CCLSHost "help - Display basic help information" -Log
Write-CCLSHost "help -list-all, help -a - Display this complete command reference" -Log
Write-CCLSHost "clear, cls - Clear the console screen" -Log
Write-CCLSHost "exit, quit - Exit the application" -Log
Write-CCLSHost "logout - Log out and exit" -Log
Write-CCLSHost "forget - Remove stored credentials" -Log
Write-CCLSHost "`nSYSTEM COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "--------------" -ForegroundColor Yellow -Log
Write-CCLSHost "setup - Configure download directories" -Log
Write-CCLSHost "check - Check system requirements" -Log
Write-CCLSHost "version - Display current version information" -Log
Write-CCLSHost "update - Update the CLI tool to the latest version" -Log
Write-CCLSHost "`nINSTALL COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "---------------" -ForegroundColor Yellow -Log
Write-CCLSHost "install help - Show install command help" -Log
Write-CCLSHost "install 7zip - Install 7-Zip utility for extraction" -Log
Write-CCLSHost "install python - Download and install Python" -Log
Write-CCLSHost "install requests - Install Python requests library" -Log
Write-CCLSHost "`nSEARCH COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "--------------" -ForegroundColor Yellow -Log
Write-CCLSHost "search help - Show search command help" -Log
Write-CCLSHost "search [id] - Search for game or bundle information by ID" -Log
Write-CCLSHost "search game [game] - Search for games matching your search term" -Log
Write-CCLSHost "search library, search lib - List all available games and bundles" -Log
Write-CCLSHost "`nDOWNLOAD COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "----------------" -ForegroundColor Yellow -Log
Write-CCLSHost "get help - Show get command help" -Log
Write-CCLSHost "get [id] - Download and install a game or bundle by ID" -Log
Write-CCLSHost "get game [name] - Download and install a game by name" -Log
Write-CCLSHost "get [id] -y - Download game by ID without confirmation prompt" -Log
Write-CCLSHost "get game [name] -y - Download game by name without confirmation" -Log
Write-CCLSHost "`nLIST COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "------------" -ForegroundColor Yellow -Log
Write-CCLSHost "list help - Show list command help" -Log
Write-CCLSHost "list all - List all installed games" -Log
Write-CCLSHost "list all -d - List all installed games with details" -Log
Write-CCLSHost "list [id] - Show info about a specific game by ID" -Log
Write-CCLSHost "list [folder name] - Show info about a specific game by folder name" -Log
Write-CCLSHost "list game [name] - Show info about a specific game by name" -Log
Write-CCLSHost "list gamedir [folder name] - Show info about a specific game by folder name" -Log
Write-CCLSHost "list [id] -d - Show detailed info about a specific game by ID" -Log
Write-CCLSHost "list [folder name] -d - Show detailed info about a specific game by folder name" -Log
Write-CCLSHost "list game [name] -d - Show detailed info about a specific game by name" -Log
Write-CCLSHost "list gamedir [folder name] -d - Show detailed info about a specific game by folder name" -Log
Write-CCLSHost "list [id] -tree - View full file tree of a specific game by ID" -Log
Write-CCLSHost "list [folder name] -tree - View full file tree of a specific game by folder name" -Log
Write-CCLSHost "list game [name] -tree - View full file tree of a specific game by name" -Log
Write-CCLSHost "list gamedir [folder name] -tree - View full file tree of a specific game by folder name" -Log
Write-CCLSHost "list [id] -tree -d - View full file tree and detailed info of a specific game by ID" -Log
Write-CCLSHost "list [folder name] -tree -d - View full file tree and detailed info of a specific game by folder name" -Log
Write-CCLSHost "list game [name] -tree -d - View full file tree and detailed info of a specific game by name" -Log
Write-CCLSHost "list gamedir [folder name] -tree -d - View full file tree and detailed info of a specific game by folder name" -Log
Write-CCLSHost "`nDELETE COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "--------------" -ForegroundColor Yellow -Log
Write-CCLSHost "del help - Show del command help" -Log
Write-CCLSHost "del [folder name] - Delete a game by folder name (with confirmation)" -Log
Write-CCLSHost "del [id] - Delete a game by id (with confirmation)" -Log
Write-CCLSHost "del game [name] - Delete a game by name (with confirmation)" -Log
Write-CCLSHost "del gamedir [folder name] - Delete a game by folder name (with confirmation)" -Log
Write-CCLSHost "del [folder name] -y - Delete a game by folder name (without confirmation)" -Log
Write-CCLSHost "del [id] -y - Delete a game by id (without confirmation)" -Log
Write-CCLSHost "del game [name] -y - Delete a game by name (without confirmation)" -Log
Write-CCLSHost "del gamedir [folder name] -y - Delete a game by folder name (without confirmation)" -Log
Write-CCLSHost "`nCHANGELOG COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "---------------" -ForegroundColor Yellow -Log
Write-CCLSHost "changelog help - Show changelog command help" -Log
Write-CCLSHost "changelog [version] - Display changelog for a specific version" -Log
Write-CCLSHost "changelog list - Show list of all available changelogs" -Log
Write-CCLSHost "changelog latest - Display changelog for the latest version" -Log
Write-CCLSHost "`nBROWSE COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "--------------" -ForegroundColor Yellow -Log
Write-CCLSHost "browse - Interactive browser for installed games with detailed info" -Log
Write-CCLSHost "browse help - Show browse command help" -Log
Write-CCLSHost "`nLOG COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "------------" -ForegroundColor Yellow -Log
Write-CCLSHost "log help - Show log command help" -Log
Write-CCLSHost "log list - Browse all log files interactively" -Log
Write-CCLSHost "log list -a, log list -all - Browse all log files interactively" -Log
Write-CCLSHost "log list -[number] - Show only specified number of newest log files" -Log
Write-CCLSHost "log view [filename] - View contents of a specific log file" -Log
Write-CCLSHost "log del [filename] - Delete a log file (with confirmation)" -Log
Write-CCLSHost "log del [filename] -y - Delete a log file (without confirmation)" -Log
Write-CCLSHost "log open [filename] - Open log file with default application" -Log
Write-CCLSHost "`nSYSTEM COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "---------------" -ForegroundColor Yellow -Log
Write-CCLSHost "sys help - Show system help" -Log
Write-CCLSHost "`nSYSTEM LIBRARY CACHE COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "---------------" -ForegroundColor Yellow -Log
Write-CCLSHost "sys cache help - Show cache system help" -Log
Write-CCLSHost "sys cache refresh - Manually refresh the library cache" -Log
Write-CCLSHost "sys cache status - Show status of library cache" -Log
Write-CCLSHost "`nSYSTEM STARTUP MESSAGE COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "------------------------" -ForegroundColor Yellow -Log
Write-CCLSHost "sys start-message help - Show startup message system help" -Log
Write-CCLSHost "sys start-message list - List available templates from server" -Log
Write-CCLSHost "sys start-message get [name] - Download a template" -Log
Write-CCLSHost "sys start-message enable [name] - Enable a downloaded template" -Log
Write-CCLSHost "sys start-message get,enable [name] - Download and enable a template" -Log
Write-CCLSHost "sys start-message try [name] - Preview a template without enabling" -Log
Write-CCLSHost "sys start-message view - View current startup message" -Log
Write-CCLSHost "sys start-message default - Disable startup message (use default)" -Log
Write-CCLSHost "sys start-message del [name] - Delete a specific template" -Log
Write-CCLSHost "sys start-message wipe - Delete all downloaded templates" -Log
Write-CCLSHost "`nDEVELOPER MODE COMMANDS" -ForegroundColor Yellow -Log
Write-CCLSHost "---------------" -ForegroundColor Yellow -Log
Write-CCLSHost "devmode help - Show devmode command help (and information on usage of devmode)" -Log
Write-CCLSHost "devmode - Activates and Deactivates Developer mode" -Log
$helpProcessed = $true
}
# Main help command
"^help$" {
Write-CCLSHost "CCLS Games CLI - Help Overview" -ForegroundColor Green -Log
Write-CCLSHost "============================" -ForegroundColor Green -Log
Write-CCLSHost "`nBasic Commands:" -ForegroundColor Cyan -Log
Write-CCLSHost " help - Show this help message" -Log
Write-CCLSHost " help -list-all, help -a - Show all available commands in one big array" -Log
Write-CCLSHost " clear, cls - Clear the console screen" -Log
Write-CCLSHost " setup - Configure download directories" -Log
Write-CCLSHost " check - Check system requirements (Python, requests, 7-Zip)" -Log
Write-CCLSHost " version - Display the current version and check for updates" -Log
Write-CCLSHost " update - Update the CLI tool to the latest version" -Log
Write-CCLSHost " exit, quit - Exit the application" -Log
Write-CCLSHost " logout - Log out and exit" -Log
Write-CCLSHost " forget - Remove stored credentials" -Log
Write-CCLSHost "`nGame Management:" -ForegroundColor Cyan -Log
Write-CCLSHost " browse - Interactive browser for installed games showing versions and status" -Log
Write-CCLSHost " search [cg0000/cb0000] - Search for game/bundle information" -Log
Write-CCLSHost " search lib - List all available games and bundles" -Log
Write-CCLSHost " get [cg0000/cb0000] - Download and install a game/bundle by ID" -Log
Write-CCLSHost " get game [name] - Download and install a game by name" -Log
Write-CCLSHost " list all - List installed games (use -d for details)" -Log
Write-CCLSHost " list game [name] - Show info about a specific game" -Log
Write-CCLSHost " del game [name] - Delete an installed game" -Log
Write-CCLSHost " changelog [version] - Display changelog for a specific version" -Log
Write-CCLSHost " log list - Browse and manage log files" -Log
Write-CCLSHost " install [utility] - Install specified utility" -Log
Write-CCLSHost "`nFor detailed help on specific commands, type:" -ForegroundColor Yellow -Log
Write-CCLSHost " [command] help - e.g., 'search help' or 'get help'" -Log
$helpProcessed = $true
}
"^sys\s+help$" {
Write-CCLSHost "CCLS Games CLI - System Command Help" -ForegroundColor Green -Log
Write-CCLSHost "================================================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " sys cache help - Show system cache message system help" -Log
Write-CCLSHost " sys start message help - Show system startup message system help" -Log
Write-CCLSHost "`nDescription:" -ForegroundColor Cyan -Log
Write-CCLSHost " These are just the the help commands for each system command." -Log
Write-CCLSHost " For more info on a specific system command run specified help command." -Log
$helpProcessed = $true
}
"^sys\s+cache\s+help$" {
Write-CCLSHost "CCLS Games CLI - System Library Cache Command Help" -ForegroundColor Green -Log
Write-CCLSHost "================================================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " sys cache refresh - Manually refresh the library cache" -Log
Write-CCLSHost " sys cache status - Show detailed cache status and information" -Log
Write-CCLSHost "`nDescription:" -ForegroundColor Cyan -Log
Write-CCLSHost " This command manually rebuilds the library cache (lib.json) by scanning" -Log
Write-CCLSHost " all installed games and calculating their sizes and metadata. This is" -Log
Write-CCLSHost " useful if the cache becomes corrupted or out of sync." -Log
Write-CCLSHost "`n The cache-status command shows detailed information about the current" -Log
Write-CCLSHost " state of the library cache including validity, file size, and integrity." -Log
Write-CCLSHost "`nWhen to use:" -ForegroundColor Cyan -Log
Write-CCLSHost " - After manually copying/moving game folders" -Log
Write-CCLSHost " - If game listings show incorrect information" -Log
Write-CCLSHost " - If the cache file becomes corrupted" -Log
Write-CCLSHost " - After manually editing game JSON files" -Log
Write-CCLSHost " - To troubleshoot cache-related issues (use 'sys cache status')" -Log
Write-CCLSHost "`nExamples:" -ForegroundColor Cyan -Log
Write-CCLSHost " sys cache refresh - Rebuild the library cache" -Log
Write-CCLSHost " sys cache status - Check current cache status and health" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - This process may take some time for large game libraries" -Log
Write-CCLSHost " - The cache is automatically updated when games are installed or deleted" -Log
Write-CCLSHost " - This command forces a complete rescan regardless of cache validity" -Log
Write-CCLSHost " - Use 'sys cache status' to diagnose cache issues before refreshing" -Log
$helpProcessed = $true
}
# Sub-help commands with standardized formatting
"^search\s+help$" {
Write-CCLSHost "CCLS Games CLI - Search Command Help" -ForegroundColor Green -Log
Write-CCLSHost "=================================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " search [id] - Search for a specific game or bundle" -Log
Write-CCLSHost " search game [term] - Search for games matching your search term" -Log
Write-CCLSHost " search library - List all available games and bundles" -Log
Write-CCLSHost " search lib - List all available games and bundles" -Log
Write-CCLSHost "`nParameters:" -ForegroundColor Cyan -Log
Write-CCLSHost " [id] - Game ID (cg0000) or Bundle ID (cb0000)" -Log
Write-CCLSHost " [term] - Used to search games and bundles matching specified term" -Log
Write-CCLSHost "`nExamples:" -ForegroundColor Cyan -Log
Write-CCLSHost " search cg0025 - Get information about game with ID cg0025" -Log
Write-CCLSHost " search cb0010 - Get information about bundle with ID cb0010" -Log
Write-CCLSHost " search game assasins - Get a list of games matching 'assasins'" -Log
Write-CCLSHost " search library - Display the complete game and bundle library" -Log
Write-CCLSHost " search lib - Display the complete game and bundle library" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - Use 'search game [term]' for a list of games matching your search term:" -Log
Write-CCLSHost " - When in the selectiong field of 'search game [term]' you can enter 'get [number]' to download the game." -Log
Write-CCLSHost " - Game/Bundle IDs can be found in the URL on the website:" -Log
Write-CCLSHost " https://games.ccls.icu/game.php?id=cg0000" -Log
Write-CCLSHost " https://games.ccls.icu/bundle.php?id=cb0000" -Log
Write-CCLSHost " - You can also find IDs by using the 'search library' command" -Log
$helpProcessed = $true
}
"^get\s+help$" {
Write-CCLSHost "CCLS Games CLI - Get Command Help" -ForegroundColor Green -Log
Write-CCLSHost "==============================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " get [id] [options] - Download and install a game or bundle by ID" -Log
Write-CCLSHost " get game [name] [options] - Download and install a game by name" -Log
Write-CCLSHost "`nParameters:" -ForegroundColor Cyan -Log
Write-CCLSHost " [id] - Game ID (cg0000) or Bundle ID (cb0000)" -Log
Write-CCLSHost " [name] - Game name (case-insensitive, supports partial matching)" -Log
Write-CCLSHost "`nOptions:" -ForegroundColor Cyan -Log
Write-CCLSHost " -y - Skip confirmation prompts (auto-confirm)" -Log
Write-CCLSHost "`nExamples:" -ForegroundColor Cyan -Log
Write-CCLSHost " get cg0025 - Download and install game with ID cg0025" -Log
Write-CCLSHost " get cb0010 -y - Download and install bundle with ID cb0010 without prompts" -Log
Write-CCLSHost " get game tekken 8 - Download and install TEKKEN 8 by name" -Log
Write-CCLSHost " get game plateup -y - Download and install PlateUp! by name without prompts" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - Game/Bundle IDs can be found using the 'search library' command" -Log
Write-CCLSHost " - Game names are case-insensitive and support partial matching" -Log
Write-CCLSHost " - The command will download, extract, and save game information" -Log
Write-CCLSHost " - Downloads can be stopped with Ctrl+Z" -Log
$helpProcessed = $true
}
"^list\s+help$" {
Write-CCLSHost "CCLS Games CLI - List Command Help" -ForegroundColor Green -Log
Write-CCLSHost "===============================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " list all [options] - List all installed games" -Log
Write-CCLSHost " list [folder name] [options] - Display information about a specific game using game folder name" -Log
Write-CCLSHost " list [id] [options] - Display information about a specific game using game ID" -Log
Write-CCLSHost " list game [name] [options] - Display information about a specific game using game name" -Log
Write-CCLSHost " list gamedir [folder name] [options] - Display information about a specific game using game folder name" -Log
Write-CCLSHost "`nParameters:" -ForegroundColor Cyan -Log
Write-CCLSHost " [id] - Game ID (cg0000) or Bundle ID (cb0000)" -Log
Write-CCLSHost " [name] - Game name (case-insensitive, supports partial matching)" -Log
Write-CCLSHost " [folder name] - Game folder name (case-insensitive)" -Log
Write-CCLSHost "`nOptions:" -ForegroundColor Cyan -Log
Write-CCLSHost " -d - View extended details (Description, etc) (works on all list commands)" -Log
Write-CCLSHost " -tree - View full file tree of a game (works only on game specific list commands)" -Log
Write-CCLSHost " -tree -d, -d -tree - View full file tree and detailed info of a game (Description, etc) (works only on game specific list commands)" -Log
Write-CCLSHost "`nExamples:" -ForegroundColor Cyan -Log
Write-CCLSHost " list the long drive - Display information about The Long Drive" -Log
Write-CCLSHost " list the long drive -d - Display extended information about The Long Drive" -Log
Write-CCLSHost " list the long drive -tree - Display full file tree of The Long Drive" -Log
Write-CCLSHost " list the long drive -tree -d - Display full file tree and extended information of The Long Drive" -Log
Write-CCLSHost " list cg0023 - Display information about The Long Drive using ID" -Log
Write-CCLSHost " list game the long drive - Display information about The Long Drive using name" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - Using 'list all -d' displays current version, size and checks for new updates on all downloaded games" -Log
Write-CCLSHost " - Using 'list [term]' only works if name of the game folder or game ID is specified" -Log
Write-CCLSHost " - Using 'list game [name]' or 'list [id]' can be useful as it works even if the game's folder name has been changed" -Log
$helpProcessed = $true
}
"^del\s+help$" {
Write-CCLSHost "CCLS Games CLI - Delete Command Help" -ForegroundColor Green -Log
Write-CCLSHost "=================================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " del [folder name] [options] - Delete an installed game using game folder name" -Log
Write-CCLSHost " del [id] [options] - Delete an installed game using game ID" -Log
Write-CCLSHost " del game [name] [options] - Delete an installed game using game name" -Log
Write-CCLSHost " del gamedir [folder name] [options] - Delete an installed game using game folder name" -Log
Write-CCLSHost "`nParameters:" -ForegroundColor Cyan -Log
Write-CCLSHost " [name] - Name of the installed game to delete" -Log
Write-CCLSHost " [folder name] - Folder name of the installed game to delete" -Log
Write-CCLSHost " [id] - ID of the installed game to delete" -Log
Write-CCLSHost "`nOptions:" -ForegroundColor Cyan -Log
Write-CCLSHost " -y - Skip confirmation prompt (auto-confirm) (works on all del command)" -Log
Write-CCLSHost "`nExamples:" -ForegroundColor Cyan -Log
Write-CCLSHost " del The Long Drive - Delete The Long Drive (with confirmation)" -Log
Write-CCLSHost " del cg0023 - Delete The Long Drive (cg0023) (with confirmation)" -Log
Write-CCLSHost " del The Long Drive -y - Delete The Long Drive without confirmation" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - If using just 'del [term]' command 'term' must match game folder or id (non-case-sensitive)" -Log
Write-CCLSHost " - It's recommended to never use '-y' if you aren't 100% certain about game ID or game name" -Log
Write-CCLSHost " - This operation permanently deletes the game files" -Log
Write-CCLSHost " - You can always re-download deleted games with the 'get' command" -Log
$helpProcessed = $true
}
# Updated help for install command
"^install\s+help$" {
Write-CCLSHost "CCLS Games CLI - Install Command Help" -ForegroundColor Green -Log
Write-CCLSHost "==================================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " install [utility] - Install required utilities" -Log
Write-CCLSHost "`nParameters:" -ForegroundColor Cyan -Log
Write-CCLSHost " [utility] - Name of the utility to install" -Log
Write-CCLSHost "`nSupported Utilities:" -ForegroundColor Cyan -Log
Write-CCLSHost " 7zip - 7-Zip for extracting downloaded files" -Log
Write-CCLSHost " python - Python interpreter (latest version)" -Log
Write-CCLSHost " requests - Python requests library for better downloads" -Log
Write-CCLSHost "`nExamples:" -ForegroundColor Cyan -Log
Write-CCLSHost " install 7zip - Install 7-Zip for extraction" -Log
Write-CCLSHost " install python - Download and install Python" -Log
Write-CCLSHost " install requests - Install Python requests library" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - 7-Zip is required for extracting game archives" -Log
Write-CCLSHost " - Python is required for advanced download features" -Log
Write-CCLSHost " - Python requests is required if using Python downloader" -Log
Write-CCLSHost " - Run 'check' to see which utilities need to be installed" -Log
$helpProcessed = $true
}
"^changelog\s+help$" {
Write-CCLSHost "CCLS Games CLI - Changelog Command Help" -ForegroundColor Green -Log
Write-CCLSHost "===================================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " changelog [version] - Display changelog for a specific version" -Log
Write-CCLSHost " changelog list - Show list of all available changelogs" -Log
Write-CCLSHost " changelog latest - Display changelog for the latest version" -Log
Write-CCLSHost "`nParameters:" -ForegroundColor Cyan -Log
Write-CCLSHost " [version] - A valid version of CLI Tool" -Log
Write-CCLSHost "`nExamples:" -ForegroundColor Cyan -Log
Write-CCLSHost " changelog 1.1.4 - Show changes in version 1.1.4" -Log
Write-CCLSHost " changelog list - View all available changelog versions" -Log
Write-CCLSHost " changelog latest - Show the most recent changes" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - Changelogs document the changes, improvements, and bug fixes in each version" -Log
Write-CCLSHost " - The current version is shown with the 'version' command" -Log
$helpProcessed = $true
}
"^devmode\s+help$" {
Write-CCLSHost "CCLS Games CLI - Devmode Command Help" -ForegroundColor Green -Log
Write-CCLSHost "===================================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost "devmode - Activates and Deactivates Developer mode" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - Devmode is a tool which can be used if you want to try the script out" -Log
Write-CCLSHost " - No login or signup is required for devmode" -Log
Write-CCLSHost " - It can be easily started by pressing 'Ctrl+Q' at the login prompt" -Log
Write-CCLSHost " - When pressed type 'devmode' in the username prompt and press Enter" -Log
Write-CCLSHost " - And you are then in devmode, it can also be activated/deactivated by typing 'devmode' in the normal CLI window" -Log
Write-CCLSHost " - NOTE: when activating 'devmode' all previously stored credetials are deleted" -Log
Write-CCLSHost " - NOTE: all commands that call external API's like 'get' and 'search' wont work when in devmode" -Log
$helpProcessed = $true
}
# Add this to your help command processing section:
"^browse\s+help$" {
Write-CCLSHost "CCLS Games CLI - Browse Command Help" -ForegroundColor Green -Log
Write-CCLSHost "=================================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " browse - Open interactive browser for installed games" -Log
Write-CCLSHost "`nDescription:" -ForegroundColor Cyan -Log
Write-CCLSHost " The browse command provides a fast, local-first interface to view and manage" -Log
Write-CCLSHost " all installed games. It shows local information immediately and only connects" -Log
Write-CCLSHost " to the server when you specifically request updates or online info." -Log
Write-CCLSHost "`nBrowser Commands:" -ForegroundColor Cyan -Log
Write-CCLSHost " view [number] - View detailed information about an installed game" -Log
Write-CCLSHost " del [number] - Delete an installed game (with confirmation)" -Log
Write-CCLSHost " update [number] - Check for updates and download if available" -Log
Write-CCLSHost " refresh [number] - Show current online information for a game" -Log
Write-CCLSHost " [number] - Same as 'view [number]'" -Log
Write-CCLSHost " [Enter] - Return to main menu" -Log
Write-CCLSHost "`nInformation Displayed:" -ForegroundColor Cyan -Log
Write-CCLSHost " - Game name (from metadata or folder name)" -Log
Write-CCLSHost " - Game ID (for CCLS games)" -Log
Write-CCLSHost " - Local folder size" -Log
Write-CCLSHost " - Local version (if available)" -Log
Write-CCLSHost " - Installation type" -Log
Write-CCLSHost "`nGame Types:" -ForegroundColor Cyan -Log
Write-CCLSHost " CCLS Game - Game installed via CLI with metadata (Green)" -Log
Write-CCLSHost " Manual Install - Game installed manually without metadata (Gray)" -Log
Write-CCLSHost "`nExamples:" -ForegroundColor Cyan -Log
Write-CCLSHost " browse - Open the installed games browser" -Log
Write-CCLSHost " [In browser] view 2 - View details for installed game #2" -Log
Write-CCLSHost " [In browser] update 3 - Check for updates for game #3" -Log
Write-CCLSHost " [In browser] refresh 1 - Show current online info for game #1" -Log
Write-CCLSHost " [In browser] del 5 - Delete installed game #5" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - Browse loads instantly using only local data" -Log
Write-CCLSHost " - Online checks only happen when you request them (update/refresh)" -Log
Write-CCLSHost " - CCLS games support all features, manual installs support view/delete only" -Log
Write-CCLSHost " - Games with metadata are shown first, then manual installations" -Log
Write-CCLSHost " - All operations use existing game management functions" -Log
$helpProcessed = $true
}
"^log\s+help$" {
Write-CCLSHost "CCLS Games CLI - Log Command Help" -ForegroundColor Green -Log
Write-CCLSHost "==============================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " log list [options] - Browse and manage log files" -Log
Write-CCLSHost " log view [filename] - View contents of a specific log file" -Log
Write-CCLSHost " log del [filename] [options] - Delete a specific log file" -Log
Write-CCLSHost " log open [filename] - Open log file with default application" -Log
Write-CCLSHost "`nList Options:" -ForegroundColor Cyan -Log
Write-CCLSHost " -a, -all - List all log files (default behavior)" -Log
Write-CCLSHost " -[number] - Limit display to specified number of files" -Log
Write-CCLSHost "`nDelete Options:" -ForegroundColor Cyan -Log
Write-CCLSHost " -y - Skip confirmation prompt" -Log
Write-CCLSHost "`nExamples:" -ForegroundColor Cyan -Log
Write-CCLSHost " log list - Browse all log files interactively" -Log
Write-CCLSHost " log list -5 - Show only the 5 newest log files" -Log
Write-CCLSHost " log list -all - Show all log files (same as 'log list')" -Log
Write-CCLSHost " log view session_2024.log - View contents of a specific log file" -Log
Write-CCLSHost " log del old_session.log - Delete a log file with confirmation" -Log
Write-CCLSHost " log del old_session.log -y - Delete a log file without confirmation" -Log
Write-CCLSHost " log open session_2024.log - Open log file in default editor" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - Log files are stored in the 'logs' folder" -Log
Write-CCLSHost " - Files are sorted by modification date (newest first)" -Log
Write-CCLSHost " - Interactive browser allows viewing, deleting, and opening files" -Log
Write-CCLSHost " - Opening files uses the system's default .log file association" -Log
$helpProcessed = $true
}
"^sys\s+start-message\s+help$" {
Write-CCLSHost "CCLS Games CLI - Startup Message System Help" -ForegroundColor Green -Log
Write-CCLSHost "=========================================" -ForegroundColor Green -Log
Write-CCLSHost "`nUsage:" -ForegroundColor Cyan -Log
Write-CCLSHost " sys start-message list - List available templates from server" -Log
Write-CCLSHost " sys start-message get [name] - Download a template" -Log
Write-CCLSHost " sys start-message enable [name] - Enable a downloaded template" -Log
Write-CCLSHost " sys start-message get,enable [name] - Download and enable a template" -Log
Write-CCLSHost " sys start-message try [name] - Preview a template without enabling" -Log
Write-CCLSHost " sys start-message view - View current startup message" -Log
Write-CCLSHost " sys start-message default - Disable startup message (use default)" -Log
Write-CCLSHost " sys start-message del [name] - Delete a specific template" -Log
Write-CCLSHost " sys start-message wipe - Delete all downloaded templates" -Log
Write-CCLSHost "`nExamples:" -ForegroundColor Cyan -Log
Write-CCLSHost " sys start-message list - See all available templates" -Log
Write-CCLSHost " sys start-message get gradient - Download the 'gradient' template" -Log
Write-CCLSHost " sys start-message enable gradient - Enable the 'gradient' template" -Log
Write-CCLSHost " sys start-message get,enable minimal - Download and enable 'minimal' template" -Log
Write-CCLSHost " sys start-message try gradient - Preview the 'gradient' template" -Log
Write-CCLSHost " sys start-message view - Show current startup message" -Log
Write-CCLSHost " sys start-message default - Go back to default system messages" -Log
Write-CCLSHost "`nNotes:" -ForegroundColor Cyan -Log
Write-CCLSHost " - Templates are JSON files that define colorful welcome messages" -Log
Write-CCLSHost " - Only one template can be active at a time" -Log
Write-CCLSHost " - Custom startup messages appear after login, before default messages" -Log
Write-CCLSHost " - Templates are stored in the settings/start-message folder" -Log
Write-CCLSHost " - Use 'try' to preview templates before enabling them" -Log
$helpProcessed = $true
}
}
if ($helpProcessed) {
continue
}
# Handle base commands (commands without parameters)
switch -Regex ($command.ToLower()) {
"^search$" {
Write-CCLSHost "Wrong command usage. Type 'search help' for a list of available commands." -ForegroundColor Red -Log
$baseCommandProcessed = $true
}
"^get$" {
Write-CCLSHost "Wrong command usage. Type 'get help' for a list of available commands." -ForegroundColor Red -Log
$baseCommandProcessed = $true
}
"^list$" {
Write-CCLSHost "Wrong command usage. Type 'list help' for a list of available commands." -ForegroundColor Red -Log
$baseCommandProcessed = $true
}
"^del$" {
Write-CCLSHost "Wrong command usage. Type 'del help' for a list of available commands." -ForegroundColor Red -Log
$baseCommandProcessed = $true
}
"^install$" {
Write-CCLSHost "Wrong command usage. Type 'install help' for a list of available commands." -ForegroundColor Red -Log
$baseCommandProcessed = $true
}
"^changelog$" {
Write-CCLSHost "Wrong command usage. Type 'changelog help' for a list of available options." -ForegroundColor Red -Log
$baseCommandProcessed = $true
}
"^log$" {
Write-CCLSHost "Wrong command usage. Type 'log help' for a list of available commands." -ForegroundColor Red -Log
$baseCommandProcessed = $true
}
"^sys$" {
Write-CCLSHost "Wrong command usage. Type 'sys start-message help' for a list of available commands." -ForegroundColor Red -Log
$baseCommandProcessed = $true
}
}
# If a base command was processed, skip the regular command processing
if ($baseCommandProcessed) {
continue
}
# Regular command processing with parameters
switch -Regex ($command.ToLower()) {
"^devmode$" {
Toggle-DevMode
}
"^browse$" {
Browse-Games
}
"^check$" {
Test-SystemRequirements
}
"^exit$|^quit$" {
$running = $false
Write-CCLSHost "Thank you for using the CCLS Games CLI Tool. Goodbye!" -ForegroundColor Cyan -Log
}
"^clear$|^cls$" {
Clear-ConsoleScreen
}
"^install(?:\s+(.+))?$" {
$utilityName = if ($matches.Count -gt 1) { $matches[1] } else { "" }
Install-Utility -UtilityName $utilityName
}
"^version$" {
Show-Version
}
"^update$" {
Update-CliTool
}
"^setup$" {
Start-Setup
}
"^search\s+(c[gb]\d{4})$" {
$id = $matches[1]
Search-Game -id $id
}
"^search\s+game\s+(.+)$" {
$searchTerm = $matches[1].Trim()
# Pass "game" as the ID and the search term as additional arguments
Search-Game -id "game" $searchTerm
}
"^get\s+(c[gb]\d{4})(?:\s+-y)?$" {
$id = $matches[1]
$skipConfirmation = $command -match "-y$"
Get-Game -id $id -SkipConfirmation:$skipConfirmation
}
"^get\s+game\s+(.+?)(?:\s+-y)?$" {
$gameName = $matches[1].Trim()
$skipConfirmation = $command -match "-y$"
# Pass "game" as the ID and the game name as additional arguments
Get-Game -id "game" -SkipConfirmation:$skipConfirmation $gameName
}
"^search\s+library$|^search\s+lib$|^library$" {
Get-GamesList
}
"^log\s+list(?:\s+(-a|-all|-\d+))?$" {
$option = if ($matches.Count -gt 1 -and $matches[1]) { $matches[1].Trim() } else { "" }
if ($option -eq "-a" -or $option -eq "-all" -or $option -eq "") {
# Show all log files
Show-LogBrowser
} elseif ($option -match "^-(\d+)$") {
# Show limited number of log files
$limit = [int]$matches[1]
if ($limit -gt 0) {
Show-LogBrowser -Limit $limit
} else {
Write-CCLSHost "Invalid limit. Please specify a positive number." -ForegroundColor Red -Log
}
} else {
Write-CCLSHost "Invalid option for log list. Use -a, -all, or -[number]." -ForegroundColor Red -Log
}
}
"^log\s+view\s+(.+)$" {
$logFileName = $matches[1].Trim()
Show-LogFile -LogFileName $logFileName
}
"^log\s+del\s+(.+?)(?:\s+-y)?$" {
$logFileName = $matches[1].Trim()
$force = $command -match "-y$"
# Remove -y from filename if present
$logFileName = $logFileName -replace "\s+-y$", ""
$logFileName = $logFileName.Trim()
Remove-LogFile -LogFileName $logFileName -Force:$force
}
"^log\s+open\s+(.+)$" {
$logFileName = $matches[1].Trim()
Open-LogFile -LogFileName $logFileName
}
"^sys\s+cache\s+status$" {
Show-LibraryCacheStatus
}
"^sys\s+cache\s+refresh$" {
Refresh-LibraryCache
}
"^sys\s+start-message\s+(.+)$" {
$params = $matches[1].Trim().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)
if ($params.Count -eq 1) {
# Single parameter commands
$action = $params[0]
Manage-StartupMessage -Action $action
} elseif ($params.Count -eq 2) {
# Two parameter commands
$action = $params[0]
$templateName = $params[1]
Manage-StartupMessage -Action $action -TemplateName $templateName
} else {
Write-CCLSHost "Invalid command format. Use: sys start-message [action] [template-name]" -ForegroundColor Red -Log
Write-CCLSHost "Type 'sys start-message list' to see available templates" -ForegroundColor Yellow -Log
}
}
"^list(?:\s+(.+?))?(?:\s+(?:-d|-tree))*$" {
if ($matches.Count -le 1 -or [string]::IsNullOrWhiteSpace($matches[1])) {
# No parameters provided
Write-CCLSHost "Wrong command usage. Type 'list help' for a list of available commands." -ForegroundColor Red -Log
continue
}
$parameters = $matches[1].Trim()
$detailed = $command -match "\s+-d\b"
$tree = $command -match "\s+-tree\b"
# Remove flags from parameters if present
$parameters = $parameters -replace "\s+(-d|-tree)+", ""
$parameters = $parameters.Trim()
# Handle 'list all' commands
if ($parameters -eq "all") {
Get-InstalledGames -Detailed:$detailed
}
# Parse the command format for individual games
elseif ($parameters -match "^(cg|cb)\d{4}$") {
# Direct ID match: list cg0055
Get-GameInfoByIdentifier -Identifier $parameters -SearchType "id" -Detailed:$detailed -Tree:$tree
}
elseif ($parameters -match "^game\s+(.+)$") {
# Game name match: list game [name]
$gameName = $matches[1].Trim()
Get-GameInfoByIdentifier -Identifier $gameName -SearchType "gamename" -Detailed:$detailed -Tree:$tree
}
elseif ($parameters -match "^gamedir\s+(.+)$") {
# Game directory match: list gamedir [folder]
$gameFolderName = $matches[1].Trim()
Get-GameInfoByIdentifier -Identifier $gameFolderName -SearchType "gamefoldername" -Detailed:$detailed -Tree:$tree
}
else {
# Default to gamefoldername for backward compatibility: list [folder]
Get-GameInfoByIdentifier -Identifier $parameters -SearchType "gamefoldername" -Detailed:$detailed -Tree:$tree
}
}
"^del(?:\s+(.+?))?(?:\s+-y)?$" {
if ($matches.Count -le 1 -or [string]::IsNullOrWhiteSpace($matches[1])) {
# No parameters provided
Write-CCLSHost "Wrong command usage. Type 'del help' for a list of available commands." -ForegroundColor Red -Log
continue
}
$parameters = $matches[1].Trim()
$force = $command -match "-y$"
# Remove -y from parameters if present
$parameters = $parameters -replace "\s+-y$", ""
$parameters = $parameters.Trim()
# Parse the command format
if ($parameters -match "^(cg|cb)\d{4}$") {
# Direct ID match: del cg0055
Remove-Game -Identifier $parameters -SearchType "id" -Force:$force
}
elseif ($parameters -match "^game\s+(.+)$") {
# Game name match: del game [name]
$gameName = $matches[1].Trim()
Remove-Game -Identifier $gameName -SearchType "game" -Force:$force
}
elseif ($parameters -match "^gamedir\s+(.+)$") {
# Game directory match: del gamedir [folder]
$gameFolderName = $matches[1].Trim()
Remove-Game -Identifier $gameFolderName -SearchType "gamedir" -Force:$force
}
else {
# Default to gamedir for backward compatibility: del [folder]
Remove-Game -Identifier $parameters -SearchType "gamedir" -Force:$force
}
}
"^changelog\s+(.+)$" {
$versionParam = $matches[1].Trim()
Show-Changelog -Version $versionParam
$commandProcessed = $true
}
"^logout$" {
Write-CCLSHost "Logging out..." -ForegroundColor Cyan -Log
# Clear stored credentials
if (Test-Path $credentialsFile) {
Remove-Item -Path $credentialsFile -Force
Write-CCLSHost "#Removed stored credentials" -Log -NoConsole
}
# Clear cached credentials
$script:cachedCredentials = $null
# Update settings to disable remember login
$settings = Initialize-Settings
$settings.RememberLogin = $false
Save-Settings -settings $settings
# Clear the console
Clear-Host
# Show manual login screen
Write-CCLSHost "Hello and welcome to the CCLS Games CLI Tool" -ForegroundColor Green -Log
Write-CCLSHost "Before proceeding to using this software you will need to sign in." -Log
Write-CCLSHost "If you do not have an account already please go to $baseUrl/login.php?signup to register a new account." -Log
# Start manual login
$loginResult = Start-ManualLogin
if ($loginResult.Success) {
# Clear console again after successful login
Clear-Host
# Check if we entered DevMode during login
if ($loginResult.DevMode) {
# DevMode was activated during login - start command interface
Start-CommandInterface -username $loginResult.Username
return
}
# Check dependencies after successful login
Test-RequiredDependencies | Out-Null
# Show oversight global message if available
if ($script:oversightEnabled -and $script:oversightData.global_message) {
$messageColor = Convert-HexToConsoleColor -HexColor $script:oversightData.global_message.color
Write-CCLSHost $script:oversightData.global_message.message -ForegroundColor $messageColor -Log
}
Show-StartupMessage
# Show version warning if needed
Show-VersionWarningIfNeeded
# Show welcome message
Write-CCLSHost "Welcome to CCLS Games CLI Tool, $($loginResult.Username)!" -ForegroundColor Green -Log
# Show appropriate message based on setup status
$settings = Initialize-Settings
if ($settings.HasCompletedSetup) {
Write-CCLSHost "Type 'help' for a list of available commands." -ForegroundColor Cyan -Log
} else {
Write-CCLSHost "ALERT, type command 'setup' to set critical values before downloading." -ForegroundColor Red -Log
}
# Continue with the command interface (don't exit the while loop)
} else {
# Login failed, exit the application
$running = $false
Write-CCLSHost "Login failed. Press any key to exit..." -ForegroundColor Red -Log
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
}
"^forget$" {
if (Test-Path $credentialsFile) {
Remove-Item -Path $credentialsFile -Force
$settings = Initialize-Settings
$settings.RememberLogin = $false
Save-Settings -settings $settings
Write-CCLSHost "Stored credentials have been removed." -ForegroundColor Green -Log
}
else {
Write-CCLSHost "No stored credentials found." -ForegroundColor Yellow -Log
}
}
default {
# Extract the base command from the input (first word)
$baseCommand = $command.Trim().Split()[0].ToLower()
# Known base commands that have help available
$knownCommands = @('search', 'get', 'list', 'del', 'install', 'log')
if ($knownCommands -contains $baseCommand) {
# If it's a known command with incorrect parameters
Write-CCLSHost "Wrong command usage. Type '$baseCommand help' for a list of available commands." -ForegroundColor Red -Log
} else {
# Completely unknown command
Write-CCLSHost "Unknown command. Type 'help' for a list of all available commands." -ForegroundColor Red -Log
}
}
}
}
}
# Add a line to log script completion
function End-Logging {
$endTime = Get-Date
"CCLS Games CLI Session ended at $endTime, duration: $(($endTime - [DateTime]::ParseExact($script:sessionStartTime, 'yyyy-MM-dd_HH-mm-ss', $null)).ToString())" | Out-File -FilePath $script:logFile -Append
}
# Register script exit event to ensure logging is completed
Register-EngineEvent -SourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action {
End-Logging
} | Out-Null
function Start-CclsCliTool {
# Initialize script variables
$script:versionChanged = $false
$script:previousVersion = $null
# Load settings to check for DevMode (this may generate errors on first run)
$settings = Initialize-Settings
Initialize-LibraryCache
if ($settings.DevMode) {
# In DevMode - skip login and warnings
Write-CCLSHost "Hello and welcome to the CCLS Games CLI Tool (Developer Mode)" -ForegroundColor Green -Log
Write-CCLSHost "You are in developer mode. 'get', 'search' and any other command that calls external API's will not work." -ForegroundColor Red -Log
Write-CCLSHost "Type 'help' for a list of available commands." -ForegroundColor Cyan -Log
# Clear any cached credentials to ensure we're not signed in
$script:cachedCredentials = $null
# Start command interface directly in DevMode
Start-CommandInterface -username "Developer"
return
}
# Try auto-login if remember login is enabled
$loginResult = @{ Success = $false; DevMode = $false }
if ($settings.RememberLogin) {
$loginResult = Start-AutoLogin
}
# If auto-login failed or is disabled, do manual login
if (-not $loginResult.Success) {
# Check if we have stored credentials that failed
$hasStoredCredentials = Test-Path $credentialsFile
if ($hasStoredCredentials) {
# Stored credentials exist but failed - show different message
Write-CCLSHost "Your stored credentials are no longer valid or have expired." -ForegroundColor Yellow -Log
Write-CCLSHost "Please sign in again to continue using the CCLS Games CLI Tool." -ForegroundColor Yellow -Log
# Add any additional messaging for failed stored credentials here
} else {
# No stored credentials - show first-time welcome message
Write-CCLSHost "Hello and welcome to the CCLS Games CLI Tool" -ForegroundColor Green -Log
Write-CCLSHost "Before proceeding to using this software you will need to sign in." -Log
Write-CCLSHost "If you do not have an account already please go to $baseUrl/login.php?signup to register a new account." -Log
}
$loginResult = Start-ManualLogin
# Clear the console after successful manual login
if ($loginResult.Success) {
Clear-Host
}
}
# If login succeeded, show main interface
if ($loginResult.Success) {
# Check if we entered DevMode during login
if ($loginResult.DevMode) {
# DevMode was activated during login - start command interface
Start-CommandInterface -username $loginResult.Username
return
}
# Check dependencies after successful login
Test-RequiredDependencies | Out-Null
# Show oversight global message if available
if ($script:oversightEnabled -and $script:oversightData.global_message) {
$messageColor = Convert-HexToConsoleColor -HexColor $script:oversightData.global_message.color
Write-CCLSHost $script:oversightData.global_message.message -ForegroundColor $messageColor -Log
}
# Check if version has changed and show notification
if ($script:versionChanged) {
$versionInfo = Test-VersionUpdate
$currentVersion = $versionInfo.CurrentVersion
Show-StartupMessage
Write-CCLSHost "Welcome to CCLS Games CLI Tool v$currentVersion, $($loginResult.Username)! Type 'changelog latest' to view the changes made." -ForegroundColor Green -Log
}
else {
Show-StartupMessage
Write-CCLSHost "Welcome to CCLS Games CLI Tool, $($loginResult.Username)!" -ForegroundColor Green -Log
}
# Show appropriate message based on setup status (only if not in DevMode)
if ($settings.HasCompletedSetup) {
Write-CCLSHost "Type 'help' for a list of available commands." -ForegroundColor Cyan -Log
}
else {
Write-CCLSHost "ALERT, type command 'setup' to set critical values before downloading." -ForegroundColor Red -Log
}
# NOW show version warning (after oversight data is loaded)
Show-VersionWarningIfNeeded
Start-CommandInterface -username $loginResult.Username
}
else {
Write-CCLSHost "Login failed. Press any key to exit..." -ForegroundColor Red -Log
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
}
# Format a file size in bytes to a human-readable string
function Format-Size {
param (
[long]$Size
)
if ($Size -ge 1GB) {
return "{0:N2} GB" -f ($Size / 1GB)
}
elseif ($Size -ge 1MB) {
return "{0:N2} MB" -f ($Size / 1MB)
}
elseif ($Size -ge 1KB) {
return "{0:N2} KB" -f ($Size / 1KB)
}
else {
return "$Size B"
}
}
# Start the application
Try {
Start-CclsCliTool
}
Finally {
# Ensure logging is completed even if script exits unexpectedly
End-Logging
}