├── .github └── ISSUE_TEMPLATE │ ├── bug-report-for-the-tool-itself.md │ ├── feature_request.md │ └── problems-with-a-sample-image-pack.md ├── .gitignore ├── AnalogueOSLibraryImageGenerator.ps1 ├── LICENSE.txt ├── README.md ├── create-local-archive.md └── specially-noted-titles.md /.github/ISSUE_TEMPLATE/bug-report-for-the-tool-itself.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report for the tool itself 3 | about: Report issues with the tooling itself 4 | title: '' 5 | labels: generation-script 6 | assignees: codewario 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. This can include problems with the generated images themselves or errors/issues you have encountered in the script. 12 | 13 | Problems with the sample packs provided with each Release should instead use the `Problems with a sample image pack` issue template. 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Console output** 26 | ``` 27 | If applicable, please copy your console output here. 28 | ``` 29 | 30 | **Desktop (please complete the following information):** 31 | - OS Version [e.g. Windows 11 Home 22H2] 32 | - PowerShell Version: [e.g. 5.1] 33 | 34 | Go to `Start > Settings > System > About` to view the Windows version you are running. Please include the `Edition` and `Version` as shown in the example. 35 | 36 | Currently, only Windows PowerShell 5.1 is supported, due to limitations with the later versions of PowerShell not having some of the required assemblies available as they have either been deprecated or have not been open-sourced. 37 | 38 | **Additional context** 39 | Add any other context about the problem here. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: Feature request 5 | labels: enhancement 6 | assignees: codewario 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/problems-with-a-sample-image-pack.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Problems with a sample image pack 3 | about: Report a problem with a sample image pack available on the latest release 4 | title: Problem with a pre-built image library 5 | labels: sample-packs 6 | assignees: codewario 7 | 8 | --- 9 | 10 | **Release version of the sample image pack** 11 | [e.g. v1.1, v1.2, etc.] 12 | 13 | Any issues created for sample image packs that are not bundled with the current latest version will be closed. You can find always find the latest Release at https://github.com/codewario/PocketLibraryImageConversion/releases/latest 14 | 15 | If the issue is with an image library you used the tool to build yourself, please use the `Bug report for the tool itself` issue template instead, or this issue will be closed. 16 | 17 | **Screenshots** 18 | Attach any relevant screenshots here or remove this section if you don't have any to share. 19 | 20 | **Additional context** 21 | Any additional information you feel is relevant to share. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | GB/ 2 | GBA/ 3 | GBA-M/ 4 | GBA-V/ 5 | GG/ 6 | lib/ 7 | dl/ 8 | boot_rom_tables/ 9 | *_palettes/ 10 | 11 | *.dat 12 | *.png 13 | *.zip 14 | *.pal -------------------------------------------------------------------------------- /AnalogueOSLibraryImageGenerator.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [switch]$OutputConvertedFiles, 4 | [ValidateSet('Minimal', 'Extra', 'Noisy')] 5 | [string]$Verbosity = 'Minimal' # Dev note: don't bother writing checks for 'Minimal' 6 | ) 7 | #Requires -PSEdition Desktop 8 | #Requires -Version 5.1 9 | 10 | # Variables 11 | $ErrorActionPreference = 'Stop' 12 | 13 | $tempWorkspace = if ( $env:USERPROFILE ) { 14 | "$env:USERPROFILE\.aostool" 15 | } 16 | else { 17 | "$env:HOME\.aostool" 18 | } 19 | 20 | $tempZipPath = "$tempWorkspace\console.zip" 21 | $tempExtractionPath = "$tempWorkspace" 22 | $tempConversionRootDir = "$tempWorkspace" 23 | $tempConversionBoxArtsDir = "$tempConversionRootDir\conv\BoxArts" 24 | $tempConversionSnapsDir = "$tempConversionRootDir\conv\Snaps" 25 | $tempConversionTitlesDir = "$tempConversionRootDir\conv\Titles" 26 | 27 | $libretro_base = 'https://github.com/libretro-thumbnails' 28 | $libretro_repo = "$libretro_base/libretro-thumbnails" 29 | $datomatic_site = 'https://datomatic.no-intro.org' 30 | 31 | # Titles for special handling during conversion 32 | $specialCaseTitles = @' 33 | # GBC 34 | Castlevania - The Adventure DX (USA)::Romhack 35 | Castlevania II - Belmont's Revenge DX (USA)::Romhack 36 | Donkey Kong Land - DX (USA)::Romhack 37 | Donkey Kong Land 2 - DX (USA)::Romhack 38 | Donkey Kong Land III DX (USA)::Romhack 39 | Kirby's Dream Land - DX (USA)::Romhack 40 | Kirby's Dream Land 2 - DX (USA)::Romhack 41 | Legend of Zelda, The - Link's Awakening DX - Redux (USA)::Romhack 42 | Metroid II - Return of Samus DX (USA)::Romhack 43 | Pokemon - Blue Version DX (USA)::Romhack 44 | Pokemon - Red Version DX (USA)::Romhack 45 | Pokemon - Yellow Version - Special Pikachu Edition DX (USA)::Romhack 46 | Pokemon Picross (USA)::Romhack 47 | Pokemon Trading Card Game 2 - The Invasion of Team GR (USA)::Romhack 48 | Super Mario Bros. Deluxe (Japan) (En) (Rev 1) (NP)::Redirect::Super Mario Bros. Deluxe (Japan) (NP) 49 | Super Mario Land - DX (USA)::Romhack 50 | Super Mario Land 2 - 6 Golden Coins DX (USA)::Romhack 51 | Survival Kids 2 - Escape the Twin Islands! (USA)::Romhack 52 | Wario Land - Super Mario Land 3 DX (USA)::Romhack 53 | 54 | # GB 55 | Mario's Picross 2 (USA) (SGB Enhanced)::Romhack 56 | Pokemon - Brown Version::Romhack 57 | Pokemon - Green Version (USA, Europe) (SGB Enhanced)::Romhack 58 | Super Mario Land 4 (Unknown) (Ja) (Unl) (Pirate)::Romhack 59 | 60 | # GBA 61 | Action Replay GBX (Europe) (En,Fr,De,It) (Alt 1) (Unl)::Redirect::Action Replay GBX (Europe) (En,Fr,De,It) (Unl) (Alt) 62 | Aero the Acro-Bat - Rascal Rival Revenge (Europe)::Redirect::Aero the Acro-Bat (Europe) 63 | Aero the Acro-Bat - Rascal Rival Revenge (USA)::Redirect::Aero the Acro-Bat (USA) 64 | AGS Aging Cartridge (World) (v7.0)::Redirect::AGS Aging Cartridge (World) (Rev 1) (v7.0) (Test Program) 65 | AGS Aging Cartridge (World) (v7.1)::Redirect::AGS Aging Cartridge (World) (v7.1) (Test Program) 66 | Care Bears - The Care Quests (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)::Redirect::Care Bears - The Care Quest (Europe) (En,Fr,De,Es,It,Nl 67 | Care Bears - The Care Quests (USA) (En,Fr,Es)::Redirect::Care Bears - The Care Quest (USA) (En,Fr,Es) 68 | Celeste Classic::Homebrew 69 | Donchan Puzzle - Hanabi de Dohn! Advance (Japan)::Redirect::Don-chan Puzzle - Hanabi de Doon! Advance (Japan) 70 | Fear Factor Unleashed (USA)::Redirect::Fear Factor - Unleashed (USA) 71 | Fire Emblem - The Binding Blade (USA)::Romhack 72 | GameShark GBA (USA) (Alt 1) (Unl)::Redirect::GameShark GBA (USA) (Unl) (Alt) 73 | GBA TV Tuner PAL (China) (v1.3) (Unl)::Redirect::GB-A TV Tuner PAL (China) (v1.3) (Unl) 74 | GBA TV Tuner PAL (China) (v2.0) (Unl)::Redirect::GB-A TV Tuner PAL (China) (v2.0) (Unl) 75 | Jump (Japan)::e-Reader 76 | Mandrake the Magician (Italy) (Proto)::Redirect::Mandrake the Magician (Italy) (It) (Proto) 77 | Memory Dumps - Promotional (USA, Australia)::e-Reader 78 | Mother 1+2 (USA)::Romhack 79 | Mother 3 (USA)::Romhack 80 | Motoracer Advance (Europe) (En,Fr,De,Es,It)::Redirect::Moto Racer Advance (Europe) (En,Fr,De,Es,It) 81 | Motoracer Advance (USA) (En,Fr,De,Es,It)::Redirect::Moto Racer Advance (USA) (En,Fr,De,Es,It) 82 | Overstorm (Unknown) (Proto)::Redirect::Overstorm (Europe) (Proto) 83 | Phantom, The (Italy) (Proto)::Redirect::Phantom, The (Italy) (It) (Proto) 84 | Pokemon (USA)::Unknown bootleg or romhack 85 | Pokemon - Doel Deoxys Distribution (Netherlands) (Kiosk)::Redirect::Pokemon - Doel Deoxys Distribution (Netherlands) (En) (Kiosk) 86 | Pokemon Channel - Line Art Card - 11-A101 - The Pikachu Star (USA, Australia) (Promo)::e-Reader 87 | Pokemon Channel - Line Art Card - 11-A102 - The Kyogre Constellation (USA) (Promo)::e-Reader 88 | Pokemon Channel - Line Art Card - 11-P101 - Jirachi (Australia) (Promo)::e-Reader 89 | Pokemon Channel - Paint Pattern Card - 11-A103 - Poke A La Card (USA, Australia) (Promo)::e-Reader 90 | Ratatouille (Greece) (En)::Redirect::Ratatouille (Australia, Greece) (En) 91 | SN Systems (Europe)::Redirect::SN Systems (Europe) (Test Program) (Unl) 92 | Tiny Chao Garden (USA) (Ja) (Phantasy Star Online Episode I _ II)::Gamecube rom 93 | Toyrobo Force (Japan)::Redirect::Toy Robo Force (Japan) 94 | Travis Pastrana's Pro MotoX (USA, Europe) (Proto) (2002-12-12)::Redirect::Travis Pastrana's Pro MotoX (USA) (Proto) (2002-12-12) 95 | Xploder Advance (Europe) (Alt 1) (Unl)::Redirect::Xploder Advance (Europe) (Unl) (Alt) 96 | '@ -split '\r?\n' | Where-Object { 97 | 98 | # Filter out empty lines and lines beginning with # 99 | $_ -notmatch '(^#|^\s*$)' 100 | } 101 | 102 | #Functions 103 | Function Remove-CacheDir { 104 | Param( 105 | [switch]$Init 106 | ) 107 | if ( Get-Item -EA Ignore $tempWorkspace\* ) { 108 | if ( $Init ) { 109 | Write-Warning "Removing stale temporary workspace files at $tempWorkspace" 110 | } 111 | Remove-Item $tempWorkspace\* -Force -Recurse 2> $null 112 | } 113 | } 114 | 115 | Function New-CacheDir { 116 | if ( !( Test-Path -PathType Container $tempWorkspace ) ) { 117 | New-Item -ItemType Directory $tempWorkspace > $null 118 | } 119 | } 120 | 121 | Function Initialize-CacheDir { 122 | Remove-CacheDir -Init 123 | New-CacheDir 124 | } 125 | 126 | Function Copy-ToClipboard { 127 | # This will work on Windows 128 | # Best effort for *nix. xclip must be present to work 129 | [CmdletBinding()] 130 | Param( 131 | [string]$String, 132 | [switch]$NoNewLines 133 | ) 134 | 135 | if ( ( $clipBin = ( Get-Command -EA SilentlyContinue -CommandType Application clip.exe ).Source ) ) { 136 | $String | & $clipBin *> $null 137 | } 138 | elseif ( ( $clipBin = ( Get-Command -EA SilentlyContinue -CommandType Application xclip ).Source ) ) { 139 | $String | & $clipBin *> $null 140 | } 141 | } 142 | 143 | ## BEGIN MENU FRAMEWORK 144 | 145 | Function Show-Menu { 146 | Param( 147 | [string]$Title = 'Menu', 148 | [string]$InputPrompt = 'Enter the corresponding number to make a menu selection', 149 | [string[]]$Options = @(), 150 | [string]$Message, 151 | [string]$CancelSelectionLabel = 'Cancel', 152 | [int]$CancelOptionValue = -1, 153 | [switch]$NoAutoCancel 154 | ) 155 | 156 | [string[]]$useOptions = if ( !$NoAutoCancel ) { 157 | $Options + $CancelSelectionLabel 158 | } 159 | else { 160 | $Options 161 | } 162 | 163 | # Keep track of empty lines 164 | [int[]]$subtractAtIndices = for ( $o = 0; $o -lt $useOptions.Count; $o++ ) { 165 | $option = $useOptions[$o] 166 | if ( [string]::IsNullOrWhiteSpace($option) ) { 167 | $o 168 | } 169 | } 170 | 171 | Clear-Host 172 | 173 | Write-Host "===== $Title =====$([Environment]::NewLine)" -ForegroundColor Blue 174 | 175 | if ( $Message ) { 176 | Write-Host "$Message$([Environment]::NewLine)" -ForegroundColor Cyan 177 | } 178 | 179 | # Don't increase the input counter for empty lines 180 | $offset = 1 181 | for ( $i = 0; $i -lt $useOptions.Count; $i++ ) { 182 | $option = $useOptions[$i] 183 | if ( $i -in $subtractAtIndices ) { 184 | $offset -= 1 185 | Write-Host 186 | } 187 | else { 188 | Write-Host "`t$($i+$offset): $($useOptions[$i])" -ForegroundColor Green 189 | } 190 | } 191 | 192 | Write-Host 193 | do { 194 | try { 195 | [int]$selection = Read-Host -Prompt $InputPrompt 196 | } 197 | catch { 198 | Write-Warning 'Numeric entries only' 199 | } 200 | } while ( ( $selection -lt 1 ) -or ( $selection -gt ( $useOptions.Count - $subtractAtIndices.Count ) ) ) 201 | 202 | if ( !$NoAutoCancel -and $selection -eq ( $useOptions.Count - $subtractAtIndices.Count ) ) { 203 | $CancelOptionValue 204 | } 205 | else { 206 | $selection 207 | } 208 | } 209 | 210 | Function Show-OKMenu { 211 | Param( 212 | [string]$Title = 'Menu', 213 | [string]$Message 214 | ) 215 | 216 | Show-Menu -Title $Title -Options 'OK' -Message $Message -NoAutoCancel > $null 217 | } 218 | 219 | ## END MENU FRAMEWORK 220 | 221 | Function Show-Instructions { 222 | Show-OKMenu -Title Instructions -Message @" 223 | Workspace Folder: $tempWorkspace 224 | 225 | This is an interactive tool to assist with downloading image libraries from the 226 | libretro-thumbnail repositories available on Github at: 227 | 228 | https://github.com/libretro-thumbnails/libretro-thumbnails 229 | 230 | and converting them to Analogue OS' ``.bin`` format. Each step is meant to be 231 | worked in order, but you can skip downloading the console library and DAT 232 | file if you already have them from a previous run. 233 | 234 | The "Download Console Image Library" step now enumerates the available image 235 | libraries so you don't have to go to Github and get the archive link yourself. 236 | However, if the site parsing breaks at any time, the old method of obtaining 237 | the ZIP link yourself is still supported with the legacy step 238 | "Download Console Image Library (Manual)". Use this if you have problems with 239 | the new download step. 240 | 241 | This tool can only download and convert images for one console at a time. 242 | Downloading a new console's images or cleaning the working directory will erase 243 | all working images along with the working diretory itself. Use the "Move" 244 | options to move the converted libraries to a permanent location on disk. 245 | 246 | At this time of writing only GBA, GB, GBC, and GG images are useful for 247 | conversion, but any libretro-thumbnail repository should be compatible. 248 | This ensures future compatibility as new cores are released and Library 249 | images become supported on openFPGA cores. 250 | 251 | The step ``How to copy to Analogue OS`` will have further details on installing 252 | the image library to your SD card, but in a nutshell Library images should be 253 | placed in the following folder, where CONSOLE is an identifier for the 254 | target game console: ``/System/Library/Images/CONSOLE/`` 255 | 256 | This tool is only supported on Windows as it relies on .NET types which are 257 | not available on MacOS or Linux. 258 | "@ 259 | } 260 | 261 | Function Show-GetConsoleImages { 262 | Param( 263 | [switch]$Manual 264 | ) 265 | if ( $Manual ) { 266 | Copy-ToClipboard $libretro_repo 267 | 268 | Show-OKMenu -Title 'Download Console Image Library' -Message @" 269 | Follow these steps, then select option 1 to continue: 270 | 271 | 0. If you already have the image archive downloaded, you may simply provide the path to the archive 272 | rather than the URL to skip the download. Otherwise, follow the instructions below. Note that 273 | an alternative archive location will not be cleaned up when the working directory initializes, 274 | unless you have saved the archive under "$tempWorkspace". 275 | 276 | 1. Go to $libretro_repo in a web browser. 277 | 278 | **The URL has been copied to your clipboard for your convenience** 279 | 280 | 2. Find the folder for the system you want to generate an image pack for. Click its link 281 | to be taken to the repository for that console's images. 282 | 283 | 3. Obtain the URL to this repo's ZIP archive by clicking the "Code" button, then right click 284 | "Download ZIP" and select "Copy Link" or "Copy Link Address". 285 | 286 | **Note that right clicking will paste in the terminal** 287 | 288 | 4. We will use this link in the next step. Select OK once you have copied the ZIP archive URL. 289 | "@ 290 | 291 | Write-Host 292 | $packUrl = Read-Host 'Please paste the libretro-thumbnail console repo ZIP link now' 293 | } 294 | else { 295 | Write-Host @' 296 | A picker will open up with consoles to select from. Please select a console you wish to 297 | generate an Analogue OS image library for. You can use the search filter to narrow down the 298 | selection list. 299 | '@ 300 | do { 301 | $packUrl = ( Get-LibretroThumbnailConsoleImageLinks | Out-GridView -Title 'Select a console' -OutputMode Single ).ZipLink 302 | if ( !$packUrl ) { 303 | Write-Warning 'No console was selected. Please select a console using the picker.' 304 | Pause 305 | } 306 | } while ( !$packUrl ) 307 | } 308 | Write-Host 'Initializing working directory' 309 | Initialize-CacheDir 310 | 311 | # Download the file if it's a URL 312 | if ( [uri]::IsWellFormedUriString($packUrl, 'Absolute') ) { 313 | Write-Host "Downloading $packUrl to $tempWorkspace" 314 | 315 | # Sidestep time-consuming byte-counting bug with the progress bar 316 | $oldProgressPreference = $ProgressPreference 317 | $ProgressPreference = 'SilentlyContinue' 318 | 319 | $startTime = Get-Date 320 | try { 321 | Invoke-WebRequest -OutFile $tempZipPath -UseBasicParsing $packUrl 322 | 323 | } 324 | finally { 325 | $ProgressPreference = $oldProgressPreference 326 | Write-Host "Download finished in $(( Get-Date ) - $startTime)" 327 | } 328 | } 329 | else { 330 | # Otherwise assume it's a path to the archive on disk 331 | $tempZipPath = ( ( $packUrl ) -replace '^["'']' ) -replace '["'']$' 332 | if ( Test-Path -LiteralPath $tempZipPath -PathType Leaf ) { 333 | Write-Host "Using provided archive location on disk: $tempZipPath" 334 | } 335 | else { 336 | Write-Warning "A URL was not provided and the value of ""$packUrl"" does not appear to be an existing file. Aborting." 337 | Pause 338 | return 339 | } 340 | } 341 | 342 | Write-Host "Unzipping $tempZipPath to $tempExtractionPath" 343 | Expand-ImageArchive -Path $tempZipPath -DestinationPath $tempExtractionPath 344 | 345 | Write-Host 'Done' 346 | Pause 347 | } 348 | 349 | Function Show-DatFileHowTo { 350 | Copy-ToClipboard $datomatic_site 351 | 352 | Show-OKMenu -Title 'Download DAT file from DAT-O-MATIC' -Message @" 353 | Follow these instructions to obtain a suitable no-intro DAT file for use with 354 | identifying each image: 355 | 356 | 1. Go to https://datomatic.no-intro.org in a web browser. 357 | 358 | **The URL has been copied to your clipboard for your convenience** 359 | 360 | 2. Click the "Download" button in the site header, then click "Standard DAT". 361 | 362 | 3. Pick the target system from the "System" dropdown. 363 | You should not need to change the defaults. 364 | 365 | 4. Click the "Prepare" button, then click the resulting "Download" button. 366 | 367 | 5. Extract the .dat file from the downloaded ZIP archive. 368 | 369 | 6. Make a note of the path to the downloaded ZIP file. 370 | You will need to provide the path to the DAT file for any of the "Create" 371 | selections from the main menu. 372 | 373 | If you have obtained a DAT file from DAT-O-MATIC previously, you can skip this 374 | step. Just remember to provide the path to the DAT file in the "Create" steps. 375 | 376 | DAT-O-MATIC ``.dat`` files do not have records for every game available in 377 | libratro-thumbnails and may be missing romhacks as well. However, you can add 378 | additional entries to your DAT file if you know the cartridge ROM information 379 | using any text editor. 380 | "@ 381 | } 382 | 383 | Function Show-ConvertPrompt { 384 | Param( 385 | [Parameter(Mandatory)] 386 | [ValidateSet('BoxArts', 'Snaps', 'Titles')] 387 | [string]$LibraryType 388 | ) 389 | 390 | # Make sure to remove any surrounding quotes from the input string 391 | $datPath = ( ( Read-Host 'Paste the path to your DAT file for the target console' ) -replace '^["'']' ) -replace '["'']$' 392 | $dat = Get-Dat -DatFile $datPath 393 | $outdir = Get-Variable -ValueOnly "tempConversion${LibraryType}Dir" 394 | 395 | # Determine scaling mode to use 396 | $scaleMode = if ( $LibraryType -eq 'BoxArts' ) { 397 | 'BoxArts' 398 | } 399 | else { 400 | 'Original' 401 | } 402 | Convert-Images -InputDirectory "$tempExtractionPath\$LibraryType" -OutputDirectory $outdir -Dat $dat -ScaleMode $scaleMode 403 | Pause 404 | } 405 | 406 | Function Show-MovePrompt { 407 | Param( 408 | [ValidateSet('BoxArts', 'Snaps', 'Titles')] 409 | [string]$LibraryType 410 | ) 411 | 412 | # Make sure any surrounding quotes are removed 413 | $path = if ( [string]::IsNullOrWhiteSpace($LibraryType) ) { 414 | ( ( Read-Host 'Provide a directory to move your converted image library folders to' ) -replace '^["'']' ) -replace '["'']$' 415 | } 416 | else { 417 | ( ( Read-Host "Provide a directory to move your $LibraryType image library to" ) -replace '^["'']' ) -replace '["'']$' 418 | } 419 | 420 | if ( !( Test-Path -PathType Container $path ) ) { 421 | Write-Host "Creating directory: $path" 422 | try { 423 | New-Item -ItemType Directory -Force $path > $null 424 | } 425 | catch { 426 | Write-Warning "An error occurred creating the directory: $($_.Exception.Message)" 427 | return 428 | } 429 | } 430 | 431 | Write-Host "Moving $LibraryType library to $path" 432 | $source = switch ( $LibraryType ) { 433 | 'BoxArts' { 434 | if ( ( Test-Path -PathType Container $tempConversionBoxArtsDir ) ) { 435 | "$tempConversionBoxArtsDir\*" 436 | } 437 | break 438 | } 439 | 440 | 'Snaps' { 441 | if ( ( Test-Path -PathType Container $tempConversionSnapsDir ) ) { 442 | "$tempConversionSnapsDir\*" 443 | } 444 | break 445 | } 446 | 447 | 'Titles' { 448 | if ( ( Test-Path -PathType Container $tempConversionTitlesDir ) ) { 449 | "$tempConversionTitlesDir\*" 450 | } 451 | break 452 | } 453 | 454 | # All if no specified type 455 | { [string]::IsNullOrWhiteSpace($LibraryType) } { 456 | if ( ( Test-Path -PathType Container $tempConversionBoxArtsDir ) ) { 457 | $tempConversionBoxArtsDir 458 | } 459 | 460 | if ( ( Test-Path -PathType Container $tempConversionSnapsDir ) ) { 461 | $tempConversionSnapsDir 462 | } 463 | 464 | if ( ( Test-Path -PathType Container $tempConversionTitlesDir ) ) { 465 | $tempConversionTitlesDir 466 | } 467 | 468 | break 469 | } 470 | } 471 | 472 | if ( $source ) { 473 | Move-Item -Force -Path $source -Destination $path > $null 474 | } 475 | else { 476 | Write-Warning 'Could not find converted images (did you convert the images yet?)' 477 | } 478 | Pause 479 | } 480 | 481 | Function Show-HowToInstallMenu { 482 | Show-OKMenu -Title 'How to copy to Analogue OS' -Message @' 483 | You can use one of the "Move" steps to either copy your image library to a 484 | location on your PC, or if your Analogue OS SD card is mounted you can move 485 | them directly to the image path for your console on the SD card. 486 | 487 | The path is as follows from the root of the SD card: 488 | /System/Library/Images/CONSOLE/*.bin 489 | 490 | The only known console identifiers for the CONSOLE portion of the path at this 491 | time of writing are: 492 | 493 | - Gameboy Advance: GBA 494 | - Gameboy/Gameboy Color: GB 495 | - Game Gear: GG 496 | 497 | If you use the "Move All Libraries" step it will create subfolders for any 498 | library type you previously converted to `.bin` format. This is not 499 | compatible with the Analogue OS Library image layout, so you should not 500 | "Move All Libraries" directly to the SD card. 501 | 502 | Instead, "Move All Libraries" to a location on your PC, then using File 503 | Explorer copy the subfolder contents for the Library image type you want 504 | to the SD card using the layout above. 505 | '@ 506 | } 507 | 508 | Function Show-OtherToolsMenu { 509 | $options = , 510 | 'Create Custom Palette File', # 1 511 | 'Create Custom Pallete File (Color Picker)', # 2 512 | 'Create GBC Boot Rom Palettes' # 3 513 | 514 | $selection = Show-Menu -Title 'Other Tools' -Options $options -CancelSelectionLabel Back -Message @' 515 | Functions and features not necessarily related to image generation 516 | '@ 517 | 518 | switch ( $selection ) { 519 | 520 | # Create Custom Palette File 521 | 1 { 522 | Clear-Host 523 | $path = ( ( Read-Host -Prompt 'Please enter the filepath to save the palette file to (*.pal)' ) -replace '^["'']' ) -replace '["'']$' 524 | if ( $path -notmatch '\.\S+$' ) { 525 | $path = "$path.pal" 526 | } 527 | New-PaletteFile -FilePath $path 528 | Pause 529 | break 530 | } 531 | 532 | # Create Custom Pallete File (Color Picker) 533 | 2 { 534 | Clear-Host 535 | Show-ColorPickerPrompt 536 | Pause 537 | break 538 | } 539 | 540 | # Create GBC Boot Rom Palettes 541 | 3 { 542 | Clear-Host 543 | if ( !( Get-InstalledModule -EA Ignore 7Zip4Powershell ) ) { 544 | $newline = [Environment]::NewLine 545 | while ( ( $consent = Read-Host -Prompt "This operation requires the ''7Zip4Powershell'' module.${NEWLINE}Would you like to install it in the user scope? (Y/N)") -notin 'y', 'n' ) {} 546 | if ( $consent -eq 'n' ) { 547 | break 548 | } 549 | } 550 | Show-GBCBootRomInstructions 551 | Pause 552 | Break 553 | } 554 | } 555 | } 556 | 557 | Function Show-GBCBootRomInstructions { 558 | Show-OKMenu -Message @' 559 | Once this prompt is dismissed, information about the palettes stored inside 560 | the GBC boot rom will be obtained from https://tcrf.net, then will use that 561 | information to generate the following palettes: 562 | 563 | - Game Palettes 564 | - Button Combo Palettes 565 | - Unused Palettes 566 | 567 | They will be saved in the current directory under the "gbc_palettes" folder. 568 | 569 | Game Palettes are the palettes used for certain GB games on the GBC that were 570 | not GB-enhanced, such as Metroid II and Pokemon Red/Blue. The "_.pal" 571 | file is a "dummy" palette used for unrecognized and unlicensed GB games. 572 | 573 | Button Combo Palettes are the alternative palettes that non-enhanced GB 574 | games could be set to with button presses and combinations. 575 | 576 | Unused Palettes are palettes available in the GBC boot rom but have colors or 577 | color combinations that are not used by any game. 578 | 579 | To install on your Pocket, copy the palette folders to 580 | "/Assets/gb/common/Palettes" on the Pocket's SD card. 581 | '@ 582 | 583 | Invoke-BootRomPaletteCreation 584 | } 585 | 586 | Function Show-ColorPickerPrompt { 587 | Show-OKMenu -Message @' 588 | A color picker will pop up for each palette color (12 total). 589 | 590 | There are 4 colors each for the BG, OBJ0, and OBJ1 palette groups. 591 | Generally, for each palette type, usually the color should get darker 592 | per index but this is not a requirement. 593 | 594 | Inputting values for each color into the picker is valid, but you 595 | must use numeric values 0-255, it does not accept hex values like 596 | FF (255). These values will be converted to hexadecimal digits when 597 | creating the palette file. 598 | 599 | LCDOff is set to FFFFFF by default. 600 | 601 | If you are unsure of what these colors represent, you may reference the 602 | following site if you wish to re-create one of the built-in palettes from 603 | the Game Boy Color: 604 | 605 | https://tcrf.net/Notes:Game_Boy_Color_Bootstrap_ROM 606 | 607 | Click cancel to abort at any time, except when canceling while setting LCDOff which 608 | will continue with FFFFFF (White) as the LCDOff color. 609 | '@ 610 | 611 | $path = ( ( Read-Host -Prompt 'Please enter the filepath to save the palette file to (*.pal)' ) -replace '^["'']' ) -replace '["'']$' 612 | # Give an extension if none provided 613 | if ( $path -notmatch '\.\S+$' ) { 614 | $path = "$path.pal" 615 | } 616 | 617 | $paletteHash = Invoke-ColorPicker 618 | New-PaletteFile -FilePath $path @paletteHash 619 | } 620 | 621 | Function Get-TcrfBootRomPaletteArchive { 622 | Param( 623 | [Uri]$Url = 'https://tcrf.net/images/a/af/CGB_Bootstrap_ROM_tables.7z', 624 | [string]$OutFile = './boot_rom_tables/palettedefs.7z' 625 | ) 626 | 627 | $outDir = Split-Path -Parent $OutFile 628 | 629 | if ( !( Test-Path -PathType Container $outDir ) ) { 630 | New-Item -ItemType Directory $outDir > $null 631 | } 632 | 633 | $oldProgressPreference = $ProgressPreference 634 | try { 635 | Invoke-WebRequest $Url -OutFile "$OutFile" 636 | } 637 | finally { 638 | $ProgressPreference = $oldProgressPreference 639 | } 640 | 641 | [System.IO.FileInfo]$OutFile 642 | } 643 | 644 | Function Expand-PaletteArchive { 645 | Param( 646 | [Parameter(Mandatory)] 647 | [Alias('Path')] 648 | [string]$ArchivePath, 649 | [string]$OutDir = './' 650 | ) 651 | 652 | # Check for and install module 653 | if ( !( Get-InstalledModule -EA SilentlyContinue 7Zip4Powershell ) ) { 654 | Write-Host 'Installing required module for this operation: 7Zip4Powershell' 655 | Install-Module 7Zip4Powershell -Force -Scope CurrentUser 656 | } 657 | 658 | Expand-7Zip -ArchiveFileName $ArchivePath -TargetPath $OutDir 659 | } 660 | 661 | Function Invoke-BootRomPaletteCreation { 662 | Param( 663 | [string]$OutDir = './gbc_palettes' 664 | ) 665 | Write-Host 'Downloading boot rom palette archive' 666 | $file = Get-TcrfBootRomPaletteArchive 667 | 668 | Write-Host 'Expanding archive' 669 | Write-Host "ArchivePath: $($file.FullName)" 670 | Write-Host "OutDir: $($file.DirectoryName)" 671 | Expand-PaletteArchive -ArchivePath $file.FullName -OutDir $file.DirectoryName 672 | 673 | # Note that these CSVs are TAB delimited 674 | $buttonCombosCsv = Import-Csv -Delimiter `t "$($file.DirectoryName)/listButtonCombos.csv" 675 | $unusedColorsCsv = Import-Csv -Delimiter `t "$($file.DirectoryName)/listUnusedColors.csv" 676 | $usedWithNamesCsv = Import-Csv -Delimiter `t "$($file.DirectoryName)/listUsedWNames.csv" 677 | 678 | # Button Combo Palettes 679 | $subdir = "$OutDir/Button Combo Palettes" 680 | if ( !(Test-Path -PathType Container $subdir ) ) { 681 | New-Item -ItemType Directory $subdir > $null 682 | } 683 | 684 | foreach ( $config in $buttonCombosCsv ) { 685 | # For name use Table Number followed by Table Entry in 2 digit hex form e.g. 0102 for TN 0x01 with TE 0x02 686 | $name = $config.'Button Combo' 687 | try { 688 | # Remove # from the hex code in the csv 689 | $paletteHash = @{ 690 | BG0 = $config.'BG Color 0x00'.Remove(0, 1) 691 | BG1 = $config.'BG Color 0x01'.Remove(0, 1) 692 | BG2 = $config.'BG Color 0x02'.Remove(0, 1) 693 | BG3 = $config.'BG Color 0x03'.Remove(0, 1) 694 | OBJ00 = $config.'OBJ0 Color 0x00'.Remove(0, 1) 695 | OBJ01 = $config.'OBJ0 Color 0x01'.Remove(0, 1) 696 | OBJ02 = $config.'OBJ0 Color 0x02'.Remove(0, 1) 697 | OBJ03 = $config.'OBJ0 Color 0x03'.Remove(0, 1) 698 | OBJ10 = $config.'OBJ1 Color 0x00'.Remove(0, 1) 699 | OBJ11 = $config.'OBJ1 Color 0x01'.Remove(0, 1) 700 | OBJ12 = $config.'OBJ1 Color 0x02'.Remove(0, 1) 701 | OBJ13 = $config.'OBJ1 Color 0x03'.Remove(0, 1) 702 | } 703 | 704 | Write-Host "Creating palette for button combo ""$name""" 705 | New-PaletteFile -FilePath "$subdir/$name.pal" @paletteHash 706 | } 707 | catch { 708 | Write-Warning "Failed to create palette file for ""NAMEHERE"": $_" 709 | } 710 | } 711 | 712 | # Unused Palettes 713 | $subdir = "$OutDir/Unused Palettes" 714 | if ( !(Test-Path -PathType Container $subdir ) ) { 715 | New-Item -ItemType Directory $subdir > $null 716 | } 717 | 718 | foreach ( $config in $unusedColorsCsv ) { 719 | # For name use Table Number followed by Table Entry in 2 digit hex form e.g. 0102 for TN 0x01 with TE 0x02 720 | $name = 'Unused {0}{1}' -f $config.'Table Number'.Remove(0, 2), $config.'Table Entry'.Remove(0, 2) 721 | try { 722 | # Remove # from the hex code in the csv 723 | $paletteHash = @{ 724 | BG0 = $config.'BG Color 0x00'.Remove(0, 1) 725 | BG1 = $config.'BG Color 0x01'.Remove(0, 1) 726 | BG2 = $config.'BG Color 0x02'.Remove(0, 1) 727 | BG3 = $config.'BG Color 0x03'.Remove(0, 1) 728 | OBJ00 = $config.'OBJ0 Color 0x00'.Remove(0, 1) 729 | OBJ01 = $config.'OBJ0 Color 0x01'.Remove(0, 1) 730 | OBJ02 = $config.'OBJ0 Color 0x02'.Remove(0, 1) 731 | OBJ03 = $config.'OBJ0 Color 0x03'.Remove(0, 1) 732 | OBJ10 = $config.'OBJ1 Color 0x00'.Remove(0, 1) 733 | OBJ11 = $config.'OBJ1 Color 0x01'.Remove(0, 1) 734 | OBJ12 = $config.'OBJ1 Color 0x02'.Remove(0, 1) 735 | OBJ13 = $config.'OBJ1 Color 0x03'.Remove(0, 1) 736 | } 737 | 738 | Write-Host "Creating palette for ""$name""" 739 | New-PaletteFile -FilePath "$subdir/$name.pal" @paletteHash 740 | } 741 | catch { 742 | Write-Warning "Failed to create palette file for ""NAMEHERE"": $_" 743 | } 744 | } 745 | 746 | # Game Palettes 747 | $subdir = "$OutDir/Game Palettes" 748 | if ( !(Test-Path -PathType Container $subdir ) ) { 749 | New-Item -ItemType Directory $subdir > $null 750 | } 751 | 752 | foreach ( $config in $usedWithNamesCsv ) { 753 | # multiple titles/revs sharing a palette are delimited by ;; 754 | $games = $config.Games -split ';;\s*' 755 | 756 | # Remove # from the hex code in the csv 757 | $paletteHash = @{ 758 | BG0 = $config.'BG Color 0x00'.Remove(0, 1) 759 | BG1 = $config.'BG Color 0x01'.Remove(0, 1) 760 | BG2 = $config.'BG Color 0x02'.Remove(0, 1) 761 | BG3 = $config.'BG Color 0x03'.Remove(0, 1) 762 | OBJ00 = $config.'OBJ0 Color 0x00'.Remove(0, 1) 763 | OBJ01 = $config.'OBJ0 Color 0x01'.Remove(0, 1) 764 | OBJ02 = $config.'OBJ0 Color 0x02'.Remove(0, 1) 765 | OBJ03 = $config.'OBJ0 Color 0x03'.Remove(0, 1) 766 | OBJ10 = $config.'OBJ1 Color 0x00'.Remove(0, 1) 767 | OBJ11 = $config.'OBJ1 Color 0x01'.Remove(0, 1) 768 | OBJ12 = $config.'OBJ1 Color 0x02'.Remove(0, 1) 769 | OBJ13 = $config.'OBJ1 Color 0x03'.Remove(0, 1) 770 | } 771 | 772 | foreach ( $game in $games ) { 773 | $useGame = if ( [string]::IsNullOrWhiteSpace($game)) { 774 | '_' 775 | } 776 | else { 777 | $game 778 | } 779 | try { 780 | Write-Host "Creating palette for ""$useGame""" 781 | New-PaletteFile -FilePath "$subdir/$useGame.pal" @paletteHash 782 | } 783 | catch { 784 | Write-Warning "Failed to create palette file for ""$useGame"": $_" 785 | } 786 | } 787 | } 788 | } 789 | 790 | Function Invoke-ColorPicker { 791 | Param( 792 | [switch]$PickLCDOff 793 | ) 794 | # Prepare required resources for color picker 795 | if ( !( 'System.Windows.Forms.ColorDialog' -as [type] ) ) { 796 | Write-Verbose 'Loading System.Windows.Forms' 797 | Add-Type -AssemblyName System.Windows.Forms 798 | } 799 | 800 | $picker = New-Object System.Windows.Forms.ColorDialog -Property @{ 801 | SolidColorOnly = $true 802 | FullOpen = $true 803 | AnyColor = $false 804 | } 805 | 806 | $hexFormatString = '{0:X2}{1:X2}{2:X2}' 807 | 808 | try { 809 | $paletteHash = @{} 810 | foreach ( $type in 'BG', 'OBJ0', 'OBJ1' ) { 811 | foreach ($i in 0..3) { 812 | 813 | $name = "$type$i" 814 | Write-Host "${name}: " -NoNewline 815 | 816 | if ( $picker.showDialog() -eq 'Cancel' ) { 817 | Write-Host 818 | throw 'User canceled the operation' 819 | } 820 | 821 | $paletteHash[$name] = $hexFormatString -f $picker.Color.R, $picker.Color.G, $picker.Color.B 822 | Write-Host $paletteHash[$name] 823 | } 824 | } 825 | 826 | if ( $PickLCDOff -and $picker.ShowDialog() -ne 'Cancel' ) { 827 | $paletteHash['LCDOff'] = $hexFormatString -f $picker.Color.R, $picker.Color.G, $picker.Color.B 828 | } 829 | } 830 | finally { 831 | if ( $picker ) { 832 | $picker.Dispose() 833 | $picker = $null 834 | } 835 | } 836 | 837 | # return the palette hash object 838 | $paletteHash 839 | } 840 | 841 | Function New-PaletteFile { 842 | [CmdletBinding(DefaultParameterSetName = 'HexValues')] 843 | Param( 844 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 845 | [ValidateLength(6, 6)] 846 | [string]$BG0, 847 | 848 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 849 | [ValidateLength(6, 6)] 850 | [string]$BG1, 851 | 852 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 853 | [ValidateLength(6, 6)] 854 | [string]$BG2, 855 | 856 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 857 | [ValidateLength(6, 6)] 858 | [string]$BG3, 859 | 860 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 861 | [ValidateLength(6, 6)] 862 | [string]$OBJ00, 863 | 864 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 865 | [ValidateLength(6, 6)] 866 | [string]$OBJ01, 867 | 868 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 869 | [ValidateLength(6, 6)] 870 | [string]$OBJ02, 871 | 872 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 873 | [ValidateLength(6, 6)] 874 | [string]$OBJ03, 875 | 876 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 877 | [ValidateLength(6, 6)] 878 | [string]$OBJ10, 879 | 880 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 881 | [ValidateLength(6, 6)] 882 | [string]$OBJ11, 883 | 884 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 885 | [ValidateLength(6, 6)] 886 | [string]$OBJ12, 887 | 888 | [Parameter(ParameterSetName = 'HexValues', Mandatory)] 889 | [ValidateLength(6, 6)] 890 | [string]$OBJ13, 891 | 892 | [Parameter(ParameterSetName = 'HexValues')] 893 | [ValidateLength(6, 6)] 894 | [string]$LCDOff = 'FFFFFF', 895 | 896 | [Parameter(Mandatory)] 897 | [string]$FilePath 898 | ) 899 | 900 | # .pal files must be 56 bytes per the analogue spec 901 | $bytes = [System.Collections.Generic.List[byte]]::new(56) 902 | 903 | foreach ( $type in 'BG', 'OBJ0', 'OBJ1' ) { 904 | # Analogue expects each color per type in reverse order from TCRF's docs 905 | $paramNames = $PSBoundParameters.Keys | Where-Object { $_ -match "^$type" } | Sort-Object -Descending 906 | 907 | foreach ( $param in $paramNames ) { 908 | # chunk the hex string into three pieces for R, G, and B values 909 | $splitColorHex = $PSBoundParameters[$param] -split '(.{2})' -ne '' 910 | if ( $splitColorHex[0] -eq '0x' ) { 911 | throw 'Hex values should not be prefixed with "0x"' 912 | } 913 | 914 | foreach ( $hex in $splitColorHex ) { 915 | $bytes.Add([byte]"0x$hex") 916 | } 917 | } 918 | } 919 | 920 | # Set Window to be same as background (per Analogue this is "normal") 921 | # TODO: Consider picker for Window colors once it's better understood how 922 | # this affects the palette. 923 | $W0Split = $PSBoundParameters['BG0'] -split '(.{2})' -ne '' 924 | $W1Split = $PSBoundParameters['BG1'] -split '(.{2})' -ne '' 925 | $W2Split = $PSBoundParameters['BG2'] -split '(.{2})' -ne '' 926 | $W3Split = $PSBoundParameters['BG3'] -split '(.{2})' -ne '' 927 | 928 | # Once again, Analogue expects reverse order from TCRF 929 | foreach ( $split in $W3Split, $W2Split, $W1Split, $W0Split) { 930 | foreach ( $hex in $split ) { 931 | $bytes.Add([byte]"0x$hex") 932 | } 933 | } 934 | 935 | $lcdoffSplit = $LCDOff -split '(.{2})' -ne '' 936 | foreach ( $hex in $lcdoffSplit ) { 937 | $bytes.Add([byte]"0x$hex") 938 | } 939 | 940 | # Write the footer 941 | # 0x81 APGB 942 | $bytes.AddRange([byte[]]@( 129, 65, 80, 71, 66 )) 943 | 944 | # Write to file 945 | [System.IO.File]::WriteAllBytes($FilePath, $bytes) 946 | } 947 | 948 | Function Get-LibretroThumbnailConsoleImageLinks { 949 | $oldProgressPreference = $ProgressPreference 950 | $ProgressPreference = 'SilentlyContinue' 951 | try { 952 | [array]$consoleLinks = ( Invoke-WebRequest -UseBasicParsing $libretro_repo ).Links | Where-Object { 953 | # Submodule links don't have a class 954 | !$_.class -and 955 | $_.href -match 'libretro-thumbnails/.+/tree' 956 | } 957 | } 958 | finally { 959 | $ProgressPreference = $oldProgressPreference 960 | } 961 | 962 | foreach ( $link in $consoleLinks ) { 963 | $Name = [regex]::Match($link.outerHTML, '(?<=\).+(?=\<\/a\>)').Value -replace '\s+@.*$' 964 | $linkPath = $link.href 965 | $targetZipName = "$([regex]::Match($linkPath, '(?<=tree/)\S+$')).zip" 966 | $targetRepoName = "$([regex]::Match($linkPath, '(?<=libretro-thumbnails\/)\S+?(?=\/)'))" 967 | # Write-Warning "ZipName: $targetZipName" 968 | # Write-Warning "RepoName: $targetRepoName" 969 | [PSCustomObject]@{ 970 | # Remove submodule commit ref from the text 971 | Name = $Name 972 | ZipLink = "$libretro_base/$targetRepoName/archive/$targetZipName" 973 | } 974 | } 975 | } 976 | 977 | function Confirm-Png { 978 | [CmdletBinding()] 979 | Param( 980 | [Parameter(Mandatory)] 981 | [string]$FilePath 982 | ) 983 | 984 | try { 985 | # Signature for PNG files 986 | [byte[]]$pngHeader = 137, 80, 78, 71, 13, 10, 26, 10 987 | 988 | # Read the first 8 bytes 989 | [System.IO.FileStream]$fStream = [System.IO.FileStream]::new($FilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) 990 | [byte[]]$actualHeader = [byte[]]::new(8) 991 | $fStream.Read($actualHeader, 0, 8) > $null 992 | 993 | if ( $Verbosity -eq 'Noisy' ) { 994 | Write-Verbose "Actual Header: $actualHeader" 995 | Write-Verbose " PNG Header: $pngHeader" 996 | } 997 | 998 | # Check that the bytes match the expected signature for PNG files 999 | # Return false on first mismatch 1000 | for ( $i = 0; $i -lt 8; $i++ ) { 1001 | if ( $actualHeader[$i] -ne $pngHeader[$i] ) { 1002 | return $false 1003 | } 1004 | } 1005 | 1006 | # Return true by default 1007 | $true 1008 | } 1009 | finally { 1010 | if ( $fStream ) { 1011 | $fStream.Dispose() 1012 | } 1013 | } 1014 | } 1015 | 1016 | # So far only a handful of box arts have been jpegs with a .png extension 1017 | # that I've noticed, and none of the titles are ones I own. Unable to confirm 1018 | # if this script is generating useful images for the Pocket to display in the 1019 | # cases where JPEGs are masquerading as PNGs. 1020 | function Confirm-Jpeg { 1021 | [CmdletBinding()] 1022 | Param( 1023 | [Parameter(Mandatory)] 1024 | [string]$FilePath, 1025 | [switch]$MismatchWarning 1026 | ) 1027 | 1028 | # Signature for PNG files 1029 | [byte[]]$jpgHeader = 255, 216 1030 | [byte[]]$jpgFooter = 255, 217 1031 | 1032 | # For JPEG detection we really need to look at the first and last two bytes, so read the whole thing 1033 | [byte[]]$fileBytes = [System.IO.File]::ReadAllBytes($FilePath) 1034 | [byte[]]$headerBytes = $fileBytes[0, 1] 1035 | [byte[]]$footerBytes = $fileBytes[-2, -1] 1036 | 1037 | # Free up bytes read for signature detection 1038 | $fileBytes = $null 1039 | [System.GC]::Collect() 1040 | 1041 | # Super noisy even for verbose output. Uncomment if debugging image headers 1042 | if ( $Verbosity -eq 'Noisy' ) { 1043 | Write-Verbose "`tActual Header: $headerBytes" 1044 | Write-Verbose "`t JPEG Header: $jpgHeader" 1045 | Write-Verbose "`tActual Footer: $footerBytes" 1046 | Write-Verbose "`t JPEG Footer: $jpgFooter" 1047 | } 1048 | 1049 | # Check that the bytes match the expected signature for JPG files 1050 | for ( $i = 0; $i -lt 2; $i++ ) { 1051 | if ( ( $headerBytes[$i] -ne $jpgHeader[$i] ) -and ( $footerBytes[$i] -ne $jpgFooter[$i] ) ) { 1052 | return $false 1053 | } 1054 | } 1055 | 1056 | # Return true by default 1057 | if ( $MismatchWarning -and ( $FilePath -notmatch '\.(jpg|jpeg)$' ) ) { 1058 | Write-Warning "JPEG masquerading: ""$FilePath""" 1059 | } 1060 | $true 1061 | } 1062 | 1063 | function Confirm-Image { 1064 | [CmdletBinding()] 1065 | Param( 1066 | [Parameter(Mandatory)] 1067 | [string]$FilePath, 1068 | [switch]$MismatchWarning 1069 | ) 1070 | 1071 | # Check if the file is an expected image format 1072 | if ( Confirm-Png $FilePath ) { 1073 | 'PNG' 1074 | } 1075 | elseif ( Confirm-Jpeg $FilePath -MismatchWarning:$MismatchWarning ) { 1076 | 'JPG' 1077 | } 1078 | } 1079 | 1080 | Function Convert-ImageToAnalogueBmp { 1081 | Param( 1082 | [Parameter(Mandatory)] 1083 | [string]$InFile, 1084 | [Parameter(Mandatory)] 1085 | [string]$OutFile, 1086 | [byte[]]$ImageHeader = @( 0x20, 0x49, 0x50, 0x41 ), 1087 | [Parameter(Mandatory)] 1088 | [ValidateSet('Original', 'BoxArts')] 1089 | [string]$ScaleMode, 1090 | [ValidateSet('PNG', 'JPG')] 1091 | [string]$SourceFormat = 'PNG' 1092 | ) 1093 | 1094 | $oldErrorActionPreference = $ErrorActionPreference 1095 | $ErrorActionPreference = 'Stop' 1096 | $success = $false 1097 | $returnObject = @{ 1098 | Output = try { 1099 | # Load required assemblies 1100 | if ( !( 'System.Windows.Media.Imaging.BitmapDecoder' -as [type] ) ) { 1101 | Add-Type -AssemblyName PresentationCore > $null 1102 | } 1103 | 1104 | # Read in the initial image as FileStream 1105 | [System.IO.FileStream]$imageStream = [System.IO.FileStream]::new( 1106 | "$(Resolve-Path -LiteralPath $InFile)", 1107 | [System.IO.FileMode]::Open, 1108 | [System.IO.FileAccess]::Read, 1109 | [System.IO.FileShare]::Read 1110 | ) 1111 | 1112 | # Read image FileStream into BitmapSource 1113 | $bitmapSource = switch ( $SourceFormat ) { 1114 | 1115 | 'PNG' { 1116 | [System.Windows.Media.Imaging.PngBitmapDecoder]::new( 1117 | $imageStream, 1118 | [System.Windows.Media.Imaging.BitmapCreateOptions]::PreservePixelFormat, 1119 | [System.Windows.Media.Imaging.BitmapCacheOption]::Default 1120 | ).Frames[0] 1121 | break 1122 | } 1123 | 1124 | 'JPG' { 1125 | [System.Windows.Media.Imaging.JpegBitmapDecoder]::new( 1126 | $imageStream, 1127 | [System.Windows.Media.Imaging.BitmapCreateOptions]::PreservePixelFormat, 1128 | [System.Windows.Media.Imaging.BitmapCacheOption]::Default 1129 | ).Frames[0] 1130 | break 1131 | } 1132 | } 1133 | 1134 | # Rotate -90 degrees per Analogue spec 1135 | $rotatedBitmap = [System.Windows.Media.Imaging.TransformedBitmap]::new( 1136 | $bitmapSource, 1137 | [System.Windows.Media.RotateTransform]::new(-90) 1138 | ) 1139 | 1140 | # Determine aspect ratio for scaling 1141 | # Only need height ratio as target canvas is landscape oriented 1142 | $scaledBitmap = switch ( $ScaleMode ) { 1143 | 'Original' { 1144 | # Don't scale at all, just use the rotated bitmap 1145 | $rotatedBitmap 1146 | break 1147 | } 1148 | 1149 | 'BoxArts' { 1150 | $scale = 165 / $rotatedBitmap.PixelHeight 1151 | 1152 | [System.Windows.Media.Imaging.TransformedBitmap]::new( 1153 | $rotatedBitmap, 1154 | [System.Windows.Media.ScaleTransform]::new($scale, $scale) 1155 | ) 1156 | break 1157 | } 1158 | } 1159 | 1160 | # Create new bitmap from rotated bitmap using BGRA32 pixel format 1161 | $convertedBitmap = [System.Windows.Media.Imaging.FormatConvertedBitmap]::new() 1162 | $convertedBitmap.BeginInit() 1163 | $convertedBitmap.Source = $scaledBitmap 1164 | $convertedBitmap.DestinationFormat = [System.Windows.Media.PixelFormats]::Bgra32 1165 | $convertedBitmap.EndInit() 1166 | 1167 | # Open output stream. We will write the transformed image data along with the 1168 | # Analogue header here. 1169 | 1170 | # Can't use Resolve-Path for non-existant files 1171 | $fullOutfileName = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutFile) 1172 | [System.IO.Stream]$imageStream2 = [System.IO.File]::OpenWrite($fullOutfileName) 1173 | $bytesWritten = 0 1174 | $imageStream2.Write($ImageHeader, 0, $ImageHeader.Length) 1175 | $bytesWritten += $ImageHeader.Length 1176 | 1177 | # Dimensions as bytes, reverse order for little endian 1178 | $h_bytes = [BitConverter]::GetBytes(( [int16]( $convertedBitmap.PixelHeight ) )) 1179 | $w_bytes = [BitConverter]::GetBytes(( [int16]( $convertedBitmap.PixelWidth ) )) 1180 | if ( !( [BitConverter]::IsLittleEndian ) ) { 1181 | [array]::Reverse($h_bytes) 1182 | [array]::Reverse($w_bytes) 1183 | } 1184 | 1185 | # Write image dimensions in bytes 1186 | $imageStream2.Write($h_bytes, 0, $h_bytes.Length) 1187 | $bytesWritten += $h_bytes.Length 1188 | $imageStream2.Write($w_bytes, 0, $w_bytes.Length) 1189 | $bytesWritten += $w_bytes.Length 1190 | 1191 | # Create pixel buffer and calculate stride 1192 | $pixels = [byte[]]::new(( $convertedBitmap.PixelWidth * $convertedBitmap.PixelHeight * 4 )) 1193 | $stride = $convertedBitmap.PixelWidth * 4 1194 | $convertedBitmap.CopyPixels($pixels, $stride, 0) 1195 | 1196 | # Write the image to the output file 1197 | $imageStream2.Write($pixels, 0, $pixels.Length) 1198 | $bytesWritten += $pixels.Length 1199 | 1200 | $success = $true 1201 | } 1202 | catch { 1203 | $_.Exception.Message 1204 | } 1205 | finally { 1206 | 1207 | if ( $imageStream ) { 1208 | $imageStream.Dispose() 1209 | $imageStream = $null 1210 | } 1211 | 1212 | if ( $imageStream2 ) { 1213 | $imageStream2.Dispose() 1214 | $imageStream2 = $null 1215 | } 1216 | 1217 | $bitmapSource = $null 1218 | $convertedBitmap = $null 1219 | $scaledBitmap = $null 1220 | 1221 | [System.GC]::Collect() 1222 | 1223 | $ErrorActionPreference = $oldErrorActionPreference 1224 | } 1225 | } 1226 | 1227 | $returnObject.Success = $success 1228 | $returnObject 1229 | } 1230 | 1231 | Function Get-Dat { 1232 | Param( 1233 | [string]$DatFile 1234 | ) 1235 | 1236 | [xml]$dat = Get-Content $DatFile 1237 | foreach ( $game in $dat.datafile.game ) { 1238 | @{ 1239 | Name = $game.name 1240 | CRC = $game.rom.crc 1241 | } 1242 | } 1243 | 1244 | $dat = $null 1245 | } 1246 | 1247 | Function Convert-Images { 1248 | [CmdletBinding()] 1249 | Param( 1250 | [Parameter(Mandatory)] 1251 | [string]$InputDirectory, 1252 | [Parameter(Mandatory)] 1253 | [string]$OutputDirectory, 1254 | [Parameter(Mandatory)] 1255 | [hashtable[]]$Dat, 1256 | [Parameter(Mandatory)] 1257 | [ValidateSet('Original', 'BoxArts')] 1258 | [string]$ScaleMode 1259 | ) 1260 | 1261 | # Bail on a bogus input directory 1262 | if ( !( Test-Path -PathType Container $InputDirectory ) ) { 1263 | Write-Warning 'Input directory does not exist (did you download the console images from step 1?)' 1264 | return 1265 | } 1266 | 1267 | # Create output directory if nonexistant 1268 | if ( !( Test-Path -PathType Container $OutputDirectory ) ) { 1269 | Write-Host "Creating output directory '$OutputDirectory'" 1270 | New-Item -Force -ItemType Directory $OutputDirectory > $null 1271 | } 1272 | 1273 | # Get list of files to convert 1274 | # libretro-thumbnails standardizes on PNG format 1275 | $filesToConvert = Get-ChildItem -LiteralPath "$(Resolve-Path $InputDirectory)" -File | Where-Object { 1276 | ( $_.Extension -match '^\.png$' ) -and 1277 | 1278 | # Ignore e-reader card entries, e-card names are usually suffixed with `-e` before the region 1279 | # TODO: This is breaking detection for Pokemon Kristall and Smargagd (German) due to the title 1280 | # not having a space between the hyphen prefixing "Edition" in the title 1281 | ( $_.BaseName -notmatch '^.*(-e|e\+).*\(' ) -and 1282 | 1283 | # Detect and remove romhacks of all types. Useless as Game Details won't show for these. 1284 | # Some hacks have no indication in the file name and cannot be detected in this way. 1285 | # TODO: Remove this and related checks if OpenFPGA ever supports image libraries, or if Analogue 1286 | # adds in something like a custom database users can provide that will trigger 1287 | # the Game Details page for otherwise unrecognized CRC IDs (would be useful 1288 | # for flashable cartridges, such as fan translation carts) 1289 | ( $_.BaseName -notmatch '(\[(Hack|T-)|\([\w\s,]*Hack)' ) -and 1290 | 1291 | # Ignore virtual console versions. These either have no physical counterpart or are identical 1292 | # to a retail version of the game. 1293 | ( $_.BaseName -notmatch 'Virtual Console' ) -and 1294 | 1295 | # Ignore pirate-flagged versions 1296 | ( $_.BaseName -notmatch '\([\w\s,]*Pirate' ) 1297 | } 1298 | 1299 | Write-Host 'Beginning conversion (this will take a while)' 1300 | $startTime = Get-Date 1301 | 1302 | # Convert each file to the .bin format 1303 | $convertedCount = 0 1304 | $results = $filesToConvert | ForEach-Object { 1305 | 1306 | $imageType = Confirm-Image $_.FullName -MismatchWarning 1307 | $inFile = if ( $imageType ) { 1308 | $_.FullName 1309 | } 1310 | else { 1311 | 1312 | # If the ".png" file is not actually an image, assume it's a symlink with contents pointing to the correct filename 1313 | $parent = Split-Path -Parent $_.FullName 1314 | $realName = ( Get-Content -Raw -LiteralPath $_.FullName ) -replace '\/', '\' 1315 | ( $realPath = "$parent\$realName" ) 1316 | 1317 | if ( $Verbosity -match '^(Noisy|Extra)$') { 1318 | Write-Verbose "Found symlink: ""$($_.FullName)"" => ""$realPath""" 1319 | } 1320 | 1321 | $imageType = Confirm-Image $realPath 1322 | } 1323 | 1324 | # Determine the base name to use, for special cases 1325 | $escapedTitle = [regex]::Escape($_.BaseName) 1326 | $useBaseName = if ( ( $match = $specialCaseTitles -match "^$escapedTitle::" ) ) { 1327 | $actualBase, $caseReason, $datRedirect = $match -split '::', 3 1328 | 1329 | if ( $caseReason -match '^Redirect' ) { 1330 | 1331 | # Sanity check 1332 | if ( !$datRedirect ) { 1333 | Write-Warning "Attempted redirect of ""$actualBase"" missing DAT reference (developer needs to fix!)" 1334 | $_.BaseName 1335 | } 1336 | else { 1337 | if ( $Verbosity -match '^(Extra|Noisy)$' ) { 1338 | Write-Verbose "Redirecting ""$actualBase"" to DAT file record ""$datRedirect""" 1339 | } 1340 | $datRedirect 1341 | } 1342 | } 1343 | else { 1344 | # If there's no other rule, ignore the title 1345 | if ( $Verbosity -match '^(Extra|Noisy)$' ) { 1346 | Write-Verbose "Ignoring ""$actualBase"": $caseReason" 1347 | } 1348 | 1349 | return @{ 1350 | Success = $false 1351 | Name = $actualBase 1352 | Output = "Ignored: $caseReason" 1353 | } 1354 | } 1355 | } 1356 | else { 1357 | $_.BaseName 1358 | } 1359 | 1360 | # Determine outfile name (skip if game not found) 1361 | $found = $false 1362 | foreach ( $game in $Dat ) { 1363 | 1364 | # Per `libretro-thumbnails` instructions, replace the following characters 1365 | # in the game name with an underscore, as these characters are illegal in 1366 | # file paths: 1367 | # 1368 | # &*/:`<>?\|" 1369 | $useGameName = $game.name -replace '[&\*/:`<>\?\\\|"]', '_' 1370 | 1371 | if ( $useBaseName -eq $useGameName ) { 1372 | $found = $true 1373 | $outFile = "$OutputDirectory\$($game.CRC).bin" 1374 | 1375 | $returnObj = Convert-ImageToAnalogueBmp -ScaleMode $ScaleMode $inFile $outFile -SourceFormat $imageType 1376 | $returnObj.Name = $_.BaseName 1377 | 1378 | if ($returnObj.Success ) { 1379 | $convertedCount++ 1380 | } 1381 | $returnObj 1382 | break 1383 | } 1384 | } 1385 | 1386 | if ( !$found ) { 1387 | Write-Verbose "Could not find a definition for ""$($_.BaseName)"" in the provided DAT, skipping..." 1388 | } 1389 | } 1390 | 1391 | $results | ForEach-Object { 1392 | if ( $OutputConvertedFiles ) { 1393 | $_ 1394 | } 1395 | elseif ( !( $_.Success ) ) { 1396 | Write-Host ( 'Did not convert "{0}": {1}' -f $_.Name, $_.Output ) 1397 | } 1398 | } 1399 | 1400 | $timespan = ( Get-Date ) - $startTime 1401 | Write-Host "Converted $convertedCount images in $timespan" 1402 | } 1403 | 1404 | Function Convert-SingleImage { 1405 | Param( 1406 | [Parameter(Mandatory)] 1407 | [string]$InFile, 1408 | [Parameter(Mandatory)] 1409 | [string]$OutFile, 1410 | [Parameter(Mandatory)] 1411 | [ValidateSet('Original', 'BoxArts')] 1412 | [string]$ScaleMode, 1413 | [byte[]]$ImageHeader 1414 | ) 1415 | 1416 | if ( ( $imageFormat = Confirm-Image $InFile ) ) { 1417 | $convArgs = @{ 1418 | ScaleMode = $ScaleMode 1419 | InFile = $InFile 1420 | OutFile = $OutFile 1421 | SourceFormat = $imageFormat 1422 | } 1423 | if ( $ImageHeader ) { 1424 | $convArgs['ImageHeader'] = $ImageHeader 1425 | } 1426 | 1427 | Convert-ImageToAnalogueBmp @convArgs 1428 | } 1429 | else { 1430 | Write-Host "This tool can only convert PNGs or JPGs" 1431 | } 1432 | } 1433 | 1434 | # We need a function that will extract the files to short paths 1435 | Function Expand-ImageArchive { 1436 | Param( 1437 | [Parameter(Mandatory)] 1438 | [string]$Path, 1439 | [Parameter(Mandatory)] 1440 | [string]$DestinationPath 1441 | ) 1442 | 1443 | # Load required assemblies 1444 | if ( !( 'System.IO.Compression.ZipFile' -as [type] ) ) { 1445 | Add-Type -AssemblyName System.IO.Compression.FileSystem > $null 1446 | } 1447 | 1448 | try { 1449 | $zip = [System.IO.Compression.ZipFile]::OpenRead($Path) 1450 | 1451 | # Treat top-level directory as optional, for the case where the archive is built from an 1452 | # archive export of the git repo 1453 | $boxartEntries = $zip.Entries | Where-Object { $_.FullName -match '^(.*/)?Named_BoxArts/.*\.png' } 1454 | $titleEntries = $zip.Entries | Where-Object { $_.FullName -match '^(.*/)?Named_Titles/.*\.png' } 1455 | $snapEntries = $zip.Entries | Where-Object { $_.FullName -match '^(.*/)?Named_Snaps/.*\.png' } 1456 | 1457 | if ( !( Test-Path -PathType Container "$DestinationPath\BoxArts" ) ) { 1458 | New-Item -EA Stop -ItemType Directory "$DestinationPath\BoxArts" > $null 1459 | } 1460 | $boxartEntries | ForEach-Object { 1461 | $entry = $_ 1462 | try { 1463 | [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, "$DestinationPath\BoxArts\$($entry.Name)", $true) 1464 | } 1465 | catch { 1466 | Write-Warning "Boxart $($entry.Name) failed to extract: $($_.Exception.Message)" 1467 | } 1468 | } 1469 | 1470 | if ( !( Test-Path -PathType Container "$DestinationPath\Titles" ) ) { 1471 | New-Item -EA Stop -ItemType Directory "$DestinationPath\Titles" > $null 1472 | } 1473 | $titleEntries | ForEach-Object { 1474 | $entry = $_ 1475 | try { 1476 | [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, "$DestinationPath\Titles\$($entry.Name)", $true) 1477 | } 1478 | catch { 1479 | Write-Warning "Title $($entry.Name) failed to extract: $($_.Exception.Message)" 1480 | } 1481 | } 1482 | 1483 | if ( !( Test-Path -PathType Container "$DestinationPath\Snaps" ) ) { 1484 | New-Item -EA Stop -ItemType Directory "$DestinationPath\Snaps" > $null 1485 | } 1486 | $snapEntries | ForEach-Object { 1487 | $entry = $_ 1488 | try { 1489 | [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, "$DestinationPath\Snaps\$($entry.Name)", $true) 1490 | } 1491 | catch { 1492 | Write-Warning "Snap $($entry.Name) failed to extract: $($_.Exception.Message)" 1493 | } 1494 | } 1495 | } 1496 | finally { 1497 | if ( $zip ) { 1498 | $zip.Dispose() 1499 | $zip = $null 1500 | } 1501 | } 1502 | } 1503 | 1504 | 1505 | # Main Execution 1506 | 1507 | $MainMenuOptions = 1508 | 'Instructions', # 1 1509 | '', 1510 | 'Download Console Image Library', # 2 1511 | 'Download Console Image Library (Manual)', # 3 1512 | 'Download No-Intro DAT file', # 4 1513 | '', 1514 | 'Create BoxArt Library', # 5 1515 | 'Create Title Library', # 6 1516 | 'Create Snaps Library', # 7 1517 | '', 1518 | 'Move BoxArt Library', # 8 1519 | 'Move Title Library', # 9 1520 | 'Move Snaps Library', # 10 1521 | 'Move All Libraries', # 11 1522 | '', 1523 | 'Open working directory', # 14 1524 | 'Clean up working directory', # 13 1525 | 'How to copy to Analogue OS', # 14 1526 | '', 1527 | 'Other Tools' # 15 1528 | 1529 | do { 1530 | $selection = Show-Menu -Title 'Analogue OS Library Image Pack Generator' -Options $MainMenuOptions -CancelSelectionLabel Quit -Message @' 1531 | This tool will download and generate an AnalogueOS-compliant 1532 | Library image pack from the libretro-thumbnails repositories. 1533 | 1534 | **This script may copy information to the clipboard.** 1535 | **Please ensure sure you have NOTHING IMPORTANT stored in the** 1536 | **clipboard before continuing** 1537 | 1538 | Press Ctrl+C at any time to quit this script. 1539 | '@ 1540 | try { 1541 | switch ( $selection ) { 1542 | 1543 | # INstructions 1544 | 1 { 1545 | Clear-Host 1546 | Show-Instructions 1547 | break 1548 | } 1549 | 1550 | # Download Console Image Library 1551 | 2 { 1552 | Clear-Host 1553 | Show-GetConsoleImages 1554 | break 1555 | } 1556 | 1557 | # Download Console Image Library (Manual) 1558 | 3 { 1559 | Clear-Host 1560 | Show-GetConsoleImages -Manual 1561 | break 1562 | } 1563 | 1564 | # Download No-Intro DAT File 1565 | 4 { 1566 | Clear-Host 1567 | Show-DatFileHowTo 1568 | break 1569 | } 1570 | 1571 | # Create BoxArt Library 1572 | 5 { 1573 | Clear-Host 1574 | Show-ConvertPrompt -LibraryType BoxArts 1575 | break 1576 | } 1577 | 1578 | # Create Titles Library 1579 | 6 { 1580 | Clear-Host 1581 | Show-ConvertPrompt -LibraryType Titles 1582 | break 1583 | } 1584 | 1585 | # Create Snaps Library 1586 | 7 { 1587 | Clear-Host 1588 | Show-ConvertPrompt -LibraryType Snaps 1589 | break 1590 | } 1591 | 1592 | # Move BoxArt Library 1593 | 8 { 1594 | Clear-Host 1595 | Show-MovePrompt -LibraryType BoxArts 1596 | break 1597 | } 1598 | 1599 | # Move Titles Library 1600 | 9 { 1601 | Clear-Host 1602 | Show-MovePrompt -LibraryType Titles 1603 | break 1604 | } 1605 | 1606 | # Move Snaps Library 1607 | 10 { 1608 | Clear-Host 1609 | Show-MovePrompt -LibraryType Snaps 1610 | break 1611 | } 1612 | 1613 | # Move All Libraries 1614 | 11 { 1615 | Clear-Host 1616 | Show-MovePrompt 1617 | break 1618 | } 1619 | 1620 | # Open working directory 1621 | 12 { 1622 | Clear-Host 1623 | Invoke-Item $tempWorkspace 1624 | break 1625 | } 1626 | 1627 | # Clean up working directory 1628 | 13 { 1629 | Write-Host 'Cleaning Up' 1630 | Remove-CacheDir 1631 | break 1632 | } 1633 | 1634 | # How to copy to Analogue OS 1635 | 14 { 1636 | Clear-Host 1637 | Show-HowToInstallMenu 1638 | break 1639 | } 1640 | 1641 | # Other Tools 1642 | 15 { 1643 | Clear-Host 1644 | Show-OtherToolsMenu 1645 | break 1646 | } 1647 | } 1648 | } 1649 | catch { 1650 | Write-Warning 'An error occurred and the current operation has been aborted.' 1651 | Write-Error -EA Continue -ErrorRecord $_ 1652 | Pause 1653 | } 1654 | } while ( $selection -ne -1 ) 1655 | 1656 | Write-Host -ForegroundColor Yellow 'Thank you, good bye!' -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2022 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Analogue Pocket Library Image Generator 2 | 3 | This is an interactive tool to assist with downloading image libraries from 4 | [libretro-thumbnail](https://github.com/libretro-thumbnails/libretro-thumbnails) 5 | and converting them to Analogue OS' ``.bin`` format. 6 | 7 | Note that at this time image libraries only seem to work with cartridge-based 8 | games. 9 | 10 | ## Note about OpenFPGA Cores 11 | Presumably, OpenFPGA cores would follow the same image library format when displaying 12 | game details. However, Analogue has yet to support Library images for titles launched 13 | through OpenFPGA cores. Until such a time that OpenFPGA cores support Library images, 14 | using these image libraries with ROMs launched through OpenFPGA cores cannot be 15 | supported. 16 | 17 | ## Quick Start 18 | To get started, run `AnalogueOSLibraryImageGenerator.ps1` in PowerShell. An 19 | interactive menu will guide you. It is recommended to read the **Introduction** 20 | before using the tool for the first time to get a feel for how it works. 21 | 22 | By default the output is minimal. You can use the following flags to control 23 | whether to show additional output when launching the script. Note that these 24 | flags will increase the conversion time a bit as writing to the console is not 25 | an instant operation: 26 | 27 | - `-OutputConvertedFiles`: Output the files which are converted to the console 28 | - `-Verbose`: Additional debugging output, useful when troubleshooting the script 29 | - `-Verbosity`: `Minimal`, `Extra`, or `Noisy`. Controls how much information is 30 | displayed when `-Verbose` is set. No effect otherwise. `Minimal` is the default. 31 | 32 | ## What game libraries can I convert? 33 | At this time of writing only GBA, GB, GBC, and GG images are useful for 34 | conversion, but any libretro-thumbnail repository should be compatible. 35 | This ensures future compatibility as official converters are released or if 36 | Library images ever become supported on openFPGA cores. 37 | 38 | ## Why didn't it generate a thumbnail for one of my games? 39 | Note that since this tool relies on both **libretro** and DAT files, it may miss 40 | thumbnails for games which don't have a **libretro-thumbnail** image or if entries 41 | are not found in your DAT file. For the latter, if you know the CRC of your 42 | cart or ROM, you can add to your DAT file at any time using the text editor of 43 | your choice. In particular, rom hacks are likely going to be missing from 44 | the DAT file you pull from [DAT-O-MATIC](https://datomatic.no-intro.org/), but there are a few missing retail games 45 | or revisions of them as well. 46 | 47 | Keep in mind that DAT-O-MATIC does split some classes of games into their own 48 | System. For example, GBA video cartridges have a special system classification 49 | and are not included in the normal DAT with most actual games. 50 | 51 | ## Where can I run this script? 52 | This tool is only supported on Windows as it relies on .NET types which are 53 | not available on MacOS or Linux. You may see some remnants in the source of a 54 | cross-platform direction, but this was quashed when I found the built-in namespaces 55 | for image manipulation are Windows-only or deprecated in non-Windows environments. 56 | 57 | I am aware that there are third-party libs/tools that can be leveraged for image 58 | manipulation, however, one of the goals of this project is portability and not 59 | requiring additional items to be installed. It is also why this script crawls 60 | the GitHub site to obtain the zip URLs instead of requiring `git` to be installed 61 | for cloning `libretro-thumbnails` and its submodules directly. 62 | 63 | That said, if anybody wants to contribute such functionality for Mac OS or Linux, 64 | pull requests are welcomed. Just try to keep any external dependencies to a 65 | "standard enough" minimum. 66 | 67 | ## Getting a "connection closed" error when downloading one of the `libretro-thumbnails` image libraries 68 | There's not a whole lot I can do about this, I hit this sometimes too when trying to convert 69 | the GBA library due to its size. GitHub terminates the code archive download if it runs for 70 | longer than 20 minutes, in my experience. This is likely due to the fact that GitHub 71 | creates code archives on-demand, and the time the exported archive remains cached is limited. 72 | 73 | As a workaround when this happens, if you choose `Download Console Image Library (Manual)`, 74 | and provide a file path (instead of a URL) to an archive that you've either: 75 | 76 | - [exported yourself from a cloned copy of the `git` repo](./create-local-archive.md); or 77 | - been able to obtain from `libretro-thumbnails` yourself via other means 78 | 79 | then the script will work with the local archive you've pointed to instead of attempting to 80 | obtain it from GitHub. Note that this script expects a `.zip` file; `.tar.gz` is not supported. 81 | 82 | ## I would just like an image library please 83 | Check out the latest [Release](https://github.com/codewario/PocketLibraryImages/releases/latest) 84 | page for the most recent sample image packs. 85 | 86 | If you have a problem with the latest version, all [Releases](https://github.com/codewario/PocketLibraryImages/releases/) 87 | have sample packs provided, so you may try one of those. You can find older stable versions of the 88 | script along with image packs for supported games at the time of release as well as prior versions 89 | of the script. Don't forget if you have a problem with the latest sample packs to 90 | please create an issue on the [project's issue tracker]([url](https://github.com/codewario/PocketLibraryImageConversion/issues)https://github.com/codewario/PocketLibraryImageConversion/issues). 91 | -------------------------------------------------------------------------------- /create-local-archive.md: -------------------------------------------------------------------------------- 1 | # Creating a local image archive 2 | 3 | Github seems to reset the connection after 20 mins, and often this happens before the archive is done downloading. 4 | The download cannot be resumed as the `content-length` isn't advertised due to the download occurring while 5 | GitHub does a `git archive` to export a zip. 6 | 7 | Follow these instructions to build a local zip to pass into the Pocket conversion script. **Git is required for this**. 8 | Supported consoles are used in the examples below but should work for any other system's repo. 9 | 10 | 1. Make sure long paths are turned on for git: `git config --system core.longpaths true` 11 | 12 | 2. `cd` to a suitable directory and run the following: 13 | 14 | - GBA: `git clone --depth 1 https://github.com/libretro-thumbnails/Nintendo_-_Game_Boy_Advance.git` 15 | - GB: `git clone --depth 1 https://github.com/libretro-thumbnails/Nintendo_-_Game_Boy.git` 16 | - GBC: `git clone --depth 1 https://github.com/libretro-thumbnails/Nintendo_-_Game_Boy_Color.git` 17 | - GG: `git clone --depth 1 https://github.com/libretro-thumbnails/Sega_-_Game_Gear.git` 18 | 19 | A fresh clone is recommended each time to save space, just make sure you remove the old working copy 20 | before cloning if it already exists. 21 | 22 | 3. Change to the directory you just cloned and run from the CLI (GBA export used in example below, use 23 | an appropriate file name for other systems): 24 | 25 | - GBA: `git archive --format=zip --output ../gba-thumbs.zip HEAD` 26 | - GB: `git archive --format=zip --output ../gb-thumbs.zip HEAD` 27 | - GBC: `git archive --format=zip --output ../gbc-thumbs.zip HEAD` 28 | - GG: `git archive --format=zip --output ../gg-thumbs.zip HEAD` 29 | 30 | 4. The image archive (close enough) as it would be downloaded from GitHub now exists one directory 31 | up at the path you provided for `--output`. 32 | 33 | Github code archives normally include the repo name and the branch as a prefix directory in the archive, 34 | but for the purposes of this process the script will also find the images if they are at the root of 35 | the archive, no prefix necessary. 36 | 37 | 5. Provide the full path to the archive when asked for a URL in the conversion script. Use the 38 | `Download Console Image Library (Manual)` selection to achieve this. -------------------------------------------------------------------------------- /specially-noted-titles.md: -------------------------------------------------------------------------------- 1 | # Specially noted games 2 | 3 | > Note: Currently still validating this list, potential entries based off games 4 | that couldn't be found in DAT files. Eventually, these special cases 5 | will be also codified in the script, but validation must occur first as there 6 | may be many false positives. 7 | 8 | This is a list of games that are explicit special cases, because they are either: 9 | 10 | - known romhacks that aren't detectable in the file name (ignore) 11 | - mismatched by name in the DAT file for the correct revision (redirect) 12 | - are otherwise useless for converting for use with Libraries on the Pocket today (ignore) 13 | 14 | Format is `ImageBaseName::Reason::RedirectToDATFileGameName` 15 | 16 | # GBC 17 | - Castlevania - The Adventure DX (USA)::Romhack 18 | - Castlevania II - Belmont's Revenge DX (USA)::Romhack 19 | - Donkey Kong Land - DX (USA)::Romhack 20 | - Donkey Kong Land 2 - DX (USA)::Romhack 21 | - Donkey Kong Land III DX (USA)::Romhack 22 | - Kirby's Dream Land - DX (USA)::Romhack 23 | - Kirby's Dream Land 2 - DX (USA)::Romhack 24 | - Legend of Zelda, The - Link's Awakening DX - Redux (USA)::Romhack 25 | - Metroid II - Return of Samus DX (USA)::Romhack 26 | - Pokemon - Blue Version DX (USA)::Romhack 27 | - Pokemon - Red Version DX (USA)::Romhack 28 | - Pokemon - Yellow Version - Special Pikachu Edition DX (USA)::Romhack 29 | - Pokemon Picross (USA)::Romhack 30 | - Pokemon Trading Card Game 2 - The Invasion of Team GR (USA)::Romhack 31 | - Super Mario Bros. Deluxe (Japan) (En) (Rev 1) (NP)::Redirect::Super Mario Bros. Deluxe (Japan) (NP) 32 | - Super Mario Land - DX (USA)::Romhack 33 | - Super Mario Land 2 - 6 Golden Coins DX (USA)::Romhack 34 | - Survival Kids 2 - Escape the Twin Islands! (USA)::Romhack 35 | - Wario Land - Super Mario Land 3 DX (USA)::Romhack 36 | 37 | # GB 38 | - Mario's Picross 2 (USA) (SGB Enhanced)::Romhack 39 | - Pokemon - Brown Version::Romhack 40 | - Pokemon - Green Version (USA, Europe) (SGB Enhanced)::Romhack 41 | - Super Mario Land 4 (Unknown) (Ja) (Unl) (Pirate)::Romhack 42 | 43 | # GBA 44 | - Action Replay GBX (Europe) (En,Fr,De,It) (Alt 1) (Unl)::Redirect::Action Replay GBX (Europe) (En,Fr,De,It) (Unl) (Alt) 45 | - Aero the Acro-Bat - Rascal Rival Revenge (Europe)::Redirect::Aero the Acro-Bat (Europe) 46 | - Aero the Acro-Bat - Rascal Rival Revenge (USA)::Redirect::Aero the Acro-Bat (USA) 47 | - AGS Aging Cartridge (World) (v7.0)::Redirect::AGS Aging Cartridge (World) (Rev 1) (v7.0) (Test Program) 48 | - AGS Aging Cartridge (World) (v7.1)::Redirect::AGS Aging Cartridge (World) (v7.1) (Test Program) 49 | - Care Bears - The Care Quests (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)::Redirect::Care Bears - The Care Quest (Europe) (En,Fr,De,Es,It,Nl,Pt,Da) 50 | - Care Bears - The Care Quests (USA) (En,Fr,Es)::Redirect::Care Bears - The Care Quest (USA) (En,Fr,Es) 51 | - Celeste Classic::Homebrew 52 | - Donchan Puzzle - Hanabi de Dohn! Advance (Japan)::Redirect::Don-chan Puzzle - Hanabi de Doon! Advance (Japan) 53 | - Fear Factor Unleashed (USA)::Redirect::Fear Factor - Unleashed (USA) 54 | - Fire Emblem - The Binding Blade (USA)::Romhack 55 | - GameShark GBA (USA) (Alt 1) (Unl)::Redirect::GameShark GBA (USA) (Unl) (Alt) 56 | - GBA TV Tuner PAL (China) (v1.3) (Unl)::Redirect::GB-A TV Tuner PAL (China) (v1.3) (Unl) 57 | - GBA TV Tuner PAL (China) (v2.0) (Unl)::Redirect::GB-A TV Tuner PAL (China) (v2.0) (Unl) 58 | - Jump (Japan)::e-Reader 59 | - Mandrake the Magician (Italy) (Proto)::Redirect::Mandrake the Magician (Italy) (It) (Proto) 60 | - Memory Dumps - Promotional (USA, Australia)::e-Reader 61 | - Mother 1+2 (USA)::Romhack 62 | - Mother 3 (USA)::Romhack 63 | - Motoracer Advance (Europe) (En,Fr,De,Es,It)::Redirect::Moto Racer Advance (Europe) (En,Fr,De,Es,It) 64 | - Motoracer Advance (USA) (En,Fr,De,Es,It)::Redirect::Moto Racer Advance (USA) (En,Fr,De,Es,It) 65 | - Overstorm (Unknown) (Proto)::Redirect::Overstorm (Europe) (Proto) 66 | - Phantom, The (Italy) (Proto)::Redirect::Phantom, The (Italy) (It) (Proto) 67 | - Pokemon (USA)::Unknown bootleg or romhack 68 | - Pokemon - Doel Deoxys Distribution (Netherlands) (Kiosk)::Redirect::Pokemon - Doel Deoxys Distribution (Netherlands) (En) (Kiosk) 69 | - Pokemon Channel - Line Art Card - 11-A101 - The Pikachu Star (USA, Australia) (Promo)::e-Reader 70 | - Pokemon Channel - Line Art Card - 11-A102 - The Kyogre Constellation (USA) (Promo)::e-Reader 71 | - Pokemon Channel - Line Art Card - 11-P101 - Jirachi (Australia) (Promo)::e-Reader 72 | - Pokemon Channel - Paint Pattern Card - 11-A103 - Poke A La Card (USA, Australia) (Promo)::e-Reader 73 | - Ratatouille (Greece) (En)::Redirect::Ratatouille (Australia, Greece) (En) 74 | - SN Systems (Europe)::Redirect::SN Systems (Europe) (Test Program) (Unl) 75 | - Tiny Chao Garden (USA) (Ja) (Phantasy Star Online Episode I _ II)::Gamecube rom 76 | - Toyrobo Force (Japan)::Redirect::Toy Robo Force (Japan) 77 | - Travis Pastrana's Pro MotoX (USA, Europe) (Proto) (2002-12-12)::Redirect::Travis Pastrana's Pro MotoX (USA) (Proto) (2002-12-12) 78 | - Xploder Advance (Europe) (Alt 1) (Unl)::Redirect::Xploder Advance (Europe) (Unl) (Alt) 79 | 80 | # GG 81 | - No known special cases 82 | 83 | # SMS (INCOMPLETE VALIDATION) 84 | 85 | Action Fighter (USA, Europe, Brazil) (Rev 1) 86 | Akai Koudan Zillion _ Zillion (Japan, Europe) (En,Ja) 87 | Alex Kidd in Miracle World (USA, Europe, Brazil) (Rev 1) 88 | Alien Syndrome (USA, Europe, Brazil) (Beta) 89 | Alien Syndrome (USA, Europe, Brazil) 90 | Andre Agassi Tennis (Europe, Brazil) 91 | Ariel the Little Mermaid (Brazil) 92 | Assault City (USA, Europe, Brazil) (Light Phaser) 93 | Astro Warrior (Japan, USA, Brazil) 94 | Ayrton Senna's Super Monaco GP II (Europe, Brazil) 95 | Aztec Adventure _ Nazca '88 - The Golden Road to Paradise (World) 96 | Back to the Future Part II (Europe, Brazil) 97 | Baku Baku (Brazil) 98 | Bank Panic (Europe, Brazil) 99 | Basketball Nightmare (Europe, Brazil) (Beta) 100 | Basketball Nightmare (Europe, Brazil) 101 | Batman Returns (Europe, Brazil) 102 | Battle Out Run (Europe, Brazil) 103 | Battlemaniacs (Brazil) 104 | Black Belt (USA, Europe, Brazil) 105 | Blade Eagle (World) 106 | Blade Eagle _ Blade Eagle 3-D (World) 107 | Bobble Bobble (Korea) (Unl) 108 | Bonanza Bros. (Europe, Brazil) 109 | Bonkers Wax Up! (Brazil) 110 | Bubble Bobble (Europe) 111 | Bubble Bobble _ Dragon Maze (Europe, Brazil) 112 | Buggy Run (Europe, Brazil) 113 | California Games (USA, Europe) 114 | California Games _ Jogos de Verao (USA, Europe, Brazil) 115 | Captain Silver (Japan, Europe) 116 | Captain Silver (Japan, Europe, Brazil) 117 | Castle of Illusion Starring Mickey Mouse (USA, Brazil) (Demo) 118 | Castle of Illusion Starring Mickey Mouse (USA, Brazil) 119 | Castle of Illusion Starring Mickey Mouse (USA, Europe, Brazil) (Demo) 120 | Castle of Illusion Starring Mickey Mouse (USA, Europe, Brazil) (Rev 1) 121 | Champions of Europe (Europe) 122 | Choplifter (USA, Europe, Brazil) (Beta) 123 | Choplifter (USA, Europe, Brazil) 124 | Chuck Rock (Europe, Brazil) 125 | Chuck Rock II - Son of Chuck (Brazil) 126 | Cloud Master (USA, Europe, Brazil) 127 | Columns (USA, Europe, Brazil) (Beta) 128 | Columns _ Shapes and Columns (USA, Europe, Brazil) 129 | Cyber Shinobi, The (Europe, Brazil) (Beta) 130 | Cyber Shinobi, The (Europe, Brazil) 131 | Cyborg Hunter (USA, Europe, Brazil) (Sample) 132 | Cyborg Hunter (USA, Europe, Brazil) 133 | Danan - The Jungle Fighter (Europe, Brazil) 134 | Dead Angle (USA, Europe, Brazil) 135 | Deep Duck Trouble Starring Donald Duck (Europe, Brazil) 136 | Dick Tracy (USA, Europe, Brazil) 137 | Dinobasher Starring Bignose the Caveman (Europe) (Proto) 138 | Dinosaur Dooley, The (Korea) (Unl) 139 | Dr. Hello (Korea) (Unl) 140 | Dr. Robotnik's Mean Bean Machine (Europe, Brazil) 141 | Dragon Crystal (Europe, Brazil) 142 | Dynamite Duke (Europe, Brazil) 143 | Dynamite Dux (Europe, Brazil) 144 | Dynamite Headdy (Brazil) 145 | E-SWAT (USA, Europe, Brazil) (Beta 1) 146 | E-SWAT (USA, Europe, Brazil) (Beta 2) 147 | E-SWAT (USA, Europe, Brazil) (Beta 3) 148 | E-SWAT (USA, Europe, Brazil) (Rev 1) 149 | E-SWAT (USA, Europe, Brazil) 150 | Earthworm Jim (Brazil) 151 | Ecco the Dolphin (Europe, Brazil) 152 | Enduro Racer _ Super Cross (USA, Europe, Brazil) 153 | F-1 Spirit - The Way to Formula-1 (Korea) (Unl) 154 | F-16 Fighting Falcon _ F-16 Fighter _ F16 Falcon Fighter (USA, Europe, Brazil) 155 | F1 (Europe, Brazil) 156 | Fantasy Zone - The Maze (USA, Europe, Brazil) 157 | Fantasy Zone II (USA, Europe, Brazil) 158 | Fire _ Ice (Brazil) 159 | Flash, The (Europe, Brazil) 160 | Flintstones, The (Europe, Brazil) 161 | Forgotten Worlds (Europe, Brazil) 162 | G-LOC Air Battle (Europe, Brazil) 163 | Gain Ground (Europe, Brazil) (Beta) 164 | Gain Ground (Europe, Brazil) 165 | Galaxy Force (Europe, Brazil) 166 | Game Box Serie Esportes Radicais (Brazil) 167 | Gangcheol RoboCop (Korea) (Unl) 168 | Gangster Town (USA, Europe, Brazil) 169 | Gauntlet (Europe, Brazil) 170 | George Foreman's KO Boxing (Europe, Brazil) 171 | Geraldinho (Brazil) 172 | Ghost House (USA, Europe, Brazil) (Beta) 173 | Ghost House (USA, Europe, Brazil) 174 | Ghostbusters (USA, Europe, Brazil) 175 | Ghouls'n Ghosts (USA, Europe, Brazil) (Demo) 176 | Ghouls'n Ghosts (USA, Europe, Brazil) 177 | Global Defense (USA, Europe, Brazil) (Beta) 178 | Global Defense (USA, Europe, Brazil) 179 | Global Gladiators (Europe, Brazil) 180 | Golden Axe (USA, Europe, Brazil) 181 | Golden Axe Warrior (USA, Europe, Brazil) 182 | Golfamania (Europe, Brazil) 183 | Golvellius - Valley of Doom (USA, Europe, Brazil) 184 | GP Rider (Europe, Brazil) 185 | Great Golf (Korea) (Unl) 186 | Great Golf _ Masters Golf (World) 187 | Great Ice Hockey (Japan, USA) 188 | Great Volleyball (USA, Europe, Brazil) 189 | Heavyweight Champ (Europe) 190 | Heroes of the Lance (Europe, Brazil) (Beta) 191 | Heroes of the Lance (Europe, Brazil) 192 | Hoshi wo Sagasite... (Japan) 193 | Impossible Mission (Europe, Brazil) (Beta) 194 | Impossible Mission (Europe, Brazil) 195 | Incredible Crash Dummies, The (Europe, Brazil) 196 | Incredible Hulk, The (Europe, Brazil) 197 | Indiana Jones and the Last Crusade (Europe, Brazil) 198 | James 'Buster' Douglas Knockout Boxing (USA) (Beta) 199 | James Bond 007 - The Duel (Brazil) 200 | James Pond 2 - Codename RoboCod (Europe, Brazil) 201 | Joe Montana Football (USA, Europe, Brazil) 202 | Jungle Book, The (Europe, Brazil) 203 | Jurassic Park (Europe, Brazil) 204 | Kenseiden (USA, Europe, Brazil) 205 | King's Quest - Quest for the Crown (USA) (Beta) 206 | Krusty's Fun House (Europe, Brazil) 207 | Kung Fu Kid (USA, Europe, Brazil) 208 | Land of Illusion Starring Mickey Mouse (Europe, Brazil) 209 | Legend of Illusion Starring Mickey Mouse (Brazil) 210 | Lemmings (Europe, Brazil) 211 | Line of Fire (Europe, Brazil) 212 | Lion King, The (Europe, Brazil) 213 | Lord of the Sword (USA, Europe, Brazil) 214 | Lucky Dime Caper Starring Donald Duck, The (Europe, Brazil) (Beta) 215 | Lucky Dime Caper Starring Donald Duck, The (Europe, Brazil) 216 | Mahjong Sengoku Jidai _ Mahjong (Japan, Hong Kong) 217 | Marksman Shooting _ Trap Shooting (USA, Brazil) 218 | Master of Darkness (Europe, Brazil) 219 | Masters of Combat (Europe, Brazil) 220 | Maze Hunter 3-D (USA, Europe, Brazil) 221 | Maze Walker (Japan) 222 | Mercs (Europe, Brazil) 223 | Michael Jackson's Moonwalker (USA, Europe, Brazil) (Beta) 224 | Michael Jackson's Moonwalker (USA, Europe, Brazil) 225 | Mickey's Ultimate Challenge (Brazil) 226 | Miracle Warriors - Seal of the Dark Lord (USA, Europe, Brazil) (Beta) 227 | Miracle Warriors - Seal of the Dark Lord (USA, Europe, Brazil) 228 | Missile Defense 3-D (USA, Europe, Brazil) (Sample) 229 | Missile Defense 3-D (USA, Europe, Brazil) 230 | Monopoly (Europe) 231 | Monopoly (USA) (Beta) 232 | Monopoly (USA) 233 | Mortal Kombat (Europe, Brazil) 234 | Mortal Kombat 3 (Brazil) 235 | Mortal Kombat II (Europe, Brazil) 236 | Ms. Pac-Man (Europe, Brazil) 237 | My Hero (USA, Europe, Brazil) 238 | Ninja Gaiden (Europe, Brazil) (Beta) 239 | Ninja Gaiden (Europe, Brazil) 240 | Ninja, The (USA, Europe, Brazil) 241 | Operation Wolf (Europe, Brazil) 242 | OutRun 3-D (Europe, Brazil) (Sample) 243 | OutRun 3-D (Europe, Brazil) 244 | OutRun Europa (Europe, Brazil) 245 | Paperboy (USA, Brazil) 246 | Penguin Adventure (Korea) (Unl) 247 | Phantasy Star (Brazil) 248 | Phantasy Star (USA, Europe) (Rev 1) 249 | Pit Fighter (Brazil) 250 | Pit Fighter (Europe) 251 | Pit-Fighter (Brazil) 252 | Populous (Europe, Brazil) 253 | Poseidon Wars 3-D (USA, Europe, Brazil) 254 | Power Boggle Boggle (Korea) (Unl) 255 | Power Strike (USA, Europe, Brazil) 256 | Power Strike II (Europe, Brazil) 257 | Predator 2 (Brazil) 258 | Prince of Persia (Europe, Brazil) 259 | Pro Wrestling (USA, Europe, Brazil) 260 | Psychic World (Europe, Brazil) 261 | Psycho Fox (USA, Europe, Brazil) 262 | Putt _ Putter (Europe, Brazil) (Beta) 263 | Putt _ Putter _ Minigolf (Europe, Brazil) 264 | Puzzle (Korea) (Unl) 265 | Quest for the Shaven Yak Starring Ren Hoek _ Stimpy (Brazil) 266 | R.C. Grand Prix (USA, Europe, Brazil) (Beta) 267 | R.C. Grand Prix (USA, Europe, Brazil) 268 | Rainbow Islands (Brazil) 269 | Rambo III (USA, Europe, Brazil) 270 | Rampage (USA, Europe, Brazil) 271 | Rastan (USA, Europe, Brazil) (Beta) 272 | Rastan (USA, Europe, Brazil) 273 | Renegade (Europe, Brazil) 274 | Rescue Mission (USA, Europe, Brazil) 275 | Road Rash (Europe, Brazil) 276 | RoboCop 3 (Europe, Brazil) 277 | RoboCop versus The Terminator (Europe, Brazil) 278 | Running Battle (Europe, Brazil) 279 | Sagaia (Europe, Brazil) 280 | Sapo Xule - S.O.S. Lagoa Poluida (Brazil, Portugal) 281 | Scramble Spirits (Europe, Brazil) 282 | Sega Chess (Europe, Brazil) 283 | Sega World Tournament Golf (Europe, Brazil) 284 | Shadow Dancer - The Secret of Shinobi _ Shadow Dancer (Europe, Brazil) 285 | Shadow of the Beast (Europe, Brazil) 286 | Shinobi (Japan, Brazil) 287 | Shinobi (USA, Europe, Brazil) (Rev 1) 288 | Shooting Gallery _ Shooting G. (USA, Europe, Brazil) 289 | Simpsons, The - Bart vs. the Space Mutants (Europe, Brazil) 290 | Simpsons, The - Bart vs. the World (Europe, Brazil) 291 | Slap Shot (Europe) (Rev 1) 292 | Slap Shot (USA, Brazil) 293 | Sonic Blast (Brazil) 294 | Sonic Chaos (Europe, Brazil) (Beta) (1993-06-30) 295 | Sonic Chaos (Europe, Brazil) (Beta) (1993-07-13) 296 | Sonic Chaos (Europe, Brazil) 297 | Sonic Spinball (Europe, Brazil) 298 | Sonic The Hedgehog (USA, Europe, Brazil) 299 | Sonic The Hedgehog 2 (Europe, Brazil) (Rev 1) 300 | Sonic The Hedgehog 2 (Europe, Brazil) 301 | Space Harrier (Japan, USA) 302 | Space Harrier 3-D _ Space Harrier 3D (USA, Europe, Brazil) 303 | Speedball (Europe) (Image Works) 304 | Speedball (Europe) (Virgin) 305 | SpellCaster (USA, Europe, Brazil) 306 | Spider-Man (USA, Europe, Brazil) 307 | Spider-Man - Return of the Sinister Six (Europe, Brazil) 308 | Sports Pad Soccer (Japan) (Rev 1) 309 | Spy vs Spy (USA, Europe, Brazil) 310 | Star Wars (Europe, Brazil) 311 | Street Fighter II' (Brazil) 312 | Streets of Rage (Europe, Brazil) 313 | Streets of Rage II (Europe, Brazil) 314 | Strider (USA, Europe, Brazil) (Demo) 315 | Strider (USA, Europe, Brazil) 316 | Strider II (Europe, Brazil) 317 | Submarine Attack (Europe, Brazil) 318 | Summer Games (Europe, Brazil) (Beta) 319 | Summer Games _ Jogos Olimpicos (Europe, Brazil) 320 | Super Boy II (Korea) (Unl) 321 | Super Bubble Bobble (Korea) (Unl) 322 | Super Monaco GP (Europe, Brazil) (Beta) 323 | Super Monaco GP (Europe, Brazil) 324 | Super Monaco GP (USA) (Beta) 325 | Super Wonder Boy (Japan, Europe) 326 | Super Wonder Boy _ Wonder Boy (Japan, Europe) 327 | Superman - The Man of Steel (Europe, Brazil) 328 | Taz in Escape from Mars (Brazil) 329 | Taz-Mania (Europe, Brazil) (Beta) 330 | Taz-Mania (Europe, Brazil) 331 | Teddy Boy (USA, Europe, Brazil) 332 | Tennis Ace _ Super Tennis (Europe, Brazil) 333 | Terminator, The (Brazil) (Rev 1) 334 | Thunder Blade (USA, Europe, Brazil) 335 | Time Soldiers (USA, Europe, Brazil) 336 | Tom and Jerry - The Movie (Europe, Brazil) 337 | Tom _ Jerry (Europe, Brazil) (Beta) 338 | TransBot (USA, Europe, Brazil) (Beta) 339 | TransBot (USA, Europe, Brazil) 340 | Ttoriui Moheom (Korea) (Unl) 341 | Ultima IV - Quest of the Avatar (Europe, Brazil) (Beta) 342 | Ultima IV - Quest of the Avatar (Europe, Brazil) 343 | Vigilante (USA, Europe, Brazil) 344 | Virtua Fighter Animation (Brazil) 345 | Wanted (USA, Europe, Brazil) 346 | Where in the World is Carmen Sandiego (Brazil) 347 | Wimbledon II _ Wimbledon (Europe, Brazil) 348 | Wolfchild (Europe, Brazil) 349 | Wonder Boy (USA, Europe, Brazil) (Rev 1) 350 | Wonder Boy III - The Dragon's Trap (USA, Europe) (Aftermarket) (Unl) 351 | World Class Leader Board (Europe, Brazil) 352 | World Cup Italia '90 _ Super Futebol II (Europe, Brazil) 353 | World Games (Europe) (Beta) 354 | World Games (Europe, Brazil) 355 | World Grand Prix (USA, Brazil) (Beta) 356 | World Grand Prix (USA, Brazil) 357 | World Soccer _ Great Soccer _ Super Futebol (World) 358 | WWF Wrestlemania - Steel Cage Challenge (Europe, Brazil) 359 | X-Men - Mojo World (Brazil) 360 | Xenon 2 - Megablast (Europe) (Image Works) 361 | Xenon 2 - Megablast (Europe) (Virgin) 362 | Ys - The Vanished Omens (USA, Europe) (Demo) 363 | Ys - The Vanished Omens _ Y's (USA, Europe, Brazil) 364 | Zillion (Europe, Brazil) (Rev 2) --------------------------------------------------------------------------------