Added plugins.txt manager to the launcher
This commit is contained in:
parent
734ee8888f
commit
2eaab91a27
@ -10,7 +10,18 @@
|
||||
#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>
|
||||
|
||||
@ -33,6 +44,22 @@ 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 = ""
|
||||
@ -71,9 +98,28 @@ 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
|
||||
; ============================================
|
||||
@ -562,6 +608,9 @@ Func Main()
|
||||
; 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()
|
||||
|
||||
@ -586,6 +635,80 @@ Func WM_HSCROLL($hWnd, $iMsg, $wParam, $lParam)
|
||||
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
|
||||
; ============================================
|
||||
@ -602,6 +725,11 @@ Func StartupChecks()
|
||||
; 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()
|
||||
@ -633,8 +761,24 @@ Func CheckRegistryPaths()
|
||||
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 $requiredLines[2] = ["*Enderal - Forgotten Stories.esm", "*SkyUI_SE.esp"]
|
||||
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)
|
||||
@ -1036,6 +1180,10 @@ Func CreateSettingsWindow()
|
||||
GUICtrlCreateTabItem("General")
|
||||
CreateControlsControls()
|
||||
|
||||
; Plugins tab
|
||||
GUICtrlCreateTabItem("Plugins")
|
||||
CreatePluginsControls()
|
||||
|
||||
; End tab items
|
||||
GUICtrlCreateTabItem("")
|
||||
|
||||
@ -1146,6 +1294,44 @@ Func CreateControlsControls()
|
||||
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 = ""
|
||||
@ -1229,6 +1415,494 @@ Func GetSupportedResolutions()
|
||||
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
|
||||
; ============================================
|
||||
@ -1313,6 +1987,18 @@ Func MainLoop()
|
||||
Case $btnOpenSavegames
|
||||
OpenSavegamesDirectory()
|
||||
|
||||
Case $btnPluginUp
|
||||
MoveSelectedPlugin(-1)
|
||||
|
||||
Case $btnPluginDown
|
||||
MoveSelectedPlugin(1)
|
||||
|
||||
Case $btnPluginsEnableAll
|
||||
SetAllPluginsEnabled(True)
|
||||
|
||||
Case $btnPluginsDisableAll
|
||||
SetAllPluginsEnabled(False)
|
||||
|
||||
EndSwitch
|
||||
WEnd
|
||||
EndFunc
|
||||
@ -1332,6 +2018,8 @@ Func ShowSettings()
|
||||
EndFunc
|
||||
|
||||
Func HideSettings()
|
||||
; Drop unsaved plugin changes so the list is read again the next time
|
||||
$bPluginListDirty = False
|
||||
GUISetState(@SW_HIDE, $hSettingsGUI)
|
||||
EndFunc
|
||||
|
||||
@ -1464,6 +2152,9 @@ Func LoadSettings()
|
||||
If @error Then $startSteam = 1
|
||||
SetCheckboxState($chkStartSteam, $startSteam)
|
||||
EndIf
|
||||
|
||||
; Load the plugin list
|
||||
LoadPluginList()
|
||||
EndFunc
|
||||
|
||||
Func SaveSettings()
|
||||
@ -1530,6 +2221,9 @@ Func SaveSettings()
|
||||
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)
|
||||
@ -1645,9 +2339,8 @@ Func OpenSavegamesDirectory()
|
||||
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 $MO2_DIR <> "" Then
|
||||
Local $explorerPPPath = $MO2_DIR & "\explorer++\Explorer++.exe"
|
||||
If FileExists($explorerPPPath) Then
|
||||
Run('"' & $explorerPPPath & '" "' & $fullSavePath & '"')
|
||||
Return
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user