enderalse/source/Launcher/Enderal Launcher.au3

1518 lines
58 KiB (Stored with Git LFS)
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 <WinAPI.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)"
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
; Steam version detection
Global $bIsSteamVersion = False
; ============================================
; 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
; ============================================
; 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
; ============================================
; Main Entry Point
; ============================================
Main()
Func Main()
; 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")
; 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
; ============================================
; 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")
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 CheckPluginsFile()
Local $requiredLines[2] = ["*Enderal - Forgotten Stories.esm", "*SkyUI_SE.esp"]
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)
; Support - shadow first, then main label
$lblSupportShadow = GUICtrlCreateLabel("Support", $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("Support", $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 + 15 + 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 + 15, $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()
; 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 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
; ============================================
; 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://mod.pub/enderal-se/38/comments")
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()
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()
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
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
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)
; Launch SKSE
Run($LAUNCHER_DIR & "\skse64_loader.exe", $LAUNCHER_DIR)
; Wait for SkyrimSE.exe to gain focus
Local $hSkyrimWnd = 0
While 1
; Check if SkyrimSE.exe window exists and has focus
$hSkyrimWnd = WinGetHandle("[CLASS:Skyrim Special Edition]")
If $hSkyrimWnd Then
; SkyrimSE window exists, check if it has focus
If WinActive($hSkyrimWnd) Then
ExitLoop
EndIf
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 gaining focus, 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 has focus, 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)
Local $mo2Dir = GetMO2Directory()
If $mo2Dir <> "" Then
Local $explorerPPPath = $mo2Dir & "\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