diff --git a/installer/windows/Language Files/EnglishExtra.nsh b/installer/windows/Language Files/EnglishExtra.nsh index 4f1c4ac11..431b393f6 100644 --- a/installer/windows/Language Files/EnglishExtra.nsh +++ b/installer/windows/Language Files/EnglishExtra.nsh @@ -20,6 +20,7 @@ ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Setup will guide you through the ${LangFileString} GEODE_TEXT_GD_MISSING "$\r$\n$\r$\nThis path does not have Geometry Dash installed!" ${LangFileString} GEODE_TEXT_GD_OLD "$\r$\n$\r$\nYour version of Geometry Dash is too old for this version of Geode!" ${LangFileString} GEODE_TEXT_MOD_LOADER_ALREADY_INSTALLED "This path has other mods installed!$\r$\nThey will be overwritten by Geode. (the dll trademark)" +${LangFileString} GEODE_TEXT_REGISTER_FILETYPE "Register .geode filetype" ; uninstaller diff --git a/installer/windows/installer.nsi b/installer/windows/installer.nsi index 35429bb55..b21eb1a48 100644 --- a/installer/windows/installer.nsi +++ b/installer/windows/installer.nsi @@ -459,6 +459,12 @@ SectionGroup "Geode" Section "Loader" LOADER_SECTION SetOutPath $INSTDIR + !define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + !define env_hkcu 'HKCU "Environment"' + + WriteRegExpandStr ${env_hkcu} "GEODE_GD_PATH" "$INSTDIR" + System::Call 'user32::SendMessageTimeoutA(i0xffff,i0x1a,i0, t"Environment",i2,i5000,i0)i' + File ${BINDIR}\Geode.dll File ${BINDIR}\Geode.pdb File ${BINDIR}\GeodeUpdater.exe @@ -480,6 +486,32 @@ SectionGroup "Geode" WriteUninstaller "GeodeUninstaller.exe" SectionEnd + Section "Register .geode Filetype" FILETYPE_SECTION + SectionGetFlags ${FILETYPE_SECTION} $0 + IntOp $0 $0 & ${SF_SELECTED} + StrCmp $0 0 done + + ; Extract the ICO to a known path + SetOutPath "$INSTDIR\geode" + File /oname=logo_inst.ico "Graphics\logo_inst.ico" + File /oname=installmods.ps1 "installmods.ps1" + + ; Register .geode extension + WriteRegStr HKCU "Software\Classes\.geode" "" "GeodeFile" + WriteRegStr HKCU "Software\Classes\GeodeFile" "" "Geode Mod" + WriteRegStr HKCU "Software\Classes\GeodeFile\DefaultIcon" "" "$INSTDIR\geode\logo_inst.ico,0" + WriteRegStr HKCU "Software\Classes\GeodeFile\shell" "" "open" + WriteRegStr HKCU "Software\Classes\GeodeFile\shell\open\command" "" \ + 'powershell.exe -ExecutionPolicy Bypass -File "$INSTDIR\geode\installmods.ps1" "%1"' + + ; Refresh icon cache + System::Call 'shell32::SHChangeNotify(i0x8000000,i0x0,p0,p0)' + + SetOutPath "$INSTDIR" + + done: + SectionEnd + Section "Resources" CreateDirectory $INSTDIR\geode\resources\geode.loader SetOutPath $INSTDIR\geode\resources\geode.loader @@ -545,6 +577,10 @@ Section "Uninstall" Delete $INSTDIR\GeodeUpdater.exe Delete $INSTDIR\XInput1_4.dll + ; Remove the .geode filetype registry keys + DeleteRegKey HKCU "Software\Classes\.geode" + DeleteRegKey HKCU "Software\Classes\GeodeFile" + # default value of DATA is an empty string # if DATA is empty, keep user data # otherwise, delete the entire geode and DATA\geode\mods dirs diff --git a/installer/windows/installmods.ps1 b/installer/windows/installmods.ps1 new file mode 100644 index 000000000..40d26fd78 --- /dev/null +++ b/installer/windows/installmods.ps1 @@ -0,0 +1,110 @@ +param ( + [Parameter(Mandatory=$true, ValueFromRemainingArguments=$true)] + [string[]]$ModFiles +) + +if (-not $env:GEODE_GD_PATH) { + Write-Error "GEODE_GD_PATH environment variable is not set." + exit +} + +$GEODE_GD_PATH = $env:GEODE_GD_PATH +$MODS_PATH = Join-Path $GEODE_GD_PATH "geode\mods" +$InstalledMods = @{} +$InstalledOrDownloaded = @{} + +function Show-InstalledModsMessage { + param([hashtable]$Mods) + Add-Type -AssemblyName System.Windows.Forms + if ($Mods.Count -eq 0) { + [System.Windows.Forms.MessageBox]::Show( + "No new mods were installed.", + "Geode Mod Installer", + [System.Windows.Forms.MessageBoxButtons]::OK, + [System.Windows.Forms.MessageBoxIcon]::Information + ) + } else { + $msg = "Installed mods:`n`n" + foreach ($mod in $Mods.Keys) { + $msg += "- $mod`n" + } + [System.Windows.Forms.MessageBox]::Show( + $msg, + "Geode Mod Installer", + [System.Windows.Forms.MessageBoxButtons]::OK, + [System.Windows.Forms.MessageBoxIcon]::Information + ) + } +} + +function Extract-Mod { + param([string]$Source, [string]$Destination) + if (Test-Path $Destination) { Remove-Item -Recurse -Force $Destination } + try { + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($Source, $Destination) + return $true + } catch { + Write-Error ("Failed to extract {0}: {1}" -f $Source, $_) + return $false + } +} + +function Install-Mod { + param([string]$ModFilePath) + + $ModName = [System.IO.Path]::GetFileNameWithoutExtension($ModFilePath) + if ($InstalledOrDownloaded.ContainsKey($ModName)) { return } + + $TempDir = Join-Path $env:TEMP "temp_$ModName" + if (-not (Extract-Mod -Source $ModFilePath -Destination $TempDir)) { return } + + $DestModPath = Join-Path $MODS_PATH ([System.IO.Path]::GetFileName($ModFilePath)) + Copy-Item -Path $ModFilePath -Destination $DestModPath -Force + + $InstalledMods[$ModName] = $true + $InstalledOrDownloaded[$ModName] = $true + + $ModJsonPath = Join-Path $TempDir "mod.json" + if (Test-Path $ModJsonPath) { + $ModJson = Get-Content $ModJsonPath -Raw | ConvertFrom-Json + if ($ModJson.PSObject.Properties.Name -contains "dependencies" -and $ModJson.dependencies) { + Write-Host "Installing dependencies - Mod versions may be incompatible with the mod." + foreach ($dep in $ModJson.dependencies.PSObject.Properties) { + $depId = $dep.Name + $depValue = $dep.Value + + if ($depValue -is [PSCustomObject] -and $depValue.importance -eq "suggested") { + Write-Host "Skipping suggested dependency $depId" + continue + } + + $DepModPath = Join-Path $MODS_PATH "$depId.geode" + if ($InstalledOrDownloaded.ContainsKey($depId) -or (Test-Path $DepModPath)) { continue } + + Write-Host "Downloading latest dependency $depId..." + try { + $TempDepPath = Join-Path $env:TEMP "$depId.geode" + Invoke-WebRequest -Uri "https://api.geode-sdk.org/v1/mods/$depId/versions/latest/download" -OutFile $TempDepPath -UseBasicParsing + Install-Mod -ModFilePath $TempDepPath + } catch { + Write-Warning ("Failed to download or install dependency {0}: {1}" -f $depId, $_) + } + } + } + } + + if (Test-Path $TempDir) { Remove-Item -Recurse -Force $TempDir } +} + +if (-not (Test-Path $MODS_PATH)) { New-Item -ItemType Directory -Path $MODS_PATH | Out-Null } + +foreach ($modFile in $ModFiles) { + if (Test-Path $modFile) { + Install-Mod -ModFilePath (Resolve-Path $modFile) + } else { + Write-Warning "$modFile does not exist." + } +} + +Show-InstalledModsMessage -Mods $InstalledMods