├── .gitignore ├── HISTORY.md ├── LICENSE ├── README.md ├── images ├── badges.png ├── batch-cache.png ├── cache-settings.png ├── config-cache-error.png ├── config-cache-warning.png ├── config-cache.png ├── extraction-settings.png ├── icon.ico ├── launch-video.gif ├── logo-v2-title.png ├── logo-v2-title.svg ├── logo-v2.png ├── logo-v2.svg ├── logo.png ├── logo.svg ├── merged-example.png ├── plugin-settings.png ├── select-file-window-bigbox.png ├── select-file-window.png ├── smart-extract-settings.png └── update-check.png ├── src ├── ArchiveCacheManager.sln ├── ArchiveCacheManager │ ├── App.config │ ├── ArchiveCacheManager.csproj │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── icon.ico ├── Core │ ├── CacheManager.cs │ ├── Config.cs │ ├── Core.csproj │ ├── DiskUtils.cs │ ├── Extractors │ │ ├── Chdman.cs │ │ ├── DolphinTool.cs │ │ ├── ExtractXiso.cs │ │ ├── Extractor.cs │ │ ├── Robocopy.cs │ │ └── Zip.cs │ ├── FastWildcard.cs │ ├── GameIndex.cs │ ├── GameInfo.cs │ ├── LaunchInfo.cs │ ├── Logger.cs │ ├── MatchSettings.cs │ ├── PathUtils.cs │ ├── ProcessUtils.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Utils.cs │ └── packages.config └── Plugin │ ├── ArchiveListWindow.Designer.cs │ ├── ArchiveListWindow.cs │ ├── ArchiveListWindow.resx │ ├── ArchiveListWindowBigBox.Designer.cs │ ├── ArchiveListWindowBigBox.cs │ ├── ArchiveListWindowBigBox.resx │ ├── BatchCacheMenuItem.cs │ ├── BatchCacheWindow.Designer.cs │ ├── BatchCacheWindow.cs │ ├── BatchCacheWindow.resx │ ├── CacheConfigWindow.Designer.cs │ ├── CacheConfigWindow.cs │ ├── CacheConfigWindow.resx │ ├── EmulatorPlatformSelectionWindow.Designer.cs │ ├── EmulatorPlatformSelectionWindow.cs │ ├── EmulatorPlatformSelectionWindow.resx │ ├── FlexibleMessageBox.cs │ ├── GameBadge.cs │ ├── GameLaunching.cs │ ├── GameMenuItem.cs │ ├── LaunchBoxDataBackup.cs │ ├── LaunchBoxSettings.cs │ ├── MessageBoxBigBox.Designer.cs │ ├── MessageBoxBigBox.cs │ ├── MessageBoxBigBox.resx │ ├── NewConfigWindow.Designer.cs │ ├── NewConfigWindow.cs │ ├── NewConfigWindow.resx │ ├── Plugin.csproj │ ├── PluginUtils.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Resources │ ├── Badges │ │ ├── Archive Cached - Neon.png │ │ ├── Archive Cached - Simple White.png │ │ └── Archive Cached.png │ ├── arrow-circle-double.png │ ├── badge-neon.svg │ ├── badge-white.svg │ ├── badge.png │ ├── badge.svg │ ├── box--plus.png │ ├── box-zipper.png │ ├── broom.png │ ├── cross-octagon.png │ ├── cross-script.png │ ├── exclamation-red.png │ ├── exclamation-white.png │ ├── exclamation.png │ ├── folder-horizontal-open.png │ ├── gear.png │ ├── hourglass.png │ ├── icon.ico │ ├── icon16x16-play.png │ ├── icon16x16.png │ ├── icon32x32.png │ ├── joystick.png │ ├── logo.png │ ├── media-cd.png │ ├── media-gc.png │ ├── media-md.png │ ├── media-n64.png │ ├── media-ps1.png │ ├── media-ps2-cd.png │ ├── media-ps2.png │ ├── media-psp.png │ ├── pencil.png │ ├── plus.png │ ├── star-blue.png │ ├── star.png │ └── tick.png │ ├── ShellIcon.cs │ ├── SystemEvents.cs │ ├── SystemMenuItem.cs │ ├── Updater.cs │ ├── UserInterface.cs │ ├── app.config │ └── packages.config └── thirdparty └── 7-Zip ├── 7z.dll ├── 7z.exe ├── History.txt ├── License.txt └── readme.txt /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | 353 | # Older versions of LaunchBox plugin assembly 354 | Unbroken.LaunchBox.Plugins/ 355 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # Archive Cache Manager change history 2 | ## v2.16 (2023-02-10) 3 | * New M3U name option - "Disc 1 Filename" 4 | * Always use the filename of the first disc of a multi-disc game for the m3u file, regardless of which disc was launched 5 | * Allows better support for The Bezel Project config files, which use config files based on the ROM name 6 | * New batch caching option to pause on caching errors (default is to skip and continue) 7 | * Minor config window tweaks 8 | 9 | ## v2.15 (2023-01-19) 10 | * New _extract-xiso_ option for Xbox iso conversion 11 | * Full iso files (redump) automatically converted and cached in xiso format 12 | * Supports both zipped and unzipped iso files 13 | * Requires _extract-xiso.exe_ to be added to the `ArchiveCacheManager\Extractors` folder 14 | * Reduced archive cache path lengths, avoiding path too long errors 15 | * Small performance improvement when checking many file priorities 16 | * Smart Extract uses Priority to select file from archive in case where individual ROM file not previously selected 17 | * Fix incorrect path for auto generated M3Us when Launch Path is not Default 18 | * Fix background thread issue when Batch Cache Games window closed while still calculating archive sizes 19 | * Interface tweaks 20 | 21 | ## v2.14 (2022-05-05) 22 | * New right-click menu option - "Batch Cache Games" 23 | * Extract or copy multiple games to the cache, ready to play later 24 | * Bulk cache games from NAS or external storage 25 | * Cached games can be played even if network or external storage disconnected 26 | 27 | ## v2.13 (2022-04-28) 28 | * Fix config window DPI scaling issue 29 | * Config window performance improvements 30 | * Option to skip version during update check 31 | 32 | ## v2.12 (2022-04-21) 33 | * Option to copy non-archive files to cache 34 | * Support for extracting additional formats 35 | * Option to extract chd to cue+bin using chdman 36 | * Option to extract rvz, wia, gcz to iso using DolphinTool 37 | * Option to specify a launch folder for cached games (game platform, game title, or emulator title) 38 | * Useful for managing common RetroArch settings 39 | * Smart Extract option to only extract required files from an archive 40 | * Useful for merged ROM sets 41 | * Emulator selection when launching a file from "Select ROM In Archive" list 42 | * Option to bypass LaunchBox's check the ROM file exists when launching a game 43 | * Allows launching cached game immediately - no waiting for slow disk spin-up or network latency 44 | * Allows playing cached games 'offline' if NAS or cloud storage unavailable 45 | * Config window and UI overhaul 46 | * Minor bug fixes 47 | 48 | ## v2.11 (2022-03-25) 49 | * Multi-disc support and automatic M3U generation 50 | * Extract and cache all discs in a multi-disc game 51 | * Generate and use M3U where supported by emulator / platform 52 | * Custom filename priority for all emulators / platforms 53 | * Option to automatically check for plugin updates 54 | * Updated 7-Zip to version 21.07 55 | * Minor bug fixes 56 | 57 | ## v2.0.10 (2022-03-08) 58 | * Support for LaunchBox 12.8 Extract ROM per platform setting 59 | 60 | ## v2.0.9 (2022-01-31) 61 | * Fix file priority for files in subfolder of archive when not in cache 62 | * Fix launching individual file from archive when not in cache 63 | 64 | ## v2.0.8 (2022-01-13) 65 | * Wildcard based filename matching for file priorities in archive 66 | * Prioritize a file extension, filename, or combination 67 | * Create priorities to automatically play preferred ROM region from GoodMerged archives 68 | * Performance improvements, especially for archives with many hundreds or thousands files 69 | * [BigBox] "Select ROM In Archive" menu option (accepts keyboard input only) 70 | 71 | ## v2.0.7 (2021-03-30) 72 | * Badge to indicate if game is in cache 73 | * Available under Badges -> Enable Archive Cached menu 74 | * Remember previous selection made in "Select Rom In Archive..." right-click menu 75 | * Previous selection automatically applied when game started normally 76 | 77 | ## v2.0.6 (2021-03-26) 78 | * Only remove items from cache path originally extracted by plugin 79 | * Additional checks for invalid cache paths 80 | 81 | ## v2.0.5 (2021-03-25) 82 | * Fix to ensure cache path valid if config file corrupt 83 | 84 | ## v2.0.4 (2021-03-24) 85 | * New feature - 'Keep' 86 | * Keep your favourite games cached and ready to play 87 | * Games marked 'Keep' will not be removed from the cache, and do not contribute to the used cache size 88 | * Configuration window updates 89 | * Cache info summary 90 | * View cached games, toggle the 'Keep' option 91 | * Manually remove games from the cache or clear it entirely 92 | * Events and errors now logged to `LaunchBox\Plugins\ArchiveCacheManager\Logs` 93 | * Minor bug fixes 94 | 95 | ## v2.0.3 (2021-03-17) 96 | * Aborting game startup process (`Esc` on Startup Screen) now terminates extract operation 97 | * Cleanup partially extracted archive from cache on 7z error, or previous startup process abort 98 | * Fix archive list error when selecting individual ROM after a previous game launch failure 99 | 100 | ## v2.0.2 (2021-03-14) 101 | * New feature - Select and play ROM file from archive 102 | * Right-click a game and click "Select ROM In Archive..." 103 | * Currently only supports LaunchBox 104 | 105 | ## v2.0.1 (2021-03-11) 106 | * Support Startup Screen progress bar during initial extraction 107 | * Minor bug fixes 108 | 109 | ## v2.0.0 (2021-03-10) 110 | * Code now open source under LGPL 111 | * Rewritten to use LaunchBox plugin API 112 | * Added configuration window in Tools menu 113 | * Support for LaunchBox & BigBox 10.x, 11.x 114 | 115 | ## v1.5 (2018-04-13) 116 | * Add support for LaunchBox.Next 117 | 118 | ## v1.4 (2018-01-25) 119 | * Add emulator + platform based file priority, for multi-system emulators 120 | 121 | ## v1.3 (2017-12-24) 122 | * Fix LaunchBox overriding Archive Cache Manager during 7-Zip 16.04 update 123 | * Include 7-Zip version 16.04 with installation 124 | 125 | ## v1.2 (2017-11-30) 126 | * Support for loading files direct from cache, bypassing LaunchBox temp folder 127 | * This is now the default behaviour (no hardlinks/junctions) 128 | * Configuration option to force using hardlinks/junctions 129 | * Fix artwork not displaying for game titles containing an apostrophe 130 | * Update to .NET Framework 4.7, inline with LaunchBox 131 | 132 | ## v1.1 (2017-01-25) 133 | * Show clear logo on loading screen, text title if logo unavailable 134 | * Display cover art in loading screen when region specific art not found 135 | * Configuration option to enable/disable verbose logging 136 | * Configuration option to force file copy from cache to LaunchBox temp folder 137 | * Support non-NTFS volumes 138 | * Support cache stored in network location 139 | 140 | ## v1.0 (2017-01-03) 141 | * Initial release 142 | -------------------------------------------------------------------------------- /images/badges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/badges.png -------------------------------------------------------------------------------- /images/batch-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/batch-cache.png -------------------------------------------------------------------------------- /images/cache-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/cache-settings.png -------------------------------------------------------------------------------- /images/config-cache-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/config-cache-error.png -------------------------------------------------------------------------------- /images/config-cache-warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/config-cache-warning.png -------------------------------------------------------------------------------- /images/config-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/config-cache.png -------------------------------------------------------------------------------- /images/extraction-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/extraction-settings.png -------------------------------------------------------------------------------- /images/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/icon.ico -------------------------------------------------------------------------------- /images/launch-video.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/launch-video.gif -------------------------------------------------------------------------------- /images/logo-v2-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/logo-v2-title.png -------------------------------------------------------------------------------- /images/logo-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/logo-v2.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/logo.png -------------------------------------------------------------------------------- /images/merged-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/merged-example.png -------------------------------------------------------------------------------- /images/plugin-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/plugin-settings.png -------------------------------------------------------------------------------- /images/select-file-window-bigbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/select-file-window-bigbox.png -------------------------------------------------------------------------------- /images/select-file-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/select-file-window.png -------------------------------------------------------------------------------- /images/smart-extract-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/smart-extract-settings.png -------------------------------------------------------------------------------- /images/update-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/images/update-check.png -------------------------------------------------------------------------------- /src/ArchiveCacheManager.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31005.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin", "Plugin\Plugin.csproj", "{205F6C26-C727-4171-A760-3599AA2D00D2}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{035823E0-C80B-49BF-9FAD-9C65EA45E3A9}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiveCacheManager", "ArchiveCacheManager\ArchiveCacheManager.csproj", "{687F96D0-DFB8-4A53-BF23-AA02BD7658EC}" 11 | ProjectSection(ProjectDependencies) = postProject 12 | {205F6C26-C727-4171-A760-3599AA2D00D2} = {205F6C26-C727-4171-A760-3599AA2D00D2} 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {205F6C26-C727-4171-A760-3599AA2D00D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {205F6C26-C727-4171-A760-3599AA2D00D2}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {205F6C26-C727-4171-A760-3599AA2D00D2}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {205F6C26-C727-4171-A760-3599AA2D00D2}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {035823E0-C80B-49BF-9FAD-9C65EA45E3A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {035823E0-C80B-49BF-9FAD-9C65EA45E3A9}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {035823E0-C80B-49BF-9FAD-9C65EA45E3A9}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {035823E0-C80B-49BF-9FAD-9C65EA45E3A9}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {687F96D0-DFB8-4A53-BF23-AA02BD7658EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {687F96D0-DFB8-4A53-BF23-AA02BD7658EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {687F96D0-DFB8-4A53-BF23-AA02BD7658EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {687F96D0-DFB8-4A53-BF23-AA02BD7658EC}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(ExtensibilityGlobals) = postSolution 38 | SolutionGuid = {B5C39C6A-A1EF-4C32-B4F0-0F2BCAE39F8F} 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /src/ArchiveCacheManager/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/ArchiveCacheManager/ArchiveCacheManager.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {687F96D0-DFB8-4A53-BF23-AA02BD7658EC} 8 | Exe 9 | ArchiveCacheManager 10 | ArchiveCacheManager 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | icon.ico 37 | 38 | 39 | OnOutputUpdated 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {035823e0-c80b-49bf-9fad-9c65ea45e3a9} 61 | Core 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | mkdir c:\LaunchBox\Plugins\ArchiveCacheManager 70 | copy /Y $(TargetPath) c:\LaunchBox\Plugins\ArchiveCacheManager 71 | mkdir $(SolutionDir)..\release\ArchiveCacheManager 72 | copy /Y $(TargetPath) $(SolutionDir)..\release\ArchiveCacheManager 73 | copy /Y $(SolutionDir)..\README.md $(SolutionDir)..\release\ArchiveCacheManager\readme.txt 74 | copy /Y $(SolutionDir)..\HISTORY.md $(SolutionDir)..\release\ArchiveCacheManager\history.txt 75 | mkdir $(SolutionDir)..\release\ArchiveCacheManager\7-Zip 76 | copy /Y $(SolutionDir)..\thirdparty\7-Zip\* $(SolutionDir)..\release\ArchiveCacheManager\7-Zip 77 | move /Y $(SolutionDir)..\release\ArchiveCacheManager\7-Zip\7z.exe $(SolutionDir)..\release\ArchiveCacheManager\7-Zip\7z.exe.original 78 | move /Y $(SolutionDir)..\release\ArchiveCacheManager\7-Zip\7z.dll $(SolutionDir)..\release\ArchiveCacheManager\7-Zip\7z.dll.original 79 | mkdir $(SolutionDir)..\release\ArchiveCacheManager\Badges 80 | copy /Y $(SolutionDir)Plugin\Resources\Badges $(SolutionDir)..\release\ArchiveCacheManager\Badges 81 | del $(SolutionDir)..\release\*.zip 82 | $(SolutionDir)..\thirdparty\7-Zip\7z.exe a $(SolutionDir)..\release\ArchiveCacheManager.zip $(SolutionDir)..\release\* 83 | 84 | -------------------------------------------------------------------------------- /src/ArchiveCacheManager/Program.cs: -------------------------------------------------------------------------------- 1 | /* Archive Cache Manager - A LaunchBox plugin which extracts and caches ROM 2 | * archives, letting you play games faster. 3 | * 4 | * Copyright (C) 2021 fraganator 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2.1 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 19 | * USA 20 | * 21 | * Links: 22 | * Plugin Homepage - https://forums.launchbox-app.com/files/file/234-archive-cache-manager/ 23 | * Forum Support - https://forums.launchbox-app.com/topic/35010-archive-cache-manager/ 24 | * GitHub Repository - https://github.com/fraganator/archive-cache-manager 25 | * 26 | * Contact: 27 | * GitHub - https://github.com/fraganator 28 | * LaunchBox Forum - https://forums.launchbox-app.com/profile/69812-fraganator/ 29 | */ 30 | 31 | /* NOTES ON LAUNCHBOX AND 7-ZIP 32 | * 33 | * When an emulator has the "Extract ROM archives before running option" 34 | * checked and a game is launched, 7z is called twice. The first call extracts 35 | * the archive with the command: 36 | * 37 | * 7z.exe x -o -y -aoa -bsp1 38 | * 39 | * where and are absolute, and is a temp 40 | * location of LaunchBox\ThirdParty\7-Zip\Temp 41 | * 42 | * Once extraction completes and 7z returns, LaunchBox will call 7z a second 43 | * time to list the archive content with the command: 44 | * 45 | * 7z.exe l -slt 46 | * 47 | * LaunchBox uses this file list to determine which rom path to supply to the 48 | * emulator. It also uses this list to determine which files to delete from the 49 | * temp location when the emulator exits. 50 | * 51 | * LaunchBox also uses 7z to perform other functions, namely extracting 52 | * downloaded metadata.xml, and performing data backups. 53 | */ 54 | 55 | using System; 56 | using System.Diagnostics; 57 | using System.Linq; 58 | 59 | namespace ArchiveCacheManager 60 | { 61 | class Program 62 | { 63 | static void Main(string[] args) 64 | { 65 | #if DEBUG 66 | Debugger.Launch(); 67 | #endif 68 | Stopwatch stopwatch = new Stopwatch(); 69 | stopwatch.Start(); 70 | 71 | Logger.Log("========"); 72 | Logger.Log(string.Format("Archive Cache Manager started with arguments: {0}", string.Join(" ", args))); 73 | 74 | if (args.Count() >= 1) 75 | { 76 | // Global try/catch handler, in case there's an unhandled exception. 77 | try 78 | { 79 | switch (args[0]) 80 | { 81 | // Expected command: 7z.exe x -o -y -aoa -bsp1 82 | case "x": 83 | Logger.Log("Running in extraction mode."); 84 | ExtractArchive(args); 85 | break; 86 | // Expected command: 7z.exe l -slt 87 | case "l": 88 | Logger.Log("Running in list mode."); 89 | ListArchive(args); 90 | break; 91 | // Expected command: 7z.exe c 92 | case "c": 93 | Logger.Log("Running in batch cache mode."); 94 | PreCacheArchive(args); 95 | break; 96 | // Unknown command, pass straight to 7z 97 | default: 98 | Logger.Log("Unknown arguments, redirecting to 7-Zip."); 99 | Zip.Call7z(args); 100 | break; 101 | } 102 | } 103 | catch (Exception e) 104 | { 105 | Logger.Log(e.ToString(), Logger.LogLevel.Exception); 106 | Environment.ExitCode = 1; 107 | } 108 | } 109 | 110 | Logger.Log($"Completed in {stopwatch.ElapsedMilliseconds}ms."); 111 | } 112 | 113 | /// 114 | /// Run the archive extraction. This will either extract the archive, or use the cached copy. 115 | /// 116 | /// 117 | static void ExtractArchive(string[] args) 118 | { 119 | // Remove leading -o 120 | string outputPath = args[2].Remove(0, 2); 121 | 122 | // Check if the destination path ends in 7-Zip\Temp, which is assumed to be for a game 123 | // Any other path is likely to be either Metadata, or some other non-game archive 124 | if (outputPath.EndsWith(@"7-Zip\Temp", StringComparison.InvariantCultureIgnoreCase)) 125 | { 126 | CacheManager.ExtractArchive(args); 127 | } 128 | else 129 | { 130 | Logger.Log("Unexpected output path, redirecting to 7-Zip."); 131 | Zip.Call7z(args); 132 | } 133 | } 134 | 135 | /// 136 | /// List the archive contents. File list may be cached files, or from archive. 137 | /// Will also apply file extension priority if configured. 138 | /// 139 | /// 140 | static void ListArchive(string[] args) 141 | { 142 | CacheManager.ListArchive(); 143 | } 144 | 145 | static void PreCacheArchive(string[] args) 146 | { 147 | CacheManager.BatchCacheArchive(args); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/ArchiveCacheManager/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Archive Cache Manager")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Archive Cache Manager")] 13 | [assembly: AssemblyCopyright("Copyright © 2023")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("687f96d0-dfb8-4a53-bf23-aa02bd7658ec")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("2.16.0.0")] 36 | [assembly: AssemblyFileVersion("2.16.0.0")] 37 | -------------------------------------------------------------------------------- /src/ArchiveCacheManager/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/ArchiveCacheManager/icon.ico -------------------------------------------------------------------------------- /src/Core/Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {035823E0-C80B-49BF-9FAD-9C65EA45E3A9} 8 | Library 9 | Properties 10 | ArchiveCacheManager 11 | ArchiveCacheManager.Core 12 | v4.7.2 13 | 512 14 | true 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | OnOutputUpdated 36 | 37 | 38 | 39 | ..\packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | copy /Y $(TargetDir)*.dll c:\LaunchBox\Plugins\ArchiveCacheManager 78 | mkdir $(SolutionDir)..\release\ArchiveCacheManager 79 | copy /Y $(TargetDir)*.dll $(SolutionDir)..\release\ArchiveCacheManager 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/Core/DiskUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace ArchiveCacheManager 7 | { 8 | public class DiskUtils 9 | { 10 | [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] 11 | static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); 12 | 13 | /// 14 | /// Deletes the entire content of a directory, and optionally the path itself. 15 | /// All files will have the FileAttributes.Normal attribute applied, so read-only files will be deleted. 16 | /// 17 | /// The path to delete. 18 | /// Whether to delete the path itself, or only the contents. 19 | public static void DeleteDirectory(string path, bool contentsOnly = false, bool unlink = false) 20 | { 21 | try 22 | { 23 | if (Directory.Exists(path)) 24 | { 25 | string linkSource = string.Empty; 26 | try 27 | { 28 | linkSource = File.ReadAllText(PathUtils.GetArchiveCacheLinkFlagPath(path)); 29 | } 30 | catch (Exception) 31 | { 32 | } 33 | 34 | // Enumerate and delete all files in all subdirectories 35 | foreach (string filePath in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) 36 | { 37 | // Clear any read-only or other special file attributes. 38 | File.SetAttributes(filePath, FileAttributes.Normal); 39 | File.Delete(filePath); 40 | } 41 | // Enumerate and delete all subdirectories 42 | foreach (string dirPath in Directory.EnumerateDirectories(path)) 43 | { 44 | Directory.Delete(dirPath, true); 45 | } 46 | 47 | if (!contentsOnly) 48 | { 49 | Directory.Delete(path, true); 50 | } 51 | 52 | if (!string.IsNullOrEmpty(linkSource) && unlink) 53 | { 54 | SetDirectoryContentsReadOnly(linkSource); 55 | File.Delete(PathUtils.GetArchiveCacheLinkFlagPath(linkSource)); 56 | } 57 | } 58 | } 59 | catch (Exception e) 60 | { 61 | Logger.Log(e.ToString(), Logger.LogLevel.Exception); 62 | } 63 | } 64 | 65 | /// 66 | /// Deletes a file from disk. Will set the FileAttributes.Normal attribute, so read-only files will be deleted. 67 | /// 68 | /// The file to delete. 69 | public static void DeleteFile(string filePath) 70 | { 71 | try 72 | { 73 | if (File.Exists(filePath)) 74 | { 75 | // Clear any read-only or other special file attributes. 76 | File.SetAttributes(filePath, FileAttributes.Normal); 77 | File.Delete(filePath); 78 | } 79 | } 80 | catch (Exception e) 81 | { 82 | Logger.Log(e.ToString(), Logger.LogLevel.Exception); 83 | } 84 | } 85 | 86 | /// 87 | /// Get the size on disk of the given directory. Recursively called to get the size of all subdirectories. 88 | /// 89 | /// The directory to get the size of. 90 | /// The size of the directory and all sub directories in bytes, 0 on error. 91 | public static long DirectorySize(DirectoryInfo directoryInfo) 92 | { 93 | long size = 0; 94 | 95 | try 96 | { 97 | if (directoryInfo.Exists) 98 | { 99 | foreach (FileInfo fileInfo in directoryInfo.GetFiles()) 100 | { 101 | size += fileInfo.Length; 102 | } 103 | foreach (DirectoryInfo dirInfo in directoryInfo.GetDirectories()) 104 | { 105 | size += DirectorySize(dirInfo); 106 | } 107 | } 108 | } 109 | catch (Exception e) 110 | { 111 | Logger.Log(e.ToString(), Logger.LogLevel.Exception); 112 | } 113 | 114 | return size; 115 | } 116 | 117 | /// 118 | /// Get the file size. If the path is a symlink, the target file size will be returned. 119 | /// 120 | /// 121 | /// The size of the file in bytes, or 0 on error. 122 | public static long GetFileSize(string path) 123 | { 124 | long size = 0; 125 | try 126 | { 127 | size = new FileInfo(path).Length; 128 | } 129 | catch (Exception) 130 | { 131 | } 132 | 133 | // A zero size could be a symlink, try open it a check the length that way. 134 | if (size == 0) 135 | { 136 | FileStream file = null; 137 | try 138 | { 139 | file = File.OpenRead(path); 140 | size = file.Length; 141 | } 142 | catch (Exception) 143 | { 144 | } 145 | 146 | if (file != null) 147 | { 148 | file.Close(); 149 | } 150 | } 151 | 152 | return size; 153 | } 154 | 155 | /// 156 | /// Sets the read-only attribute on all files in the path, including subdirectories. Will NOT set read-only 157 | /// on files used internally by Archive Cache Manager. 158 | /// 159 | /// The root path of the files to set read-only. 160 | public static void SetDirectoryContentsReadOnly(string path) 161 | { 162 | // These files will not be set read-only, as they are written by Archive Cache Manager. 163 | string[] managerFiles = PathUtils.GetManagerFiles(path); 164 | 165 | try 166 | { 167 | // Set archive files as read-only, so LB doesn't delete them. 168 | foreach (string filePath in Directory.GetFiles(path, "*", SearchOption.AllDirectories)) 169 | { 170 | if (!managerFiles.Contains(filePath)) 171 | { 172 | File.SetAttributes(filePath, File.GetAttributes(filePath) | FileAttributes.ReadOnly); 173 | } 174 | } 175 | } 176 | catch (Exception e) 177 | { 178 | Logger.Log(e.ToString(), Logger.LogLevel.Exception); 179 | } 180 | } 181 | 182 | /// 183 | /// Sets the read-only attribute on the specified file. 184 | /// 185 | /// The path of the file to set read-only. 186 | public static void SetFileReadOnly(string filePath) 187 | { 188 | try 189 | { 190 | File.SetAttributes(filePath, File.GetAttributes(filePath) | FileAttributes.ReadOnly); 191 | } 192 | catch (Exception e) 193 | { 194 | Logger.Log(e.ToString(), Logger.LogLevel.Exception); 195 | } 196 | } 197 | 198 | /// 199 | /// Create an empty file. If the path does not exist, it will be created. 200 | /// 201 | /// 202 | public static bool CreateFile(string path) 203 | { 204 | StreamWriter writer = null; 205 | bool success = false; 206 | 207 | try 208 | { 209 | Directory.CreateDirectory(Path.GetDirectoryName(path)); 210 | writer = new StreamWriter(path, true); 211 | success = true; 212 | } 213 | catch (Exception e) 214 | { 215 | Logger.Log(e.ToString(), Logger.LogLevel.Exception); 216 | } 217 | finally 218 | { 219 | writer.Close(); 220 | } 221 | 222 | return success; 223 | } 224 | 225 | public static void HardLink(string dest, string source) 226 | { 227 | bool result = CreateHardLink(dest, source, IntPtr.Zero); 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/Core/Extractors/Chdman.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace ArchiveCacheManager 10 | { 11 | public class Chdman : Extractor 12 | { 13 | string executablePath = Path.Combine(PathUtils.GetExtractorRootPath(), "chdman.exe"); 14 | 15 | public Chdman() 16 | { 17 | 18 | } 19 | 20 | public static bool SupportedType(string archivePath) 21 | { 22 | return PathUtils.HasExtension(archivePath, new string[] { ".chd" }); 23 | } 24 | 25 | public override bool Extract(string archivePath, string cachePath, string[] includeList = null, string[] excludeList = null) 26 | { 27 | string args = string.Format("extractcd -i \"{0}\" -o \"{1}\" -f", archivePath, Path.Combine(cachePath, Path.GetFileNameWithoutExtension(archivePath) + ".cue")); 28 | 29 | // chdman reports the progress status on stderr, not stdout 30 | (string stdout, string stderr, int exitCode) = ProcessUtils.RunProcess(executablePath, args, false, null, true, ExtractionProgress); 31 | 32 | if (exitCode != 0) 33 | { 34 | Logger.Log(string.Format("chdman returned exit code {0} with error output:\r\n{1}", exitCode, stderr)); 35 | Environment.ExitCode = exitCode; 36 | } 37 | 38 | return exitCode == 0; 39 | } 40 | 41 | public override long GetSize(string archivePath, string fileInArchive = null) 42 | { 43 | string args = string.Format("info -i \"{0}\"", archivePath); 44 | 45 | (string stdout, string stderr, int exitCode) = ProcessUtils.RunProcess(executablePath, args); 46 | 47 | if (exitCode != 0) 48 | { 49 | Logger.Log(string.Format("chdman returned exit code {0} with error output:\r\n{1}", exitCode, stderr)); 50 | Environment.ExitCode = exitCode; 51 | } 52 | 53 | /* 54 | stdout will be in the format below: 55 | ------ 56 | c:\LaunchBox\ThirdParty\chdman>chdman info -i "c:\Emulation\ROMs\Doom (USA).zip" 57 | chdman - MAME Compressed Hunks of Data (CHD) manager 0.242 (mame0242) 58 | Input file: c:\Emulation\ROMs\Sony - Playstation\Doom (USA).chd 59 | File Version: 5 60 | Logical size: 302,660,928 bytes 61 | Hunk Size: 19,584 bytes 62 | Total Hunks: 15,455 63 | Unit Size: 2,448 bytes 64 | Total Units: 123,636 65 | Compression: cdlz (CD LZMA), cdzl (CD Deflate), cdfl (CD FLAC) 66 | CHD size: 182,307,697 bytes 67 | Ratio: 60.2% 68 | SHA1: 1fe56d1e220712bdc2681bb55f2e90b457cc593b 69 | Data SHA1: cdcd62e6d75a6e22a9f5172c5c9175def92dce84 70 | Metadata: Tag='CHT2' Index=0 Length=92 bytes 71 | TRACK:1 TYPE:MODE2_RAW SUBTYPE:NONE FRAMES:35789 PREGAP:0 PG 72 | Metadata: Tag='CHT2' Index=1 Length=91 bytes 73 | TRACK:2 TYPE:AUDIO SUBTYPE:NONE FRAMES:14344 PREGAP:150 PGTY 74 | Metadata: Tag='CHT2' Index=2 Length=90 bytes 75 | TRACK:3 TYPE:AUDIO SUBTYPE:NONE FRAMES:8844 PREGAP:150 PGTYP 76 | Metadata: Tag='CHT2' Index=3 Length=91 bytes 77 | TRACK:4 TYPE:AUDIO SUBTYPE:NONE FRAMES:17854 PREGAP:150 PGTY 78 | Metadata: Tag='CHT2' Index=4 Length=91 bytes 79 | TRACK:5 TYPE:AUDIO SUBTYPE:NONE FRAMES:15611 PREGAP:150 PGTY 80 | Metadata: Tag='CHT2' Index=5 Length=90 bytes 81 | TRACK:6 TYPE:AUDIO SUBTYPE:NONE FRAMES:9752 PREGAP:150 PGTYP 82 | Metadata: Tag='CHT2' Index=6 Length=90 bytes 83 | TRACK:7 TYPE:AUDIO SUBTYPE:NONE FRAMES:4187 PREGAP:150 PGTYP 84 | Metadata: Tag='CHT2' Index=7 Length=91 bytes 85 | TRACK:8 TYPE:AUDIO SUBTYPE:NONE FRAMES:17245 PREGAP:150 PGTY 86 | */ 87 | 88 | long size = 0; 89 | 90 | if (exitCode == 0) 91 | { 92 | string[] stdoutArray = stdout.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); 93 | // We use the "logical size" as the uncompressed size, and while this isn't accurate, it's good enough. 94 | // Find the string beginning with "Logical size:", then take the substring after it which will be "xxx,xxx,xxx bytes" 95 | string sizeString = Array.Find(stdoutArray, a => a.StartsWith("Logical size:")).Substring(14); 96 | // Remove the trailing " bytes" from the string, leaving "xxx,xxx,xxx" 97 | sizeString = sizeString.Split(" ".ToSingleArray(), StringSplitOptions.RemoveEmptyEntries)[0]; 98 | sizeString = sizeString.Replace(",", string.Empty); 99 | size = Convert.ToInt64(sizeString); 100 | } 101 | 102 | return size; 103 | } 104 | 105 | public override string[] List(string archivePath) 106 | { 107 | string[] fileList = new string[2]; 108 | fileList[0] = Path.GetFileNameWithoutExtension(archivePath) + ".cue"; 109 | fileList[1] = Path.GetFileNameWithoutExtension(archivePath) + ".bin"; 110 | return fileList; 111 | } 112 | 113 | public override string Name() 114 | { 115 | return "chdman"; 116 | } 117 | 118 | public override string GetExtractorPath() 119 | { 120 | return executablePath; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Core/Extractors/DolphinTool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace ArchiveCacheManager 10 | { 11 | public class DolphinTool : Extractor 12 | { 13 | string executablePath = Path.Combine(PathUtils.GetExtractorRootPath(), "DolphinTool.exe"); 14 | 15 | public DolphinTool() 16 | { 17 | 18 | } 19 | 20 | public static bool SupportedType(string archivePath) 21 | { 22 | return PathUtils.HasExtension(archivePath, new string[] { ".rvz", ".wia", ".gcz" }); 23 | } 24 | 25 | public override bool Extract(string archivePath, string cachePath, string[] includeList = null, string[] excludeList = null) 26 | { 27 | string args = string.Format("convert -i \"{0}\" -o \"{1}\" -f iso", archivePath, Path.Combine(cachePath, Path.GetFileNameWithoutExtension(archivePath) + ".iso")); 28 | 29 | // chdman reports the progress status on stderr, not stdout 30 | (string stdout, string stderr, int exitCode) = ProcessUtils.RunProcess(executablePath, args); 31 | 32 | if (exitCode != 0) 33 | { 34 | Logger.Log(string.Format("DolphinTool returned exit code {0} with error output:\r\n{1}", exitCode, stderr)); 35 | Environment.ExitCode = exitCode; 36 | } 37 | 38 | return exitCode == 0; 39 | } 40 | 41 | public override long GetSize(string archivePath, string fileInArchive = null) 42 | { 43 | return DiskUtils.GetFileSize(archivePath); 44 | } 45 | 46 | public override string[] List(string archivePath) 47 | { 48 | return string.Format("{0}.iso", Path.GetFileNameWithoutExtension(archivePath)).ToSingleArray(); 49 | } 50 | 51 | public override string Name() 52 | { 53 | return "DolphinTool"; 54 | } 55 | 56 | public override string GetExtractorPath() 57 | { 58 | return executablePath; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Core/Extractors/ExtractXiso.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace ArchiveCacheManager 10 | { 11 | public class ExtractXiso : Extractor 12 | { 13 | string executablePath = Path.Combine(PathUtils.GetExtractorRootPath(), "extract-xiso.exe"); 14 | 15 | public ExtractXiso() 16 | { 17 | 18 | } 19 | 20 | public static bool SupportedType(string archivePath) 21 | { 22 | return PathUtils.HasExtension(archivePath, new string[] { ".zip", ".iso" }); 23 | } 24 | 25 | public override bool Extract(string archivePath, string cachePath, string[] includeList = null, string[] excludeList = null) 26 | { 27 | string isoPath = string.Empty; 28 | string extension = Path.GetExtension(archivePath).ToLower(); 29 | 30 | if (extension.Equals(".zip")) 31 | { 32 | Extractor zip = new Zip(); 33 | if (zip.Extract(archivePath, cachePath, "*.iso".ToSingleArray(), excludeList)) 34 | { 35 | isoPath = Directory.GetFiles(cachePath, "*.iso")[0]; 36 | } 37 | } 38 | else if (extension.Equals(".iso")) 39 | { 40 | Extractor robocopy = new Robocopy(); 41 | if (robocopy.Extract(archivePath, cachePath, includeList, excludeList)) 42 | { 43 | isoPath = Directory.GetFiles(cachePath, "*.iso")[0]; 44 | } 45 | } 46 | 47 | // Double-check isoPath is set, and not the original archive path, as it will be deleted. 48 | if (string.IsNullOrEmpty(isoPath) || string.Equals(isoPath, archivePath, StringComparison.InvariantCultureIgnoreCase)) 49 | { 50 | Logger.Log($"extract-xiso path check failed: {isoPath}"); 51 | Environment.ExitCode = 1; 52 | return false; 53 | } 54 | 55 | // -d Output directory 56 | // -D Delete old iso after conversion 57 | // -r Rewrite mode 58 | // Is safe to use -D option here to delete old iso, as we're working on copy in the cache 59 | string args = string.Format("-d \"{0}\" -D -r \"{1}\"", cachePath, isoPath); 60 | 61 | (string stdout, string stderr, int exitCode) = ProcessUtils.RunProcess(executablePath, args, false, null, false, ExtractionProgress); 62 | 63 | if (exitCode != 0) 64 | { 65 | Logger.Log($"extract-xiso returned exit code {exitCode} with error output:\r\n{stderr}"); 66 | Environment.ExitCode = exitCode; 67 | } 68 | 69 | return exitCode == 0; 70 | } 71 | 72 | public override long GetSize(string archivePath, string fileInArchive = null) 73 | { 74 | string extension = Path.GetExtension(archivePath).ToLower(); 75 | 76 | // If iso is still zipped, get its decompressed size. This will be (much) larger than the final xiso size. 77 | // This size will be used when calculating how much room to make in the cache, so no harm in over-estimating. 78 | // Actual converted xiso size will be checked and set in LaunchInfo.UpdateSizeFromCache() once extraction is complete. 79 | if (extension.Equals(".zip")) 80 | { 81 | Extractor zip = new Zip(); 82 | return zip.GetSize(archivePath, fileInArchive); 83 | } 84 | 85 | string args = string.Format("-l \"{0}\"", archivePath); 86 | 87 | (string stdout, string stderr, int exitCode) = ProcessUtils.RunProcess(executablePath, args); 88 | 89 | /* 90 | stdout will be in the format below: 91 | ------ 92 | c:\LaunchBox\ThirdParty\extract-xiso>extract-xiso -l "c:\Emulation\ROMs\JSRF - Jet Set Radio Future (USA).iso" 93 | extract-xiso v2.7.1 (01.11.14) for win32 - written by in 94 | 95 | listing JSRF - Jet Set Radio Future (USA).iso: 96 | 97 | \default.xbe (2281472 bytes) 98 | \Media\ (0 bytes) 99 | \Media\Cache\ (0 bytes) 100 | \Media\Cache\Cache00.tbl (3786 bytes) 101 | \Media\Cache\Cache01.tbl (5869 bytes) 102 | \Media\Cache\Cache02.tbl (72 bytes) 103 | \Media\Cache\Cache03.tbl (72 bytes) 104 | \Media\Cache\Cache04.tbl (72 bytes) 105 | \Media\Cache\Cache05.tbl (72 bytes) 106 | \Media\Cache\Cache06.tbl (72 bytes) 107 | \Media\Cache\Cache07.tbl (72 bytes) 108 | \Media\Cache\Cache08.tbl (72 bytes) 109 | [...] 110 | \Media\Z_ADX\MUSENJ\gjv009_01.adx (124092 bytes) 111 | \Media\Z_ADX\MUSENJ\gjv010_01.adx (117900 bytes) 112 | \Media\Z_ADX\MUSENJ\gjv011_01.adx (93078 bytes) 113 | 114 | 3221 files in c:\Emulation\ROMs\JSRF - Jet Set Radio Future (USA).iso total 2498145117 bytes 115 | */ 116 | 117 | long size = 0; 118 | 119 | if (exitCode == 0) 120 | { 121 | // Split on the archive path, which appears just before the byte size. Result should be two elements. 122 | string[] stdoutArray = stdout.Split(new string[] { archivePath }, StringSplitOptions.RemoveEmptyEntries); 123 | // Second element should be in form " total xxxxx bytes" 124 | string sizeString = stdoutArray[1]; 125 | // Convert to array of 3 elements, where second element is the size. 126 | sizeString = sizeString.Split(" ".ToSingleArray(), StringSplitOptions.RemoveEmptyEntries)[1]; 127 | size = Convert.ToInt64(sizeString); 128 | } 129 | else 130 | { 131 | Logger.Log($"extract-xiso returned exit code {exitCode} with error output:\r\n{stderr}"); 132 | Environment.ExitCode = exitCode; 133 | } 134 | 135 | return size; 136 | } 137 | 138 | public override string[] List(string archivePath) 139 | { 140 | return string.Format("{0}.iso", Path.GetFileNameWithoutExtension(archivePath)).ToSingleArray(); 141 | } 142 | 143 | public override string Name() 144 | { 145 | return "extract-xiso"; 146 | } 147 | 148 | public override string GetExtractorPath() 149 | { 150 | return executablePath; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Core/Extractors/Extractor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace ArchiveCacheManager 10 | { 11 | public abstract class Extractor 12 | { 13 | private static int mProgressDivisor = 1; 14 | private static int mProgressOffset = 0; 15 | // This regex handles 7z, robocopy, and chdman. 16 | // Other extractors may be different, so set ProgressRegex accordingly. 17 | private static string mProgressRegex = "(\\d+\\.?\\d*)%(.*)"; 18 | 19 | public int ProgressDivisor 20 | { 21 | get => mProgressDivisor; 22 | set => mProgressDivisor = Math.Max(value, 1); 23 | } 24 | 25 | public int ProgressOffset 26 | { 27 | get => mProgressOffset; 28 | set => mProgressOffset = Math.Max(value, 0); 29 | } 30 | 31 | protected string ProgressRegex 32 | { 33 | get => mProgressRegex; 34 | set => mProgressRegex = value; 35 | } 36 | 37 | public static string ExtractionProgress(string stdout) 38 | { 39 | string progress = stdout; 40 | 41 | if (stdout != null) 42 | { 43 | try 44 | { 45 | Match match = Regex.Match(stdout, mProgressRegex); 46 | if (match.Success) 47 | { 48 | progress = string.Format("{0,3}% - extracting", (int)(double.Parse(match.Groups[1].Value) / mProgressDivisor) + mProgressOffset); 49 | } 50 | } 51 | catch (Exception) 52 | { 53 | 54 | } 55 | } 56 | 57 | return progress; 58 | } 59 | 60 | /// 61 | /// Name of the extractor, used for informational purposes. 62 | /// 63 | /// Name of the extractor. 64 | public abstract string Name(); 65 | 66 | /// 67 | /// Get the size of specified archive after extraction. 68 | /// 69 | /// 70 | /// /// 71 | /// The extracted size of the archive in bytes. 72 | public abstract long GetSize(string archivePath, string fileInArchive = null); 73 | 74 | /// 75 | /// Run the extract command on the specified archive. Console output should be redirected to this app's console so 76 | /// LaunchBox has access to the extraction progress. 77 | /// 78 | /// 79 | /// True on successful extraction, False otherwise. 80 | public abstract bool Extract(string archivePath, string cachePath, string[] includeList = null, string[] excludeList = null); 81 | 82 | /// 83 | /// Get a file list for the specified archive. 84 | /// 85 | /// Archive to list. 86 | /// The list of files in an archive. 87 | public abstract string[] List(string archivePath); 88 | 89 | public abstract string GetExtractorPath(); 90 | } 91 | } -------------------------------------------------------------------------------- /src/Core/Extractors/Robocopy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace ArchiveCacheManager 10 | { 11 | public class Robocopy : Extractor 12 | { 13 | private long? archiveSize; 14 | private string archiveSizePath; 15 | 16 | public Robocopy() 17 | { 18 | archiveSize = null; 19 | archiveSizePath = string.Empty; 20 | } 21 | 22 | public override bool Extract(string archivePath, string cachePath, string[] includeList = null, string[] excludeList = null) 23 | { 24 | // If the file is less than 50MB, the overhead of calling Robocopy isn't worth it. Instead just use File.Copy(). 25 | if (GetSize(archivePath) > 52_428_800) 26 | { 27 | string args = string.Format("/c robocopy \"{0}\" \"{1}\" \"{2}\"", Path.GetDirectoryName(archivePath), cachePath, Path.GetFileName(archivePath)); 28 | 29 | (string stdout, string stderr, int exitCode) = ProcessUtils.RunProcess("cmd.exe", args, true, ExtractionProgress, true); 30 | 31 | if (exitCode >= 8) 32 | { 33 | Logger.Log(string.Format("Robocopy returned exit code {0} with error output:\r\n{1}", exitCode, stdout)); 34 | Environment.ExitCode = exitCode; 35 | } 36 | 37 | return exitCode < 8; 38 | } 39 | else 40 | { 41 | try 42 | { 43 | File.Copy(archivePath, Path.Combine(cachePath, Path.GetFileName(archivePath)), true); 44 | return true; 45 | } 46 | catch (Exception e) 47 | { 48 | Logger.Log($"File copy error: {e.ToString()}"); 49 | Console.Out.WriteLine(e.Message); 50 | Environment.ExitCode = 1; 51 | } 52 | } 53 | 54 | return false; 55 | } 56 | 57 | public override long GetSize(string archivePath, string fileInArchive = null) 58 | { 59 | if (!Equals(archivePath, archiveSizePath) || archiveSize == null) 60 | { 61 | archiveSizePath = archivePath; 62 | archiveSize = DiskUtils.GetFileSize(archivePath); 63 | } 64 | 65 | return (long)archiveSize; 66 | } 67 | 68 | public override string[] List(string archivePath) 69 | { 70 | return Path.GetFileName(archivePath).ToSingleArray(); 71 | } 72 | 73 | public override string Name() 74 | { 75 | return "File Copy"; 76 | } 77 | 78 | public override string GetExtractorPath() 79 | { 80 | return null; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Core/Extractors/Zip.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace ArchiveCacheManager 10 | { 11 | /// 12 | /// Handles all calls to 7-Zip. 13 | /// 14 | public class Zip : Extractor 15 | { 16 | public override string Name() 17 | { 18 | return "7-Zip"; 19 | } 20 | 21 | public override long GetSize(string archivePath, string fileInArchive = null) 22 | { 23 | var (stdout, _, exitCode) = ListArchiveDetails(archivePath, fileInArchive.ToSingleArray(), null, false); 24 | 25 | if (exitCode == 0) 26 | { 27 | return ParseArchiveSize(stdout); 28 | } 29 | 30 | return 0; 31 | } 32 | 33 | private long ParseArchiveSize(string stdout) 34 | { 35 | try 36 | { 37 | string[] stdoutArray = stdout.Split(new string[] { "------------------------" }, StringSplitOptions.RemoveEmptyEntries); 38 | return ParseSize(stdoutArray[stdoutArray.Length - 1]); 39 | } 40 | catch (Exception e) 41 | { 42 | Logger.Log($"Failed to parse archive size ({this.GetType()}):\r\n{e.ToString()}"); 43 | } 44 | 45 | return 0; 46 | } 47 | 48 | private long ParseSize(string line) 49 | { 50 | try 51 | { 52 | // Take substring at 25th char, after date/time/attr details and before file sizes 53 | return Convert.ToInt64(line.Substring(25).Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[0]); 54 | } 55 | catch (Exception e) 56 | { 57 | Logger.Log($"Failed to parse archive size ({this.GetType()}):\r\n{e.ToString()}"); 58 | } 59 | 60 | return 0; 61 | } 62 | 63 | public override bool Extract(string archivePath, string cachePath, string[] includeList = null, string[] excludeList = null) 64 | { 65 | // x = extract 66 | // {0} = archive path 67 | // -o{1} = output path 68 | // -y = answer yes to any queries 69 | // -aoa = overwrite all existing files 70 | // -bsp1 = redirect progress to stdout 71 | string args = string.Format("x \"{0}\" \"-o{1}\" -y -aoa -bsp1 {2}", archivePath, cachePath, GetIncludeExcludeArgs(includeList, excludeList, false)); 72 | 73 | var (_, _, exitCode) = Run7z(args, true); 74 | return exitCode == 0; 75 | } 76 | 77 | public override string[] List(string archivePath) 78 | { 79 | var (stdout, _, exitCode) = ListArchiveDetails(archivePath); 80 | 81 | /* 82 | stdout will be in the format below: 83 | -------- 84 | c:\LaunchBox\ThirdParty\7-Zip>7z l "c:\Emulation\ROMs\Doom (USA).zip" 85 | 86 | 7-Zip 19.00 (x64) : Copyright (c) 1999-2018 Igor Pavlov : 2019-02-21 87 | 88 | Scanning the drive for archives: 89 | 1 file, 260733247 bytes (249 MiB) 90 | 91 | Listing archive: c:\Emulation\ROMs\Doom (USA).zip 92 | 93 | -- 94 | Path = c:\Emulation\ROMs\Doom (USA).zip 95 | Type = zip 96 | Physical Size = 260733247 97 | Comment = TORRENTZIPPED-9F8E0391 98 | 99 | Date Time Attr Size Compressed Name 100 | ------------------- ----- ------------ ------------ ------------------------ 101 | 1996-12-24 23:32:00 ..... 84175728 69019477 Doom (USA) (Track 1).bin 102 | 1996-12-24 23:32:00 ..... 33737088 31332352 Doom (USA) (Track 2).bin 103 | 1996-12-24 23:32:00 ..... 20801088 19163186 Doom (USA) (Track 3).bin 104 | 1996-12-24 23:32:00 ..... 41992608 38498123 Doom (USA) (Track 4).bin 105 | 1996-12-24 23:32:00 ..... 36717072 34627868 Doom (USA) (Track 5).bin 106 | 1996-12-24 23:32:00 ..... 22936704 21946175 Doom (USA) (Track 6).bin 107 | 1996-12-24 23:32:00 ..... 9847824 8577248 Doom (USA) (Track 7).bin 108 | 1996-12-24 23:32:00 ..... 40560240 37567531 Doom (USA) (Track 8).bin 109 | 1996-12-24 23:32:00 ..... 814 147 Doom (USA).cue 110 | ------------------- ----- ------------ ------------ ------------------------ 111 | 1996-12-24 23:32:00 290769166 260732107 9 files 112 | 113 | c:\LaunchBox\ThirdParty\7-Zip> 114 | -------- 115 | */ 116 | 117 | string[] fileList = Array.Empty(); 118 | 119 | if (exitCode == 0) 120 | { 121 | // Split on the "----" dividers (see above). There will then be three sections, the header info, the files, and the summary. 122 | string[] stdoutArray = stdout.Split(new string[] { "------------------- ----- ------------ ------------ ------------------------" }, StringSplitOptions.RemoveEmptyEntries); 123 | 124 | if (stdoutArray.Length > 2) 125 | { 126 | // Split the files on "\r\n", so we have an array with one element per filename + info 127 | fileList = stdoutArray[1].Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); 128 | for (int i = 0; i < fileList.Length; i++) 129 | { 130 | // Split the string at the 53rd char, after the date/time/attr/size/compressed info. 131 | fileList[i] = fileList[i].Substring(53).Trim(); 132 | } 133 | } 134 | } 135 | else 136 | { 137 | Logger.Log(string.Format("Error listing archive {0}.", archivePath)); 138 | Environment.ExitCode = exitCode; 139 | } 140 | 141 | return fileList; 142 | } 143 | 144 | public static string Get7zVersion() 145 | { 146 | var (stdout, _, _) = Run7z(""); 147 | return stdout.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[0]; 148 | } 149 | 150 | public static bool SupportedType(string archivePath) 151 | { 152 | return PathUtils.HasExtension(archivePath, new string[] { ".zip", ".7z", ".rar", ".gz", ".gzip" }); 153 | } 154 | 155 | /// 156 | /// Run the 7z list command on the specified archive. 157 | /// 158 | /// 159 | /// Tuple of (stdout, stderr, exitCode). 160 | private (string, string, int) ListArchiveDetails(string archivePath, string[] includeList = null, string[] excludeList = null, bool prefixWildcard = false) 161 | { 162 | // l = list 163 | // {0} = archive path 164 | string args = string.Format("l \"{0}\" {1}", archivePath, GetIncludeExcludeArgs(includeList, excludeList, prefixWildcard)); 165 | 166 | return Run7z(args); 167 | } 168 | 169 | private string GetIncludeExcludeArgs(string[] includeList, string[] excludeList, bool prefixWildcard) 170 | { 171 | string includeExcludeArgs = string.Empty; 172 | // -i!"" = include files which match wildcard 173 | // -x!"" = exclude files which match wildcard 174 | // -r = recursive search for files 175 | if (includeList != null && includeList.Count() > 0) 176 | { 177 | foreach (var include in includeList) 178 | { 179 | includeExcludeArgs = string.Format("{0} \"-i!{1}{2}\"", includeExcludeArgs, prefixWildcard ? "*" : "", include); 180 | } 181 | } 182 | 183 | if (excludeList != null && excludeList.Count() > 0) 184 | { 185 | foreach (var exclude in excludeList) 186 | { 187 | includeExcludeArgs = string.Format("{0} \"-x!{1}{2}\"", includeExcludeArgs, prefixWildcard ? "*" : "", exclude); 188 | } 189 | } 190 | 191 | if (!string.IsNullOrEmpty(includeExcludeArgs)) 192 | { 193 | includeExcludeArgs += " -r"; 194 | } 195 | 196 | return includeExcludeArgs.Trim(); 197 | } 198 | 199 | /// 200 | /// Run the desired 7z command. 201 | /// 202 | /// 203 | public static void Call7z(string[] args) 204 | { 205 | string[] quotedArgs = args; 206 | string argString; 207 | 208 | // Wrap any args containing spaces with double-quotes. 209 | for (int i = 0; i < quotedArgs.Count(); i++) 210 | { 211 | if (quotedArgs[i].Contains(" ") && !quotedArgs[i].StartsWith("\"") && !quotedArgs[i].EndsWith("\"")) 212 | { 213 | quotedArgs[i] = string.Format("\"{0}\"", quotedArgs[i]); 214 | } 215 | } 216 | 217 | argString = String.Join(" ", quotedArgs); 218 | 219 | var (stdout, stderr, exitCode) = Run7z(argString); 220 | 221 | // Print the results to console and set the error code for LaunchBox to deal with 222 | Console.Write(stdout); 223 | Console.Write(stderr); 224 | Environment.ExitCode = exitCode; 225 | } 226 | 227 | /// 228 | /// Run 7z with the specified arguments. Results are returned by ref. 229 | /// 230 | /// 231 | /// Tuple of (stdout, stderr, exitCode). 232 | static (string, string, int) Run7z(string args, bool redirectOutput = false, bool redirectError = false) 233 | { 234 | (string stdout, string stderr, int exitCode) = ProcessUtils.RunProcess(PathUtils.GetLaunchBox7zPath(), args, redirectOutput, ExtractionProgress, redirectError); 235 | 236 | if (exitCode != 0) 237 | { 238 | Logger.Log(string.Format("7-Zip returned exit code {0} with error output:\r\n{1}", exitCode, stderr)); 239 | Environment.ExitCode = exitCode; 240 | } 241 | 242 | return (stdout, stderr, exitCode); 243 | } 244 | 245 | public override string GetExtractorPath() 246 | { 247 | return PathUtils.GetLaunchBox7zPath(); 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/Core/FastWildcard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FastWildcard 4 | { 5 | public class FastWildcard 6 | { 7 | private const char SingleWildcardCharacter = '?'; 8 | private const char MultiWildcardCharacter = '*'; 9 | 10 | private static readonly char[] WildcardCharacters = {SingleWildcardCharacter, MultiWildcardCharacter}; 11 | 12 | /// 13 | /// Returns if the input string matches the given wildcard pattern . 14 | /// Uses default . 15 | /// 16 | /// Input string to match on 17 | /// Wildcard pattern to match against 18 | /// True if a match is found, false otherwise 19 | public static bool IsMatch(string str, string pattern) 20 | { 21 | return IsMatch(str, pattern, new MatchSettings()); 22 | } 23 | 24 | /// 25 | /// Returns if the input string matches the given wildcard pattern . 26 | /// Uses the supplied . 27 | /// 28 | /// Input string to match on 29 | /// Wildcard pattern to match with 30 | /// Match settings to use 31 | /// True if a match is found, false otherwise 32 | public static bool IsMatch(string str, string pattern, MatchSettings matchSettings) 33 | { 34 | // Pattern must contain something 35 | if (String.IsNullOrEmpty(pattern)) 36 | { 37 | throw new ArgumentOutOfRangeException(nameof(pattern)); 38 | } 39 | 40 | // Uninitialised string never matches 41 | if (str == null) 42 | { 43 | throw new ArgumentNullException(nameof(str)); 44 | } 45 | 46 | // Multi character wildcard matches everything 47 | if (pattern == "*") 48 | { 49 | return true; 50 | } 51 | 52 | // Empty string does not match 53 | if (str.Length == 0) 54 | { 55 | return false; 56 | } 57 | 58 | #if (NETSTANDARD || NETCOREAPP) && !NETSTANDARD1_3 && !NETSTANDARD2_0 59 | var strSpan = str.AsSpan(); 60 | var patternSpan = pattern.AsSpan(); 61 | #endif 62 | 63 | var strIndex = 0; 64 | 65 | for (var patternIndex = 0; patternIndex < pattern.Length; patternIndex++) 66 | { 67 | var patternCh = pattern[patternIndex]; 68 | #if (NETSTANDARD || NETCOREAPP) && !NETSTANDARD1_3 && !NETSTANDARD2_0 69 | var patternChSpan = patternSpan.Slice(patternIndex, 1); 70 | #endif 71 | 72 | if (strIndex == str.Length) 73 | { 74 | // At end of pattern for this longer string so always matches '*' 75 | if (patternCh == '*' && patternIndex == pattern.Length - 1) 76 | { 77 | return true; 78 | } 79 | 80 | return false; 81 | } 82 | 83 | // Character match 84 | #if (NETSTANDARD || NETCOREAPP) && !NETSTANDARD1_3 && !NETSTANDARD2_0 85 | var strCh = strSpan.Slice(strIndex, 1); 86 | if (patternChSpan.Equals(strCh, matchSettings.StringComparison)) 87 | #else 88 | var strCh = str[strIndex]; 89 | var patternChEqualsStrAtIndex = matchSettings.StringComparison == StringComparison.Ordinal 90 | ? patternCh.Equals(strCh) 91 | : patternCh.ToString().Equals(strCh.ToString(), matchSettings.StringComparison); 92 | if (patternChEqualsStrAtIndex) 93 | #endif 94 | { 95 | strIndex++; 96 | continue; 97 | } 98 | 99 | // Single wildcard match 100 | if (patternCh == '?') 101 | { 102 | strIndex++; 103 | continue; 104 | } 105 | 106 | // No match 107 | if (patternCh != '*') 108 | { 109 | return false; 110 | } 111 | 112 | // Multi character wildcard - last character in the pattern 113 | if (patternIndex == pattern.Length - 1) 114 | { 115 | return true; 116 | } 117 | 118 | // Match pattern to input string character-by-character until the next wildcard (or end of string if there is none) 119 | var patternChMatchStartIndex = patternIndex + 1; 120 | 121 | var nextWildcardIndex = pattern.IndexOfAny(WildcardCharacters, patternChMatchStartIndex); 122 | var patternChMatchEndIndex = nextWildcardIndex == -1 123 | ? pattern.Length - 1 124 | : nextWildcardIndex - 1; 125 | 126 | var comparisonLength = patternChMatchEndIndex - patternIndex; 127 | 128 | #if (NETSTANDARD || NETCOREAPP) && !NETSTANDARD1_3 && !NETSTANDARD2_0 129 | var comparison = patternSpan.Slice(patternChMatchStartIndex, comparisonLength); 130 | var skipToStringIndex = strSpan.Slice(strIndex).IndexOf(comparison, matchSettings.StringComparison) + strIndex; 131 | #else 132 | var comparison = pattern.Substring(patternChMatchStartIndex, comparisonLength); 133 | var skipToStringIndex = str.IndexOf(comparison, strIndex, matchSettings.StringComparison); 134 | #endif 135 | 136 | // Handle repeated instances of the same character at end of pattern 137 | if (comparisonLength == 1 && nextWildcardIndex == -1) 138 | { 139 | var skipCandidateIndex = 0; 140 | while (skipCandidateIndex == 0) 141 | { 142 | var skipToStringIndexNew = skipToStringIndex + 1; 143 | #if (NETSTANDARD || NETCOREAPP) && !NETSTANDARD1_3 && !NETSTANDARD2_0 144 | skipCandidateIndex = strSpan.Slice(skipToStringIndexNew).IndexOf(comparison, matchSettings.StringComparison); 145 | #else 146 | skipCandidateIndex = str.IndexOf(comparison, skipToStringIndexNew, matchSettings.StringComparison) - (skipToStringIndexNew); 147 | #endif 148 | if (skipCandidateIndex == 0) 149 | { 150 | skipToStringIndex = skipToStringIndexNew; 151 | } 152 | } 153 | } 154 | 155 | if (skipToStringIndex == -1) 156 | { 157 | return false; 158 | } 159 | 160 | strIndex = skipToStringIndex; 161 | } 162 | 163 | // Pattern processing completed but rest of input string was not 164 | if (strIndex < str.Length) 165 | { 166 | return false; 167 | } 168 | 169 | return true; 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Core/GameIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using IniParser; 8 | using IniParser.Model; 9 | 10 | namespace ArchiveCacheManager 11 | { 12 | public class GameIndex 13 | { 14 | private readonly string SelectedFile = "SelectedFile"; 15 | 16 | private static IniData mGameIndex = null; 17 | 18 | static GameIndex() 19 | { 20 | Load(); 21 | } 22 | 23 | public static void Load() 24 | { 25 | string gameIndexPath = PathUtils.GetPluginGameIndexPath(); 26 | mGameIndex = null; 27 | 28 | if (File.Exists(gameIndexPath)) 29 | { 30 | var parser = new FileIniDataParser(); 31 | mGameIndex = new IniData(); 32 | 33 | try 34 | { 35 | mGameIndex = parser.ReadFile(gameIndexPath); 36 | } 37 | catch (Exception e) 38 | { 39 | Logger.Log(string.Format("Error parsing game index file from {0}. Deleting invalid file.", gameIndexPath)); 40 | Logger.Log(e.ToString(), Logger.LogLevel.Exception); 41 | File.Delete(gameIndexPath); 42 | mGameIndex = null; 43 | } 44 | } 45 | } 46 | 47 | public static void Save() 48 | { 49 | string gameIndexPath = PathUtils.GetPluginGameIndexPath(); 50 | 51 | if (mGameIndex != null) 52 | { 53 | var parser = new FileIniDataParser(); 54 | 55 | try 56 | { 57 | parser.WriteFile(gameIndexPath, mGameIndex); 58 | } 59 | catch (Exception e) 60 | { 61 | Logger.Log(string.Format("Error saving game index file to {0}.", gameIndexPath)); 62 | Logger.Log(e.ToString(), Logger.LogLevel.Exception); 63 | } 64 | } 65 | } 66 | 67 | public static string GetSelectedFile(string gameId) 68 | { 69 | string selectedFile = string.Empty; 70 | 71 | if (mGameIndex != null && mGameIndex.Sections.ContainsSection(gameId)) 72 | { 73 | selectedFile = mGameIndex[gameId][nameof(SelectedFile)] ?? string.Empty; 74 | } 75 | 76 | return selectedFile; 77 | } 78 | 79 | public static void SetSelectedFile(string gameId, string selectedFile) 80 | { 81 | if (mGameIndex == null) 82 | { 83 | mGameIndex = new IniData(); 84 | } 85 | 86 | mGameIndex[gameId][nameof(SelectedFile)] = selectedFile; 87 | 88 | Save(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Core/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace ArchiveCacheManager 5 | { 6 | /// 7 | /// Helper class to log plugin status and errors. 8 | /// 9 | public class Logger 10 | { 11 | public enum LogLevel 12 | { 13 | None = 0, 14 | Exception, 15 | Debug, 16 | Info 17 | }; 18 | 19 | /// 20 | /// Initialise the log file. Will delete the previous log file if it exists. 21 | /// 22 | public static void Init() 23 | { 24 | try 25 | { 26 | string logPath = PathUtils.GetLogPath(); 27 | 28 | if (!Directory.Exists(logPath)) 29 | { 30 | Directory.CreateDirectory(logPath); 31 | } 32 | else 33 | { 34 | string[] logs = Directory.GetFiles(PathUtils.GetLogPath(), "*.log"); 35 | 36 | Array.Sort(logs); 37 | 38 | for (int i = 0; i < logs.Length - 10; i++) 39 | { 40 | File.Delete(logs[i]); 41 | } 42 | } 43 | 44 | string oldLogFilePath = Path.Combine(PathUtils.GetPluginRootPath(), "events.log"); 45 | 46 | if (File.Exists(oldLogFilePath)) 47 | { 48 | File.Delete(oldLogFilePath); 49 | } 50 | } 51 | catch (IOException) 52 | { 53 | } 54 | } 55 | 56 | /// 57 | /// Write a message to the log file. All entries will be timestamped. 58 | /// 59 | /// The message to log. 60 | /// The severity of the log entry. Default is LogLevel.Info. 61 | public static void Log(string message, LogLevel logLevel = LogLevel.Info) 62 | { 63 | StreamWriter writer = null; 64 | 65 | try 66 | { 67 | writer = new StreamWriter(PathUtils.GetLogFilePath(), true); 68 | writer.Write(string.Format("{0} - {1}\r\n", GetDateTime(), message)); 69 | } 70 | catch (IOException) 71 | { 72 | 73 | } 74 | finally 75 | { 76 | if (writer != null) 77 | { 78 | writer.Close(); 79 | } 80 | } 81 | } 82 | 83 | /// 84 | /// Returns the current date and time in YYYY-MM-DD HH:MM:SS format. 85 | /// 86 | /// The current date and time in YYYY-MM-DD HH:MM:SS format. 87 | private static string GetDateTime() 88 | { 89 | DateTime dt = DateTime.Now; 90 | return string.Format("{0:0000}-{1:00}-{2:00} {3:00}:{4:00}:{5:00}", dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Core/MatchSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FastWildcard 4 | { 5 | public class MatchSettings 6 | { 7 | private const StringComparison DefaultStringComparison = StringComparison.Ordinal; 8 | 9 | /// 10 | /// Case rules to use when comparing matching characters. 11 | /// Defaults to . 12 | /// 13 | public StringComparison StringComparison { get; set; } 14 | 15 | public MatchSettings() 16 | { 17 | StringComparison = DefaultStringComparison; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Core/ProcessUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ArchiveCacheManager 10 | { 11 | public static class ProcessUtils 12 | { 13 | static Process mProcess; 14 | 15 | public static void KillProcess() 16 | { 17 | try 18 | { 19 | mProcess.Kill(); 20 | mProcess.WaitForExit(); 21 | } 22 | catch (Exception) 23 | { 24 | } 25 | } 26 | 27 | public static (string, string, int) RunProcess(string executable, string args, bool redirectOutput = false, Func processOutput = null, bool redirectError = false, Func processError = null) 28 | { 29 | string stdout; 30 | string stderr; 31 | int exitCode; 32 | mProcess = new Process(); 33 | mProcess.StartInfo.FileName = executable; 34 | mProcess.StartInfo.Arguments = args; 35 | mProcess.StartInfo.UseShellExecute = false; 36 | mProcess.StartInfo.CreateNoWindow = true; 37 | mProcess.StartInfo.RedirectStandardOutput = true; 38 | mProcess.StartInfo.RedirectStandardError = true; 39 | StringBuilder asyncError = new StringBuilder(); 40 | StringBuilder asyncOutput = new StringBuilder(); 41 | string processedStdout = string.Empty; 42 | string processedStderr = string.Empty; 43 | mProcess.OutputDataReceived += new DataReceivedEventHandler((sender, e) => 44 | { 45 | if (redirectOutput) 46 | { 47 | processedStdout = (processOutput != null) ? processOutput(e.Data) : e.Data; 48 | Console.Out.WriteLine(processedStdout); 49 | } 50 | asyncOutput.Append("\r\n" + e.Data); 51 | }); 52 | mProcess.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => 53 | { 54 | if (redirectError) 55 | { 56 | if (processError != null) 57 | { 58 | processedStderr = processError(e.Data); 59 | Console.Out.WriteLine(processedStderr); 60 | } 61 | else 62 | { 63 | Console.Error.WriteLine(e.Data); 64 | } 65 | } 66 | asyncError.Append("\r\n" + e.Data); 67 | }); 68 | 69 | try 70 | { 71 | mProcess.Start(); 72 | mProcess.BeginErrorReadLine(); 73 | mProcess.BeginOutputReadLine(); 74 | 75 | // LB allows terminating the extraction process by pressing Esc on the loading screen. If this process is killed, 76 | // the child process (the real 7z in this case) will NOT be terminated. Add 7z as a tracked child process, which 77 | // will be automatically killed if this process is also killed. 78 | ChildProcessTracker.AddProcess(mProcess); 79 | 80 | mProcess.WaitForExit(); 81 | exitCode = mProcess.ExitCode; 82 | } 83 | catch (Exception e) 84 | { 85 | exitCode = -1; 86 | Logger.Log(e.ToString(), Logger.LogLevel.Exception); 87 | } 88 | 89 | stdout = asyncOutput.ToString(); 90 | stderr = asyncError.ToString(); 91 | 92 | return (stdout, stderr, exitCode); 93 | } 94 | } 95 | 96 | /// 97 | /// Allows processes to be automatically killed if this parent process unexpectedly quits. 98 | /// This feature requires Windows 8 or greater. On Windows 7, nothing is done. 99 | /// StackOverflow post: 100 | /// https://stackoverflow.com/a/37034966 101 | /// References: 102 | /// https://stackoverflow.com/a/4657392/386091 103 | /// https://stackoverflow.com/a/9164742/386091 104 | public static class ChildProcessTracker 105 | { 106 | /// 107 | /// Add the process to be tracked. If our current process is killed, the child processes 108 | /// that we are tracking will be automatically killed, too. If the child process terminates 109 | /// first, that's fine, too. 110 | /// 111 | public static void AddProcess(Process process) 112 | { 113 | if (s_jobHandle != IntPtr.Zero) 114 | { 115 | bool success = AssignProcessToJobObject(s_jobHandle, process.Handle); 116 | if (!success && !process.HasExited) 117 | throw new Win32Exception(); 118 | } 119 | } 120 | 121 | static ChildProcessTracker() 122 | { 123 | // This feature requires Windows 8 or later. To support Windows 7 requires 124 | // registry settings to be added if you are using Visual Studio plus an 125 | // app.manifest change. 126 | // https://stackoverflow.com/a/4232259/386091 127 | // https://stackoverflow.com/a/9507862/386091 128 | if (Environment.OSVersion.Version < new Version(6, 2)) 129 | return; 130 | 131 | // The job name is optional (and can be null) but it helps with diagnostics. 132 | // If it's not null, it has to be unique. Use SysInternals' Handle command-line 133 | // utility: handle -a ChildProcessTracker 134 | string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id; 135 | s_jobHandle = CreateJobObject(IntPtr.Zero, jobName); 136 | 137 | var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION(); 138 | 139 | // This is the key flag. When our process is killed, Windows will automatically 140 | // close the job handle, and when that happens, we want the child processes to 141 | // be killed, too. 142 | info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; 143 | 144 | var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); 145 | extendedInfo.BasicLimitInformation = info; 146 | 147 | int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); 148 | IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); 149 | try 150 | { 151 | Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); 152 | 153 | if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation, 154 | extendedInfoPtr, (uint)length)) 155 | { 156 | throw new Win32Exception(); 157 | } 158 | } 159 | finally 160 | { 161 | Marshal.FreeHGlobal(extendedInfoPtr); 162 | } 163 | } 164 | 165 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 166 | static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name); 167 | 168 | [DllImport("kernel32.dll")] 169 | static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType, 170 | IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); 171 | 172 | [DllImport("kernel32.dll", SetLastError = true)] 173 | static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); 174 | 175 | // Windows will automatically close any open job handles when our process terminates. 176 | // This can be verified by using SysInternals' Handle utility. When the job handle 177 | // is closed, the child processes will be killed. 178 | private static readonly IntPtr s_jobHandle; 179 | } 180 | 181 | public enum JobObjectInfoType 182 | { 183 | AssociateCompletionPortInformation = 7, 184 | BasicLimitInformation = 2, 185 | BasicUIRestrictions = 4, 186 | EndOfJobTimeInformation = 6, 187 | ExtendedLimitInformation = 9, 188 | SecurityLimitInformation = 5, 189 | GroupInformation = 11 190 | } 191 | 192 | [StructLayout(LayoutKind.Sequential)] 193 | public struct JOBOBJECT_BASIC_LIMIT_INFORMATION 194 | { 195 | public Int64 PerProcessUserTimeLimit; 196 | public Int64 PerJobUserTimeLimit; 197 | public JOBOBJECTLIMIT LimitFlags; 198 | public UIntPtr MinimumWorkingSetSize; 199 | public UIntPtr MaximumWorkingSetSize; 200 | public UInt32 ActiveProcessLimit; 201 | public Int64 Affinity; 202 | public UInt32 PriorityClass; 203 | public UInt32 SchedulingClass; 204 | } 205 | 206 | [Flags] 207 | public enum JOBOBJECTLIMIT : uint 208 | { 209 | JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000 210 | } 211 | 212 | [StructLayout(LayoutKind.Sequential)] 213 | public struct IO_COUNTERS 214 | { 215 | public UInt64 ReadOperationCount; 216 | public UInt64 WriteOperationCount; 217 | public UInt64 OtherOperationCount; 218 | public UInt64 ReadTransferCount; 219 | public UInt64 WriteTransferCount; 220 | public UInt64 OtherTransferCount; 221 | } 222 | 223 | [StructLayout(LayoutKind.Sequential)] 224 | public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION 225 | { 226 | public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; 227 | public IO_COUNTERS IoInfo; 228 | public UIntPtr ProcessMemoryLimit; 229 | public UIntPtr JobMemoryLimit; 230 | public UIntPtr PeakProcessMemoryUsed; 231 | public UIntPtr PeakJobMemoryUsed; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/Core/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Archive Cache Manager Core")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Archive Cache Manager")] 13 | [assembly: AssemblyCopyright("Copyright © 2023")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("035823e0-c80b-49bf-9fad-9c65ea45e3a9")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("2.16.0.0")] 36 | [assembly: AssemblyFileVersion("2.16.0.0")] 37 | -------------------------------------------------------------------------------- /src/Core/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ArchiveCacheManager 8 | { 9 | static class Utils 10 | { 11 | /// 12 | /// Convert the provided object into an array with the object as its single item. 13 | /// If the object is null, the resulting array will be null (NOT an array with a single null element) 14 | /// 15 | /// The type of the object that will be provided and contained in the returned array. 16 | /// The item which will be contained in the return array as its single item. 17 | /// An array with as its single item. 18 | public static T[] ToSingleArray(this T singleElement) => singleElement != null ? new[] { singleElement } : null; 19 | 20 | public static string[] SplitExtensions(string extensions) 21 | { 22 | return extensions.ToLower().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(ex => ex.Trim()).ToArray(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Plugin/ArchiveListWindow.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace ArchiveCacheManager 3 | { 4 | partial class ArchiveListWindow 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); 33 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); 34 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ArchiveListWindow)); 35 | this.cancelButton = new System.Windows.Forms.Button(); 36 | this.okButton = new System.Windows.Forms.Button(); 37 | this.archiveNameLabel = new System.Windows.Forms.Label(); 38 | this.emulatorComboBox = new System.Windows.Forms.ComboBox(); 39 | this.emulatorComboBoxLabel = new System.Windows.Forms.Label(); 40 | this.fileListGridView = new System.Windows.Forms.DataGridView(); 41 | this.File = new System.Windows.Forms.DataGridViewTextBoxColumn(); 42 | ((System.ComponentModel.ISupportInitialize)(this.fileListGridView)).BeginInit(); 43 | this.SuspendLayout(); 44 | // 45 | // cancelButton 46 | // 47 | this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 48 | this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 49 | this.cancelButton.Image = global::ArchiveCacheManager.Resources.cross_script; 50 | this.cancelButton.Location = new System.Drawing.Point(93, 413); 51 | this.cancelButton.Name = "cancelButton"; 52 | this.cancelButton.Size = new System.Drawing.Size(75, 28); 53 | this.cancelButton.TabIndex = 2; 54 | this.cancelButton.Text = "Cancel"; 55 | this.cancelButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 56 | this.cancelButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; 57 | this.cancelButton.UseVisualStyleBackColor = true; 58 | // 59 | // okButton 60 | // 61 | this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 62 | this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; 63 | this.okButton.Image = global::ArchiveCacheManager.Resources.tick; 64 | this.okButton.Location = new System.Drawing.Point(12, 413); 65 | this.okButton.Name = "okButton"; 66 | this.okButton.Size = new System.Drawing.Size(75, 28); 67 | this.okButton.TabIndex = 1; 68 | this.okButton.Text = "Play!"; 69 | this.okButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 70 | this.okButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; 71 | this.okButton.UseVisualStyleBackColor = true; 72 | this.okButton.Click += new System.EventHandler(this.okButton_Click); 73 | // 74 | // archiveNameLabel 75 | // 76 | this.archiveNameLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 77 | | System.Windows.Forms.AnchorStyles.Right))); 78 | this.archiveNameLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 79 | this.archiveNameLabel.Location = new System.Drawing.Point(9, 9); 80 | this.archiveNameLabel.Name = "archiveNameLabel"; 81 | this.archiveNameLabel.Size = new System.Drawing.Size(522, 21); 82 | this.archiveNameLabel.TabIndex = 4; 83 | this.archiveNameLabel.Text = "Game.zip"; 84 | this.archiveNameLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; 85 | // 86 | // emulatorComboBox 87 | // 88 | this.emulatorComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 89 | this.emulatorComboBox.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource; 90 | this.emulatorComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 91 | this.emulatorComboBox.FormattingEnabled = true; 92 | this.emulatorComboBox.Location = new System.Drawing.Point(304, 418); 93 | this.emulatorComboBox.Name = "emulatorComboBox"; 94 | this.emulatorComboBox.Size = new System.Drawing.Size(228, 21); 95 | this.emulatorComboBox.TabIndex = 5; 96 | // 97 | // emulatorComboBoxLabel 98 | // 99 | this.emulatorComboBoxLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 100 | this.emulatorComboBoxLabel.AutoSize = true; 101 | this.emulatorComboBoxLabel.Location = new System.Drawing.Point(243, 421); 102 | this.emulatorComboBoxLabel.Name = "emulatorComboBoxLabel"; 103 | this.emulatorComboBoxLabel.Size = new System.Drawing.Size(51, 13); 104 | this.emulatorComboBoxLabel.TabIndex = 6; 105 | this.emulatorComboBoxLabel.Text = "Emulator:"; 106 | // 107 | // fileListGridView 108 | // 109 | this.fileListGridView.AllowUserToAddRows = false; 110 | this.fileListGridView.AllowUserToDeleteRows = false; 111 | this.fileListGridView.AllowUserToResizeRows = false; 112 | this.fileListGridView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 113 | | System.Windows.Forms.AnchorStyles.Left) 114 | | System.Windows.Forms.AnchorStyles.Right))); 115 | this.fileListGridView.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; 116 | this.fileListGridView.BackgroundColor = System.Drawing.SystemColors.ControlLightLight; 117 | this.fileListGridView.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; 118 | dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; 119 | dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Control; 120 | dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 121 | dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.WindowText; 122 | dataGridViewCellStyle1.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3); 123 | dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight; 124 | dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText; 125 | dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.False; 126 | this.fileListGridView.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1; 127 | this.fileListGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 128 | this.fileListGridView.ColumnHeadersVisible = false; 129 | this.fileListGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { 130 | this.File}); 131 | this.fileListGridView.EditMode = System.Windows.Forms.DataGridViewEditMode.EditOnEnter; 132 | this.fileListGridView.Location = new System.Drawing.Point(12, 33); 133 | this.fileListGridView.MultiSelect = false; 134 | this.fileListGridView.Name = "fileListGridView"; 135 | this.fileListGridView.RowHeadersVisible = false; 136 | this.fileListGridView.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; 137 | this.fileListGridView.Size = new System.Drawing.Size(520, 374); 138 | this.fileListGridView.StandardTab = true; 139 | this.fileListGridView.TabIndex = 9; 140 | // 141 | // File 142 | // 143 | dataGridViewCellStyle2.Padding = new System.Windows.Forms.Padding(3, 0, 0, 0); 144 | this.File.DefaultCellStyle = dataGridViewCellStyle2; 145 | this.File.HeaderText = "File"; 146 | this.File.Name = "File"; 147 | this.File.ReadOnly = true; 148 | // 149 | // ArchiveListWindow 150 | // 151 | this.AcceptButton = this.okButton; 152 | this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); 153 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; 154 | this.CancelButton = this.cancelButton; 155 | this.ClientSize = new System.Drawing.Size(544, 453); 156 | this.Controls.Add(this.fileListGridView); 157 | this.Controls.Add(this.emulatorComboBox); 158 | this.Controls.Add(this.emulatorComboBoxLabel); 159 | this.Controls.Add(this.archiveNameLabel); 160 | this.Controls.Add(this.cancelButton); 161 | this.Controls.Add(this.okButton); 162 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 163 | this.MinimizeBox = false; 164 | this.MinimumSize = new System.Drawing.Size(500, 200); 165 | this.Name = "ArchiveListWindow"; 166 | this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show; 167 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 168 | this.Text = "Select File"; 169 | ((System.ComponentModel.ISupportInitialize)(this.fileListGridView)).EndInit(); 170 | this.ResumeLayout(false); 171 | this.PerformLayout(); 172 | 173 | } 174 | 175 | #endregion 176 | private System.Windows.Forms.Button cancelButton; 177 | private System.Windows.Forms.Button okButton; 178 | private System.Windows.Forms.Label archiveNameLabel; 179 | private System.Windows.Forms.ComboBox emulatorComboBox; 180 | private System.Windows.Forms.Label emulatorComboBoxLabel; 181 | private System.Windows.Forms.DataGridView fileListGridView; 182 | private System.Windows.Forms.DataGridViewTextBoxColumn File; 183 | } 184 | } -------------------------------------------------------------------------------- /src/Plugin/ArchiveListWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace ArchiveCacheManager 12 | { 13 | public partial class ArchiveListWindow : Form 14 | { 15 | public string SelectedFile; 16 | public int EmulatorIndex; 17 | 18 | public ArchiveListWindow(string archiveName, string[] fileList, string[] emulatorList, string selection = "") 19 | { 20 | InitializeComponent(); 21 | 22 | archiveNameLabel.Text = archiveName; 23 | 24 | emulatorComboBox.Items.Clear(); 25 | if (emulatorList.Count() > 0) 26 | { 27 | emulatorComboBox.Items.AddRange(emulatorList); 28 | emulatorComboBox.SelectedIndex = 0; 29 | EmulatorIndex = emulatorComboBox.SelectedIndex; 30 | emulatorComboBox.Enabled = true; 31 | } 32 | else 33 | { 34 | emulatorComboBox.Enabled = false; 35 | } 36 | 37 | fileListGridView.Rows.Clear(); 38 | for (int i = 0; i < fileList.Length; i++) 39 | { 40 | fileListGridView.Rows.Add(new object[] { fileList[i] }); 41 | if (string.Equals(fileList[i], selection, StringComparison.InvariantCultureIgnoreCase)) 42 | { 43 | fileListGridView.Rows[i].Selected = true; 44 | fileListGridView.CurrentCell = fileListGridView.Rows[i].Cells["File"]; 45 | } 46 | } 47 | 48 | // Check that setting the selected item above actually worked. If not, set it to the first item. 49 | if (fileListGridView.SelectedRows.Count == 0) 50 | { 51 | fileListGridView.Rows[0].Selected = true; 52 | fileListGridView.CurrentCell = fileListGridView.Rows[0].Cells["File"]; 53 | } 54 | SelectedFile = string.Empty; 55 | 56 | UserInterface.ApplyTheme(this); 57 | //fileListGridView.Columns["File"].DefaultCellStyle.Padding = new Padding(34, 0, 0, 0); 58 | //fileListGridView.CellPainting += fileListGridView_CellPainting; 59 | } 60 | 61 | private void okButton_Click(object sender, EventArgs e) 62 | { 63 | SelectedFile = fileListGridView.SelectedRows[0].Cells["File"].Value.ToString(); 64 | EmulatorIndex = emulatorComboBox.SelectedIndex; 65 | } 66 | 67 | private void fileListBox_MouseDoubleClick(object sender, MouseEventArgs e) 68 | { 69 | okButton.PerformClick(); 70 | } 71 | 72 | /* 73 | private void fileListGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) 74 | { 75 | int priorityIndex = 0; 76 | int selectedIndex = 0; 77 | 78 | if (e.RowIndex < 0) 79 | return; 80 | 81 | if (e.ColumnIndex == fileListGridView.Columns["File"].Index) 82 | { 83 | if (e.RowIndex == priorityIndex) 84 | { 85 | UserInterface.DrawCellIcon(e, Resources.star_blue); 86 | } 87 | 88 | if (e.RowIndex == selectedIndex) 89 | { 90 | UserInterface.DrawCellIcon(e, Resources.star, 15, false); 91 | } 92 | } 93 | } 94 | */ 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Plugin/ArchiveListWindowBigBox.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace ArchiveCacheManager 3 | { 4 | partial class ArchiveListWindowBigBox 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ArchiveListWindowBigBox)); 33 | this.fileListBox = new System.Windows.Forms.ListBox(); 34 | this.archiveNameLabel = new System.Windows.Forms.Label(); 35 | this.okButton = new System.Windows.Forms.Button(); 36 | this.cancelButton = new System.Windows.Forms.Button(); 37 | this.SuspendLayout(); 38 | // 39 | // fileListBox 40 | // 41 | this.fileListBox.BackColor = System.Drawing.Color.Black; 42 | this.fileListBox.BorderStyle = System.Windows.Forms.BorderStyle.None; 43 | this.fileListBox.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable; 44 | this.fileListBox.Font = new System.Drawing.Font("Calibri", 24F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 45 | this.fileListBox.ForeColor = System.Drawing.Color.White; 46 | this.fileListBox.FormattingEnabled = true; 47 | this.fileListBox.IntegralHeight = false; 48 | this.fileListBox.ItemHeight = 44; 49 | this.fileListBox.Location = new System.Drawing.Point(3, 65); 50 | this.fileListBox.Margin = new System.Windows.Forms.Padding(0); 51 | this.fileListBox.Name = "fileListBox"; 52 | this.fileListBox.Size = new System.Drawing.Size(794, 468); 53 | this.fileListBox.TabIndex = 0; 54 | this.fileListBox.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.FileListBox_DrawItem); 55 | this.fileListBox.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.fileListBox_MouseDoubleClick); 56 | // 57 | // archiveNameLabel 58 | // 59 | this.archiveNameLabel.BackColor = System.Drawing.Color.Black; 60 | this.archiveNameLabel.Font = new System.Drawing.Font("Calibri", 20.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 61 | this.archiveNameLabel.ForeColor = System.Drawing.Color.White; 62 | this.archiveNameLabel.Location = new System.Drawing.Point(3, 3); 63 | this.archiveNameLabel.Margin = new System.Windows.Forms.Padding(0); 64 | this.archiveNameLabel.Name = "archiveNameLabel"; 65 | this.archiveNameLabel.Size = new System.Drawing.Size(794, 62); 66 | this.archiveNameLabel.TabIndex = 4; 67 | this.archiveNameLabel.Text = "Game.zip"; 68 | this.archiveNameLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 69 | // 70 | // okButton 71 | // 72 | this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 73 | this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; 74 | this.okButton.Image = global::ArchiveCacheManager.Resources.tick; 75 | this.okButton.Location = new System.Drawing.Point(39, 98); 76 | this.okButton.Name = "okButton"; 77 | this.okButton.Size = new System.Drawing.Size(75, 23); 78 | this.okButton.TabIndex = 5; 79 | this.okButton.Text = "Play!"; 80 | this.okButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 81 | this.okButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; 82 | this.okButton.UseVisualStyleBackColor = true; 83 | this.okButton.Click += new System.EventHandler(this.okButton_Click); 84 | // 85 | // cancelButton 86 | // 87 | this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 88 | this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 89 | this.cancelButton.Image = global::ArchiveCacheManager.Resources.cross_script; 90 | this.cancelButton.Location = new System.Drawing.Point(120, 98); 91 | this.cancelButton.Name = "cancelButton"; 92 | this.cancelButton.Size = new System.Drawing.Size(75, 23); 93 | this.cancelButton.TabIndex = 6; 94 | this.cancelButton.Text = "Cancel"; 95 | this.cancelButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 96 | this.cancelButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; 97 | this.cancelButton.UseVisualStyleBackColor = true; 98 | // 99 | // ArchiveListWindowBigBox 100 | // 101 | this.AcceptButton = this.okButton; 102 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; 103 | this.BackColor = System.Drawing.Color.DarkGray; 104 | this.CancelButton = this.cancelButton; 105 | this.ClientSize = new System.Drawing.Size(800, 536); 106 | this.Controls.Add(this.fileListBox); 107 | this.Controls.Add(this.cancelButton); 108 | this.Controls.Add(this.okButton); 109 | this.Controls.Add(this.archiveNameLabel); 110 | this.DoubleBuffered = true; 111 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; 112 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 113 | this.MaximizeBox = false; 114 | this.MinimizeBox = false; 115 | this.Name = "ArchiveListWindowBigBox"; 116 | this.Opacity = 0.92D; 117 | this.ShowInTaskbar = false; 118 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 119 | this.Text = "Select File"; 120 | this.ResumeLayout(false); 121 | 122 | } 123 | 124 | #endregion 125 | 126 | private System.Windows.Forms.ListBox fileListBox; 127 | private System.Windows.Forms.Label archiveNameLabel; 128 | private System.Windows.Forms.Button cancelButton; 129 | private System.Windows.Forms.Button okButton; 130 | } 131 | } -------------------------------------------------------------------------------- /src/Plugin/ArchiveListWindowBigBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using Unbroken.LaunchBox.Plugins; 11 | using Unbroken.LaunchBox.Plugins.Data; 12 | 13 | namespace ArchiveCacheManager 14 | { 15 | public partial class ArchiveListWindowBigBox : Form //, IBigBoxThemeElementPlugin 16 | { 17 | public string SelectedFile; 18 | 19 | public ArchiveListWindowBigBox(string archiveName, string[] fileList, string selection = "") 20 | { 21 | InitializeComponent(); 22 | 23 | UserInterface.ScaleControlFont(fileListBox, 96.0f / fileListBox.DeviceDpi); 24 | UserInterface.ScaleControlFont(archiveNameLabel, 96.0f / archiveNameLabel.DeviceDpi); 25 | 26 | if (LaunchBoxSettings.HideMouseCursor) 27 | { 28 | Cursor.Hide(); 29 | } 30 | 31 | archiveNameLabel.Text = archiveName; 32 | 33 | fileListBox.Items.Clear(); 34 | fileListBox.Items.AddRange(fileList); 35 | if (selection != string.Empty) 36 | { 37 | fileListBox.SelectedItem = selection; 38 | } 39 | // Check that setting the selected item above actually worked. If not, set it to the first item. 40 | if (fileListBox.SelectedItems.Count == 0) 41 | { 42 | fileListBox.SelectedIndex = 0; 43 | } 44 | SelectedFile = string.Empty; 45 | } 46 | 47 | private void FileListBox_DrawItem(object sender, DrawItemEventArgs e) 48 | { 49 | e.DrawBackground(); 50 | Graphics g = e.Graphics; 51 | Brush brush = ((e.State & DrawItemState.Selected) == DrawItemState.Selected) ? 52 | new SolidBrush(Color.FromArgb(0x5F, 0x33, 0x99, 0xFF)) : new SolidBrush(e.BackColor); 53 | g.FillRectangle(brush, e.Bounds); 54 | e.Graphics.DrawString(" " + fileListBox.Items[e.Index].ToString(), e.Font, 55 | new SolidBrush(e.ForeColor), e.Bounds, StringFormat.GenericDefault); 56 | //e.DrawFocusRectangle(); 57 | } 58 | 59 | private void okButton_Click(object sender, EventArgs e) 60 | { 61 | SelectedFile = fileListBox.SelectedItem.ToString(); 62 | } 63 | 64 | private void fileListBox_MouseDoubleClick(object sender, MouseEventArgs e) 65 | { 66 | okButton.PerformClick(); 67 | } 68 | 69 | 70 | 71 | 72 | #if false 73 | public void OnSelectionChanged(FilterType filterType, string filterValue, IPlatform platform, IPlatformCategory category, IPlaylist playlist, IGame game) 74 | { 75 | 76 | } 77 | 78 | public bool OnEnter() 79 | { 80 | SelectedFile = fileListBox.SelectedItem.ToString(); 81 | 82 | this.DialogResult = DialogResult.OK; 83 | this.Close(); 84 | 85 | return true; 86 | } 87 | 88 | public bool OnEscape() 89 | { 90 | this.DialogResult = DialogResult.Cancel; 91 | this.Close(); 92 | 93 | return true; 94 | } 95 | 96 | public bool OnUp(bool held) 97 | { 98 | if (fileListBox.SelectedIndex > 0) 99 | { 100 | fileListBox.SelectedIndex--; 101 | } 102 | 103 | return true; 104 | } 105 | 106 | public bool OnDown(bool held) 107 | { 108 | if (fileListBox.SelectedIndex < fileListBox.Items.Count - 1) 109 | { 110 | fileListBox.SelectedIndex++; 111 | } 112 | 113 | return true; 114 | } 115 | 116 | public bool OnLeft(bool held) 117 | { 118 | return true; 119 | } 120 | 121 | public bool OnRight(bool held) 122 | { 123 | return true; 124 | } 125 | 126 | public bool OnPageDown() 127 | { 128 | return true; 129 | } 130 | 131 | public bool OnPageUp() 132 | { 133 | return true; 134 | } 135 | #endif 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Plugin/BatchCacheMenuItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | using System.Windows.Interop; 9 | using Unbroken.LaunchBox.Plugins; 10 | using Unbroken.LaunchBox.Plugins.Data; 11 | 12 | namespace ArchiveCacheManager 13 | { 14 | class BatchCacheMenuItem : IGameMenuItemPlugin 15 | { 16 | public bool SupportsMultipleGames => true; 17 | public string Caption => "Batch Cache Games..."; 18 | public Image IconImage => Resources.icon16x16; 19 | public bool ShowInLaunchBox => true; 20 | public bool ShowInBigBox => false; 21 | public bool GetIsValidForGame(IGame selectedGame) => true; 22 | public bool GetIsValidForGames(IGame[] selectedGames) => true; 23 | public void OnSelected(IGame selectedGame) => OnSelected(new IGame[] { selectedGame }); 24 | 25 | public void OnSelected(IGame[] selectedGames) 26 | { 27 | BatchCacheWindow window = new BatchCacheWindow(selectedGames); 28 | NativeWindow parent = new NativeWindow(); 29 | 30 | // Glue between the main app window (WPF) and this window (WinForms) 31 | parent.AssignHandle(new WindowInteropHelper(System.Windows.Application.Current.MainWindow).Handle); 32 | window.ShowDialog(parent); 33 | 34 | if (window.RefreshLaunchBox && !PluginHelper.StateManager.IsBigBox) 35 | { 36 | PluginHelper.LaunchBoxMainViewModel.RefreshData(); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Plugin/CacheConfigWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | 12 | namespace ArchiveCacheManager 13 | { 14 | public partial class CacheConfigWindow : Form 15 | { 16 | public CacheConfigWindow() 17 | { 18 | InitializeComponent(); 19 | 20 | cachePath.Text = Config.CachePath; 21 | cacheSize.Value = Config.CacheSize; 22 | minArchiveSize.Value = Config.MinArchiveSize; 23 | 24 | updateEnabledState(); 25 | 26 | UserInterface.ApplyTheme(this); 27 | } 28 | 29 | private void updateEnabledState() 30 | { 31 | okButton.Enabled = (cachePath.Text != string.Empty); 32 | } 33 | 34 | private void okButton_Click(object sender, EventArgs e) 35 | { 36 | if (!PathUtils.IsPathSafe(cachePath.Text)) 37 | { 38 | UserInterface.ErrorDialog($"ERROR! The cache path can not be set to {Path.GetFullPath(cachePath.Text)}.\r\nPlease change the cache path.", this); 39 | return; 40 | } 41 | 42 | try 43 | { 44 | // Don't warn if path is unchanged, as it probably already has files in it. 45 | if (!PathUtils.ComparePaths(cachePath.Text, Config.CachePath) && 46 | (Directory.EnumerateFiles(cachePath.Text, "*", SearchOption.AllDirectories).Any() || Directory.EnumerateDirectories(cachePath.Text).Any())) 47 | { 48 | DialogResult result = FlexibleMessageBox.Show(this, "WARNING! The selected cache path already contains files. These files WILL be deleted when the cache is cleaned. Continue?", "Warning!", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); 49 | 50 | if (result == DialogResult.No) 51 | { 52 | return; 53 | } 54 | } 55 | } 56 | catch (DirectoryNotFoundException) 57 | { 58 | } 59 | 60 | Config.CachePath = cachePath.Text; 61 | Config.CacheSize = Convert.ToInt64(cacheSize.Value); 62 | Config.MinArchiveSize = Convert.ToInt64(minArchiveSize.Value); 63 | 64 | this.DialogResult = DialogResult.OK; 65 | this.Close(); 66 | } 67 | 68 | private void cachePath_TextChanged(object sender, EventArgs e) 69 | { 70 | updateEnabledState(); 71 | } 72 | 73 | private void cachePathBrowseButton_Click(object sender, EventArgs e) 74 | { 75 | FolderBrowserDialog dialog = new FolderBrowserDialog(); 76 | string browsePath = PathUtils.CachePath(cachePath.Text); 77 | 78 | dialog.SelectedPath = Directory.Exists(browsePath) ? browsePath : PathUtils.GetLaunchBoxRootPath(); 79 | dialog.ShowNewFolderButton = true; 80 | 81 | if (dialog.ShowDialog() == DialogResult.OK) 82 | { 83 | cachePath.Text = PathUtils.GetRelativePath(PathUtils.GetLaunchBoxRootPath(), dialog.SelectedPath); 84 | } 85 | 86 | updateEnabledState(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Plugin/EmulatorPlatformSelectionWindow.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace ArchiveCacheManager 3 | { 4 | partial class EmulatorPlatformSelectionWindow 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | this.components = new System.ComponentModel.Container(); 33 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(EmulatorPlatformSelectionWindow)); 34 | this.platformComboBox = new System.Windows.Forms.ComboBox(); 35 | this.emulatorComboBox = new System.Windows.Forms.ComboBox(); 36 | this.emulatorComboBoxLabel = new System.Windows.Forms.Label(); 37 | this.platformComboBoxLabel = new System.Windows.Forms.Label(); 38 | this.cancelButton = new System.Windows.Forms.Button(); 39 | this.okButton = new System.Windows.Forms.Button(); 40 | this.toolTip = new System.Windows.Forms.ToolTip(this.components); 41 | this.SuspendLayout(); 42 | // 43 | // platformComboBox 44 | // 45 | this.platformComboBox.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource; 46 | this.platformComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 47 | this.platformComboBox.FormattingEnabled = true; 48 | this.platformComboBox.Location = new System.Drawing.Point(12, 77); 49 | this.platformComboBox.Name = "platformComboBox"; 50 | this.platformComboBox.Size = new System.Drawing.Size(428, 21); 51 | this.platformComboBox.TabIndex = 3; 52 | this.toolTip.SetToolTip(this.platformComboBox, "Platform to apply extension priority to."); 53 | // 54 | // emulatorComboBox 55 | // 56 | this.emulatorComboBox.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource; 57 | this.emulatorComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 58 | this.emulatorComboBox.FormattingEnabled = true; 59 | this.emulatorComboBox.Location = new System.Drawing.Point(12, 28); 60 | this.emulatorComboBox.Name = "emulatorComboBox"; 61 | this.emulatorComboBox.Size = new System.Drawing.Size(428, 21); 62 | this.emulatorComboBox.TabIndex = 2; 63 | this.toolTip.SetToolTip(this.emulatorComboBox, "Emulator to apply extension priority to."); 64 | this.emulatorComboBox.SelectionChangeCommitted += new System.EventHandler(this.emulatorComboBox_SelectionChangeCommitted); 65 | // 66 | // emulatorComboBoxLabel 67 | // 68 | this.emulatorComboBoxLabel.AutoSize = true; 69 | this.emulatorComboBoxLabel.Location = new System.Drawing.Point(9, 12); 70 | this.emulatorComboBoxLabel.Name = "emulatorComboBoxLabel"; 71 | this.emulatorComboBoxLabel.Size = new System.Drawing.Size(51, 13); 72 | this.emulatorComboBoxLabel.TabIndex = 5; 73 | this.emulatorComboBoxLabel.Text = "Emulator:"; 74 | // 75 | // platformComboBoxLabel 76 | // 77 | this.platformComboBoxLabel.AutoSize = true; 78 | this.platformComboBoxLabel.Location = new System.Drawing.Point(9, 61); 79 | this.platformComboBoxLabel.Name = "platformComboBoxLabel"; 80 | this.platformComboBoxLabel.Size = new System.Drawing.Size(48, 13); 81 | this.platformComboBoxLabel.TabIndex = 6; 82 | this.platformComboBoxLabel.Text = "Platform:"; 83 | // 84 | // cancelButton 85 | // 86 | this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 87 | this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 88 | this.cancelButton.Image = global::ArchiveCacheManager.Resources.cross_script; 89 | this.cancelButton.Location = new System.Drawing.Point(98, 112); 90 | this.cancelButton.Name = "cancelButton"; 91 | this.cancelButton.Size = new System.Drawing.Size(80, 28); 92 | this.cancelButton.TabIndex = 1; 93 | this.cancelButton.Text = "Cancel"; 94 | this.cancelButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 95 | this.cancelButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; 96 | this.cancelButton.UseVisualStyleBackColor = true; 97 | // 98 | // okButton 99 | // 100 | this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 101 | this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; 102 | this.okButton.Image = global::ArchiveCacheManager.Resources.tick; 103 | this.okButton.Location = new System.Drawing.Point(12, 112); 104 | this.okButton.Name = "okButton"; 105 | this.okButton.Size = new System.Drawing.Size(80, 28); 106 | this.okButton.TabIndex = 0; 107 | this.okButton.Text = "OK"; 108 | this.okButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 109 | this.okButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; 110 | this.okButton.UseVisualStyleBackColor = true; 111 | this.okButton.Click += new System.EventHandler(this.okButton_Click); 112 | // 113 | // EmulatorPlatformSelectionWindow 114 | // 115 | this.AcceptButton = this.okButton; 116 | this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); 117 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; 118 | this.CancelButton = this.cancelButton; 119 | this.ClientSize = new System.Drawing.Size(452, 152); 120 | this.Controls.Add(this.cancelButton); 121 | this.Controls.Add(this.okButton); 122 | this.Controls.Add(this.platformComboBoxLabel); 123 | this.Controls.Add(this.emulatorComboBoxLabel); 124 | this.Controls.Add(this.emulatorComboBox); 125 | this.Controls.Add(this.platformComboBox); 126 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 127 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 128 | this.MaximizeBox = false; 129 | this.MinimizeBox = false; 130 | this.Name = "EmulatorPlatformSelectionWindow"; 131 | this.ShowInTaskbar = false; 132 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 133 | this.Text = "Emulator \\ Platform Selection"; 134 | this.ResumeLayout(false); 135 | this.PerformLayout(); 136 | 137 | } 138 | 139 | #endregion 140 | 141 | private System.Windows.Forms.ComboBox platformComboBox; 142 | private System.Windows.Forms.ComboBox emulatorComboBox; 143 | private System.Windows.Forms.Label emulatorComboBoxLabel; 144 | private System.Windows.Forms.Label platformComboBoxLabel; 145 | private System.Windows.Forms.Button cancelButton; 146 | private System.Windows.Forms.Button okButton; 147 | private System.Windows.Forms.ToolTip toolTip; 148 | } 149 | } -------------------------------------------------------------------------------- /src/Plugin/EmulatorPlatformSelectionWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using Unbroken.LaunchBox.Plugins; 11 | 12 | namespace ArchiveCacheManager 13 | { 14 | public partial class EmulatorPlatformSelectionWindow : Form 15 | { 16 | public string Emulator = ""; 17 | public string Platform = ""; 18 | 19 | public EmulatorPlatformSelectionWindow(string emulator = null, string platform = null) 20 | { 21 | InitializeComponent(); 22 | 23 | emulatorComboBox.Items.Clear(); 24 | platformComboBox.Items.Clear(); 25 | 26 | if (emulator != null && platform != null) 27 | { 28 | emulatorComboBox.Items.Add(emulator); 29 | platformComboBox.Items.Add(platform); 30 | emulatorComboBox.SelectedIndex = 0; 31 | platformComboBox.SelectedIndex = 0; 32 | } 33 | else 34 | { 35 | emulatorComboBox.Items.AddRange(PluginHelper.DataManager.GetAllEmulators().Select(emu => emu.Title).ToArray()); 36 | if (emulatorComboBox.Items.Count > 0) 37 | { 38 | emulatorComboBox.SelectedIndex = 0; 39 | populatePlatforms(emulatorComboBox.SelectedItem.ToString()); 40 | } 41 | } 42 | 43 | updateEnabledState(); 44 | 45 | UserInterface.ApplyTheme(this); 46 | } 47 | 48 | private void populatePlatforms(string emulatorName) 49 | { 50 | platformComboBox.Items.Clear(); 51 | 52 | var platforms = PluginHelper.DataManager.GetAllEmulators().Single(emulator => emulator.Title == emulatorName).GetAllEmulatorPlatforms(); 53 | platformComboBox.Items.AddRange(platforms.Select(platform => platform.Platform).ToArray()); 54 | 55 | if (platformComboBox.Items.Count > 0) 56 | { 57 | platformComboBox.SelectedIndex = 0; 58 | } 59 | } 60 | 61 | private void updateEnabledState() 62 | { 63 | okButton.Enabled = (emulatorComboBox.Items.Count > 0 && 64 | emulatorComboBox.SelectedIndex != -1 && 65 | platformComboBox.Items.Count > 0 && 66 | platformComboBox.SelectedIndex != -1); 67 | 68 | emulatorComboBox.Enabled = (emulatorComboBox.Items.Count != 0); 69 | platformComboBox.Enabled = (platformComboBox.Items.Count != 0); 70 | } 71 | 72 | private void emulatorComboBox_SelectionChangeCommitted(object sender, EventArgs e) 73 | { 74 | populatePlatforms(emulatorComboBox.SelectedItem.ToString()); 75 | 76 | updateEnabledState(); 77 | } 78 | 79 | private void platformComboBox_SelectionChangeCommitted(object sender, EventArgs e) 80 | { 81 | updateEnabledState(); 82 | } 83 | 84 | private void okButton_Click(object sender, EventArgs e) 85 | { 86 | Emulator = emulatorComboBox.SelectedItem.ToString(); 87 | Platform = platformComboBox.SelectedItem.ToString(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Plugin/GameBadge.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.IO; 8 | using Unbroken.LaunchBox.Plugins.Data; 9 | 10 | namespace ArchiveCacheManager 11 | { 12 | class GameBadge : IGameBadge 13 | { 14 | private int mIndex = 99; 15 | 16 | public string Name => "Archive Cached"; 17 | public string UniqueId => "Archive Cached"; 18 | public Image DefaultIcon => Resources.badge; 19 | public int Index 20 | { 21 | get => mIndex; 22 | set => mIndex = value; 23 | } 24 | 25 | public bool GetAppliesToGame(IGame game) 26 | { 27 | return File.Exists(PathUtils.GetArchiveCacheGameInfoPath(PathUtils.ArchiveCachePath(PathUtils.GetAbsolutePath(game.ApplicationPath)))); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Plugin/GameMenuItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.IO; 8 | using Unbroken.LaunchBox.Plugins; 9 | using Unbroken.LaunchBox.Plugins.Data; 10 | using System.Windows.Forms; 11 | using System.Windows.Interop; 12 | 13 | namespace ArchiveCacheManager 14 | { 15 | class GameMenuItem : IGameMenuItemPlugin 16 | { 17 | public bool SupportsMultipleGames => false; 18 | public string Caption => PluginHelper.StateManager.IsBigBox ? "Select ROM In Archive" : "Select ROM In Archive..."; 19 | public Image IconImage => Resources.icon16x16_play; 20 | public bool ShowInLaunchBox => true; 21 | public bool ShowInBigBox => true; 22 | 23 | public bool GetIsValidForGame(IGame selectedGame) => Zip.SupportedType(selectedGame.ApplicationPath) 24 | && PluginUtils.GetEmulatorPlatformAutoExtract(selectedGame.EmulatorId, selectedGame.Platform); 25 | public bool GetIsValidForGames(IGame[] selectedGames) => false; 26 | 27 | public void OnSelected(IGame selectedGame) 28 | { 29 | string path = PathUtils.GetAbsolutePath(selectedGame.ApplicationPath); 30 | 31 | if (File.Exists(path)) 32 | { 33 | // HACK 34 | // In case where game is launched, but launch failed or aborted, 7z isn't cleaned up. If this code then runs, it will 35 | // call the archive cache manager version of 7z, which will not return the correct results (file priority will be applied, 36 | // and the first file listing removed. Restore 7z here, just in case it wasn't cleaned up properly previously. 37 | GameLaunching.Restore7z(); 38 | 39 | string[] fileList = new Zip().List(path); 40 | 41 | if (fileList.Count() == 0) 42 | { 43 | string errorMessage = string.Format("Error listing contents of {0}.\r\n\r\nCheck {1} for details.", Path.GetFileName(selectedGame.ApplicationPath), Path.GetFileName(PathUtils.GetLogFilePath())); 44 | 45 | if (PluginHelper.StateManager.IsBigBox) 46 | { 47 | MessageBoxBigBox messageBox = new MessageBoxBigBox(errorMessage); 48 | messageBox.ShowDialog(); 49 | } 50 | else 51 | { 52 | UserInterface.ErrorDialog(errorMessage); 53 | } 54 | 55 | return; 56 | } 57 | 58 | var emulatorsTuple = PluginUtils.GetPlatformEmulators(selectedGame.Platform, selectedGame.EmulatorId); 59 | 60 | Form window; 61 | if (PluginHelper.StateManager.IsBigBox) 62 | { 63 | window = new ArchiveListWindowBigBox(Path.GetFileName(selectedGame.ApplicationPath), fileList, GameIndex.GetSelectedFile(selectedGame.Id)); 64 | } 65 | else 66 | { 67 | window = new ArchiveListWindow(Path.GetFileName(selectedGame.ApplicationPath), fileList, emulatorsTuple.Select(emu => PluginUtils.GetEmulatorTitle(emu.Item1, emu.Item2)).ToArray(), GameIndex.GetSelectedFile(selectedGame.Id)); 68 | } 69 | //NativeWindow parent = new NativeWindow(); 70 | 71 | // Glue between the main app window (WPF) and this window (WinForms) 72 | //parent.AssignHandle(new WindowInteropHelper(System.Windows.Application.Current.MainWindow).Handle); 73 | window.ShowDialog();// parent); 74 | 75 | if (window.DialogResult == DialogResult.OK) 76 | { 77 | if (PluginHelper.StateManager.IsBigBox) 78 | { 79 | GameIndex.SetSelectedFile(selectedGame.Id, (window as ArchiveListWindowBigBox).SelectedFile); 80 | PluginHelper.BigBoxMainViewModel.PlayGame(selectedGame, null, PluginHelper.DataManager.GetEmulatorById(selectedGame.EmulatorId), null); 81 | } 82 | else 83 | { 84 | int emulatorIndex = (window as ArchiveListWindow).EmulatorIndex; 85 | // Use a specific command line for the IEmulatorPlatform. This covers the case where RetroArch has more than one core configured for the same platform. 86 | string commandLine = emulatorsTuple[emulatorIndex].Item2.CommandLine; 87 | // Use the game's custom command line if it exists and we're running with the game's emulator (index 0) 88 | if (emulatorIndex == 0 && !string.IsNullOrEmpty(selectedGame.CommandLine)) 89 | { 90 | commandLine = selectedGame.CommandLine; 91 | } 92 | GameIndex.SetSelectedFile(selectedGame.Id, (window as ArchiveListWindow).SelectedFile); 93 | PluginHelper.LaunchBoxMainViewModel.PlayGame(selectedGame, null, emulatorsTuple[emulatorIndex].Item1, commandLine); 94 | } 95 | } 96 | } 97 | else 98 | { 99 | UserInterface.ErrorDialog($"Couldn't find the archive file:\r\n\r\n{path}"); 100 | } 101 | } 102 | 103 | public void OnSelected(IGame[] selectedGames) 104 | { 105 | 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Plugin/LaunchBoxSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Xml; 7 | using System.Xml.Linq; 8 | using System.IO; 9 | using Unbroken.LaunchBox.Plugins; 10 | using System.Drawing; 11 | 12 | namespace ArchiveCacheManager 13 | { 14 | class LaunchBoxSettings 15 | { 16 | public static bool HideMouseCursor = false; 17 | 18 | // The values below are LB/BB defaults. That said, they don't directly map to 19 | // either DirectInput / XInput values, or standard key codes 20 | public static int ControllerUpButton = 0; 21 | public static int ControllerDownButton = 0; 22 | public static int ControllerSelectButton = 1; 23 | public static int ControllerBackButton = 2; 24 | public static int ControllerPlayButton = 3; 25 | public static int KeyboardUp = 24; 26 | public static int KeyboardDown = 26; 27 | public static int KeyboardSelect = 6; 28 | public static int KeyboardBack = 13; 29 | public static int KeyboardPlay = 59; 30 | 31 | // Default LaunchBox dialog dark theme 32 | public static Color DialogAccentColor = Color.FromArgb(65, 100, 148); 33 | public static Color DialogHighlightColor = Color.FromArgb(76, 79, 98); 34 | public static Color DialogBackgroundColor = Color.FromArgb(42, 43, 52); 35 | public static Color DialogBorderColor = Color.FromArgb(42, 43, 52); 36 | public static Color DialogForegroundColor = Color.FromArgb(240, 240, 240); 37 | public static double DialogContrastMultiplier = 1.0; 38 | 39 | static LaunchBoxSettings() 40 | { 41 | Load(); 42 | } 43 | 44 | public static void Load() 45 | { 46 | string dataPath = Path.Combine(PathUtils.GetLaunchBoxRootPath(), "Data"); 47 | 48 | if (PluginHelper.StateManager.IsBigBox) 49 | { 50 | LoadBigBox(Path.Combine(dataPath, "BigBoxSettings.xml")); 51 | } 52 | else 53 | { 54 | LoadLaunchBox(Path.Combine(dataPath, "Settings.xml")); 55 | } 56 | } 57 | 58 | private static void LoadLaunchBox(string settingsPath) 59 | { 60 | XmlDocument settings = new XmlDocument(); 61 | settings.Load(settingsPath); 62 | XmlNode node; 63 | string xpathPrefix = "/LaunchBox/Settings/"; 64 | 65 | #region Controller Mapping 66 | node = settings.SelectSingleNode(xpathPrefix + "ControllerSelectButton"); 67 | ControllerSelectButton = (node != null ? Convert.ToInt32(node.InnerText) : ControllerSelectButton); 68 | 69 | // Use the context menu button as the back / cancel button 70 | node = settings.SelectSingleNode(xpathPrefix + "ControllerContextMenuButton"); 71 | ControllerBackButton = (node != null ? Convert.ToInt32(node.InnerText) : ControllerBackButton); 72 | 73 | node = settings.SelectSingleNode(xpathPrefix + "ControllerPlayButton"); 74 | ControllerPlayButton = (node != null ? Convert.ToInt32(node.InnerText) : ControllerPlayButton); 75 | #endregion 76 | 77 | // Disable theme colour loading, until light theme colour contrast calcs are implemented 78 | /* 79 | #region Dialog Theme Colours 80 | node = settings.SelectSingleNode(xpathPrefix + nameof(DialogAccentColor)); 81 | DialogAccentColor = (node != null ? Color.FromArgb(Convert.ToInt32(node.InnerText)) : DialogAccentColor); 82 | 83 | node = settings.SelectSingleNode(xpathPrefix + nameof(DialogHighlightColor)); 84 | DialogHighlightColor = (node != null ? Color.FromArgb(Convert.ToInt32(node.InnerText)) : DialogHighlightColor); 85 | 86 | node = settings.SelectSingleNode(xpathPrefix + nameof(DialogBackgroundColor)); 87 | DialogBackgroundColor = (node != null ? Color.FromArgb(Convert.ToInt32(node.InnerText)) : DialogBackgroundColor); 88 | 89 | node = settings.SelectSingleNode(xpathPrefix + nameof(DialogBorderColor)); 90 | DialogBorderColor = (node != null ? Color.FromArgb(Convert.ToInt32(node.InnerText)) : DialogBorderColor); 91 | 92 | node = settings.SelectSingleNode(xpathPrefix + nameof(DialogForegroundColor)); 93 | DialogForegroundColor = (node != null ? Color.FromArgb(Convert.ToInt32(node.InnerText)) : DialogForegroundColor); 94 | 95 | node = settings.SelectSingleNode(xpathPrefix + nameof(DialogContrastMultiplier)); 96 | DialogContrastMultiplier = (node != null ? Convert.ToDouble(node.InnerText) : DialogContrastMultiplier); 97 | #endregion 98 | */ 99 | } 100 | 101 | private static void LoadBigBox(string settingsPath) 102 | { 103 | XmlDocument settings = new XmlDocument(); 104 | settings.Load(settingsPath); 105 | XmlNode node; 106 | string xpathPrefix = "/LaunchBox/BigBoxSettings/"; 107 | 108 | node = settings.SelectSingleNode(xpathPrefix + "HideMouseCursor"); 109 | HideMouseCursor = (node != null ? Convert.ToBoolean(node.InnerText) : HideMouseCursor); 110 | 111 | node = settings.SelectSingleNode(xpathPrefix + "ControllerSelectButton"); 112 | ControllerSelectButton = (node != null ? Convert.ToInt32(node.InnerText) : ControllerSelectButton); 113 | 114 | node = settings.SelectSingleNode(xpathPrefix + "ControllerBackButton"); 115 | ControllerBackButton = (node != null ? Convert.ToInt32(node.InnerText) : ControllerBackButton); 116 | 117 | node = settings.SelectSingleNode(xpathPrefix + "ControllerPlayButton"); 118 | ControllerPlayButton = (node != null ? Convert.ToInt32(node.InnerText) : ControllerPlayButton); 119 | 120 | node = settings.SelectSingleNode(xpathPrefix + "KeyboardUp"); 121 | KeyboardUp = (node != null ? Convert.ToInt32(node.InnerText) : KeyboardUp); 122 | 123 | node = settings.SelectSingleNode(xpathPrefix + "KeyboardDown"); 124 | KeyboardDown = (node != null ? Convert.ToInt32(node.InnerText) : KeyboardDown); 125 | 126 | node = settings.SelectSingleNode(xpathPrefix + "KeyboardSelect"); 127 | KeyboardSelect = (node != null ? Convert.ToInt32(node.InnerText) : KeyboardSelect); 128 | 129 | node = settings.SelectSingleNode(xpathPrefix + "KeyboardBack"); 130 | KeyboardBack = (node != null ? Convert.ToInt32(node.InnerText) : KeyboardBack); 131 | 132 | node = settings.SelectSingleNode(xpathPrefix + "KeyboardPlay"); 133 | KeyboardPlay = (node != null ? Convert.ToInt32(node.InnerText) : KeyboardPlay); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/Plugin/MessageBoxBigBox.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace ArchiveCacheManager 3 | { 4 | partial class MessageBoxBigBox 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | this.message = new System.Windows.Forms.Label(); 33 | this.cancelButton = new System.Windows.Forms.Button(); 34 | this.okButton = new System.Windows.Forms.Button(); 35 | this.SuspendLayout(); 36 | // 37 | // message 38 | // 39 | this.message.BackColor = System.Drawing.Color.Black; 40 | this.message.Font = new System.Drawing.Font("Calibri", 24F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 41 | this.message.ForeColor = System.Drawing.Color.White; 42 | this.message.Location = new System.Drawing.Point(3, 3); 43 | this.message.Margin = new System.Windows.Forms.Padding(0); 44 | this.message.Name = "message"; 45 | this.message.Size = new System.Drawing.Size(794, 294); 46 | this.message.TabIndex = 5; 47 | this.message.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 48 | // 49 | // cancelButton 50 | // 51 | this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 52 | this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 53 | this.cancelButton.Image = global::ArchiveCacheManager.Resources.cross_script; 54 | this.cancelButton.Location = new System.Drawing.Point(403, 89); 55 | this.cancelButton.Name = "cancelButton"; 56 | this.cancelButton.Size = new System.Drawing.Size(75, 23); 57 | this.cancelButton.TabIndex = 8; 58 | this.cancelButton.Text = "Cancel"; 59 | this.cancelButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 60 | this.cancelButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; 61 | this.cancelButton.UseVisualStyleBackColor = true; 62 | // 63 | // okButton 64 | // 65 | this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 66 | this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; 67 | this.okButton.Image = global::ArchiveCacheManager.Resources.tick; 68 | this.okButton.Location = new System.Drawing.Point(322, 89); 69 | this.okButton.Name = "okButton"; 70 | this.okButton.Size = new System.Drawing.Size(75, 23); 71 | this.okButton.TabIndex = 7; 72 | this.okButton.Text = "Play!"; 73 | this.okButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 74 | this.okButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; 75 | this.okButton.UseVisualStyleBackColor = true; 76 | // 77 | // MessageBoxBigBox 78 | // 79 | this.AcceptButton = this.okButton; 80 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; 81 | this.BackColor = System.Drawing.Color.DarkGray; 82 | this.CancelButton = this.cancelButton; 83 | this.ClientSize = new System.Drawing.Size(800, 300); 84 | this.Controls.Add(this.message); 85 | this.Controls.Add(this.cancelButton); 86 | this.Controls.Add(this.okButton); 87 | this.DoubleBuffered = true; 88 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; 89 | this.Name = "MessageBoxBigBox"; 90 | this.Opacity = 0.95D; 91 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 92 | this.Text = "Message Box"; 93 | this.ResumeLayout(false); 94 | 95 | } 96 | 97 | #endregion 98 | 99 | private System.Windows.Forms.Label message; 100 | private System.Windows.Forms.Button cancelButton; 101 | private System.Windows.Forms.Button okButton; 102 | } 103 | } -------------------------------------------------------------------------------- /src/Plugin/MessageBoxBigBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | 12 | namespace ArchiveCacheManager 13 | { 14 | public partial class MessageBoxBigBox : Form 15 | { 16 | public MessageBoxBigBox(string text) 17 | { 18 | InitializeComponent(); 19 | 20 | UserInterface.ScaleControlFont(message, 96.0f / message.DeviceDpi); 21 | 22 | message.Text = text; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Plugin/MessageBoxBigBox.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /src/Plugin/Plugin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {205F6C26-C727-4171-A760-3599AA2D00D2} 8 | Library 9 | Properties 10 | ArchiveCacheManager 11 | ArchiveCacheManager.Plugin 12 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 13 | v4.7.2 14 | 512 15 | true 16 | 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | true 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | true 36 | 37 | 38 | OnOutputUpdated 39 | 40 | 41 | 42 | ..\packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll 43 | 44 | 45 | ..\packages\Octokit.0.50.0\lib\net46\Octokit.dll 46 | True 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ..\packages\System.Drawing.Common.4.7.2\lib\net461\System.Drawing.Common.dll 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | False 68 | ..\..\thirdparty\Unbroken.LaunchBox.Plugins\12.8\Unbroken.LaunchBox.Plugins.dll 69 | 70 | 71 | 72 | 73 | 74 | 75 | Form 76 | 77 | 78 | ArchiveListWindowBigBox.cs 79 | 80 | 81 | Form 82 | 83 | 84 | ArchiveListWindow.cs 85 | 86 | 87 | Form 88 | 89 | 90 | CacheConfigWindow.cs 91 | 92 | 93 | Form 94 | 95 | 96 | NewConfigWindow.cs 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | Form 105 | 106 | 107 | MessageBoxBigBox.cs 108 | 109 | 110 | 111 | Form 112 | 113 | 114 | EmulatorPlatformSelectionWindow.cs 115 | 116 | 117 | 118 | Form 119 | 120 | 121 | BatchCacheWindow.cs 122 | 123 | 124 | True 125 | True 126 | Resources.resx 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | ArchiveListWindowBigBox.cs 143 | 144 | 145 | ArchiveListWindow.cs 146 | 147 | 148 | CacheConfigWindow.cs 149 | 150 | 151 | NewConfigWindow.cs 152 | Designer 153 | 154 | 155 | MessageBoxBigBox.cs 156 | 157 | 158 | EmulatorPlatformSelectionWindow.cs 159 | 160 | 161 | BatchCacheWindow.cs 162 | 163 | 164 | ResXFileCodeGenerator 165 | Designer 166 | Resources.Designer.cs 167 | 168 | 169 | 170 | 171 | {035823e0-c80b-49bf-9fad-9c65ea45e3a9} 172 | Core 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | copy /Y $(TargetPath) c:\LaunchBox\Plugins\ArchiveCacheManager 271 | mkdir $(SolutionDir)..\release\ArchiveCacheManager 272 | copy /Y $(TargetPath) $(SolutionDir)..\release\ArchiveCacheManager 273 | 274 | -------------------------------------------------------------------------------- /src/Plugin/PluginUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Unbroken.LaunchBox.Plugins; 9 | using Unbroken.LaunchBox.Plugins.Data; 10 | 11 | namespace ArchiveCacheManager 12 | { 13 | static class PluginUtils 14 | { 15 | /// 16 | /// Opens a URL with the default web browser. 17 | /// 18 | /// 19 | public static void OpenURL(string url) 20 | { 21 | ProcessStartInfo ps = new ProcessStartInfo(url); 22 | ps.UseShellExecute = true; 23 | ps.Verb = "Open"; 24 | Process.Start(ps); 25 | } 26 | 27 | public static string GetArchivePath(IGame game, IAdditionalApplication app) 28 | { 29 | return PathUtils.GetAbsolutePath((app != null && app.ApplicationPath != string.Empty) ? app.ApplicationPath : game.ApplicationPath); 30 | } 31 | 32 | public static bool GetEmulatorPlatformAutoExtract(string emulatorId, string platformName) 33 | { 34 | try 35 | { 36 | var emulator = PluginHelper.DataManager.GetEmulatorById(emulatorId); 37 | var emulatorPlatform = Array.Find(emulator.GetAllEmulatorPlatforms(), p => p.Platform.Equals(platformName)); 38 | 39 | #if LAUNCHBOX_PRE_12_8 40 | return emulator.AutoExtract; 41 | #else 42 | // emulatorPlatform.AutoExtract will be null if the Emulator settings haven't been changed since updating to LaunchBox 12.8 43 | // So perform two checks to determine if AutoExtract is true, one at the emulator level, and one at the emulatorPlatform level 44 | return (emulator.AutoExtract && emulatorPlatform.AutoExtract == null) || (emulatorPlatform.AutoExtract == true); 45 | #endif 46 | } 47 | catch (Exception) 48 | { 49 | } 50 | 51 | return false; 52 | } 53 | 54 | public static bool GetEmulatorPlatformM3uDiscLoadEnabled(string emulatorId, string platformName) 55 | { 56 | var emulator = PluginHelper.DataManager.GetEmulatorById(emulatorId); 57 | var emulatorPlatform = Array.Find(emulator.GetAllEmulatorPlatforms(), p => p.Platform.Equals(platformName)); 58 | 59 | return emulatorPlatform.M3uDiscLoadEnabled; 60 | } 61 | 62 | public static void SetEmulatorPlatformM3uDiscLoadEnabled(string emulatorId, string platformName, bool enabled) 63 | { 64 | var emulator = PluginHelper.DataManager.GetEmulatorById(emulatorId); 65 | var emulatorPlatform = Array.Find(emulator.GetAllEmulatorPlatforms(), p => p.Platform.Equals(platformName)); 66 | 67 | emulatorPlatform.M3uDiscLoadEnabled = enabled; 68 | } 69 | 70 | /// 71 | /// Checks if a game is a multi-disc game. Game must have additional apps with Disc property set, and game path must be one of the additional app paths. 72 | /// 73 | /// 74 | /// True is game is multi-disc, False otherwise. 75 | public static bool IsGameMultiDisc(IGame game) 76 | { 77 | var additionalApps = game.GetAllAdditionalApplications(); 78 | 79 | return Array.Exists(additionalApps, a => a.Disc != null && a.ApplicationPath == game.ApplicationPath); 80 | } 81 | 82 | /// 83 | /// Check if the selected additional app is a disc from a multi-disc game. 84 | /// 85 | /// 86 | /// True if app is from multi-disc game, False otherwise. 87 | public static bool IsAdditionalAppMultiDisc(IAdditionalApplication app) 88 | { 89 | return IsGameMultiDisc(PluginHelper.DataManager.GetGameById(app.GameId)) && app.Disc != null; 90 | } 91 | 92 | /// 93 | /// Check if the launched game or additional application is a multi-disc game. 94 | /// 95 | /// 96 | /// 97 | /// True if launched game is multi-disc, False otherwise. 98 | public static bool IsLaunchedGameMultiDisc(IGame game, IAdditionalApplication app) 99 | { 100 | return (app != null && IsAdditionalAppMultiDisc(app)) || (app == null && IsGameMultiDisc(game)); 101 | } 102 | 103 | /// 104 | /// Get info on a multi-disc game. 105 | /// 106 | /// totalDiscs is the total number of discs in a game. Determined by counting additional apps with Disc property set. Will be 0 if not a multi-dsc game. 107 | /// selectedDisc is the selected disc, based on the additional app Disc property. Will be 1 if additional app is null, and 0 if not a multi-disc game. 108 | /// discs is a list of discs and associated info in disc order. Will be empty if not a multi-disc game. 109 | /// 110 | /// 111 | /// 112 | /// Tuple of (totalDiscs, selectedDisc, discs). 113 | public static (int, int, List) GetMultiDiscInfo(IGame game, IAdditionalApplication app) 114 | { 115 | int totalDiscs = 0; 116 | int selectedDisc = 0; 117 | List discs = new List(); 118 | 119 | if (!IsLaunchedGameMultiDisc(game, app)) 120 | { 121 | totalDiscs = 0; 122 | selectedDisc = 0; 123 | discs.Clear(); 124 | } 125 | 126 | var additionalApps = game.GetAllAdditionalApplications(); 127 | var discApps = Array.FindAll(additionalApps, a => a.Disc != null); 128 | foreach (var discApp in discApps) 129 | { 130 | DiscInfo discInfo = new DiscInfo(); 131 | discInfo.ApplicationId = discApp.Id; 132 | discInfo.ArchivePath = PathUtils.GetAbsolutePath(discApp.ApplicationPath); 133 | discInfo.Version = discApp.Version; 134 | discInfo.Disc = (int)discApp.Disc; 135 | discs.Add(discInfo); 136 | } 137 | discs.Sort((a, b) => a.Disc - b.Disc); 138 | 139 | if (app != null) 140 | { 141 | selectedDisc = (int)app.Disc; 142 | } 143 | else 144 | { 145 | selectedDisc = (int)Array.Find(discApps, a => a.ApplicationPath == game.ApplicationPath).Disc; 146 | } 147 | 148 | totalDiscs = discs.Count; 149 | 150 | return (totalDiscs, selectedDisc, discs); 151 | } 152 | 153 | public static IAdditionalApplication GetAdditionalApplicationById(string gameId, string appId) 154 | { 155 | if (string.IsNullOrEmpty(appId)) 156 | { 157 | return null; 158 | } 159 | 160 | var additionalApps = PluginHelper.DataManager.GetGameById(gameId).GetAllAdditionalApplications(); 161 | return Array.Find(additionalApps, app => app.Id == appId); 162 | } 163 | 164 | /// 165 | /// Get a list of (emulator, emulator platform) tuples for a given platform. 166 | /// If the default emulator ID is specified, the resulting list will include the default emulator at index 0. 167 | /// 168 | /// 169 | /// 170 | /// A list of (IEmulator, IEmulatorPlatform) tuples. 171 | public static List<(IEmulator, IEmulatorPlatform)> GetPlatformEmulators(string platform, string defaultEmulatorId = null) 172 | { 173 | List<(IEmulator, IEmulatorPlatform)> emulators = new List<(IEmulator, IEmulatorPlatform)>(); 174 | 175 | foreach (var emulator in PluginHelper.DataManager.GetAllEmulators()) 176 | { 177 | foreach (var emulatorPlatform in emulator.GetAllEmulatorPlatforms()) 178 | { 179 | if (string.Equals(emulatorPlatform.Platform, platform)) 180 | { 181 | if (string.Equals(emulator.Id, defaultEmulatorId)) 182 | { 183 | if (!emulators.Select(emu => emu.Item1.Id).Contains(defaultEmulatorId) || emulatorPlatform.IsDefault) 184 | { 185 | emulators.Insert(0, (emulator, emulatorPlatform)); 186 | } 187 | else 188 | { 189 | emulators.Add((emulator, emulatorPlatform)); 190 | } 191 | } 192 | else 193 | { 194 | emulators.Add((emulator, emulatorPlatform)); 195 | } 196 | } 197 | } 198 | } 199 | 200 | return emulators; 201 | } 202 | 203 | /// 204 | /// Get the emulator title. If the emulator is RetroArch, also gets the core in use 205 | /// 206 | /// 207 | /// 208 | /// The emulator title, plus the core where applicable. 209 | public static string GetEmulatorTitle(IEmulator emulator, IEmulatorPlatform emulatorPlatform) 210 | { 211 | string title = emulator.Title; 212 | 213 | if (string.Equals(emulator.Title, "Retroarch", StringComparison.InvariantCultureIgnoreCase)) 214 | { 215 | try 216 | { 217 | string corePath = emulatorPlatform.CommandLine.Split(new[] { ' ' })[1].Trim(new[] { '"' }); 218 | string core = Path.GetFileNameWithoutExtension(Path.GetFileName(corePath)); 219 | title = string.Format("{0} ({1})", title, core); 220 | } 221 | catch (Exception) 222 | { 223 | } 224 | } 225 | 226 | return title; 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/Plugin/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: System.Windows.ThemeInfo(System.Windows.ResourceDictionaryLocation.None, System.Windows.ResourceDictionaryLocation.SourceAssembly)] 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("Archive Cache Manager Plugin")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("Archive Cache Manager")] 15 | [assembly: AssemblyCopyright("Copyright © 2023")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [assembly: Guid("205f6c26-c727-4171-a760-3599aa2d00d2")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | [assembly: AssemblyVersion("2.16.0.0")] 38 | [assembly: AssemblyFileVersion("2.16.0.0")] 39 | -------------------------------------------------------------------------------- /src/Plugin/Resources/Badges/Archive Cached - Neon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/Badges/Archive Cached - Neon.png -------------------------------------------------------------------------------- /src/Plugin/Resources/Badges/Archive Cached - Simple White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/Badges/Archive Cached - Simple White.png -------------------------------------------------------------------------------- /src/Plugin/Resources/Badges/Archive Cached.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/Badges/Archive Cached.png -------------------------------------------------------------------------------- /src/Plugin/Resources/arrow-circle-double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/arrow-circle-double.png -------------------------------------------------------------------------------- /src/Plugin/Resources/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/badge.png -------------------------------------------------------------------------------- /src/Plugin/Resources/box--plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/box--plus.png -------------------------------------------------------------------------------- /src/Plugin/Resources/box-zipper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/box-zipper.png -------------------------------------------------------------------------------- /src/Plugin/Resources/broom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/broom.png -------------------------------------------------------------------------------- /src/Plugin/Resources/cross-octagon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/cross-octagon.png -------------------------------------------------------------------------------- /src/Plugin/Resources/cross-script.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/cross-script.png -------------------------------------------------------------------------------- /src/Plugin/Resources/exclamation-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/exclamation-red.png -------------------------------------------------------------------------------- /src/Plugin/Resources/exclamation-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/exclamation-white.png -------------------------------------------------------------------------------- /src/Plugin/Resources/exclamation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/exclamation.png -------------------------------------------------------------------------------- /src/Plugin/Resources/folder-horizontal-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/folder-horizontal-open.png -------------------------------------------------------------------------------- /src/Plugin/Resources/gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/gear.png -------------------------------------------------------------------------------- /src/Plugin/Resources/hourglass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/hourglass.png -------------------------------------------------------------------------------- /src/Plugin/Resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/icon.ico -------------------------------------------------------------------------------- /src/Plugin/Resources/icon16x16-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/icon16x16-play.png -------------------------------------------------------------------------------- /src/Plugin/Resources/icon16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/icon16x16.png -------------------------------------------------------------------------------- /src/Plugin/Resources/icon32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/icon32x32.png -------------------------------------------------------------------------------- /src/Plugin/Resources/joystick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/joystick.png -------------------------------------------------------------------------------- /src/Plugin/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/logo.png -------------------------------------------------------------------------------- /src/Plugin/Resources/media-cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/media-cd.png -------------------------------------------------------------------------------- /src/Plugin/Resources/media-gc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/media-gc.png -------------------------------------------------------------------------------- /src/Plugin/Resources/media-md.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/media-md.png -------------------------------------------------------------------------------- /src/Plugin/Resources/media-n64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/media-n64.png -------------------------------------------------------------------------------- /src/Plugin/Resources/media-ps1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/media-ps1.png -------------------------------------------------------------------------------- /src/Plugin/Resources/media-ps2-cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/media-ps2-cd.png -------------------------------------------------------------------------------- /src/Plugin/Resources/media-ps2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/media-ps2.png -------------------------------------------------------------------------------- /src/Plugin/Resources/media-psp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/media-psp.png -------------------------------------------------------------------------------- /src/Plugin/Resources/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/pencil.png -------------------------------------------------------------------------------- /src/Plugin/Resources/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/plus.png -------------------------------------------------------------------------------- /src/Plugin/Resources/star-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/star-blue.png -------------------------------------------------------------------------------- /src/Plugin/Resources/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/star.png -------------------------------------------------------------------------------- /src/Plugin/Resources/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/src/Plugin/Resources/tick.png -------------------------------------------------------------------------------- /src/Plugin/SystemEvents.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Unbroken.LaunchBox.Plugins; 3 | 4 | namespace ArchiveCacheManager 5 | { 6 | class SystemEvents : ISystemEventsPlugin 7 | { 8 | public void OnEventRaised(string eventType) 9 | { 10 | if (eventType == "PluginInitialized") 11 | { 12 | #if DEBUG 13 | Debugger.Launch(); 14 | #endif 15 | Logger.Init(); 16 | Logger.Log("-------- PLUGIN INITIALIZED --------"); 17 | Logger.Log(string.Format("Archive Cache Manager plugin initialized ({0}).", CacheManager.VersionString)); 18 | // Restore 7z in event Archive Cache Manager files are still in ThirdParty\7-Zip (caused by crash, etc) 19 | GameLaunching.Restore7z(); 20 | // Remove any invalid entries from the cache (from failed or aborted launches, or game.ini changes) 21 | CacheManager.VerifyCacheIntegrity(); 22 | } 23 | // Only perform the actions below in LaunchBox 24 | else if (eventType == "LaunchBoxStartupCompleted") 25 | { 26 | // Restore any overridden settings if LaunchBox closed before they could be restored on normal game launch 27 | LaunchBoxDataBackup.RestoreAllSettingsDelay(1000); 28 | 29 | if (Config.UpdateCheck == true) 30 | { 31 | Updater.CheckForUpdate(2000); 32 | } 33 | // UpdateCheck will be null if the option has never been set before. Prompt the user to enable or disable update checks. 34 | else if (Config.UpdateCheck == null) 35 | { 36 | Updater.EnableUpdateCheckPrompt(2000); 37 | } 38 | } 39 | else if (eventType == "LaunchBoxShutdownBeginning" || eventType == "BigBoxShutdownBeginning") 40 | { 41 | Logger.Log("LaunchBox / BigBox shutdown."); 42 | // Restore 7z in event Archive Cache Manager files are still in ThirdParty\7-Zip (caused by crash, etc) 43 | GameLaunching.Restore7z(); 44 | // Restore any overridden settings if LaunchBox closed before they could be restored on normal game launch 45 | LaunchBoxDataBackup.RestoreAllSettings(); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Plugin/SystemMenuItem.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Unbroken.LaunchBox.Plugins; 3 | using System.Windows.Forms; 4 | using System.Windows.Interop; 5 | 6 | namespace ArchiveCacheManager 7 | { 8 | public class SystemMenuItem : ISystemMenuItemPlugin 9 | { 10 | public string Caption => "Archive Cache Manager..."; 11 | public Image IconImage => Resources.icon16x16; 12 | public bool ShowInLaunchBox => true; 13 | public bool ShowInBigBox => false; 14 | public bool AllowInBigBoxWhenLocked => false; 15 | 16 | public void OnSelected() 17 | { 18 | //ConfigWindow window = new ConfigWindow(); 19 | NewConfigWindow window = new NewConfigWindow(); 20 | NativeWindow parent = new NativeWindow(); 21 | 22 | // Glue between the main app window (WPF) and this window (WinForms) 23 | parent.AssignHandle(new WindowInteropHelper(System.Windows.Application.Current.MainWindow).Handle); 24 | window.ShowDialog(parent); 25 | 26 | if (window.RefreshLaunchBox && !PluginHelper.StateManager.IsBigBox) 27 | { 28 | PluginHelper.LaunchBoxMainViewModel.RefreshData(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Plugin/Updater.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using Octokit; 8 | 9 | namespace ArchiveCacheManager 10 | { 11 | class Updater 12 | { 13 | public static async void CheckForUpdate(int delay = 0) 14 | { 15 | await Task.Delay(delay); 16 | 17 | try 18 | { 19 | var client = new GitHubClient(new ProductHeaderValue("archive-cache-manager")); 20 | var latestRelease = await client.Repository.Release.GetLatest("fraganator", "archive-cache-manager"); 21 | string latestReleaseString = latestRelease.TagName.Replace("v", ""); 22 | Version latestVersion = new Version(latestReleaseString); 23 | 24 | if (latestVersion > CacheManager.Version) 25 | { 26 | Logger.Log(string.Format("Update check found new version: {0}", latestRelease.TagName)); 27 | 28 | if (string.IsNullOrEmpty(Config.SkipUpdate) || latestVersion > new Version(Config.SkipUpdate)) 29 | { 30 | var result = FlexibleMessageBox.Show(string.Format("Version {0} of Archive Cache Manager is now available for download. New features include:\r\n\r\n{1}\r\n", latestReleaseString, latestRelease.Body), 31 | "Update Check", MessageBoxButtons.YesNoCancel, Resources.icon32x32, MessageBoxDefaultButton.Button1, 32 | "View Homepage", "Skip This Version", "Remind Me Later"); 33 | if (result == DialogResult.Yes) 34 | { 35 | PluginUtils.OpenURL("https://forums.launchbox-app.com/files/file/234-archive-cache-manager/"); 36 | } 37 | else if (result == DialogResult.No) 38 | { 39 | Config.SkipUpdate = latestVersion.ToString(); 40 | Config.Save(); 41 | } 42 | } 43 | } 44 | else 45 | { 46 | Logger.Log("Update check found latest version installed."); 47 | } 48 | } 49 | catch (Exception e) 50 | { 51 | Logger.Log(string.Format("Update check failed: {0}", e.ToString())); 52 | } 53 | } 54 | 55 | public static async void EnableUpdateCheckPrompt(int delay = 0) 56 | { 57 | await Task.Delay(delay); 58 | 59 | var result = FlexibleMessageBox.Show("Archive Cache Manager can notify you when a new update is available.\r\nNothing will be automatically downloaded or installed.\r\n\r\nEnable update check on startup?", 60 | "Enable update check?", MessageBoxButtons.YesNo, Resources.icon32x32, MessageBoxDefaultButton.Button2); 61 | 62 | Config.UpdateCheck = (result == DialogResult.Yes); 63 | Config.Save(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Plugin/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Plugin/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /thirdparty/7-Zip/7z.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/thirdparty/7-Zip/7z.dll -------------------------------------------------------------------------------- /thirdparty/7-Zip/7z.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraganator/archive-cache-manager/7d3ac88159db5ff9942365a20d35cc261234f567/thirdparty/7-Zip/7z.exe -------------------------------------------------------------------------------- /thirdparty/7-Zip/License.txt: -------------------------------------------------------------------------------- 1 | 7-Zip 2 | ~~~~~ 3 | License for use and distribution 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | 7-Zip Copyright (C) 1999-2021 Igor Pavlov. 7 | 8 | The licenses for files are: 9 | 10 | 1) 7z.dll: 11 | - The "GNU LGPL" as main license for most of the code 12 | - The "GNU LGPL" with "unRAR license restriction" for some code 13 | - The "BSD 3-clause License" for some code 14 | 2) All other files: the "GNU LGPL". 15 | 16 | Redistributions in binary form must reproduce related license information from this file. 17 | 18 | Note: 19 | You can use 7-Zip on any computer, including a computer in a commercial 20 | organization. You don't need to register or pay for 7-Zip. 21 | 22 | 23 | GNU LGPL information 24 | -------------------- 25 | 26 | This library is free software; you can redistribute it and/or 27 | modify it under the terms of the GNU Lesser General Public 28 | License as published by the Free Software Foundation; either 29 | version 2.1 of the License, or (at your option) any later version. 30 | 31 | This library is distributed in the hope that it will be useful, 32 | but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 34 | Lesser General Public License for more details. 35 | 36 | You can receive a copy of the GNU Lesser General Public License from 37 | http://www.gnu.org/ 38 | 39 | 40 | 41 | 42 | BSD 3-clause License 43 | -------------------- 44 | 45 | The "BSD 3-clause License" is used for the code in 7z.dll that implements LZFSE data decompression. 46 | That code was derived from the code in the "LZFSE compression library" developed by Apple Inc, 47 | that also uses the "BSD 3-clause License": 48 | 49 | ---- 50 | Copyright (c) 2015-2016, Apple Inc. All rights reserved. 51 | 52 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 53 | 54 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 55 | 56 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 57 | in the documentation and/or other materials provided with the distribution. 58 | 59 | 3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived 60 | from this software without specific prior written permission. 61 | 62 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 63 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 64 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 65 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 66 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 67 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 68 | ---- 69 | 70 | 71 | 72 | 73 | unRAR license restriction 74 | ------------------------- 75 | 76 | The decompression engine for RAR archives was developed using source 77 | code of unRAR program. 78 | All copyrights to original unRAR code are owned by Alexander Roshal. 79 | 80 | The license for original unRAR code has the following restriction: 81 | 82 | The unRAR sources cannot be used to re-create the RAR compression algorithm, 83 | which is proprietary. Distribution of modified unRAR sources in separate form 84 | or as a part of other software is permitted, provided that it is clearly 85 | stated in the documentation and source comments that the code may 86 | not be used to develop a RAR (WinRAR) compatible archiver. 87 | 88 | 89 | -- 90 | Igor Pavlov 91 | -------------------------------------------------------------------------------- /thirdparty/7-Zip/readme.txt: -------------------------------------------------------------------------------- 1 | 7-Zip 21.07 2 | ----------- 3 | 4 | 7-Zip is a file archiver for Windows. 5 | 6 | 7-Zip Copyright (C) 1999-2021 Igor Pavlov. 7 | 8 | The main features of 7-Zip: 9 | 10 | - High compression ratio in the new 7z format 11 | - Supported formats: 12 | - Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM. 13 | - Unpacking only: AR, ARJ, Base64, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, 14 | IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, 15 | RPM, SquashFS, UDF, UEFI, VDI, VHD, VHDX, VMDK, XAR and Z. 16 | - Fast compression and decompression 17 | - Self-extracting capability for 7z format 18 | - Strong AES-256 encryption in 7z and ZIP formats 19 | - Integration with Windows Shell 20 | - Powerful File Manager 21 | - Powerful command line version 22 | - Localizations for 90 languages 23 | 24 | 25 | 7-Zip is free software distributed under the GNU LGPL (except for unRar code). 26 | Read License.txt for more information about license. 27 | 28 | 29 | This distribution package contains the following files: 30 | 31 | 7zFM.exe - 7-Zip File Manager 32 | 7-zip.dll - Plugin for Windows Shell 33 | 7-zip32.dll - Plugin for Windows Shell (32-bit plugin for 64-bit system) 34 | 7zg.exe - GUI module 35 | 7z.exe - Command line version 36 | 7z.dll - 7-Zip engine module 37 | 7z.sfx - SFX module (Windows version) 38 | 7zCon.sfx - SFX module (Console version) 39 | 40 | License.txt - License information 41 | readme.txt - This file 42 | History.txt - History of 7-Zip 43 | 7-zip.chm - User's Manual in HTML Help format 44 | descript.ion - Description for files 45 | 46 | Lang\en.ttt - English (base) localization file 47 | Lang\*.txt - Localization files 48 | 49 | 50 | --- 51 | End of document 52 | --------------------------------------------------------------------------------