; PARAVIS FLASH - разметка USB-диска (однокнопочный вариант) ; 1) FAT32 16 ГБ (PARAVIS) ; 2) FAT 32 МБ (VTOYEFI, под Ventoy) ; 3) exFAT остальное минус 3 МБ (DATA) ; В конце диска остаются 3 МБ нераспределёнными. ; ВНИМАНИЕ: все данные на выбранном диске уничтожаются. #RequireAdmin #NoTrayIcon #include #include #include #include #include #include Opt("GUIOnEventMode", 1) Opt("MustDeclareVars", 1) Global Const $MB = 1048576 Global Const $P1_MB = 16384 Global Const $P2_MB = 32 Global Const $TAIL_MB = 3 Global $gTestDisk = -1 ; служебный параметр /testdisk=N (только для автотеста) Global $gWimPath = "" ; путь к архиву (параметр /wim=..., иначе рядом с exe) Global $gRun = StringFormat("%04d%02d%02d_%02d%02d%02d", @YEAR, @MON, @MDAY, @HOUR, @MIN, @SEC) ; метка запуска для логов Global $gFailInfo = "" ; на каком шаге и на какой раздел шла запись Global $gLastOut = "" ; последний вывод внешней команды (для разбора ошибок) ; Служебные режимы (не для обычного запуска): ; /selftest - записать диагностику в %TEMP%\paravis_selftest.txt и выйти ; /testdisk=N - работать с диском N, не требуя выбора в списке (автотест на VHD) If $CmdLine[0] > 0 Then Local $ai, $arg, $argl For $ai = 1 To $CmdLine[0] $arg = $CmdLine[$ai] $argl = StringLower($arg) If $argl = "/selftest" Then Local $hSf = FileOpen(@TempDir & "\paravis_selftest.txt", $FO_OVERWRITE + $FO_ANSI) FileWriteLine($hSf, "windows=" & @WindowsDir) FileWriteLine($hSf, "system_disks=" & _SystemDiskIds()) FileWriteLine($hSf, "admin=" & IsAdmin()) FileWriteLine($hSf, "wim=" & @ScriptDir & "\Data.pf") FileWriteLine($hSf, "wim_exists=" & FileExists(@ScriptDir & "\Data.pf")) FileClose($hSf) Exit EndIf If StringLeft($argl, 8) = "/verify=" Then ; Диагностика проверки раскладки на уже готовом диске (только чтение). Local $vd = Number(StringTrimLeft($arg, 8)) Local $vok = False Local $vtxt = _VerifyLayout($vd, $vok) Local $vf = FileOpen(@TempDir & "\paravis_verify.txt", $FO_OVERWRITE + $FO_ANSI) FileWriteLine($vf, "disk=" & $vd & " ok=" & $vok) FileWriteLine($vf, $vtxt) FileClose($vf) Exit EndIf If StringLeft($argl, 10) = "/testdisk=" Then $gTestDisk = Number(StringTrimLeft($arg, 10)) If StringLeft($argl, 5) = "/wim=" Then $gWimPath = StringTrimLeft($arg, 5) Next EndIf Global $hStatus, $hGo, $hProg, $hWork, $hHint Global $gBusy = False ; идёт разметка/запись Global $gCanInstall = False ; можно ли устанавливать на выбранный диск Global $gModal = False ; открыто модальное окно Global $aDiskNum[0] ; номера дисков в порядке выпадающего списка Global $aDiskSize[0] ; их размеры на момент заполнения списка Global $aDiskName[0] ; названия моделей Global $aDiskCap[0] ; объёмы для показа Global $gSelIdx = 0 ; выбранный индекс Global $gSig = "-" ; подпись текущего состава устройств (на старте всегда отличается) Global $hPopup = 0 ; окно раскрытого списка Global $hItemName[0] ; пункты: название Global $hItemCap[0] ; пункты: объём Global $aItemBg[0] ; последние цвета пунктов Global $gDropGuard = 0 ; защита от закрытия сразу после открытия Global $gNeedScan = False ; пришло сообщение о смене устройств Global $gDevTick = 0 ; когда пришло это сообщение Global $gCancel = False ; пользователь нажал «ОТМЕНИТЬ» Global $gCancelDown = False Global $gCancelBg = -1 Global $gApplied = 0 ; сколько образов уже распаковано Global $gPhase = 0 ; 0 — простой, 1 — разметка, 2 — копирование файлов Global $gRunPid = 0 ; процесс текущей команды (для отмены копирования) Global $gInstalled = False ; на выбранном диске уже стоит сборка (маркеры на месте) Global $gClickGuard = 0 ; защита от «залипшего» клика после закрытия окна ; --- оформление Global Const $CLR_BG = 0xF7F8FA Global Const $CLR_HEAD = 0x1E2A44 Global Const $CLR_DIM = 0x6B7280 Global $hGUI = GUICreate("Paravis installer", 376, 183, -1, -1, $WS_POPUP, $WS_EX_APPWINDOW) GUISetBkColor($CLR_BG, $hGUI) GUISetOnEvent($GUI_EVENT_CLOSE, "_Quit") GUISetFont(9, 400, 0, "Tahoma", $hGUI) ; шапка Global $hHead = GUICtrlCreateLabel("", 0, 0, 376, 42) GUICtrlSetBkColor($hHead, $CLR_HEAD) Global $hTitle = GUICtrlCreateLabel("Paravis Flash", 16, 2, 300, 24) GUICtrlSetFont($hTitle, 15, 700, 0, "Tahoma") GUICtrlSetColor($hTitle, 0xFFFFFF) GUICtrlSetBkColor($hTitle, $CLR_HEAD) Global $hSub = GUICtrlCreateLabel("Запись загрузочной флешки", 18, 26, 300, 14) GUICtrlSetColor($hSub, 0xC7D0E0) GUICtrlSetBkColor($hSub, $CLR_HEAD) Global $hMin = GUICtrlCreateLabel("—", 306, 2, 30, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE, $SS_NOTIFY)) GUICtrlSetFont($hMin, 10, 400, 0, "Tahoma") GUICtrlSetColor($hMin, 0xC7D0E0) GUICtrlSetBkColor($hMin, $CLR_HEAD) Global $hCloseBtn = GUICtrlCreateLabel("✕", 338, 2, 30, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE, $SS_NOTIFY)) GUICtrlSetFont($hCloseBtn, 9, 400, 0, "Tahoma") GUICtrlSetColor($hCloseBtn, 0xC7D0E0) GUICtrlSetBkColor($hCloseBtn, $CLR_HEAD) ; карточка выбора Local $hCard = GUICtrlCreateLabel("", 8, 45, 360, 46) GUICtrlSetBkColor($hCard, 0xFFFFFF) Global $hLbl = GUICtrlCreateLabel("Выбран накопитель:", 16, 45, 340, 15) GUICtrlSetColor($hLbl, $CLR_DIM) GUICtrlSetBkColor($hLbl, 0xFFFFFF) Global $hComboFrame = GUICtrlCreateLabel("", 15, 61, 342, 28) GUICtrlSetBkColor($hComboFrame, 0xD5DAE3) Global $hDrop = GUICtrlCreateLabel("", 16, 62, 340, 26) GUICtrlSetBkColor($hDrop, 0xFFFFFF) Global $hDropName = GUICtrlCreateLabel("", 21, 62, 240, 26, BitOR($SS_LEFT, $SS_CENTERIMAGE, $SS_NOTIFY)) GUICtrlSetFont($hDropName, 9, 400, 0, "Tahoma") GUICtrlSetColor($hDropName, 0x1F2430) GUICtrlSetBkColor($hDropName, 0xFFFFFF) Global $hDropSize = GUICtrlCreateLabel("", 262, 62, 74, 26, BitOR($SS_RIGHT, $SS_CENTERIMAGE, $SS_NOTIFY)) GUICtrlSetFont($hDropSize, 9, 400, 0, "Tahoma") GUICtrlSetColor($hDropSize, 0x1F2430) GUICtrlSetBkColor($hDropSize, 0xFFFFFF) GUICtrlSetFont($hDrop, 9, 400, 0, "Tahoma") GUICtrlSetColor($hDrop, 0x1F2430) GUICtrlSetBkColor($hDrop, 0xFFFFFF) Global $hArrow = GUICtrlCreateLabel("▼", 336, 62, 16, 26, BitOR($SS_CENTER, $SS_CENTERIMAGE, $SS_NOTIFY)) GUICtrlSetFont($hArrow, 8, 400, 0, "Tahoma") GUICtrlSetColor($hArrow, 0x6B7280) GUICtrlSetBkColor($hArrow, 0xFFFFFF) GUICtrlSetOnEvent($hDrop, "_DropToggle") GUICtrlSetOnEvent($hArrow, "_DropToggle") $hStatus = GUICtrlCreateLabel("", 14, 95, 348, 15) GUICtrlSetColor($hStatus, $CLR_DIM) GUICtrlSetBkColor($hStatus, $CLR_BG) $hProg = GUICtrlCreateProgress(14, 139, 348, 5) GUICtrlSetState($hProg, $GUI_HIDE) ; текст установки — на месте красного предупреждения $hWork = GUICtrlCreateLabel("", 14, 115, 348, 18, $SS_CENTER) GUICtrlSetFont($hWork, 9, 700, 0, "Tahoma") GUICtrlSetColor($hWork, 0x1F2430) GUICtrlSetBkColor($hWork, $CLR_BG) GUICtrlSetState($hWork, $GUI_HIDE) $hGo = GUICtrlCreateLabel("УСТАНОВИТЬ", 14, 147, 348, 30, BitOR($SS_CENTER, $SS_CENTERIMAGE, $SS_NOTIFY)) GUICtrlSetFont($hGo, 10, 700, 0, "Tahoma") GUICtrlSetColor($hGo, 0xFFFFFF) GUICtrlSetBkColor($hGo, 0x2563EB) ; нажатие на кнопку обрабатывается опросом (_GoPoll), а не событием AutoIt $hHint = GUICtrlCreateLabel("Все данные на выбранном диске будут уничтожены", 14, 115, 348, 18, $SS_CENTER) GUICtrlSetColor($hHint, 0xC0392B) GUICtrlSetFont($hHint, 9, 700, 0, "Tahoma") GUICtrlSetBkColor($hHint, $CLR_BG) GUISetState(@SW_SHOW, $hGUI) AdlibRegister("_BtnPoll", 40) AdlibRegister("_AutoRefresh", 250) GUIRegisterMsg(0x0219, "_OnDeviceChange") _Refresh() While 1 Sleep(50) WEnd Func _Quit() Exit EndFunc Func _Refresh() _ScanDisks() EndFunc Func _AutoRefresh() ; Список перечитывается только когда система сообщила о смене устройств. ; Постоянного опроса нет — окно не подтормаживает при перетаскивании и наведении. If Not $gNeedScan Then Return If $gBusy Or $gModal Or $hPopup <> 0 Then Return If TimerDiff($gDevTick) < 500 Then Return $gNeedScan = False _ScanDisks() EndFunc Func _OnDeviceChange($hWnd, $iMsg, $wParam, $lParam) ; WM_DEVICECHANGE: подключили/отключили накопитель If $wParam = 0x8000 Or $wParam = 0x8004 Or $wParam = 0x0007 Then If Not $gNeedScan Then $gNeedScan = True $gDevTick = TimerInit() EndIf EndIf Return $GUI_RUNDEFMSG EndFunc Func _ScanDisks() ; Перечитывает USB-диски. Если состав не изменился — ничего не трогает. Local $sig = "", $n = 0 Local $nums[0], $sizes[0], $names[0], $caps[0] Local $oStorage = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\.\root\Microsoft\Windows\Storage") If IsObj($oStorage) Then Local $col = $oStorage.ExecQuery("SELECT * FROM MSFT_Disk") If IsObj($col) Then For $d In $col If Number($d.BusType) = 7 Then _PushDisk($nums, $sizes, $names, $caps, $n, Number($d.Number), Number($d.Size), $d.FriendlyName) $sig = $sig & Number($d.Number) & ":" & Number($d.Size) & "|" EndIf Next EndIf EndIf If $n = 0 Then ; страховка: если провайдер хранилища недоступен Local $oWMI = ObjGet("winmgmts:\\.\root\cimv2") If IsObj($oWMI) Then Local $col2 = $oWMI.ExecQuery("SELECT * FROM Win32_DiskDrive WHERE InterfaceType='USB' OR PNPDeviceID LIKE 'USBSTOR%' OR MediaType='External hard disk media' OR MediaType='Removable Media'") If IsObj($col2) Then For $d In $col2 Local $num = Number(StringRegExpReplace($d.DeviceID, ".*PHYSICALDRIVE(\d+).*", "$1")) _PushDisk($nums, $sizes, $names, $caps, $n, $num, Number($d.Size), $d.Model) $sig = $sig & $num & ":" & Number($d.Size) & "|" Next EndIf EndIf EndIf If $sig = $gSig Then Return ; состав тот же — экран не меняем $gSig = $sig ; запоминаем выбранный диск, чтобы не сбить выбор Local $curNum = -1 If UBound($aDiskNum) > 0 And $gSelIdx >= 0 And $gSelIdx < UBound($aDiskNum) Then $curNum = $aDiskNum[$gSelIdx] $aDiskNum = $nums $aDiskSize = $sizes $aDiskName = $names $aDiskCap = $caps If $n = 0 Then GUICtrlSetData($hDropName, "") GUICtrlSetData($hDropSize, "") GUICtrlSetData($hLbl, "Подходящие накопители отсутствуют!") GUICtrlSetData($hStatus, "Найдено: 0") $gSelIdx = 0 $gInstalled = False _ArrowState() _RefreshGo() Return EndIf Local $found = 0, $i For $i = 0 To UBound($aDiskNum) - 1 If $aDiskNum[$i] = $curNum Then $found = $i ExitLoop EndIf Next $gSelIdx = $found _ShowSelected() _RefreshGo() GUICtrlSetData($hLbl, "Выбран накопитель:") GUICtrlSetData($hStatus, "Найдено: " & UBound($aDiskNum)) _ArrowState() EndFunc Func _PushDisk(ByRef $nums, ByRef $sizes, ByRef $names, ByRef $caps, ByRef $n, $num, $size, $model) ReDim $nums[$n + 1] ReDim $sizes[$n + 1] ReDim $names[$n + 1] ReDim $caps[$n + 1] $nums[$n] = $num $sizes[$n] = $size Local $m = StringStripWS(String($model), 3) If $m = "" Then $m = "(без названия)" $names[$n] = $m $caps[$n] = _NominalSize($size) $n = $n + 1 EndFunc Func _ShowSelected() If UBound($aDiskName) = 0 Then Return GUICtrlSetData($hDropName, $aDiskName[$gSelIdx]) GUICtrlSetData($hDropSize, $aDiskCap[$gSelIdx]) _CheckInstalled() EndFunc Func _NominalGb($bytes) ; «Магазинный» объём в ГБ (64, а не 60) Local $gb = Number($bytes) / 1000000000 Local $list[23] = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1000, 1500, 2000, 3000, 4000, 6000, 8000, 10000, 12000, 14000, 16000, 18000, 20000] Local $i For $i = 0 To UBound($list) - 1 If $gb <= $list[$i] * 1.02 Then Return $list[$i] Next Return $gb EndFunc Func _NominalSize($bytes) Local $n = _NominalGb($bytes) If $n >= 1000 Then Return StringFormat("%g ТБ", $n / 1000) Return StringFormat("%d ГБ", $n) EndFunc Func _SelectedIndex() If UBound($aDiskNum) = 0 Then Return -1 If $gSelIdx < 0 Or $gSelIdx >= UBound($aDiskNum) Then Return -1 Return $gSelIdx EndFunc ; --- свой выпадающий список Func _DropToggle() If $gBusy Or $gModal Then Return If UBound($aDiskName) = 0 Then Return If $hPopup <> 0 Then _DropClose() Else _DropOpen() EndIf EndFunc Func _DropOpen() Local $n = UBound($aDiskName) If $n = 0 Then Return Local $itemH = 24 Local $p = WinGetPos($hGUI) Local $c = WinGetClientSize($hGUI) If Not IsArray($p) Or Not IsArray($c) Then Return Local $borderX = ($p[2] - $c[0]) / 2 Local $titleH = $p[3] - $c[1] - $borderX ; рамка — как у остальных элементов: 1 px светло-серым, без системной рамки $hPopup = GUICreate("", 342, $n * $itemH + 2, $p[0] + $borderX + 15, $p[1] + $titleH + 89, $WS_POPUP, $WS_EX_TOOLWINDOW, $hGUI) GUISetBkColor(0xD5DAE3, $hPopup) Local $hPanel = GUICtrlCreateLabel("", 1, 1, 340, $n * $itemH) GUICtrlSetBkColor($hPanel, 0xFFFFFF) ReDim $hItemName[$n] ReDim $hItemCap[$n] ReDim $aItemBg[$n] Local $i For $i = 0 To $n - 1 Local $y = 1 + $i * $itemH $hItemName[$i] = GUICtrlCreateLabel(" " & $aDiskName[$i], 1, $y, 248, $itemH, BitOR($SS_LEFT, $SS_CENTERIMAGE, $SS_NOTIFY)) GUICtrlSetFont($hItemName[$i], 9, 400, 0, "Tahoma") GUICtrlSetColor($hItemName[$i], 0x1F2430) GUICtrlSetBkColor($hItemName[$i], 0xFFFFFF) GUICtrlSetOnEvent($hItemName[$i], "_DropPick") $hItemCap[$i] = GUICtrlCreateLabel($aDiskCap[$i] & " ", 249, $y, 92, $itemH, BitOR($SS_RIGHT, $SS_CENTERIMAGE, $SS_NOTIFY)) GUICtrlSetFont($hItemCap[$i], 9, 400, 0, "Tahoma") GUICtrlSetColor($hItemCap[$i], 0x6B7280) GUICtrlSetBkColor($hItemCap[$i], 0xFFFFFF) GUICtrlSetOnEvent($hItemCap[$i], "_DropPick") $aItemBg[$i] = -1 Next $gDropGuard = TimerInit() GUISetState(@SW_SHOW, $hPopup) EndFunc Func _DropClose() If $hPopup <> 0 Then GUIDelete($hPopup) $hPopup = 0 EndIf EndFunc Func _DropPick() Local $i For $i = 0 To UBound($aDiskName) - 1 If @GUI_CtrlId = $hItemName[$i] Or @GUI_CtrlId = $hItemCap[$i] Then $gSelIdx = $i _ShowSelected() _RefreshGo() ExitLoop EndIf Next _DropClose() EndFunc Func _DiskSize($num) Local $sz = 0 Local $oStorage = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\.\root\Microsoft\Windows\Storage") If IsObj($oStorage) Then Local $col = $oStorage.ExecQuery("SELECT * FROM MSFT_Disk") If IsObj($col) Then For $d In $col If Number($d.Number) = $num Then $sz = Number($d.Size) Next EndIf EndIf If $sz = 0 Then Local $oWMI = ObjGet("winmgmts:\\.\root\cimv2") If IsObj($oWMI) Then Local $col2 = $oWMI.ExecQuery("SELECT * FROM Win32_DiskDrive WHERE Index=" & $num) If IsObj($col2) Then For $d In $col2 $sz = Number($d.Size) Next EndIf EndIf EndIf Return $sz EndFunc Func _SystemDiskIds() ; Возвращает строку вида "|0|" со номерами дисков, на которых лежит Windows. Local $ids = "" Local $oWMI = ObjGet("winmgmts:\\.\root\cimv2") If Not IsObj($oWMI) Then Return "" Local $sys = StringLeft(@WindowsDir, 2) If StringRight($sys, 1) <> ":" Then Return "" Local $col = $oWMI.ExecQuery("ASSOCIATORS OF {Win32_LogicalDisk.DeviceID='" & $sys & "'} WHERE AssocClass=Win32_LogicalDiskToPartition") If Not IsObj($col) Then Return "" For $part In $col Local $m = StringRegExp(String($part.DeviceID), "(?i)Disk #(\d+)", 1) If IsArray($m) Then $ids = $ids & "|" & Number($m[0]) & "|" Next Return $ids EndFunc Func _Apply() If $gBusy Then Return If Not $gCanInstall And $gTestDisk < 0 Then Return Local $disk = $gTestDisk Local $expectSize = 0 If $disk < 0 Then Local $idx = _SelectedIndex() If $idx < 0 Then _Msg(BitOR($MB_ICONWARNING, $MB_OK), "Устройство не выбрано", "Выберите флешку в списке.") Return EndIf $disk = $aDiskNum[$idx] $expectSize = $aDiskSize[$idx] EndIf ; Страховка: никогда не размечаем диск, на котором стоит Windows. Local $sysIds = _SystemDiskIds() If StringInStr($sysIds, "|" & $disk & "|") > 0 Then _Msg(BitOR($MB_ICONERROR, $MB_OK), "Это системный диск", _ "Диск " & $disk & " содержит Windows. Разметка отменена - выберите другой диск.") Return EndIf Local $sizeBytes = _DiskSize($disk) If $sizeBytes <= 0 Then _Msg(BitOR($MB_ICONERROR, $MB_OK), "Ошибка", "Не удалось определить размер диска " & $disk & ".") Return EndIf ; Сверка: диск должен быть тем же, что был в списке (защита от сдвига индексов) If $expectSize > 0 And Abs($sizeBytes - $expectSize) > ($expectSize / 100) Then _Msg(BitOR($MB_ICONERROR, $MB_OK), "Диск не совпадает", _ "Размер выбранного диска (" & Round($sizeBytes / 1073741824, 1) & " ГБ) не совпадает с тем, " & _ "что был в списке (" & Round($expectSize / 1073741824, 1) & " ГБ)." & @CRLF & @CRLF & _ "Нажмите «Обновить» и выберите устройство заново.") Return EndIf Local $totalMB = Int($sizeBytes / $MB) Local $p3 = $totalMB - 1 - $P1_MB - $P2_MB - $TAIL_MB If $p3 < 512 Then _Msg(BitOR($MB_ICONERROR, $MB_OK), "Слишком маленький диск", _ "Нужен диск не меньше " & ($P1_MB + $P2_MB + $TAIL_MB + 512) & " МБ." & @CRLF & _ "Сейчас: " & $totalMB & " МБ.") Return EndIf Local $msg = "Диск " & $disk & " (" & StringFormat("%.1f", $sizeBytes / 1073741824) & " ГБ)" & @CRLF & @CRLF & _ "Будет создано:" & @CRLF & _ " 1) FAT32 16 ГБ метка PARAVIS" & @CRLF & _ " 2) FAT 32 МБ метка VTOYEFI" & @CRLF & _ " 3) exFAT " & StringFormat("%.1f", $p3 / 1024) & " ГБ метка DATA" & @CRLF & @CRLF & _ "Затем на разделы будут распакованы файлы из архива Data.pf" & @CRLF & _ "(1 -> PARAVIS, 2 -> VTOYEFI, 3 -> DATA)." & @CRLF & @CRLF & _ "ВНИМАНИЕ! ВСЕ ДАННЫЕ НА ЭТОМ ДИСКЕ БУДУТ УНИЧТОЖЕНЫ." & @CRLF & _ "Продолжить?" GUICtrlSetData($hGo, "Ожидание подтверждения...") GUICtrlSetBkColor($hGo, 0xAEB6C4) GUICtrlSetColor($hGo, 0xEDEFF3) If _Msg(BitOR($MB_ICONWARNING, $MB_YESNO, $MB_DEFBUTTON2), "Подтверждение", $msg) <> $IDYES Then _GoCaption() _RefreshGo() Return EndIf _BusySet(True) $gPhase = 1 ; Если диск был offline (например, после тестов ВМ) — включаем его перед разметкой. ; На уже online-диске diskpart вернёт ошибку, но это не помешает основному скрипту. Local $onlineTmp = @TempDir & "\paravis_" & $gRun & "_online.txt" Local $hOn = FileOpen($onlineTmp, $FO_OVERWRITE + $FO_ANSI) If $hOn <> -1 Then FileWrite($hOn, "select disk " & $disk & @CRLF & "online disk" & @CRLF & "exit" & @CRLF) FileClose($hOn) RunWait(@ComSpec & ' /c diskpart.exe /s "' & $onlineTmp & '" > nul 2>&1', "", @SW_HIDE) FileDelete($onlineTmp) EndIf Local $script = "select disk " & $disk & @CRLF & _ "clean" & @CRLF & _ "convert mbr" & @CRLF & _ "create partition primary size=" & $P1_MB & @CRLF & _ "format fs=fat32 quick label=""PARAVIS""" & @CRLF & _ "active" & @CRLF & "assign" & @CRLF & _ "create partition primary size=" & $P2_MB & @CRLF & _ "format fs=fat quick label=""VTOYEFI""" & @CRLF & _ "assign" & @CRLF & _ "create partition primary size=" & $p3 & @CRLF & _ "format fs=exfat quick label=""DATA""" & @CRLF & _ "assign" & @CRLF & "exit" & @CRLF Local $tmp = @TempDir & "\paravis_" & $gRun & "_diskpart.txt" Local $hFile = FileOpen($tmp, $FO_OVERWRITE + $FO_ANSI) If $hFile = -1 Then _Msg(BitOR($MB_ICONERROR, $MB_OK), "Ошибка", "Не удалось создать файл скрипта.") Return EndIf FileWrite($hFile, $script) FileClose($hFile) GUICtrlSetData($hWork, "Разметка диска " & $disk & "…") Local $logFile = @TempDir & "\paravis_" & $gRun & "_diskpart_log.txt" ; Разметку не прерываем: ждём её конца, но нажатие «ОТМЕНИТЬ» запоминаем Local $pidDp = Run(@ComSpec & ' /c diskpart.exe /s "' & $tmp & '" > "' & $logFile & '" 2>&1', "", @SW_HIDE) If $pidDp Then While ProcessExists($pidDp) _CancelPoll() Sleep(50) WEnd EndIf Local $log = "" If FileExists($logFile) Then $log = FileRead($logFile) Sleep(1500) Local $ok = False Local $check = _VerifyLayout($disk, $ok) If Not $ok Then GUICtrlSetData($hWork, "Раскладка не совпала с ожидаемой") GUICtrlSetState($hProg, $GUI_HIDE) _Msg(BitOR($MB_ICONERROR, $MB_OK), "Ошибка разметки", _ "РАСКЛАДКА НЕ СОВПАЛА С ОЖИДАЕМОЙ" & @CRLF & @CRLF & $check & @CRLF & @CRLF & _ "Лог: " & $logFile & @CRLF & @CRLF & StringLeft($log, 600)) _BusySet(False) GUICtrlSetState($hWork, $GUI_HIDE) GUICtrlSetState($hHint, $GUI_SHOW) _Refresh() Return EndIf ; --- разметка верна, распаковываем образы на разделы If $gCancel Then _BusySet(False) GUICtrlSetState($hWork, $GUI_HIDE) GUICtrlSetState($hHint, $GUI_SHOW) _Refresh() _Msg(BitOR($MB_ICONINFORMATION, $MB_OK), "Отменено", _ "Операция отменена пользователем." & @CRLF & @CRLF & _ "Диск размечен, файлы не скопированы.") Return EndIf Local $wim = _FindWim() If $wim = "" Then GUICtrlSetData($hWork, "Размечено, архив Data.pf не найден") _Msg(BitOR($MB_ICONWARNING, $MB_OK), "Разметка выполнена", _ "Диск размечен, раскладка верна." & @CRLF & @CRLF & $check & @CRLF & @CRLF & _ "Но файл Data.pf не найден рядом с программой - файлы не скопированы.") _BusySet(False) GUICtrlSetState($hWork, $GUI_HIDE) GUICtrlSetState($hHint, $GUI_SHOW) _Refresh() Return EndIf $gPhase = 2 Local $res = _ApplyImages($disk, $wim) $gPhase = 0 If $res = 1 Then _HideVentoy($disk) GUICtrlSetData($hWork, "Готово") GUICtrlSetState($hProg, $GUI_HIDE) ; После успешной установки — предупреждение и запуск установщика загрузчиков _Msg(BitOR($MB_ICONINFORMATION, $MB_OK), "Установка завершена", _ "Установка завершена успешно. Сейчас будет запущен установщик загрузчиков. " & _ "Выберите Legacy и UEFI загрузчики. Если этого не сделать, то будет работать только UEFI.") Local $p1 = _PartitionLetter($disk, 0) If $p1 <> "" Then Local $loader = $p1 & "Change_BootLoader.exe" If FileExists($loader) Then Local $pidLoader = Run('"' & $loader & '"') If $pidLoader <> 0 Then Exit _Msg(BitOR($MB_ICONWARNING, $MB_OK), "Установщик загрузчиков", _ "Не удалось запустить Change_BootLoader.exe." & @CRLF & @CRLF & _ "Путь: " & $loader) Else _Msg(BitOR($MB_ICONWARNING, $MB_OK), "Установщик загрузчиков", _ "Файл Change_BootLoader.exe не найден на разделе PARAVIS." & @CRLF & @CRLF & _ "Путь: " & $loader) EndIf Else _Msg(BitOR($MB_ICONWARNING, $MB_OK), "Установщик загрузчиков", _ "Не удалось определить букву раздела PARAVIS.") EndIf ElseIf $res = 2 Then GUICtrlSetData($hWork, "Ошибка записи") GUICtrlSetState($hProg, $GUI_HIDE) _Msg(BitOR($MB_ICONERROR, $MB_OK), "Ошибка копирования файлов", _ "Диск размечен, но скопировать файлы не удалось." & @CRLF & @CRLF & _ "Запустите установку ещё раз.") ElseIf $res = 3 Then GUICtrlSetData($hWork, "Отменено") GUICtrlSetState($hProg, $GUI_HIDE) _BusySet(False) GUICtrlSetState($hWork, $GUI_HIDE) GUICtrlSetState($hHint, $GUI_SHOW) _Refresh() _Msg(BitOR($MB_ICONINFORMATION, $MB_OK), "Отменено", _ "Операция отменена пользователем." & @CRLF & @CRLF & _ "Диск размечен, скопировано образов: " & $gApplied & " из 3.") Return EndIf _BusySet(False) GUICtrlSetState($hWork, $GUI_HIDE) GUICtrlSetState($hHint, $GUI_SHOW) _Refresh() EndFunc Func _FindWim() ; Архив ищется ТОЛЬКО рядом с exe и только под именем Data.pf. ; Параметр /wim= оставлен как служебный (автотесты), в обычной работе не нужен. If $gWimPath <> "" Then If FileExists($gWimPath) Then Return $gWimPath _Msg(BitOR($MB_ICONWARNING, $MB_OK), "Архив не найден", "Файл не найден:" & @CRLF & $gWimPath) EndIf Local $p = @ScriptDir & "\Data.pf" If FileExists($p) Then Return $p Return "" EndFunc Func _PartitionLetter($disk, $idx) ; Буква раздела с номером $idx (0..2) на диске $disk. Возвращает "X:\" или "". Local $oWMI = ObjGet("winmgmts:\\.\root\cimv2") If Not IsObj($oWMI) Then Return "" Local $parts = $oWMI.ExecQuery("SELECT * FROM Win32_DiskPartition WHERE DiskIndex=" & $disk & " AND Index=" & $idx) If Not IsObj($parts) Then Return "" For $p In $parts Local $ld = $oWMI.ExecQuery("ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" & $p.DeviceID & "'} WHERE AssocClass=Win32_LogicalDiskToPartition") If IsObj($ld) Then For $l In $ld Local $d = String($l.DeviceID) If StringLen($d) = 2 Then Return $d & "\" Next EndIf Next Return "" EndFunc Func _RunCmd($cmdline, $caption) ; Запускает команду напрямую (без временных .cmd), показывает проценты, ; возвращает код возврата: cmd печатает PF_EXIT=<код> после команды. Local $full = @ComSpec & ' /v:on /c "' & $cmdline & ' & echo PF_EXIT=!errorlevel!"' Local $pid = Run($full, "", @SW_HIDE, 2 + 4) If $pid = 0 Then Return -1 $gRunPid = $pid Local $out = "", $buf = "", $shown = -1 While 1 $buf = StdoutRead($pid) If @error Then ExitLoop If $buf <> "" Then $out = $out & $buf Local $m = StringRegExp($out, "(\d{1,3}(?:[.,]\d+)?)\s*%", 3) If IsArray($m) Then Local $last = Number($m[UBound($m) - 1]) If $last <> $shown Then $shown = $last GUICtrlSetData($hWork, $caption) GUICtrlSetData($hProg, $last) EndIf EndIf EndIf If Not ProcessExists($pid) Then ExitLoop _CancelPoll() Sleep(50) WEnd $gRunPid = 0 $out = $out & StdoutRead($pid) $gLastOut = $out Local $code = -1 Local $mm = StringRegExp($out, "PF_EXIT=(\d+)", 3) If IsArray($mm) Then $code = Number($mm[UBound($mm) - 1]) Return $code EndFunc Func _DismApply($wim, $index, $dir, $caption) ; Прямая распаковка образа на раздел через DISM (Windows 8 и новее). Local $cmd = 'dism.exe /Apply-Image /ImageFile:"' & $wim & '" /Index:' & $index & ' /ApplyDir:' & $dir Local $rc = _RunCmd($cmd, $caption) If $rc = 0 Then Return True GUICtrlSetData($hWork, $caption & ": ОШИБКА (код " & $rc & ")") Return False EndFunc Func _HideVentoy($disk) ; Убираем букву у раздела VTOYEFI (2-й раздел) — в проводнике он не появится. ; Работает по номеру диска и раздела, без привязки к буквам. Local $tmp = @TempDir & "\paravis_" & $gRun & "_hide.txt" Local $h = FileOpen($tmp, $FO_OVERWRITE + $FO_ANSI) If $h = -1 Then Return FileWrite($h, "select disk " & $disk & @CRLF & _ "select partition 2" & @CRLF & _ "remove" & @CRLF & _ "set id=EF override" & @CRLF & _ "exit" & @CRLF) FileClose($h) RunWait(@ComSpec & ' /c diskpart.exe /s "' & $tmp & '" > nul 2>&1', "", @SW_HIDE) FileDelete($tmp) EndFunc Func _ApplyImages($disk, $wim) ; 1 -> PARAVIS, 2 -> VTOYEFI, 3 -> DATA Local $names[3] = ["PARAVIS", "VTOYEFI", "DATA"] Local $i For $i = 0 To 2 If $gCancel Then Return 3 ; буква может появиться не мгновенно после assign — ждём до 15 с Local $dir = "" Local $try For $try = 1 To 15 $dir = _PartitionLetter($disk, $i) If $dir <> "" Then ExitLoop Sleep(1000) Next If $dir = "" Then _Msg(BitOR($MB_ICONERROR, $MB_OK), "Ошибка", _ "Не удалось определить букву раздела " & ($i + 1) & " (" & $names[$i] & ")." & @CRLF & _ "Проверьте, что раздел подключён (diskpart: assign).") Return 2 EndIf If $gCancel Then Return 3 Local $cap = $names[$i] & " → " & $dir $gFailInfo = "образ " & ($i + 1) & " (" & $names[$i] & ") -> раздел " & $dir GUICtrlSetData($hProg, 0) GUICtrlSetState($hProg, $GUI_SHOW) GUICtrlSetData($hWork, $cap) Local $r $r = _DismApply($wim, $i + 1, $dir, $cap) If Not $r Then If $gCancel Then Return 3 Return 2 EndIf $gApplied = $gApplied + 1 If $gCancel Then Return 3 Next Return 1 EndFunc Func _VerifyLayout($disk, ByRef $okFlag) ; Проверка результата по WMI (без разбора текста diskpart, не зависит от языка Windows). $okFlag = False Local $oWMI = ObjGet("winmgmts:\\.\root\cimv2") If Not IsObj($oWMI) Then Return "разделов: ? (нет доступа к WMI)" Local $want = StringSplit("PARAVIS|VTOYEFI|DATA", "|", 2) Local $rep = "" Local $n = 0 Local $labels = "|" Local $sz1 = 0, $sz2 = 0, $sz3 = 0 Local $col = $oWMI.ExecQuery("SELECT * FROM Win32_DiskPartition WHERE DiskIndex=" & $disk) If IsObj($col) Then For $p In $col $n = $n + 1 Local $mbSz = Int(Number($p.Size) / $MB) If $n = 1 Then $sz1 = $mbSz If $n = 2 Then $sz2 = $mbSz If $n = 3 Then $sz3 = $mbSz Local $line = " раздел " & $n & ": " & $mbSz & " МБ" Local $lds = $oWMI.ExecQuery("ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" & $p.DeviceID & "'} WHERE AssocClass=Win32_LogicalDiskToPartition") If IsObj($lds) Then For $l In $lds Local $vn = StringStripWS(String($l.VolumeName), 3) $line = $line & " " & $l.DeviceID & " " & $vn & " " & String($l.FileSystem) If $vn <> "" Then $labels = $labels & $vn & "|" Next EndIf $rep = $rep & $line & @CRLF Next EndIf Local $miss = "" For $w In $want If StringInStr($labels, "|" & $w & "|") = 0 Then $miss = $miss & " " & $w Next Local $sizeOk = (Abs($sz1 - $P1_MB) <= 8) And (Abs($sz2 - $P2_MB) <= 8) Local $head = "разделов: " & $n If $n <> 3 Then $head = $head & " (ожидалось 3)" If $miss <> "" Then $rep = $rep & "нет меток:" & $miss & @CRLF If Not $sizeOk And $n = 3 Then $rep = $rep & "не совпали размеры 1-го и 2-го разделов" & @CRLF $okFlag = ($n = 3) And ($miss = "") And $sizeOk Return $head & @CRLF & $rep EndFunc Func _BtnPoll() Static $goCur = -1, $comboCur = -1 If $gModal Then Return If $gBusy Then _CancelPoll() Return EndIf Local $ci = GUIGetCursorInfo($hGUI) If Not IsArray($ci) Then Return ; главная кнопка — клик через опрос If _GoPoll() Then _Apply() Return EndIf ; --- кнопки шапки: подсветка, нажатие, перетаскивание окна Static $minCur = -1, $closeCur = -1, $minDown = False, $closeDown = False Static $drag = False, $dragX = 0, $dragY = 0 Local $minBg = $CLR_HEAD, $closeBg = $CLR_HEAD If $ci[4] = $hMin Then If $ci[2] = 1 Then $minBg = 0x3A4A6B Else $minBg = 0x2C3A57 EndIf EndIf If $ci[4] = $hCloseBtn Then If $ci[2] = 1 Then $closeBg = 0xC0392B Else $closeBg = 0xE04B3F EndIf EndIf If $minBg <> $minCur Then GUICtrlSetBkColor($hMin, $minBg) $minCur = $minBg EndIf If $closeBg <> $closeCur Then GUICtrlSetBkColor($hCloseBtn, $closeBg) $closeCur = $closeBg EndIf ; нажатие засчитывается, если отпустили на той же кнопке If $ci[4] = $hMin And $ci[2] = 1 Then $minDown = True ElseIf $minDown Then $minDown = False If $ci[4] = $hMin And $ci[2] = 0 Then GUISetState(@SW_MINIMIZE, $hGUI) EndIf If $ci[4] = $hCloseBtn And $ci[2] = 1 Then $closeDown = True ElseIf $closeDown Then $closeDown = False If $ci[4] = $hCloseBtn And $ci[2] = 0 Then Exit EndIf ; перетаскивание за шапку (кнопки шапки исключены) If ($ci[4] = $hHead Or $ci[4] = $hTitle Or $ci[4] = $hSub) And $ci[2] = 1 And Not $drag Then Local $m = MouseGetPos() Local $wp = WinGetPos($hGUI) If IsArray($m) And IsArray($wp) Then $drag = True $dragX = $m[0] - $wp[0] $dragY = $m[1] - $wp[1] EndIf EndIf If $drag Then If $ci[2] = 0 Then $drag = False Else Local $m2 = MouseGetPos() If IsArray($m2) Then WinMove($hGUI, "", $m2[0] - $dragX, $m2[1] - $dragY) EndIf EndIf ; главная кнопка Local $go If (Not $gCanInstall) Or BitAND(GUICtrlGetState($hGo), $GUI_DISABLE) Then $go = 0xAEB6C4 ElseIf $ci[4] = $hGo And $ci[2] = 1 Then $go = 0x1B4FD8 ElseIf $ci[4] = $hGo Then $go = 0x3B82F6 Else $go = 0x2563EB EndIf If $go <> $goCur Then GUICtrlSetBkColor($hGo, $go) $goCur = $go EndIf ; поле списка: раскрытие по нажатию (события надписи не всегда приходят) Static $dropDown = False Local $hasDisks = (UBound($aDiskName) > 0) If $hasDisks And ($ci[4] = $hDrop Or $ci[4] = $hArrow Or $ci[4] = $hDropName Or $ci[4] = $hDropSize) And $ci[2] = 1 And Not $dropDown Then _DropToggle() $dropDown = True EndIf If $ci[2] = 0 Then $dropDown = False ; поле списка: подсветка рамки Local $cb If $hasDisks And ($ci[4] = $hDrop Or $ci[4] = $hArrow Or $ci[4] = $hComboFrame Or $ci[4] = $hDropName Or $ci[4] = $hDropSize) Then $cb = 0x7FA6F0 Else $cb = 0xD5DAE3 EndIf If $cb <> $comboCur Then GUICtrlSetBkColor($hComboFrame, $cb) $comboCur = $cb EndIf ; раскрытый список: подсветка пункта, выбор и закрытие по клику вне If $hPopup <> 0 Then Local $ci2 = GUIGetCursorInfo($hPopup) If IsArray($ci2) Then Local $k, $bg, $over = -1 For $k = 0 To UBound($hItemName) - 1 $bg = 0xFFFFFF If $ci2[4] = $hItemName[$k] Or $ci2[4] = $hItemCap[$k] Then $bg = 0xEEF3FF $over = $k EndIf If $k < UBound($aItemBg) And $aItemBg[$k] <> $bg Then GUICtrlSetBkColor($hItemName[$k], $bg) GUICtrlSetBkColor($hItemCap[$k], $bg) $aItemBg[$k] = $bg EndIf Next If $ci2[2] = 1 And TimerDiff($gDropGuard) > 400 Then If $over >= 0 Then ; выбрали пункт $gSelIdx = $over _ShowSelected() _RefreshGo() _DropClose() ElseIf $ci2[4] = 0 Then ; клик мимо — просто закрыть _DropClose() EndIf EndIf EndIf EndIf EndFunc Func _DimControls($on) ; Затеняем поле выбора и кнопку «Обновить», когда интерфейс недоступен If $on Then GUICtrlSetBkColor($hDrop, 0xF2F3F5) GUICtrlSetBkColor($hDropName, 0xF2F3F5) GUICtrlSetColor($hDropName, 0xA0A6B1) GUICtrlSetBkColor($hDropSize, 0xF2F3F5) GUICtrlSetColor($hDropSize, 0xB4B9C2) GUICtrlSetColor($hArrow, 0xC7CBD3) GUICtrlSetBkColor($hArrow, 0xF2F3F5) GUICtrlSetBkColor($hComboFrame, 0xE3E6EB) _DropClose() Else GUICtrlSetBkColor($hDrop, 0xFFFFFF) GUICtrlSetBkColor($hDropName, 0xFFFFFF) GUICtrlSetColor($hDropName, 0x1F2430) GUICtrlSetBkColor($hDropSize, 0xFFFFFF) GUICtrlSetColor($hDropSize, 0x6B7280) GUICtrlSetBkColor($hArrow, 0xFFFFFF) _ArrowState() GUICtrlSetBkColor($hComboFrame, 0xD5DAE3) EndIf EndFunc Func _BusySet($b) ; Идёт установка: предупреждение скрыто, показан ход работ, всё затенено, ; кнопка превращается в «ОТМЕНИТЬ» $gBusy = $b If $b Then $gCancel = False $gCancelDown = False $gCancelBg = -1 $gApplied = 0 GUICtrlSetState($hHint, $GUI_HIDE) GUICtrlSetState($hWork, $GUI_SHOW) _DimControls(True) GUICtrlSetData($hGo, "ОТМЕНИТЬ") GUICtrlSetColor($hGo, 0xFFFFFF) _CancelLook() Else $gCancel = False $gCancelDown = False $gPhase = 0 $gRunPid = 0 _CheckInstalled() _DimControls(False) _RefreshGo() EndIf EndFunc Func _CancelLook() ; Кнопка отмены: красная (под курсором светлее, нажата темнее), ; после нажатия — серая «Отменяю. Ждите...» Local $bg, $fg If $gCancel Then $bg = 0xAEB6C4 $fg = 0xEDEFF3 Else $bg = 0xC0392B $fg = 0xFFFFFF Local $ci = GUIGetCursorInfo($hGUI) If IsArray($ci) And $ci[4] = $hGo Then If $ci[2] = 1 Then $bg = 0xA93226 Else $bg = 0xD6453C EndIf EndIf EndIf If $bg <> $gCancelBg Then GUICtrlSetBkColor($hGo, $bg) GUICtrlSetColor($hGo, $fg) $gCancelBg = $bg EndIf EndFunc Func _GoPoll() ; Клик по главной кнопке через опрос курсора (события AutoIt копятся и срабатывают позже) Static $down = False Local $ci = GUIGetCursorInfo($hGUI) If Not IsArray($ci) Then Return False If $ci[4] = $hGo And $ci[2] = 1 Then $down = True Return False EndIf If $down Then $down = False If $ci[4] = $hGo And $ci[2] = 0 Then If TimerDiff($gClickGuard) < 400 Then Return False Return True EndIf EndIf Return False EndFunc Func _RequestCancel() ; Нажали «ОТМЕНИТЬ»: кнопка сразу серая и больше не реагирует на клики. ; Разметку доигрываем до конца, копирование прерываем сразу. If $gCancel Then Return $gCancel = True $gCancelDown = False GUICtrlSetData($hGo, "Отменяю. Ждите...") GUICtrlSetBkColor($hGo, 0xAEB6C4) GUICtrlSetColor($hGo, 0xEDEFF3) $gCancelBg = 0xAEB6C4 If $gPhase = 2 Then _KillCopy() EndFunc Func _KillCopy() ; Прерываем текущую распаковку: гасим всё дерево процессов cmd/dism If $gRunPid <> 0 Then Run(@ComSpec & ' /c taskkill /T /F /PID ' & $gRunPid, "", @SW_HIDE) $gRunPid = 0 EndIf EndFunc Func _CancelPoll() ; Нажатие «ОТМЕНИТЬ» во время установки. ; Разметку не прерываем — ждём её конца. Копирование прерываем сразу. If Not $gBusy Then Return If $gCancel Then Return _CancelLook() If _GoPoll() Then _RequestCancel() EndFunc Func _Msg($flags, $title, $text) ; Пока открыто окно — интерфейс затенён и неактивен Local $prev = $gModal $gModal = True _GoState(False) _DimControls(True) Local $r = MsgBox($flags, $title, $text) $gModal = $prev $gClickGuard = TimerInit() If Not $gBusy Then _DimControls(False) _RefreshGo() Else _CancelLook() EndIf Return $r EndFunc Func _GoState($on) ; Цвет главной кнопки: активна / недоступна (идёт запись или открыт диалог) If $on Then GUICtrlSetBkColor($hGo, 0x2563EB) GUICtrlSetColor($hGo, 0xFFFFFF) Else GUICtrlSetBkColor($hGo, 0xAEB6C4) GUICtrlSetColor($hGo, 0xEDEFF3) EndIf EndFunc Func _ArrowState() ; Треугольник списка: тусклый, если накопителей нет If UBound($aDiskName) = 0 Then GUICtrlSetColor($hArrow, 0xC7CBD3) Else GUICtrlSetColor($hArrow, 0x6B7280) EndIf EndFunc Func _GoCaption() ; Надпись на главной кнопке: сборка уже стоит — «Переустановить» If $gBusy Then Return If $gInstalled Then GUICtrlSetData($hGo, "Переустановить") Else GUICtrlSetData($hGo, "УСТАНОВИТЬ") EndIf EndFunc Func _CheckInstalled() ; Сборка считается установленной, если на выбранном диске есть оба маркера: ; 1-й раздел: \efi\Paravis\Boot\NOT.DEL, 3-й раздел: \ISO\Ventoy\NOT.DEL $gInstalled = False If UBound($aDiskNum) > 0 And $gSelIdx >= 0 And $gSelIdx < UBound($aDiskNum) Then Local $disk = $aDiskNum[$gSelIdx] Local $p1 = _PartitionLetter($disk, 0) If $p1 <> "" And FileExists($p1 & "efi\Paravis\Boot\NOT.DEL") Then Local $p3 = _PartitionLetter($disk, 2) If $p3 <> "" And FileExists($p3 & "ISO\Ventoy\NOT.DEL") Then $gInstalled = True EndIf EndIf _GoCaption() EndFunc Func _CanInstall() If UBound($aDiskNum) = 0 Then Return False If $gSelIdx < 0 Or $gSelIdx >= UBound($aDiskSize) Then Return False ; порог: заявленный объём меньше 32 ГБ — установка запрещена If _NominalGb($aDiskSize[$gSelIdx]) < 32 Then Return False Return True EndFunc Func _RefreshGo() $gCanInstall = _CanInstall() _GoCaption() _GoState($gCanInstall) EndFunc