2375 lines
90 KiB
AutoIt
2375 lines
90 KiB
AutoIt
#NoTrayIcon
|
|
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
|
|
#AutoIt3Wrapper_Icon=enderal.ico
|
|
#AutoIt3Wrapper_Res_File_Add=background.jpg, RT_RCDATA, BACKGROUND
|
|
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
|
|
|
|
#include <GUIConstantsEx.au3>
|
|
#include <WindowsConstants.au3>
|
|
#include <GDIPlus.au3>
|
|
#include <ButtonConstants.au3>
|
|
#include <StaticConstants.au3>
|
|
#include <ComboConstants.au3>
|
|
#include <ListViewConstants.au3>
|
|
#include <GuiListView.au3>
|
|
#include <GuiImageList.au3>
|
|
#include <FileConstants.au3>
|
|
#include <StructureConstants.au3>
|
|
#include <WindowsNotifsConstants.au3>
|
|
#include <WindowsSysColorConstants.au3>
|
|
#include <APIThemeConstants.au3>
|
|
#include <Array.au3>
|
|
#include <WinAPI.au3>
|
|
#include <WinAPITheme.au3>
|
|
#include <WinAPIGdiDC.au3>
|
|
#include <WinAPIProc.au3>
|
|
#include <SendMessage.au3>
|
|
|
|
; ============================================
|
|
; Constants and Global Variables
|
|
; ============================================
|
|
Global Const $LAUNCHER_WIDTH = 945
|
|
Global Const $LAUNCHER_HEIGHT = 578
|
|
Global Const $SETTINGS_MARGIN = 50
|
|
Global Const $SETTINGS_WIDTH = $LAUNCHER_WIDTH - ($SETTINGS_MARGIN * 2)
|
|
Global Const $SETTINGS_HEIGHT = $LAUNCHER_HEIGHT - ($SETTINGS_MARGIN * 2)
|
|
Global Const $TAB_LEFT_MARGIN = 42
|
|
Global Const $TAB_TOP_MARGIN = 74
|
|
Global Const $TAB_ROW_HEIGHT = 42
|
|
Global Const $LOCALAPPDATA = EnvGet("LOCALAPPDATA")
|
|
Global Const $REG_KEY = "HKEY_CURRENT_USER\Software\SureAI\EnderalSE"
|
|
Global Const $REG_INSTALL_PATH = "Install_Path"
|
|
Global Const $REG_EXE_PATH = "Exe_Path"
|
|
Global Const $REG_START_STEAM = "Start_Steam"
|
|
Global Const $AA_TAA = "TAA (Best quality)"
|
|
Global Const $AA_FXAA = "FXAA (Low)"
|
|
Global Const $AA_OFF = "Off (Best performance)"
|
|
; Plugin load order classes - the engine always loads masters before regular plugins
|
|
Global Const $PLUGIN_CLASS_MASTER = 1
|
|
Global Const $PLUGIN_CLASS_NORMAL = 2
|
|
Global Const $PLUGIN_FLAG_MASTER = 0x00000001
|
|
Global Const $PLUGIN_FLAG_LIGHT = 0x00000200
|
|
; Check box images of the plugin list (state image indexes)
|
|
Global Const $PLUGIN_STATE_UNCHECKED = 1
|
|
Global Const $PLUGIN_STATE_CHECKED = 2
|
|
Global Const $PLUGIN_STATE_UNCHECKED_DIMMED = 3
|
|
Global Const $PLUGIN_STATE_CHECKED_DIMMED = 4
|
|
; Theme parts used to draw them (BUTTON class)
|
|
Global Const $THEME_BP_CHECKBOX = 3
|
|
Global Const $THEME_CB_UNCHECKED = 1
|
|
Global Const $THEME_CB_UNCHECKED_DISABLED = 4
|
|
Global Const $THEME_CB_CHECKED = 5
|
|
Global Const $THEME_CB_CHECKED_DISABLED = 8
|
|
Global $PLUGINS_DIR = ""
|
|
Global $PLUGINS_FILE = ""
|
|
Global $DOCUMENTS_DIR = ""
|
|
Global $ENDERAL_INI = ""
|
|
Global $ENDERAL_PREFS_INI = ""
|
|
Global $LAUNCHER_DIR = @ScriptDir
|
|
|
|
; GUI handles
|
|
Global $hMainGUI, $hSettingsGUI
|
|
Global $btnPlay, $btnSettings, $btnExit
|
|
Global $btnClose, $btnMinimize
|
|
Global $lblCredits, $lblWiki, $lblSupport, $lblChangelog
|
|
Global $lblMinimizeShadow, $lblCloseShadow
|
|
Global $lblCreditsShadow, $lblWikiShadow, $lblSupportShadow, $lblChangelogShadow
|
|
Global $hBackgroundPic = 0
|
|
|
|
; Settings controls
|
|
Global $tabSettings
|
|
Global $btnSave, $btnCancel
|
|
|
|
; Display tab controls
|
|
Global $lblResolution, $cmbResolution
|
|
Global $lblAntialiasing, $cmbAntialiasing
|
|
Global $chkWindowed
|
|
Global $btnLow, $btnMedium, $btnHigh, $btnUltra
|
|
|
|
; Rendering tab controls
|
|
Global $chk64bitRT, $chkSSAO, $chkVolumetricLighting, $chkSSR
|
|
|
|
; SSE Display Tweaks controls
|
|
Global $grpSSEDisplayTweaks, $chkSSEDisplayTweaks
|
|
Global $lblFramerateLimit, $sldFramerateLimit, $lblFramerateLimitValue
|
|
|
|
; Controls tab controls
|
|
Global $chkController, $chkVibration
|
|
Global $chkStartSteam
|
|
Global $grpIniFiles, $btnOpenEnderalIni, $btnOpenEnderalPrefsIni, $btnOpenCustomIni, $btnOpenModIni, $btnOpenSavegames
|
|
|
|
; Plugins tab controls
|
|
Global $lvPlugins, $hLvPlugins = 0
|
|
Global $btnPluginUp, $btnPluginDown, $btnPluginsEnableAll, $btnPluginsDisableAll
|
|
Global $lblPluginsCount, $lblPluginsHint
|
|
Global $hPluginStateImages = 0
|
|
|
|
; Plugin list state
|
|
; [i][0] = file name, [i][1] = enabled, [i][2] = locked (always loaded by the game),
|
|
; [i][3] = load order class, [i][4] = type label, [i][5] = present in plugins.txt
|
|
Global $aPluginList[1][6]
|
|
Global $iPluginListCount = 0
|
|
Global $bPluginListLoaded = False
|
|
Global $bPluginListDirty = False
|
|
Global $bPluginListUpdating = False
|
|
Global $bPluginListReadOnly = False
|
|
|
|
; Steam version detection
|
|
Global $bIsSteamVersion = False
|
|
|
|
; Mod Organizer 2 directory, empty when the launcher was not started by MO2
|
|
Global $MO2_DIR = ""
|
|
|
|
; ============================================
|
|
; Helper Functions
|
|
; ============================================
|
|
Func DirExists($path)
|
|
Return FileExists($path) And StringInStr(FileGetAttrib($path), "D")
|
|
EndFunc
|
|
|
|
Func SetCheckboxState($ctrl, $value, $checkedValue = "1")
|
|
If $value = $checkedValue Then
|
|
GUICtrlSetState($ctrl, $GUI_CHECKED)
|
|
Else
|
|
GUICtrlSetState($ctrl, $GUI_UNCHECKED)
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func GetIniFileName($filePath)
|
|
Return StringRegExpReplace($filePath, ".*\\", "")
|
|
EndFunc
|
|
|
|
Func GetCustomIniPath()
|
|
; Returns the path to EnderalCustom.ini or SkyrimCustom.ini based on current INI mode
|
|
Local $iniFileName = GetIniFileName($ENDERAL_INI)
|
|
If StringInStr($iniFileName, "Enderal") Then
|
|
Return $DOCUMENTS_DIR & "\EnderalCustom.ini"
|
|
Else
|
|
Return $DOCUMENTS_DIR & "\SkyrimCustom.ini"
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func GetCustomIniFileName()
|
|
; Returns just the filename (EnderalCustom.ini or SkyrimCustom.ini)
|
|
Local $iniFileName = GetIniFileName($ENDERAL_INI)
|
|
If StringInStr($iniFileName, "Enderal") Then
|
|
Return "EnderalCustom.ini"
|
|
Else
|
|
Return "SkyrimCustom.ini"
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func GetModIniFileName()
|
|
; Returns just the filename for the Mod INI
|
|
Return "Enderal - Forgotten Stories.ini"
|
|
EndFunc
|
|
|
|
Func GetDataIniPath()
|
|
; Returns the path to Data\Enderal - Forgotten Stories.ini
|
|
Return $LAUNCHER_DIR & "\Data\" & GetModIniFileName()
|
|
EndFunc
|
|
|
|
Func IniReadWithCustom($section, $key, $default)
|
|
; Read with precedence: Data INI > Custom INI > Base INI
|
|
; 1. Check Data\Enderal - Forgotten Stories.ini first (highest priority)
|
|
Local $dataIniPath = GetDataIniPath()
|
|
If FileExists($dataIniPath) Then
|
|
Local $dataValue = IniRead($dataIniPath, $section, $key, "")
|
|
If $dataValue <> "" Then
|
|
Return $dataValue
|
|
EndIf
|
|
EndIf
|
|
|
|
; 2. Check custom INI (EnderalCustom.ini or SkyrimCustom.ini)
|
|
Local $customIniPath = GetCustomIniPath()
|
|
If FileExists($customIniPath) Then
|
|
Local $customValue = IniRead($customIniPath, $section, $key, "")
|
|
If $customValue <> "" Then
|
|
Return $customValue
|
|
EndIf
|
|
EndIf
|
|
|
|
; 3. Fall back to base INI
|
|
Return IniRead($ENDERAL_INI, $section, $key, $default)
|
|
EndFunc
|
|
|
|
Func IniWriteWithCustom($section, $key, $value)
|
|
; Write with precedence: Data INI > Custom INI > Base INI
|
|
; 1. Check Data\Enderal - Forgotten Stories.ini first (highest priority)
|
|
Local $dataIniPath = GetDataIniPath()
|
|
If FileExists($dataIniPath) Then
|
|
Local $dataValue = IniRead($dataIniPath, $section, $key, "")
|
|
If $dataValue <> "" Then
|
|
Return IniWrite($dataIniPath, $section, $key, $value)
|
|
EndIf
|
|
EndIf
|
|
|
|
; 2. Check custom INI (EnderalCustom.ini or SkyrimCustom.ini)
|
|
Local $customIniPath = GetCustomIniPath()
|
|
If FileExists($customIniPath) Then
|
|
Local $customValue = IniRead($customIniPath, $section, $key, "")
|
|
If $customValue <> "" Then
|
|
Return IniWrite($customIniPath, $section, $key, $value)
|
|
EndIf
|
|
EndIf
|
|
|
|
; 3. Fall back to base INI
|
|
Return IniWrite($ENDERAL_INI, $section, $key, $value)
|
|
EndFunc
|
|
|
|
Func UpdateCustomIniButtonState()
|
|
; Enable or disable the custom INI button based on file existence
|
|
Local $customIniPath = GetCustomIniPath()
|
|
Local $customIniName = GetCustomIniFileName()
|
|
If FileExists($customIniPath) Then
|
|
GUICtrlSetData($btnOpenCustomIni, $customIniName)
|
|
GUICtrlSetState($btnOpenCustomIni, $GUI_ENABLE)
|
|
Else
|
|
GUICtrlSetData($btnOpenCustomIni, $customIniName & " (N/A)")
|
|
GUICtrlSetState($btnOpenCustomIni, $GUI_DISABLE)
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func UpdateModIniButtonState()
|
|
; Enable or disable the Mod INI button based on file existence
|
|
Local $modIniPath = GetDataIniPath()
|
|
Local $modIniName = GetModIniFileName()
|
|
If FileExists($modIniPath) Then
|
|
GUICtrlSetData($btnOpenModIni, $modIniName)
|
|
GUICtrlSetState($btnOpenModIni, $GUI_ENABLE)
|
|
Else
|
|
GUICtrlSetData($btnOpenModIni, $modIniName & " (N/A)")
|
|
GUICtrlSetState($btnOpenModIni, $GUI_DISABLE)
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func RunWithAssociation($filePath)
|
|
; Get file extension
|
|
Local $ext = StringRegExpReplace($filePath, ".*(\.[^.]+)$", "$1")
|
|
If $ext = $filePath Then Return False
|
|
|
|
; Get file type from registry
|
|
Local $fileType = RegRead("HKEY_CLASSES_ROOT\" & $ext, "")
|
|
If @error Or $fileType = "" Then
|
|
; Fallback to notepad if no association found
|
|
Run('notepad.exe "' & $filePath & '"')
|
|
Return True
|
|
EndIf
|
|
|
|
; Get command for opening this file type
|
|
Local $command = RegRead("HKEY_CLASSES_ROOT\" & $fileType & "\shell\open\command", "")
|
|
If @error Or $command = "" Then
|
|
; Fallback to notepad if no command found
|
|
Run('notepad.exe "' & $filePath & '"')
|
|
Return True
|
|
EndIf
|
|
|
|
; Replace %1 or %L with the file path, handle quoted and unquoted variants
|
|
If StringInStr($command, "%1") Or StringInStr($command, "%L") Then
|
|
$command = StringReplace($command, '"%1"', '"' & $filePath & '"')
|
|
$command = StringReplace($command, "%1", '"' & $filePath & '"')
|
|
$command = StringReplace($command, '"%L"', '"' & $filePath & '"')
|
|
$command = StringReplace($command, "%L", '"' & $filePath & '"')
|
|
Else
|
|
; Append file path if no placeholder
|
|
$command = $command & ' "' & $filePath & '"'
|
|
EndIf
|
|
|
|
Run($command)
|
|
Return True
|
|
EndFunc
|
|
|
|
Func WriteCheckboxToIni($ctrl, $file, $section, $key, $checkedVal = "1", $uncheckedVal = "0")
|
|
If BitAND(GUICtrlRead($ctrl), $GUI_CHECKED) Then
|
|
IniWrite($file, $section, $key, $checkedVal)
|
|
Else
|
|
IniWrite($file, $section, $key, $uncheckedVal)
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func WriteCheckboxWithCustom($ctrl, $section, $key, $checkedVal = "1", $uncheckedVal = "0")
|
|
; Write to custom INI if key exists there, otherwise to base INI ($ENDERAL_INI)
|
|
If BitAND(GUICtrlRead($ctrl), $GUI_CHECKED) Then
|
|
IniWriteWithCustom($section, $key, $checkedVal)
|
|
Else
|
|
IniWriteWithCustom($section, $key, $uncheckedVal)
|
|
EndIf
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; DPI Awareness Functions
|
|
; ============================================
|
|
Func SetDPIAwareness()
|
|
; Set DPI awareness for SkyrimSE.exe to prevent Windows scaling issues
|
|
Local $exePath = $LAUNCHER_DIR & "\SkyrimSE.exe"
|
|
If Not FileExists($exePath) Then Return
|
|
|
|
Local $regKey = "HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
|
|
RegWrite($regKey, $exePath, "REG_SZ", "~ HIGHDPIAWARE")
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Version Detection Functions
|
|
; ============================================
|
|
Func GetSkyrimVersion()
|
|
Local $exePath = $LAUNCHER_DIR & "\SkyrimSE.exe"
|
|
If Not FileExists($exePath) Then Return ""
|
|
|
|
; Try FileVersion and ProductVersion first (older versions use these)
|
|
Local $version = FileGetVersion($exePath, "FileVersion")
|
|
If @error Or $version = "" Or $version = "1.0.0.0" Then
|
|
$version = FileGetVersion($exePath, "ProductVersion")
|
|
EndIf
|
|
; Fall back to standard version
|
|
If @error Or $version = "" Or $version = "1.0.0.0" Then
|
|
$version = FileGetVersion($exePath)
|
|
EndIf
|
|
If @error Or $version = "" Or $version = "1.0.0.0" Then Return ""
|
|
|
|
; Return only major.minor.build (strip revision)
|
|
Local $parts = StringSplit($version, ".")
|
|
If $parts[0] >= 3 Then
|
|
Return $parts[1] & "." & $parts[2] & "." & $parts[3]
|
|
EndIf
|
|
Return $version
|
|
EndFunc
|
|
|
|
Func GetBaseDirectoryName()
|
|
; Returns "Skyrim Special Edition GOG" if Galaxy64.dll exists, otherwise "Skyrim Special Edition"
|
|
If FileExists($LAUNCHER_DIR & "\Galaxy64.dll") Then
|
|
Return "Skyrim Special Edition GOG"
|
|
Else
|
|
Return "Skyrim Special Edition"
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func GetEnderalBaseDirectoryName()
|
|
; Returns "Enderal Special Edition GOG" if Galaxy64.dll exists, otherwise "Enderal Special Edition"
|
|
If FileExists($LAUNCHER_DIR & "\Galaxy64.dll") Then
|
|
Return "Enderal Special Edition GOG"
|
|
Else
|
|
Return "Enderal Special Edition"
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func GetEnderalVersion()
|
|
; First try to read from EnderalVersion.ini (no section header, just key=value)
|
|
Local $versionIni = $LAUNCHER_DIR & "\Data\SKSE\Plugins\EnderalVersion.ini"
|
|
If FileExists($versionIni) Then
|
|
Local $content = FileRead($versionIni)
|
|
; Match line starting with "version" followed by "=" and capture until end of line
|
|
Local $match = StringRegExp($content, "(?im)^version\s*=\s*(.+)$", 1)
|
|
If Not @error And UBound($match) > 0 Then
|
|
Return StringStripWS($match[0], 3)
|
|
EndIf
|
|
EndIf
|
|
|
|
; Check if Enderal ESM exists (outdated installation)
|
|
Local $esmPath = $LAUNCHER_DIR & "\Data\Enderal - Forgotten Stories.esm"
|
|
If FileExists($esmPath) Then Return "outdated"
|
|
|
|
; Neither file exists
|
|
Return ""
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Update Function
|
|
; ============================================
|
|
Func CheckAndApplyUpdate()
|
|
; Check if update is needed: SkyrimSE.exe is not 1.6.659 AND skse64_1_6_659.dll exists
|
|
Local $skyrimVersion = GetSkyrimVersion()
|
|
Local $oldSkseDll = $LAUNCHER_DIR & "\skse64_1_6_659.dll"
|
|
|
|
If $skyrimVersion <> "1.6.659" And FileExists($oldSkseDll) Then
|
|
; Files to delete unconditionally
|
|
Local $filesToDelete[] = [ _
|
|
"skse64_1_6_659.dll", _
|
|
"tbb.dll", _
|
|
"tbbmalloc.dll", _
|
|
"enderal readme.txt", _
|
|
"Skyrim_Default.ini", _
|
|
"Data\Skyrim - Patch.bsa", _
|
|
"Data\Enderal SE v2.0.12.4 Changelog.txt", _
|
|
"Data\SKSE\Plugins\JContainers64.dll", _
|
|
"Data\SKSE\Plugins\YesImSure.json", _
|
|
"Data\meshes\actors\character\character assets\skeleton.xml", _
|
|
"Data\meshes\actors\character\character assets female\skeleton_female.xml", _
|
|
"Data\SkyUI_SE.ini" _
|
|
]
|
|
|
|
; Files to delete only if size matches [path, size]
|
|
Local $filesToDeleteWithSize[][2] = [ _
|
|
["Data\Interface\controls\pc\controlmap.txt", 8492], _
|
|
["Data\Interface\exported\widgets\skyui\followerpanel.swf", 15541], _
|
|
["Data\Interface\dialoguemenu.swf", 24622], _
|
|
["Data\Interface\fonts_en2.swf", 33021], _
|
|
["Data\Interface\hudmenu.swf", 372484], _
|
|
["Data\Interface\startmenu.swf", 66683], _
|
|
["Data\Interface\fontconfig.txt", 1211], _
|
|
["Data\Scripts\ActiveMagicEffect.pex", 7497], _
|
|
["Data\Scripts\Actor.pex", 14869], _
|
|
["Data\Scripts\ActorBase.pex", 2643], _
|
|
["Data\Scripts\ActorValueInfo.pex", 1791], _
|
|
["Data\Scripts\Alias.pex", 3949], _
|
|
["Data\Scripts\Ammo.pex", 549], _
|
|
["Data\Scripts\Apparatus.pex", 522], _
|
|
["Data\Scripts\Armor.pex", 4983], _
|
|
["Data\Scripts\ArmorAddon.pex", 1169], _
|
|
["Data\Scripts\Art.pex", 500], _
|
|
["Data\Scripts\Book.pex", 574], _
|
|
["Data\Scripts\Camera.pex", 1171], _
|
|
["Data\Scripts\Cell.pex", 1264], _
|
|
["Data\Scripts\ColorComponent.pex", 1206], _
|
|
["Data\Scripts\ColorForm.pex", 1124], _
|
|
["Data\Scripts\CombatStyle.pex", 3260], _
|
|
["Data\Scripts\ConstructibleObject.pex", 1047], _
|
|
["Data\Scripts\DefaultObjectManager.pex", 540], _
|
|
["Data\Scripts\Enchantment.pex", 1149], _
|
|
["Data\Scripts\EquipSlot.pex", 515], _
|
|
["Data\Scripts\Faction.pex", 3819], _
|
|
["Data\Scripts\Flora.pex", 651], _
|
|
["Data\Scripts\Form.pex", 5057], _
|
|
["Data\Scripts\FormList.pex", 813], _
|
|
["Data\Scripts\FormType.pex", 7963], _
|
|
["Data\Scripts\GameData.pex", 1898], _
|
|
["Data\Scripts\HeadPart.pex", 1274], _
|
|
["Data\Scripts\Ingredient.pex", 1369], _
|
|
["Data\Scripts\Input.pex", 968], _
|
|
["Data\Scripts\JArray.pex", 3575], _
|
|
["Data\Scripts\JAtomic.pex", 1973], _
|
|
["Data\Scripts\JContainers.pex", 1184], _
|
|
["Data\Scripts\JContainers_DomainExample.pex", 14101], _
|
|
["Data\Scripts\JDB.pex", 1373], _
|
|
["Data\Scripts\JFormDB.pex", 1933], _
|
|
["Data\Scripts\JFormMap.pex", 1675], _
|
|
["Data\Scripts\JIntMap.pex", 1672], _
|
|
["Data\Scripts\JLua.pex", 1162], _
|
|
["Data\Scripts\JMap.pex", 1669], _
|
|
["Data\Scripts\JString.pex", 944], _
|
|
["Data\Scripts\JValue.pex", 2589], _
|
|
["Data\Scripts\Keyword.pex", 714], _
|
|
["Data\Scripts\LeveledActor.pex", 805], _
|
|
["Data\Scripts\LeveledItem.pex", 1022], _
|
|
["Data\Scripts\LeveledSpell.pex", 802], _
|
|
["Data\Scripts\Location.pex", 1744], _
|
|
["Data\Scripts\MagicEffect.pex", 2501], _
|
|
["Data\Scripts\Math.pex", 1436], _
|
|
["Data\Scripts\ModEvent.pex", 952], _
|
|
["Data\Scripts\NetImmerse.pex", 2929], _
|
|
["Data\Scripts\ObjectReference.pex", 16307], _
|
|
["Data\Scripts\Outfit.pex", 505], _
|
|
["Data\Scripts\Perk.pex", 1431], _
|
|
["Data\Scripts\Potion.pex", 1298], _
|
|
["Data\Scripts\Quest.pex", 5314], _
|
|
["Data\Scripts\Race.pex", 6618], _
|
|
["Data\Scripts\Scroll.pex", 1387], _
|
|
["Data\Scripts\Shout.pex", 766], _
|
|
["Data\Scripts\SKSE.pex", 845], _
|
|
["Data\Scripts\SoulGem.pex", 506], _
|
|
["Data\Scripts\Sound.pex", 723], _
|
|
["Data\Scripts\SoundDescriptor.pex", 900], _
|
|
["Data\Scripts\SpawnerTask.pex", 907], _
|
|
["Data\Scripts\Spell.pex", 1673], _
|
|
["Data\Scripts\StringUtil.pex", 1100], _
|
|
["Data\Scripts\TextureSet.pex", 593], _
|
|
["Data\Scripts\TreeObject.pex", 650], _
|
|
["Data\Scripts\UI.pex", 2070], _
|
|
["Data\Scripts\UICallback.pex", 1139], _
|
|
["Data\Scripts\Utility.pex", 2658], _
|
|
["Data\Scripts\Weapon.pex", 3259], _
|
|
["Data\Scripts\Weather.pex", 1107], _
|
|
["Data\Scripts\WornObject.pex", 1629] _
|
|
]
|
|
|
|
; Delete unconditional files
|
|
For $i = 0 To UBound($filesToDelete) - 1
|
|
Local $fullPath = $LAUNCHER_DIR & "\" & $filesToDelete[$i]
|
|
If FileExists($fullPath) Then
|
|
FileDelete($fullPath)
|
|
EndIf
|
|
Next
|
|
|
|
; Delete files only if size matches
|
|
For $i = 0 To UBound($filesToDeleteWithSize) - 1
|
|
Local $fullPath = $LAUNCHER_DIR & "\" & $filesToDeleteWithSize[$i][0]
|
|
If FileExists($fullPath) Then
|
|
Local $fileSize = FileGetSize($fullPath)
|
|
If $fileSize = $filesToDeleteWithSize[$i][1] Then
|
|
FileDelete($fullPath)
|
|
EndIf
|
|
EndIf
|
|
Next
|
|
|
|
; Delete directories
|
|
Local $dirsToDelete[] = [ _
|
|
"Data\SKSE\Plugins\JCData", _
|
|
"Skyrim" _
|
|
]
|
|
|
|
For $i = 0 To UBound($dirsToDelete) - 1
|
|
Local $fullPath = $LAUNCHER_DIR & "\" & $dirsToDelete[$i]
|
|
If FileExists($fullPath) Then
|
|
DirRemove($fullPath, 1)
|
|
EndIf
|
|
Next
|
|
|
|
; Set borderless windowed mode in EnderalPrefs.ini if it exists
|
|
Local $prefsPath = GetDocumentsPath() & "\My Games\" & GetEnderalBaseDirectoryName() & "\EnderalPrefs.ini"
|
|
If FileExists($prefsPath) Then
|
|
IniWrite($prefsPath, "Display", "bBorderless", "1")
|
|
IniWrite($prefsPath, "Display", "bFull Screen", "0")
|
|
EndIf
|
|
|
|
MsgBox(64, "Update", "Enderal SE has been successfully updated.")
|
|
EndIf
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Main Entry Point
|
|
; ============================================
|
|
Main()
|
|
|
|
Func Main()
|
|
; Check and apply update if needed
|
|
CheckAndApplyUpdate()
|
|
|
|
; Check if SkyrimRedirector is installed
|
|
Local $redirectorDll = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SkyrimRedirector.dll"
|
|
Local $redirectorIni = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SkyrimRedirector.ini"
|
|
|
|
If FileExists($redirectorDll) Then
|
|
If FileExists($redirectorIni) Then
|
|
; Read paths from SkyrimRedirector.ini
|
|
$ENDERAL_INI = IniRead($redirectorIni, "Redirection", "Ini", "")
|
|
$ENDERAL_PREFS_INI = IniRead($redirectorIni, "Redirection", "PrefsIni", "")
|
|
$PLUGINS_FILE = IniRead($redirectorIni, "Redirection", "Plugins", "")
|
|
|
|
; Derive directories from file paths
|
|
$DOCUMENTS_DIR = StringRegExpReplace($ENDERAL_INI, "\\[^\\]+$", "")
|
|
$PLUGINS_DIR = StringRegExpReplace($PLUGINS_FILE, "\\[^\\]+$", "")
|
|
Else
|
|
; SkyrimRedirector.dll exists without INI - create INI with Enderal defaults
|
|
Local $baseDir = GetEnderalBaseDirectoryName()
|
|
|
|
; Set plugins directory path
|
|
$PLUGINS_DIR = $LOCALAPPDATA & "\" & $baseDir
|
|
$PLUGINS_FILE = $PLUGINS_DIR & "\plugins.txt"
|
|
|
|
; Get Documents folder path with Enderal INI names
|
|
$DOCUMENTS_DIR = GetDocumentsPath() & "\My Games\" & $baseDir
|
|
$ENDERAL_INI = $DOCUMENTS_DIR & "\Enderal.ini"
|
|
$ENDERAL_PREFS_INI = $DOCUMENTS_DIR & "\EnderalPrefs.ini"
|
|
|
|
; Create SkyrimRedirector.ini with default paths
|
|
IniWrite($redirectorIni, "Redirection", "Ini", $ENDERAL_INI)
|
|
IniWrite($redirectorIni, "Redirection", "PrefsIni", $ENDERAL_PREFS_INI)
|
|
IniWrite($redirectorIni, "Redirection", "Plugins", $PLUGINS_FILE)
|
|
EndIf
|
|
Else
|
|
; No SkyrimRedirector - use Skyrim default paths
|
|
Local $baseDir = GetBaseDirectoryName()
|
|
|
|
; Set plugins directory path
|
|
$PLUGINS_DIR = $LOCALAPPDATA & "\" & $baseDir
|
|
$PLUGINS_FILE = $PLUGINS_DIR & "\plugins.txt"
|
|
|
|
; Get Documents folder path
|
|
$DOCUMENTS_DIR = GetDocumentsPath() & "\My Games\" & $baseDir
|
|
$ENDERAL_INI = $DOCUMENTS_DIR & "\Skyrim.ini"
|
|
$ENDERAL_PREFS_INI = $DOCUMENTS_DIR & "\SkyrimPrefs.ini"
|
|
EndIf
|
|
|
|
; Perform startup checks
|
|
StartupChecks()
|
|
|
|
; Auto-start Steam if enabled (Steam version only)
|
|
If $bIsSteamVersion Then
|
|
Local $startSteam = RegRead($REG_KEY, $REG_START_STEAM)
|
|
If Not @error And $startSteam = 1 Then
|
|
; Check if Steam is already running
|
|
If Not ProcessExists("steam.exe") Then
|
|
ShellExecute("steam://open/main")
|
|
EndIf
|
|
EndIf
|
|
EndIf
|
|
|
|
; Initialize GDI+
|
|
_GDIPlus_Startup()
|
|
|
|
; Create and show main window
|
|
CreateMainWindow()
|
|
|
|
; Create settings window (hidden)
|
|
CreateSettingsWindow()
|
|
|
|
; Register message handler for dragging
|
|
GUIRegisterMsg($WM_LBUTTONDOWN, "WM_LBUTTONDOWN")
|
|
|
|
; Register message handler for slider updates
|
|
GUIRegisterMsg($WM_HSCROLL, "WM_HSCROLL")
|
|
|
|
; Register message handler for plugin list check boxes
|
|
GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")
|
|
|
|
; Main loop
|
|
MainLoop()
|
|
|
|
; Cleanup
|
|
_GDIPlus_Shutdown()
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Window Drag Handler
|
|
; ============================================
|
|
Func WM_LBUTTONDOWN($hWnd, $iMsg, $wParam, $lParam)
|
|
If $hWnd = $hMainGUI Then
|
|
_SendMessage($hMainGUI, $WM_SYSCOMMAND, 0xF012, 0)
|
|
EndIf
|
|
Return $GUI_RUNDEFMSG
|
|
EndFunc
|
|
|
|
Func WM_HSCROLL($hWnd, $iMsg, $wParam, $lParam)
|
|
If $lParam = GUICtrlGetHandle($sldFramerateLimit) Then
|
|
GUICtrlSetData($lblFramerateLimitValue, GUICtrlRead($sldFramerateLimit))
|
|
EndIf
|
|
Return $GUI_RUNDEFMSG
|
|
EndFunc
|
|
|
|
Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
|
|
; Handles the plugin list: check box changes send no GUIGetMsg event, and rows that
|
|
; cannot be changed are drawn dimmed
|
|
If $hLvPlugins = 0 Then Return $GUI_RUNDEFMSG
|
|
|
|
Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
|
|
If @error Then Return $GUI_RUNDEFMSG
|
|
If HWnd(DllStructGetData($tNMHDR, "hWndFrom")) <> $hLvPlugins Then Return $GUI_RUNDEFMSG
|
|
|
|
Switch DllStructGetData($tNMHDR, "Code")
|
|
Case $NM_CUSTOMDRAW
|
|
Return DrawPluginRow($lParam)
|
|
Case $LVN_ITEMCHANGING, $LVN_ITEMCHANGED
|
|
Return OnPluginStateChange(DllStructGetData($tNMHDR, "Code"), $lParam)
|
|
EndSwitch
|
|
|
|
Return $GUI_RUNDEFMSG
|
|
EndFunc
|
|
|
|
Func DrawPluginRow($lParam)
|
|
Local $tCustomDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $lParam)
|
|
If @error Then Return $CDRF_DODEFAULT
|
|
|
|
Switch DllStructGetData($tCustomDraw, "dwDrawStage")
|
|
Case $CDDS_PREPAINT
|
|
Return $CDRF_NOTIFYITEMDRAW
|
|
Case $CDDS_ITEMPREPAINT
|
|
Local $iItem = DllStructGetData($tCustomDraw, "dwItemSpec")
|
|
If $iItem >= 0 And $iItem < $iPluginListCount And IsPluginRowFixed($iItem) Then
|
|
DllStructSetData($tCustomDraw, "clrText", _WinAPI_GetSysColor($COLOR_GRAYTEXT))
|
|
EndIf
|
|
Return $CDRF_NEWFONT
|
|
EndSwitch
|
|
|
|
Return $CDRF_DODEFAULT
|
|
EndFunc
|
|
|
|
Func OnPluginStateChange($iCode, $lParam)
|
|
If $bPluginListUpdating Then Return $GUI_RUNDEFMSG
|
|
|
|
Local $tNMLV = DllStructCreate($tagNMLISTVIEW, $lParam)
|
|
If @error Then Return $GUI_RUNDEFMSG
|
|
If Not BitAND(DllStructGetData($tNMLV, "Changed"), $LVIF_STATE) Then Return $GUI_RUNDEFMSG
|
|
|
|
Local $iItem = DllStructGetData($tNMLV, "Item")
|
|
Local $iOldCheck = BitShift(BitAND(DllStructGetData($tNMLV, "OldState"), $LVIS_STATEIMAGEMASK), 12)
|
|
Local $iNewCheck = BitShift(BitAND(DllStructGetData($tNMLV, "NewState"), $LVIS_STATEIMAGEMASK), 12)
|
|
|
|
; A check state change has both state image indexes set
|
|
Local $bCheckChanged = ($iOldCheck <> 0 And $iNewCheck <> 0 And $iOldCheck <> $iNewCheck)
|
|
Local $bValidItem = ($iItem >= 0 And $iItem < $iPluginListCount)
|
|
|
|
If $iCode = $LVN_ITEMCHANGING Then
|
|
; Rows that cannot be changed keep their state
|
|
If $bCheckChanged And $bValidItem And IsPluginRowFixed($iItem) Then Return True
|
|
Return $GUI_RUNDEFMSG
|
|
EndIf
|
|
|
|
If $bCheckChanged And $bValidItem Then
|
|
If IsPluginRowFixed($iItem) Then
|
|
; Safety net in case the rejection above did not take effect
|
|
$bPluginListUpdating = True
|
|
SetPluginRowState($iItem)
|
|
$bPluginListUpdating = False
|
|
Else
|
|
$aPluginList[$iItem][1] = ($iNewCheck = $PLUGIN_STATE_CHECKED)
|
|
$bPluginListDirty = True
|
|
EndIf
|
|
EndIf
|
|
|
|
UpdatePluginButtons()
|
|
Return $GUI_RUNDEFMSG
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Startup Checks
|
|
; ============================================
|
|
Func StartupChecks()
|
|
; Check if Enderal is installed
|
|
If Not FileExists($LAUNCHER_DIR & "\Data\Enderal - Forgotten Stories.esm") Then
|
|
MsgBox(16, "Error", "Unable to find Enderal - Forgotten Stories.esm (Enderal SE is not installed?)")
|
|
Exit
|
|
EndIf
|
|
|
|
If @OSVersion = 'WIN_10' Or @OSVersion = 'WIN_11' Then DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext" , "HWND", "DPI_AWARENESS_CONTEXT" -2)
|
|
If @OSVersion = 'WIN_81' Then DllCall("User32.dll", "bool", "SetProcessDPIAware")
|
|
|
|
; Detect Steam version by checking for steam_api64.dll
|
|
$bIsSteamVersion = FileExists($LAUNCHER_DIR & "\steam_api64.dll")
|
|
|
|
; Detect Mod Organizer 2 while its process is still our parent.
|
|
; Under MO2 the load order belongs to the active MO2 profile, so the launcher must not touch it.
|
|
$MO2_DIR = GetMO2Directory()
|
|
$bPluginListReadOnly = ($MO2_DIR <> "")
|
|
|
|
CheckRegistryPaths()
|
|
CheckPluginsFile()
|
|
CheckINIFiles()
|
|
CheckSaveDirectory()
|
|
EndFunc
|
|
|
|
Func CheckRegistryPaths()
|
|
Local $installPathExpected = $LAUNCHER_DIR & "\"
|
|
Local $exePathExpected = @ScriptFullPath
|
|
|
|
; Check and update Install_Path
|
|
Local $installPath = RegRead($REG_KEY, $REG_INSTALL_PATH)
|
|
If @error Or $installPath <> $installPathExpected Then
|
|
RegWrite($REG_KEY, $REG_INSTALL_PATH, "REG_SZ", $installPathExpected)
|
|
EndIf
|
|
|
|
; Check and update Exe_Path
|
|
Local $exePath = RegRead($REG_KEY, $REG_EXE_PATH)
|
|
If @error Or $exePath <> $exePathExpected Then
|
|
RegWrite($REG_KEY, $REG_EXE_PATH, "REG_SZ", $exePathExpected)
|
|
EndIf
|
|
|
|
; For Steam version: initialize Start_Steam if it doesn't exist
|
|
If $bIsSteamVersion Then
|
|
Local $startSteam = RegRead($REG_KEY, $REG_START_STEAM)
|
|
If @error Then
|
|
RegWrite($REG_KEY, $REG_START_STEAM, "REG_DWORD", 1)
|
|
EndIf
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func GetRequiredPlugins()
|
|
; Plugins Enderal cannot run without - always kept enabled
|
|
Local $aRequired[2] = ["Enderal - Forgotten Stories.esm", "SkyUI_SE.esp"]
|
|
Return $aRequired
|
|
EndFunc
|
|
|
|
Func CheckPluginsFile()
|
|
Local $aRequired = GetRequiredPlugins()
|
|
Local $requiredLines[UBound($aRequired)]
|
|
Local $iRequired = 0
|
|
|
|
; Only enforce required plugins that are installed (SkyUI may be absent)
|
|
For $i = 0 To UBound($aRequired) - 1
|
|
If Not FileExists($LAUNCHER_DIR & "\Data\" & $aRequired[$i]) Then ContinueLoop
|
|
$requiredLines[$iRequired] = "*" & $aRequired[$i]
|
|
$iRequired += 1
|
|
Next
|
|
ReDim $requiredLines[$iRequired]
|
|
|
|
If Not DirExists($PLUGINS_DIR) Then
|
|
DirCreate($PLUGINS_DIR)
|
|
EndIf
|
|
|
|
Local $fileContent = ""
|
|
If FileExists($PLUGINS_FILE) Then
|
|
$fileContent = FileRead($PLUGINS_FILE)
|
|
EndIf
|
|
|
|
Local $modified = False
|
|
Local $lines = StringSplit($fileContent, @CRLF, 1)
|
|
|
|
For $i = 0 To UBound($requiredLines) - 1
|
|
Local $lineWithAsterisk = $requiredLines[$i]
|
|
Local $lineWithoutAsterisk = StringTrimLeft($lineWithAsterisk, 1)
|
|
|
|
Local $foundWithAsterisk = False
|
|
Local $foundWithoutAsterisk = False
|
|
Local $lineIndex = -1
|
|
|
|
For $j = 1 To $lines[0]
|
|
Local $trimmedLine = StringStripWS($lines[$j], 3)
|
|
If $trimmedLine = $lineWithAsterisk Then
|
|
$foundWithAsterisk = True
|
|
ExitLoop
|
|
ElseIf $trimmedLine = $lineWithoutAsterisk Then
|
|
$foundWithoutAsterisk = True
|
|
$lineIndex = $j
|
|
ExitLoop
|
|
EndIf
|
|
Next
|
|
|
|
If $foundWithAsterisk Then
|
|
ContinueLoop
|
|
ElseIf $foundWithoutAsterisk Then
|
|
$lines[$lineIndex] = $lineWithAsterisk
|
|
$modified = True
|
|
Else
|
|
If $fileContent <> "" And StringRight($fileContent, 2) <> @CRLF Then
|
|
$fileContent &= @CRLF
|
|
EndIf
|
|
$fileContent &= $lineWithAsterisk & @CRLF
|
|
$modified = True
|
|
ContinueLoop
|
|
EndIf
|
|
Next
|
|
|
|
If $modified Then
|
|
Local $newContent = ""
|
|
For $j = 1 To $lines[0]
|
|
$newContent &= $lines[$j]
|
|
If $j < $lines[0] Then $newContent &= @CRLF
|
|
Next
|
|
|
|
If StringInStr($fileContent, "*Enderal - Forgotten Stories.esm") Or StringInStr($fileContent, "*SkyUI_SE.esp") Then
|
|
$newContent = $fileContent
|
|
EndIf
|
|
|
|
Local $hFile = FileOpen($PLUGINS_FILE, 2)
|
|
FileWrite($hFile, $newContent)
|
|
FileClose($hFile)
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func GetPhysicalResolution()
|
|
; Get physical screen resolution using EnumDisplaySettingsW
|
|
; This bypasses DPI scaling which affects @DesktopWidth/@DesktopHeight
|
|
Local Const $DEVMODE_SIZE = 220
|
|
Local Const $OFFSET_dmPelsWidth = 172
|
|
Local Const $OFFSET_dmPelsHeight = 176
|
|
Local Const $ENUM_CURRENT_SETTINGS = -1
|
|
|
|
Local $tDevMode = DllStructCreate("byte[" & $DEVMODE_SIZE & "]")
|
|
DllStructSetData($tDevMode, 1, $DEVMODE_SIZE, 68) ; dmSize at offset 68
|
|
|
|
Local $aResult = DllCall("user32.dll", "bool", "EnumDisplaySettingsW", _
|
|
"ptr", 0, _
|
|
"dword", $ENUM_CURRENT_SETTINGS, _
|
|
"struct*", $tDevMode)
|
|
|
|
If @error Or Not $aResult[0] Then
|
|
; Fallback to AutoIt macros if DllCall fails
|
|
Local $aRes[2] = [@DesktopWidth, @DesktopHeight]
|
|
Return $aRes
|
|
EndIf
|
|
|
|
Local $width = DllStructGetData(DllStructCreate("dword", DllStructGetPtr($tDevMode) + $OFFSET_dmPelsWidth), 1)
|
|
Local $height = DllStructGetData(DllStructCreate("dword", DllStructGetPtr($tDevMode) + $OFFSET_dmPelsHeight), 1)
|
|
|
|
Local $aRes[2] = [$width, $height]
|
|
Return $aRes
|
|
EndFunc
|
|
|
|
Func CheckINIFiles()
|
|
If Not DirExists($DOCUMENTS_DIR) Then
|
|
DirCreate($DOCUMENTS_DIR)
|
|
EndIf
|
|
|
|
Local $iniExists = FileExists($ENDERAL_INI)
|
|
Local $prefsExists = FileExists($ENDERAL_PREFS_INI)
|
|
|
|
If Not $iniExists Or Not $prefsExists Then
|
|
Local $aPhysicalRes = GetPhysicalResolution()
|
|
Local $screenWidth = $aPhysicalRes[0]
|
|
Local $screenHeight = $aPhysicalRes[1]
|
|
|
|
If Not $iniExists Then
|
|
Local $defaultIni = $LAUNCHER_DIR & "\Enderal_default.ini"
|
|
If Not FileExists($defaultIni) Then $defaultIni = $LAUNCHER_DIR & "\Skyrim_default.ini"
|
|
If FileExists($defaultIni) Then FileCopy($defaultIni, $ENDERAL_INI)
|
|
EndIf
|
|
|
|
If Not $prefsExists Then
|
|
Local $defaultPrefsIni = $LAUNCHER_DIR & "\EnderalPrefs_default.ini"
|
|
If Not FileExists($defaultPrefsIni) Then $defaultPrefsIni = $LAUNCHER_DIR & "\SkyrimPrefs_default.ini"
|
|
If FileExists($defaultPrefsIni) Then
|
|
FileCopy($defaultPrefsIni, $ENDERAL_PREFS_INI)
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "iSize W", $screenWidth)
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "iSize H", $screenHeight)
|
|
EndIf
|
|
EndIf
|
|
Else
|
|
Local $shadowValue = IniRead($ENDERAL_PREFS_INI, "Display", "iShadowMaskQuarter", "")
|
|
If $shadowValue <> "4" Then
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "iShadowMaskQuarter", "4")
|
|
EndIf
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func CheckSaveDirectory()
|
|
; Read sLocalSavePath from Enderal.ini (or custom INI) and ensure the directory exists
|
|
Local $savePath = IniReadWithCustom("General", "sLocalSavePath", "")
|
|
|
|
; Save path is relative to Skyrim's documents directory
|
|
Local $skyrimDocsDir = GetDocumentsPath() & "\My Games\" & GetBaseDirectoryName()
|
|
|
|
; Fix empty or legacy relative paths that start with ".."
|
|
If $savePath = "" Or StringLeft($savePath, 2) = ".." Then
|
|
Local $newSavePath = "EnderalSaves\"
|
|
Local $newFullPath = $skyrimDocsDir & "\" & $newSavePath
|
|
|
|
; Ensure new directory exists and update INI
|
|
If Not DirExists($newFullPath) Then
|
|
DirCreate($newFullPath)
|
|
EndIf
|
|
IniWriteWithCustom("General", "sLocalSavePath", $newSavePath)
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func GetDocumentsPath()
|
|
Local $oShell = ObjCreate("Shell.Application")
|
|
If IsObj($oShell) Then
|
|
Local $oFolder = $oShell.Namespace(0x05)
|
|
If IsObj($oFolder) Then
|
|
Return $oFolder.Self.Path
|
|
EndIf
|
|
EndIf
|
|
Return @MyDocumentsDir
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Main Window
|
|
; ============================================
|
|
Func CreateMainWindow()
|
|
$hMainGUI = GUICreate("Enderal Launcher", $LAUNCHER_WIDTH, $LAUNCHER_HEIGHT, -1, -1, $WS_POPUP + $WS_CLIPCHILDREN)
|
|
GUISetIcon($LAUNCHER_DIR & "\enderal.ico")
|
|
;GUISetBkColor(0x1A1A1A, $hMainGUI)
|
|
|
|
; Set background image
|
|
SetBackgroundImage()
|
|
|
|
; Create top link bar on the left
|
|
Local $linkBarY = 10
|
|
Local $linkSpacing = 80
|
|
Local $startX = 20
|
|
|
|
; Credits - shadow first, then main label
|
|
$lblCreditsShadow = GUICtrlCreateLabel("Credits", $startX + 1, $linkBarY + 1, 70, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 10, 400, 4)
|
|
GUICtrlSetColor(-1, 0x000000)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
$lblCredits = GUICtrlCreateLabel("Credits", $startX, $linkBarY, 70, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 10, 400, 4)
|
|
GUICtrlSetColor(-1, 0xFFFFFF)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
; Wiki - shadow first, then main label
|
|
$lblWikiShadow = GUICtrlCreateLabel("Wiki", $startX + $linkSpacing + 1, $linkBarY + 1, 70, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 10, 400, 4)
|
|
GUICtrlSetColor(-1, 0x000000)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
$lblWiki = GUICtrlCreateLabel("Wiki", $startX + $linkSpacing, $linkBarY, 70, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 10, 400, 4)
|
|
GUICtrlSetColor(-1, 0xFFFFFF)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
; Discord - shadow first, then main label
|
|
$lblSupportShadow = GUICtrlCreateLabel("Discord", $startX + $linkSpacing * 2 + 1, $linkBarY + 1, 70, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 10, 400, 4)
|
|
GUICtrlSetColor(-1, 0x000000)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
$lblSupport = GUICtrlCreateLabel("Discord", $startX + $linkSpacing * 2, $linkBarY, 70, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 10, 400, 4)
|
|
GUICtrlSetColor(-1, 0xFFFFFF)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
; Changelog - shadow first, then main label
|
|
$lblChangelogShadow = GUICtrlCreateLabel("Changelog", $startX + $linkSpacing * 3 + 9 + 1, $linkBarY + 1, 100, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 10, 400, 4)
|
|
GUICtrlSetColor(-1, 0x000000)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
$lblChangelog = GUICtrlCreateLabel("Changelog", $startX + $linkSpacing * 3 + 9, $linkBarY, 100, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 10, 400, 4)
|
|
GUICtrlSetColor(-1, 0xFFFFFF)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
; Create close and minimize buttons at top right
|
|
; Minimize - shadow first, then main label
|
|
$lblMinimizeShadow = GUICtrlCreateLabel("_", $LAUNCHER_WIDTH - 60 + 1, 5 + 1, 25, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 14, 700)
|
|
GUICtrlSetColor(-1, 0x000000)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
$btnMinimize = GUICtrlCreateLabel("_", $LAUNCHER_WIDTH - 60, 5, 25, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 14, 700)
|
|
GUICtrlSetColor(-1, 0xFFFFFF)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
; Close - shadow first, then main label
|
|
$lblCloseShadow = GUICtrlCreateLabel("X", $LAUNCHER_WIDTH - 30 + 1, 5 + 1, 25, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 12, 700)
|
|
GUICtrlSetColor(-1, 0x000000)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
$btnClose = GUICtrlCreateLabel("X", $LAUNCHER_WIDTH - 30, 5, 25, 25, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 12, 700)
|
|
GUICtrlSetColor(-1, 0xFFFFFF)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUICtrlSetCursor(-1, 0)
|
|
|
|
; Create main buttons
|
|
Local $sTempDir = @TempDir & "\EnderalLauncher\" ; Create a unique temp folder
|
|
DirCreate($sTempDir)
|
|
|
|
Local $btnWidth = 200
|
|
Local $btnHeight = 82
|
|
Local $btnX = $LAUNCHER_WIDTH - $btnWidth - 60
|
|
Local $btnStartY = $LAUNCHER_HEIGHT - 420
|
|
|
|
Local $sDestinationFile = $sTempDir & "play.bmp"
|
|
FileInstall("play.bmp", $sDestinationFile, 1)
|
|
$btnPlay = GUICtrlCreateButton("PLAY", $btnX, $btnStartY, $btnWidth, $btnHeight, $BS_BITMAP)
|
|
GUICtrlSetFont(-1, 12, 400, $GUI_FONTNORMAL, "Tahoma", $CLEARTYPE_QUALITY)
|
|
GUICtrlSetImage($btnPlay, $sDestinationFile)
|
|
GUICtrlSetCursor($btnPlay, 0)
|
|
|
|
$sDestinationFile = $sTempDir & "settings.bmp"
|
|
FileInstall("settings.bmp", $sDestinationFile, 1)
|
|
$btnSettings = GUICtrlCreateButton("Settings", $btnX, $btnStartY + 110, $btnWidth, $btnHeight, $BS_BITMAP)
|
|
GUICtrlSetFont(-1, 12, 400, $GUI_FONTNORMAL, "Tahoma", $CLEARTYPE_QUALITY)
|
|
GUICtrlSetImage($btnSettings, $sDestinationFile)
|
|
GUICtrlSetCursor($btnSettings, 0)
|
|
|
|
$sDestinationFile = $sTempDir & "exit.bmp"
|
|
FileInstall("exit.bmp", $sDestinationFile, 1)
|
|
$btnExit = GUICtrlCreateButton("Exit", $btnX, $btnStartY + 220, $btnWidth, $btnHeight, $BS_BITMAP)
|
|
GUICtrlSetFont(-1, 12, 400, $GUI_FONTNORMAL, "Tahoma", $CLEARTYPE_QUALITY)
|
|
GUICtrlSetImage($btnExit, $sDestinationFile)
|
|
GUICtrlSetCursor($btnExit, 0)
|
|
|
|
; Create bottom version bar
|
|
CreateVersionBar()
|
|
|
|
GUISetState(@SW_SHOW, $hMainGUI)
|
|
EndFunc
|
|
|
|
Func CreateVersionBar()
|
|
Local $bottomY = $LAUNCHER_HEIGHT - 30
|
|
Local $fontSize = 9
|
|
|
|
; Get versions
|
|
Local $skyrimVer = GetSkyrimVersion()
|
|
Local $enderalVer = GetEnderalVersion()
|
|
|
|
; Determine display text for each version (include "v" only when version is found)
|
|
Local $skyrimText = "N/A"
|
|
If $skyrimVer <> "" Then $skyrimText = "v" & $skyrimVer
|
|
|
|
Local $enderalText = "N/A"
|
|
If $enderalVer <> "" Then $enderalText = "v" & $enderalVer
|
|
|
|
; Build full version string
|
|
Local $fullText = "Skyrim SE " & $skyrimText & " | Enderal SE " & $enderalText
|
|
|
|
; Center the text using a wide label with center alignment
|
|
Local $labelWidth = 500
|
|
Local $startX = ($LAUNCHER_WIDTH - $labelWidth) / 2
|
|
|
|
; Shadow label
|
|
GUICtrlCreateLabel($fullText, $startX + 1, $bottomY + 1, $labelWidth, 20, $SS_CENTER)
|
|
GUICtrlSetFont(-1, $fontSize, 400)
|
|
GUICtrlSetColor(-1, 0x000000)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
|
|
; Main label
|
|
GUICtrlCreateLabel($fullText, $startX, $bottomY, $labelWidth, 20, $SS_CENTER)
|
|
GUICtrlSetFont(-1, $fontSize, 400)
|
|
GUICtrlSetColor(-1, 0xFFFFFF)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
EndFunc
|
|
|
|
Func SetBackgroundImage()
|
|
; Extract background from embedded resource to temp
|
|
Local $sTempDir = @TempDir & "\EnderalLauncher\"
|
|
DirCreate($sTempDir)
|
|
Local $bgFile = $sTempDir & "background.jpg"
|
|
FileInstall("background.jpg", $bgFile, 1)
|
|
|
|
If FileExists($bgFile) Then
|
|
Local $hImage = _GDIPlus_ImageLoadFromFile($bgFile)
|
|
If $hImage Then
|
|
Local $hGraphic = _GDIPlus_GraphicsCreateFromHWND($hMainGUI)
|
|
Local $hBitmap = _GDIPlus_BitmapCreateFromGraphics($LAUNCHER_WIDTH, $LAUNCHER_HEIGHT, $hGraphic)
|
|
Local $hBuffer = _GDIPlus_ImageGetGraphicsContext($hBitmap)
|
|
|
|
; Get original image dimensions
|
|
Local $imgWidth = _GDIPlus_ImageGetWidth($hImage)
|
|
Local $imgHeight = _GDIPlus_ImageGetHeight($hImage)
|
|
|
|
; Calculate scale factor to cover the entire window (crop to fit)
|
|
Local $scaleX = $LAUNCHER_WIDTH / $imgWidth
|
|
Local $scaleY = $LAUNCHER_HEIGHT / $imgHeight
|
|
Local $scale = ($scaleX > $scaleY) ? $scaleX : $scaleY
|
|
|
|
; Calculate scaled dimensions
|
|
Local $scaledWidth = $imgWidth * $scale
|
|
Local $scaledHeight = $imgHeight * $scale
|
|
|
|
; Calculate offset to center the image (crop from center)
|
|
Local $offsetX = ($LAUNCHER_WIDTH - $scaledWidth) / 2
|
|
Local $offsetY = ($LAUNCHER_HEIGHT - $scaledHeight) / 2
|
|
|
|
; Draw image with aspect ratio preserved, centered and cropped
|
|
_GDIPlus_GraphicsDrawImageRect($hBuffer, $hImage, $offsetX, $offsetY, $scaledWidth, $scaledHeight)
|
|
|
|
Local $hGDIBitmap = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hBitmap)
|
|
$hBackgroundPic = GUICtrlCreatePic("", 0, 0, $LAUNCHER_WIDTH, $LAUNCHER_HEIGHT)
|
|
_SetBitmapToCtrl($hBackgroundPic, $hGDIBitmap)
|
|
GUICtrlSetState($hBackgroundPic, $GUI_DISABLE)
|
|
|
|
_WinAPI_DeleteObject($hGDIBitmap)
|
|
_GDIPlus_GraphicsDispose($hBuffer)
|
|
_GDIPlus_BitmapDispose($hBitmap)
|
|
_GDIPlus_GraphicsDispose($hGraphic)
|
|
_GDIPlus_ImageDispose($hImage)
|
|
EndIf
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func _SetBitmapToCtrl($idPic, $hBitmap)
|
|
Local $hWnd = GUICtrlGetHandle($idPic)
|
|
_SendMessage($hWnd, 0x0172, 0, $hBitmap)
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Settings Window (child window)
|
|
; ============================================
|
|
Func CreateSettingsWindow()
|
|
; Create settings as a child window of main window
|
|
$hSettingsGUI = GUICreate("Settings", $SETTINGS_WIDTH, $SETTINGS_HEIGHT, _
|
|
$SETTINGS_MARGIN, $SETTINGS_MARGIN, $WS_POPUP, $WS_EX_MDICHILD, $hMainGUI)
|
|
;GUISetBkColor(0xF0F0F0, $hSettingsGUI)
|
|
|
|
; Tab control
|
|
$tabSettings = GUICtrlCreateTab(15, 20, $SETTINGS_WIDTH - 30, $SETTINGS_HEIGHT - 80)
|
|
|
|
; Display tab (includes rendering controls on right side)
|
|
GUICtrlCreateTabItem("Display")
|
|
CreateDisplayControls()
|
|
CreateRenderingControls()
|
|
|
|
; General tab
|
|
GUICtrlCreateTabItem("General")
|
|
CreateControlsControls()
|
|
|
|
; Plugins tab
|
|
GUICtrlCreateTabItem("Plugins")
|
|
CreatePluginsControls()
|
|
|
|
; End tab items
|
|
GUICtrlCreateTabItem("")
|
|
|
|
; Save and Cancel buttons (outside tabs)
|
|
$btnSave = GUICtrlCreateButton("Save", $SETTINGS_WIDTH - 205, $SETTINGS_HEIGHT - 50, 90, 35)
|
|
$btnCancel = GUICtrlCreateButton("Cancel", $SETTINGS_WIDTH - 105, $SETTINGS_HEIGHT - 50, 90, 35)
|
|
|
|
; Hide initially
|
|
GUISetState(@SW_HIDE, $hSettingsGUI)
|
|
|
|
; Switch back to main GUI
|
|
GUISwitch($hMainGUI)
|
|
EndFunc
|
|
|
|
Func CreateDisplayControls()
|
|
Local $leftMargin = $TAB_LEFT_MARGIN
|
|
Local $topMargin = $TAB_TOP_MARGIN
|
|
Local $rowHeight = $TAB_ROW_HEIGHT
|
|
|
|
; Resolution
|
|
$lblResolution = GUICtrlCreateLabel("Resolution:", $leftMargin, $topMargin, 100, 20)
|
|
$cmbResolution = GUICtrlCreateCombo("", $leftMargin + 110, $topMargin - 3, 200, 200, $CBS_DROPDOWNLIST)
|
|
PopulateResolutions()
|
|
|
|
; Antialiasing
|
|
$lblAntialiasing = GUICtrlCreateLabel("Antialiasing:", $leftMargin, $topMargin + $rowHeight, 100, 20)
|
|
$cmbAntialiasing = GUICtrlCreateCombo("", $leftMargin + 110, $topMargin + $rowHeight - 3, 200, 200, $CBS_DROPDOWNLIST)
|
|
GUICtrlSetData($cmbAntialiasing, $AA_TAA & "|" & $AA_FXAA & "|" & $AA_OFF)
|
|
|
|
; Windowed Mode
|
|
$chkWindowed = GUICtrlCreateCheckbox("Windowed Mode", $leftMargin, $topMargin + $rowHeight * 2, 150, 20)
|
|
|
|
; Detail group
|
|
Local $columnWidth = 370
|
|
GUICtrlCreateGroup("Detail Presets", $leftMargin, $topMargin + $rowHeight * 3, $columnWidth, 80)
|
|
Local $detailBtnY = $topMargin + $rowHeight * 3 + 30
|
|
Local $detailBtnWidth = 85
|
|
Local $detailBtnHeight = 32
|
|
$btnLow = GUICtrlCreateButton("Low", $leftMargin + 8, $detailBtnY, $detailBtnWidth, $detailBtnHeight)
|
|
$btnMedium = GUICtrlCreateButton("Medium", $leftMargin + 95, $detailBtnY, $detailBtnWidth, $detailBtnHeight)
|
|
$btnHigh = GUICtrlCreateButton("High", $leftMargin + 182, $detailBtnY, $detailBtnWidth, $detailBtnHeight)
|
|
$btnUltra = GUICtrlCreateButton("Ultra", $leftMargin + 269, $detailBtnY, $detailBtnWidth, $detailBtnHeight)
|
|
GUICtrlCreateGroup("", -99, -99, 1, 1)
|
|
|
|
; SSE Display Tweaks group
|
|
Local $sseGroupY = $topMargin + $rowHeight * 3 + 100
|
|
$grpSSEDisplayTweaks = GUICtrlCreateGroup("SSE Display Tweaks", $leftMargin, $sseGroupY, $columnWidth, 90)
|
|
$chkSSEDisplayTweaks = GUICtrlCreateCheckbox("Enable", $leftMargin + 10, $sseGroupY + 25, 200, 20)
|
|
GUICtrlSetTip(-1, "Enables support for refresh rates higher than 60 Hz.")
|
|
$lblFramerateLimit = GUICtrlCreateLabel("Framerate limit:", $leftMargin + 10, $sseGroupY + 55, 110, 20)
|
|
$sldFramerateLimit = GUICtrlCreateSlider($leftMargin + 130, $sseGroupY + 50, 185, 25)
|
|
GUICtrlSetLimit($sldFramerateLimit, @DesktopRefresh, 30)
|
|
$lblFramerateLimitValue = GUICtrlCreateLabel("60", $leftMargin + 320, $sseGroupY + 55, 35, 20)
|
|
GUICtrlCreateGroup("", -99, -99, 1, 1)
|
|
EndFunc
|
|
|
|
Func CreateRenderingControls()
|
|
; Position on right side of Display tab
|
|
Local $leftMargin = $TAB_LEFT_MARGIN + 370 + 40
|
|
Local $topMargin = $TAB_TOP_MARGIN
|
|
Local $rowHeight = $TAB_ROW_HEIGHT
|
|
Local $columnWidth = 370
|
|
|
|
$chk64bitRT = GUICtrlCreateCheckbox("64-bit RenderTarget", $leftMargin, $topMargin, $columnWidth - 20, 20)
|
|
GUICtrlSetTip(-1, "Uses 64-bit precision for HDR rendering instead of 32-bit." & @CRLF & "Reduces color banding in skies and gradients. Uses more VRAM.")
|
|
|
|
$chkSSAO = GUICtrlCreateCheckbox("Screen Space Ambient Occlusion", $leftMargin, $topMargin + $rowHeight, $columnWidth - 20, 20)
|
|
GUICtrlSetTip(-1, "Adds soft shadows in corners and where objects meet surfaces." & @CRLF & "Makes scenes look more realistic. Has a performance cost.")
|
|
|
|
$chkVolumetricLighting = GUICtrlCreateCheckbox("Volumetric Lighting", $leftMargin, $topMargin + $rowHeight * 2, $columnWidth - 20, 20)
|
|
GUICtrlSetTip(-1, "Adds visible light rays (god rays) through fog and atmosphere." & @CRLF & "Creates atmospheric sunbeams. Has a performance cost.")
|
|
|
|
$chkSSR = GUICtrlCreateCheckbox("Screen Space Reflections", $leftMargin, $topMargin + $rowHeight * 3, $columnWidth - 20, 20)
|
|
GUICtrlSetTip(-1, "Skyrim SE is affected by the green water bug with this option enabled." & @CRLF & "Recommended to enable only with ENB or Community Shaders.")
|
|
EndFunc
|
|
|
|
Func CreateControlsControls()
|
|
Local $leftMargin = $TAB_LEFT_MARGIN
|
|
Local $topMargin = $TAB_TOP_MARGIN
|
|
Local $rowHeight = $TAB_ROW_HEIGHT
|
|
Local $columnWidth = 370
|
|
Local $rightMargin = $leftMargin + $columnWidth + 20
|
|
|
|
; Controls group
|
|
GUICtrlCreateGroup("Controls", $leftMargin, $topMargin, $columnWidth, 80)
|
|
$chkController = GUICtrlCreateCheckbox("Enable Controller", $leftMargin + 10, $topMargin + 25, $columnWidth - 20, 20)
|
|
$chkVibration = GUICtrlCreateCheckbox("Controller Vibration", $leftMargin + 10, $topMargin + 50, $columnWidth - 20, 20)
|
|
GUICtrlCreateGroup("", -99, -99, 1, 1)
|
|
|
|
; Steam-only: Start Steam checkbox
|
|
$chkStartSteam = GUICtrlCreateCheckbox("Start Steam on launcher start", $leftMargin, $topMargin + 95, $columnWidth, 20)
|
|
If Not $bIsSteamVersion Then
|
|
GUICtrlSetState($chkStartSteam, $GUI_HIDE)
|
|
EndIf
|
|
|
|
; INI Files group (right side)
|
|
$grpIniFiles = GUICtrlCreateGroup("INI Files", $rightMargin, $topMargin, $columnWidth, 313)
|
|
Local $iniButtonWidth = $columnWidth - 30
|
|
Local $iniButtonHeight = 44
|
|
Local $iniButtonSpacing = 54
|
|
$btnOpenEnderalIni = GUICtrlCreateButton(GetIniFileName($ENDERAL_INI), $rightMargin + 15, $topMargin + 28, $iniButtonWidth, $iniButtonHeight)
|
|
$btnOpenEnderalPrefsIni = GUICtrlCreateButton(GetIniFileName($ENDERAL_PREFS_INI), $rightMargin + 15, $topMargin + 28 + $iniButtonSpacing, $iniButtonWidth, $iniButtonHeight)
|
|
$btnOpenCustomIni = GUICtrlCreateButton(GetCustomIniFileName(), $rightMargin + 15, $topMargin + 28 + $iniButtonSpacing * 2, $iniButtonWidth, $iniButtonHeight)
|
|
UpdateCustomIniButtonState()
|
|
$btnOpenModIni = GUICtrlCreateButton(GetModIniFileName(), $rightMargin + 15, $topMargin + 28 + $iniButtonSpacing * 3, $iniButtonWidth, $iniButtonHeight)
|
|
UpdateModIniButtonState()
|
|
$btnOpenSavegames = GUICtrlCreateButton("Saved games directory", $rightMargin + 15, $topMargin + 28 + $iniButtonSpacing * 4, $iniButtonWidth, $iniButtonHeight)
|
|
GUICtrlCreateGroup("", -99, -99, 1, 1)
|
|
EndFunc
|
|
|
|
Func CreatePluginsControls()
|
|
Local $leftMargin = $TAB_LEFT_MARGIN
|
|
Local $topMargin = 58
|
|
Local $listWidth = 560
|
|
Local $listHeight = 340
|
|
Local $sideX = $leftMargin + $listWidth + 20
|
|
Local $sideWidth = 198
|
|
Local $btnHeight = 32
|
|
|
|
$lvPlugins = GUICtrlCreateListView("Plugin|Type", $leftMargin, $topMargin, $listWidth, $listHeight, _
|
|
BitOR($LVS_REPORT, $LVS_SINGLESEL, $LVS_SHOWSELALWAYS, $LVS_NOSORTHEADER), _
|
|
BitOR($LVS_EX_CHECKBOXES, $LVS_EX_FULLROWSELECT, $LVS_EX_DOUBLEBUFFER))
|
|
$hLvPlugins = GUICtrlGetHandle($lvPlugins)
|
|
_GUICtrlListView_SetColumnWidth($hLvPlugins, 0, 420)
|
|
_GUICtrlListView_SetColumnWidth($hLvPlugins, 1, 120)
|
|
CreatePluginStateImages()
|
|
|
|
$btnPluginUp = GUICtrlCreateButton("Move Up", $sideX, $topMargin, $sideWidth, $btnHeight)
|
|
GUICtrlSetState(-1, $GUI_DISABLE)
|
|
$btnPluginDown = GUICtrlCreateButton("Move Down", $sideX, $topMargin + 38, $sideWidth, $btnHeight)
|
|
GUICtrlSetState(-1, $GUI_DISABLE)
|
|
$btnPluginsEnableAll = GUICtrlCreateButton("Enable All", $sideX, $topMargin + 90, $sideWidth, $btnHeight)
|
|
$btnPluginsDisableAll = GUICtrlCreateButton("Disable All", $sideX, $topMargin + 128, $sideWidth, $btnHeight)
|
|
|
|
$lblPluginsCount = GUICtrlCreateLabel("", $sideX, $topMargin + 178, $sideWidth, 20)
|
|
|
|
Local $hint = "Masters (ESM/ESL) always load before regular plugins." & @CRLF & @CRLF & _
|
|
"Plugins Enderal requires are loaded by the game itself and cannot be changed."
|
|
If $bPluginListReadOnly Then
|
|
$hint = "The load order belongs to the active Mod Organizer profile." & @CRLF & @CRLF & _
|
|
"Enable, disable and sort plugins in Mod Organizer instead."
|
|
EndIf
|
|
|
|
$lblPluginsHint = GUICtrlCreateLabel($hint, $sideX, $topMargin + 206, $sideWidth, 134)
|
|
GUICtrlSetFont(-1, 8)
|
|
GUICtrlSetColor(-1, 0x606060)
|
|
EndFunc
|
|
|
|
Func PopulateResolutions()
|
|
Local $aResolutions = GetSupportedResolutions()
|
|
Local $resolutions = ""
|
|
|
|
For $i = 0 To UBound($aResolutions) - 1
|
|
If $i > 0 Then $resolutions &= "|"
|
|
$resolutions &= $aResolutions[$i]
|
|
Next
|
|
|
|
GUICtrlSetData($cmbResolution, $resolutions)
|
|
EndFunc
|
|
|
|
Func GetSupportedResolutions()
|
|
; DEVMODEW structure size and offsets
|
|
Local Const $DEVMODE_SIZE = 220
|
|
Local Const $OFFSET_dmPelsWidth = 172
|
|
Local Const $OFFSET_dmPelsHeight = 176
|
|
|
|
Local $tDevMode = DllStructCreate("byte[" & $DEVMODE_SIZE & "]")
|
|
DllStructSetData($tDevMode, 1, $DEVMODE_SIZE, 68) ; dmSize at offset 68
|
|
|
|
Local $aTemp[100][2] ; Temporary array for width, height pairs
|
|
Local $count = 0
|
|
Local $iModeNum = 0
|
|
|
|
; Enumerate all display modes
|
|
While 1
|
|
Local $aResult = DllCall("user32.dll", "bool", "EnumDisplaySettingsW", _
|
|
"ptr", 0, _
|
|
"dword", $iModeNum, _
|
|
"struct*", $tDevMode)
|
|
|
|
If @error Or Not $aResult[0] Then ExitLoop
|
|
|
|
Local $width = DllStructGetData(DllStructCreate("dword", DllStructGetPtr($tDevMode) + $OFFSET_dmPelsWidth), 1)
|
|
Local $height = DllStructGetData(DllStructCreate("dword", DllStructGetPtr($tDevMode) + $OFFSET_dmPelsHeight), 1)
|
|
|
|
; Check for duplicate resolutions
|
|
Local $isDuplicate = False
|
|
For $i = 0 To $count - 1
|
|
If $aTemp[$i][0] = $width And $aTemp[$i][1] = $height Then
|
|
$isDuplicate = True
|
|
ExitLoop
|
|
EndIf
|
|
Next
|
|
|
|
If Not $isDuplicate And $width >= 800 And $height >= 600 Then
|
|
If $count >= UBound($aTemp) Then
|
|
ReDim $aTemp[$count + 50][2]
|
|
EndIf
|
|
$aTemp[$count][0] = $width
|
|
$aTemp[$count][1] = $height
|
|
$count += 1
|
|
EndIf
|
|
|
|
$iModeNum += 1
|
|
WEnd
|
|
|
|
; Sort by total pixels (width * height) descending
|
|
For $i = 0 To $count - 2
|
|
For $j = $i + 1 To $count - 1
|
|
Local $pixelsI = $aTemp[$i][0] * $aTemp[$i][1]
|
|
Local $pixelsJ = $aTemp[$j][0] * $aTemp[$j][1]
|
|
If $pixelsJ > $pixelsI Then
|
|
Local $tmpW = $aTemp[$i][0]
|
|
Local $tmpH = $aTemp[$i][1]
|
|
$aTemp[$i][0] = $aTemp[$j][0]
|
|
$aTemp[$i][1] = $aTemp[$j][1]
|
|
$aTemp[$j][0] = $tmpW
|
|
$aTemp[$j][1] = $tmpH
|
|
EndIf
|
|
Next
|
|
Next
|
|
|
|
; Build result array
|
|
Local $aResult[$count]
|
|
For $i = 0 To $count - 1
|
|
$aResult[$i] = $aTemp[$i][0] & "x" & $aTemp[$i][1]
|
|
Next
|
|
|
|
Return $aResult
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Plugin Management (plugins.txt)
|
|
; ============================================
|
|
Func GetPluginHeaderFlags($filePath)
|
|
; Reads the record flags of the plugin's TES4 header
|
|
Local $hFile = FileOpen($filePath, $FO_BINARY)
|
|
If $hFile = -1 Then Return 0
|
|
|
|
Local $header = FileRead($hFile, 12)
|
|
FileClose($hFile)
|
|
If BinaryLen($header) < 12 Then Return 0
|
|
If BinaryToString(BinaryMid($header, 1, 4)) <> "TES4" Then Return 0
|
|
|
|
; Record flags are a little endian dword at offset 8
|
|
Local $hex = StringTrimLeft(String(BinaryMid($header, 9, 4)), 2)
|
|
Return Dec(StringMid($hex, 7, 2) & StringMid($hex, 5, 2) & StringMid($hex, 3, 2) & StringMid($hex, 1, 2), 2)
|
|
EndFunc
|
|
|
|
Func GetPluginTypeInfo($filePath, $fileName)
|
|
; Returns [load order class, type label]
|
|
Local $ext = StringLower(StringRight($fileName, 4))
|
|
Local $flags = GetPluginHeaderFlags($filePath)
|
|
Local $bMaster = BitAND($flags, $PLUGIN_FLAG_MASTER) <> 0 Or $ext = ".esm" Or $ext = ".esl"
|
|
Local $bLight = BitAND($flags, $PLUGIN_FLAG_LIGHT) <> 0 Or $ext = ".esl"
|
|
|
|
Local $aInfo[2]
|
|
$aInfo[0] = $bMaster ? $PLUGIN_CLASS_MASTER : $PLUGIN_CLASS_NORMAL
|
|
If $bLight Then
|
|
$aInfo[1] = "Light"
|
|
ElseIf $bMaster Then
|
|
$aInfo[1] = "Master"
|
|
Else
|
|
$aInfo[1] = "Plugin"
|
|
EndIf
|
|
Return $aInfo
|
|
EndFunc
|
|
|
|
Func ScanDataPlugins()
|
|
; All plugin files present in the Data directory, sorted by name
|
|
; [i][0] = file name, [i][1] = load order class, [i][2] = type label
|
|
Local $dataDir = $LAUNCHER_DIR & "\Data"
|
|
Local $aFiles[64][3]
|
|
Local $iCount = 0
|
|
|
|
Local $hSearch = FileFindFirstFile($dataDir & "\*.es*")
|
|
If $hSearch <> -1 Then
|
|
While 1
|
|
Local $fileName = FileFindNextFile($hSearch)
|
|
If @error Then ExitLoop
|
|
If @extended Then ContinueLoop ; skip directories
|
|
|
|
Local $ext = StringLower(StringRight($fileName, 4))
|
|
If $ext <> ".esm" And $ext <> ".esp" And $ext <> ".esl" Then ContinueLoop
|
|
|
|
If $iCount >= UBound($aFiles) Then ReDim $aFiles[$iCount + 64][3]
|
|
|
|
Local $aInfo = GetPluginTypeInfo($dataDir & "\" & $fileName, $fileName)
|
|
$aFiles[$iCount][0] = $fileName
|
|
$aFiles[$iCount][1] = $aInfo[0]
|
|
$aFiles[$iCount][2] = $aInfo[1]
|
|
$iCount += 1
|
|
WEnd
|
|
FileClose($hSearch)
|
|
EndIf
|
|
|
|
ReDim $aFiles[$iCount][3]
|
|
If $iCount > 1 Then _ArraySort($aFiles, 0, 0, 0, 0)
|
|
Return $aFiles
|
|
EndFunc
|
|
|
|
Func ReadPluginsFile()
|
|
; plugins.txt entries in file order: [i][0] = file name, [i][1] = enabled
|
|
Local $aListed[1][2]
|
|
Local $iCount = 0
|
|
|
|
If FileExists($PLUGINS_FILE) Then
|
|
Local $hFile = FileOpen($PLUGINS_FILE, BitOR($FO_READ, $FO_UTF8))
|
|
If $hFile <> -1 Then
|
|
Local $content = FileRead($hFile)
|
|
FileClose($hFile)
|
|
|
|
; Drop a byte order mark left over from other tools
|
|
If StringLeft($content, 1) = ChrW(65279) Then $content = StringTrimLeft($content, 1)
|
|
|
|
Local $aLines = StringSplit(StringStripCR($content), @LF)
|
|
ReDim $aListed[$aLines[0] + 1][2]
|
|
|
|
For $i = 1 To $aLines[0]
|
|
Local $line = StringStripWS($aLines[$i], 3)
|
|
If $line = "" Or StringLeft($line, 1) = "#" Then ContinueLoop
|
|
|
|
Local $bEnabled = (StringLeft($line, 1) = "*")
|
|
If $bEnabled Then $line = StringStripWS(StringTrimLeft($line, 1), 3)
|
|
If $line = "" Then ContinueLoop
|
|
|
|
$aListed[$iCount][0] = $line
|
|
$aListed[$iCount][1] = $bEnabled
|
|
$iCount += 1
|
|
Next
|
|
EndIf
|
|
EndIf
|
|
|
|
ReDim $aListed[$iCount][2]
|
|
Return $aListed
|
|
EndFunc
|
|
|
|
Func GetCCCPlugins()
|
|
; Skyrim.ccc entries - the game loads these on its own whenever the files exist
|
|
Local $aEntries[1]
|
|
Local $iCount = 0
|
|
Local $cccFile = $LAUNCHER_DIR & "\Skyrim.ccc"
|
|
|
|
If FileExists($cccFile) Then
|
|
Local $aLines = StringSplit(StringStripCR(FileRead($cccFile)), @LF)
|
|
ReDim $aEntries[$aLines[0] + 1]
|
|
|
|
For $i = 1 To $aLines[0]
|
|
Local $line = StringStripWS($aLines[$i], 3)
|
|
If $line = "" Or StringLeft($line, 1) = "#" Then ContinueLoop
|
|
$aEntries[$iCount] = $line
|
|
$iCount += 1
|
|
Next
|
|
EndIf
|
|
|
|
ReDim $aEntries[$iCount]
|
|
Return $aEntries
|
|
EndFunc
|
|
|
|
Func GetImplicitPlugins()
|
|
; Plugins the game loads by itself, in the order it loads them: the Skyrim masters,
|
|
; the Skyrim.ccc entries, then the plugins Enderal requires
|
|
Local $aMasters[5] = ["Skyrim.esm", "Update.esm", "Dawnguard.esm", "HearthFires.esm", "Dragonborn.esm"]
|
|
Local $aCCC = GetCCCPlugins()
|
|
Local $aRequired = GetRequiredPlugins()
|
|
|
|
Local $aImplicit[UBound($aMasters) + UBound($aCCC) + UBound($aRequired)]
|
|
Local $iCount = 0
|
|
|
|
For $i = 0 To UBound($aMasters) - 1
|
|
$aImplicit[$iCount] = $aMasters[$i]
|
|
$iCount += 1
|
|
Next
|
|
For $i = 0 To UBound($aCCC) - 1
|
|
$aImplicit[$iCount] = $aCCC[$i]
|
|
$iCount += 1
|
|
Next
|
|
For $i = 0 To UBound($aRequired) - 1
|
|
$aImplicit[$iCount] = $aRequired[$i]
|
|
$iCount += 1
|
|
Next
|
|
|
|
Return $aImplicit
|
|
EndFunc
|
|
|
|
Func IsRequiredPlugin($fileName)
|
|
Local $aRequired = GetRequiredPlugins()
|
|
For $i = 0 To UBound($aRequired) - 1
|
|
If $aRequired[$i] = $fileName Then Return True
|
|
Next
|
|
Return False
|
|
EndFunc
|
|
|
|
Func FindPluginFile(ByRef $aFiles, $fileName)
|
|
For $i = 0 To UBound($aFiles) - 1
|
|
If $aFiles[$i][0] = $fileName Then Return $i
|
|
Next
|
|
Return -1
|
|
EndFunc
|
|
|
|
Func FindListedPlugin(ByRef $aListed, $fileName)
|
|
For $i = 0 To UBound($aListed) - 1
|
|
If $aListed[$i][0] = $fileName Then Return $i
|
|
Next
|
|
Return -1
|
|
EndFunc
|
|
|
|
Func FindPluginRow(ByRef $aList, $iCount, $fileName)
|
|
For $i = 0 To $iCount - 1
|
|
If $aList[$i][0] = $fileName Then Return $i
|
|
Next
|
|
Return -1
|
|
EndFunc
|
|
|
|
Func AddPluginRow(ByRef $aList, ByRef $iCount, ByRef $aFiles, $iFile, $bEnabled, $bLocked, $bListed)
|
|
$aList[$iCount][0] = $aFiles[$iFile][0]
|
|
$aList[$iCount][1] = $bEnabled
|
|
$aList[$iCount][2] = $bLocked
|
|
$aList[$iCount][3] = $aFiles[$iFile][1]
|
|
$aList[$iCount][4] = $bLocked ? $aFiles[$iFile][2] & " (fixed)" : $aFiles[$iFile][2]
|
|
$aList[$iCount][5] = $bListed
|
|
$iCount += 1
|
|
EndFunc
|
|
|
|
Func LoadPluginList()
|
|
; Keep unsaved changes when the settings are reloaded (e.g. after applying a detail preset)
|
|
If $bPluginListDirty Then Return
|
|
|
|
Local $aFiles = ScanDataPlugins()
|
|
Local $aListed = ReadPluginsFile()
|
|
Local $aImplicit = GetImplicitPlugins()
|
|
Local $iFileCount = UBound($aFiles)
|
|
|
|
Local $aList[$iFileCount + 1][6]
|
|
Local $iCount = 0
|
|
Local $iFile
|
|
|
|
; Plugins loaded by the game itself come first and cannot be changed
|
|
For $i = 0 To UBound($aImplicit) - 1
|
|
$iFile = FindPluginFile($aFiles, $aImplicit[$i])
|
|
If $iFile < 0 Then ContinueLoop ; not installed
|
|
If FindPluginRow($aList, $iCount, $aFiles[$iFile][0]) >= 0 Then ContinueLoop
|
|
; The plugins Enderal requires are always written back to plugins.txt as enabled,
|
|
; the remaining ones only when they were listed there before
|
|
Local $bListed = IsRequiredPlugin($aFiles[$iFile][0]) Or FindListedPlugin($aListed, $aFiles[$iFile][0]) >= 0
|
|
AddPluginRow($aList, $iCount, $aFiles, $iFile, True, True, $bListed)
|
|
Next
|
|
|
|
; Then the plugins.txt entries in their current order
|
|
For $i = 0 To UBound($aListed) - 1
|
|
$iFile = FindPluginFile($aFiles, $aListed[$i][0])
|
|
If $iFile < 0 Then ContinueLoop ; entry without a file - drop it
|
|
If FindPluginRow($aList, $iCount, $aFiles[$iFile][0]) >= 0 Then ContinueLoop
|
|
AddPluginRow($aList, $iCount, $aFiles, $iFile, $aListed[$i][1], False, True)
|
|
Next
|
|
|
|
; Finally the plugins that are not in plugins.txt yet, masters first
|
|
For $iClass = $PLUGIN_CLASS_MASTER To $PLUGIN_CLASS_NORMAL
|
|
For $i = 0 To $iFileCount - 1
|
|
If $aFiles[$i][1] <> $iClass Then ContinueLoop
|
|
If FindPluginRow($aList, $iCount, $aFiles[$i][0]) >= 0 Then ContinueLoop
|
|
AddPluginRow($aList, $iCount, $aFiles, $i, False, False, False)
|
|
Next
|
|
Next
|
|
|
|
; Keep masters above regular plugins - the engine loads them that way regardless of plugins.txt
|
|
Local $aSorted[$iFileCount + 1][6]
|
|
Local $iSorted = 0
|
|
For $iRank = 0 To 2
|
|
For $i = 0 To $iCount - 1
|
|
Local $iRowRank = $aList[$i][2] ? 0 : ($aList[$i][3] = $PLUGIN_CLASS_MASTER ? 1 : 2)
|
|
If $iRowRank <> $iRank Then ContinueLoop
|
|
For $c = 0 To 5
|
|
$aSorted[$iSorted][$c] = $aList[$i][$c]
|
|
Next
|
|
$iSorted += 1
|
|
Next
|
|
Next
|
|
|
|
$aPluginList = $aSorted
|
|
$iPluginListCount = $iSorted
|
|
$bPluginListLoaded = True
|
|
|
|
FillPluginListView()
|
|
EndFunc
|
|
|
|
Func CreatePluginStateImages()
|
|
; Check box images for the plugin list: the two extra dimmed ones mark rows that cannot be changed.
|
|
; Without visual styles the list keeps the check boxes the control provides itself.
|
|
If Not _WinAPI_IsThemeActive() Then Return
|
|
|
|
Local $hTheme = _WinAPI_OpenThemeData($hLvPlugins, "BUTTON")
|
|
If $hTheme = 0 Then Return
|
|
|
|
Local $hWndDC = _WinAPI_GetDC($hLvPlugins)
|
|
Local $tSize = _WinAPI_GetThemePartSize($hTheme, $THEME_BP_CHECKBOX, $THEME_CB_UNCHECKED, $hWndDC, 0, $TS_TRUE)
|
|
Local $iError = @error
|
|
_WinAPI_ReleaseDC($hLvPlugins, $hWndDC)
|
|
|
|
Local $iWidth = 16, $iHeight = 16
|
|
If Not $iError Then
|
|
$iWidth = DllStructGetData($tSize, "X")
|
|
$iHeight = DllStructGetData($tSize, "Y")
|
|
EndIf
|
|
|
|
If $iWidth >= 8 And $iHeight >= 8 Then
|
|
Local $hBitmap = CreateTransparentBitmap($iWidth * 4, $iHeight)
|
|
If $hBitmap <> 0 Then
|
|
; Draw the four states next to each other, in state image index order
|
|
Local $aStates[4] = [$THEME_CB_UNCHECKED, $THEME_CB_CHECKED, _
|
|
$THEME_CB_UNCHECKED_DISABLED, $THEME_CB_CHECKED_DISABLED]
|
|
Local $hMemDC = _WinAPI_CreateCompatibleDC(0)
|
|
Local $hPrevBitmap = _WinAPI_SelectObject($hMemDC, $hBitmap)
|
|
Local $tRect = DllStructCreate($tagRECT)
|
|
DllStructSetData($tRect, "Top", 0)
|
|
DllStructSetData($tRect, "Bottom", $iHeight)
|
|
|
|
For $i = 0 To UBound($aStates) - 1
|
|
DllStructSetData($tRect, "Left", $i * $iWidth)
|
|
DllStructSetData($tRect, "Right", ($i + 1) * $iWidth)
|
|
_WinAPI_DrawThemeBackground($hTheme, $THEME_BP_CHECKBOX, $aStates[$i], $hMemDC, $tRect)
|
|
Next
|
|
|
|
_WinAPI_SelectObject($hMemDC, $hPrevBitmap)
|
|
_WinAPI_DeleteDC($hMemDC)
|
|
|
|
$hPluginStateImages = _GUIImageList_Create($iWidth, $iHeight, 5, 0, 4, 4)
|
|
If $hPluginStateImages <> 0 Then
|
|
_GUIImageList_Add($hPluginStateImages, $hBitmap)
|
|
_GUICtrlListView_SetImageList($hLvPlugins, $hPluginStateImages, 2) ; 2 = LVSIL_STATE
|
|
EndIf
|
|
|
|
_WinAPI_DeleteObject($hBitmap)
|
|
EndIf
|
|
EndIf
|
|
|
|
_WinAPI_CloseThemeData($hTheme)
|
|
EndFunc
|
|
|
|
Func CreateTransparentBitmap($iWidth, $iHeight)
|
|
; Top down 32 bit device independent bitmap, cleared to fully transparent
|
|
Local $tBitmapInfo = DllStructCreate("dword Size;long Width;long Height;ushort Planes;ushort BitCount;" & _
|
|
"dword Compression;dword SizeImage;long XPelsPerMeter;long YPelsPerMeter;dword ClrUsed;dword ClrImportant")
|
|
DllStructSetData($tBitmapInfo, "Size", DllStructGetSize($tBitmapInfo))
|
|
DllStructSetData($tBitmapInfo, "Width", $iWidth)
|
|
DllStructSetData($tBitmapInfo, "Height", -$iHeight)
|
|
DllStructSetData($tBitmapInfo, "Planes", 1)
|
|
DllStructSetData($tBitmapInfo, "BitCount", 32)
|
|
DllStructSetData($tBitmapInfo, "Compression", 0) ; BI_RGB
|
|
|
|
Local $aCall = DllCall("gdi32.dll", "handle", "CreateDIBSection", "handle", 0, "struct*", $tBitmapInfo, _
|
|
"uint", 0, "ptr*", 0, "handle", 0, "dword", 0)
|
|
If @error Or Not IsArray($aCall) Then Return 0
|
|
Return $aCall[0]
|
|
EndFunc
|
|
|
|
Func IsPluginRowFixed($iIndex)
|
|
; True when the row cannot be toggled: plugins the game loads itself, or MO2 owning the load order
|
|
If $bPluginListReadOnly Then Return True
|
|
Return $aPluginList[$iIndex][2]
|
|
EndFunc
|
|
|
|
Func SetPluginRowState($iIndex)
|
|
If $hPluginStateImages = 0 Then
|
|
; No dimmed images available - fall back to the plain check box
|
|
_GUICtrlListView_SetItemChecked($hLvPlugins, $iIndex, $aPluginList[$iIndex][1])
|
|
Return
|
|
EndIf
|
|
|
|
Local $iState
|
|
If IsPluginRowFixed($iIndex) Then
|
|
$iState = $aPluginList[$iIndex][1] ? $PLUGIN_STATE_CHECKED_DIMMED : $PLUGIN_STATE_UNCHECKED_DIMMED
|
|
Else
|
|
$iState = $aPluginList[$iIndex][1] ? $PLUGIN_STATE_CHECKED : $PLUGIN_STATE_UNCHECKED
|
|
EndIf
|
|
|
|
_GUICtrlListView_SetItemState($hLvPlugins, $iIndex, BitShift($iState, -12), $LVIS_STATEIMAGEMASK)
|
|
EndFunc
|
|
|
|
Func FillPluginListView()
|
|
$bPluginListUpdating = True
|
|
_GUICtrlListView_BeginUpdate($hLvPlugins)
|
|
_GUICtrlListView_DeleteAllItems($hLvPlugins)
|
|
|
|
For $i = 0 To $iPluginListCount - 1
|
|
_GUICtrlListView_AddItem($hLvPlugins, $aPluginList[$i][0])
|
|
_GUICtrlListView_AddSubItem($hLvPlugins, $i, $aPluginList[$i][4], 1)
|
|
SetPluginRowState($i)
|
|
Next
|
|
|
|
_GUICtrlListView_EndUpdate($hLvPlugins)
|
|
$bPluginListUpdating = False
|
|
|
|
UpdatePluginColumnWidths()
|
|
UpdatePluginButtons()
|
|
EndFunc
|
|
|
|
Func UpdatePluginColumnWidths()
|
|
; Fit both columns into the visible area, with or without a scroll bar
|
|
Local $tRect = _WinAPI_GetClientRect($hLvPlugins)
|
|
If @error Then Return
|
|
|
|
Local $iClientWidth = DllStructGetData($tRect, "Right") - DllStructGetData($tRect, "Left")
|
|
Local $iTypeWidth = 120
|
|
If $iClientWidth < $iTypeWidth * 2 Then Return
|
|
|
|
_GUICtrlListView_SetColumnWidth($hLvPlugins, 1, $iTypeWidth)
|
|
_GUICtrlListView_SetColumnWidth($hLvPlugins, 0, $iClientWidth - $iTypeWidth)
|
|
EndFunc
|
|
|
|
Func UpdatePluginRow($iIndex)
|
|
$bPluginListUpdating = True
|
|
_GUICtrlListView_SetItemText($hLvPlugins, $iIndex, $aPluginList[$iIndex][0], 0)
|
|
_GUICtrlListView_SetItemText($hLvPlugins, $iIndex, $aPluginList[$iIndex][4], 1)
|
|
SetPluginRowState($iIndex)
|
|
$bPluginListUpdating = False
|
|
EndFunc
|
|
|
|
Func GetSelectedPluginIndex()
|
|
If $hLvPlugins = 0 Then Return -1
|
|
|
|
Local $aIndices = _GUICtrlListView_GetSelectedIndices($hLvPlugins, True)
|
|
If UBound($aIndices) < 2 Or $aIndices[0] < 1 Then Return -1
|
|
Return $aIndices[1]
|
|
EndFunc
|
|
|
|
Func CanMovePlugin($iIndex, $iTarget)
|
|
If $bPluginListReadOnly Then Return False
|
|
If $iIndex < 0 Or $iIndex >= $iPluginListCount Then Return False
|
|
If $iTarget < 0 Or $iTarget >= $iPluginListCount Then Return False
|
|
; Plugins loaded by the game itself keep their position
|
|
If $aPluginList[$iIndex][2] Or $aPluginList[$iTarget][2] Then Return False
|
|
; Masters cannot be mixed with regular plugins
|
|
If $aPluginList[$iIndex][3] <> $aPluginList[$iTarget][3] Then Return False
|
|
Return True
|
|
EndFunc
|
|
|
|
Func MoveSelectedPlugin($iDirection)
|
|
Local $iIndex = GetSelectedPluginIndex()
|
|
Local $iTarget = $iIndex + $iDirection
|
|
If Not CanMovePlugin($iIndex, $iTarget) Then Return
|
|
|
|
For $c = 0 To 5
|
|
Local $swap = $aPluginList[$iIndex][$c]
|
|
$aPluginList[$iIndex][$c] = $aPluginList[$iTarget][$c]
|
|
$aPluginList[$iTarget][$c] = $swap
|
|
Next
|
|
|
|
UpdatePluginRow($iIndex)
|
|
UpdatePluginRow($iTarget)
|
|
|
|
$bPluginListUpdating = True
|
|
_GUICtrlListView_SetItemSelected($hLvPlugins, $iIndex, False)
|
|
_GUICtrlListView_SetItemSelected($hLvPlugins, $iTarget, True, True)
|
|
$bPluginListUpdating = False
|
|
_GUICtrlListView_EnsureVisible($hLvPlugins, $iTarget)
|
|
|
|
$bPluginListDirty = True
|
|
UpdatePluginButtons()
|
|
EndFunc
|
|
|
|
Func SetAllPluginsEnabled($bEnabled)
|
|
If $bPluginListReadOnly Then Return
|
|
|
|
For $i = 0 To $iPluginListCount - 1
|
|
If $aPluginList[$i][2] Then ContinueLoop ; locked plugins stay enabled
|
|
If $aPluginList[$i][1] = $bEnabled Then ContinueLoop
|
|
$aPluginList[$i][1] = $bEnabled
|
|
UpdatePluginRow($i)
|
|
$bPluginListDirty = True
|
|
Next
|
|
|
|
UpdatePluginButtons()
|
|
EndFunc
|
|
|
|
Func UpdatePluginButtons()
|
|
Local $iIndex = GetSelectedPluginIndex()
|
|
GUICtrlSetState($btnPluginUp, CanMovePlugin($iIndex, $iIndex - 1) ? $GUI_ENABLE : $GUI_DISABLE)
|
|
GUICtrlSetState($btnPluginDown, CanMovePlugin($iIndex, $iIndex + 1) ? $GUI_ENABLE : $GUI_DISABLE)
|
|
|
|
Local $iEnabled = 0
|
|
Local $iChangeable = 0
|
|
For $i = 0 To $iPluginListCount - 1
|
|
If $aPluginList[$i][1] Then $iEnabled += 1
|
|
If Not IsPluginRowFixed($i) Then $iChangeable += 1
|
|
Next
|
|
|
|
GUICtrlSetData($lblPluginsCount, $iEnabled & " of " & $iPluginListCount & " enabled")
|
|
GUICtrlSetState($btnPluginsEnableAll, $iChangeable > 0 ? $GUI_ENABLE : $GUI_DISABLE)
|
|
GUICtrlSetState($btnPluginsDisableAll, $iChangeable > 0 ? $GUI_ENABLE : $GUI_DISABLE)
|
|
EndFunc
|
|
|
|
Func SavePluginsFile()
|
|
; Under MO2 the load order is owned by the active profile
|
|
If $bPluginListReadOnly Then Return
|
|
If Not $bPluginListLoaded Or Not $bPluginListDirty Or $iPluginListCount = 0 Then Return
|
|
|
|
Local $content = ""
|
|
For $i = 0 To $iPluginListCount - 1
|
|
; Plugins the game loads on its own are only written back when they were listed before
|
|
If $aPluginList[$i][2] And Not $aPluginList[$i][5] Then ContinueLoop
|
|
Local $bEnabled = $aPluginList[$i][2] ? True : $aPluginList[$i][1]
|
|
$content &= ($bEnabled ? "*" : "") & $aPluginList[$i][0] & @CRLF
|
|
Next
|
|
|
|
If Not DirExists($PLUGINS_DIR) Then DirCreate($PLUGINS_DIR)
|
|
|
|
Local $hFile = FileOpen($PLUGINS_FILE, BitOR($FO_OVERWRITE, $FO_UTF8_NOBOM))
|
|
If $hFile = -1 Then
|
|
MsgBox(16, "Error", "Unable to write the plugin list:" & @CRLF & $PLUGINS_FILE)
|
|
Return
|
|
EndIf
|
|
|
|
FileWrite($hFile, $content)
|
|
FileClose($hFile)
|
|
|
|
$bPluginListDirty = False
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Main Loop
|
|
; ============================================
|
|
Func MainLoop()
|
|
While 1
|
|
Local $msg = GUIGetMsg(1)
|
|
Local $msgID = $msg[0]
|
|
Local $msgHwnd = $msg[1]
|
|
|
|
Switch $msgID
|
|
Case $GUI_EVENT_CLOSE
|
|
If $msgHwnd = $hMainGUI Then
|
|
Exit
|
|
ElseIf $msgHwnd = $hSettingsGUI Then
|
|
HideSettings()
|
|
EndIf
|
|
|
|
Case $btnClose, $lblCloseShadow
|
|
Exit
|
|
|
|
Case $btnMinimize, $lblMinimizeShadow
|
|
GUISetState(@SW_MINIMIZE, $hMainGUI)
|
|
|
|
Case $btnExit
|
|
Exit
|
|
|
|
Case $btnPlay
|
|
LaunchGame()
|
|
|
|
Case $btnSettings
|
|
ShowSettings()
|
|
|
|
Case $lblCredits, $lblCreditsShadow
|
|
ShellExecute("https://mod.pub/enderal-se/38/docs/5-credits")
|
|
|
|
Case $lblWiki, $lblWikiShadow
|
|
ShellExecute("https://wiki.en.sureai.net/Enderal")
|
|
|
|
Case $lblSupport, $lblSupportShadow
|
|
ShellExecute("https://go.eddoursul.win/enderal-se-launcher-support")
|
|
|
|
Case $lblChangelog, $lblChangelogShadow
|
|
ShellExecute("https://mod.pub/enderal-se/38/changelogs")
|
|
|
|
Case $btnSave
|
|
SaveSettings()
|
|
HideSettings()
|
|
|
|
Case $btnCancel
|
|
HideSettings()
|
|
|
|
Case $btnLow
|
|
ApplyDetailPreset("Low")
|
|
|
|
Case $btnMedium
|
|
ApplyDetailPreset("Medium")
|
|
|
|
Case $btnHigh
|
|
ApplyDetailPreset("High")
|
|
|
|
Case $btnUltra
|
|
ApplyDetailPreset("Ultra")
|
|
|
|
Case $chkSSEDisplayTweaks
|
|
RefreshResolution()
|
|
|
|
Case $sldFramerateLimit
|
|
GUICtrlSetData($lblFramerateLimitValue, GUICtrlRead($sldFramerateLimit))
|
|
|
|
Case $btnOpenEnderalIni
|
|
RunWithAssociation($ENDERAL_INI)
|
|
|
|
Case $btnOpenEnderalPrefsIni
|
|
RunWithAssociation($ENDERAL_PREFS_INI)
|
|
|
|
Case $btnOpenCustomIni
|
|
OpenCustomIni()
|
|
|
|
Case $btnOpenModIni
|
|
OpenModIni()
|
|
|
|
Case $btnOpenSavegames
|
|
OpenSavegamesDirectory()
|
|
|
|
Case $btnPluginUp
|
|
MoveSelectedPlugin(-1)
|
|
|
|
Case $btnPluginDown
|
|
MoveSelectedPlugin(1)
|
|
|
|
Case $btnPluginsEnableAll
|
|
SetAllPluginsEnabled(True)
|
|
|
|
Case $btnPluginsDisableAll
|
|
SetAllPluginsEnabled(False)
|
|
|
|
EndSwitch
|
|
WEnd
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Settings Show/Hide
|
|
; ============================================
|
|
Func ShowSettings()
|
|
; Load settings values
|
|
LoadSettings()
|
|
|
|
; Select first tab
|
|
GUICtrlSetData($tabSettings, 0)
|
|
|
|
; Show settings window
|
|
GUISetState(@SW_SHOW, $hSettingsGUI)
|
|
EndFunc
|
|
|
|
Func HideSettings()
|
|
; Drop unsaved plugin changes so the list is read again the next time
|
|
$bPluginListDirty = False
|
|
GUISetState(@SW_HIDE, $hSettingsGUI)
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Settings Load/Save
|
|
; ============================================
|
|
Func RefreshResolution()
|
|
; Read base resolution from Skyrim prefs
|
|
Local $width = IniRead($ENDERAL_PREFS_INI, "Display", "iSize W", @DesktopWidth)
|
|
Local $height = IniRead($ENDERAL_PREFS_INI, "Display", "iSize H", @DesktopHeight)
|
|
Local $resolution = $width & "x" & $height
|
|
|
|
; If SSE Display Tweaks checkbox is checked, read from its INI
|
|
Local $sseDisplayTweaksIni = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SSEDisplayTweaks.ini"
|
|
If BitAND(GUICtrlRead($chkSSEDisplayTweaks), $GUI_CHECKED) And FileExists($sseDisplayTweaksIni) Then
|
|
Local $sseRes = IniRead($sseDisplayTweaksIni, "Render", "Resolution", "")
|
|
If $sseRes <> "" Then
|
|
$resolution = $sseRes
|
|
EndIf
|
|
; Load and enable framerate limit
|
|
Local $fpsLimit = IniRead($sseDisplayTweaksIni, "Render", "FramerateLimit", "60")
|
|
If Number($fpsLimit) < 30 Then $fpsLimit = 30
|
|
If Number($fpsLimit) > @DesktopRefresh Then $fpsLimit = @DesktopRefresh
|
|
GUICtrlSetData($sldFramerateLimit, $fpsLimit)
|
|
GUICtrlSetData($lblFramerateLimitValue, $fpsLimit)
|
|
GUICtrlSetState($sldFramerateLimit, $GUI_ENABLE)
|
|
GUICtrlSetState($lblFramerateLimit, $GUI_ENABLE)
|
|
GUICtrlSetState($lblFramerateLimitValue, $GUI_ENABLE)
|
|
Else
|
|
; Disable framerate limit slider
|
|
GUICtrlSetData($sldFramerateLimit, 60)
|
|
GUICtrlSetData($lblFramerateLimitValue, "60")
|
|
GUICtrlSetState($sldFramerateLimit, $GUI_DISABLE)
|
|
GUICtrlSetState($lblFramerateLimit, $GUI_DISABLE)
|
|
GUICtrlSetState($lblFramerateLimitValue, $GUI_DISABLE)
|
|
EndIf
|
|
|
|
GUICtrlSetData($cmbResolution, $resolution)
|
|
EndFunc
|
|
|
|
Func LoadSettings()
|
|
; SSE Display Tweaks paths (used in multiple places)
|
|
Local $dllPath = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SSEDisplayTweaks.dll"
|
|
Local $bakPath = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SSEDisplayTweaks.dll.bak"
|
|
Local $sseDisplayTweaksIni = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SSEDisplayTweaks.ini"
|
|
|
|
; Load Display settings
|
|
Local $width = IniRead($ENDERAL_PREFS_INI, "Display", "iSize W", @DesktopWidth)
|
|
Local $height = IniRead($ENDERAL_PREFS_INI, "Display", "iSize H", @DesktopHeight)
|
|
Local $resolution = $width & "x" & $height
|
|
|
|
; If SSE Display Tweaks is enabled, read resolution from its INI
|
|
If FileExists($dllPath) And FileExists($sseDisplayTweaksIni) Then
|
|
Local $sseRes = IniRead($sseDisplayTweaksIni, "Render", "Resolution", "")
|
|
If $sseRes <> "" Then
|
|
$resolution = $sseRes
|
|
EndIf
|
|
EndIf
|
|
|
|
GUICtrlSetData($cmbResolution, $resolution)
|
|
|
|
; Load Antialiasing
|
|
Local $taa = IniRead($ENDERAL_PREFS_INI, "Display", "bUseTAA", "0")
|
|
Local $fxaa = IniRead($ENDERAL_PREFS_INI, "Display", "bFXAAEnabled", "0")
|
|
If $taa = "1" Then
|
|
GUICtrlSetData($cmbAntialiasing, $AA_TAA)
|
|
ElseIf $fxaa = "1" Then
|
|
GUICtrlSetData($cmbAntialiasing, $AA_FXAA)
|
|
Else
|
|
GUICtrlSetData($cmbAntialiasing, $AA_OFF)
|
|
EndIf
|
|
|
|
; Load Windowed Mode
|
|
Local $fullscreen = IniRead($ENDERAL_PREFS_INI, "Display", "bFull Screen", "1")
|
|
SetCheckboxState($chkWindowed, $fullscreen, "0")
|
|
|
|
; Load Rendering settings
|
|
If FileExists($dllPath) Then
|
|
GUICtrlSetData($grpSSEDisplayTweaks, "SSE Display Tweaks")
|
|
GUICtrlSetState($chkSSEDisplayTweaks, $GUI_CHECKED + $GUI_ENABLE)
|
|
; Load framerate limit from INI
|
|
Local $fpsLimit = IniRead($sseDisplayTweaksIni, "Render", "FramerateLimit", "60")
|
|
If Number($fpsLimit) < 30 Then $fpsLimit = 30
|
|
If Number($fpsLimit) > @DesktopRefresh Then $fpsLimit = @DesktopRefresh
|
|
GUICtrlSetData($sldFramerateLimit, $fpsLimit)
|
|
GUICtrlSetData($lblFramerateLimitValue, $fpsLimit)
|
|
GUICtrlSetState($sldFramerateLimit, $GUI_ENABLE)
|
|
GUICtrlSetState($lblFramerateLimit, $GUI_ENABLE)
|
|
GUICtrlSetState($lblFramerateLimitValue, $GUI_ENABLE)
|
|
ElseIf FileExists($bakPath) Then
|
|
GUICtrlSetData($grpSSEDisplayTweaks, "SSE Display Tweaks")
|
|
GUICtrlSetState($chkSSEDisplayTweaks, $GUI_UNCHECKED + $GUI_ENABLE)
|
|
GUICtrlSetData($sldFramerateLimit, 60)
|
|
GUICtrlSetData($lblFramerateLimitValue, "60")
|
|
GUICtrlSetState($sldFramerateLimit, $GUI_DISABLE)
|
|
GUICtrlSetState($lblFramerateLimit, $GUI_DISABLE)
|
|
GUICtrlSetState($lblFramerateLimitValue, $GUI_DISABLE)
|
|
Else
|
|
GUICtrlSetData($grpSSEDisplayTweaks, "SSE Display Tweaks (not installed)")
|
|
GUICtrlSetState($chkSSEDisplayTweaks, $GUI_UNCHECKED + $GUI_DISABLE)
|
|
GUICtrlSetData($sldFramerateLimit, 60)
|
|
GUICtrlSetData($lblFramerateLimitValue, "60")
|
|
GUICtrlSetState($sldFramerateLimit, $GUI_DISABLE)
|
|
GUICtrlSetState($lblFramerateLimit, $GUI_DISABLE)
|
|
GUICtrlSetState($lblFramerateLimitValue, $GUI_DISABLE)
|
|
EndIf
|
|
|
|
SetCheckboxState($chk64bitRT, IniRead($ENDERAL_PREFS_INI, "Display", "bUse64bitsHDRRenderTarget", "0"))
|
|
SetCheckboxState($chkSSAO, IniRead($ENDERAL_PREFS_INI, "Display", "bSAOEnable", "0"))
|
|
SetCheckboxState($chkVolumetricLighting, IniRead($ENDERAL_PREFS_INI, "Display", "bVolumetricLightingEnable", "0"))
|
|
|
|
Local $ssr = IniReadWithCustom("Display", "fWaterSSRIntensity", "0.0001")
|
|
SetCheckboxState($chkSSR, Number($ssr) > 1 ? "1" : "0")
|
|
|
|
; Load Controls settings
|
|
SetCheckboxState($chkController, IniRead($ENDERAL_PREFS_INI, "MAIN", "bGamepadEnable", "0"))
|
|
SetCheckboxState($chkVibration, IniRead($ENDERAL_PREFS_INI, "Controls", "bGamePadRumble", "0"))
|
|
|
|
; Update INI Files group label based on SkyrimRedirector status
|
|
Local $redirectorDll = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SkyrimRedirector.dll"
|
|
If FileExists($redirectorDll) Then
|
|
GUICtrlSetData($grpIniFiles, "INI Files (SkyrimRedirector is active)")
|
|
Else
|
|
GUICtrlSetData($grpIniFiles, "INI Files (SkyrimRedirector is not installed)")
|
|
EndIf
|
|
|
|
; Load Start Steam checkbox (Steam version only)
|
|
If $bIsSteamVersion Then
|
|
Local $startSteam = RegRead($REG_KEY, $REG_START_STEAM)
|
|
If @error Then $startSteam = 1
|
|
SetCheckboxState($chkStartSteam, $startSteam)
|
|
EndIf
|
|
|
|
; Load the plugin list
|
|
LoadPluginList()
|
|
EndFunc
|
|
|
|
Func SaveSettings()
|
|
; Save Resolution
|
|
Local $resolution = GUICtrlRead($cmbResolution)
|
|
Local $resParts = StringSplit($resolution, "x")
|
|
If $resParts[0] = 2 Then
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "iSize W", $resParts[1])
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "iSize H", $resParts[2])
|
|
|
|
; Sync resolution to SSEDisplayTweaks.ini if present
|
|
Local $sseDisplayTweaksIni = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SSEDisplayTweaks.ini"
|
|
If FileExists($sseDisplayTweaksIni) Then
|
|
Local $currentRes = IniRead($sseDisplayTweaksIni, "Render", "Resolution", "")
|
|
If $currentRes <> "" Then
|
|
IniWrite($sseDisplayTweaksIni, "Render", "Resolution", $resolution)
|
|
EndIf
|
|
EndIf
|
|
EndIf
|
|
|
|
; Save Antialiasing
|
|
Local $aa = GUICtrlRead($cmbAntialiasing)
|
|
Switch $aa
|
|
Case $AA_TAA
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "bUseTAA", "1")
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "bFXAAEnabled", "0")
|
|
Case $AA_FXAA
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "bUseTAA", "0")
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "bFXAAEnabled", "1")
|
|
Case $AA_OFF
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "bUseTAA", "0")
|
|
IniWrite($ENDERAL_PREFS_INI, "Display", "bFXAAEnabled", "0")
|
|
EndSwitch
|
|
|
|
; Save Windowed Mode (inverted: checked = "0" for fullscreen)
|
|
WriteCheckboxToIni($chkWindowed, $ENDERAL_PREFS_INI, "Display", "bFull Screen", "0", "1")
|
|
|
|
; Save SSE Display Tweaks (file-based toggle)
|
|
Local $dllPath = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SSEDisplayTweaks.dll"
|
|
Local $bakPath = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SSEDisplayTweaks.dll.bak"
|
|
Local $sseDisplayTweaksIni = $LAUNCHER_DIR & "\Data\SKSE\Plugins\SSEDisplayTweaks.ini"
|
|
If BitAND(GUICtrlRead($chkSSEDisplayTweaks), $GUI_CHECKED) Then
|
|
If FileExists($bakPath) And Not FileExists($dllPath) Then FileMove($bakPath, $dllPath)
|
|
; Save framerate limit
|
|
If FileExists($sseDisplayTweaksIni) Then
|
|
IniWrite($sseDisplayTweaksIni, "Render", "FramerateLimit", GUICtrlRead($sldFramerateLimit))
|
|
EndIf
|
|
Else
|
|
If FileExists($dllPath) Then FileMove($dllPath, $bakPath, 1)
|
|
EndIf
|
|
|
|
; Save Rendering settings
|
|
WriteCheckboxToIni($chk64bitRT, $ENDERAL_PREFS_INI, "Display", "bUse64bitsHDRRenderTarget")
|
|
WriteCheckboxToIni($chkSSAO, $ENDERAL_PREFS_INI, "Display", "bSAOEnable")
|
|
WriteCheckboxToIni($chkVolumetricLighting, $ENDERAL_PREFS_INI, "Display", "bVolumetricLightingEnable")
|
|
WriteCheckboxWithCustom($chkSSR, "Display", "fWaterSSRIntensity", "1.3", "0.0001")
|
|
|
|
; Save Controls settings
|
|
WriteCheckboxToIni($chkController, $ENDERAL_PREFS_INI, "MAIN", "bGamepadEnable")
|
|
WriteCheckboxToIni($chkVibration, $ENDERAL_PREFS_INI, "Controls", "bGamePadRumble")
|
|
|
|
; Save Start Steam checkbox (Steam version only)
|
|
If $bIsSteamVersion Then
|
|
Local $startSteam = (GUICtrlRead($chkStartSteam) = $GUI_CHECKED) ? 1 : 0
|
|
RegWrite($REG_KEY, $REG_START_STEAM, "REG_DWORD", $startSteam)
|
|
EndIf
|
|
|
|
; Save the plugin list
|
|
SavePluginsFile()
|
|
EndFunc
|
|
|
|
Func ApplyDetailPreset($presetName)
|
|
Local $presetFile = $LAUNCHER_DIR & "\" & $presetName & ".ini"
|
|
If Not FileExists($presetFile) Then
|
|
MsgBox(16, "Error", "Preset file not found: " & $presetFile)
|
|
Return
|
|
EndIf
|
|
|
|
Local $sections = IniReadSectionNames($presetFile)
|
|
If @error Then
|
|
MsgBox(16, "Error", "Could not read preset file " & $presetFile)
|
|
Return
|
|
EndIf
|
|
|
|
For $i = 1 To $sections[0]
|
|
Local $sectionData = IniReadSection($presetFile, $sections[$i])
|
|
If Not @error Then
|
|
For $j = 1 To $sectionData[0][0]
|
|
IniWrite($ENDERAL_PREFS_INI, $sections[$i], $sectionData[$j][0], $sectionData[$j][1])
|
|
Next
|
|
EndIf
|
|
Next
|
|
|
|
MsgBox(64, "Settings Updated", "Video settings have been updated.")
|
|
LoadSettings()
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Game Launch
|
|
; ============================================
|
|
Func LaunchGame()
|
|
If Not FileExists($LAUNCHER_DIR & "\skse64_loader.exe") Then
|
|
MsgBox(16, "Error", "Unable to find skse64_loader.exe! SKSE is not installed.")
|
|
Return
|
|
EndIf
|
|
|
|
; Hide main buttons
|
|
GUICtrlSetState($btnPlay, $GUI_HIDE)
|
|
GUICtrlSetState($btnSettings, $GUI_HIDE)
|
|
GUICtrlSetState($btnExit, $GUI_HIDE)
|
|
|
|
; Create launching modal (aligned with main buttons)
|
|
Local $modalWidth = 200
|
|
Local $modalHeight = 70
|
|
Local $modalX = $LAUNCHER_WIDTH - $modalWidth - 60
|
|
Local $modalY = ($LAUNCHER_HEIGHT - $modalHeight) / 2
|
|
Local $hLaunchingGUI = GUICreate("", $modalWidth, $modalHeight, $modalX, $modalY, $WS_POPUP, $WS_EX_MDICHILD, $hMainGUI)
|
|
GUISetBkColor(0x1A1A1A, $hLaunchingGUI)
|
|
GUICtrlCreateLabel("Launching...", 0, 20, $modalWidth, 35, $SS_CENTER)
|
|
GUICtrlSetFont(-1, 14, 400, 0, "Tahoma")
|
|
GUICtrlSetColor(-1, 0xFFFFFF)
|
|
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)
|
|
GUISetState(@SW_SHOW, $hLaunchingGUI)
|
|
|
|
; Set DPI awareness for SkyrimSE.exe
|
|
SetDPIAwareness()
|
|
|
|
; Launch SKSE
|
|
Run($LAUNCHER_DIR & "\skse64_loader.exe", $LAUNCHER_DIR)
|
|
|
|
; Wait for SkyrimSE.exe window to appear (GOG or Steam version)
|
|
While 1
|
|
If WinExists("[CLASS:Skyrim Special Edition GOG]") Or WinExists("[CLASS:Skyrim Special Edition]") Then
|
|
ExitLoop
|
|
EndIf
|
|
|
|
; Also check if the process is no longer running (user closed it quickly)
|
|
If Not ProcessExists("SkyrimSE.exe") And Not ProcessExists("skse64_loader.exe") Then
|
|
; Process ended before window appeared, close modal and restore buttons
|
|
GUIDelete($hLaunchingGUI)
|
|
GUICtrlSetState($btnPlay, $GUI_SHOW)
|
|
GUICtrlSetState($btnSettings, $GUI_SHOW)
|
|
GUICtrlSetState($btnExit, $GUI_SHOW)
|
|
Return
|
|
EndIf
|
|
|
|
Sleep(100)
|
|
WEnd
|
|
|
|
; SkyrimSE.exe window exists, exit launcher
|
|
Exit
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Get Mod Organizer 2 Directory
|
|
; ============================================
|
|
Func GetMO2Directory()
|
|
Local $parentPID = _WinAPI_GetParentProcess()
|
|
If @error Or $parentPID = 0 Then Return ""
|
|
|
|
Local $parentPath = _WinAPI_GetProcessFileName($parentPID)
|
|
If @error Or $parentPath = "" Then Return ""
|
|
|
|
; Check if parent is ModOrganizer.exe
|
|
If StringInStr($parentPath, "ModOrganizer.exe") Then
|
|
Return StringRegExpReplace($parentPath, "\\[^\\]+$", "")
|
|
EndIf
|
|
|
|
Return ""
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Open Savegames Directory
|
|
; ============================================
|
|
Func OpenSavegamesDirectory()
|
|
Local $savePath = IniReadWithCustom("General", "sLocalSavePath", "Saves\")
|
|
Local $skyrimDocsDir = GetDocumentsPath() & "\My Games\" & GetBaseDirectoryName()
|
|
Local $fullSavePath = $skyrimDocsDir & "\" & $savePath
|
|
If Not DirExists($fullSavePath) Then
|
|
MsgBox(16, "Error", "Savegames directory not found: " & $fullSavePath)
|
|
Return
|
|
EndIf
|
|
|
|
; Check if running under Mod Organizer (parent process is ModOrganizer.exe)
|
|
If $MO2_DIR <> "" Then
|
|
Local $explorerPPPath = $MO2_DIR & "\explorer++\Explorer++.exe"
|
|
If FileExists($explorerPPPath) Then
|
|
Run('"' & $explorerPPPath & '" "' & $fullSavePath & '"')
|
|
Return
|
|
EndIf
|
|
; Explorer++ not found, show path in message box
|
|
MsgBox(64, "Saved Games Location", "Your saved games are located at:" & @CRLF & @CRLF & $fullSavePath)
|
|
Else
|
|
; Not running under MO2, open normally
|
|
ShellExecute($fullSavePath)
|
|
EndIf
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Open Custom INI File
|
|
; ============================================
|
|
Func OpenCustomIni()
|
|
Local $customIniPath = GetCustomIniPath()
|
|
If FileExists($customIniPath) Then
|
|
RunWithAssociation($customIniPath)
|
|
EndIf
|
|
EndFunc
|
|
|
|
; ============================================
|
|
; Open Mod INI File
|
|
; ============================================
|
|
Func OpenModIni()
|
|
Local $modIniPath = GetDataIniPath()
|
|
If FileExists($modIniPath) Then
|
|
RunWithAssociation($modIniPath)
|
|
EndIf
|
|
EndFunc
|