Files
cartreader/tools/oscr_tool/oscr_tool.ps1
2025-06-20 13:48:29 +02:00

1645 lines
86 KiB
PowerShell

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$ErrorActionPreference = "Stop"
try {
$configPath = "Arduino IDE\portable\sketchbook\Cart_Reader\config.h"
$applyPath = "apply_config.txt"
function Get-FileWithProgress {
param (
[Parameter(Mandatory = $true)][string]$Url,
[Parameter(Mandatory = $true)][string]$Destination
)
Add-Type -AssemblyName System.Net.Http
Write-Host "Downloading: $Url"
$client = New-Object System.Net.Http.HttpClient
$response = $client.GetAsync($Url, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).Result
$total = $response.Content.Headers.ContentLength
$stream = $response.Content.ReadAsStreamAsync().Result
$fileStream = [System.IO.File]::OpenWrite($Destination)
$buffer = New-Object byte[] 8192
$read = 0
$totalRead = 0
$lastPercent = -1
do {
$read = $stream.Read($buffer, 0, $buffer.Length)
if ($read -gt 0) {
$fileStream.Write($buffer, 0, $read)
$totalRead += $read
if ($total) {
$percent = [math]::Floor(($totalRead / $total) * 100)
if ($percent -ne $lastPercent) {
Write-Progress -Activity "Downloading" -Status "$percent% complete" -PercentComplete $percent
$lastPercent = $percent
}
}
}
} while ($read -gt 0)
$fileStream.Close()
$stream.Close()
Write-Progress -Activity "Downloading" -Completed
}
function Expand-Zip {
param (
[string]$ZipPath,
[string]$OutPath
)
Expand-Archive -Path $ZipPath -DestinationPath $OutPath -Force
}
function Update-OSCR {
param()
try {
$root = $PSScriptRoot
Set-Location $root
# Check if Arduino IDE folder exists to skip Steps 1-3
if (Test-Path "$root\Arduino IDE") {
Write-Host "Arduino IDE folder already exists. Skipping Steps 1, 2, 3."
} else {
### Step 1: Arduino IDE ###
Write-Host "Step 1: Downloading Arduino IDE..." -ForegroundColor Green
$ideZip = Join-Path $root "arduino-1.8.19-windows.zip"
Get-FileWithProgress -Url "https://downloads.arduino.cc/arduino-1.8.19-windows.zip" -Destination $ideZip
Write-Host "Verifying SHA256..."
$expectedHash = "C704A821089EAB2588F1DEAE775916219B1517FEBD1DD574FF29958DCA873945"
$hash = (Get-FileHash $ideZip -Algorithm SHA256).Hash
if ($hash -ne $expectedHash) {
Write-Error "Checksum mismatch! Aborting."
exit 1
} else {
Write-Host "Checksum OK."
}
Write-Host "Extracting Arduino IDE..."
Expand-Zip -ZipPath $ideZip -OutPath $root
Remove-Item $ideZip
Rename-Item -Path "$root\arduino-1.8.19" -NewName "Arduino IDE"
### Step 2: Update AVR-GCC compiler ###
Write-Host "Step 2: Downloading latest AVR-GCC compiler..." -ForegroundColor Green
$avrGccZip = Join-Path $root "avr-gcc-14.1.0-x64-windows.zip"
Get-FileWithProgress -Url "https://github.com/ZakKemble/avr-gcc-build/releases/download/v14.1.0-1/avr-gcc-14.1.0-x64-windows.zip" -Destination $avrGccZip
Write-Host "Verifying AVR-GCC SHA256..."
$expectedAvrHash = "d0efbf289004b2d700ae039aa0b592d7d34a9e797e8fe8aa1fef249e997bbae7"
$avrHash = (Get-FileHash $avrGccZip -Algorithm SHA256).Hash
if ($avrHash -ne $expectedAvrHash) {
Write-Error "AVR-GCC checksum mismatch! Aborting."
exit 1
} else {
Write-Host "AVR-GCC checksum OK."
}
# Extract new AVR-GCC
Write-Host "Extracting AVR-GCC..."
Expand-Zip -ZipPath $avrGccZip -OutPath "$root\temp_avr"
Remove-Item $avrGccZip
# Replace old AVR folder with new one
$toolsPath = "$root\Arduino IDE\hardware\tools"
$oldAvrPath = "$toolsPath\avr"
$backupAvrPath = "$root\avr_backup"
if (Test-Path $oldAvrPath) {
Write-Host "Backing up old AVR compiler..."
# Remove existing backup if it exists
if (Test-Path $backupAvrPath) {
Remove-Item $backupAvrPath -Recurse -Force
}
Move-Item $oldAvrPath $backupAvrPath
}
# Move new AVR-GCC to tools folder
Write-Host "Installing new AVR-GCC compiler..."
Move-Item "$root\temp_avr\avr-gcc-14.1.0-x64-windows" "$toolsPath\avr"
# Copy essential files from old AVR installation if backup exists
if (Test-Path $backupAvrPath) {
Write-Host "Copying essential files from old AVR installation..."
# Copy old avrdude.exe
$oldAvrdude = "$backupAvrPath\bin\avrdude.exe"
$newAvrdudeDir = "$toolsPath\avr\bin"
if (Test-Path $oldAvrdude) {
Copy-Item $oldAvrdude $newAvrdudeDir -Force
Write-Host "Copied avrdude.exe"
}
# Copy old libusb0.dll
$oldLibusb = "$backupAvrPath\bin\libusb0.dll"
if (Test-Path $oldLibusb) {
Copy-Item $oldLibusb $newAvrdudeDir -Force
Write-Host "Copied libusb0.dll"
}
# Delete new avrdude.conf
Remove-Item "$toolsPath\avr\bin\avrdude.conf"
# Copy builtin_tools_versions.txt
$oldVersions = "$backupAvrPath\builtin_tools_versions.txt"
$newAvrDir = "$toolsPath\avr"
if (Test-Path $oldVersions) {
Copy-Item $oldVersions $newAvrDir -Force
Write-Host "Copied builtin_tools_versions.txt"
}
# Copy etc folder with old avrdude.conf
$oldEtc = "$backupAvrPath\etc"
$newAvrDir = "$toolsPath\avr"
if (Test-Path $oldEtc) {
Copy-Item $oldEtc $newAvrDir -Recurse -Force
Write-Host "Copied etc folder"
}
}
# Clean up temporary files
Remove-Item "$root\temp_avr" -Recurse -Force
Remove-Item "$root\avr_backup" -Recurse -Force
### Step 3: Update AVRDUDE ###
if ($true) {
# New avrdude 8.0 hangs on old PC, skip for now
Write-Host "Skipping Step 3: Updating AVRDUDE..." -ForegroundColor Green
}
else {
Write-Host "Step 3: Updating AVRDUDE..." -ForegroundColor Green
$avrdudeZip = Join-Path $root "avrdude-v8.0-windows-x64.zip"
$avrdudeBinPath = "$root\Arduino IDE\hardware\tools\avr\bin"
$avrdudeEtcPath = "$root\Arduino IDE\hardware\tools\avr\etc"
$avrdudeExePath = Join-Path $avrdudeBinPath "avrdude.exe"
$avrdudeConfPath = Join-Path $avrdudeEtcPath "avrdude.conf"
Write-Host "Downloading AVRDUDE..."
Get-FileWithProgress -Url "https://github.com/avrdudes/avrdude/releases/download/v8.0/avrdude-v8.0-windows-x64.zip" -Destination $avrdudeZip
Write-Host "Verifying AVRDUDE SHA256..."
$expectedAvrdudeHash = "f4aa811042ef95b52c68531f6e5044c5b5a8711bcd4b495d6b9af20f9ac41325"
$avrdudeHash = (Get-FileHash $avrdudeZip -Algorithm SHA256).Hash
if ($avrdudeHash -ne $expectedAvrdudeHash) {
Write-Error "AVRDUDE checksum mismatch! Aborting."
exit 1
} else {
Write-Host "AVRDUDE checksum OK."
}
Write-Host "Extracting AVRDUDE..."
$tempAvrdudeDir = "$root\temp_avrdude"
Expand-Zip -ZipPath $avrdudeZip -OutPath $tempAvrdudeDir
Remove-Item $avrdudeZip
# Ensure target directories exist
if (-not (Test-Path $avrdudeBinPath)) {
New-Item -ItemType Directory -Path $avrdudeBinPath -Force
}
if (-not (Test-Path $avrdudeEtcPath)) {
New-Item -ItemType Directory -Path $avrdudeEtcPath -Force
}
# Find the extracted avrdude files and copy them
$extractedAvrdudeExe = Get-ChildItem -Path $tempAvrdudeDir -Name "avrdude.exe" -Recurse | Select-Object -First 1
$extractedAvrdudeConf = Get-ChildItem -Path $tempAvrdudeDir -Name "avrdude.conf" -Recurse | Select-Object -First 1
if ($extractedAvrdudeExe) {
$fullAvrdudeExePath = Join-Path $tempAvrdudeDir $extractedAvrdudeExe
Write-Host "Installing avrdude.exe to $avrdudeBinPath"
Copy-Item $fullAvrdudeExePath $avrdudeExePath -Force
} else {
Write-Error "Could not find avrdude.exe in extracted files!"
}
if ($extractedAvrdudeConf) {
$fullAvrdudeConfPath = Join-Path $tempAvrdudeDir $extractedAvrdudeConf
Write-Host "Installing avrdude.conf to $avrdudeEtcPath"
Copy-Item $fullAvrdudeConfPath $avrdudeConfPath -Force
} else {
Write-Error "Could not find avrdude.conf in extracted files!"
}
# Clean up temporary files
Remove-Item $tempAvrdudeDir -Recurse -Force
Write-Host "AVRDUDE v8.0 installation complete!"
}
}
# Step 4: Arduino CLI Setup
Write-Host "Step 4: Setting up Arduino CLI..." -ForegroundColor Green
# Make sure libraries folder exists
New-Item -ItemType Directory -Path "$root\Arduino IDE\portable\sketchbook\libraries" -Force | Out-Null
$arduinoCliZip = Join-Path $root "arduino-cli_1.2.2_Windows_64bit.zip"
$arduinoCliExe = Join-Path $root "Arduino IDE\arduino-cli.exe"
# Download arduino-cli if not already present
if (-not (Test-Path $arduinoCliExe)) {
Write-Host "Downloading Arduino CLI..."
Get-FileWithProgress -Url "https://github.com/arduino/arduino-cli/releases/download/v1.2.2/arduino-cli_1.2.2_Windows_64bit.zip" -Destination $arduinoCliZip
Write-Host "Verifying Arduino CLI SHA256..."
$expectedCliHash = "bdd3ed88a361af8539e51a1cc0bf831b269be155ddfdd90cb96a900ce78723b7"
$cliHash = (Get-FileHash $arduinoCliZip -Algorithm SHA256).Hash
if ($cliHash -ne $expectedCliHash) {
Write-Error "Arduino CLI checksum mismatch! Aborting."
exit 1
} else {
Write-Host "Arduino CLI checksum OK."
}
Write-Host "Extracting Arduino CLI..."
Expand-Zip -ZipPath $arduinoCliZip -OutPath "$root\temp_cli"
Remove-Item $arduinoCliZip
# Move arduino-cli.exe to Arduino IDE directory
Move-Item "$root\temp_cli\arduino-cli.exe" $arduinoCliExe
Remove-Item "$root\temp_cli" -Recurse -Force
} else {
Write-Host "Arduino CLI already exists, skipping download."
}
# Configure Arduino CLI for portable mode
Write-Host "Configuring Arduino CLI for portable mode..."
Set-Location "$root\Arduino IDE"
# Initialize configuration
& ".\arduino-cli.exe" --config-dir "portable\data" config init
# Set portable sketchbook directory
& ".\arduino-cli.exe" --config-dir "portable\data" config set directories.data "portable\data"
& ".\arduino-cli.exe" --config-dir "portable\data" config set directories.downloads "portable\downloads"
& ".\arduino-cli.exe" --config-dir "portable\data" config set directories.user "portable\sketchbook"
# Update package index
Write-Host "Updating Arduino CLI package index..."
& ".\arduino-cli.exe" --config-dir "portable\data" core update-index
# Step 5: Install Required Libraries
Write-Host "Step 5: Installing required libraries..." -ForegroundColor Green
$libraries = @(
"SdFat",
"Adafruit BusIO",
"U8g2",
"Adafruit NeoPixel",
"RotaryEncoder",
"Etherkit Si5351",
"RTClib",
"FreqCount"
)
foreach ($lib in $libraries) {
Write-Host "Installing library: $lib"
try {
& ".\arduino-cli.exe" --config-dir "portable\data" lib install $lib
} catch {
Write-Warning "Failed to install library: $lib - $($_.Exception.Message)"
}
}
# Update all libraries
Write-Host "Updating library index..."
& ".\arduino-cli.exe" --config-dir "portable\data" lib update-index
Write-Host "Upgrading all libraries..."
& ".\arduino-cli.exe" --config-dir "portable\data" lib upgrade
Write-Host "Arduino CLI setup and library installation complete!"
# Return to original directory
Set-Location $root
### Step 6: OSCR Sketch ###
Write-Host "Step 6: Downloading OSCR sketch..." -ForegroundColor Green
$sketchZip = Join-Path $root "master.zip"
Get-FileWithProgress -Url "https://github.com/sanni/cartreader/archive/refs/heads/master.zip" -Destination $sketchZip
Expand-Zip -ZipPath $sketchZip -OutPath $root
Remove-Item $sketchZip
$sketchRoot = Join-Path $root "cartreader-master"
# Delete existing Cart_Reader folder if it exists before moving the new one
$cartReaderDest = "$root\Arduino IDE\portable\sketchbook\Cart_Reader"
if (Test-Path $cartReaderDest) {
Write-Host "Existing Cart_Reader folder found. Removing..."
Remove-Item $cartReaderDest -Recurse -Force
}
# Delete existing SD Card folder if it exists before moving
$sdCardDest = "$root\SD Card"
if (Test-Path $sdCardDest) {
Write-Host "Existing SD Card folder found. Removing..."
Remove-Item $sdCardDest -Recurse -Force
}
Move-Item "$sketchRoot\Cart_Reader" $cartReaderDest
Move-Item "$sketchRoot\sd" $sdCardDest
Move-Item "$sketchRoot\LICENSE" "$root\LICENSE.txt" -Force
Move-Item "$sketchRoot\tools\oscr_tool\launch_oscr_tool.bat" "$root" -Force
Move-Item "$sketchRoot\tools\oscr_tool\oscr_tool.ps1" "$root" -Force
Remove-Item "$sdCardDest\README.md","$cartReaderDest\README.md","$cartReaderDest\LICENSE.txt" -ErrorAction SilentlyContinue
Remove-Item "$sketchRoot" -Recurse -Force
### Step 7: CH341 Drivers ###
Write-Host "Step 7: Downloading CH341 driver..." -ForegroundColor Green
# Delete existing CH341 Drivers folder if it exists before moving
$CH341Dest = "$root\CH341 Drivers"
if (Test-Path $CH341Dest) {
Write-Host "Existing CH341 Drivers folder found. Removing..."
Remove-Item $CH341Dest -Recurse -Force
}
$drvZip = Join-Path $root "drivers.zip"
Get-FileWithProgress -Url "https://www.wch.cn/download/file?id=5" -Destination $drvZip
Expand-Zip -ZipPath $drvZip -OutPath "$root\drivers"
Move-Item "$root\drivers\CH341SER" "$root\CH341 Drivers" -Force
Remove-Item "$root\drivers" -Recurse -Force
Remove-Item $drvZip -Force
### Step 8: Optimize U8g2 ###
Write-Host "Step 8: Optimizing U8g2 for size..." -ForegroundColor Green
$u8g2Header = "$root\Arduino IDE\portable\sketchbook\libraries\U8g2\src\clib\u8g2.h"
(Get-Content $u8g2Header) `
-replace '#define U8G2_16BIT', '//#define U8G2_16BIT' `
-replace '#define U8G2_WITH_HVLINE_SPEED_OPTIMIZATION', '//#define U8G2_WITH_HVLINE_SPEED_OPTIMIZATION' `
-replace '#define U8G2_WITH_INTERSECTION', '//#define U8G2_WITH_INTERSECTION' `
-replace '#define U8G2_WITH_CLIP_WINDOW_SUPPORT', '//#define U8G2_WITH_CLIP_WINDOW_SUPPORT' `
-replace '#define U8G2_WITH_FONT_ROTATION', '//#define U8G2_WITH_FONT_ROTATION' `
-replace '#define U8G2_WITH_UNICODE', '//#define U8G2_WITH_UNICODE' |
Set-Content -Encoding ASCII $u8g2Header
Write-Host "DONE. Portable Arduino IDE for OSCR with updated AVR-GCC compiler is ready."
}
catch {
Write-Host "Error. Update failed: $($_.Exception.Message)"
}
}
if (-not (Test-Path $configPath)) {
Write-Host "Portable Arduino IDE for OSCR not found."
Update-OSCR
}
if (-not (Test-Path $configPath)) {
throw "Missing config.h at $configPath"
}
# Define tooltip texts
$tooltipTexts = @{
"Hardware" = "Select the hardware version of your OSCR."
"ENABLE_RTC" = "If installed this will enable the Real Time Clock module which adds the current time to each dumped ROM."
"RTC module type" = "Select the type of RTC module you have installed."
"ENABLE_3V3FIX" = "Some Mega2560s can't run 16MHz at 3.3V. So this will down-clock the OSCR to run at 8MHz."
"ENABLE_CLOCKGEN" = "Enable if you have the Clock Gen module installed, it is used in the 7800, C64, GPC, N64, SFM, SNES, SUPRACAN, SV modules."
"ENABLE_CONFIG" = "Allows changing some configuration values via a config file on the SD card but not firmware options."
"ENABLE_GLOBAL_LOG" = "This will duplicate all screen output to a txt file on the SD card."
"ENABLE_UPDATER" = "Disable this if you don't want to use the firmware updater utility."
"OPTION_CLOCKGEN_CALIBRATION" = "Enables the calibration utility for the Clockgen (found under Super Nintendo)."
"OPTION_CLOCKGEN_USE_CALIBRATION" = "Uses the value found via the calibration utility to match the frequency better."
"OPTION_N64_FASTCRC" = "Calculate the CRC32 of an N64 ROM while it is being dumped instead of afterwards."
"OPTION_N64_SAVESUMMARY" = "Add an text file with additional info about the dumped ROM to the directory."
"ENABLE_2600" = "The Atari 2600 is a home video game console developed and produced by Atari, Inc. Released in September 1977."
"ENABLE_5200" = "The Atari 5200 SuperSystem or simply Atari 5200 is a home video game console introduced in 1982 by Atari, Inc."
"ENABLE_7800" = "The Atari 7800 ProSystem, or simply the Atari 7800, is a home video game console officially released by Atari Corporation in 1986."
"ENABLE_ARC" = "The Arcadia 2001 is a second-generation 8-bit home video game console released by Emerson Radio in May 1982."
"ENABLE_ATARI8" = "The Atari 8-bit computers, formally launched as the Atari Home Computer System, are a series of home computers introduced by Atari, Inc., in 1979 with the Atari 400 and Atari 800."
"ENABLE_BALLY" = "The Bally Astrocade is a video game console designed by the videogame division of Bally and initially made available in December 1977."
"ENABLE_C64" = "The Commodore 64, also known as the C64, is an 8-bit home computer introduced in January 1982 by Commodore International."
"ENABLE_COLV" = "ColecoVision is a second-generation home video-game console developed by Coleco and launched in North America in August 1982."
"ENABLE_CONTROLLERTEST" = "This enables the Controller Test for N64, it allows to check the analog stick and buttons."
"ENABLE_CPS3" = "The CP System III or CPS-3 is an arcade system board that was first used by Capcom in 1996 with the arcade game Red Earth."
"ENABLE_FAIRCHILD" = "The Fairchild Channel F is a home video game console released by Fairchild Camera and Instrument in November 1976."
"ENABLE_FLASH" = "This enables flashing ROM files in CPS3, Flash8/16, GB, GBA, GBM, GBS, LYNX, MD, N64, NES, PCE, SFM, SNES modules."
"ENABLE_FLASH16" = "This enables flashing using the 16bit flashrom adapter, also needed for CPS3."
"ENABLE_FLASH8" = "This enables the Flashrom menu which allows you to flash single rom chips using the 8-bit flashrom adapter, also needed for flashing SNES repros."
"ENABLE_GBX" = "The Game Boy, Game Boy Color and Game Boy Advance are handheld game consoles developed by Nintendo, launched in the Japanese home market in 1989, 1998 and 2001."
"ENABLE_GPC" = "Enables the SFC Game Processor RAM Cassette menu."
"ENABLE_INTV" = "The Intellivision is a home video game console released by Mattel Electronics in 1979."
"ENABLE_JAGUAR" = "The Atari Jaguar is a home video game console developed by Atari Corporation and released in North America in November 1993."
"ENABLE_LEAP" = "The Leapster Learning Game System is an educational handheld game console made by LeapFrog Enterprises."
"ENABLE_LJ" = "The Bandai Little Jammer is a cartridge based music entertainment system."
"ENABLE_LJPRO" = "The Bandai Little Jammer Pro is a cartridge based music entertainment system."
"ENABLE_LOOPY" = "The Casio Loopy is a 32-bit home video game console released in Japan in October 1995."
"ENABLE_LYNX" = "The Atari Lynx is a fourth-generation hand-held game console released by Atari Corporation in September 1989."
"ENABLE_MD" = "The Sega Mega Drive is a 16-bit video game console released by Sega 1988 in Japan and in 1989 in North America as the Genesis."
"ENABLE_MSX" = "MSX is a standardized home computer architecture developed by ASCII Corporation and released by multiple manufacturers starting 1983."
"ENABLE_N64" = "The Nintendo 64 is a home video game console released by Nintendo in Japan on June 23, 1996."
"ENABLE_NES" = "The Nintendo Entertainment System is an 8-bit home video game console produced by Nintendo. It was first released in Japan on July 15, 1983, as the Family Computer."
"ENABLE_NGP" = "The Neo Geo Pocket series is a line of handheld game consoles developed and manufactured by SNK between 1998 and 2001."
"ENABLE_ODY2" = "The Magnavox Odyssey 2, also known as Philips Odyssey 2, is a home video game console of the second generation that was released in 1978."
"ENABLE_PCE" = "The TurboGrafx-16, known in Japan as the PC Engine, is a home video game console developed by Hudson Soft, manufactured by NEC and released in Japan in 1987 and in North America in 1989."
"ENABLE_PCW" = "The Pocket Challenge W is a handheld device released by Benesse Corporation."
"ENABLE_POKE" = "The Pokémon Mini is a handheld game console designed and manufactured by Nintendo in collaboration with The Pokémon Company and released in North America on November 16, 2001."
"ENABLE_PV1000" = "The Casio PV-1000 is a third-generation home video game console manufactured by Casio and released in Japan in 1983."
"ENABLE_PYUUTA" = "The Tomy Tutor, originally sold in Japan as the Pyūta and in the UK as the Grandstand Tutor, is a home computer produced by the Japanese toymaker Tomy in 1982."
"ENABLE_RCA" = "The RCA Studio II is a home video game console made by RCA that debuted in January 1977."
"ENABLE_SELFTEST" = "The self test allows you to quickly check each of the cartridge slot pins for short circuits."
"ENABLE_SFM" = "Nintendo Power was a video game distribution service for Super Famicom operated by Nintendo that ran exclusively in Japan from 1997 until February 2007."
"ENABLE_SMS" = "The Master System is an 8-bit third-generation home video game console manufactured and developed by Sega."
"ENABLE_SNES" = "The Super Nintendo is a 16-bit home video game console developed by Nintendo that was released in 1991 in North America."
"ENABLE_ST" = "The SuFami Turbo is an accessory by Bandai for Nintendo's Super Famicom system and was released in 1996."
"ENABLE_SUPRACAN" = "The Super A'can is a home video game console released exclusively in 1995 by Funtech/Dunhuang Technology and People's Republic of China by Sino Wealth Electronic Ltd."
"ENABLE_SV" = "The Satellaview was a satellite modem peripheral produced by Nintendo for the Super Famicom in 1995."
"ENABLE_TI99" = "The TI-99/4 and TI-99/4A are home computers released by Texas Instruments in 1979 and 1981, respectively."
"ENABLE_TRS80" = "The TRS-80 Micro Computer System is a desktop microcomputer developed by American company Tandy Corporation and sold through their Radio Shack stores in 1977."
"ENABLE_VBOY" = "The Virtual Boy is a 32-bit tabletop portable video game console developed and manufactured by Nintendo and released in 1995."
"ENABLE_VECTREX" = "The Vectrex is a vector display-based home video game console that was developed by Smith Engineering and manufactured and sold by General Consumer Electronics starting October 1982."
"ENABLE_VIC20" = "The VIC-20 is an 8-bit entry level home computer that was sold by Commodore Business Machines, it was announced in 1980."
"ENABLE_VSELECT" = "If you have an OSCR with VSELECT automatic voltage selection you need to enable this option."
"ENABLE_VSMILE" = "The V.Smile is a sixth-generation educational home video game console manufactured and released by VTech released in 2004."
"ENABLE_WS" = "The WonderSwan is a handheld game console released in Japan by Bandai in March 1999."
"ENABLE_WSV" = "The Watara Supervision, also known as the QuickShot Supervision in the UK, is a monochrome handheld game console, originating from Asia, and introduced in 1992."
"OPTION_MD_DEFAULT_SAVE_TYPE" = "Configure how the MD core saves are formatted. 0: Output each byte once (default), 1: Duplicate each byte, 2: Same as 1 + pad with 0xFF to 64KB."
"OPTION_LCD_BG_COLOR" = "Set the backlight color of the LCD."
"OPTION_REVERSE_SORT" = "Enable to sort files/folders from newest to oldest in the file browser."
}
function Get-ConfigData {
$configLinesAll = Get-Content $configPath -ErrorAction Stop
$marker = "/*==== PROCESSING =================================================*/"
$markerIndex = $null
for ($i = 0; $i -lt $configLinesAll.Count; $i++) {
if ($configLinesAll[$i] -like "*$marker*") {
$markerIndex = $i
break
}
}
if ($null -ne $markerIndex) {
$configLines = $configLinesAll[0..($markerIndex - 1)]
} else {
$configLines = $configLinesAll
}
$configDefs = @{}
foreach ($line in $configLines) {
if ($line -match '^(#define|//#define)\s+(\w+)\b') {
$name = $matches[2]
if ($name -eq "CONFIG_H_") { continue }
# Special handling for OPTION_LCD_BG_COLOR
if ($name -eq "OPTION_LCD_BG_COLOR") {
if ($line -match '#define\s+OPTION_LCD_BG_COLOR\s+(\d+),\s*(\d+),\s*(\d+)') {
$g = [int]$matches[1]
$r = [int]$matches[2]
$b = [int]$matches[3]
if ($g -eq 0 -and $r -eq 0 -and $b -eq 100) { $configDefs[$name] = "Blue" }
elseif ($g -eq 0 -and $r -eq 100 -and $b -eq 0) { $configDefs[$name] = "Red" }
elseif ($g -eq 100 -and $r -eq 0 -and $b -eq 0) { $configDefs[$name] = "Green" }
elseif ($g -eq 100 -and $r -eq 100 -and $b -eq 0) { $configDefs[$name] = "Yellow" }
elseif ($g -eq 0 -and $r -eq 100 -and $b -eq 100) { $configDefs[$name] = "Magenta" }
elseif ($g -eq 100 -and $r -eq 0 -and $b -eq 100) { $configDefs[$name] = "Cyan" }
elseif ($g -eq 100 -and $r -eq 100 -and $b -eq 100) { $configDefs[$name] = "White" }
elseif ($g -eq 50 -and $r -eq 100 -and $b -eq 0) { $configDefs[$name] = "Orange" }
else { $configDefs[$name] = "$g,$r,$b" }
} else {
$configDefs[$name] = $null
}
continue
}
if ($name -eq "OPTION_LCD_BG_COLOR") { continue }
# Special handling for numeric options
if ($name -eq "OPTION_MD_DEFAULT_SAVE_TYPE") {
if ($line -match '#define\s+OPTION_MD_DEFAULT_SAVE_TYPE\s+(\d+)') {
$configDefs[$name] = [int]$matches[1]
} else {
$configDefs[$name] = $null
}
} else {
$enabled = $line -like "#define*"
$configDefs[$name] = $enabled
}
}
}
return $configDefs
}
$configDefs = Get-ConfigData
$applyChanges = @{}
if (Test-Path $applyPath) {
$applyContent = Get-Content $applyPath -ErrorAction SilentlyContinue
if ($applyContent) {
foreach ($line in $applyContent) {
$line = $line.Trim()
if ($line -match '^(#|//)?\s*#define\s+(\w+)') {
$name = $matches[2]
if ($name -eq "CONFIG_H_") { continue }
# Special handling for OPTION_LCD_BG_COLOR
if ($name -eq "OPTION_LCD_BG_COLOR") {
if ($line -match '#define\s+OPTION_LCD_BG_COLOR\s+(\d+),\s*(\d+),\s*(\d+)') {
$g = [int]$matches[1]
$r = [int]$matches[2]
$b = [int]$matches[3]
if ($g -eq 0 -and $r -eq 0 -and $b -eq 100) { $applyChanges[$name] = "Blue" }
elseif ($g -eq 0 -and $r -eq 100 -and $b -eq 0) { $applyChanges[$name] = "Red" }
elseif ($g -eq 100 -and $r -eq 0 -and $b -eq 0) { $applyChanges[$name] = "Green" }
elseif ($g -eq 100 -and $r -eq 100 -and $b -eq 0) { $applyChanges[$name] = "Yellow" }
elseif ($g -eq 0 -and $r -eq 100 -and $b -eq 100) { $applyChanges[$name] = "Magenta" }
elseif ($g -eq 100 -and $r -eq 0 -and $b -eq 100) { $applyChanges[$name] = "Cyan" }
elseif ($g -eq 100 -and $r -eq 100 -and $b -eq 100) { $applyChanges[$name] = "White" }
elseif ($g -eq 50 -and $r -eq 100 -and $b -eq 0) { $applyChanges[$name] = "Orange" }
else { $applyChanges[$name] = "$g,$r,$b" }
} else {
$applyChanges[$name] = $null
}
continue
}
if ($name -eq "OPTION_LCD_BG_COLOR") { continue }
# Special handling for numeric options
if ($name -eq "OPTION_MD_DEFAULT_SAVE_TYPE") {
if ($line -match '#define\s+OPTION_MD_DEFAULT_SAVE_TYPE\s+(\d+)') {
$applyChanges[$name] = [int]$matches[1]
} else {
$applyChanges[$name] = $null
}
} else {
$enable = -not ($line.StartsWith("//"))
$applyChanges[$name] = $enable
}
}
}
}
}
$hardwareKeys = @("HW1", "HW2", "HW3", "HW4", "HW5", "SERIAL_MONITOR")
$rtcKeys = @("DS1307", "DS3231")
$currentHardware = $hardwareKeys | Where-Object { $configDefs[$_] } | Select-Object -First 1
if (-not $currentHardware) { $currentHardware = $hardwareKeys[0] }
$applyHardware = $hardwareKeys | Where-Object { $applyChanges.ContainsKey($_) -and $applyChanges[$_] } | Select-Object -First 1
if (-not $applyHardware) { $applyHardware = $currentHardware }
$currentRTC = $rtcKeys | Where-Object { $configDefs[$_] } | Select-Object -First 1
if (-not $currentRTC) { $currentRTC = $rtcKeys[0] }
$applyRTC = $rtcKeys | Where-Object { $applyChanges.ContainsKey($_) -and $applyChanges[$_] } | Select-Object -First 1
if (-not $applyRTC) { $applyRTC = $currentRTC }
$allKeys = ($configDefs.Keys + $applyChanges.Keys) | Sort-Object -Unique
$hardwareSet = $hardwareKeys + "ENABLE_RTC", "RTC", "ENABLE_3V3FIX", "ENABLE_CLOCKGEN", "ENABLE_VSELECT"
$optionSet = @(
"ENABLE_CONFIG", "ENABLE_GLOBAL_LOG", "ENABLE_UPDATER"
) + ($allKeys | Where-Object { $_ -like "OPTION_*" })
$moduleSet = $allKeys | Where-Object {
($_ -notin $hardwareSet) -and
($_ -notin $optionSet) -and
($_ -ne "CONFIG_H_") -and
($_ -notin $rtcKeys) -and
($_ -ne "use_md_conf")
}
function Add-ConfigRows {
param(
$grid, $keys, $configDefs, $applyChanges,
$currentHardware, $applyHardware, $currentRTC, $applyRTC, $tooltipTexts
)
$grid.Rows.Clear()
foreach ($key in $keys) {
try {
if ($key -eq "Hardware") {
$future = $applyHardware
$rowIndex = $grid.Rows.Add($key, $future)
$row = $grid.Rows[$rowIndex]
$row.Cells[1].Items.Clear()
$row.Cells[1].Items.AddRange($hardwareKeys)
$isDifferent = $future -ne $currentHardware
}
elseif ($key -eq "RTC") {
$future = $applyRTC
$displayName = "RTC module type"
$rowIndex = $grid.Rows.Add($displayName, $future)
$row = $grid.Rows[$rowIndex]
$row.Cells[1].Items.Clear()
$row.Cells[1].Items.AddRange($rtcKeys)
$isDifferent = $future -ne $currentRTC
}
elseif ($key -eq "OPTION_MD_DEFAULT_SAVE_TYPE") {
$current = if ($configDefs.ContainsKey($key)) { $configDefs[$key] } else { $null }
$future = if ($applyChanges.ContainsKey($key)) { $applyChanges[$key] } else { $current }
$displayValue = if ($null -eq $future) { "Disabled" } else { $future.ToString() }
$rowIndex = $grid.Rows.Add($key, $displayValue)
$row = $grid.Rows[$rowIndex]
$row.Cells[1].Items.Clear()
$row.Cells[1].Items.AddRange(@("Disabled", "0", "1", "2"))
$isDifferent = $current -ne $future
}
elseif ($key -eq "OPTION_LCD_BG_COLOR") {
$current = if ($configDefs.ContainsKey($key)) { $configDefs[$key] } else { $null }
$future = if ($applyChanges.ContainsKey($key)) { $applyChanges[$key] } else { $current }
$displayValue = if ($null -eq $future) { "Red" } else { $future.ToString() }
$rowIndex = $grid.Rows.Add($key, $displayValue)
$row = $grid.Rows[$rowIndex]
$row.Cells[1].Items.Clear()
$row.Cells[1].Items.AddRange(@("Red", "Green", "Blue", "Yellow", "Magenta", "Cyan", "White", "Orange"))
$isDifferent = $current -ne $future
}
else {
$current = if ($configDefs.ContainsKey($key)) { $configDefs[$key] } else { $false }
$future = if ($applyChanges.ContainsKey($key)) { $applyChanges[$key] } else { $current }
$rowIndex = $grid.Rows.Add($key, $future.ToString())
$row = $grid.Rows[$rowIndex]
$row.Cells[1].Items.Clear()
$row.Cells[1].Items.AddRange(@("True", "False"))
$isDifferent = $current -ne $future
}
if ($isDifferent) {
$row.Cells[1].Style.ForeColor = [System.Drawing.Color]::Red
} else {
$row.Cells[1].Style.ForeColor = [System.Drawing.Color]::Black
}
}
catch {
Write-Warning "Error adding row for key '$key': $_"
}
}
}
$form = New-Object Windows.Forms.Form
$form.Text = "Open Source Cartridge Reader"
$form.Size = New-Object Drawing.Size(760, 920)
$form.StartPosition = "CenterScreen"
$form.Topmost = $false
$form.MinimumSize = New-Object Drawing.Size(760, 920)
# Remove scrollbars from the main panel
$panel = New-Object Windows.Forms.Panel
$panel.AutoScroll = $false
$panel.Dock = "Fill"
$panel.HorizontalScroll.Enabled = $false
$panel.HorizontalScroll.Visible = $false
$panel.VerticalScroll.Enabled = $false
$panel.VerticalScroll.Visible = $true
function New-Grid {
param($rowCount)
$grid = New-Object Windows.Forms.DataGridView
$headerHeight = 23
$rowHeight = 22
$buffer = 5
$calculatedHeight = $headerHeight + ($rowCount * $rowHeight) + $buffer
$gridWidth = 700
$grid.Size = New-Object Drawing.Size($gridWidth, $calculatedHeight)
$grid.Location = New-Object Drawing.Point(10, 0)
$grid.Anchor = "Top, Left, Right"
$grid.AutoSizeColumnsMode = 'None'
$grid.AllowUserToAddRows = $false
$grid.AllowUserToDeleteRows = $false
$grid.AllowUserToResizeRows = $false
$grid.EditMode = "EditOnEnter"
$grid.SelectionMode = "CellSelect"
$grid.RowHeadersVisible = $false
$grid.ScrollBars = [System.Windows.Forms.ScrollBars]::Vertical
$colDefine = New-Object Windows.Forms.DataGridViewTextBoxColumn
$colDefine.Name = "Define"
$colDefine.HeaderText = "Define"
$colDefine.ReadOnly = $true
$colDefine.Width = 454
$colFuture = New-Object Windows.Forms.DataGridViewComboBoxColumn
$colFuture.Name = "Will Be After Apply"
$colFuture.HeaderText = "Value"
$colFuture.Width = 260
$grid.Columns.AddRange($colDefine, $colFuture)
$grid.add_Scroll({
param($src, $e)
if ($e.ScrollOrientation -eq [System.Windows.Forms.ScrollOrientation]::HorizontalScroll) {
$e.NewValue = 0
}
})
$grid.add_DataError({
param($src, $e)
$e.ThrowException = $false
Write-Warning "DataGrid error: $($e.Exception.Message)"
})
return $grid
}
# Function to get available COM ports
function Get-ComPorts {
try {
$ports = [System.IO.Ports.SerialPort]::GetPortNames() | Sort-Object
if ($ports.Count -eq 0) {
return @("No COM ports found")
}
return $ports
} catch {
return @("Error detecting ports")
}
}
# COM Port dropdown
$comPortDropdown = New-Object Windows.Forms.ComboBox
$comPortDropdown.Location = New-Object Drawing.Point(10, 12)
$comPortDropdown.Size = New-Object Drawing.Size(80, 25)
$comPortDropdown.DropDownStyle = "DropDownList"
$comPortPorts = Get-ComPorts
$comPortDropdown.Items.AddRange($comPortPorts)
if ($comPortPorts.Count -gt 0 -and $comPortPorts[0] -ne "No COM ports found") {
$comPortDropdown.SelectedIndex = 0
}
# Refresh COM Port button
$btnRefreshCOM = New-Object Windows.Forms.Button
$btnRefreshCOM.Text = "Refresh"
$btnRefreshCOM.Size = New-Object Drawing.Size(80, 35)
$btnRefreshCOM.Location = New-Object Drawing.Point(100, 10)
$btnRefreshCOM.BackColor = [System.Drawing.Color]::LightGray
$btnRefreshCOM.FlatStyle = "Flat"
# Update button
$btnUpdate = New-Object Windows.Forms.Button
$btnUpdate.Text = "Update"
$btnUpdate.Size = New-Object Drawing.Size(80, 35)
$btnUpdate.Location = New-Object Drawing.Point(190, 10)
$btnUpdate.BackColor = [System.Drawing.Color]::LightPink
$btnUpdate.FlatStyle = "Flat"
# Backup button
$btnBackup = New-Object Windows.Forms.Button
$btnBackup.Text = "Backup"
$btnBackup.Size = New-Object Drawing.Size(80, 35)
$btnBackup.Location = New-Object Drawing.Point(280, 10)
$btnBackup.BackColor = [System.Drawing.Color]::LightYellow
$btnBackup.FlatStyle = "Flat"
# Apply button
$btnApply = New-Object Windows.Forms.Button
$btnApply.Text = "Apply Changes"
$btnApply.Size = New-Object Drawing.Size(80, 35)
$btnApply.Location = New-Object Drawing.Point(370, 10)
$btnApply.BackColor = [System.Drawing.Color]::LightBlue
$btnApply.FlatStyle = "Flat"
# Arduino IDE button
$btnArduinoIDE = New-Object Windows.Forms.Button
$btnArduinoIDE.Text = "Arduino IDE"
$btnArduinoIDE.Size = New-Object Drawing.Size(80, 35)
$btnArduinoIDE.Location = New-Object Drawing.Point(460, 10)
$btnArduinoIDE.BackColor = [System.Drawing.Color]::LightGreen
$btnArduinoIDE.FlatStyle = "Flat"
# Restore button
$btnRestore = New-Object Windows.Forms.Button
$btnRestore.Text = "Restore"
$btnRestore.Size = New-Object Drawing.Size(80, 35)
$btnRestore.Location = New-Object Drawing.Point(550, 10)
$btnRestore.BackColor = [System.Drawing.Color]::LightCoral
$btnRestore.FlatStyle = "Flat"
# --- Copy to SD Button ---
$btnCopyToSD = New-Object Windows.Forms.Button
$btnCopyToSD.Text = "Copy to SD"
$btnCopyToSD.Size = New-Object Drawing.Size(80, 35)
$btnCopyToSD.Location = New-Object Drawing.Point(640, 10)
$btnCopyToSD.BackColor = [System.Drawing.Color]::LightCyan
$btnCopyToSD.FlatStyle = "Flat"
# Create tooltips for buttons
$tooltip = New-Object Windows.Forms.ToolTip
$tooltip.SetToolTip($btnRefreshCOM, "Searches for available COM ports")
$tooltip.SetToolTip($btnUpdate, "Updates the OSCR Arduino sketch")
$tooltip.SetToolTip($btnBackup, "Saves the current firmware of the OSCR to the PC")
$tooltip.SetToolTip($btnApply, "Writes the changes colored in red to the config.h")
$tooltip.SetToolTip($btnArduinoIDE, "Launches the Arduino IDE so you can compile and flash the code")
$tooltip.SetToolTip($btnRestore, "Restores the saved firmware in case something went wrong")
$tooltip.SetToolTip($btnCopyToSD, "Copies the game databases to the SD card and hides them")
# Status label
$statusLabel = New-Object Windows.Forms.Label
$statusLabel.Text = "Red text indicates changes from current config in apply_config.txt"
$statusLabel.Location = New-Object Drawing.Point(10, 50)
$statusLabel.Size = New-Object Drawing.Size(575, 20)
$statusLabel.ForeColor = [System.Drawing.Color]::DarkGreen
$labelHardware = New-Object Windows.Forms.Label
$labelHardware.Text = "Hardware Config"
$labelHardware.Location = New-Object Drawing.Point(10, 75)
$labelHardware.Size = New-Object Drawing.Size(700, 20)
$labelHardware.Font = New-Object Drawing.Font("Microsoft Sans Serif", 10, [System.Drawing.FontStyle]::Bold)
$hardwareDisplayKeys = @("Hardware", "ENABLE_RTC", "RTC", "ENABLE_3V3FIX", "ENABLE_CLOCKGEN", "ENABLE_VSELECT")
$gridHardware = New-Grid -rowCount $hardwareDisplayKeys.Count
$gridHardware.Location = New-Object Drawing.Point(10, 100)
# Add tooltip functionality to hardware grid
$gridHardware.add_CellMouseEnter({
param($src, $e)
if ($e.RowIndex -ge 0 -and $e.ColumnIndex -eq 0) {
$key = $src.Rows[$e.RowIndex].Cells[0].Value
if ($tooltipTexts.ContainsKey($key)) {
$src.ShowCellToolTips = $true
$src.Rows[$e.RowIndex].Cells[0].ToolTipText = $tooltipTexts[$key]
}
}
})
$labelOption = New-Object Windows.Forms.Label
$labelOption.Text = "Options Config"
$labelOption.Location = New-Object Drawing.Point(10, ($gridHardware.Location.Y + $gridHardware.Height + 15))
$labelOption.Size = New-Object Drawing.Size(700, 20)
$labelOption.Font = New-Object Drawing.Font("Microsoft Sans Serif", 10, [System.Drawing.FontStyle]::Bold)
$gridOption = New-Grid -rowCount $optionSet.Count
$gridOption.Location = New-Object Drawing.Point(10, ($labelOption.Location.Y + 25))
# Add tooltip functionality to option grid
$gridOption.add_CellMouseEnter({
param($src, $e)
if ($e.RowIndex -ge 0 -and $e.ColumnIndex -eq 0) {
$key = $src.Rows[$e.RowIndex].Cells[0].Value
if ($tooltipTexts.ContainsKey($key)) {
$src.ShowCellToolTips = $true
$src.Rows[$e.RowIndex].Cells[0].ToolTipText = $tooltipTexts[$key]
}
}
})
$labelModule = New-Object Windows.Forms.Label
$labelModule.Text = "Module Config"
$labelModule.Location = New-Object Drawing.Point(10, ($gridOption.Location.Y + $gridOption.Height + 15))
$labelModule.Size = New-Object Drawing.Size(700, 20)
$labelModule.Font = New-Object Drawing.Font("Microsoft Sans Serif", 10, [System.Drawing.FontStyle]::Bold)
# --- Only the module grid should scroll ---
# Set a fixed height for the module grid and enable vertical scrolling
$gridModuleVisibleRows = 12
$gridModule = New-Grid -rowCount $gridModuleVisibleRows
$gridModule.Location = New-Object Drawing.Point(10, ($labelModule.Location.Y + 25))
$gridModule.Height = 23 + ($gridModuleVisibleRows * 22) + 5
$gridModule.ScrollBars = [System.Windows.Forms.ScrollBars]::Vertical
$gridModule.Anchor = "Top, Left, Right"
# Add tooltip functionality to module grid
$gridModule.add_CellMouseEnter({
param($src, $e)
if ($e.RowIndex -ge 0 -and $e.ColumnIndex -eq 0) {
$key = $src.Rows[$e.RowIndex].Cells[0].Value
if ($tooltipTexts.ContainsKey($key)) {
$src.ShowCellToolTips = $true
$src.Rows[$e.RowIndex].Cells[0].ToolTipText = $tooltipTexts[$key]
}
}
})
# Add configuration rows
Add-ConfigRows -grid $gridHardware -keys $hardwareDisplayKeys -configDefs $configDefs -applyChanges $applyChanges -currentHardware $currentHardware -applyHardware $applyHardware -currentRTC $currentRTC -applyRTC $applyRTC -tooltipTexts $tooltipTexts
Add-ConfigRows -grid $gridOption -keys $optionSet -configDefs $configDefs -applyChanges $applyChanges -currentHardware $currentHardware -applyHardware $applyHardware -currentRTC $currentRTC -applyRTC $applyRTC -tooltipTexts $tooltipTexts
Add-ConfigRows -grid $gridModule -keys $moduleSet -configDefs $configDefs -applyChanges $applyChanges -currentHardware $currentHardware -applyHardware $applyHardware -currentRTC $currentRTC -applyRTC $applyRTC -tooltipTexts $tooltipTexts
# Backup button click handler
$btnBackup.Add_Click({
try {
$selectedPort = $comPortDropdown.SelectedItem
if (-not $selectedPort -or $selectedPort -eq "No COM ports found" -or $selectedPort -eq "Error detecting ports") {
[System.Windows.Forms.MessageBox]::Show("Please select a valid COM port first.", "No COM Port Selected", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning)
return
}
# Define paths
$avrdudeExe = ".\Arduino IDE\hardware\tools\avr\bin\avrdude.exe"
$avrdudeConf = ".\Arduino IDE\hardware\tools\avr\etc\avrdude.conf"
$backupFile = "backup.hex"
# Build arguments array
$avrdudeArgs = @(
"-C", "`"$avrdudeConf`""
"-c", "wiring"
"-P", $selectedPort
"-b", "115200"
"-p", "m2560"
"-U", "flash:r:$backupFile"
)
$statusLabel.Text = "Creating backup... Please wait."
$statusLabel.ForeColor = [System.Drawing.Color]::Orange
$statusLabel.Refresh()
# Run avrdude directly in PowerShell
$process = Start-Process -FilePath $avrdudeExe -ArgumentList $avrdudeArgs -Wait -PassThru -NoNewWindow
if ($process.ExitCode -eq 0) {
$statusLabel.Text = "Backup created successfully as backup.hex"
$statusLabel.ForeColor = [System.Drawing.Color]::Green
} else {
$statusLabel.Text = "Backup failed. Check COM port and connection."
$statusLabel.ForeColor = [System.Drawing.Color]::Red
}
} catch {
[System.Windows.Forms.MessageBox]::Show("Error creating backup:`n$($_.Exception.Message)", "Backup Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
$statusLabel.Text = "Backup failed with error."
$statusLabel.ForeColor = [System.Drawing.Color]::Red
}
})
$btnArduinoIDE.Add_Click({
try {
$prefsPath = "Arduino IDE\portable\preferences.txt"
$selectedPort = $comPortDropdown.SelectedItem
if (-not $selectedPort -or $selectedPort -eq "No COM ports found" -or $selectedPort -eq "Error detecting ports") {
[System.Windows.Forms.MessageBox]::Show("Please select a valid COM port first.", "No COM Port Selected", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning)
return
}
# --- Build last.sketch0.path dynamically ---
$scriptDir = $null
if ($PSScriptRoot) {
$scriptDir = $PSScriptRoot
} elseif ($MyInvocation.MyCommand.Path) {
$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
} else {
$scriptDir = Get-Location
}
$sketchRelPath = "Arduino IDE\portable\sketchbook\Cart_Reader\Cart_Reader.ino"
$sketchFullPath = [System.IO.Path]::Combine($scriptDir, $sketchRelPath)
$sketchFullPath = [System.IO.Path]::GetFullPath($sketchFullPath)
# Define all target settings
$targetSettings = @{
"board" = "board=mega"
"custom_cpu" = "custom_cpu=mega_atmega2560"
"serial.port" = "serial.port=$selectedPort"
"last.sketch0.path" = "last.sketch0.path=$sketchFullPath"
"last.sketch.count" = "last.sketch.count=1"
"compiler.warning_level" = "compiler.warning_level=all"
"editor.linenumbers" = "editor.linenumbers=true"
"editor.save_on_verify" = "editor.save_on_verify=false"
"upload.verbose" = "upload.verbose=true"
}
if (-not (Test-Path $prefsPath)) {
Write-Host "preferences.txt file not found. Creating new file..." -ForegroundColor Yellow
$parentDir = Split-Path $prefsPath -Parent
if (-not (Test-Path $parentDir)) {
New-Item -ItemType Directory -Path $parentDir -Force | Out-Null
Write-Host "Created directory: $parentDir" -ForegroundColor Green
}
$newFileContent = $targetSettings.Values
try {
[System.IO.File]::WriteAllLines($prefsPath, $newFileContent, [System.Text.UTF8Encoding]::new($false))
Write-Host "Created new preferences.txt with required settings:" -ForegroundColor Green
foreach ($setting in $targetSettings.Values) {
Write-Host " $setting" -ForegroundColor White
}
}
catch {
Write-Host "Error creating file: $($_.Exception.Message)" -ForegroundColor Red
}
}
else {
$content = Get-Content $prefsPath
Write-Host "Checking Arduino IDE preferences..." -ForegroundColor Green
# Track which settings are found and correct
$settingsStatus = @{}
foreach ($key in $targetSettings.Keys) {
$settingsStatus[$key] = @{
Found = $false
Correct = $false
}
}
$modified = $false
# Check existing content
foreach ($line in $content) {
foreach ($key in $targetSettings.Keys) {
$pattern = "^" + [regex]::Escape($key) + "="
if ($line -match $pattern) {
$settingsStatus[$key].Found = $true
if ($line -eq $targetSettings[$key]) {
$settingsStatus[$key].Correct = $true
}
break
}
}
}
# Build new content
$newContent = @()
foreach ($line in $content) {
$lineProcessed = $false
foreach ($key in $targetSettings.Keys) {
$pattern = "^" + [regex]::Escape($key) + "="
if ($line -match $pattern) {
if (-not $settingsStatus[$key].Correct) {
$newContent += $targetSettings[$key]
$modified = $true
} else {
$newContent += $line
}
$lineProcessed = $true
break
}
}
if (-not $lineProcessed) {
$newContent += $line
}
}
# Add missing settings
foreach ($key in $targetSettings.Keys) {
if (-not $settingsStatus[$key].Found) {
$newContent += $targetSettings[$key]
$modified = $true
}
}
if ($modified) {
try {
[System.IO.File]::WriteAllLines($prefsPath, $newContent, [System.Text.UTF8Encoding]::new($false))
Write-Host "`nChanges saved successfully!" -ForegroundColor Green
Write-Host "Updated preferences:" -ForegroundColor Green
foreach ($setting in $targetSettings.Values) {
Write-Host " $setting" -ForegroundColor White
}
}
catch {
Write-Host "`nError writing to file: $($_.Exception.Message)" -ForegroundColor Red
}
} else {
Write-Host "`nNo changes needed - all settings are correct!" -ForegroundColor Green
}
}
Start-Sleep -Milliseconds 500
Write-Host "Launching Arduino IDE..." -ForegroundColor Green
if (Test-Path ".\Arduino IDE\arduino.exe") {
Start-Process -FilePath ".\Arduino IDE\arduino.exe"
$statusLabel.Text = "Arduino IDE launched (preferences checked)"
$statusLabel.ForeColor = [System.Drawing.Color]::Green
} else {
[System.Windows.Forms.MessageBox]::Show("Arduino IDE not found at .\Arduino IDE\arduino.exe", "Arduino IDE Not Found", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
} catch {
[System.Windows.Forms.MessageBox]::Show("Error launching Arduino IDE:`n$($_.Exception.Message)", "Launch Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
})
# Refresh COM Port button click handler
$btnRefreshCOM.Add_Click({
try {
$comPortDropdown.Items.Clear()
$comPortPorts = Get-ComPorts
$comPortDropdown.Items.AddRange($comPortPorts)
if ($comPortPorts.Count -gt 0 -and $comPortPorts -notcontains "No COM ports found") {
$statusLabel.ForeColor = [System.Drawing.Color]::Green
$comPortDropdown.SelectedIndex = 0
$statusLabel.Text = "COM ports refreshed"
}
else {
$statusLabel.ForeColor = [System.Drawing.Color]::Red
$comPortDropdown.SelectedIndex = 0
$statusLabel.Text = "COM ports refreshed"
# Ask user if they want to install CH341 drivers
$result = [System.Windows.Forms.MessageBox]::Show("No COM ports found. Would you like to install CH341 drivers?", "Install Drivers", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Question)
if ($result -eq [System.Windows.Forms.DialogResult]::Yes) {
try {
Start-Process -FilePath ".\CH341 Drivers\SETUP.EXE" -Verb RunAs
} catch {
[System.Windows.Forms.MessageBox]::Show("Error launching driver installer:`n$($_.Exception.Message)", "Driver Install Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
}
}
} catch {
[System.Windows.Forms.MessageBox]::Show("Error refreshing COM ports:`n$($_.Exception.Message)", "Refresh Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
})
# Update button click handler
$btnUpdate.Add_Click({
try {
$statusLabel.Text = "Updating OSCR... Please wait."
$statusLabel.ForeColor = [System.Drawing.Color]::Orange
$statusLabel.Refresh()
Update-OSCR
# Reload config data after update
$script:configDefs = Get-ConfigData
$script:applyChanges = @{}
if (Test-Path $applyPath) {
$applyContent = Get-Content $applyPath -ErrorAction SilentlyContinue
if ($applyContent) {
foreach ($line in $applyContent) {
$line = $line.Trim()
if ($line -match '^(#|//)?\s*#define\s+(\w+)') {
$name = $matches[2]
if ($name -eq "CONFIG_H_") { continue }
if ($name -eq "OPTION_LCD_BG_COLOR") {
if ($line -match '#define\s+OPTION_LCD_BG_COLOR\s+(\d+),\s*(\d+),\s*(\d+)') {
$g = [int]$matches[1]; $r = [int]$matches[2]; $b = [int]$matches[3]
if ($g -eq 0 -and $r -eq 0 -and $b -eq 100) { $script:applyChanges[$name] = "Blue" }
elseif ($g -eq 0 -and $r -eq 100 -and $b -eq 0) { $script:applyChanges[$name] = "Red" }
elseif ($g -eq 100 -and $r -eq 0 -and $b -eq 0) { $script:applyChanges[$name] = "Green" }
elseif ($g -eq 100 -and $r -eq 100 -and $b -eq 0) { $script:applyChanges[$name] = "Yellow" }
elseif ($g -eq 0 -and $r -eq 100 -and $b -eq 100) { $script:applyChanges[$name] = "Magenta" }
elseif ($g -eq 100 -and $r -eq 0 -and $b -eq 100) { $script:applyChanges[$name] = "Cyan" }
elseif ($g -eq 100 -and $r -eq 100 -and $b -eq 100) { $script:applyChanges[$name] = "White" }
elseif ($g -eq 50 -and $r -eq 100 -and $b -eq 0) { $script:applyChanges[$name] = "Orange" }
else { $script:applyChanges[$name] = "$g,$r,$b" }
} else { $script:applyChanges[$name] = $null }
} elseif ($name -eq "OPTION_MD_DEFAULT_SAVE_TYPE") {
if ($line -match '#define\s+OPTION_MD_DEFAULT_SAVE_TYPE\s+(\d+)') {
$script:applyChanges[$name] = [int]$matches[1]
} else { $script:applyChanges[$name] = $null }
} else {
$enable = -not ($line.StartsWith("//"))
$script:applyChanges[$name] = $enable
}
}
}
}
}
# Refresh hardware and RTC selections
$script:currentHardware = $hardwareKeys | Where-Object { $script:configDefs[$_] } | Select-Object -First 1
if (-not $script:currentHardware) { $script:currentHardware = $hardwareKeys[0] }
$script:applyHardware = $hardwareKeys | Where-Object { $script:applyChanges.ContainsKey($_) -and $script:applyChanges[$_] } | Select-Object -First 1
if (-not $script:applyHardware) { $script:applyHardware = $script:currentHardware }
$script:currentRTC = $rtcKeys | Where-Object { $script:configDefs[$_] } | Select-Object -First 1
if (-not $script:currentRTC) { $script:currentRTC = $rtcKeys[0] }
$script:applyRTC = $rtcKeys | Where-Object { $script:applyChanges.ContainsKey($_) -and $script:applyChanges[$_] } | Select-Object -First 1
if (-not $script:applyRTC) { $script:applyRTC = $script:currentRTC }
# Refresh all grids
Add-ConfigRows -grid $gridHardware -keys $hardwareDisplayKeys -configDefs $script:configDefs -applyChanges $script:applyChanges -currentHardware $script:currentHardware -applyHardware $script:applyHardware -currentRTC $script:currentRTC -applyRTC $script:applyRTC -tooltipTexts $tooltipTexts
Add-ConfigRows -grid $gridOption -keys $optionSet -configDefs $script:configDefs -applyChanges $script:applyChanges -currentHardware $script:currentHardware -applyHardware $script:applyHardware -currentRTC $script:currentRTC -applyRTC $script:applyRTC -tooltipTexts $tooltipTexts
Add-ConfigRows -grid $gridModule -keys $moduleSet -configDefs $script:configDefs -applyChanges $script:applyChanges -currentHardware $script:currentHardware -applyHardware $script:applyHardware -currentRTC $script:currentRTC -applyRTC $script:applyRTC -tooltipTexts $tooltipTexts
$statusLabel.Text = "OSCR updated successfully and config reloaded"
$statusLabel.ForeColor = [System.Drawing.Color]::Green
} catch {
[System.Windows.Forms.MessageBox]::Show("Error updating OSCR:`n$($_.Exception.Message)", "Update Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
$statusLabel.Text = "Update failed with error."
$statusLabel.ForeColor = [System.Drawing.Color]::Red
}
})
# Restore button click handler
$btnRestore.Add_Click({
try {
if (-not (Test-Path "backup.hex")) {
[System.Windows.Forms.MessageBox]::Show("backup.hex file not found. Please create a backup first.", "Backup File Not Found", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning)
return
}
$selectedPort = $comPortDropdown.SelectedItem
if (-not $selectedPort -or $selectedPort -eq "No COM ports found" -or $selectedPort -eq "Error detecting ports") {
[System.Windows.Forms.MessageBox]::Show("Please select a valid COM port first.", "No COM Port Selected", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning)
return
}
$result = [System.Windows.Forms.MessageBox]::Show("This will restore the firmware from backup.hex. Are you sure?", "Confirm Restore", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Question)
if ($result -ne [System.Windows.Forms.DialogResult]::Yes) {
return
}
# Define avrdude executable and config paths
$avrdudeExe = ".\Arduino IDE\hardware\tools\avr\bin\avrdude.exe"
$avrdudeConf = ".\Arduino IDE\hardware\tools\avr\etc\avrdude.conf"
# Build arguments array for Start-Process
$avrdudeArgs = @(
"-C", "`"$avrdudeConf`""
"-D"
"-c", "wiring"
"-P", $selectedPort
"-b", "115200"
"-p", "m2560"
"-U", "flash:w:backup.hex"
)
$statusLabel.Text = "Restoring firmware... Please wait."
$statusLabel.ForeColor = [System.Drawing.Color]::Orange
$statusLabel.Refresh()
# Run avrdude directly in PowerShell
$process = Start-Process -FilePath $avrdudeExe -ArgumentList $avrdudeArgs -Wait -PassThru -NoNewWindow
if ($process.ExitCode -eq 0) {
$statusLabel.Text = "Firmware restored successfully"
$statusLabel.ForeColor = [System.Drawing.Color]::Green
} else {
$statusLabel.Text = "Restore failed. Check COM port and connection."
$statusLabel.ForeColor = [System.Drawing.Color]::Red
}
} catch {
[System.Windows.Forms.MessageBox]::Show("Error restoring firmware:`n$($_.Exception.Message)", "Restore Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
$statusLabel.Text = "Restore failed with error."
$statusLabel.ForeColor = [System.Drawing.Color]::Red
}
})
# Apply button click handler
$btnApply.Add_Click({
try {
$newConfigDefs = @{}
# Handle hardware selection
if ($gridHardware.Rows.Count -gt 0) {
$selectedHw = $gridHardware.Rows[0].Cells[1].Value
if ($selectedHw) {
foreach ($hwKey in $hardwareKeys) {
$newConfigDefs[$hwKey] = ($hwKey -eq $selectedHw)
}
}
}
# Handle other hardware settings
if ($gridHardware.Rows.Count -gt 1) {
$enableRtcValue = $gridHardware.Rows[1].Cells[1].Value
if ($enableRtcValue -ne $null) {
$newConfigDefs["ENABLE_RTC"] = ($enableRtcValue -eq "True")
}
}
if ($gridHardware.Rows.Count -gt 2) {
$selectedRtc = $gridHardware.Rows[2].Cells[1].Value
if ($selectedRtc) {
foreach ($rtcKey in $rtcKeys) {
$newConfigDefs[$rtcKey] = ($rtcKey -eq $selectedRtc)
}
}
}
if ($gridHardware.Rows.Count -gt 3) {
$enable3v3Value = $gridHardware.Rows[3].Cells[1].Value
if ($enable3v3Value -ne $null) {
$newConfigDefs["ENABLE_3V3FIX"] = ($enable3v3Value -eq "True")
}
}
if ($gridHardware.Rows.Count -gt 4) {
$enableClockgenValue = $gridHardware.Rows[4].Cells[1].Value
if ($enableClockgenValue -ne $null) {
$newConfigDefs["ENABLE_CLOCKGEN"] = ($enableClockgenValue -eq "True")
}
}
if ($gridHardware.Rows.Count -gt 5) {
$enableVselectValue = $gridHardware.Rows[5].Cells[1].Value
if ($enableVselectValue -ne $null) {
$newConfigDefs["ENABLE_VSELECT"] = ($enableVselectValue -eq "True")
}
}
# Handle option settings
for ($i = 0; $i -lt $gridOption.Rows.Count; $i++) {
$key = $gridOption.Rows[$i].Cells[0].Value
$val = $gridOption.Rows[$i].Cells[1].Value
if ($key -and $val -ne $null) {
if ($key -eq "OPTION_MD_DEFAULT_SAVE_TYPE") {
if ($val -eq "Disabled") {
$newConfigDefs[$key] = $null
} else {
$newConfigDefs[$key] = [int]$val
}
} elseif ($key -eq "OPTION_LCD_BG_COLOR") {
$newConfigDefs[$key] = $val
} else {
$newConfigDefs[$key] = ($val -eq "True")
}
}
}
# Handle module settings
for ($i = 0; $i -lt $gridModule.Rows.Count; $i++) {
$key = $gridModule.Rows[$i].Cells[0].Value
$val = $gridModule.Rows[$i].Cells[1].Value
if ($key -and $val -ne $null) {
$newConfigDefs[$key] = ($val -eq "True")
}
}
# Check if there are changes from the original apply_config.txt
$hasChanges = $false
foreach ($key in $newConfigDefs.Keys) {
$guiValue = $newConfigDefs[$key]
$originalApplyValue = if ($applyChanges.ContainsKey($key)) { $applyChanges[$key] } else { $null }
if ($originalApplyValue -eq $null -or $guiValue -ne $originalApplyValue) {
$hasChanges = $true
break
}
}
if (-not $hasChanges) {
foreach ($key in $applyChanges.Keys) {
if (-not $newConfigDefs.ContainsKey($key)) {
$hasChanges = $true
break
}
}
}
# Always update config.h first
try {
$originalLines = Get-Content $configPath -ErrorAction Stop
$newLines = @()
foreach ($line in $originalLines) {
if ($line -match '^(#define|//#define)\s+(\w+)\b') {
$defineName = $matches[2]
if ($defineName -eq "CONFIG_H_") {
$newLines += $line
continue
}
if ($newConfigDefs.ContainsKey($defineName)) {
if ($defineName -eq "OPTION_MD_DEFAULT_SAVE_TYPE") {
$value = $newConfigDefs[$defineName]
$newLine = if ($value -eq $null) { "//#define $defineName 0" } else { "#define $defineName $value" }
$newLines += $newLine
} elseif ($defineName -eq "OPTION_LCD_BG_COLOR") {
$value = $newConfigDefs[$defineName]
# Map color name to RGB string if needed, otherwise use as-is if already RGB
if ($null -eq $value) {
$rgb = "100, 0, 0" # fallback to Green if null
} elseif ($value -match '^\d+\s*,\s*\d+\s*,\s*\d+$') {
$rgb = $value
} else {
switch ($value) {
"Red" { $rgb = "0, 100, 0" }
"Green" { $rgb = "100, 0, 0" }
"Blue" { $rgb = "0, 0, 100" }
"Yellow" { $rgb = "100, 100, 0" }
"Magenta" { $rgb = "0, 100, 100" }
"Cyan" { $rgb = "100, 0, 100" }
"White" { $rgb = "100, 100, 100" }
"Orange" { $rgb = "50, 100, 0" }
default { $rgb = "100, 0, 0" } # fallback to Green if unknown
}
}
$newLine = "#define $defineName $rgb"
$newLines += $newLine
} else {
$shouldEnable = $newConfigDefs[$defineName]
$newLine = if ($shouldEnable) { "#define $defineName" } else { "//#define $defineName" }
$newLines += $newLine
}
} else {
$newLines += $line
}
} else {
$newLines += $line
}
}
[System.IO.File]::WriteAllLines($configPath, $newLines, [System.Text.UTF8Encoding]::new($false))
$statusLabel.Text = "Config.h updated successfully"
$statusLabel.ForeColor = [System.Drawing.Color]::Green
} catch {
[System.Windows.Forms.MessageBox]::Show("Error updating config.h:`n$($_.Exception.Message)", "Config Update Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
return
}
# Handle apply_config.txt based on whether changes were made
if ($hasChanges) {
$result = [System.Windows.Forms.MessageBox]::Show(
"You made changes to the configuration settings.`n`nDo you want to save these changes to apply_config.txt?",
"Save Configuration Changes?",
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Question
)
if ($result -eq [System.Windows.Forms.DialogResult]::Yes) {
$linesToWrite = @()
foreach ($key in ($newConfigDefs.Keys | Sort-Object)) {
$val = $newConfigDefs[$key]
if ($key -eq "OPTION_MD_DEFAULT_SAVE_TYPE") {
$line = if ($val -eq $null) { "//#define $key 0" } else { "#define $key $val" }
$linesToWrite += $line
} elseif ($key -eq "OPTION_LCD_BG_COLOR") {
# Map color name to RGB string if needed, otherwise use as-is if already RGB
if ($null -eq $val) {
$rgb = "100, 0, 0"
} elseif ($val -match '^\d+\s*,\s*\d+\s*,\s*\d+$') {
$rgb = $val
} else {
switch ($val) {
"Red" { $rgb = "0, 100, 0" }
"Green" { $rgb = "100, 0, 0" }
"Blue" { $rgb = "0, 0, 100" }
"Yellow" { $rgb = "100, 100, 0" }
"Magenta" { $rgb = "0, 100, 100" }
"Cyan" { $rgb = "100, 0, 100" }
"White" { $rgb = "100, 100, 100" }
"Orange" { $rgb = "50, 100, 0" }
default { $rgb = "100, 0, 0" }
}
}
$line = "#define $key $rgb"
$linesToWrite += $line
} else {
$line = if ($val) { "#define $key" } else { "//#define $key" }
$linesToWrite += $line
}
}
if ($linesToWrite.Count -gt 0) {
[System.IO.File]::WriteAllLines($applyPath, $linesToWrite, [System.Text.UTF8Encoding]::new($false))
}
}
}
# Reload config data and refresh display to remove red text
$script:configDefs = Get-ConfigData
$script:applyChanges = @{} # Clear apply changes since config is now up to date
$script:currentHardware = $hardwareKeys | Where-Object { $script:configDefs[$_] } | Select-Object -First 1
if (-not $script:currentHardware) { $script:currentHardware = $hardwareKeys[0] }
$script:currentRTC = $rtcKeys | Where-Object { $script:configDefs[$_] } | Select-Object -First 1
if (-not $script:currentRTC) { $script:currentRTC = $rtcKeys[0] }
$script:applyHardware = $script:currentHardware
$script:applyRTC = $script:currentRTC
Add-ConfigRows -grid $gridHardware -keys $hardwareDisplayKeys -configDefs $script:configDefs -applyChanges $script:applyChanges -currentHardware $script:currentHardware -applyHardware $script:applyHardware -currentRTC $script:currentRTC -applyRTC $script:currentRTC -tooltipTexts $tooltipTexts
Add-ConfigRows -grid $gridOption -keys $optionSet -configDefs $script:configDefs -applyChanges $script:applyChanges -currentHardware $script:currentHardware -applyHardware $script:applyHardware -currentRTC $script:currentRTC -applyRTC $script:currentRTC -tooltipTexts $tooltipTexts
Add-ConfigRows -grid $gridModule -keys $moduleSet -configDefs $script:configDefs -applyChanges $script:applyChanges -currentHardware $script:currentHardware -applyHardware $script:applyHardware -currentRTC $script:currentRTC -applyRTC $script:currentRTC -tooltipTexts $tooltipTexts
} catch {
[System.Windows.Forms.MessageBox]::Show("Error applying changes:`n$($_.Exception.Message)", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
})
# Copy to SD button click handler
$btnCopyToSD.Add_Click({
try {
# Get current script directory more defensively
$scriptDir = $null
if ($PSScriptRoot) {
$scriptDir = $PSScriptRoot
} elseif ($MyInvocation.MyCommand.Path) {
$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
} else {
$scriptDir = Get-Location
}
# Ask user to select SD card root
$folderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog
$folderBrowser.Description = "Select the root of your SD card"
$folderBrowser.ShowNewFolderButton = $false
$folderBrowser.RootFolder = [System.Environment+SpecialFolder]::MyComputer
$dialogResult = $folderBrowser.ShowDialog()
if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK) {
$sdPath = $folderBrowser.SelectedPath
# Validate SD path
if (-not $sdPath -or $sdPath.Trim() -eq "") {
[System.Windows.Forms.MessageBox]::Show("No SD card path selected.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
return
}
# Build source path
$sourcePath = $null
if ($scriptDir) {
$sourcePath = [System.IO.Path]::Combine($scriptDir, "SD Card")
}
if (-not $sourcePath -or $sourcePath.Trim() -eq "") {
[System.Windows.Forms.MessageBox]::Show("Could not determine source path.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
return
}
# Check if source folder exists
if (-not (Test-Path -Path $sourcePath -PathType Container)) {
[System.Windows.Forms.MessageBox]::Show("The folder 'SD Card' was not found at: $sourcePath", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
return
}
# Get all items recursively, excluding config.txt
$allItems = @()
try {
$allItems = Get-ChildItem -Path $sourcePath -Recurse -Force -ErrorAction Stop | Where-Object {
$_.Name.ToLower() -ne "config.txt"
}
} catch {
[System.Windows.Forms.MessageBox]::Show("Failed to scan source directory: $($_.Exception.Message)", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
return
}
if (-not $allItems -or $allItems.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("No files found to copy.", "Information", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
return
}
$fileItems = $allItems | Where-Object { -not $_.PSIsContainer }
$totalFiles = if ($fileItems) { $fileItems.Count } else { 0 }
$currentFile = 0
# Process each item
foreach ($item in $allItems) {
try {
if (-not $item -or -not $item.FullName) {
Write-Host "Skipping invalid item" -ForegroundColor Yellow
continue
}
# Calculate relative path using .NET methods for better reliability
$itemFullPath = $item.FullName
$sourceFullPath = (Get-Item $sourcePath).FullName
if (-not $itemFullPath.StartsWith($sourceFullPath)) {
Write-Host "Skipping item outside source path: $itemFullPath" -ForegroundColor Yellow
continue
}
$relativePath = $itemFullPath.Substring($sourceFullPath.Length).TrimStart([System.IO.Path]::DirectorySeparatorChar)
if (-not $relativePath -or $relativePath.Trim() -eq "") {
Write-Host "Skipping item with empty relative path" -ForegroundColor Yellow
continue
}
$targetPath = [System.IO.Path]::Combine($sdPath, $relativePath)
if ($item.PSIsContainer) {
# Create directory
if (-not (Test-Path -Path $targetPath -PathType Container)) {
New-Item -ItemType Directory -Path $targetPath -Force -ErrorAction Stop | Out-Null
Write-Host "Created directory: $relativePath" -ForegroundColor Blue
}
} else {
# Copy file
$targetDir = [System.IO.Path]::GetDirectoryName($targetPath)
if ($targetDir -and -not (Test-Path -Path $targetDir -PathType Container)) {
New-Item -ItemType Directory -Path $targetDir -Force -ErrorAction Stop | Out-Null
}
Copy-Item -Path $itemFullPath -Destination $targetPath -Force -ErrorAction Stop
# Set file as hidden
if (Test-Path -Path $targetPath -PathType Leaf) {
try {
$file = Get-Item -Path $targetPath -Force -ErrorAction SilentlyContinue
if ($file) {
$file.Attributes = $file.Attributes -bor [System.IO.FileAttributes]::Hidden
}
} catch {
Write-Host "Warning: Could not hide file $relativePath" -ForegroundColor Yellow
}
}
$currentFile++
if ($totalFiles -gt 0) {
Write-Progress -Activity "Copying to SD Card" -Status "File $currentFile of $totalFiles" -PercentComplete (($currentFile / $totalFiles) * 100)
}
Write-Host "Copied and hid: $relativePath"
}
} catch {
Write-Host "Failed to process item: $($item.Name) - $($_.Exception.Message)" -ForegroundColor Red
}
}
Write-Progress -Activity "Copying to SD Card" -Completed
Write-Host "Copy operation completed successfully!"
} else {
Write-Host "User cancelled SD card selection" -ForegroundColor Yellow
}
} catch {
$errorMessage = "Copy operation failed: $($_.Exception.Message)"
Write-Host $errorMessage -ForegroundColor Red
Write-Host "Stack trace: $($_.ScriptStackTrace)" -ForegroundColor Red
[System.Windows.Forms.MessageBox]::Show($errorMessage, "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
})
# Enable vertical scrolling for the panel content
$panel.AutoScroll = $false
$panel.HorizontalScroll.Enabled = $false
$panel.HorizontalScroll.Visible = $false
$panel.Controls.AddRange(@(
$comPortDropdown, $btnRefreshCOM, $btnUpdate, $btnBackup, $btnApply, $btnArduinoIDE, $btnRestore, $btnCopyToSD, $statusLabel,
$labelHardware, $gridHardware,
$labelOption, $gridOption,
$labelModule, $gridModule
))
$form.Controls.Add($panel)
# Set focus to first grid
$form.Add_Shown({
$gridHardware.Focus()
})
[void] $form.ShowDialog()
} catch {
$errorMsg = "Script Error: $($_.Exception.Message)`n`nLine: $($_.InvocationInfo.ScriptLineNumber)"
Write-Error $errorMsg
# Show error dialog if Windows Forms is available
if ([System.Windows.Forms.MessageBox]) {
[System.Windows.Forms.MessageBox]::Show($errorMsg, "Script Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
}