├── LEShortcutCreator.cmd ├── LICENSE ├── NOTICE ├── README.md ├── screenshot-win10.png ├── screenshot-win11.png ├── screenshot-win7.png └── screenshot-win81.png /LEShortcutCreator.cmd: -------------------------------------------------------------------------------- 1 | <# ::: Batch code starts here ::::::::::::::::::::::::::::::::::::::::::::::::: 2 | : 3 | : Batch (*.BAT/*.CMD) wrapper for Powershell scripts 4 | : 5 | : Inspiration: 6 | : - https://stackoverflow.com/questions/29645/#8597794 7 | : - https://stackoverflow.com/questions/9074476/#9074483 8 | : 9 | : Batch code explained: 10 | : setlocal New variables only exists for this script, and do not affect Windows. 11 | : 12 | : PowerShell arguments explained: 13 | : -Sta Enables support for file drag'n'drop in Windows forms. 14 | : -NoProfile Makes sure no PowerShell settings disrupts script execution. 15 | : -ExecutionPolicy Bypass Makes sure no PowerShell settings disrupts script execution. 16 | : -WindowStyle hidden Hides the console window. 17 | 18 | @echo off 19 | setlocal 20 | 21 | :: Test if PowerShell can be found 22 | :: Verify that the PowerShell command works before continuing. 23 | SET PowerShell_default_location=%systemroot%\System32\WindowsPowerShell\v1.0\powershell.exe 24 | where /Q PowerShell 25 | if %ERRORLEVEL% == 0 ( 26 | FOR /f "delims=" %%F IN ('where PowerShell') DO (SET PowerShell=%%F) 27 | ) else ( 28 | if exist "%PowerShell_default_location%" ( 29 | SET PowerShell=%PowerShell_default_location% 30 | ) else ( 31 | REM mshta javascript:alert^("ERROR: PowerShell not found."^);close^(^); 32 | mshta vbscript:Execute^("msgbox ""PowerShell not found."",0,""ERROR"":close"^) 33 | EXIT /B 34 | ) 35 | ) 36 | 37 | :: Run :: Execute this file directly as a PowerShell script 38 | :: No temporary files needed, but errors messages is harder to understand. 39 | set POWERSHELL_BAT_ARGS=%0 %* 40 | if defined POWERSHELL_BAT_ARGS set POWERSHELL_BAT_ARGS=%POWERSHELL_BAT_ARGS:"=\"% 41 | if defined POWERSHELL_BAT_ARGS set POWERSHELL_BAT_ARGS=%POWERSHELL_BAT_ARGS:$=`$% 42 | "%PowerShell%" -Sta -NoProfile -ExecutionPolicy Bypass -WindowStyle hidden -Command Invoke-Expression $('$args=@(^&{$args} %POWERSHELL_BAT_ARGS%);'+[String]::Join([char]10,$(Get-Content '%~f0'))) 43 | EXIT /B 44 | 45 | :: Run DEBUG :: Execute a temporary copy of this file with a *.ps1 extension 46 | :: By running a real *.ps1 file errors messages gets easier to understand. 47 | set TMPFILE=%~d0%~p0%~n0.debug.ps1 48 | COPY "%~f0" "%TMPFILE%" >NUL 49 | "%PowerShell%" -Sta -NoProfile -ExecutionPolicy Bypass -File "%TMPFILE%" %* 50 | DEL "%TMPFILE%" >NUL 51 | PAUSE 52 | EXIT /B 53 | #> 54 | 55 | 56 | 57 | ## ::: PowerShell Code Starts here :::::::::::::::::::::::::::::::::::::::::::: 58 | 59 | ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### 60 | ### ### 61 | ### ============== Locale Emulator Shortcut Creator =============== ### 62 | ### ### 63 | ### Usage Instructions ### 64 | ### ------------------ ### 65 | ### Either launch the script normally to get a Graphical User Interface. ### 66 | ### The interface will let you create shortcuts that uses Locale ### 67 | ### Emulator. ### 68 | ### ### 69 | ### OR drag'n'drop files onto the script file to create shortcut files ### 70 | ### automatically without any graphical interface interaction. These ### 71 | ### shortcut files will be created in the same directory as the script ### 72 | ### file. ### 73 | ### ### 74 | ### ### 75 | ### Locale Emulator ### 76 | ### --------------- ### 77 | ### A software that can run applications (that has no Unicode support) ### 78 | ### with a different locale than the systems default. ### 79 | ### Usually used to run Japanese Games on non-Japanese versions of ### 80 | ### Windows. ### 81 | ### ### 82 | ### Exellent software, highly recommended. ### 83 | ### URL: https://pooi.moe/Locale-Emulator/ ### 84 | ### ### 85 | ### ### 86 | ### This Script ### 87 | ### ----------- ### 88 | ### Creates shortcut files for applications that will run them through ### 89 | ### Locale Emulator. ### 90 | ### ### 91 | ### ### 92 | ### Why? ### 93 | ### ---- ### 94 | ### Shortcut files created by Locale Emulator each requires their own ### 95 | ### config file. This config file is created simultaneously and stored ### 96 | ### in the targeted applications install directory. ### 97 | ### ### 98 | ### This script creates shortcut files that can use Locale Emulator ### 99 | ### without these extra config files. They instead uses Locale Emulator's ### 100 | ### global config file inside Locale Emulator's install directory. ### 101 | ### ### 102 | ### The script can also be used as a launcher for Locale Emulator. ### 103 | ### Applications can be launched directly from the Graphical User ### 104 | ### Interface. ### 105 | ### ### 106 | ### ### 107 | ### Author ### 108 | ### ------ ### 109 | ### Svintoo, 2018-10-11 ### 110 | ### ### 111 | 112 | 113 | 114 | # Stop script on any error 115 | $ErrorActionPreference = "Stop" 116 | 117 | 118 | #REGION BEGIN Script File Location { 119 | 120 | # Figure out the absolute path to this script 121 | if (Test-Path -LiteralPath $MyInvocation.MyCommand.Definition) { 122 | # Script file is run as a PowerShell (*.ps1) script. 123 | # This method of finding the script file path is compatible with PowerShell V2 and up. 124 | $script_path = $MyInvocation.MyCommand.Definition 125 | $file_targets = $Args 126 | } else { 127 | # Script file is run as a Batch (*.cmd/*.bat) script. 128 | # Script file path should be in the first argument. 129 | $script_path, $file_targets = $Args 130 | } 131 | 132 | $script_file = Split-Path $script_path -Leaf 133 | $script_dir = Split-Path $script_path -Parent 134 | #ENDREGION Script File Location } 135 | 136 | 137 | 138 | #REGION BEGIN Files { 139 | $Files = New-Object PSObject -Property @{ 140 | Script = New-Object PSObject -Property @{ 141 | Config = Join-Path $script_dir ([IO.Path]::GetFileNameWithoutExtension($script_file) + ".config.xml") 142 | } 143 | LE = New-Object PSObject -Property @{ 144 | Runner = "LEProc.exe" 145 | Config = "LEConfig.xml" 146 | Editor = "LEGUI.exe" 147 | } 148 | } 149 | #ENDREGION Files } 150 | 151 | 152 | 153 | #REGION BEGIN Create Shortcut { 154 | function Create-LEShortcutFile { 155 | Param ( 156 | <## No Alias ##>[String]$Target, # Mandatory 157 | [Alias("Args") ][String]$TargetArgs, 158 | [Alias("LEPath") ][String]$LocaleEmulatorPath, # Mandatory 159 | [Alias("LangName")][String]$LanguageName, 160 | [Alias("LangID") ][String]$LanguageID, # Mandatory 161 | [Alias("Path") ][String]$ShortcutFilePath, # Mandatory 162 | [Alias("WorkDir") ][String]$WorkingDirectory 163 | ) 164 | 165 | if (-Not $Target -Or -Not $LocaleEmulatorPath -Or -Not $LanguageID -Or -Not $ShortcutFilePath) { 166 | throw "Create-LEShortcutFile: Missing arguments " 167 | } 168 | 169 | if (-Not $WorkingDirectory) { $WorkingDirectory = Split-Path $Target -Parent } 170 | 171 | if ($LanguageName) { 172 | $Description = "\(^-^)/ $LanguageName with Locale Emulator \(^-^)/" 173 | } else { 174 | $Description = "\(^-^)/ Run with Locale Emulator \(^-^)/" 175 | } 176 | 177 | if (-Not (Test-Path -LiteralPath $ShortcutFilePath)) { 178 | New-Item -ItemType file $ShortcutFilePath | Out-Null 179 | } 180 | 181 | $Shell = New-Object -ComObject Shell.Application 182 | 183 | $ShortcutDirectory = Split-Path $ShortcutFilePath -Parent 184 | $ShortcutFilename = Split-Path $ShortcutFilePath -Leaf 185 | 186 | $Shortcut = $Shell.NameSpace($ShortcutDirectory).ParseName($ShortcutFilename).GetLink 187 | $Shortcut.Description = $Description 188 | $Shortcut.Path = $LocaleEmulatorPath 189 | $Shortcut.Arguments = "-runas `"$LanguageID`" `"$Target`" $TargetArgs".Trim() 190 | $Shortcut.WorkingDirectory = $WorkingDirectory 191 | $Shortcut.SetIconLocation($Target, 0) 192 | 193 | $Shortcut.Save() | Out-Null 194 | } 195 | #ENDREGION Create Shortcut } 196 | 197 | 198 | 199 | #REGION BEGIN Get Config { 200 | <# 201 | 202 | 203 | $Language 204 | $DefaultSaveDirectory 205 | $LELocation 206 | 207 | #> 208 | function Get-ConfigFile { 209 | $ConfigPath = $Files.Script.Config 210 | 211 | if (Test-Path -LiteralPath $ConfigPath -PathType Leaf -ErrorAction SilentlyContinue) { 212 | $config = try{ ([Xml](Get-Content $ConfigPath)).Config } catch {} 213 | } 214 | 215 | # Return $Null if config file is not found or can not be parsed 216 | return $config 217 | } 218 | #ENDREGION Get Config } 219 | 220 | 221 | 222 | #REGION BEGIN FileSystem Paths { 223 | <# # Explanation to why a Regular Expression solution is used for normalizing paths # # 224 | # (Join-Path $path1 '') -Eq (Join-Path $path2 '') 225 | # o Handles paths that doesn't exists: "C:\fake\" == "C:\fake\" 226 | # o Handles both \ and /: "C:\asdf\" == "C:/asdf/" 227 | # o Handles missing \ at end of line: "C:\asdf\" == "C:\asdf" 228 | # o Handles multiple \ at end of line: "C:\asdf\" == "C:\asdf\\" 229 | # x Doesn't handle multiple \ anywhere else: "C:\\asdf" != "C:\asdf" 230 | # x Doesn't handle spaces before \ and end: "C: \asdf" != "C:\asdf" #NOTE: Only in PowerShell. 231 | # "C:\asdf " != "C:\asdf" Not valid in Batch (cmd). 232 | # (Get-ItemProperty -LiteralPath $path1).FullName -Eq (Get-ItemProperty -LiteralPath $path2).FullName 233 | # x Doesn't handle paths that doesn't exists: "C:\fake\" != "C:\fake\" 234 | # o Handles both \ and /: "C:\asdf\" == "C:/asdf/" 235 | # x Doesn't handle missing \ at end of line: "C:\asdf\" != "C:\asdf" 236 | # o Handles multiple \ at end of line: "C:\asdf\" == "C:\asdf\\" 237 | # o Handles multiple \ anywhere else: "C:\\asdf" != "C:\asdf" 238 | # o Handles spaces before \ and end: "C: \asdf" == "C:\asdf" #NOTE: Only in PowerShell. 239 | # "C:\asdf " != "C:\asdf" Not valid in Batch (cmd). 240 | # -Not (Compare-Object (Get-ItemProperty -LiteralPath $path1) (Get-ItemProperty -LiteralPath $path2)) 241 | # NO! Returns $Null if EQUAL. Code is hard to understand. 242 | # AND doesn't handle paths that doesn't exists. 243 | #> 244 | 245 | # All strings that resolve to the same file system path are here normalized to the same string 246 | function Normalize-Path([String]$path) { 247 | # C:/path/somewhere => C:\path\somewhere 248 | # C:\path\\somewhere => C:\path\somewhere 249 | # C:\path\somewhere\ => C:\path\somewhere 250 | # C:\path \somewhere => C:\path\somewhere # PowerShell ignores whitespaces at end of file/folder names 251 | $path -Replace "/", "\" ` 252 | -Replace "\\\\+", "\" ` 253 | -Replace "\\$", "" ` 254 | -Replace " +\\", "\" 255 | } 256 | 257 | # Compare two or more file system paths 258 | function Equal-Paths { 259 | if ($Args.Count -LT 2) { 260 | throw "Equal-Paths: At least two paths are needed for comparison" 261 | } 262 | 263 | $reference_path = Normalize-Path $Args[0] 264 | 265 | for ($i = 1 ; $i -LT $Args.Count ; $i++) { 266 | $path = Normalize-Path $Args[$i] 267 | 268 | if ($path -NE $reference_path) { 269 | return $False 270 | } 271 | } 272 | 273 | return $True 274 | } 275 | #ENDREGION FileSystem Paths } 276 | 277 | 278 | 279 | #REGION BEGIN Locale Emulator Functions { 280 | function Verify-LEDirectory([String]$Directory) { 281 | # Test-Path arguments '-ErrorAction SilentlyContinue' is ignored when: 282 | # - using arguments '-PathType Container', OR 283 | # - receiving an empty string ("") as path. 284 | # Hence the try-catch. 285 | $dir_exists = try { Test-Path -LiteralPath $Directory -PathType Container } catch { $False } 286 | 287 | if (-Not $dir_exists) { return $False } 288 | if (-Not (Test-Path -LiteralPath (Join-Path -Path $Directory -ChildPath $Files.LE.Runner))) { return $False } 289 | 290 | return $True 291 | } 292 | 293 | function Get-LEDirectories { 294 | Param ( 295 | [String]$ConfigLEDirectory = "" 296 | ) 297 | 298 | $LEDirs = New-Object System.Collections.ArrayList 299 | 300 | # Config directory 301 | if ($ConfigLEDirectory) { 302 | $DirectoryPath = $ConfigLEDirectory 303 | $Valid = Verify-LEDirectory $DirectoryPath 304 | $LEDirs.Add((New-Object PSObject -Property @{Path = $DirectoryPath; Valid = $Valid})) | Out-Null 305 | } 306 | 307 | # Script directory 308 | $DirectoryPath = $script_dir 309 | $Valid = Verify-LEDirectory $DirectoryPath 310 | if ($Valid) { 311 | $LEDirs.Add((New-Object PSObject -Property @{Path = $DirectoryPath; Valid = $Valid})) | Out-Null 312 | } 313 | 314 | # Install directory 315 | if (-Not (Test-Path "HKCR:")) { New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT | Out-Null } 316 | $regKeyName = "HKCR:\CLSID\{C52B9871-E5E9-41FD-B84D-C5ACADBEC7AE}\InprocServer32" 317 | $regValueName = "CodeBase" 318 | if (Test-Path $regKeyName) { 319 | $regKey = Get-Item $regKeyName 320 | $LE_DLLPath = [String](Get-ItemProperty $regKey.PSPath).$regValueName 321 | if ($LE_DLLPath) { 322 | #Ex: $LE_DLLPath = "file:///C:/path/to/Locale Emulator/LEContextMenuHandler.DLL" 323 | $DirectoryPath = $LE_DLLPath -Replace '^file:///','' -Replace '/[^/]+$','' -Replace '/','\' 324 | $Valid = Verify-LEDirectory $DirectoryPath 325 | $LEDirs.Add((New-Object PSObject -Property @{Path = $DirectoryPath; Valid = $Valid})) | Out-Null 326 | } 327 | } 328 | 329 | return ,$LEDirs 330 | } 331 | 332 | # Returns a list of the profiles in Locale Emulator's config file. 333 | function Get-LELanguages([String]$Directory) { 334 | # ArrayList is used instead of Powershell arrays because: 335 | # - Faster when changing the number of elements in the array. 336 | # - required when used with ComboBoxes in Windows Forms. 337 | $Languages = New-Object System.Collections.ArrayList 338 | 339 | [XML]$Config = try { 340 | Get-Content (Join-Path -Path $Directory -ChildPath $Files.LE.Config) 341 | } catch { 342 | # Return empty language list if the LE config file doesn't exist 343 | return ,$Languages 344 | } 345 | 346 | if ($Config.LEConfig.Profiles.Profile -Is [Xml.XmlLinkedNode]) { 347 | # Only one element in XML document 348 | $Profiles = @($Config.LEConfig.Profiles.Profile) 349 | } elseif ($Config.LEConfig.Profiles.Profile -Is [Array]) { 350 | # Multiple elements in XML document 351 | $Profiles = $Config.LEConfig.Profiles.Profile 352 | } 353 | 354 | $Profiles | % { 355 | if ($_.Name -And $_.Guid) { 356 | $Language = New-Object PSObject -Property @{Name = $_.Name; Guid = $_.Guid} 357 | $Languages.Add($Language) | Out-Null 358 | } 359 | } 360 | 361 | return ,$Languages 362 | } 363 | #ENDREGION Locale Emulator Functions } 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | ### ### ### ### ### The Automatic Part ### ### ### ### ### 374 | ### ### 375 | ### No user interaction here. ### 376 | ### ### 377 | ### File paths are received through script arguments ### 378 | ### (by drag'n'drop) and shortcuts are created ### 379 | ### automatically. ### 380 | ### ### 381 | ### NOTE: Settings in the config file are used here ### 382 | ### ### 383 | 384 | #REGION BEGIN NoGUI { 385 | Add-Type -AssemblyName PresentationFramework 386 | 387 | if ($file_targets.Count -GT 0) { 388 | # Verify that all targets exists 389 | ForEach ($Target in $file_targets) { 390 | $TargetExists = Test-Path -LiteralPath $Target -PathType Leaf -ErrorAction SilentlyContinue 391 | if (-Not $TargetExists) { 392 | [System.Windows.MessageBox]::Show("File not found: $Target") 393 | Exit 394 | } 395 | } 396 | 397 | # Parse config file (if it exists) 398 | $Config = Get-ConfigFile 399 | 400 | # Find Locale Emulator 401 | if ($Config) { 402 | $LEDirectories = Get-LEDirectories -ConfigLEDirectory $Config.LELocation 403 | $LanguageName = $Config.Language 404 | } else { 405 | $LEDirectories = Get-LEDirectories 406 | } 407 | 408 | if ($LEDirectories.Count -Eq 0) { 409 | [System.Windows.MessageBox]::Show("Can't find Locale Emulator") 410 | Exit 411 | } 412 | 413 | $LEDirectory = $LEDirectories[0] 414 | 415 | if (-Not $LEDirectory.Valid) { 416 | [System.Windows.MessageBox]::Show("Configured path to Locale Emulator is invalid: ${$LEDirectory.Path}") 417 | Exit 418 | } 419 | 420 | $LocaleEmulatorPath = Join-Path $LEDirectory.Path $Files.LE.Runner 421 | 422 | # Fetch all available Languages (Profiles) configured in Locale Emulator 423 | $Languages = Get-LELanguages $LEDirectory.Path 424 | 425 | if ($Languages.Count -Eq 0) { 426 | [System.Windows.MessageBox]::Show("Locale Emulator has no configured profiles: ${Join-Path $LEDirectory.Path $Files.LE.Config}") 427 | Exit 428 | } 429 | 430 | # Select one language 431 | if ($LanguageName) { 432 | $Language = $Languages | Where { $_.Name -Eq $LanguageName } | Select -First 1 433 | } 434 | 435 | if (-Not $Language) { $Language = $Languages[0] } 436 | 437 | # Create all shortcut files and save them to the script directory 438 | ForEach ($Target in $file_targets) { 439 | $ShortcutFilename = [IO.Path]::GetFileNameWithoutExtension($Target) 440 | $ShortcutFilename = "(LE)" + $ShortcutFilename + ".lnk" 441 | $ShortcutFilePath = Join-Path $script_dir $ShortcutFilename 442 | 443 | Create-LEShortcutFile -Target $Target ` 444 | -LocaleEmulatorPath $LocaleEmulatorPath ` 445 | -LanguageName $Language.Name ` 446 | -LanguageID $Language.Guid ` 447 | -ShortcutFilePath $ShortcutFilePath 448 | } 449 | 450 | Exit 451 | } 452 | #ENDREGION NoGUI } 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | ### ### ### ### ### The Interactive Part ### ### ### ### ### 462 | ### ### 463 | ### Displays a Graphical User Interface (GUI) ### 464 | ### ### 465 | ### A user can here select files and configure settings ### 466 | ### before deciding to either create shortcuts OR launch ### 467 | ### a selected file directly. ### 468 | ### ### 469 | ### Selected settings can be saved and remembered. ### 470 | ### ### 471 | 472 | #REGION BEGIN GUI { 473 | <# This form was created using POSHGUI.com a free online gui designer for PowerShell #> 474 | Add-Type -AssemblyName System.Windows.Forms 475 | [System.Windows.Forms.Application]::EnableVisualStyles() 476 | 477 | 478 | 479 | #REGION BEGIN GUI Colors { 480 | $TextBoxColors = New-Object PSObject -Property @{ 481 | None = [System.Drawing.Color]::Empty 482 | Invalid = "255,235,238" # Light Pink (textbox bgcolor, used when text is invalid) 483 | } 484 | #ENDREGION GUI Colors } 485 | 486 | 487 | 488 | #REGION BEGIN GUI Elements { 489 | $Form = New-Object System.Windows.Forms.Form 490 | $Form.ClientSize = New-Object System.Drawing.Size(400,400) #'400,400' 491 | $Form.Text = "LE Shortcut Creator" 492 | $Form.TopMost = $False 493 | $Form.MaximizeBox = $False 494 | $Form.SizeGripStyle = "Hide" 495 | $Form.StartPosition = "CenterScreen" # Manual, CenterScreen, WindowsDefaultLocation, WindowsDefaultBounds, CenterParent 496 | $Form.FormBorderStyle = 'FixedDialog' # None, FixedSingle, Fixed3D, FixedDialog, Sizable, FixedToolWindow, SizableToolWindow 497 | 498 | $labels_x = 9 499 | $browse_buttons_width = 74 500 | $browse_buttons_height = 24 501 | $browse_buttons_x = 316 502 | $browse_textbox_width = 304 503 | $browse_textbox_height = 22 504 | $lang_buttons_width = 88 505 | $lang_buttons_height = 26 506 | $bottom_buttons_y = 360 507 | 508 | $Target_Label = New-Object System.Windows.Forms.Label 509 | $Target_Label.Text = "Target(s)" 510 | $Target_Label.AutoSize = $True 511 | $Target_Label.Width = 25 512 | $Target_Label.Height = 10 513 | $Target_Label.Location = New-Object System.Drawing.Point($labels_x,5) 514 | 515 | $Target_TextBox = New-Object System.Windows.Forms.TextBox 516 | $Target_TextBox.Multiline = $False 517 | $Target_TextBox.Width = $browse_textbox_width 518 | $Target_TextBox.Height = $browse_textbox_height 519 | $Target_TextBox.AutoSize = $False #Win11 520 | $Target_TextBox.Location = New-Object System.Drawing.Point(11,24) 521 | Add-Member -InputObject $Target_TextBox -MemberType "NoteProperty" -Name "Targets" -Value @() 522 | # Note that the initial directory fallback below is modified during execution 523 | Add-Member -InputObject $Target_TextBox -MemberType "NoteProperty" -Name "FileBrowserInitialDirectoryFallback" ` 524 | -Value $script_dir 525 | 526 | $Target_Button = New-Object System.Windows.Forms.Button 527 | $Target_Button.Text = "Browse..." 528 | $Target_Button.Width = $browse_buttons_width 529 | $Target_Button.Height = $browse_buttons_height 530 | $Target_Button.Location = New-Object System.Drawing.Point($browse_buttons_x,23) 531 | 532 | $Args_Label = New-Object System.Windows.Forms.Label 533 | $Args_Label.Text = "Target Arguments (optional)" 534 | $Args_Label.AutoSize = $True 535 | $Args_Label.Width = 25 536 | $Args_Label.Height = 10 537 | $Args_Label.Location = New-Object System.Drawing.Point($labels_x,59) 538 | 539 | $Args_TextBox = New-Object System.Windows.Forms.TextBox 540 | $Args_TextBox.Multiline = $False 541 | $Args_TextBox.Width = $browse_textbox_width + $browse_buttons_width 542 | $Args_TextBox.Height = $browse_textbox_height 543 | $Args_TextBox.AutoSize = $False #Win11 544 | $Args_TextBox.Location = New-Object System.Drawing.Point(11,78) 545 | 546 | $Lang_Groupbox = New-Object System.Windows.Forms.Groupbox 547 | $Lang_Groupbox.Height = 56 548 | $Lang_Groupbox.Width = 378 549 | $Lang_Groupbox.Text = "Language" 550 | $Lang_Groupbox.Location = New-Object System.Drawing.Point(11,119) 551 | 552 | $Lang_ComboBox = New-Object System.Windows.Forms.ComboBox 553 | $Lang_ComboBox.Width = 266 554 | $Lang_ComboBox.Height = 24 555 | $Lang_ComboBox.Location = New-Object System.Drawing.Point(9,19) 556 | $Lang_ComboBox.DropDownStyle = "DropDownList" 557 | $Lang_ComboBox.DisplayMember = "Name" 558 | $Lang_ComboBox.ValueMember = "Guid" 559 | $Lang_ComboBox.DataSource = New-Object System.Collections.ArrayList(@(,@(New-Object PSObject -Property @{Name="";Guid=""}))) 560 | 561 | $Lang_Button_Edit = New-Object System.Windows.Forms.Button 562 | $Lang_Button_Edit.Text = "Edit List" 563 | $Lang_Button_Edit.Width = $lang_buttons_width 564 | $Lang_Button_Edit.Height = $lang_buttons_height 565 | $Lang_Button_Edit.Location = New-Object System.Drawing.Point(282,18) 566 | $Lang_Button_Edit.Enabled = $False 567 | 568 | $SaveSettings_Checkbox = New-Object System.Windows.Forms.Checkbox 569 | $SaveSettings_Checkbox.Text = "Remember Settings" 570 | $SaveSettings_Checkbox.AutoSize = $True 571 | $SaveSettings_Checkbox.AutoCheck = $True 572 | $SaveSettings_Checkbox.Location = New-Object System.Drawing.Point(($labels_x+2),187) 573 | 574 | $SaveDir_Label = New-Object System.Windows.Forms.Label 575 | $SaveDir_Label.Text = "Default Save directory" 576 | $SaveDir_Label.AutoSize = $True 577 | $SaveDir_Label.Width = 25 578 | $SaveDir_Label.Height = 10 579 | $SaveDir_Label.Location = New-Object System.Drawing.Point($labels_x,223) 580 | 581 | $SaveDir_TextBox = New-Object System.Windows.Forms.TextBox 582 | $SaveDir_TextBox.Multiline = $False 583 | $SaveDir_TextBox.Width = $browse_textbox_width 584 | $SaveDir_TextBox.Height = $browse_textbox_height 585 | $SaveDir_TextBox.AutoSize = $False #Win11 586 | $SaveDir_TextBox.Location = New-Object System.Drawing.Point(11,242) 587 | $SaveDir_TextBox.text = [Environment]::GetFolderPath("Desktop") 588 | Add-Member -InputObject $SaveDir_TextBox -MemberType "NoteProperty" -Name "TextWas" -Value $SaveDir_TextBox.text 589 | 590 | $SaveDir_Button = New-Object System.Windows.Forms.Button 591 | $SaveDir_Button.Text = "Browse..." 592 | $SaveDir_Button.Width = $browse_buttons_width 593 | $SaveDir_Button.Height = $browse_buttons_height 594 | $SaveDir_Button.Location = New-Object System.Drawing.Point($browse_buttons_x,241) 595 | 596 | $LELocation_Label = New-Object System.Windows.Forms.Label 597 | $LELocation_Label.Text = "Locale Emulator location" 598 | $LELocation_Label.AutoSize = $True 599 | $LELocation_Label.Width = 25 600 | $LELocation_Label.Height = 10 601 | $LELocation_Label.Location = New-Object System.Drawing.Point($labels_x,277) 602 | 603 | $LELocation_TextBox = New-Object System.Windows.Forms.TextBox 604 | $LELocation_TextBox.Multiline = $False 605 | $LELocation_TextBox.Width = $browse_textbox_width 606 | $LELocation_TextBox.Height = $browse_textbox_height 607 | $LELocation_TextBox.AutoSize = $False #Win11 608 | $LELocation_TextBox.BackColor = $TextBoxColors.Invalid 609 | $LELocation_TextBox.Location = New-Object System.Drawing.Point(11,294) 610 | Add-Member -InputObject $LELocation_TextBox -MemberType "NoteProperty" -Name "TextWas" -Value "" 611 | Add-Member -InputObject $LELocation_TextBox -MemberType "NoteProperty" -Name "Valid" -Value $False 612 | Add-Member -InputObject $LELocation_TextBox -MemberType "NoteProperty" -Name "NewValid" -Value $Null 613 | 614 | $LELocation_Button = New-Object System.Windows.Forms.Button 615 | $LELocation_Button.Text = "Browse..." 616 | $LELocation_Button.Width = $browse_buttons_width 617 | $LELocation_Button.Height = $browse_buttons_height 618 | $LELocation_Button.Location = New-Object System.Drawing.Point($browse_buttons_x,293) 619 | 620 | $Run_Button = New-Object System.Windows.Forms.Button 621 | $Run_Button.Text = "Run Target" 622 | $Run_Button.Width = 115 623 | $Run_Button.Height = 30 624 | $Run_Button.Location = New-Object System.Drawing.Point(10,$bottom_buttons_y) 625 | $Run_Button.Enabled = $False 626 | 627 | $Save_Button = New-Object System.Windows.Forms.Button 628 | Add-Member -InputObject $Save_Button -MemberType "NoteProperty" -Name "TextSingular" -Value "Save Shortcut" 629 | Add-Member -InputObject $Save_Button -MemberType "NoteProperty" -Name "TextPlural" -Value "Save Shortcuts" 630 | $Save_Button.Text = $Save_Button.textSingular 631 | $Save_Button.Width = 115 632 | $Save_Button.Height = 30 633 | $Save_Button.Location = New-Object System.Drawing.Point(142,$bottom_buttons_y) 634 | $Save_Button.Enabled = $False 635 | 636 | $Quit_Button = New-Object System.Windows.Forms.Button 637 | $Quit_Button.Text = "Quit" 638 | $Quit_Button.Width = 115 639 | $Quit_Button.Height = 30 640 | $Quit_Button.Location = New-Object System.Drawing.Point(275,$bottom_buttons_y) 641 | 642 | $form_controls = @($Target_TextBox,$Target_Button,$Target_Label,$Args_TextBox,$Args_Label,$Lang_Groupbox,$SaveSettings_Checkbox,$SaveDir_TextBox,$SaveDir_Button,$SaveDir_Label,$LELocation_TextBox,$LELocation_Button,$LELocation_Label,$Run_Button,$Save_Button,$Quit_Button) 643 | $lang_controls = @($Lang_ComboBox,$Lang_Button_Edit) 644 | $controls = $form_controls + $lang_controls 645 | 646 | $Form.controls.AddRange($form_controls) 647 | $Lang_Groupbox.controls.AddRange($lang_controls) 648 | #ENDREGION GUI Elements } 649 | 650 | 651 | 652 | #REGION BEGIN GUI LELocations Elements } 653 | $Form_LELocations = New-Object System.Windows.Forms.Form 654 | $Form_LELocations.ClientSize = New-Object System.Drawing.Size(400,182) 655 | $Form_LELocations.Text = "LE Locations" 656 | $Form_LELocations.TopMost = $False 657 | $Form_LELocations.MaximizeBox = $False 658 | $Form_LELocations.SizeGripStyle = "Hide" 659 | $Form_LELocations.StartPosition = "CenterScreen" # Manual, CenterScreen, WindowsDefaultLocation, WindowsDefaultBounds, CenterParent 660 | $Form_LELocations.FormBorderStyle = 'FixedDialog' # None, FixedSingle, Fixed3D, FixedDialog, Sizable, FixedToolWindow, SizableToolWindow 661 | Add-Member -InputObject $Form_LELocations -MemberType "NoteProperty" -Name "ButtonAction" -Value "" 662 | 663 | $LELocations_Label = New-Object System.Windows.Forms.Label 664 | $LELocations_Label.Text = "Which Locale Emultor do you want to use?" 665 | $LELocations_Label.AutoSize = $True 666 | $LELocations_Label.Height = 10 667 | $LELocations_Label.Location = New-Object System.Drawing.Point(9,8) 668 | 669 | $LELocations_ListBox = New-Object System.Windows.Forms.ListBox 670 | $LELocations_ListBox.SelectionMode = "one" 671 | $LELocations_ListBox.Sorted = $False 672 | $LELocations_ListBox.HorizontalScrollbar = $True 673 | $LELocations_ListBox.Width = 378 674 | $LELocations_ListBox.Height = 100 675 | $LELocations_ListBox.Location = New-Object System.Drawing.Point(11,32) 676 | $LELocations_ListBox.DisplayMember = "Name" 677 | $LELocations_ListBox.ValueMember = "Value" 678 | $LELocations_ListBox.DataSource = New-Object System.Collections.ArrayList(@(,@(New-Object PSObject -Property @{Name=""; Value=""}))) 679 | Add-Member -InputObject $LELocations_ListBox -MemberType "NoteProperty" -Name "LastSelection" -Value "" 680 | 681 | $LELocations_ButtonCancel = New-Object System.Windows.Forms.Button 682 | $LELocations_ButtonCancel.Text = "Cancel" 683 | $LELocations_ButtonCancel.width = 115 684 | $LELocations_ButtonCancel.Height = 30 685 | $LELocations_ButtonCancel.Location = New-Object System.Drawing.Point(10,142) 686 | 687 | $LELocations_ButtonBrowse = New-Object System.Windows.Forms.Button 688 | $LELocations_ButtonBrowse.Text = "Browse" 689 | $LELocations_ButtonBrowse.Width = 115 690 | $LELocations_ButtonBrowse.Height = 30 691 | $LELocations_ButtonBrowse.Location = New-Object System.Drawing.Point(142,142) 692 | 693 | $LELocations_ButtonSelect = New-Object System.Windows.Forms.Button 694 | $LELocations_ButtonSelect.Text = "Select" 695 | $LELocations_ButtonSelect.Width = 115 696 | $LELocations_ButtonSelect.Height = 30 697 | $LELocations_ButtonSelect.Location = New-Object System.Drawing.Point(275,142) 698 | 699 | $lelocation_controls = @($LELocations_ListBox,$LELocations_Label,$LELocations_ButtonCancel,$LELocations_ButtonBrowse,$LELocations_ButtonSelect) 700 | $Form_LELocations.controls.AddRange($lelocation_controls) 701 | #ENDREGION GUI LELocations Elements } 702 | 703 | 704 | 705 | #REGION BEGIN GUI Functions { 706 | 707 | # Tries to find a valid absolute path in a string (best effort). 708 | # Should return an empty string on failure. 709 | function Find-ValidPath([String]$String) { 710 | $Path = $String.Trim(' "') -Replace '^[^a-zA-Z]+','' 711 | $Path = Normalize-Path $Path 712 | 713 | #if ($Path.Length -LT 2 -Or $Path -Match '^[a-zA-Z][^:]') { 714 | if ($Path.Length -LT 2 -Or -Not ($Path -Match '^[a-zA-Z]+:')) { 715 | return "" 716 | } 717 | 718 | if ($Path) { 719 | # Shorten the path until a valid one is found 720 | while ($Path -NE "") { 721 | # Test-Path arguments '-ErrorAction SilentlyContinue' is ignored when: 722 | # - using arguments '-PathType Container', OR 723 | # - receiving an empty string ("") as path. 724 | # Hence the try-catch. 725 | $valid_path = try { Test-Path -LiteralPath $Path -PathType Container } catch { $False } 726 | if ($valid_path) { break } 727 | $Path = Split-Path $Path -Parent 728 | } 729 | } 730 | 731 | return $Path 732 | } 733 | 734 | function Show-SaveFileDialog { 735 | Param ( 736 | [String]$Title = "", 737 | [String]$InitialFilename = "", 738 | [String]$InitialDirectory = "", 739 | [String]$InitialDirectoryFallback = "", 740 | [String]$Filter = "", 741 | [Int] $FilterIndex = 0 742 | ) 743 | 744 | $SaveFileDialog = New-Object -Typename System.Windows.Forms.SaveFileDialog 745 | $SaveFileDialog.Title = $Title 746 | $SaveFileDialog.Filter = $Filter 747 | $SaveFileDialog.FilterIndex = $FilterIndex 748 | $SaveFileDialog.FileName = $InitialFilename 749 | $SaveFileDialog.ShowHelp = $True 750 | 751 | if ($InitialDirectory -And ($InitialDirectory = Find-ValidPath $InitialDirectory)) { 752 | $SaveFileDialog.InitialDirectory = $InitialDirectory 753 | } elseif ($InitialDirectoryFallback) { 754 | $SaveFileDialog.InitialDirectory = $InitialDirectoryFallback 755 | } 756 | 757 | $dialog_result = $SaveFileDialog.ShowDialog() 758 | 759 | if ($dialog_result -Eq [System.Windows.Forms.DialogResult]::OK) { 760 | return $SaveFileDialog.Filename 761 | } else { 762 | return $Null 763 | } 764 | } 765 | 766 | function Show-OpenFileDialog { 767 | Param ( 768 | [String]$InitialDirectory = "", 769 | [String]$InitialDirectoryFallback = "", 770 | [String]$Filter = "", 771 | [Int] $FilterIndex = 0, 772 | [Switch]$CheckFileExists, 773 | [Switch]$Multiselect 774 | ) 775 | 776 | $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog 777 | $OpenFileDialog.Filter = $Filter #"All files (*.*)| *.*|Applications (*.exe)| *.exe" 778 | $OpenFileDialog.FilterIndex = $FilterIndex 779 | $OpenFileDialog.CheckFileExists = $CheckFileExists 780 | $OpenFileDialog.Multiselect = $Multiselect 781 | $OpenFileDialog.ShowHelp = $True # OpenFileDialog won't show unless the help button is displayed 782 | 783 | if ($InitialDirectory -And ($InitialDirectory = Find-ValidPath $InitialDirectory)) { 784 | $OpenFileDialog.InitialDirectory = $InitialDirectory 785 | } elseif ($InitialDirectoryFallback) { 786 | $OpenFileDialog.InitialDirectory = $InitialDirectoryFallback 787 | } 788 | 789 | $dialog_result = $OpenFileDialog.ShowDialog() 790 | 791 | if ($dialog_result -NE [System.Windows.Forms.DialogResult]::OK) { 792 | return $Null 793 | } elseif ($OpenFileDialog.FileNames.Count -Eq 1) { 794 | return $OpenFileDialog.FileName 795 | } else { 796 | return '"' + ($OpenFileDialog.FileNames -Join '" "') + '"' 797 | } 798 | } #END Function Show-OpenFileDialog 799 | 800 | 801 | function Show-FolderBrowserDialog([String]$Description, [bool]$ShowNewFolderButton = $False, [String]$InitialDirectory = "") { 802 | $FolderBrowserDialog = New-Object System.Windows.Forms.FolderBrowserDialog 803 | $FolderBrowserDialog.Description = $Description 804 | $FolderBrowserDialog.RootFolder = "Desktop" # Desktop, MyComputer, MyDocuments, Favorites, Personal, DesktopDirectory 805 | $FolderBrowserDialog.ShowNewFolderButton = $ShowNewFolderButton 806 | 807 | if ($InitialDirectory -And ($InitialDirectory = Find-ValidPath $InitialDirectory)) { 808 | $FolderBrowserDialog.SelectedPath = $InitialDirectory 809 | } else { 810 | $FolderBrowserDialog.SelectedPath = [Environment]::GetFolderPath("Desktop") 811 | } 812 | 813 | $dialog_result = $FolderBrowserDialog.ShowDialog() 814 | 815 | if ($dialog_result -NE [System.Windows.Forms.DialogResult]::OK) { 816 | return $Null 817 | } else { 818 | return $FolderBrowserDialog.SelectedPath 819 | } 820 | } #END Function Show-FolderBrowserDialog 821 | 822 | 823 | function Show-LELocationDialog($LEPaths, $DefaultSelection) { 824 | $Options = New-Object System.Collections.ArrayList 825 | if (-Not $DefaultSelection) { $DefaultSelection = $LELocations_ListBox.LastSelection } 826 | 827 | ForEach ($LEPath in $LEPaths) { 828 | $Options.Add((New-Object PSObject -Property @{Name=$LEPath; Value=$LEPath})) | Out-Null 829 | } 830 | 831 | $LELocations_ListBox.DataSource = $Options 832 | 833 | $Option = $LELocations_ListBox.DataSource | Where { $_.Value -Eq $DefaultSelection } | Select -First 1 834 | if ($Option) { $LELocations_ListBox.SelectedItem = $Option } 835 | 836 | #$Form_LELocations.Location = Get-NewGUIPosition $Form_LELocations 837 | $Form_LELocations.ShowDialog() | Out-Null 838 | 839 | Switch -Exact ($Form_LELocations.ButtonAction) { 840 | "Select" { return $LELocations_ListBox.SelectedValue } 841 | "Browse" { return "browse" } 842 | "Cancel" { return $Null } 843 | default { Throw "Fatal Error: This should never happen" } 844 | } 845 | } 846 | 847 | function GUI-Update-LE { 848 | GUI-Update-Languages 849 | GUI-Update-RunCreate 850 | } 851 | 852 | function GUI-Disable-LE { 853 | GUI-Disable-Languages 854 | GUI-Disable-RunCreate 855 | } 856 | 857 | function GUI-Update-RunCreate { 858 | if (-Not $LELocation_TextBox.Valid -Or -Not $Lang_ComboBox.Enabled) { 859 | $Run_Button.Enabled = $False 860 | $Save_Button.Enabled = $False 861 | return 862 | } 863 | 864 | if ($Target_TextBox.Targets.Count -Eq 1) { 865 | $Run_Button.Enabled = $True 866 | $Save_Button.Enabled = $True 867 | $Save_Button.Text = $Save_Button.TextSingular 868 | } elseif ($Target_TextBox.Targets.Count -ge 2) { 869 | $Run_Button.Enabled = $False 870 | $Save_Button.Enabled = $True 871 | $Save_Button.Text = $Save_Button.TextPlural 872 | } 873 | } 874 | 875 | function GUI-Disable-RunCreate { 876 | $Run_Button.Enabled = $False 877 | $Save_Button.Enabled = $False 878 | } 879 | 880 | function GUI-Update-Languages { 881 | $Languages = Get-LELanguages -Directory $LELocation_TextBox.Text 882 | 883 | if ($Languages.Count -Eq 0) { 884 | # ComboBox.DataSource must at all times be a 885 | # System.Collections.ArrayList containing at least one PSObject. 886 | # Otherwise it will start displaying the elements incorrectly in the GUI. 887 | $empty_language = New-Object PSObject -Property @{Name=""; Guid=""} 888 | $Languages.Add($empty_language) | Out-Null 889 | $Lang_ComboBox.DataSource = $Languages 890 | $Lang_ComboBox.Enabled = $False 891 | return 892 | } 893 | 894 | $Lang_ComboBox.Enabled = $True 895 | 896 | $language_differences = Compare-Object -Property Name,Guid $Languages $Lang_ComboBox.DataSource 897 | $current_selection_name = $Lang_ComboBox.SelectedItem.Name 898 | 899 | if ($language_differences) { 900 | $Lang_ComboBox.DataSource = $Languages 901 | 902 | # If possible, change the selection to the same language as before 903 | $Language = $Lang_ComboBox.DataSource | Where { $_.Name -Eq $current_selection_name } | Select -First 1 904 | if ($Language) { $Lang_ComboBox.SelectedItem = $Language } 905 | } 906 | } 907 | 908 | function GUI-Disable-Languages { 909 | $Lang_ComboBox.Enabled = $False 910 | } 911 | 912 | function Save-ConfigFile { 913 | $Language = $Lang_ComboBox.SelectedItem.Name 914 | $DefaultSaveDirectory = $SaveDir_TextBox.Text 915 | $LELocation = $LELocation_TextBox.Text 916 | 917 | $Config = @" 918 | 919 | 920 | $Language 921 | $DefaultSaveDirectory 922 | $LELocation 923 | 924 | "@ 925 | 926 | $ConfigPath = $Files.Script.Config 927 | $Config | Out-File $ConfigPath 928 | } 929 | 930 | function Quit-Application { 931 | if ($SaveSettings_Checkbox.Checked) { 932 | Save-ConfigFile 933 | } elseif (Test-Path -LiteralPath $Files.Script.Config -PathType Leaf -ErrorAction SilentlyContinue) { 934 | Remove-Item -Path $Files.Script.Config 935 | } 936 | 937 | $Form.Close() 938 | } 939 | #ENDREGION GUI Functions } 940 | 941 | 942 | 943 | #REGION BEGIN GUI Events { 944 | # Here there be event code for the main window 945 | 946 | # Mouse drop support 947 | #NOTE: This requires that PowerShell was executed with: -Sta 948 | $Form.AllowDrop = $True 949 | $Form.Add_DragEnter({ $_.Effect = 'Copy' }) 950 | $Form.Add_DragDrop({ 951 | if ($_.Data.GetDataPresent([Windows.Forms.DataFormats]::Text)) { 952 | # If the dropped data is: plain text 953 | $Target_TextBox.Text = $_.Data.GetData([Windows.Forms.DataFormats]::Text) 954 | } elseif ($_.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop)) { 955 | # If the dropped data is: one or more files 956 | $Filenames = $_.Data.GetData([Windows.Forms.DataFormats]::FileDrop) 957 | 958 | if ($Filenames.Count -Eq 1) { 959 | $Target_TextBox.Text = $Filenames[0] 960 | } else { 961 | $Target_TextBox.Text = '"' + ($Filenames -Join '" "') + '"' 962 | } 963 | } 964 | }) 965 | 966 | 967 | $Target_TextBox.Add_TextChanged({ 968 | # Normalize targets text string 969 | $targets_text = $Target_TextBox.Text 970 | $targets_text = $targets_text.Trim() # Remove surrounding whitespaces 971 | 972 | # This script block is run if targets are deemed invalid 973 | $targets_invalid_scriptblock = { 974 | $Target_TextBox.Targets = @() 975 | $Target_TextBox.BackColor = $TextBoxColors.Invalid 976 | GUI-Disable-RunCreate 977 | } 978 | 979 | # If empty string 980 | if ($targets_text -Eq "") { 981 | Invoke-Command -ScriptBlock $targets_invalid_scriptblock 982 | $Target_TextBox.BackColor = $TextBoxColors.None # No error color on empty string 983 | return 984 | } 985 | 986 | # If NOT inside quotation marks 987 | if ($targets_text -Match '^[^"]' -And $targets_text -Match '[^"]$') { 988 | $targets_text = '"' + $targets_text + '"' 989 | } 990 | 991 | #TODO: Maybe this isn't needed? 992 | if ($targets_text -Eq '""') { 993 | Invoke-Command -ScriptBlock $targets_invalid_scriptblock 994 | return 995 | } 996 | 997 | # If uneven number of double quotes (") 998 | if ( ([Regex]::Matches($targets_text,'"')).Count % 2 -NE 0 ) { 999 | Invoke-Command -ScriptBlock $targets_invalid_scriptblock 1000 | return 1001 | } 1002 | 1003 | # Convert $targets_text to an array of strings 1004 | $Targets = [Regex]::Matches($targets_text,'"([^"])+"') | ForEach {$_.Value -Replace '"',''} | Select -Uniq 1005 | # Restore the array wrapping that PowerShell removes if it only contains one element 1006 | if ($Targets.GetType() -Eq [String]) { 1007 | $Targets = @($Targets) 1008 | } 1009 | 1010 | # Check that all targets can be found 1011 | ForEach ( $Target in $Targets ) { 1012 | $valid_path = Test-Path -LiteralPath $Target -PathType Leaf -ErrorAction SilentlyContinue 1013 | 1014 | if (-Not $valid_path) { 1015 | Invoke-Command -ScriptBlock $targets_invalid_scriptblock 1016 | return 1017 | } 1018 | } 1019 | 1020 | # Save targets 1021 | $Target_TextBox.FileBrowserInitialDirectoryFallback = $Targets[0] 1022 | $Target_TextBox.Targets = $Targets 1023 | 1024 | # Clear Background Color 1025 | $Target_TextBox.BackColor = $TextBoxColors.None 1026 | 1027 | # Try to activate the "Run" and "Create Shortcut" buttons 1028 | GUI-Update-RunCreate 1029 | }) 1030 | 1031 | 1032 | $Target_Button.Add_Click({ 1033 | $Filenames = Show-OpenFileDialog -InitialDirectory $Target_TextBox.Text ` 1034 | -InitialDirectoryFallback $Target_TextBox.FileBrowserInitialDirectoryFallback ` 1035 | -Filter "All files (*.*)| *.*|Applications (*.exe)| *.exe" ` 1036 | -FilterIndex 2 ` 1037 | -CheckFileExists ` 1038 | -Multiselect 1039 | if ($Filenames -NE $Null) { $Target_TextBox.Text = $Filenames } 1040 | }) 1041 | 1042 | 1043 | $Lang_Button_Edit.Add_Click({ 1044 | $Path = Join-Path $LELocation_TextBox.Text $Files.LE.Editor 1045 | 1046 | if (-Not (Test-Path $Path)) { 1047 | $Title = "Unable to start editor" 1048 | $Message = "The Locale Emulator GUI seems to be missing.$([Environment]::NewLine 1049 | )File not found:$([Environment]::NewLine 1050 | )$Path" 1051 | $Buttons = "OK" 1052 | $Icon = "Error" 1053 | [System.Windows.MessageBox]::Show($Message,$Title,$Buttons,$Icon) 1054 | return 1055 | } 1056 | 1057 | # Launch the LE Editor 1058 | #TODO: Prevent the GUI from being used until the LE Editor has exited 1059 | #$Form.Enabled = $False 1060 | $PS = New-Object System.Diagnostics.Process 1061 | $PS.StartInfo.Filename = $Path 1062 | $PS.Start() 1063 | while (-Not $PS.HasExited) { 1064 | [System.Windows.Forms.Application]::DoEvents() # IMPORTANT!!! 1065 | Start-Sleep -Milliseconds 100 1066 | } 1067 | #$Form.Enabled = $True 1068 | 1069 | GUI-Update-Languages 1070 | }) 1071 | 1072 | 1073 | $SaveDir_TextBox.Add_LostFocus({ 1074 | # Test-Path arguments '-ErrorAction SilentlyContinue' is ignored when: 1075 | # - using arguments '-PathType Container', OR 1076 | # - receiving an empty string ("") as path. 1077 | # Hence the try-catch. 1078 | $dir_exists = try { Test-Path -LiteralPath $SaveDir_TextBox.Text -PathType Container } catch { $False } 1079 | 1080 | if ($dir_exists) { 1081 | $SaveDir_TextBox.TextWas = $SaveDir_TextBox.Text 1082 | } else { 1083 | $SaveDir_TextBox.Text = $SaveDir_TextBox.TextWas 1084 | } 1085 | }) 1086 | 1087 | 1088 | $SaveDir_Button.Add_Click({ 1089 | $Directory = Show-FolderBrowserDialog -Description "Choose the default directory where to save shortcuts" ` 1090 | -ShowNewFolderButton $True ` 1091 | -InitialDirectory $SaveDir_TextBox.Text 1092 | if ($Directory -NE $Null) { $SaveDir_TextBox.Text = $Directory } 1093 | }) 1094 | 1095 | 1096 | $LELocation_TextBox.Add_TextChanged({ 1097 | $path = $LELocation_TextBox.Text 1098 | $path_was = $LELocation_TextBox.TextWas 1099 | $pre_verification_result = $LELocation_TextBox.NewValid 1100 | 1101 | $LELocation_TextBox.TextWas = $LELocation_TextBox.Text 1102 | $LELocation_TextBox.NewValid = $Null 1103 | 1104 | if (Equal-Paths $path $path_was) { 1105 | return 1106 | } 1107 | 1108 | if ($pre_verification_result -NE $Null) { 1109 | $path_is_valid = $pre_verification_result 1110 | } else { 1111 | $path_is_valid = Verify-LEDirectory $path 1112 | } 1113 | 1114 | if ($path_is_valid) { 1115 | $LELocation_TextBox.Valid = $True 1116 | $LELocation_TextBox.BackColor = $TextBoxColors.None 1117 | $Lang_Button_Edit.Enabled = Test-Path -LiteralPath (Join-Path $LELocation_TextBox.Text $Files.LE.Editor) 1118 | GUI-Update-LE 1119 | } else { 1120 | $LELocation_TextBox.Valid = $False 1121 | $LELocation_TextBox.BackColor = $TextBoxColors.Invalid 1122 | $Lang_Button_Edit.Enabled = $False 1123 | GUI-Disable-LE 1124 | } 1125 | }) 1126 | 1127 | 1128 | ## Select a folder containing Locale Emulator (LE) 1129 | # Normally show a Folder Browser Dialog. 1130 | # BUT: If one or more LE directories has been autodetected (that is not the 1131 | # same as $LELocation_TextBox.Text), then show a selection list instead. 1132 | $LELocation_Button.Add_Click({ 1133 | # Fetch a list of autodetected LE directories (as a list of strings) 1134 | $Directories = $LEDirectories | Where { $_.Valid } | Select -ExpandProperty Path 1135 | # Restore the array wrapping that PowerShell removes if an array only contains one element 1136 | if (-Not($Directories -Is [Array])) { 1137 | $Directories = @($Directories) 1138 | } 1139 | 1140 | $OnlyShowFolderBrowser = $Directories.Count -Eq 0 -Or ` 1141 | ($Directories.Count -Eq 1 -And (Equal-Paths $Directories[0] $LELocation_TextBox.Text)) 1142 | 1143 | # Sets the default selection in the Selection List dialog window 1144 | $DefaultSelection = $LELocation_TextBox.Text 1145 | 1146 | while ($True) { 1147 | # Show-LELocationDialog 1148 | if (-Not($OnlyShowFolderBrowser)) { 1149 | $Directory = Show-LELocationDialog $Directories $DefaultSelection 1150 | $DefaultSelection = $Null # Do not use more than once. 1151 | # The Dialog Window will remember the last selection used automatically. 1152 | 1153 | if ($Directory -Eq $Null) { break } # User cancelled 1154 | elseif ($Directory -NE "browse") { break } # User selected an LE directory 1155 | # User chose "browse": The execution continues 1156 | } 1157 | 1158 | # Show-FolderBrowserDialog 1159 | while ($True) { 1160 | $Directory = Show-FolderBrowserDialog -Description "Where is Locale Emulator?" ` 1161 | -initialDirectory $LELocation_TextBox.Text 1162 | 1163 | if ($Directory -Eq $Null) { break } # User cancelled 1164 | if (Verify-LEDirectory $Directory) { break } # User selected a valid LE directory 1165 | # User selected an invalid directory: Display error message and try again 1166 | $Title = "Locale Emulator not found" 1167 | $Message = "Locale Emulator was not found in the selected folder." 1168 | $Buttons = "OK" 1169 | $Icon = "Error" 1170 | [System.Windows.MessageBox]::Show($Message,$Title,$Buttons,$Icon) 1171 | } 1172 | 1173 | # If only FolderBrowserDialog is used: always break 1174 | # Else if user cancelled the FolderBrowserDialog: Return to LELocationDialog 1175 | if ($OnlyShowFolderBrowser -Or $Directory) { break } 1176 | } 1177 | 1178 | if (-Not($Directory)) { return } 1179 | 1180 | # Below code TRIGGERS: $LELocation_TextBox.Add_TextChanged() 1181 | $LELocation_TextBox.NewValid = $True 1182 | $LELocation_TextBox.Text = $Directory 1183 | }) 1184 | 1185 | 1186 | # Click this button to run the selected executable 1187 | $Run_Button.Add_Click({ 1188 | $LEProgram = Join-Path $LELocation_TextBox.Text $Files.LE.Runner 1189 | $LanguageID = $Lang_ComboBox.SelectedValue 1190 | $Target = $Target_TextBox.Targets[0] 1191 | $TargetArgs = $Args_TextBox.Text 1192 | 1193 | Start-Process -FilePath $LEProgram ` 1194 | -ArgumentList "-runas `"$LanguageID`" `"$Target`" $TargetArgs" 1195 | }) 1196 | 1197 | 1198 | # Click here to create a shortcut file to the executable 1199 | $Save_Button.Add_Click({ 1200 | $LEProgram = Join-Path $LELocation_TextBox.Text $Files.LE.Runner 1201 | $LanguageName = $Lang_ComboBox.SelectedItem.Name 1202 | $LanguageID = $Lang_ComboBox.SelectedValue 1203 | $TargetArgs = $Args_TextBox.Text 1204 | $DefaultSaveDir = $SaveDir_TextBox.Text 1205 | 1206 | if ($Target_TextBox.Targets.Count -Eq 1) { 1207 | # One Target # 1208 | # Let the user decide both the directory and the filename. 1209 | $Target = $Target_TextBox.Targets[0] 1210 | 1211 | # Generate a filename as a suggestion to the user 1212 | $InitialFilename = [IO.Path]::GetFileNameWithoutExtension($Target) 1213 | $InitialFilename = "(LE)" + $InitialFilename 1214 | 1215 | $ShortcutFilePath = Show-SaveFileDialog -InitialFilename $InitialFilename ` 1216 | -InitialDirectory $DefaultSaveDir ` 1217 | -InitialDirectoryFallback [Environment]::GetFolderPath("Desktop") ` 1218 | -Filter "Shortcuts| *.lnk" 1219 | 1220 | if (-Not $ShortcutFilePath) { 1221 | return 1222 | } 1223 | 1224 | if (-Not ($ShortcutFilePath -Match '\.lnk$')) { 1225 | $ShortcutFilePath += '.lnk' 1226 | } 1227 | 1228 | Create-LEShortcutFile -Target $Target ` 1229 | -Args $TargetArgs ` 1230 | -LEPath $LEProgram ` 1231 | -LangID $LanguageID ` 1232 | -Path $ShortcutFilePath 1233 | } else { 1234 | # Multiple Targets # 1235 | # Only let the user decide the directory. Filenames are automatically generated. 1236 | $Targets = $Target_TextBox.Targets 1237 | 1238 | $ShortcutDirectory = Show-FolderBrowserDialog -Description "Select the directory to where you want to save the shortcut files." ` 1239 | -initialDirectory $DefaultSaveDir 1240 | 1241 | if (-Not $ShortcutDirectory) { 1242 | return 1243 | } 1244 | 1245 | ForEach ($Target in $Targets) { 1246 | $ShortcutFilename = [IO.Path]::GetFileNameWithoutExtension($Target) 1247 | $ShortcutFilename = "(LE)" + $ShortcutFilename + ".lnk" 1248 | 1249 | $ShortcutFilePath = Join-Path $ShortcutDirectory $ShortcutFilename 1250 | 1251 | Create-LEShortcutFile -Target $Target ` 1252 | -Args $TargetArgs ` 1253 | -LEPath $LEProgram ` 1254 | -LangName $LanguageName ` 1255 | -LangID $LanguageID ` 1256 | -Path $ShortcutFilePath 1257 | } 1258 | } 1259 | }) 1260 | 1261 | 1262 | # Escape key quits application 1263 | ForEach ($control in $controls) { 1264 | $control.Add_KeyDown({ 1265 | if ($_.KeyCode -Eq "Escape") { 1266 | Quit-Application 1267 | } 1268 | }) 1269 | } 1270 | 1271 | # Quit button quits application 1272 | $Quit_Button.Add_Click({ 1273 | Quit-Application 1274 | }) 1275 | #ENDREGION GUI Events } 1276 | 1277 | 1278 | 1279 | #REGION BEGIN GUI LELocations Events { 1280 | # Here there be event code for the LELocation window 1281 | # For more info, check out: $LELocation_Button.Add_Click 1282 | 1283 | $Form_LELocations.Add_Shown({ 1284 | $LELocations_ListBox.LastSelection = $LELocations_ListBox.SelectedValue 1285 | $LELocations_ListBox.Focus() # Give default focus to the ListBox 1286 | }) 1287 | 1288 | $LELocations_ListBox.Add_SelectedIndexChanged({ 1289 | $LELocations_ListBox.LastSelection = $LELocations_ListBox.SelectedValue 1290 | }) 1291 | 1292 | # Escape key clicks the Cancel button 1293 | ForEach ($lelocation_control in $lelocation_controls) { 1294 | $lelocation_control.Add_KeyDown({ 1295 | if ($_.KeyCode -Eq "Escape") { 1296 | $LELocations_ButtonCancel.PerformClick() 1297 | } 1298 | }) 1299 | } 1300 | 1301 | # Enter Key clicks the Select button (only if ListBox is in focus) 1302 | $LELocations_ListBox.Add_KeyDown({ 1303 | if ($_.KeyCode -Eq "Enter") { 1304 | $LELocations_ButtonSelect.PerformClick() 1305 | } 1306 | }) 1307 | 1308 | $LELocations_ButtonCancel.Add_Click({ 1309 | $Form_LELocations.ButtonAction = "Cancel" 1310 | $Form_LELocations.Close() 1311 | }) 1312 | 1313 | $LELocations_ButtonBrowse.Add_Click({ 1314 | $Form_LELocations.ButtonAction = "Browse" 1315 | $Form_LELocations.Close() 1316 | }) 1317 | 1318 | $LELocations_ButtonSelect.Add_Click({ 1319 | $Form_LELocations.ButtonAction = "Select" 1320 | $Form_LELocations.Close() 1321 | }) 1322 | #ENDREGION GUI LELocations Events } 1323 | 1324 | 1325 | 1326 | #REGION BEGIN GUI Show { 1327 | # Here we finally initialize the main window and display it 1328 | 1329 | $Config = Get-ConfigFile 1330 | 1331 | if ($Config) { 1332 | $SaveSettings_Checkbox.Checked = $True 1333 | $LEDirectories = Get-LEDirectories -ConfigLEDirectory $Config.LELocation 1334 | 1335 | $dir_exists = try { Test-Path -LiteralPath $Config.DefaultSaveDirectory -PathType Container } catch { $False } 1336 | if ($dir_exists) { $SaveDir_TextBox.text = $Config.DefaultSaveDirectory } 1337 | } else { 1338 | $SaveSettings_Checkbox.Checked = $False 1339 | $LEDirectories = Get-LEDirectories 1340 | } 1341 | 1342 | if ($LEDirectories.Count -GT 0) { 1343 | $LEDirectory = $LEDirectories[0] 1344 | # Below code TRIGGERS: $LELocation_TextBox.Add_TextChanged() 1345 | $LELocation_TextBox.NewValid = $LEDirectory.Valid 1346 | $LELocation_TextBox.Text = $LEDirectory.Path 1347 | } 1348 | 1349 | if ($Config) { 1350 | #NOTE: $Lang_ComboBox.DataSource was maybe populated by triggering $LELocation_TextBox.Add_TextChanged() above 1351 | # If possible, change the selection to the same language as in the config 1352 | $Language = $Lang_ComboBox.DataSource | Where { $_.Name -Eq $Config.Language } | Select -First 1 1353 | if ($Language) { $Lang_ComboBox.SelectedItem = $Language } 1354 | } 1355 | 1356 | # Show Application Window 1357 | #[void]$Form.ShowDialog() 1358 | [System.Windows.Forms.Application]::Run($Form) 1359 | #ENDREGION GUI Show } 1360 | 1361 | #ENDREGION GUI } 1362 | 1363 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Initial creator and author 2 | Name: Svintoo 3 | Date: 2018-10-11 4 | URL: https://github.com/Svintooo/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LE Shortcut Creator 2 | Create shortcuts for games/applications that will run them through Locale Emulator. 3 | 4 | Written in PowerShell (don't let the \*.cmd extention fool you). 5 | Compatible with Windows 7, Windows 8.1, Windows 10, and Windows 11. 6 | 7 |

Windows 7Windows 8.1Windows 10Windows 11

8 | 9 | ## Features 10 | - File drag'n'drop: Drop files on the user interface. 11 | - Batch mode: Add multiple files at once and create shortcut files for all of them. 12 | - Non-GUI mode: Give the script a list of files as parameters and shortcut files will be created without any user interaction. 13 | - Config file: Can remember last used settings between executions. 14 | - Launcher: Can be used to directly launch applications through Locale Emulator without creating a shortcut file first. 15 | 16 | ## Install Instructions 17 | Download `LEShortcutCreator.cmd` from the [releases page](https://github.com/Svintooo/LEShortcutCreator/releases). 18 | 19 | If Locale Emulator is installed it will autodetect its location. Alternatively 20 | it can autodetect an uninstalled Locale Emulator if `LEShortcutCreator.cmd` is 21 | placed in the same directory. 22 | 23 | **IMPORTANT:** Make sure to at least once run `LEInstaller.exe` from Locale Emulator (even if you do not want to have it installed). This creates some necessary dll-files that Locale Emulator needs to function. 24 | 25 | ## Usage Instructions 26 | Either double click `LEShortcutCreator.cmd` to get a Graphical User Interface. 27 | The interface will let you create shortcuts that uses Locale Emulator. 28 | Also supports file drag'n'drop. 29 | 30 | OR drag'n'drop files onto the `LEShortcutCreator.cmd` file directly to create 31 | shortcut files automatically without any graphical interface interaction. These 32 | shortcut files will be created in the same directory as the script file. 33 | 34 | 35 | ## Locale Emulator 36 | A software that can run applications (that has no Unicode support) 37 | with a different locale than the systems default. 38 | It is usually used to run Japanese Games on non-Japanese versions of 39 | Windows. 40 | 41 | Excellent software, highly recommended. 42 | URL: https://pooi.moe/Locale-Emulator/ 43 | 44 | 45 | ## This Script 46 | Creates shortcut files for applications that will run them through 47 | Locale Emulator. 48 | 49 | 50 | ## Why? 51 | Shortcut files created by Locale Emulator itself each require their own 52 | config file. This config file is created simultaneously and stored 53 | in the targeted applications install directory. 54 | 55 | This script creates shortcut files that can use Locale Emulator 56 | without these extra config files. They instead uses Locale Emulator's 57 | global config file inside Locale Emulator's install directory (`LEConfig.xml`). 58 | 59 | 60 | ## PowerShell in a \*.cmd file? 61 | Yes. The script has a header written in BATCH, but everything else in the file is PowerShell code. 62 | The only thing the BATCH code does is to execute the rest of the file as a PowerShell script. 63 | 64 | I want the script to be executed by double clicking it. 65 | PowerShell files (\*.ps1) does not allow double clicking for security reasons. 66 | But BATCH files (\*.bat, \*.cmd) does not have this restriction. 67 | So by wrapping the PowerShell code in a BATCH script I circumvent this restriction. 68 | 69 | 70 | ## Common Problems 71 | 72 | ### The "Locale Emulator location" text field is automatically filled on start. But the text field is pink, and the "Edit List" button is greyed out 73 | **Solution:** Either reinstall Locale Emulator (run `LEInstaller.exe`), or put the `LEShortcutCreator.cmd` file in the same directory as Locale Emulator. 74 | **Reason:** This can happen if Locale Emulator was once installed, and later the Locale Emulator files were moved/deleted without uninstalling it first. The path to Locale Emulator then still exists in the Windows Registry, which is read by LE Shortcut Creator. 75 | 76 | ### No languages are listed in the dropdown menu 77 | **Solution:** Click the "Edit List" button. This will start the Locale Emulator GUI. 78 | Just close this GUI (you do not need to use it). 79 | The default languages should now show up in the dropdown menu. 80 | **Reason:** The Locale Emulator file `LEConfig.xml` is not included in the download. But a default config is created after running the GUI the first time. 81 | 82 | ### Clicking the "Edit List" button gives a weird error message "Could not load file or assembly..." 83 | **Solution:** Just run and immediately close `LEInstaller.exe` from Locale Emulator folder (you do not need to perform the actual install). 84 | **Reason:** Locale Emulator needs some dll-files to function that is not included in the download. Instead, these dll-files are created when running `LEInstaller.exe` for the first time. 85 | -------------------------------------------------------------------------------- /screenshot-win10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Svintooo/LEShortcutCreator/9705e69a4cac751e4bb2180f19aa5e229417af73/screenshot-win10.png -------------------------------------------------------------------------------- /screenshot-win11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Svintooo/LEShortcutCreator/9705e69a4cac751e4bb2180f19aa5e229417af73/screenshot-win11.png -------------------------------------------------------------------------------- /screenshot-win7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Svintooo/LEShortcutCreator/9705e69a4cac751e4bb2180f19aa5e229417af73/screenshot-win7.png -------------------------------------------------------------------------------- /screenshot-win81.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Svintooo/LEShortcutCreator/9705e69a4cac751e4bb2180f19aa5e229417af73/screenshot-win81.png --------------------------------------------------------------------------------