1 (изменено: Alectric, 2023-11-19 07:01:23)

Тема: AHK: COM Port terminal

Программа для работы с COM-портом.
Возможности:
     - автоматический поиск порта;
     - чтение данных с порта;
     - всевозможные типы представления данных (бинарные данные, символ, шестнадцатеричное, десятичное, 1 2 4 8 байта, значение с точкой);
     - фильтрация данных (правой кнопкой по таблице);
     - построение графика по выбранному типу данных онлайн (правой кнопкой мыши на нужное число в таблице, программа ориентируется по смещению от первого принятого байта пакета, данные не отрисовываются если контрольная сумма не совпадает (если выбрана контрольная сумма));
     - сохранение, загрузка собранных данных буфера приема;
     - анализ пакетов данных (группировка по одинаковым признакам);
     - отправка данных в порт;
     - добавление контрольной суммы к данным (1 байт CRC, 2 байта CRC, Modbus RTU);
     - настройки параметров CRC-16;
     - расшифровка модбас сообщений (при условии что данные приняты корректно);
     - построение графиков на основе данных из порта онлайн;
     - до 12-ти графиков с возможностью отключать ненужные;
     - сохранение графика в файл в виде изображения.
     - свободный формат разделителя (например: "123,456,789;" или "123I456I789]" - одна точка трёх графиков);
     - настройки нигде не сохраняются;
     - программа разделена на два потока: в первом крутится интерфейс и обрабатываются данные, во втором происходит работа с COM портом. Теперь данные практически не дробятся на несколько пакетов и обрабатываются как единый блок;
     - добавлена настройка "Пропуск пакетов" - правый клик на информационную строку "В обработке Х". На случай если нужны данные в текущем времени, чтобы не ждать обработку всех накопившихся пакетов, если они поступают быстрее чем обрабатываются;
     - добавлена настройка "ReadIntervalTimeout" (Максимальное время, которое может пройти до поступления следующего байта в линию связи, в миллисекундах.) Для более точного разделения данных на пакеты. Настройка определяется автоматически в зависимости от параметров порта, но есть возможность ввести вручную.
     - добавлена "Карта регистров Modbus" (возможности: сохранять и загружать сохраненные карты модбас; выбрать приоретный регистр для опроса чаще остальных; изменение значения Holding регистров; изменение адреса устройства для выбранных регистров)


В будущем думаю добавить Modbus TCP. (но это не скоро)

Тема для обсуждения.


Version:="3.5"
#SingleInstance,off

#NoEnv
;ListLines,Off
#KeyHistory,0
AutoTrim,Off
CoordMode,Mouse,Screen
CoordMode,ToolTip
CoordMode,Pixel
CoordMode,Caret,Screen
DetectHiddenWindows,On
FloatFormat:=3
Menu,Tray,UseErrorLevel
Menu,Tray,NoIcon
SendMode,Input
SetBatchLines,-1
SetControlDelay,-1
SetKeyDelay,-1
SetTitleMatchMode,2
SetWinDelay,-1
SetWorkingDir,%A_ScriptDir%
StringCaseSense,Off
OnExit,EXIT
ComObjError(false)
Gdip_Initialised:=Gdip_Startup()
;Meta:="\.*?+[{|()^$"
global Ptr := A_PtrSize ? "UPtr" : "UInt"
global COMFail

OnMessage(0x8080,"GetPointer")

_Diskonnect:=0		; UChar 1	+0
_ApplyCOMSettings:=1	; UChar 1	+1
_COMBusy:=2		; UChar 1	+2
_COMFail:=3		; UChar 1	+3
_DataAvailable:=4	; UChar 1	+4
_SendDataNow:=5		; UChar 1	+5
_RS232_Baud:=6		; Int   4	+6
_RS232_Parity:=10	; Int   4	+10
_RS232_Data:=14		; Int   4	+14
_RS232_Stop:=26		; Int   4	+26
_COMPort:=30		; Str   12	+30
_SendSize:=42		; Int   4	+42
_DataPos:=46		; Int   4	+46
_PackagesRecieved:=50	; Int   4	+50
_ReadIntervalTimeout:=54	; Int   4	+54
_RS232_Bytes_Reqest:=58	; Int   4	+58
_MapRun:=62	; Int   4	+62
_MapSkip:=66	; Int   4	+66
_RTimeout:=70	; Int   4	+70
_EmptyPacket:=74	; Int   4	+74

_SendDataOffset:=100
_RecieveDataOffset:=10000
RTimeout:=100
EmptyPacket:=1
DefaultBReq:=16
DataSize:=1048576
DataPos:=0
ReadPos:=0

2ndThread=%1%
hBufinfo=%2%
hMainWin=%3%
msPointer=%4%

OnMessage(WM_MOVING:=0x0216, "WM_MOVING")
;OnMessage(WM_MOUSEMOVE:=0x200, "WM_MOUSEMOVE")
;IDC_SIZENS := DllCall("User32.dll\LoadCursor", Ptr, NULL, "Int", 32645, "UPtr")
IDC_SIZEWE := DllCall("User32.dll\LoadCursor", Ptr, NULL, "Int", 32644, Ptr)
IDC_SIZEALL:= DllCall("User32.dll\LoadCursor", Ptr, NULL, "Int", 32646, Ptr)
hIDC_ARROW := DllCall("User32.dll\LoadCursor", Ptr, NULL, "Int", 32512, Ptr)
IDC_ARROW := DllCall("CopyImage",Ptr,hIDC_ARROW,"UInt",0x2,"Int",0,"Int",0,"UInt",0)

RS232_Bytes_Reqest:=DefaultBReq
Rownums:=100000
;if (a_osversion~="10")
;  add_size=65
;else
;  add_size=55
E_Text_h:=510
hGroupBufer_h:=E_Text_h+101
E_Gr_Snd_h:=E_Text_h-72
E_1_ShiftSize:=170
E_1_h:=E_1_ShiftSize+25
E_RTU_Val_y:=E_1_h-E_1_ShiftSize+5
CreateGui_w:=960
CreateGui_h:=E_Text_h+140
ShiftSpaceX:=500
ShiftSpaceY:=950

Packages_Recieved:=0
RS232_Baud:=9600
RS232_Parity:="N"
RS232_Data:=8
RS232_Stop:=1
Timeout:=((RS232_Data+4)/RS232_Baud)*1000
ReadIntervalTimeout:=Floor(Timeout)
if (ReadIntervalTimeout=0)
  ReadIntervalTimeout:=1
Gdip_Points_Num:=2000
Gdip_W:=358
Gdip_H:=358
Gdip_M:=Gdip_H//2
Gdip_ZoomX:=1
Gdip_ZoomY:=1
Gdip_X_Offset:=0 ;Gdip_Y_Offset:=Gdip_M
ColourPen1:=0x0000ff
ColourPen2:=0xff0000
ColourPen3:=0x00ff00
ColourPen4:=0x00ffff
ColourPen5:=0xff00ff
ColourPen6:=0xcccc00
ColourPen7:=0x808080
ColourPen8:=0x0000c0
ColourPen9:=0xc00000
ColourPen10:=0x00c000
ColourPen11:=0x0000ff
ColourPen12:=0xff0000
Monitor_value:={}
MaxNumOfGraf:=12
loop,% MaxNumOfGraf
  Monitor_value%a_index%:={}
Monitor_value_T:={}
GrafSmooth:=1
AutoMinMax:=1
AutoEnding:=1
HideSamePol:=0xC867

ICON_CRC_ERROR:=132
ICON_EMPTY:=0
ICON_ERROR:=78
ICON_INFO:=222
ICON_REGISTER:=13 ;268
ICON_START:=138
ICON_STOP:=113
ICON_V:=145
ICON_X:=272

ICON_CLEAN:=50
ICON_OPEN:=4
ICON_CLOSE:=132
ICON_SAVE:=259
ICON_EXIT:=28
ICON_CLEAR:=146
ICON_BACK:=147
ICON_FILTER:=56
ICON_SKIP:=25
ICON_ANALYZE:=69
ICON_HIDE_PCKGS:=66
ICON_HELP:=211
ICON_ABOUT:=222
ICON_SHOW:=255
ICON_HIDE:=256
ICON_FAVORITE:=209
ICON_MANUAL:=29
ICON_RESET:=239
ICON_SMOOTH:=131
ICON_STOP_SCROLL:=175
ICON_SIZE:=23
ICON_GRAFDRAW:=217

COLUMN_#:=1
COLUMN_HOOK:=2
COLUMN_INFO:=3
COLUMN_BIN:=4
COLUMN_SYM:=5
COLUMN_HEX:=6
COLUMN_DEC:=7
COLUMN_WORD:=8
COLUMN_INT:=9
COLUMN_DWORD:=10
COLUMN_DINT:=11
COLUMN_FLOAT:=12
COLUMN_DOUBLE:=13

RTUFunc1:="Read Coil Status (Чтение DO) 01"
RTUFunc2:="Read Input Status (Чтение DI) 02"
RTUFunc3:="Read Holding Reg. (Чтение AO) 03"
RTUFunc4:="Read Input Register (Чтение AI) 04"
RTUFunc5:="Force Single Coil (Запись DO) 05"
RTUFunc6:="Preset Single Reg. (Запись AO) 06"
RTUFunc15:="Force Mult. Coils (Запись неск. DO) 0f"
RTUFunc16:="Force Mult. Reg. (Запись неск. AO) 10"
RTU_Command_List:= RTUFunc1 "|" RTUFunc2 "|" RTUFunc3 "|" RTUFunc4 "|" RTUFunc5 "|" RTUFunc6 "|" RTUFunc15 "|" RTUFunc16 ;

RTUF1:="DO"
RTUF2:="DI"
RTUF3:="AO"
RTUF4:="AI"
RTUF5:="1 DO"
RTUF6:="1 AO"
RTUF15:="# DO"
RTUF16:="# AO"

RTUErr1:="У слейва нет такой команды."
RTUErr2:="Регистр недоступен."
RTUErr3:="Недопустимая величина значения."
RTUErr4:="Ошибка во время выполнения действия."
RTUErr5:="Обрабатываю запрос, ждите..."
RTUErr6:="Обрабатываю команду, ждите..."
RTUErr7:="Не могу выполнить программную функцию. Запросите диагностические данные."
RTUErr8:="Ошибка паритета. Повторите запрос или обратитесь в сервис."
RTUErr10:="Шлюз не правильно настроен или перегружен."
RTUErr11:="Слейв устройства нет в сети или от него нет ответа."

MapTypeOfData:={INT:1,WORD:2,DINT:3,DWORD:4,REAL:5}
MapDataOfType:={1:"INT",2:"WORD",3:"DINT",4:"DWORD",5:"REAL"}
RS232_Baud_List:={50:1,75:2,110:3,150:4,300:5,600:6,1200:7,1800:8,2000:9,2400:10,3600:11,4800:12,7200:13,9600:14,14400:15,19200:16,28800:17,38400:18,57600:19,115200:20,250000:21,300000:22,375000:23,500000:24,750000:25,1500000:26,3000000:27}
RS232_Parity_List:={"N":1,"E":2,"O":3,"M":4,"S":5}
RS232_Data_List:={5:1,6:2,7:3,8:4}

if !2ndThread ; Main
{
  MenuCreate:
  menu,ListRightClick,add,Очистить,Update
  menu,ListRightClick,add,Вернуть все,UnUpdate
  menu,ListRightClick,add,Очистить безвозвратно,ClUpdate
  menu,ListRightClick,add
  menu,ListRightClick,add,Фильтровать данные,DataFilter
  menu,ListRightClick,add,Анализ пакетов,En_Analyzer
  menu,ListRightClick,add
  menu,ListRightClick,add,Копировать,ListCopySelected
  menu,ListRightClick,add,Отрисовать график,GrafDrawFromSelected
  menu,ListRightClick,disable,Отрисовать график ;
  Menu,ListRightClick,Icon,Очистить,shell32.dll,% ICON_CLEAR
  Menu,ListRightClick,Icon,Вернуть все,shell32.dll,% ICON_BACK
  Menu,ListRightClick,Icon,Очистить безвозвратно,shell32.dll,% ICON_X
  Menu,ListRightClick,Icon,Фильтровать данные,shell32.dll,% ICON_FILTER
;  Menu,ListRightClick,Icon,Анализ пакетов,shell32.dll,% ICON_ANALYZE
  Menu,ListRightClick,Icon,Отрисовать график,shell32.dll,% ICON_GRAFDRAW

  menu,SecondListRightClick,add,Скрывать одинаковые пакеты,HideSamePackages
  menu,SecondListRightClick,add,Копировать,SecondListCopySelected
  menu,SecondListRightClick,add,Отрисовать график,GrafDrawFromSelected
  menu,SecondListRightClick,disable,Отрисовать график ;
  menu,SecondListRightClick,check,Скрывать одинаковые пакеты
;  Menu,SecondListRightClick,Icon,Скрывать одинаковые пакеты,shell32.dll,% ICON_HIDE_PCKGS
  Menu,SecondListRightClick,Icon,Отрисовать график,shell32.dll,% ICON_GRAFDRAW
  HideSamePackages:=1

  menu,ListRightClick2,add,Пропуск пакетов,SkeepPack
;  Menu,ListRightClick2,Icon,Пропуск пакетов,shell32.dll,% ICON_SKIP

;--------------------------------------------------

  menu,FileMenu,Add,Открыть,OpenFile
  menu,FileMenu,Add,Сохранить,SaveFile
  menu,FileMenu,Add
  menu,FileMenu,Add,Открыть карту регистров Modbus,ModbusOpenFromMain
  menu,FileMenu,Add
  menu,FileMenu,Add,Количество знаков после запятой,ZeroCount
  menu,FileMenu,Add
  menu,FileMenu,Add,Выход,EXIT
  Menu,FileMenu,Icon,Открыть,shell32.dll,% ICON_OPEN
  Menu,FileMenu,Icon,Сохранить,shell32.dll,% ICON_SAVE
  Menu,FileMenu,Icon,Открыть карту регистров Modbus,shell32.dll,% ICON_OPEN
  Menu,FileMenu,Icon,Выход,shell32.dll,% ICON_EXIT

  menu,BufferMenu,add,Очистить,Update
  menu,BufferMenu,add,Вернуть все,UnUpdate
  menu,BufferMenu,add,Очистить безвозвратно,ClUpdate
  menu,BufferMenu,add,Фильтровать данные,DataFilter
  menu,BufferMenu,add,Пропуск пакетов,SkeepPack
  menu,BufferMenu,add
  menu,BufferMenu,add,Анализ пакетов,En_Analyzer
  menu,BufferMenu,add,Скрывать одинаковые пакеты,HideSamePackages
  menu,BufferMenu,check,Скрывать одинаковые пакеты
  menu,BufferMenu,disable,Скрывать одинаковые пакеты
  Menu,BufferMenu,Icon,Очистить,shell32.dll,% ICON_CLEAR
  Menu,BufferMenu,Icon,Вернуть все,shell32.dll,% ICON_BACK
  Menu,BufferMenu,Icon,Очистить безвозвратно,shell32.dll,% ICON_X
  Menu,BufferMenu,Icon,Фильтровать данные,shell32.dll,% ICON_FILTER
;  Menu,BufferMenu,Icon,Пропуск пакетов,shell32.dll,% ICON_SKIP
;  Menu,BufferMenu,Icon,Анализ пакетов,shell32.dll,% ICON_ANALYZE
;  Menu,BufferMenu,Icon,Скрывать одинаковые пакеты,shell32.dll,% ICON_HIDE_PCKGS

  menu,GrafMenu,add,Открыть,GrafOpen
  menu,GrafMenu,add,Закрыть,GrafClose
  menu,GrafMenu,add
  menu,GrafMenu,add,Сохранить текущее изображение графика,GrafSaveImage
  menu,GrafMenu,add,Сохранить все данные графика в текстовый документ,GrafSaveData
  menu,GrafMenu,add
  menu,GrafMenu,add,Автоматическое максимальное значение,GrafAutoMinMax
  menu,GrafMenu,add,Задать максимальное значение вручную	F2,GrafChangeMinMax
  menu,GrafMenu,add
  menu,GrafMenu,add,Автоматически определять разделитель графиков,GrafAutoEnding
  menu,GrafMenu,add,Сбросить автоопределение разделителя	F5,GrafAutoEndingReset
  menu,GrafMenu,add,Задать разделитель вручную	F3,GrafChangeEnding
  menu,GrafMenu,add
  menu,GrafMenu,add,Сглаживание "ступенек"	F4,GrafSmooth
  menu,GrafMenu,add,Остановить прокрутку	F1,GrafPause
  menu,GrafMenu,add
  menu,GrafMenu,add,Очистить график,GrafClear
  menu,GrafMenu,add
  menu,GrafMenu,add,Увеличить	Колесико мыши или Ctrl+,ZoomIn
  menu,GrafMenu,add,Уменьшить	Колесико мыши или Ctrl-,ZoomOut
  gosub,GrafMenuDisable
  menu,GrafMenu,Check,Автоматическое максимальное значение ;
  menu,GrafMenu,Check,Автоматически определять разделитель графиков ;
  menu,GrafMenu,Check,Сглаживание "ступенек"	F4 ;
  Menu,GrafMenu,Icon,Открыть,shell32.dll,% ICON_SHOW
  Menu,GrafMenu,Icon,Закрыть,shell32.dll,% ICON_HIDE
  Menu,GrafMenu,Icon,Сохранить текущее изображение графика,shell32.dll,% ICON_SAVE
;  Menu,GrafMenu,Icon,Сохранить все данные графика в текстовый документ,shell32.dll,% ICON_SAVE
;  Menu,GrafMenu,Icon,Автоматическое максимальное значение,shell32.dll,% ICON_FAVORITE
  Menu,GrafMenu,Icon,Задать максимальное значение вручную	F2,shell32.dll,% ICON_MANUAL
;  Menu,GrafMenu,Icon,Автоматически определять разделитель графиков,shell32.dll,% ICON_FAVORITE
  Menu,GrafMenu,Icon,Сбросить автоопределение разделителя	F5,shell32.dll,% ICON_RESET
  Menu,GrafMenu,Icon,Задать разделитель вручную	F3,shell32.dll,% ICON_MANUAL
;  Menu,GrafMenu,Icon,Сглаживание "ступенек"	F4,shell32.dll,% ICON_SMOOTH
;  Menu,GrafMenu,Icon,Остановить прокрутку	F1,shell32.dll,% ICON_STOP_SCROLL
  Menu,GrafMenu,Icon,Очистить график,shell32.dll,% ICON_CLEAR
  Menu,GrafMenu,Icon,Увеличить	Колесико мыши или Ctrl+,shell32.dll,% ICON_SIZE
  Menu,GrafMenu,Icon,Уменьшить	Колесико мыши или Ctrl-,shell32.dll,% ICON_SIZE

  menu,HelpMenu,add,О программе,GuiAbout
;  Menu,HelpMenu,Icon,Помощь,shell32.dll,% ICON_HELP
  Menu,HelpMenu,Icon,О программе,shell32.dll,% ICON_ABOUT

  menu,MenuBar,Add,Файл,:FileMenu
  menu,MenuBar,Add,Буфер приема,:BufferMenu
  menu,MenuBar,Add,График,:GrafMenu
  menu,MenuBar,Add,Справка,:HelpMenu

;--------------------------------------------------
  GuiCreate:
  Gui,1:Menu,MenuBar
  Gui,1:+hwndhMainWin ; -DPIScale ; +alwaysontop
  Gui,1:+MinimizeBox +maximizebox +Resize ; +E0x80000
  Gui,1:Margin,10,10
  Gui,1:font,s8,Tahoma
  Gui,1:Add,StatusBar,+hwndhStatusBar,Готов

  ; Настройки COM-порта ;
  Gui,1:Add,GroupBox,xm ym w210 h165,Настройки COM-порта ;
  Gui,1:Add,Radio,Checked vChoiceCOM xp+10 yp+20 w130,USB-COM устройство ;
  Gui,1:Add,Radio,+hwndhCOMNo xp y+10 w106,COM-порт №:
  Gui,1:Add,Radio,+hwndhCOMName xp y+3 w106,Искать по имени:
  Gui,1:Add,Edit,xp+110 yp-20 w50 vManualCOM gChoseCOMNo Number Limit3,1
  Gui,1:Add,UpDown,Range1-30
  Gui,1:Add,Edit,xp y+3 w80 vManualCOMByName gChoseCOMName,CH340
  Gui,1:add,Button,xp-110 y+5 w90 gAddSettings,Дополнительно ;
  Gui,1:add,Button,x+10 yp w90 gAceptSettings,Применить ;
  Gui,1:add,text,xp-98 y+6 w187 h40 v_text_ gDiskonnectCOM

  ; Отправить данные в порт ;
  Gui,1:Add,GroupBox,xm y+15 w210 h%E_Gr_Snd_h%,Отправить данные в порт ;
  Gui,1:add,text,xp+5 yp+22 +hwndhZeroState,0
  Gui,1:Add,Edit,x+1 yp-2 w190 h%E_1_h% vDataToSend gDataToSendCheck +hwndE_1,
  Gui,1:add,Button,+hwndhE_RTU_Addres_6 xm+22 yp-4 gFindAdd,Найти
  Gui,1:add,text,+hwndhE_RTU_Addres_1 x+3 yp+4,Адрес DEC: ;Адрес DEC:
  Gui,1:add,text,+hwndhE_RTU_Addres_2 xp+35 y+7,HEX:
  Gui,1:add,Edit,+hwndhE_RTU_Addres_3 xm+130 yp-24 w70 gChangeAddres Number Limit3 vRTU_SlaveAdress,
  Gui,1:Add,UpDown,+hwndhE_RTU_Addres_4 Range0-255 Wrap,1
  Gui,1:add,Edit,+hwndhE_RTU_Addres_5 xp y+ w70 gChangeAddres vRTU_SlaveAdressHEX,1
  GuiControl,hide,% hZeroState

  Gui,1:add,text,+hwndhE_RTU_Command_1 xm+10 yp+15,Команда: ;Команда:
  Gui,1:add,DropDownList,+hwndhE_RTU_Command_2 xm+10 y+ w190 Choose3 vRTU_Command gSubmitExt AltSubmit,% RTU_Command_List
  Gui,1:add,text,+hwndhE_RTU_StartReg_1 xm+19 y+10,Начальный рег. DEC: ;№ регистра DEC:
  Gui,1:add,text,+hwndhE_RTU_StartReg_2 xp+85 y+7,HEX:
  Gui,1:add,Edit,+hwndhE_RTU_StartReg_3 xm+130 yp-24 w70 gChangeStartReg Number vRTU_StartReg,
  Gui,1:Add,UpDown,+hwndhE_RTU_StartReg_4 Range0-65535 Wrap 0x80,0
  Gui,1:add,Edit,+hwndhE_RTU_StartReg_5 xp y+ w70 gChangeStartReg vRTU_StartRegHEX,0
  Gui,1:add,text,+hwndhE_RTU_RegNum_1 xm+10 y+10,Кол-во регистров DEC: ;Кол-во регистров DEC:
  Gui,1:add,text,+hwndhE_RTU_RegNum_2 xp+94 y+7,HEX:
  Gui,1:add,Edit,+hwndhE_RTU_RegNum_3 xm+130 yp-24 w70 gChangeRegNum Number vRTU_RegNum,
  Gui,1:Add,UpDown,+hwndhE_RTU_RegNum_4 Range1-100 Wrap 0x80,1
  Gui,1:add,Edit,+hwndhE_RTU_RegNum_5 xp y+ w70 gChangeRegNum vRTU_RegNumHEX,1
  Gui,1:add,text,+hwndhE_RTU_Val_1 xm+10 yp+15,Значение: ;Значение:
  Gui,1:add,checkbox,+hwndhE_RTU_Val_2 xm+10 y+%E_RTU_Val_y% vBeliveMe,Данным верить ;Данным верить:

   loop,2
     GuiControl,hide,% hE_RTU_Command_%a_index%
   loop,5
     GuiControl,hide,% hE_RTU_StartReg_%a_index%
   loop,6
     GuiControl,hide,% hE_RTU_Addres_%a_index%
   loop,5
     GuiControl,hide,% hE_RTU_RegNum_%a_index%
   loop,3
     GuiControl,hide,% hE_RTU_Val_%a_index%

  Gui,1:add,Radio,xm+10 y+5 gConvertToSYM vChoiceFormat,SYM
  Gui,1:add,Radio,x+5 yp gConvertToDEC,DEC
  Gui,1:add,Radio,x+5 yp gConvertToHEX Checked,HEX
  Gui,1:add,Radio,x+5 yp gConvertToBIN,BIN
  PreviousConversion:="hex"
  Gui,1:Add,GroupBox,+hwndhEDEC1 xm+10 y+2 w190 h55,DEC ;
  Gui,1:add,Radio,+hwndhEDEC2 xm+20 yp+15 gChooseDEC vChoiceDEC,Unsigned
  Gui,1:add,Radio,+hwndhEDEC3 x+5 yp gChooseDEC,Signed
  Gui,1:add,Radio,+hwndhEDEC4 x+5 yp gChooseDEC,Float
  Gui,1:add,Radio,+hwndhEDEC5 xm+20 y+5 gChooseDECSize Group vChoiceDECSize,1
  Gui,1:add,Radio,+hwndhEDEC6 x+5 yp gChooseDECSize,2
  Gui,1:add,Radio,+hwndhEDEC7 x+5 yp gChooseDECSize,4
  Gui,1:add,Radio,+hwndhEDEC8 x+5 yp gChooseDECSize,8   Bytes
   loop,8
     GuiControl,hide,% hEDEC%a_index%

  Gui,1:add,text,xm+10 y+10,Добавить контрольную сумму: ; Добавить контрольную сумму ;
  Gui,1:add,DropDownList,xp y+5 w190 +hwndhCS vAddCS gSubmitExt Choose1 AltSubmit,Нет|Сумма 8 бит|CRC-16|Modbus RTU|Modbus RTU шаблон ;
  Gui,1:add,Radio,+hwndhECSType1 xp y+ gChoiceCSType vChoiceCSType Checked,Сумма
  Gui,1:add,Radio,+hwndhECSType2 xp yp+15 gChoiceCSType,Полином=
  Gui,1:add,checkbox,+hwndhECSType3 x+ yp-15 Checked gSubmiting vRefIn,RefIn
  Gui,1:add,checkbox,+hwndhECSType4 x+ yp Checked gSubmiting vRefOut,RefOut
  Gui,1:add,Edit,+hwndhECSType5 xp-47 y+ w46 Limit4 gSubmiting vPolynom,8005
  Gui,1:add,text,+hwndhECSType6 x+5 yp+2,init=
  Gui,1:add,Edit,+hwndhECSType7 x+ yp-2 w46 Limit4 gSubmiting vInitVal,FFFF
  Gui,1:add,text,+hwndhECSType8 xp-115 y+2,XorOut=
  Gui,1:add,Edit,+hwndhECSType9 x+ yp-2 w46 Limit4 gSubmiting vXorOut,0000
  Gui,1:add,checkbox,+hwndhECSType10 x+ yp+4 gSubmiting vRefPoly,RefPoly
  loop,10
    GuiControl,hide,% hECSType%a_index%
  Gui,1:add,Button,xm+10 y+5 w120 h23 gButtonSendData,Отправить ;
  Gui,1:add,Button,x+ yp w69 h23 gSendDataRepitedly +hwndhSendDataRepitedly,Циклично ;

  Gui,1:add,Button,xm+10 yp-50 gModbusMapOpen +hwndhModbusMapOpen,Карта регистров ;
  GuiControl,hide,% hModbusMapOpen

  ; Буфер приёма COM-порта ;
  Gui,1:Add,GroupBox,xm+220 ym w720 h%hGroupBufer_h% +hwndhGroupBufer,Буфер приёма COM-порта ;
  Gui,1:add,text,+hwndhBufinfo xp+10 yp+20 w110 vBufinfo gClearQueue,В обработке 0. ;
  SaveBufer:={}

  Gui,1:add,text,+hwndhhide1 xp yp  w128,Ожидаемое число байт: ;
  Gui,1:add,edit,+hwndhhide2 x+5 yp-3 w70 Number Limit3 vRS232_Bytes_Reqest gSubmiting,% RS232_Bytes_Reqest
  Gui,1:add,UpDown,+hwndhhide3 Range1-128 gSubmitExt,% RS232_Bytes_Reqest
  Gui,1:Add,checkbox,+hwndhAutoByRe x+5 yp+3 checked vAuto_Bytes_Reqest gSubmiting,Автоматически ;
  GuiControl,hide,% hAutoByRe
  GuiControl,hide,% hhide1
  GuiControl,hide,% hhide2
  GuiControl,hide,% hhide3

  Gui,1:add,checkbox,xp-209 y+9 vFollowSelect gSubmitExt,Сохранять выделение таблицы (мониторить значение) ;
  Gui,1:add,ListView,xp y+7 w320 h%E_Text_h% +LV0x10000 v_List_ gListGetSelected AltSubmit grid Count%Rownums% NoSortHdr ReadOnly +hwndE_LV,#|№ байта|№ байта или описание|BIN|SYM|HEX|DEC|WORD|INT|DWORD|DINT|Float|Double
  ImageListID:=IL_Create(300)
  loop,300
    IL_Add(ImageListID,"shell32.dll",a_index)
  LV_SetImageList(ImageListID)

  ; Разделитель 1
  Gui,1:Add,Progress,x+0 yp w20 h%E_Text_h% vProgress cNavy backgroundSilver Vertical +hwndhProgress

  Gui,1:add,checkbox,x+0 ym+20 +hwndE_111 vEN_Kurvendrucker gKurvendrucker,Включить графопостроитель ;
  Gui,1:add,checkbox,xp y+10 +hwndE_112 vAddCRLF gSubmitExt Checked,Добавить перевод строки после каждого сообщения ;
  Gui,1:font,s12,Courier New
  tmp1:=E_Text_h-36
  Gui,1:add,edit,xp y+5 w360 h%tmp1% HScroll VScroll ReadOnly Multi v_InText_ hwndE_Text

  Gui,1:font,s8,Tahoma
  Gui,1:add,ListView,xp yp w360 h%tmp1% +LV0x10000 v_SecondList_ gSecondListGetSelected AltSubmit grid Count%Rownums% NoSortHdr ReadOnly +hwndE_SLV,#|№ байта|№ байта или описание|BIN|SYM|HEX|DEC|WORD|INT|DWORD|DINT|Float|Double
  Gui,1:font,s12,Courier New
  LV_SetImageList(ImageListID)
  Gui,1:ListView,% E_LV
  GuiControl,Hide,% E_SLV
  AnBufer:={}
  AnCufer:={}

  ; Разделитель 2
  Gui,1:Add,Progress,xp y+0 h20 w360 vProgress2 cSilver backgroundNavy +hwndhProgress2,100
  Gui,1:add,edit,% "xp+2 yp+22 w" Gdip_W-4 " h" 12 " +hwndhGrafPos"
  ;GuiControl,Hide,% hGrafPos

  Gui,1:font,s8,Tahoma
  Gui,1:add,button,xp-340 y+10 w200 gToTheEnd +hwndhTtEButton,Продолжить прокрутку ;
  Gui,1:add,button,% "x+10 yp w490 vUpdateButton gUpdate +hwndhClButton",Обновить ;

  Gui,1:add,checkbox,xm+230 yp gSubmiting vGrafPause +hwndhECB0,Остановить прокрутку (F1)

  Gui,1:add,text,xm+218 y+1 w1 +hwndhE0, ;
  Gui,1:font,s10 w1000,Tahoma
  loop,% MaxNumOfGraf
    Gui,1:add,checkbox,% "x+5 yp w50 gSubmiting vEnableGraf" a_index " checked c" ColourPen%a_index% " +hwndhECB" a_index,% "[" a_index "]"

  loop,% MaxNumOfGraf+1
  {
    i:=a_index-1
    GuiControl,hide,% hECB%i%
  }
  GuiControl,hide,% hE0
  Gui,1:font,s8,Tahoma
  Gui,1:Show,w%CreateGui_w% h%CreateGui_h%,COM Terminal v%Version%
  Gui,1:+MinSize

  E%E_LV%=
  ( LTrim
  )

  E%E_LV2%=
  ( LTrim
  Варианты представления выделенных байтов.
  )

  E%hProgress%=
  ( LTrim
  Шкала заполнения таблицы. При переполнении таблица и текстовое поле очистятся.
  )

  E%hProgress2%=
  ( LTrim
  Позиция в графике. Правый клик - сменить позицию.
  )

  E%E_1%=
  ( LTrim

  )

  E%E_111%=
  ( LTrim
  Kurvendrucker по немецки.
  )

  E%hECB12%=
  ( LTrim
  Правый клик - отключить лишние графики. Правый клик на отключенный - включить все.
  )

  E%hBufinfo%=
  ( LTrim
  Клик чтобы очистить очередь. Правый клик - меню.
  )

  E%hECSType1%=
  ( LTrim
  Сумма всех байтов данных
  )

  E%hECSType2%=
  ( LTrim
  Циклический избыточный код
  )

  E%hECSType3%=
  ( LTrim
  Обратный порядок битов для входных данных
  )

  E%hECSType4%=
  ( LTrim
  Обратный порядок битов для итога контрольной суммы
  )

  E%hECSType5%=
  ( LTrim
  Порождающее полиномиальное число в формате HEX
  )

  E%hECSType6%=
  ( LTrim
  Стартовое значение контрольной суммы (число в формате HEX)
  )
  E%hECSType7%:=E%hECSType6%

  E%hECSType8%=
  ( LTrim
  Применить исключающее-ИЛИ к контрольной сумме с данным числом в формате HEX
  )
  E%hECSType9%:=E%hECSType8%

  E%hECSType10%=
  ( LTrim
  Обратный порядок битов для полиномиального числа
  )

  E%hECB1%:=E%hECB2%:=E%hECB3%:=E%hECB4%:=E%hECB5%:=E%hECB6%:=E%hECB7%:=E%hECB8%:=E%hECB9%:=E%hECB10%:=E%hECB11%:=E%hECB12%
  VarSetCapacity(E_Text_start, 4), VarSetCapacity(E_Text_end, 4)

  Gdip_Initialise:
  if Gdip_Initialised
  {
    E%E_LV2%.=" Нажмите на нужный вариант для отрисовки графика."
    gui,3:+owner1
    Gui,3:+hwndhGraf -Caption +E0x80000 +LastFound +ToolWindow ;+AlwaysOnTop WS_EX_LAYERED := 0x80000
    Gui,3:Show,x0 y0 w1 h1
  ;  SetParent(hGraf,hMainWin,,0,0)

    Gdip_FontFamilyCreate(Gdip_Font)

    Gdip_hbm2 := CreateDIBSection(A_ScreenWidth,A_ScreenHeight)
    pMainHolst := CreateCompatibleDC()
    Gdip_obm2 := SelectObject(pMainHolst,Gdip_hbm2)
    pMainHolst_G := Gdip_GraphicsFromHDC(pMainHolst)
    Gdip_SetSmoothingMode(pMainHolst_G,4)

    Gdip_Font:="Lucida Console"
    Gdip_Font_Size:="s15"
    Gdip_Font_W:=10
    Gdip_Font_H:=15
    Gdip_Font_Selected_Colour:="ff000000"

    Gdip_WhiteBrush:= Gdip_BrushCreateSolid(0xffffffff) ; белая кисть для заливки
    Gdip_WhiteBrush2:= Gdip_BrushCreateSolid(0xd0ffffff) ; белая кисть для заливки
    Gdip_BlackBrush:= Gdip_BrushCreateSolid(0xff000000) ; черная кисть для заливки
    Gdip_WhitePen := Gdip_CreatePen(0xffffffff,1) ; карандаш для стирания
    Gdip_GrayPen := Gdip_CreatePen(0xff808080,1) ; карандаш для оси
    Gdip_GrayPen2 := Gdip_CreatePen(0xffe0e0e0,1) ; карандаш для оси
    Gdip_GrayPen3 := Gdip_CreatePen(0x11000000,1) ; карандаш для оси
    Gdip_BluePen := Gdip_CreatePen(0xff0000ff,1) ; карандашы для графика
    Gdip_BlackPen := Gdip_CreatePen(0xff000000,2) ; карандашы для графика
    Line_w:=1

    loop,% MaxNumOfGraf
    {
      Gdip_Pen%a_index%:=Gdip_CreatePen(0xff000000 | ColourPen%a_index%,Line_w)
      Gdip_Brush%a_index%:= Gdip_BrushCreateSolid(0x40000000 | ColourPen%a_index%) ; белая кисть для заливки
    }

    Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush,0,0,Gdip_Points_Num,Gdip_H)
  }

  Gui,1:ListView,% E_SLV
  LV_ModifyCol(COLUMN_#,65)
  LV_ModifyCol(COLUMN_HOOK,0)
  LV_ModifyCol(COLUMN_INFO,45)
  LV_ModifyCol(COLUMN_BIN,60)
  LV_ModifyCol(COLUMN_SYM,20)
  LV_ModifyCol(COLUMN_HEX,37)
  LV_ModifyCol(COLUMN_DEC,32)
  LV_ModifyCol(COLUMN_WORD,50)
  LV_ModifyCol(COLUMN_INT,50)
  LV_ModifyCol(COLUMN_DWORD,20)
  LV_ModifyCol(COLUMN_DINT,20)
  LV_ModifyCol(COLUMN_FLOAT,80)
  LV_ModifyCol(COLUMN_DOUBLE,40)
  Gui,1:ListView,% E_LV
  LV_ModifyCol(COLUMN_#,65)
  LV_ModifyCol(COLUMN_HOOK,0)
  LV_ModifyCol(COLUMN_INFO,45)
  LV_ModifyCol(COLUMN_BIN,60)
  LV_ModifyCol(COLUMN_SYM,20)
  LV_ModifyCol(COLUMN_HEX,37)
  LV_ModifyCol(COLUMN_DEC,32)
  LV_ModifyCol(COLUMN_WORD,50)
  LV_ModifyCol(COLUMN_INT,50)
  LV_ModifyCol(COLUMN_DWORD,20)
  LV_ModifyCol(COLUMN_DINT,20)
  LV_ModifyCol(COLUMN_FLOAT,80)
  LV_ModifyCol(COLUMN_DOUBLE,40)

  Gui,2:-sysmenu +owner1
  Gui,2:+hwndhAddWin ; -DPIScale ; +alwaysontop
  ;Gui,2:Color,dfdfdf
  Gui,2:Margin,10,10
  Gui,2:font,s8
  Gui,2:add,text,xm ym w120,Скорость передачи: ;
  Gui,2:Add,DropDownList,+hwndhRS232_Baud_Choice x+3 yp-3 w195 Choose14 vRS232_Baud_Choice gCalcTimeout,50|75|110|150|300|600|1200|1800|2000|2400|3600|4800|7200|9600|14400|19200|28800|38400|57600|115200|250000|300000|375000|500000|750000|1500000|3000000
  Gui,2:add,text,xm y+10 w120,Контрольные биты: ;
  Gui,2:Add,DropDownList,+hwndhRS232_Parity_Choice x+3 yp-3 w195 Choose1 vRS232_Parity_Choice gCalcTimeout,N (None) - без проверки|E (EVEN) - проверка на четность|O (Odd) - проверка на нечетность|M (MARk) - бит всегда 1|S (SPACE) - бит всегда 0
  Gui,2:add,text,xm y+10 w120,Число бит на символ: ;
  Gui,2:Add,DropDownList,+hwndhRS232_Data_Choice x+3 yp-3 w195 Choose4 vRS232_Data_Choice gCalcTimeout,5|6|7|8
  Gui,2:add,text,xm y+10 w120,Число стоповых бит: ;
  Gui,2:Add,DropDownList,+hwndhRS232_Stop_Choice x+3 yp-3 w195 Choose1 vRS232_Stop_Choice gCalcTimeout,1|2

  Gui,2:add,text,xm y+10 w120,ReadIntervalTimeout: ;
  Gui,2:Add,Edit,+hwndhRITimeout x+3 yp-3 w50 vReadIntervalTimeout,% ReadIntervalTimeout
  Gui,2:add,text,x+5 yp+3 w20 ,ms
  Gui,2:add,text,+hwndhTimeout x+10 yp w120,Расчетный = %Timeout% ms ;


  Gui,2:add,Button,xm y+10 w80 gDefaultSettings default,Поумолчанию ;
  Gui,2:add,Button,x+65 yp w80 gEndAddSettings default,Применить ;
  Gui,2:add,Button,x+10 yp w80 gCancelAddSettings,Отмена ;

  Gui,4:-sysmenu +owner1
  Gui,4:+hwndhFilterWin ; -DPIScale ; +alwaysontop
  Gui,4:Margin,10,10
  Gui,4:font,s8
  Gui,4:add,checkbox,xm ym gChangeFilter_Addres vFilter_Addres_EN,Фильтровать пакеты по Byte_№1/"Адресу" ;
  Gui,4:add,Edit, xm+255 yp-3 w70 gChangeFilter_Addres Number Limit3 vFilter_Addres,
  Gui,4:Add,UpDown, Range1-255 Wrap 0x80,1
  Gui,4:add,Edit, xp y+1 w70 gChangeFilter_Addres vFilter_AddresHEX,1
  Gui,4:add,checkbox,xm y+20 gChangeFilter_Command vFilter_Command_EN,Фильтровать пакеты по Byte_№2/"Команде" ;
  Gui,4:add,Edit,+hwndhFiltE1 xm+255 yp-3 w70 gChangeFilter_Command Number Limit3 vFilter_Command,
  Gui,4:Add,UpDown,+hwndhFiltE2 Range1-255 Wrap 0x80,1
  Gui,4:add,Edit,+hwndhFiltE3 xp y+1 w70 gChangeFilter_Command vFilter_CommandHEX,1
  Gui,4:Add,DropDownList,+hwndhFiltE4 xp-200 yp w195 gChangeFilter_Command vFilter_CommandRTU Choose3 AltSubmit,% RTU_Command_List

  Gui,4:add,checkbox,xm y+20 gChangeFilter_Byte vFilter_Byte_EN,Фильтровать по Byte_№3/"Кол-во байт" ;
  Gui,4:add,Edit, xm+255 yp-3 w70 gChangeFilter_Byte Number Limit3 vFilter_Byte,
  Gui,4:Add,UpDown, Range1-255 Wrap 0x80,1
  Gui,4:add,Edit, xp y+1 w70 gChangeFilter_Byte vFilter_ByteHEX,1

  Gui,4:Add,Radio,+hwndhFiltE5 xm y+20 Checked gChangeFilter_Addres vFilter_Message_EN,Принимать все ;
  Gui,4:Add,Radio,+hwndhFiltE6 xp y+5 gChangeFilter_Addres,Не принимать запросы мастера ;
  Gui,4:Add,Radio,+hwndhFiltE7 xp y+5 gChangeFilter_Addres,Не принимать ответы слейва ;
  Gui,4:Add,checkbox,+hwndhFiltE8 xp y+15 gChangeFilter_Addres vFilter_CRC_EN,Не принимать ошибки контрольной суммы ;
  Gui,4:add,Button,xm+255 y+20 w70 gDataFilterOk,Ok ;

  ;GuiControl,Show,% hFiltE4
  ;GuiControl,Show,% hFiltE5
  ;GuiControl,Show,% hFiltE6
  ;GuiControl,Show,% hFiltE7
  ;loop,3
  ;  GuiControl,disable,% hFiltE%a_index%
  GuiControl,hide,% hFiltE4
  GuiControl,hide,% hFiltE5
  GuiControl,hide,% hFiltE6
  GuiControl,hide,% hFiltE7
  loop,3
    GuiControl,enable,% hFiltE%a_index%
  GuiControl,hide,% hFiltE8


  gosub,6GuiCreate
  gosub,7GuiCreate
  gosub,8GuiCreate
  Gui,1:default
  gosub,HotkeysOn

  varsetcapacity(Data,DataSize,0xFF)
  msPointer:=&Data

  MainPID:=DllCall("GetCurrentProcessId")
  ; run slave
  Params:=" " MainPID " " hBufinfo " " hMainWin " " msPointer
  if a_iscompiled
    run,% a_scriptfullpath . Params,,UseErrorLevel,SlavePID
  else
    run,% """" a_ahkPath """ """ a_scriptfullpath """" Params,,UseErrorLevel,SlavePID
  settimer,WM_MOUSEMOVE,100
;  gosub,EndAddSettings
  F:=Func("CheckPIDExist").Bind(SlavePID)
  settimer,% F,2000

;  while(!slPointer)
;  {
;    sleep,100
;    if (a_index>20)
;    {
;      msgbox,Main`nОшибка получения указателя.
;      goto,EXIT
;    }
;  }
;  SLM:=new _ClassMemory("Ahk_PID" SlavePID)
;  SLM.write(slPointer+_COMBusy,0,"uChar")
  NumPut(0,Data,_COMBusy,"UChar")
  NumPut(DefaultBReq,Data,_RS232_Bytes_Reqest,"Int")

  loop,300
    LV_Add("Icon" a_index,,,a_index)

  gosub,AceptSettings
  if !FileOpened
  gosub,Update

MainLoop:
loop
{
  Loop_Time:=oldLoop_Time!="" ? a_tickcount-oldLoop_Time : 0
  oldLoop_Time:=a_tickcount
  sleep,10
  while(FileOpened)
    sleep,100
  if (NumGet(Data,_Diskonnect,"UChar"))
    sleep,100
  else if (NumGet(Data,_COMBusy,"UChar"))
  {
    NumPut(0,Data,_COMBusy,"UChar")
    GuiControl,,_text_,% COMPort "`n`nнедоступен"
    UnCOMPort:=COMPort
    settimer,ResetUnCOMPort,2000
    ComPortList:=SearchCOM("",1)
    GuiControl,,% E_Text,% COMPort " недоступен.`nВыберите другой порт вручную.`n`nНайденные в системе порты:`n" ComPortList
  }
  else if (NumGet(Data,_COMFail,"UChar"))
  {
    NumPut(0,Data,_COMFail,"UChar")
    gosub,AceptSettings
  }
  else
  {
;    ; отправляем данные в буфер порта
    NumPut(MapRun,Data,_MapRun,"Int")
    if MapRun
    {
      ADDCS:=4
      AddCSLenght:=2
      if SendDataRepitedly
        gosub,SendDataRepitedly
      if (OldMapNum=MapNum)
        MapTimeOut+=Loop_Time
      else
        MapTimeOut:=0
;tooltip,% MapTimeOut "`n" MapNext
      OldMapNum:=MapNum
      if (MapTimeOut>MapTOut)
      {
        MapNext:=1
        Gui,6:default
        loop,9
          LV_GetText(tmp%a_index%,MapNum,a_index)
        LV_Modify(MapNum,,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,"",tmp9)
        Gui,1:default
      }
      if MapNext
      {
        if !MapPriority
          MapNum++
        else
        {
          if !MapPrioNext
          {
            MapPrioNext:=1
            SMapNum:=MapNum
            MapNum:=MapPriority
          }
          else
          {
            MapPrioNext:=0
            MapNum:=SMapNum+1
            if (MapNum=MapPriority)
              MapNum++
          }
        }
        if (MapNum>MapList.MaxIndex())
          MapNum:=1
        MapTimeOut:=0
        MapNext:=0
        Gui,6:default
        LV_Modify(MapNum,"Focus")
        Gui,1:default
        gosub,SendData
      }
    }

    if SendDataRepitedly
      gosub,SendData

    if (!doonce and !NumGet(Data,_Diskonnect,"UChar"))
    {
      dots:=""
      doonce:=1
      GuiControl,,_text_,% "Подключен " COMPort ".`n`n`t`tОтключиться"
      GuiControl,,% E_Text,% "Подключен " COMPort "`r`n"
    }

    InWork:=NumGet(Data,_PackagesRecieved,"Int")-ReadPack
    if (InWork<0)
    {
      ReadPack:=NumGet(Data,_PackagesRecieved,"Int")
      InWork:=0
    }
    if (InWork>=10 and !swapcolor)
    {
      swapcolor:=!swapcolor
      GuiControl,+cRed,Bufinfo
    }
    else if (InWork<10 and swapcolor)
    {
      swapcolor:=!swapcolor
      GuiControl,+cblack,Bufinfo
    }
;    ; принимаем данные из буфера порта
    GuiControl,,Bufinfo,% "В обработке " InWork " пакетов."
    if NumGet(Data,_DataAvailable,"UChar")
    {
      DataFromSlaveSize:=NumGet(Data,_RecieveDataOffset+ReadPos,"Int")
;      tooltip,% DataFromSlaveSize "`n" ReadPos
      if (DataFromSlaveSize<0 or DataFromSlaveSize>4096) ;DefaultBReq
      {
        NumPut(0,Data,_DataAvailable,"UChar")
        if (DataFromSlaveSize=-2)
          ReadPos:=ReadPack:=0
        else if (DataFromSlaveSize>DefaultBReq)
          gosub,ClearQueue
        continue
      }
      if (DataFromSlaveSize>0)
      {
        Bufer:={}
        NumPut(0xFFFFFFFF,Data,_RecieveDataOffset+ReadPos,"UInt")
        loop,% DataFromSlaveSize
        {
          addr:=_RecieveDataOffset+ReadPos+3+a_index
          Bufer[a_index]:=NumGet(Data,addr,"UChar")
          NumPut(0xFF,Data,addr,"UChar")
        }
        SaveBufer.Push(Bufer.Clone())
        ReadPos+=DataFromSlaveSize+4
        ReadPack++
        Packages_Recieved++
        if (SkeepPack and InWork>3)
          continue
        RS232_Bytes_Received:=DataFromSlaveSize
        Received_Addres:=Bufer[1]
        Received_Command:=Bufer[2]
        Received_Byte:=Bufer[3]
        ; определить корректность контрольной суммы
        CheckSummIsCorrect:=0
        if (AddCS>=4 and RS232_Bytes_Received>2)
        {
          tmp1:=(Bufer[RS232_Bytes_Received]<<8)|Bufer[RS232_Bytes_Received-1]
          tmp2:=CRC16(Bufer,RS232_Bytes_Received-2)
          CheckSummIsCorrect:=(tmp1==tmp2)
        }
        else if (AddCS=2 or (AddCS=3 and RS232_Bytes_Received>2))
        {
          tmp0:=CRC16(Bufer,RS232_Bytes_Received-AddCSLenght,(ChoiceCSType=1),Polynom,InitVal,Refin,RefOut,XorOut,RefPoly)
          if AddCS=2
            tmp1:=Bufer[RS232_Bytes_Received]
          else if AddCS=3
            tmp1:=(Bufer[RS232_Bytes_Received]<<8)|Bufer[RS232_Bytes_Received-1]
          tmp2:=(AddCS=3) ? tmp0 : tmp0&0xff
          CheckSummIsCorrect:=(tmp1==tmp2)
        }
        ; определить тип пакета Modbus
        Received_Message_Type:=""
        if (AddCS>=4)
        {
          if CheckSummIsCorrect
          {
            if (Received_Command>0 and Received_Command<5)
            {
              tmp1:=Bufer[3]
              if (RS232_Bytes_Received=tmp1+5)
                Received_Message_Type:="Ответ" (tmp1=3?" возможно Запрос":"")
              else if (RS232_Bytes_Received=8)
                Received_Message_Type:="Запрос"
            }
            else if (Received_Command>4 and Received_Command<7)
              Received_Message_Type:="Запрос/Ответ"
            else if (Received_Command=0x0f or Received_Command=0x10)
            {
              tmp1:=Bufer[7]
              if (RS232_Bytes_Received=tmp1+9)
                Received_Message_Type:="Запрос"
              else
                Received_Message_Type:="Ответ"
            }
          }
        }
        anindex:=RS232_Bytes_Received "|" Received_Addres "|" Received_Command "|" (Received_Message_Type?0:Received_Byte) "|" Received_Message_Type "|" CheckSummIsCorrect
        if !isobject(AnBufer[anindex])
        {
          AnBufer[anindex]:={}
          AnCufer[anindex]:={}
        }
        AnBufer[anindex].Push(Bufer.Clone())
        AnCufer[anindex].Push(Packages_Recieved)
        if Analyzer
        {
          updateOnce:=0
          AnalyzerTimer:=0
          continue
        }
        Gosub,ParseBuffer
        if (FoundAdd="" and CheckSummIsCorrect)
          FoundAdd:=Received_Addres
      }
    }
    else if Analyzer
    {
      AnalyzerTimer+=Loop_Time
      if (AnalyzerTimer>1000 and !updateOnce)
      {
        Update:=1
        updateOnce:=1
        gosub,Analyze
      }
    }
  }
  gosub,SetFormatFloat
}
}
else ;Slave
{
  SysGet,CAPTION,4
  SysGet,XBORDER,5
  SysGet,YBORDER,6
  SysGet,XEDGE,45
  SysGet,YEDGE,46
  BORDERX:=XBORDER+XEDGE
  BORDERY:=YBORDER+YEDGE+CAPTION

  F:=Func("CheckPIDExist").Bind(2ndThread)
  settimer,% F,2000

;  varsetcapacity(Data,DataSize,0xFF)
;  slPointer:=&Data
;  while(numget(Data,_COMBusy,"uChar"))
;  {
;    PostMessage,0x8080,0,%slPointer%,,ahk_pid %2ndThread%
;    sleep,100
;    if (a_index>20)
;    {
;      msgbox,Slave`nОшибка получения указателя.
;      goto,EXIT
;    }
;  }
  Controlgetpos,Bx,By,Bw,Bh,,ahk_id %hBufinfo%
  Gui,1:Margin,0,0
  Gui,1:font,s8,Tahoma
  gui,1:+hwndhSlaveWin -border
  Gui,1:+0x40000000 -0x80000000
  gui,1:add,text,x0 y0 w200 vBufinfo,Текст
  gui,1:show,NA w200

  gui,2:add,text,x1000 y1000 w1000 h1000 +hwndhTest,Test
  Controlgetpos,Testx,Testy,Testw,Testh,,ahk_id %hTest%
  gui,2:Destroy
  DPICoef:=Testw/1000
;tooltip,% Bx "`n" By "`n" Bw "`n" Bh "`n`n" Testx-Testw "`n" Testy-Testh "`n`n" BORDERX "`n" BORDERY

  SetParent(hSlaveWin,hMainWin,,Bx+Bw-BORDERX*DPICoef,By-25-BORDERY*DPICoef)

  settimer,CheckMainExist,1000
  Menu,Tray,Icon
  SlavePID:=DllCall("GetCurrentProcessId")
  varsetcapacity(In_Data,_RecieveDataOffset)
  varsetcapacity(tmp_In_Data,_RecieveDataOffset)
  MSM:=new _ClassMemory("Ahk_PID" 2ndThread)
  varsetcapacity(Send_Data,_RecieveDataOffset-_SendDataOffset)
  MSM.write(msPointer+_SendDataNow,0,"UChar")
  DllCall("QueryPerformanceCounter", "Int64*", StartTime)

  tltp:=[]


SlaveLoop:
loop
{
  Loop_Time:=oldLoop_Time!="" ? a_tickcount-oldLoop_Time : 0
  oldLoop_Time:=a_tickcount
/*
Array =
Size			Int  4	+0
Data byte 0		byte 1	+4
Data byte 1		byte 1
Data byte 2		byte 1
Data byte n		byte 1
Size			Int  4
Data byte 0		byte 1
Data byte 1		byte 1
Data byte 2		byte 1
Data byte n		byte 1
...

if Size == 0xFFFFFFFF
  End
*/

  if MSM.Read(msPointer+_Diskonnect,"UChar")
  {
    PackagesRecieved:=0
    RS232_Close(RS232_FileHandle)
  }
  else if (MSM.Read(msPointer+_ApplyCOMSettings,"UChar"))
  {
    MSM.write(msPointer+_ApplyCOMSettings,0,"UChar")
    PackagesRecieved:=0
    PackagesSended:=0
    RS232_Close(RS232_FileHandle)
    varsetcapacity(var,12)
    MSM.ReadRaw(msPointer+_COMPort,var,12)
    COMPort:=StrGet(&var,6)
    RS232_Baud:=MSM.Read(msPointer+_RS232_Baud,"Int")
    RS232_Parity:=chr(MSM.Read(msPointer+_RS232_Parity,"Int"))
    RS232_Data:=MSM.Read(msPointer+_RS232_Data,"Int")
    RS232_Stop:=MSM.Read(msPointer+_RS232_Stop,"Int")
    ReadIntervalTimeout:=MSM.Read(msPointer+_ReadIntervalTimeout,"Int")
;tooltip,% COMPort "`n" RS232_Baud "`n" RS232_Parity "`n" RS232_Data "`n" RS232_Stop "`n" ReadIntervalTimeout
    RS232_FileHandle:=RS232_Get_FileHandle(COMPort
                                          ,RS232_Baud
                                          ,RS232_Parity
                                          ,RS232_Data
                                          ,RS232_Stop
                                          ,ReadIntervalTimeout)

    if !RS232_FileHandle
      MSM.write(msPointer+_COMBusy,1,"UChar")
  }
  if COMFail
  {
    RS232_FileHandle:=0
    MSM.write(msPointer+_COMFail,1,"UChar")
    COMFail:=0
  }
  if RS232_FileHandle
  {
    if (MSM.Read(msPointer+_SendDataNow,"UChar")=1)
    {
      size:=MSM.Read(msPointer+_SendSize,"Int")
      if size
      {
        PackagesSended++
        GuiControl,,Bufinfo,% "_______ в буфере " PackagesRecieved ", отправлено " PackagesSended " пакетов."
        MSM.ReadRaw(msPointer+_SendDataOffset,Send_Data,size)
        RS232_ClearRXBuffer(RS232_FileHandle)
        RS232_Write(RS232_FileHandle,&Send_Data,size)
      }
      MSM.write(msPointer+_SendDataNow,2,"UChar")
      DllCall("QueryPerformanceCounter", "Int64*", StartTime)
    }
    MSM.write(msPointer+_DataPos,DataPos,"Int")
    MSM.write(msPointer+_PackagesRecieved,PackagesRecieved,"Int")
    RS232_Bytes_Reqest:=MSM.Read(msPointer+_RS232_Bytes_Reqest,"Int")
    MapRun:=MSM.Read(msPointer+_MapRun,"Int")
    RTimeout:=MSM.Read(msPointer+_RTimeout,"Int")
    EmptyPacket:=MSM.Read(msPointer+_EmptyPacket,"Int")
    RS232_Read(RS232_FileHandle,1,&In_Data,RS232_Bytes_Received)
    if RS232_Bytes_Received
    {
      Offset:=RS232_Bytes_Received
      Packet:=false
      EmptyPack:=0
      DllCall("QueryPerformanceCounter", "Int64*", StartTime)
      loop,% 1000//ReadIntervalTimeout
      {
        RS232_Read(RS232_FileHandle,4096,&In_Data+Offset,RS232_Bytes_Received)
        DllCall("QueryPerformanceFrequency", "Int64*", frequency)
        DllCall("QueryPerformanceCounter","Int64*",EndTime)
        RTimer:=(EndTime-StartTime)/frequency*1000
        if RS232_Bytes_Received
        {
          EmptyPack:=0
          Packet:=true
          DllCall("QueryPerformanceCounter", "Int64*", StartTime)
          Offset+=RS232_Bytes_Received
        }
        else if Packet
          EmptyPack++
        if (RTimer>ReadIntervalTimeout*(EmptyPacket+4) or EmptyPack=EmptyPacket)
        {
          MSM.write(msPointer+_SendDataNow,0,"UChar")
          GuiControl,,Bufinfo,% "_______ в буфере " PackagesRecieved ", отправлено " PackagesSended " пакетов."
          break
        }
      }
      RS232_Bytes_Received:=Offset
    }
    else
    {
      DllCall("QueryPerformanceFrequency", "Int64*", frequency)
      DllCall("QueryPerformanceCounter","Int64*",EndTime)
      RTimer:=(EndTime-StartTime)/frequency*1000
      if (RTimer>RTimeout)
      {
        MSM.write(msPointer+_SendDataNow,0,"UChar")
        DllCall("QueryPerformanceCounter", "Int64*", StartTime)
      }
    }

      if (MapRun and MSM.Read(msPointer+_MapSkip,"Int")=2)
      {
        MSM.Write(msPointer+_MapSkip,0,"Int")
        RS232_Bytes_Received:=0
      }
;    }
;    else
;      sleep,10

    if RS232_Bytes_Received
    {
      PackagesRecieved++
      GuiControl,,Bufinfo,% "(чтение) в буфере " PackagesRecieved ", отправлено " PackagesSended " пакетов."
      MSM.WriteRaw(msPointer+_RecieveDataOffset+DataPos+4,&In_Data,RS232_Bytes_Received)
      MSM.write(msPointer+_RecieveDataOffset+DataPos,RS232_Bytes_Received,"Int")
      DataPos+=RS232_Bytes_Received+4
      MSM.write(msPointer+_DataAvailable,1,"UChar")
      RS232_Bytes_Received:=0
    }
    if (DataPos>DataSize-_RecieveDataOffset-10240)
    {
      MSM.write(msPointer+_RecieveDataOffset+DataPos,0xFFFFFFFE,"UInt")
      DataPos:=0
      PackagesRecieved:=0
    }
;    GuiControl,,Bufinfo,% "_______ в буфере " PackagesRecieved ", отправлено " PackagesSended " пакетов."
  }
  else
    sleep,100
}
}
return

ZeroCount:
  InputBox,tmp1,Количество знаков после запятой,,,240,120,,,,12,3
  if !errorlevel
    FloatFormat:=tmp1
  return

SetFormatFloat:
  SetFormat,Float,0.%FloatFormat%
  return

6GuiCreate:
  MapList:=[]
  tmpMap:=[]
  MapTOut:=700
  menu,MapRmenu,add,Добавить,MapAdd
  menu,MapRmenu,add,Редактировать,MapEdit
  menu,MapRmenu,add,Изменить адрес выбранных,MapAddresChange
  menu,MapRmenu,add
  menu,MapRmenu,add,Копировать текст ячейки,MapCopy
  menu,MapRmenu,add,Приоритетный опрос первого выделенного,MapPrioritySet
  menu,MapRmenu,add
  menu,MapRmenu,add,Удалить,MapDelete

  menu,MapFileMenu,Add,Открыть,MapOpen
  menu,MapFileMenu,Add,Сохранить,MapSave
  menu,MapFileMenu,Add
  menu,MapFileMenu,Add,Количество знаков после запятой,ZeroCount
  menu,MapFileMenu,Add
  menu,MapFileMenu,Add,Выход,MapExit
  Menu,MapFileMenu,Icon,Открыть,shell32.dll,% ICON_OPEN
  Menu,MapFileMenu,Icon,Сохранить,shell32.dll,% ICON_SAVE
  Menu,MapFileMenu,Icon,Выход,shell32.dll,% ICON_EXIT

  Menu,MapConnection,add,Пуск,MapRun
  Menu,MapConnection,add,Стоп,MapStop
  Menu,MapConnection,add,Задать таймаут,MapSetTOut
  Menu,MapConnection,Icon,Пуск,shell32.dll,% ICON_START
  Menu,MapConnection,Icon,Стоп,shell32.dll,% ICON_STOP

  menu,MapMenuBar,Add,Файл,:MapFileMenu
  menu,MapMenuBar,Add,Регистры,:MapRmenu
  menu,MapMenuBar,Add,Связь,:MapConnection

  Gui,6:Menu,MapMenuBar
  Gui,6:+hwndhMapWin ; -DPIScale ; +alwaysontop
  Gui,6:+MinimizeBox +maximizebox +Resize ; +E0x80000
  Gui,6:Margin,0,0
  Gui,6:font,s8,Tahoma
  Gui,6:Add,StatusBar,+hwndhMapStatusBar
  Gui,6:add,ListView,xm ym w800 h600 +LV0x10000 v_MapList_ gMapGetSelected AltSubmit grid Count256 NoSortHdr ReadOnly +hwndMap_LV,Адрес|№ регистра|Тип регистра|Тип данных|Значение|Значение HEX|Значение BIN|Актуальность|Коментарий
;  Gui,6:add,Button,xp y+5,Пуск
  Gui,6:default
  LV_ModifyCol(1,50)
  LV_ModifyCol(2,60)
  LV_ModifyCol(3,20)
  LV_ModifyCol(4,50)
  LV_ModifyCol(5,70)
  LV_ModifyCol(6,70)
  LV_ModifyCol(7,130)
  LV_ModifyCol(8,20)
  LV_ModifyCol(9,300)
  Gui,1:default
  return

7GuiCreate:
  Gui,7:-sysmenu +owner6
  Gui,7:+hwndhAddMapWin ; -DPIScale ; +alwaysontop
  ;Gui,7:Color,dfdfdf
  Gui,7:Margin,10,10
  Gui,7:font,s8
  Gui,7:add,text,xm ym w120,Адрес: ;
  Gui,7:Add,Edit,x+3 yp-3 w50 vMapAddres gMapChangeAddres +hwndhMapAddres -Multi,1
  Gui,7:add,text,x+5 yp+3,HEX:
  Gui,7:Add,Edit,x+3 yp-3 w50 vMapAddresHEX gMapChangeAddres +hwndhMapAddresHEX -Multi,1
  Gui,7:add,text,xm y+10 w120,Номер регистра: ;
  Gui,7:Add,Edit,x+3 yp-3 w50 vMapRegNum gMapChangeRegNum +hwndhMapRegNum -Multi,0
  Gui,7:add,text,x+5 yp+3,HEX:
  Gui,7:Add,Edit,x+3 yp-3 w50 vMapRegNumHEX gMapChangeRegNum +hwndhMapRegNumHEX -Multi,0
  Gui,7:add,text,xm y+10 w120,Тип данных: ;
  Gui,7:Add,DropDownList,x+3 yp-3 w195 Choose1 vMapDataType +hwndhMapDataType,INT|WORD|DINT|DWORD|REAL
  Gui,7:add,text,xm y+10 w120,Тип регистра: ;
  Gui,7:Add,DropDownList,x+3 yp-3 w195 AltSubmit Choose1 vMapRegType +hwndhMapRegType,% RTUFunc3 "|" RTUFunc4
  Gui,7:add,text,xm y+10 w120,Коментарий: ;
  Gui,7:Add,Edit,xm y+3 w315 h70 vMapComment +hwndhMapComment Limit120 -WantReturn
  Gui,7:add,Button,xm+145 y+10 w80 gMapAddReg default +hwndhMapAddReg,Добавить ;
  Gui,7:add,Button,x+10 yp w80 gMapCancelReg,Отмена ;
  return

8GuiCreate:
  Gui,8:-sysmenu +owner6
  Gui,8:+hwndhEditMapWin ; -DPIScale ; +alwaysontop
  ;Gui,8:Color,dfdfdf
  Gui,8:Margin,10,10
  Gui,8:font,s8
  Gui,8:add,text,xm ym w100,Текущее значение: ;
  Gui,8:Add,Edit,x+3 yp-3 w220 +ReadOnly vMapValueNow +hwndhMapValueNow -Multi
  Gui,8:add,text,xm y+10 w100,Новое значение: ;
  Gui,8:Add,Edit,x+3 yp-3 w220 vMapValueNew +hwndhMapValueNew -Multi
  Gui,8:add,Button,xm+145 y+10 w80 gMapEditValue default +hwndhMapEditValue,Применить ;
  Gui,8:add,Button,x+10 yp w80 gMapCancelValue,Закрыть ;
  return

OpenFile:
  if Analyzer
    gosub,En_Analyzer
;  Thread,NoTimers
  FileSelectFile,OpenFileName,3,% OldOpenFileName,Открыть сохраненные данные буфера приема,COM terminal buffer data (*.ctbd)
;  Thread,NoTimers,false
  if errorlevel
  {
    tooltip,Отменено
    settimer,RemoveTooltip,-2000
    return
  }
  OldOpenFileName:=OpenFileName
  OpenFileObj:=FileOpen(OpenFileName,"r","UTF-8-RAW")
  if !IsObject(OpenFileObj)
  {
    MsgBox,% "Не возможно открыть файл " OpenFileName "."
    return
  }
  gosub,DiskonnectCOM
  FileLoading:=1
  tooltip,Загрузка файла...`nДвойной клик чтобы отменить.
  sleep,200
  SaveBufer:={}
  AnBufer:={}
  varsetcapacity(tmpsavedata,4)
  OpenFileObj.RawRead(tmpsavedata,4)
  LoadSize:=numget(tmpsavedata,0,"Uint")
  TotalLoadSize:=4
  loop,% LoadSize
  {
    if StopAnalyze
    {
      StopAnalyze:=0
      tooltip
      break
    }
    if (TotalLoadSize>=OpenFileObj.Length)
      break
    GuiControl,,Progress,% a_index/LoadSize*100
    LoadBlock:=a_index
    SaveBufer[LoadBlock]:={}
    OpenFileObj.RawRead(tmpsavedata,2)
    LoadSizeB:=numget(tmpsavedata,0,"UShort")
    TotalLoadSize+=2
    ReadPack++
    loop,% LoadSizeB
    {
      OpenFileObj.RawRead(tmpsavedata,1)
      SaveBufer[LoadBlock,a_index]:=numget(tmpsavedata,0,"UChar")
      TotalLoadSize+=1
    }
  }
  GuiControl,,Progress,0
  OpenFileObj.Close()
  FileLoading:=0
  LoadFile:=1
  gosub,UnUpdate
  LoadFile:=0
  FileOpened:=1
  GuiControl,,_text_,% "`n`tФайл открыт"
  return

SaveFile:
  if !OldSaveFileName
    OldSaveFileName:=COMPort " " a_DD "." a_MM "." a_YYYY " " a_hour "." a_min
  Thread,NoTimers
  FileSelectFile,SaveFileName,S16,% OldSaveFileName,Сохранить данные буфера приема,COM terminal buffer data (*.ctbd)
  Thread,NoTimers,false
  if errorlevel
  {
    tooltip,Отменено
    settimer,RemoveTooltip,-2000
    return
  }
  if (!instr(SaveFileName,".ctbd") and FileExist(SaveFileName ".ctbd"))
  {
    msgBox,36,Сохранить данные буфера приема,Файл %SaveFileName%.ctbd существует.`nЗаменить?
    IfMsgBox No
      return
  }
  FileSaving:=1
  tooltip,Сохранение файла...`nДвойной клик чтобы остановить.
  sleep,200
  SaveFileName:=instr(SaveFileName,".ctbd")?SaveFileName:SaveFileName . ".ctbd"
  OldSaveFileName:=SaveFileName
  SaveFileObj:=FileOpen(SaveFileName,"w","UTF-8-RAW")
  if !IsObject(SaveFileObj)
  {
    MsgBox,% "Не возможно создать файл " SaveFileName "."
    tooltip
    return
  }
  varsetcapacity(tmpsavedata,4)
  SaveSize:=SaveBufer.maxindex()
  numput(SaveSize,tmpsavedata,0,"Uint")
  SaveFileObj.RawWrite(tmpsavedata,4)
  loop,% SaveSize
  {
    if StopAnalyze
    {
      StopAnalyze:=0
      tooltip
      break
    }
    GuiControl,,Progress,% a_index/SaveSize*100
    SaveSizeB:=SaveBufer[a_index].maxindex()
    SaveBlock:=a_index
    numput(SaveSizeB,tmpsavedata,0,"UShort")
    SaveFileObj.RawWrite(tmpsavedata,2)
    loop,% SaveSizeB
    {
      numput(SaveBufer[SaveBlock,a_index],tmpsavedata,0,"UChar")
      SaveFileObj.RawWrite(tmpsavedata,1)
    }
  }
  GuiControl,,Progress,0
  SaveFileObj.Close()
  FileSaving:=0
  tooltip
  Msgbox,Сохранено.
  return

GuiAbout:
  gui,5:destroy
  Gui,5:+owner1 -MinimizeBox -MaximizeBox
  Gui,5:font,s8,Tahoma
  gui,5:margin,70,10
  gui,5:add,text,xm+25,Free software.
  gui,5:add,text,xp-10 y+15,COM Port terminal
  gui,5:add,text,xp+13 y+5,Версия: %Version%
  gui,5:add,text,xp-13 y+15,Создатель: Alectric.
  gui,5:add,Link,xp+13 y+15,<a href="http://forum.script-coding.com/viewtopic.php?id=15215">Серый форум</a>
  gui,5:add,Link,xp-20 y+5,<a href="http://forum.script-coding.com/viewtopic.php?id=15216">Тема для обсуждения</a>
  gui,5:show,,О программе
  return

CheckMainExist:
  process,exist,% 2ndThread
  if !errorlevel
    exitapp
  return

ClearQueue:
  ReadPos:=NumGet(Data,_DataPos,"Int")
  ReadPack:=NumGet(Data,_PackagesRecieved,"Int")
  if (ReadPos<0)
    ReadPos:=0
  if (ReadPack<0)
    ReadPack:=0
  return

showtext:
  if !TurnBack
  {
    SendMessage, 0xB0, &E_Text_start, &E_Text_end, , ahk_id %E_Text%  ; EM_GETSEL
    SendMessage, 0x0E, , , , ahk_id %E_Text%    ;  WM_GETTEXTLENGTH
    textlength:=Errorlevel
    if !(NumGet(E_Text_start)=textlength)
      GuiControl, -Redraw, %E_Text%
    SendMessage, 0x00B1, textlength, textlength, , ahk_id %E_Text%    ;  EM_SETSEL
  }
  SendMessage, 0x00C2 , TRUE, &InText,, ahk_id %E_Text%  ;	EM_REPLACESEL
  if !TurnBack
  {
    if !(NumGet(E_Text_start)=textlength)
      SendMessage, 0x0B1, NumGet(E_Text_start), NumGet(E_Text_end), , ahk_id %E_Text%  ; EM_SETSEL
    GuiControl, +Redraw, %E_Text%
  }
  return

DiskonnectCOM:
  GuiControl,,_text_,% COMPort "`n`t`tОтключен"
  NumPut(1,Data,_Diskonnect,"UChar")
  return

ParseBuffer:
  if LoadFile
  {
    Received_Addres:=Bufer[1]
    Received_Command:=Bufer[2]
    Received_Byte:=Bufer[3]
    ; определить корректность контрольной суммы
    CheckSummIsCorrect:=0
    if (AddCS>=4 and RS232_Bytes_Received>2)
    {
      tmp1:=(Bufer[RS232_Bytes_Received]<<8)|Bufer[RS232_Bytes_Received-1]
      tmp2:=CRC16(Bufer,RS232_Bytes_Received-2)
      CheckSummIsCorrect:=(tmp1==tmp2)
    }
    else if (AddCS=2 or (AddCS=3 and RS232_Bytes_Received>2))
    {
      tmp0:=CRC16(Bufer,RS232_Bytes_Received-AddCSLenght,(ChoiceCSType=1),Polynom,InitVal,Refin,RefOut,XorOut,RefPoly)
      if AddCS=2
        tmp1:=Bufer[RS232_Bytes_Received]
      else if AddCS=3
        tmp1:=(Bufer[RS232_Bytes_Received]<<8)|Bufer[RS232_Bytes_Received-1]
      tmp2:=(AddCS=3) ? tmp0 : tmp0&0xff
      CheckSummIsCorrect:=(tmp1==tmp2)
    }
    ; определить тип пакета Modbus
    Received_Message_Type:=""
    if (AddCS>=4)
    {
      if  CheckSummIsCorrect
      {
        if (Received_Command>0 and Received_Command<5)
        {
          tmp1:=Bufer[3]
          if (RS232_Bytes_Received=tmp1+5)
            Received_Message_Type:="Ответ" (tmp1=3?" возможно Запрос":"")
          else if (RS232_Bytes_Received=8)
            Received_Message_Type:="Запрос"
        }
        else if (Received_Command>4 and Received_Command<7)
          Received_Message_Type:="Запрос/Ответ"
        else if (Received_Command=0x0f or Received_Command=0x10)
        {
          tmp1:=Bufer[7]
          if (RS232_Bytes_Received=tmp1+9)
            Received_Message_Type:="Запрос"
          else
            Received_Message_Type:="Ответ"
        }
      }
    }
    anindex:=RS232_Bytes_Received "|" Received_Addres "|" Received_Command "|" (Received_Message_Type?0:Received_Byte) "|" Received_Message_Type "|" CheckSummIsCorrect
    if !isobject(AnBufer[anindex])
      AnBufer[anindex]:={}
    AnBufer[anindex].Push(Bufer.Clone())
  }
  ; отфильтровать пакеты
  Filtered:=0
  if (!Analyzer)
  if (Filter_Addres_EN or Filter_Command_EN or Filter_Byte_EN or Filter_Message_EN>1 or Filter_CRC_EN)
  {
    if (Filter_Addres_EN and Received_Addres!=Filter_Addres)
      Filtered:=1
    if (Filter_Command_EN and Received_Command!=Filter_Command)
      Filtered:=1
    if (Filter_Byte_EN and Received_Byte!=Filter_Byte)
      Filtered:=1
    if (Filter_Message_EN=2 and Received_Message_Type="Запрос")
      Filtered:=1
    if (Filter_Message_EN=3 and Received_Message_Type="Ответ" or Received_Message_Type="Ответ возможно Запрос")
      Filtered:=1
    if (AddCS>1 and Filter_CRC_EN and !CheckSummIsCorrect)
      Filtered:=1
  }
  if (!Filtered or Analyzer)
  {
    if (!Analyzer and !TurnBack)
      GuiControl,-Redraw,% E_LV
    Add_rn:=0
    regcount:=0

    ; разобраться с принятыми данными
    loop,% RS232_Bytes_Received
    {
      rows++
      LV_Icon:=ICON_EMPTY
      LV_Hash:=""
      LV_DEC:=Bufer[a_index]
      LV_NumByte:=a_index-1
      LV_HEX:=format("0x{:02x}",LV_DEC)
      LV_Sym:=Chr(LV_HEX)
      LV_Bin:=DECtoBIN(LV_HEX)
      loop,% 8-strlen(LV_Bin)
        LV_Bin:="0" . LV_Bin
      LV_7Seg:=Arr7SEGtoASCII[(LV_DEC&~(1<<7))] . (LV_DEC&(1<<7)) ? "." : ""
      LV_WORD:=LV_INT:=LV_DWORD:=LV_DINT:=LV_Float:=LV_Double:=""
      if (MapRun and a_index=4)
      {
        Gui,6:default
        LV_GetText(tmp4,MapNum,4)
        Gui,1:default
      }
      if (RS232_Bytes_Received-a_index+2>2 and RS232_Bytes_Received>1)
      {
        varsetcapacity(LV_INT_tmp,2)
        numput(Bufer[a_index],LV_INT_tmp,1,"uchar")
        numput(Bufer[a_index+1],LV_INT_tmp,0,"uchar")
        LV_WORD:=numget(LV_INT_tmp,0,"ushort")
        LV_INT:=numget(LV_INT_tmp,0,"short")
        if (tmp4="INT")
        {
          tmp5:=LV_INT
          tmp6:=DATAtoHEX(&LV_INT_tmp,0,2,1)
          tmp7:=DATAtoBIN(&LV_INT_tmp,0,2,1)
        }
        else if (tmp4="WORD")
        {
          tmp5:=LV_WORD
          tmp6:=DATAtoHEX(&LV_INT_tmp,0,2,1)
          tmp7:=DATAtoBIN(&LV_INT_tmp,0,2,1)
        }
        AddAdd(LV_INT)
      }
      if (RS232_Bytes_Received-a_index+2>4 and RS232_Bytes_Received>3)
      {
        varsetcapacity(LV_DINT_tmp,4)
        numput(Bufer[a_index],LV_DINT_tmp,3,"uchar")
        numput(Bufer[a_index+1],LV_DINT_tmp,2,"uchar")
        numput(Bufer[a_index+2],LV_DINT_tmp,1,"uchar")
        numput(Bufer[a_index+3],LV_DINT_tmp,0,"uchar")
        LV_DWORD:=numget(LV_DINT_tmp,0,"uint")
        LV_DINT:=numget(LV_DINT_tmp,0,"int")
        LV_Float:=numget(LV_DINT_tmp,0,"float")
        if (tmp4="DINT")
        {
          tmp5:=LV_DINT
          tmp6:=DATAtoHEX(&LV_DINT_tmp,0,4,1)
          tmp7:=DATAtoBIN(&LV_DINT_tmp,0,4,1)
        }
        else if (tmp4="DWORD")
        {
          tmp5:=LV_DWORD
          tmp6:=DATAtoHEX(&LV_DINT_tmp,0,4,1)
          tmp7:=DATAtoBIN(&LV_DINT_tmp,0,4,1)
        }
        else if (tmp4="REAL")
        {
          tmp5:=numget(LV_DINT_tmp,0,"float")
          tmp6:=DATAtoHEX(&LV_DINT_tmp,0,4,1)
          tmp7:=DATAtoBIN(&LV_DINT_tmp,0,4,1)
        }
        AddAdd(LV_DINT)
        AddAdd(LV_Float)
      }
      if (RS232_Bytes_Received-a_index+2>8 and RS232_Bytes_Received>7)
      {
        varsetcapacity(LV_Double_tmp,8)
        numput(Bufer[a_index],LV_Double_tmp,7,"uchar")
        numput(Bufer[a_index+1],LV_Double_tmp,6,"uchar")
        numput(Bufer[a_index+2],LV_Double_tmp,5,"uchar")
        numput(Bufer[a_index+3],LV_Double_tmp,4,"uchar")
        numput(Bufer[a_index+4],LV_Double_tmp,3,"uchar")
        numput(Bufer[a_index+5],LV_Double_tmp,2,"uchar")
        numput(Bufer[a_index+6],LV_Double_tmp,1,"uchar")
        numput(Bufer[a_index+7],LV_Double_tmp,0,"uchar")
        LV_Double:=numget(LV_Double_tmp,0,"Double")
        AddAdd(LV_Double)
      }
      if (MapRun and MapNum and a_index=4)
      {
        Gui,6:default
        LV_GetText(tmp1,MapNum,1)
        LV_GetText(tmp2,MapNum,2)
        LV_GetText(tmp3,MapNum,3)
        LV_GetText(tmp4,MapNum,4)
        LV_GetText(tmp8,MapNum,8)
        LV_GetText(tmp9,MapNum,9)
        if (Bufer[2]&0x80)
        {
          tmp5:=""
          tmp6:=""
          tmpt:=Bufer[3]
          tmp7:=RTUErr%tmpt%
        }
        if (CheckSummIsCorrect)
        {
          tmpMap[MapNum]:=[tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8,tmp9]
          LV_Modify(MapNum,,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8="/"?"\":"/",tmp9)
          if (MapChangeVtmp and MapNum=MapEditNum)
          {
            if (MapChangeVtmp=1)
              GuiControl,,% hMapValueNow,% tmp5
            else if (MapChangeVtmp=2)
              GuiControl,,% hMapValueNow,% tmp6
            else if (MapChangeVtmp=3)
              GuiControl,,% hMapValueNow,% tmp7
          }
        }
        else
          LV_Modify(MapNum,,tmp1,tmp2,tmp3,tmp4,tmpMap[MapNum,5],tmpMap[MapNum,6],tmpMap[MapNum,7],"",tmp9)
        LV_Modify(MapNum,"Focus")
        MapNext:=1
        Gui,1:default
      }
      if EN_Kurvendrucker
      {
        Monitorintext.=LV_Sym
        DetectAutoEndtext.=LV_Sym
        MonLen:=strlen(DetectAutoEndtext)-100
        if (strlen(DetectAutoEndtext)>100)
          StringTrimLeft,DetectAutoEndtext,DetectAutoEndtext,% MonLen
;tooltip,% DetectAutoEndtext
      }
      if (a_index=1)
      {
        LV_Icon:=ICON_START
        if !Received_Message_Type
          LV_Hash:="Start " Packages_Recieved
        else
          LV_Hash:=Received_Message_Type " " Packages_Recieved
      }


      if (AddCS>=4)
      {
  ; работаем с Modbus
  ;      if CheckSummIsCorrect
        {
          LV_NumByte:=""
          if (Received_Message_Type="Ответ" or Received_Message_Type="Ответ возможно Запрос") ; ---------------------------------------------------------
          {
            if (Received_Command>0 and Received_Command<5)
            {
              if (a_index=3)
              {
                if !RTUErrorWasOcured
                  LV_NumByte:="Кол-во байт далее"
                else
                  LV_NumByte:=RTUErr%LV_DEC%
                LV_Icon:=ICON_INFO
              }
              if (a_index>3 and a_index<RS232_Bytes_Received-1)
              {
                if ((Received_Command>2 and Received_Command<5) and !(a_index&1))
                {
                  LV_NumByte:="+" regcount " (+0x" format("{:x}",regcount) ")"
                  regcount++
                  LV_Icon:=ICON_REGISTER
                  LV_Hash:="Значения регистров по смещению"
                }
                if (Received_Command>0 and Received_Command<3)
                {
                  LV_NumByte:=(regcount+1)*8 "-" regcount*8
                  regcount++
                  LV_Icon:=ICON_REGISTER
                  LV_Hash:="Значения битов по смещению"
                }
              }

            }
            else if (Received_Command=0xf or Received_Command=0x10)
            {
              if (a_index=3)
              {
                LV_NumByte:="Стартовый регистр"
                LV_Icon:=ICON_INFO
                LV_tmp:=LV_WORD
              }
              if (a_index=4)
                LV_NumByte:=LV_tmp " (" format("0x{:x}",LV_tmp) ")"
              if (a_index=5)
              {
                LV_NumByte:="Кол-во запрашиваемых регистров"
                LV_Icon:=ICON_INFO
                LV_tmp:=LV_WORD
              }
              if (a_index=6)
                LV_NumByte:=LV_tmp " (" format("0x{:x}",LV_tmp) ")"
            }
          }
          else if (Received_Message_Type="Запрос") ; ---------------------------------------------------------
          {
            if (Received_Command>0 and Received_Command<5)
            {
              if (a_index=3)
              {
                LV_NumByte:="Стартовый регистр"
                LV_Icon:=ICON_INFO
                LV_tmp:=LV_WORD
              }
              if (a_index=4)
                LV_NumByte:=LV_tmp " (" format("0x{:x}",LV_tmp) ")"
              if (a_index=5)
              {
                if (Received_Command<3)
                  LV_NumByte:="Кол-во запрашиваемых бит"
                if  (Received_Command>2)
                  LV_NumByte:="Кол-во запрашиваемых регистров"
                LV_Icon:=ICON_INFO
                LV_tmp:=LV_WORD
              }
              if (a_index=6)
                LV_NumByte:=LV_tmp " (" format("0x{:x}",LV_tmp) ")"
            }
            else if (Received_Command=0xf or Received_Command=0x10)
            {
              if (a_index=3)
              {
                LV_NumByte:="Стартовый регистр"
                LV_Icon:=ICON_INFO
                LV_tmp:=LV_WORD
              }
              if (a_index=4)
                LV_NumByte:=LV_tmp " (" format("0x{:x}",LV_tmp) ")"
              if (a_index=5)
              {
                LV_NumByte:="Кол-во записываемых регистров"
                LV_Icon:=ICON_INFO
                LV_tmp:=LV_WORD
              }
              if (a_index=6)
                LV_NumByte:=LV_tmp " (" format("0x{:x}",LV_tmp) ")"
              if (a_index=7)
              {
                LV_NumByte:="Кол-во байт далее = " LV_DEC
                LV_Icon:=ICON_INFO
                LV_tmp:=LV_WORD
              }
              if (a_index>7 and a_index<RS232_Bytes_Received-1)
              {
                if (Received_Command=0x10 and !(a_index&1))
                {
                  LV_NumByte:="+" regcount " (+0x" format("{:x}",regcount) ")"
                  regcount++
                  LV_Icon:=ICON_REGISTER
                  LV_Hash:="Значения регистров по смещению"
                }
                if (Received_Command=0xf)
                {
                  LV_NumByte:=(regcount+1)*8-1 "-" regcount*8
                  regcount++
                  LV_Icon:=ICON_REGISTER
                  LV_Hash:="Значения битов по смещению"
                }
              }
            }
          }
          else if (Received_Message_Type="Запрос/Ответ") ; ---------------------------------------------------------
          {
            if (a_index=3)
            {
              LV_NumByte:="Регистр"
              LV_Icon:=ICON_INFO
              LV_tmp:=LV_WORD
            }
            if (a_index=4)
              LV_NumByte:=LV_tmp " (" format("0x{:x}",LV_tmp) ")"
            if (a_index=5)
            {
              LV_NumByte:="Значение"
              LV_Icon:=ICON_INFO
              if (LV_WORD=0xff00)
                LV_tmp:="ON (" format("0x{:x}",LV_WORD) ")"
              else if (LV_WORD=0x0000)
                LV_tmp:="OFF (" format("0x{:x}",LV_WORD) ")"
              else
                LV_tmp:=LV_WORD " (" format("0x{:x}",LV_WORD) ")"
            }
            if (a_index=6)
              LV_NumByte:=LV_tmp
          }
          if (a_index=1)
            LV_NumByte:="Адрес"
          if (a_index=2)
          {
            RTUErrorWasOcured:=0
            if (LV_DEC&0x80)
            {
              RTUErrorWasOcured:=1
              LV_NumByte:="Ошибка"
              LV_Icon:=ICON_ERROR
            }
            else
            {
              LV_NumByte:=RTUFunc%LV_DEC%
              LV_Hash:=RTUF%LV_DEC%
            }
          }
          if (a_index=3 and RTUErrorWasOcured)
          {
            LV_NumByte:=RTUErr%LV_DEC%
            LV_Icon:=ICON_INFO
          }
        }
  ;      else
  ;        LV_NumByte:=""
        if (a_index>RS232_Bytes_Received-2)
            LV_NumByte:=""
      }
      if (a_index>1 and AddCS>1 and !CheckSummIsCorrect)
        LV_Icon:=ICON_X
      if (AddCS>1)
      {
        if ((AddCS>2 and a_index=(RS232_Bytes_Received-1)) or (a_index=RS232_Bytes_Received))
        {
          if CheckSummIsCorrect
          {
            LV_Icon:=ICON_V
            LV_Hash:="CRC" ;"V"
            LV_NumByte:="Контрольная сумма верна"
          }
          else
          {
            LV_Icon:=ICON_CRC_ERROR
            LV_Hash:="CRC" ;"X"
            LV_NumByte:="Ошибка контрольной суммы"
          }
        }
      }
  ; -------------------------------------------------------------------------------------
      if !EN_Kurvendrucker
        LV_Add("Icon" LV_Icon,LV_Hash,a_index-1,LV_NumByte,LV_Bin,LV_Sym,LV_HEX,LV_DEC,LV_WORD,LV_INT,LV_DWORD,LV_DINT,LV_Float,LV_Double)
  ;  #|№ байта|№ байта или описание|BIN|SYM|HEX|DEC|INT|WORD|DINT|DWORD|Float|Double
  ; -------------------------------------------------------------------------------------
      if !(LV_HEX=0x0a or LV_HEX=0x0d)
        tmp_intext.=(LV_HEX ? LV_Sym : " ")
      if (LV_HEX=0x0a) ; and LV_HEX_old=0x0d)
      {
        InText:=tmp_intext . (instr(tmp_intext,"`r`n") ? "" : "`r`n")
        tmp_intext=
        Add_rn:=1
        if (!Analyzer)
          gosub,showtext
      }
      LV_HEX_old:=LV_HEX
    }
    if !EN_Kurvendrucker
      LV_Add("Icon" 0,," ")
    if (!Analyzer and !TurnBack)
      GuiControl,+Redraw,% E_LV
    if (!Analyzer and !TurnBack)
      GuiControl,,Progress,% rows/Rownums*100
    if (rows>Rownums and !Analyzer and !TurnBack)
      gosub,Update
    if (tmp_intext)
    {
      InText:=tmp_intext . (AddCRLF ? "`r`n" : "")
      Add_rn:=1
      tmp_intext=
      if (!Analyzer)
        gosub,showtext
    }
    if (!FollowSelect and !LV_GetCount("S") and !Analyzer and !TurnBack)
    {
      LV_Modify(LV_GetCount(),"Vis")
      sleep,10
    }
    else if (FollowSelect and CountSelected:=LV_GetCount("S") and !Analyzer and !TurnBack)
    {
      startpos:=LV_GetCount()-OldGetCount+LV_GetNext()
      LV_Modify(0,"-Select")
      loop,% CountSelected
        LV_Modify(startpos+a_index-1,"Select")
      LV_Modify(startpos+CountSelected-1,"Vis Focus")
      sleep,10
      gosub,ListGetSelected
    }
    else if (FollowSelect and !LV_GetCount("S") and !Analyzer and !TurnBack)
    {
      LV_Modify(LV_GetCount(),"Vis")
      sleep,10
    }
    if EN_MonitorVar
    {
      varsetcapacity(Monitorvalue_tmp,Slave_Len)
      loop,% Slave_Len
        numput(Bufer[Slave_Start_Byte+a_index],Monitorvalue_tmp,Slave_Len-a_index,"uchar")
;tooltip,% Slave_Data_Type "`n=" Bufer[Slave_Start_Byte+1] "=`nstb=" Slave_Start_Byte "`nsl=" Slave_Len,0,,7
      Monitorvalue:=numget(Monitorvalue_tmp,0,Slave_Data_Type)

      if GrafPause
        Gdip_X_Offset+=1
      else
        Gdip_Marker_mx-=Gdip_ZoomX
      Monitor_value.InsertAt(1,Monitorvalue)
      Monitor_value_T.InsertAt(1,a_hour ":" a_min "." a_sec "." a_MSec)
      gosub,GrafDraw
    }
    OldGetCount:=LV_GetCount()

    if EN_Kurvendrucker
    {
      if AutoEnding
      {
        TryFind++
        if (TryFind>10)
        {
          findMetaSbl(DetectAutoEndtext,SplSymbol,EndSymbol)
          TryFind:=0
        }
      }
;tooltip,% TryFind "`n""" SplSymbol """`n""" EndSymbol """"
      if (EndSymbol!="")
      while(EndPos:=instr(Monitorintext,EndSymbol))
      {
;        RS232_Bytes_Reqest:=EndPos*2
;        GuiControl,,RS232_Bytes_Reqest,% RS232_Bytes_Reqest
        Monitoringtext:=substr(Monitorintext,1,EndPos-1)
        StringTrimLeft,Monitorintext,Monitorintext,% EndPos
        if (SplSymbol==EndSymbol)
        {
          loop,parse,Monitoringtext,% EndSymbol
          {
            if (a_loopfield!="")
            {
              tmp1:=a_loopfield
              if instr(tmp1,"`r")
                StringTrimRight,tmp1,tmp1,1
              Monitor_value1.InsertAt(1,tmp1)
              Monitor_value_T.InsertAt(1,a_hour ":" a_min "." a_sec "." a_MSec)
              if GrafPause
                Gdip_X_Offset+=1
              else
                Gdip_Marker_mx-=Gdip_ZoomX
            }
          }
          NumOfGraf:=1
        }
        else
        {
          if (Monitoringtext!="")
          {
            NumOfGraf:=0
            loop,parse,Monitoringtext,% SplSymbol
            {
              if (a_loopfield!="")
              {
                tmp1:=a_loopfield
                if instr(tmp1,"`r")
                  StringTrimRight,tmp1,tmp1,1
                Monitor_value%a_index%.InsertAt(1,tmp1)
                NumOfGraf++
              }
            }
            Monitor_value_T.InsertAt(1,a_hour ":" a_min "." a_sec "." a_MSec)
            if GrafPause
              Gdip_X_Offset+=1
            else
              Gdip_Marker_mx-=Gdip_ZoomX
          }
        }
      }
      gosub,GrafDraw
    }
  }
  return

ClUpdate:
  Thread,NoTimers
  msgBox,36,Удалить все данные,Вы действительно хотите удалить все данные?
  Thread,NoTimers,false
  IfMsgBox No
    return
  ObjClear(Monitor_value)
  ObjClear(Monitor_value_T)
  loop,% MaxNumOfGraf
    ObjClear(Monitor_value%a_index%)
  ObjClear(SaveBufer)
  ObjClear(AnBufer)
  ObjClear(AnCufer)
  settimer,Update,-500
  return

Update:
  Update:=1
  If !analyzer
  {
    Packages_Recieved:=0
    rows=
    GuiControl,,_InText_
    GuiControl,,Progress,0
    LV_Delete()
    tmp_intext=
  }
  else
    gosub,Analyze
  return

UnUpdate:
  gosub,Update
  TurnBack:=1
  tooltip,Загрузка таблицы...`nДвойной клик чтобы отменить.
  GuiControl,-Redraw,% E_LV
  GuiControl,-Redraw,% E_Text
  loop,% SaveBufer.maxindex()
  {
    if StopAnalyze
    {
      StopAnalyze:=0
      tooltip
      break
    }
    Packages_Recieved++
    GuiControl,,Progress,% a_index/SaveBufer.maxindex()*100
    Gui,1:ListView,% E_LV
    Bufer:=SaveBufer[a_index]
    RS232_Bytes_Received:=Bufer.maxindex()
    gosub,ParseBuffer
  }
  LV_Modify(LV_GetCount(),"Vis")
  GuiControl,+Redraw,% E_LV
  GuiControl,+Redraw,% E_Text
  tooltip
  GuiControl,,Progress,0
  TurnBack:=0
  return

SkeepPack:
  SkeepPack:=!SkeepPack
  if SkeepPack
  {
    menu,ListRightClick2,Check,Пропуск пакетов ;
    menu,BufferMenu,Check,Пропуск пакетов ;
    gosub,ClearQueue
  }
  else
  {
    menu,ListRightClick2,UnCheck,Пропуск пакетов ;
    menu,BufferMenu,UnCheck,Пропуск пакетов ;
  }
  return

ToTheEnd:
  SendMessage, 0x0E, , , , ahk_id %E_Text%    ;  WM_GETTEXTLENGTH
  textlength:=Errorlevel
  SendMessage, 0x00B1, textlength, textlength, , ahk_id %E_Text%    ;  EM_SETSEL
  GuiControl, +Redraw, %E_Text%
  LV_Modify(0,"-Select")
  return

ResetUnCOMPort:
  UnCOMPort:=""
  return

ComList:
  ComPortList:=SearchCOM("",1)
  if !FileOpened
    GuiControl,,% E_Text,% "`n`nНайденные в системе порты:`n" (ComPortList ? ComPortList : "Портов нет.")
  return

AceptSettings:
  gosub,submiting
  FileOpened:=0
  settimer,ComList,1000
  COMPort:=""
  while(!COMPort)
  {
    sleep,1000
    if FileOpened
      return
    gosub,submiting
    dots.=">"
    if (strlen(dots)>20)
      dots:=""
    if (ChoiceCOM=1)
      COMPort:=SearchCOM("USB")
    else if (ChoiceCOM=2)
      COMPort:="COM" ManualCOM
    else if (ChoiceCOM=3)
      COMPort:=SearchCOM(ManualCOMByName)
    if (!COMPort and ChoiceCOM and !FileOpened)
      GuiControl,,_text_,% COMPort "`nCOM-порт не обнаружен`nПоиск " dots
    if (COMPort==UnCOMPort)
      COMPort:=""
  }
  settimer,ComList,off
  settimer,ResetUnCOMPort,off
  GuiControl,,_text_,% COMPort "`n`tподключение"
  GuiControl,,% E_Text,% COMPort " подключение"
  doonce:=0
  StrPut(COMPort,&Data+_COMPort,12)
  NumPut(RS232_Baud,Data,_RS232_Baud,"Int")
  NumPut(asc(RS232_Parity),Data,_RS232_Parity,"Int")
  NumPut(RS232_Data,Data,_RS232_Data,"Int")
  NumPut(RS232_Stop,Data,_RS232_Stop,"Int")
  NumPut(RTimeout,Data,_RTimeout,"Int")
  NumPut(EmptyPacket,Data,_EmptyPacket,"Int")
  NumPut(ReadIntervalTimeout,Data,_ReadIntervalTimeout,"Int")
  NumPut(0,Data,_Diskonnect,"UChar")
  NumPut(1,Data,_ApplyCOMSettings,"UChar")
  sleep,300
;  gosub,ClearQueue
  Gui,6:default
  SB_SetText(COMPort)
  Gui,1:default
  return

ChoiceCSType:
  gosub,Submiting
  if (ChoiceCSType=1)
  {
    loop,8
    {
      a:=a_index+2
      GuiControl,hide,% hECSType%a%
    }
  }
  else
  {
    loop,8
    {
      a:=a_index+2
      GuiControl,show,% hECSType%a%
    }
  }
  return

Submiting:
  Gui,1:submit,nohide
  AddCSLenght:=(AddCS>2 ? 2:AddCS-1)
  Polynom:=instr(Polynom,"0x")?Polynom+0:"0x" Polynom
  InitVal:=instr(InitVal,"0x")?InitVal:"0x" InitVal
  XorOut:=instr(XorOut,"0x")?XorOut:"0x" XorOut
  return

SubmitExt:
  gosub,submiting
  if (a_GuiControl="AddCS")
  {
    if (AddCS=5)
    {
      MoveControl(E_1,,E_1_ShiftSize,,-E_1_ShiftSize)
      loop,2
        GuiControl,Show,% hE_RTU_Command_%a_index%
      loop,5
        GuiControl,Show,% hE_RTU_StartReg_%a_index%
      loop,6
        GuiControl,Show,% hE_RTU_Addres_%a_index%
      loop,5
        GuiControl,Show,% hE_RTU_RegNum_%a_index%
      loop,2
        GuiControl,Show,% hE_RTU_Val_%a_index%
    }
    else
    {
      GuiControl,enable,% E_1
      if (PrevAddCS=5)
        MoveControl(E_1,,-E_1_ShiftSize,,E_1_ShiftSize)
      loop,2
        GuiControl,hide,% hE_RTU_Command_%a_index%
      loop,5
        GuiControl,hide,% hE_RTU_StartReg_%a_index%
      loop,6
        GuiControl,hide,% hE_RTU_Addres_%a_index%
      loop,5
        GuiControl,hide,% hE_RTU_RegNum_%a_index%
      loop,2
        GuiControl,hide,% hE_RTU_Val_%a_index%
    }
    if (AddCS>=4)
    {
      GuiControl,Show,% hFiltE4
      GuiControl,Show,% hFiltE5
      GuiControl,Show,% hFiltE6
      GuiControl,Show,% hFiltE7
;      GuiControl,Show,% hAutoByRe
      loop,3
        GuiControl,disable,% hFiltE%a_index%
      Gui,4:submit,nohide
      Filter_CommandFlag:=1
      if (Filter_CommandRTU>0 and Filter_CommandRTU<7)
        Filter_Command:=Filter_CommandRTU
      else if (Filter_CommandRTU>6)
        Filter_Command:=Filter_CommandRTU+8
      GuiControl,,% hFiltE1,% Filter_Command
      GuiControl,show,% hModbusMapOpen
    }
    else
    {
      GuiControl,hide,% hFiltE4
      GuiControl,hide,% hFiltE5
      GuiControl,hide,% hFiltE6
      GuiControl,hide,% hFiltE7
;      GuiControl,hide,% hAutoByRe
      loop,3
        GuiControl,enable,% hFiltE%a_index%
      GuiControl,hide,% hModbusMapOpen
    }
    if (AddCS>1)
      GuiControl,show,% hFiltE8
    else
      GuiControl,hide,% hFiltE8
    if (AddCS=3)
    {
      loop,10
        GuiControl,show,% hECSType%a_index%
      gosub,ChoiceCSType
    }
    else
    {
      loop,10
        GuiControl,hide,% hECSType%a_index%
    }
    PrevAddCS:=AddCS
  }
  if (AddCS=5)
  {
    if (RTU_Command>0 and RTU_Command<5)
    {
      loop,5
        guicontrol,enable,% hE_RTU_RegNum_%a_index%
      loop,2
        GuiControl,disable,% hE_RTU_Val_%a_index%
      GuiControl,disable,% E_1
    }
    if (RTU_Command=5 or RTU_Command=6)
    {
      if SendDataRepitedly
        gosub,SendDataRepitedly
      loop,5
        guicontrol,disable,% hE_RTU_RegNum_%a_index%
      loop,2
        GuiControl,enable,% hE_RTU_Val_%a_index%
      GuiControl,enable,% E_1
    }
    if (RTU_Command=7 or RTU_Command=8)
    {
      if SendDataRepitedly
        gosub,SendDataRepitedly
      loop,5
        guicontrol,enable,% hE_RTU_RegNum_%a_index%
      loop,2
        GuiControl,enable,% hE_RTU_Val_%a_index%
      GuiControl,enable,% E_1
    }
  }
  if (hi_lim<1)
    hi_lim:=1
  if ModbusMapOpen
    AddCS=4
  return

ChangeAddres:
  gosub,submiting
  if (a_GuiControl="RTU_SlaveAdress")
  {
    if RTU_SlaveAdressHEXFlag
    {
      RTU_SlaveAdressHEXFlag:=0
      return
    }
    RTU_SlaveAdressFlag:=1
    GuiControl,,RTU_SlaveAdressHEX,% format("{:X}",RTU_SlaveAdress)
  }
  else if (a_GuiControl="RTU_SlaveAdressHEX")
  {
    if RTU_SlaveAdressFlag
    {
      RTU_SlaveAdressFlag:=0
      return
    }
    RTU_SlaveAdressHEXFlag:=1
    tmp:="0x" RTU_SlaveAdressHEX
    GuiControl,,RTU_SlaveAdress,% tmp+0
  }
  return

ChangeStartReg:
  gosub,submiting
  if (a_GuiControl="RTU_StartReg")
  {
    if RTU_StartRegHEXFlag
    {
      RTU_StartRegHEXFlag:=0
      return
    }
    RTU_StartRegFlag:=1
    GuiControl,,RTU_StartRegHEX,% format("{:X}",RTU_StartReg)
  }
  else if (a_GuiControl="RTU_StartRegHEX")
  {
    if RTU_StartRegFlag
    {
      RTU_StartRegFlag:=0
      return
    }
    RTU_StartRegHEXFlag:=1
    tmp:="0x" RTU_StartRegHEX
    GuiControl,,RTU_StartReg,% tmp+0
  }
  return

ChangeRegNum:
  gosub,submiting
  if (a_GuiControl="RTU_RegNum")
  {
    if RTU_RegNumHEXFlag
    {
      RTU_RegNumHEXFlag:=0
      return
    }
    RTU_RegNumFlag:=1
    GuiControl,,RTU_RegNumHEX,% format("{:X}",RTU_RegNum)
  }
  else if (a_GuiControl="RTU_RegNumHEX")
  {
    if RTU_RegNumFlag
    {
      RTU_RegNumFlag:=0
      return
    }
    RTU_RegNumHEXFlag:=1
    tmp:="0x" RTU_RegNumHEX
    GuiControl,,RTU_RegNum,% tmp+0
  }
  return

ChangeFilter_Addres:
  Gui,4:submit,nohide
  if (a_GuiControl="Filter_Addres")
  {
    if Filter_AddresHEXFlag
    {
      Filter_AddresHEXFlag:=0
      return
    }
    Filter_AddresFlag:=1
    GuiControl,,Filter_AddresHEX,% format("{:X}",Filter_Addres)
  }
  else if (a_GuiControl="Filter_AddresHEX")
  {
    if Filter_AddresFlag
    {
      Filter_AddresFlag:=0
      return
    }
    Filter_AddresHEXFlag:=1
    tmp:="0x" Filter_AddresHEX
    GuiControl,,Filter_Addres,% tmp+0
  }
  return

ChangeFilter_Command:
  Gui,4:submit,nohide
  if (a_GuiControl="Filter_Command")
  {
    if (Filter_CommandHEXFlag)
    {
      Filter_CommandHEXFlag:=0
      return
    }
    Filter_CommandFlag:=1
    GuiControl,,Filter_CommandHEX,% format("{:X}",Filter_Command)
  }
  else if (a_GuiControl="Filter_CommandHEX")
  {
    if (Filter_CommandFlag)
    {
      Filter_CommandFlag:=0
      return
    }
    Filter_CommandHEXFlag:=1
    tmp:="0x" Filter_CommandHEX
    GuiControl,,Filter_Command,% tmp+0
    Filter_Command:=tmp+0
  }
  else if (a_GuiControl="Filter_CommandRTU")
  {
    Filter_CommandFlag:=1
    if (Filter_CommandRTU>0 and Filter_CommandRTU<7)
      Filter_Command:=Filter_CommandRTU
    else if (Filter_CommandRTU>6)
      Filter_Command:=Filter_CommandRTU+8
    GuiControl,,Filter_Command,% Filter_Command
  }
  return

ChangeFilter_Byte:
  Gui,4:submit,nohide
  if (a_GuiControl="Filter_Byte")
  {
    if Filter_ByteHEXFlag
    {
      Filter_ByteHEXFlag:=0
      return
    }
    Filter_ByteFlag:=1
    GuiControl,,Filter_ByteHEX,% format("{:X}",Filter_Byte)
  }
  else if (a_GuiControl="Filter_ByteHEX")
  {
    if Filter_ByteFlag
    {
      Filter_ByteFlag:=0
      return
    }
    Filter_ByteHEXFlag:=1
    tmp:="0x" Filter_ByteHEX
    GuiControl,,Filter_Byte,% tmp+0
  }
  return

ChoseCOMNo:
  gosub,submiting
  if (!noChoseCOMNo)
  {
    noChoseCOMNo:=1
    return
  }
  GuiControl,,% hCOMNo,1
  return

ChoseCOMName:
  gosub,submiting
  GuiControl,,% hCOMName,1
  return

AddSettings:
  Gui,2:show
  return
CalcTimeout:
  Gui,2:submit,nohide
  stringleft,RS232_Parity_Choice,RS232_Parity_Choice,1
  RS232_Baud:=RS232_Baud_Choice
  RS232_Parity:=RS232_Parity_Choice
  RS232_Data:=RS232_Data_Choice
  RS232_Stop:=RS232_Stop_Choice
  Timeout:=((RS232_Data+4)/RS232_Baud)*1000
  ReadIntervalTimeout:=Floor(Timeout)
  if (ReadIntervalTimeout=0)
    ReadIntervalTimeout:=1
  GuiControl,,% hTimeout,% "Расчетный = " Timeout " ms"
  GuiControl,,% hRITimeout,% ReadIntervalTimeout
  return
CancelAddSettings:
  Gui,2:hide
  Gui,1:show
  return
EndAddSettings:
  Gui,2:submit
  if !ModbusMapOpen
    Gui,1:show
  stringleft,RS232_Parity_Choice,RS232_Parity_Choice,1
  RS232_Baud:=RS232_Baud_Choice
  RS232_Parity:=RS232_Parity_Choice
  RS232_Data:=RS232_Data_Choice
  RS232_Stop:=RS232_Stop_Choice
  gosub,AceptSettings
  return
DefaultSettings:
  GuiControl,Choose,RS232_Baud_Choice,14
  GuiControl,Choose,RS232_Parity_Choice,1
  GuiControl,Choose,RS232_Data_Choice,4
  GuiControl,Choose,RS232_Stop_Choice,1
  gosub,CalcTimeout
  return

ButtonSendData:
  gosub,submiting
  SendData:=1
  NumPut(2,Data,_MapSkip,"Int")
  gosub,SendData
  return

SendData:
  if (NumGet(Data,_SendDataNow,"UChar"))
    return
  gosub,submiting
  SendData:=0
  if MapChangeV
  {
;    if (MapNum>1)
;      MapNum--
    AddCS:=5
    AddCSLenght:=2
    RTU_Command:=6
    RTU_SlaveAdress:=MapList[MapEditNum,1]
    RTU_StartReg:=MapList[MapEditNum,2]
    if (MapList[MapEditNum,3]="INT" or MapList[MapEditNum,3]="WORD")
      RTU_RegNum:=1
    else
      RTU_RegNum:=2
    DataToSend:=MapValueNew
    ChoiceFormat:=MapChangeV+1
    if (MapList[MapEditNum,3]="INT")
      ChoiceType:="Short"
    else if (MapList[MapEditNum,3]="WORD")
      ChoiceType:="UShort"
    else if (MapList[MapEditNum,3]="DINT")
      ChoiceType:="Int"
    else if (MapList[MapEditNum,3]="DWORD")
      ChoiceType:="UInt"
    else if (MapList[MapEditNum,3]="REAL")
      ChoiceType:="Float"
    ChoiceSize:=RTU_RegNum*2
;msgbox,% RTU_SlaveAdress "`n" RTU_StartReg "`n" RTU_RegNum "`n" DataToSend "`n" ChoiceFormat "`nm4=" (MapList[MapEditNum,3]) "`n"  "`n"  "`n"
    NumPut(2,Data,_MapSkip,"Int")
  }

  if (AddCS=5)
  {
    if (RTU_Command>0 and RTU_Command<5)
    {
;      if (Auto_Bytes_Reqest and (RTU_Command=1 or RTU_Command=2))
;        RS232_Bytes_Reqest:=Ceil(RTU_RegNum/8)+6
;      if (Auto_Bytes_Reqest and (RTU_Command=3 or RTU_Command=4))
;        RS232_Bytes_Reqest:=RTU_RegNum*2+6
      size_to_send:=6
      varsetcapacity(VarDataToSend,size_to_send+AddCSLenght)
      NumPut(RTU_SlaveAdress,VarDataToSend,0,"uchar")
      NumPut(RTU_Command,VarDataToSend,1,"uchar")
      NumPut(SwapByte(RTU_StartReg),VarDataToSend,2,"ushort")
      NumPut(SwapByte(RTU_RegNum),VarDataToSend,4,"ushort")
      NumPut(CRC16(VarDataToSend,size_to_send),VarDataToSend,size_to_send,"ushort")
    }
    else if (RTU_Command=5 or RTU_Command=6)
    {
;      if Auto_Bytes_Reqest
;        RS232_Bytes_Reqest:=9
      size_to_send:=6
      varsetcapacity(VarDataToSend,size_to_send+AddCSLenght)
      NumPut(RTU_SlaveAdress,VarDataToSend,0,"uchar")
      NumPut(RTU_Command,VarDataToSend,1,"uchar")
      NumPut(SwapByte(RTU_StartReg),VarDataToSend,2,"ushort")

      if (ChoiceFormat=1) ;text
        size_to_send_tmp:=STRToDATA(DataToSend,VarDataToSend_tmp)
      else if (ChoiceFormat=2) ;DEC
        size_to_send_tmp:=DECToDATA(DataToSend,VarDataToSend_tmp,1,ChoiceType,ChoiceSize)
      else if (ChoiceFormat=3) ;HEX
        size_to_send_tmp:=HEXstrToDATA(DataToSend,VarDataToSend_tmp)
      else if (ChoiceFormat=4) ;BIN
        size_to_send_tmp:=BINstrToDATA(DataToSend,VarDataToSend_tmp)

      NumPut(numget(VarDataToSend_tmp,0,"Uchar"),VarDataToSend,4,"ushort")
      NumPut(numget(VarDataToSend_tmp,1,"Uchar"),VarDataToSend,5,"ushort")
      NumPut(CRC16(VarDataToSend,size_to_send),VarDataToSend,size_to_send,"ushort")
    }
    else if (RTU_Command=7 or RTU_Command=8)
    {
;      if Auto_Bytes_Reqest
;        RS232_Bytes_Reqest:=9
      if (RTU_Command=7)
      {
        RTU_Command=15
        RTU_ByteNum:=ceil(RTU_RegNum/8)
        size_to_send:=7+ceil(RTU_RegNum/8)
      }
      if (RTU_Command=8)
      {
        RTU_Command=16
        RTU_ByteNum:=RTU_RegNum*2
        size_to_send:=7+RTU_ByteNum
      }
      varsetcapacity(VarDataToSend,size_to_send+AddCSLenght)
      NumPut(RTU_SlaveAdress,VarDataToSend,0,"uchar")
      NumPut(RTU_Command,VarDataToSend,1,"uchar")
      NumPut(SwapByte(RTU_StartReg),VarDataToSend,2,"ushort")
      NumPut(SwapByte(RTU_RegNum),VarDataToSend,4,"ushort")
      NumPut(RTU_ByteNum,VarDataToSend,6,"uchar")
      if (ChoiceFormat=1) ;text
        size_to_send_tmp:=STRToDATA(DataToSend,VarDataToSend_tmp)
      else if (ChoiceFormat=2) ;DEC
        size_to_send_tmp:=DECToDATA(DataToSend,VarDataToSend_tmp,1,ChoiceType,ChoiceSize)
      else if (ChoiceFormat=3) ;HEX
        size_to_send_tmp:=HEXstrToDATA(DataToSend,VarDataToSend_tmp)
      else if (ChoiceFormat=4) ;BIN
        size_to_send_tmp:=BINstrToDATA(DataToSend,VarDataToSend_tmp)
      loop,% RTU_ByteNum
        NumPut(numget(VarDataToSend_tmp,a_index-1,"Uchar"),VarDataToSend,a_index+6,"uchar")
      NumPut(CRC16(VarDataToSend,size_to_send),VarDataToSend,size_to_send,"ushort")
    }
    if Auto_Bytes_Reqest
      Reset_Bytes_Reqest:=1
    if !MapRun
    if (RTU_Command>4 and !BeliveMe)
      return
    else if (RTU_Command>4 and BeliveMe)
      guicontrol,,BeliveMe,0
  }
  else if (AddCS=4)
  {
    if (ChoiceFormat=1) ;text
      size_to_send:=STRToDATA(DataToSend,VarDataToSend)
    else if (ChoiceFormat=2) ;dec
      size_to_send:=DECToDATA(DataToSend,VarDataToSend,1,ChoiceType,ChoiceSize)
    else if (ChoiceFormat=3) ;HEX
      size_to_send:=HEXstrToDATA(DataToSend,VarDataToSend)
    else if (ChoiceFormat=4) ;BIN
      size_to_send:=BINstrToDATA(DataToSend,VarDataToSend)
    NumPut(CRC16(VarDataToSend,size_to_send),VarDataToSend,size_to_send,"ushort")
  }
  else
  {
    if (ChoiceFormat=1) ;text
      size_to_send:=STRToDATA(DataToSend,VarDataToSend)
    else if (ChoiceFormat=2) ;dec
      size_to_send:=DECToDATA(DataToSend,VarDataToSend,1,ChoiceType,ChoiceSize)
    else if (ChoiceFormat=3) ;HEX
      size_to_send:=HEXstrToDATA(DataToSend,VarDataToSend)
    else if (ChoiceFormat=4) ;BIN
      size_to_send:=BINstrToDATA(DataToSend,VarDataToSend)
    if (AddCS>0)
      NumPut(CRC16(VarDataToSend,size_to_send,(ChoiceCSType=1),Polynom,InitVal,Refin,RefOut,XorOut,RefPoly),VarDataToSend,size_to_send,AddCS==2?"Uchar":"ushort")
  }
  if (MapRun and !MapChangeV)
  {
    AddCSLenght:=2
    size_to_send:=6
    varsetcapacity(VarDataToSend,size_to_send+AddCSLenght)
    NumPut(MapList[MapNum,1],VarDataToSend,0,"uchar")
    NumPut((MapList[MapNum,4]+2),VarDataToSend,1,"uchar")
    NumPut(SwapByte(MapList[MapNum,2]),VarDataToSend,2,"ushort")
    if (MapList[MapNum,3]="INT" or MapList[MapNum,3]="WORD")
      NumPut(SwapByte(1),VarDataToSend,4,"ushort")
    else
      NumPut(SwapByte(2),VarDataToSend,4,"ushort")
    NumPut(CRC16(VarDataToSend,size_to_send),VarDataToSend,size_to_send,"ushort")
  }
  if !size_to_send
    return
  size:=size_to_send+AddCSLenght
  if (size>1024)
  {
    SB_SetText("Слишком много данных для отправки. " size " byte.")
    return
  }
  sended:="Отправлено: "
  loop,% size
  {
    if (a_index<100)
      sended.=format("{:02X}",NumGet(VarDataToSend,a_index-1,"UChar")) " "
    NumPut(NumGet(VarDataToSend,a_index-1,"UChar"),Data,_SendDataOffset+a_index-1,"UChar")
  }
  SB_SetText(sended)
  NumPut(size,Data,_SendSize,"Int")
  NumPut(1,Data,_SendDataNow,"uChar")
  if SearchAdd
  {
    RTU_SlaveAdress++
    if RTU_SlaveAdress=256
    {
      SearchAdd:=false
      NumPut(DefaultBReq,Data,_RS232_Bytes_Reqest,"Int")
      if SendDataRepitedly
        gosub,SendDataRepitedly
      if (FoundAdd!="")
        GuiControl,,RTU_SlaveAdress,% FoundAdd
    }
    else
      GuiControl,,RTU_SlaveAdress,% RTU_SlaveAdress
  }
  if MapChangeV
    MapChangeV:=0
  return

SendDataRepitedly:
  SendDataRepitedly:=!SendDataRepitedly
  if SendDataRepitedly
    GuiControl,,% hSendDataRepitedly,Стоп ;
  else
  {
    GuiControl,,% hSendDataRepitedly,Циклично ;
    GuiControl,,% hE_RTU_Addres_6,Найти ;
    SearchAdd:=false
  }
  return

FindAdd:
  gosub,submiting
  SearchAdd:=!SearchAdd
  if SearchAdd
  {
    FoundAdd:=""
    GuiControl,,% hE_RTU_Addres_6,Стоп ;
    NumPut(20,Data,_RS232_Bytes_Reqest,"Int")
    if (RTU_SlaveAdress=255)
    {
      RTU_SlaveAdress:=0
      GuiControl,,RTU_SlaveAdress,% RTU_SlaveAdress
    }
    if !SendDataRepitedly
      gosub,SendDataRepitedly
  }
  else
  {
    NumPut(DefaultBReq,Data,_RS232_Bytes_Reqest,"Int")
    if (FoundAdd!="")
      GuiControl,,RTU_SlaveAdress,% FoundAdd
    GuiControl,,% hE_RTU_Addres_6,Найти ;
    if SendDataRepitedly
      gosub,SendDataRepitedly
  }
  return

DataToSendCheck:
  gosub,submiting
  tmp1:=DataToSend
  if (ChoiceFormat=1)
    GuiControl,hide,% hZeroState
  else if (ChoiceFormat=2)
  {
    GuiControl,hide,% hZeroState
    RegExMatch(DataToSend,"[0-9 -\.]+",tmp1)
    if instr(DataToSend,".")
    {
      hEDEC4once:=0
      GuiControl,,% hEDEC4,1
      GuiControl,disable,% hEDEC5
      GuiControl,disable,% hEDEC6
;      GuiControl,,% hEDEC7,1
    }
    else if instr(DataToSend,"-")
    {
      hEDEC4once:=0
      GuiControl,,% hEDEC3,1
      GuiControl,enable,% hEDEC5
      GuiControl,enable,% hEDEC6
    }
    if (!hEDEC4once and !instr(DataToSend,"-") and !instr(DataToSend,"."))
    {
      hEDEC4once:=1
      GuiControl,,% hEDEC2,1
      GuiControl,enable,% hEDEC5
      GuiControl,enable,% hEDEC6
    }
  }
  else if (ChoiceFormat=3)
  {
    RegExMatch(DataToSend,"[A-Fa-f0-9 ]+",tmp1)
    StringReplace,tmp2,DataToSend,%A_SPACE%,,All
    if (strlen(tmp2)&1)
      GuiControl,show,% hZeroState
    else
      GuiControl,hide,% hZeroState
  }
  else if (ChoiceFormat=4)
  {
    RegExMatch(DataToSend,"[0-1 ]+",tmp1)
    StringReplace,tmp2,DataToSend,%A_SPACE%,,All
    if (Mod(strlen(tmp2),8))
      GuiControl,show,% hZeroState
    else
      GuiControl,hide,% hZeroState
  }
  if (DataToSend!=tmp1)
  {
    GuiControl,,DataToSend,% tmp1
    PostMessage,0x00B1,1000,1000,,ahk_id %E_1%    ;  EM_SETSEL
    PostMessage,0xB7,,,,ahk_id %E_1%    ;  EM_SCROLLCARET
  }
  return



ConvertToSYM: ; to text
  gosub,SetFormatFloat
  gosub,ResetDECChoise
  gosub,submiting
  if (PreviousConversion="hex") ; hex to text
  {
    size:=HEXstrToDATA(DataToSend,tmp0)
    tmp_DataToSend:=DATAtoSTR(&tmp0,0,size)
  }
  else if (PreviousConversion="bin") ; bin to text
  {
    size:=BINstrToDATA(DataToSend,tmp1)
    tmp_DataToSend:=DATAtoSTR(&tmp1,0,size)
  }
  else if (PreviousConversion="dec") ; dec to text
  {
    DECToDATA(DataToSend,tmp1,1,ChoiceType,ChoiceSize)
    tmp_DataToSend:=DATAtoSTR(&tmp1,0,ChoiceSize)
  }
  GuiControl,,DataToSend,% tmp_DataToSend
  PreviousConversion:="sym"
  gosub,DataToSendCheck
  return

ConvertToHEX: ; to hex
  gosub,SetFormatFloat
  gosub,ResetDECChoise
  gosub,submiting
  if (PreviousConversion="sym") ; text to hex
  {
    size:=STRToDATA(DataToSend,tmp0)
    tmp_DataToSend:=DATAtoHEX(&tmp0,0,size,0,1)
  }
  else if (PreviousConversion="bin") ; bin to hex
  {
    size:=BINstrToDATA(DataToSend,tmp0,1)
    tmp_DataToSend:=DATAtoHEX(&tmp0,0,size,1)
  }
  else if (PreviousConversion="dec") ; dec to hex
  {
    DECToDATA(DataToSend,tmp1,1,ChoiceType,ChoiceSize)
    tmp_DataToSend:=DATAtoHEX(&tmp1,0,ChoiceSize)
  }
  GuiControl,,DataToSend,% tmp_DataToSend
  PreviousConversion:="hex"
  gosub,DataToSendCheck
  return

ConvertToBIN: ; to bin
  gosub,SetFormatFloat
  gosub,ResetDECChoise
  gosub,submiting
  if (PreviousConversion="sym") ; text to bin
  {
    size:=STRToDATA(DataToSend,tmp0)
    tmp_DataToSend:=DATAtoBIN(&tmp0,0,size)
  }
  else if (PreviousConversion="hex") ; hex to bin
  {
    size:=HEXstrToDATA(DataToSend,tmp0)
    tmp_DataToSend:=DATAtoBIN(&tmp0,0,size)
  }
  else if (PreviousConversion="dec") ; dec to bin
  {
    DECToDATA(DataToSend,tmp1,1,ChoiceType,ChoiceSize)
    tmp_DataToSend:=DATAtoBIN(&tmp1,0,ChoiceSize)
  }
  GuiControl,,DataToSend,% tmp_DataToSend
  PreviousConversion:="bin"
  gosub,DataToSendCheck
  return

ConvertToDEC: ; to dec
  gosub,SetFormatFloat
  loop,8
    GuiControl,show,% hEDEC%a_index%
  gosub,submiting
  old_DataToSend:=DataToSend
  GuiControl,hide,% hZeroState
  return
ConvertToDECRun:
  gosub,SetFormatFloat
  if !(ChoiceDEC and ChoiceDECSize)
    return
  ChoicePType:={1:"char",2:"short",3:"int",4:"int64"}
  if (ChoiceDEC=1)
    ChoiceType:="U" ChoicePType[ChoiceDECSize]
  else if (ChoiceDEC=2)
    ChoiceType:="" ChoicePType[ChoiceDECSize]
  else if (ChoiceDEC=3 and ChoiceDECSize=3)
    ChoiceType:="float"
  else if (ChoiceDEC=3 and ChoiceDECSize=4)
    ChoiceType:="duoble"
  if (ChoiceDECSize=3)
    ChoiceSize:=4
  else if (ChoiceDECSize=4)
    ChoiceSize:=8
  else
    ChoiceSize:=ChoiceDECSize
  if (PreviousConversion="sym") ; text to dec
  {
    STRToDATA(old_DataToSend,tmp0,1)
    tmp_DataToSend:=numget(tmp0,0,ChoiceType)
  }
  else if (PreviousConversion="hex") ; hex to dec
  {
    ClearVar(old_DataToSend)
    HEXstrToDATA(old_DataToSend,tmp0,1)
    tmp_DataToSend:=numget(tmp0,0,ChoiceType)
  }
  else if (PreviousConversion="bin") ; bin to dec
  {
    BINstrToDATA(old_DataToSend,tmp0,1)
    tmp_DataToSend:=numget(tmp0,0,ChoiceType)
  }
  GuiControl,,DataToSend,% tmp_DataToSend
  PreviousConversionWasDEC:=1
  return

ChooseDEC:
  gosub,submiting
  if (ChoiceDEC=3)
  {
    if (ChoiceDECSize and ChoiceDECSize<3)
    {
      GuiControl,,% hEDEC5,0
      GuiControl,,% hEDEC6,0
      GuiControl,,% hEDEC7,1
      ChoiceDECSize:=3
    }
    GuiControl,disable,% hEDEC5
    GuiControl,disable,% hEDEC6
  }
  else
  {
    GuiControl,enable,% hEDEC5
    GuiControl,enable,% hEDEC6
  }
  gosub,ConvertToDECRun
  return

ChooseDECSize:
  gosub,submiting
  GuiControl,disable,% hEDEC%a_index%
  gosub,ConvertToDECRun
  return

ResetDECChoise:
  loop,8
    GuiControl,hide,% hEDEC%a_index%
  GuiControl,,% hEDEC2,1
  GuiControl,,% hEDEC2,0
  GuiControl,,% hEDEC5,1
  GuiControl,,% hEDEC5,0
  ChoiceDEC:=0
  ChoiceDECSize:=0
  if PreviousConversionWasDEC
  {
    PreviousConversion:="dec"
    GuiControl,hide,% hZeroState
    PreviousConversionWasDEC:=0
  }
  return

ModbusOpenFromMain:
  MapNum:=1
  gosub,ModbusMapOpen
  gosub,MapOpen
  gosub,MapRun
  return
ModbusMapOpen:
  gui,6:show,,Карта регистров Modbus
  Gui,6:default
  MapNum:=1
  ModbusMapOpen:=1
  SB_SetText(COMPort)
  Gui,1:default
  return

6GuiSize:
  Gui,6:default
  if (!Old_GuiMapWidth or !Old_GuiMapHeight)
  {
    Old_GuiMapWidth:=A_GuiWidth
    Old_GuiMapHeight:=A_GuiHeight
  }
  if (A_GuiHeight<50 or A_EventInfo=1)
  {
    Gui,1:default
    return
  }
  DeltaGuiMapWidth:=A_GuiWidth-Old_GuiMapWidth
  DeltaGuiMapHeight:=A_GuiHeight-Old_GuiMapHeight
  MoveControl(Map_LV,,,DeltaGuiMapWidth,DeltaGuiMapHeight)
  Old_GuiMapWidth:=A_GuiWidth
  Old_GuiMapHeight:=A_GuiHeight
  Gui,1:default
  return

6GuiClose:
6GuiEscape:
MapExit:
  ModbusMapOpen:=0
  MapRun:=0
  gosub,SubmitExt
  Gui,6:hide
  return

MapOpen:
  Gui,6:default
;  Thread,NoTimers
  FileSelectFile,OpenMapFileName,3,% OldOpenMapFileName,Открыть сохраненную карту регистров,Modbus registers map file (*.mrmf)
;  Thread,NoTimers,false
  if errorlevel
  {
    tooltip,Отменено
    settimer,RemoveTooltip,-2000
    Gui,1:default
    return
  }
  OldOpenMapFileName:=OpenMapFileName
  OpenMapFileObj:=FileOpen(OpenMapFileName,"r","UTF-8-RAW")
  if !IsObject(OpenMapFileObj)
  {
    MsgBox,% "Не возможно открыть файл " OpenMapFileName "."
    Gui,1:default
    return
  }
  gosub,DiskonnectCOM
  FileLoading:=1
  tooltip,Загрузка файла...`nДвойной клик чтобы отменить.
  sleep,200
  TotalLoadSize:=4
  varsetcapacity(tmpsavedata,28)
  OpenMapFileObj.RawRead(tmpsavedata,28)
  LoadSize:=numget(tmpsavedata,0,"Uint")
  RS232_Baud:=numget(tmpsavedata,4,"UInt")
  RS232_Parity:=chr(numget(tmpsavedata,8,"UInt"))
  RS232_Data:=numget(tmpsavedata,12,"UInt")
  RS232_Stop:=numget(tmpsavedata,16,"UInt")
  ReadIntervalTimeout:=numget(tmpsavedata,20,"UInt")
  MapTOut:=numget(tmpsavedata,24,"UInt")
  MapList:=[]
  LV_Delete()
  loop,% LoadSize
  {
    if StopAnalyze
    {
      StopAnalyze:=0
      msgbox,Отменено
      break
    }
    if (TotalLoadSize>=OpenMapFileObj.Length)
      break
    TotalLoadSize+=276
    varsetcapacity(tmpsavedata,16)
    OpenMapFileObj.RawRead(tmpsavedata,16)
    MapAddres:=numget(tmpsavedata,0,"UInt")
    MapRegNum:=numget(tmpsavedata,4,"UInt")
    MapDataType:=MapDataOfType[numget(tmpsavedata,8,"UInt")]
    MapRegType:=numget(tmpsavedata,12,"UInt")
    varsetcapacity(tmpsavedata,256)
    OpenMapFileObj.RawRead(tmpsavedata,240)
    MapComment:=uDATAtoSTR(&tmpsavedata,0,120)
    MapList.Push([MapAddres,MapRegNum,MapDataType,MapRegType,MapComment])
    LV_ADD(,MapAddres " (" format("0x{:02X}",MapAddres) ")",MapRegNum " (" format("0x{:02X}",MapRegNum) ")",(MapRegType=1)?"Holding":"Input",MapDataType,,,,,MapComment)
  }
  LV_ModifyCol(9,"Auto")
  OpenMapFileObj.Close()
  Gui,1:default
  GuiControl,Choose,% hRS232_Baud_Choice,% RS232_Baud_List[RS232_Baud]
  GuiControl,Choose,% hRS232_Parity_Choice,% RS232_Parity_List[RS232_Parity]
  GuiControl,Choose,% hRS232_Data_Choice,% RS232_Data_List[RS232_Data]
  GuiControl,Choose,% hRS232_Stop_Choice,% RS232_Stop
  ReadIntervalTimeout:=ReadIntervalTimeout>255?255:ReadIntervalTimeout
  GuiControl,,% hRITimeout,% ReadIntervalTimeout
  tooltip,Загружено
  settimer,RemoveTooltip,-2000
  Gosub,EndAddSettings
  gosub,MapRun
  return

MapSave:
  Gui,6:default
  /*
    Количество регистров			4 byte
    Скорость					4 byte
    Четность					4 byte
    Бит данных					4 byte
    Стоп биты					4 byte
    ReadIntervalTimeout				4 byte
    MapTOut					4 byte

    Адрес					4 byte
    № регистра					4 byte
    Тип данных	- INT UINT DINT UDINT REAL	4 byte
    Тип регистра	- holding input		4 byte
    Коментарий					240 byte
    						=280 byte
  */
  if !OldSaveMapFileName
    OldSaveMapFileName:="ModbusMap " a_DD "." a_MM "." a_YYYY " " a_hour "." a_min
  Thread,NoTimers
  FileSelectFile,SaveMapFileName,S16,% OldSaveMapFileName,Сохранить карту регистров,Modbus registers map file (*.mrmf)
  Thread,NoTimers,false
  if errorlevel
  {
    tooltip,Отменено
    settimer,RemoveTooltip,-2000
    return
    Gui,1:default
  }
  if (!instr(SaveMapFileName,".mrmf") and FileExist(SaveMapFileName ".mrmf"))
  {
    msgBox,36,Сохранить карту регистров,Файл %SaveMapFileName%.mrmf существует.`nЗаменить?
    IfMsgBox No
    {
      return
      Gui,1:default
    }
  }
  FileSaving:=1
  tooltip,Сохранение файла...`nДвойной клик чтобы остановить.
  sleep,200
  SaveMapFileName:=instr(SaveMapFileName,".mrmf")?SaveMapFileName:SaveMapFileName . ".mrmf"
  OldSaveMapFileName:=SaveMapFileName
  SaveMapFileObj:=FileOpen(SaveMapFileName,"w","UTF-8-RAW")
  if !IsObject(SaveMapFileObj)
  {
    MsgBox,% "Не возможно создать файл " SaveMapFileName "."
    tooltip
    return
    Gui,1:default
  }
  varsetcapacity(tmpsavedata,28,0)
  SaveSize:=MapList.maxindex()
  numput(SaveSize,tmpsavedata,0,"Uint")
  numput(RS232_Baud,tmpsavedata,4,"UInt")
  numput(asc(RS232_Parity),tmpsavedata,8,"UInt")
  numput(RS232_Data,tmpsavedata,12,"UInt")
  numput(RS232_Stop,tmpsavedata,16,"UInt")
  numput(ReadIntervalTimeout,tmpsavedata,20,"UInt")
  numput(MapTOut,tmpsavedata,24,"UInt")
  SaveMapFileObj.RawWrite(tmpsavedata,28)
  loop,% SaveSize
  {
    if StopAnalyze
    {
      StopAnalyze:=0
      Msgbox,Отменено
      break
    }
;msgbox,% MapList[a_index,1] "`n" MapList[a_index,2] "`n" MapList[a_index,3] "`n" MapList[a_index,4] "`n" MapList[a_index,5] "`n"
    varsetcapacity(tmpsavedata,16,0)
    numput(MapList[a_index,1],tmpsavedata,0,"UInt")
    numput(MapList[a_index,2],tmpsavedata,4,"UInt")
    numput(MapTypeOfData[MapList[a_index,3]],tmpsavedata,8,"UInt")
    numput(MapList[a_index,4],tmpsavedata,12,"UInt")
    SaveMapFileObj.RawWrite(tmpsavedata,16)
    varsetcapacity(tmpsavedata,240,0)
    uSTRToDATA(MapList[a_index,5],tmpsavedata)
    SaveMapFileObj.RawWrite(tmpsavedata,240)
  }
  SaveMapFileObj.Close()
  FileSaving:=0
  tooltip,Сохранено.
  settimer,RemoveTooltip,-2000
  Gui,1:default
  return

MapGetSelected:
  Gui,6:default
  LVMap_Column:=LV_SubItemHitTest(Map_LV)
  LVMap_Row:=LV_GetNext()
  LV_GetText(LVMap_Text,LVMap_Row,LVMap_Column)
;tooltip,% LVMap_Column "`n" LVMap_Row
  if (A_GuiEvent="RightClick")
  {
    if (LVMap_Column=0 and LVMap_Row=0)
    {
      menu,MapRmenu,disable,Редактировать
      menu,MapRmenu,disable,Удалить
    }
    else
    {
      menu,MapRmenu,enable,Редактировать
      menu,MapRmenu,enable,Удалить
    }
    menu,MapRmenu,show
  }
  if (A_GuiEvent="DoubleClick")
  {
    MapEditNum:=LVMap_Row
    GuiControl,,% hMapValueNow,% LVMap_Text
    GuiControl,,% hMapValueNew,% LVMap_Text
    if (LVMap_Column=5)
      gosub,MapChangeDEC
    else if (LVMap_Column=6)
      gosub,MapChangeHEX
    else if (LVMap_Column=7)
      gosub,MapChangeBIN
    else if LVMap_Column
      gosub,MapEdit
  }
  if (A_GuiEvent="Normal")
  {
  }
  Gui,1:default
  return

MapPrioritySet:
  MapPriority:=LVMap_Row
  return

MapCopy:
  clipboard:=LVMap_Text
  tooltip,% "Скопированно:`t`t" LVMap_Text
  settimer,RemoveTooltip,-2000
  return

MapRun:
;  Gui,6:default
  NumPut(9,Data,_RS232_Bytes_Reqest,"Int")
  MapRun:=1
;  Gui,1:default
  return

MapStop:
;  Gui,6:default
  NumPut(DefaultBReq,Data,_RS232_Bytes_Reqest,"Int")
  MapRun:=0
;  Gui,1:default
  return

MapSetTOut:
  InputBox,tmp1,Задать таймаут ответа,Сколько миллисекунд ждать ответа,,240,160,,,,12,% MapTOut
  if !ErrorLevel
    MapTOut:=abs(tmp1)
  return

MapChangeAddres:
  Gui,7:default
  Gui,7:submit,nohide
  if (a_GuiControl="MapAddres")
  {
    if MapAddresHEXFlag
    {
      MapAddresHEXFlag:=0
      return
    }
    MapAddresFlag:=1
    GuiControl,,MapAddresHEX,% format("{:X}",MapAddres)
  }
  else if (a_GuiControl="MapAddresHEX")
  {
    if MapAddresFlag
    {
      MapAddresFlag:=0
      return
    }
    MapAddresHEXFlag:=1
    tmp:="0x" MapAddresHEX
    GuiControl,,MapAddres,% tmp+0
  }
  Gui,1:default
  return

MapChangeRegNum:
  Gui,7:default
  Gui,7:submit,nohide
  if (a_GuiControl="MapRegNum")
  {
    if MapRegNumHEXFlag
    {
      MapRegNumHEXFlag:=0
      return
    }
    MapRegNumFlag:=1
    GuiControl,,MapRegNumHEX,% format("{:X}",MapRegNum)
  }
  else if (a_GuiControl="MapRegNumHEX")
  {
    if MapRegNumFlag
    {
      MapRegNumFlag:=0
      return
    }
    MapRegNumHEXFlag:=1
    tmp:="0x" MapRegNumHEX
    GuiControl,,MapRegNum,% tmp+0
  }
  Gui,1:default
  return

MapAddresChange:
  InputBox,tmpA,Задать новый адрес,Новый адрес для выделенных строк,,240,160,,,,12,1
  if !ErrorLevel
  {
    if (tmpA>255 or !tmp1)
      tmpA:=1
    Gui,6:default
    LVMap_Row:=0
    loop
    {
      LVMap_Row:=LV_GetNext(LVMap_Row)
      if !LVMap_Row
        break
      loop,9
        LV_GetText(tmp%a_index%,LVMap_Row,a_index)
      LV_Modify(LVMap_Row,,tmpA,tmp2,tmp3,tmp4,"","","","",tmp9)
      MapList[LVMap_Row,1]:=tmpA
    }
    Gui,1:default
  }
  return

MapAdd:
  Gui,7:show
  return

MapAddReg:
  gui,7:submit
  Gui,6:default
  if !MapEdit
  {
    if !LVMap_Row
      LVMap_Row:=MapList.MaxIndex()?(MapList.MaxIndex()+1):1
    LV_Insert(LVMap_Row,,MapAddres " (" format("0x{:02X}",MapAddres) ")",MapRegNum " (" format("0x{:02X}",MapRegNum) ")",(MapRegType=1)?"Holding":"Input",MapDataType,,,,,MapComment)
    MapList.InsertAt(LVMap_Row,[MapAddres,MapRegNum,MapDataType,MapRegType,MapComment])
  }
  else
  {
    MapEdit:=0
    GuiControl,,% hMapAddReg,Добавить
    LV_Modify(LVMap_Row,,MapAddres " (" format("0x{:02X}",MapAddres) ")",MapRegNum " (" format("0x{:02X}",MapRegNum) ")",(MapRegType=1)?"Holding":"Input",MapDataType,,,,,MapComment)
    MapList[LVMap_Row]:=[MapAddres,MapRegNum,MapDataType,MapRegType,MapComment]
  }
  LV_ModifyCol(9,"Auto")
  Gui,1:default
  return

MapDelete:
  Gui,6:default
  loop
  {
    LVMap_Row:=LV_GetNext()
    if !LVMap_Row
      break
    LV_Delete(LVMap_Row)
    MapList.RemoveAt(LVMap_Row)
    tmpMap.RemoveAt(LVMap_Row)
  }
  Gui,1:default
  return

MapEdit:
  Gui,6:default
  MapEdit:=1
  GuiControl,,% hMapAddres,% MapList[LVMap_Row,1]
  GuiControl,,% hMapRegNum,% MapList[LVMap_Row,2]
  GuiControl,Choose,% hMapDataType,% MapList[LVMap_Row,3]
  GuiControl,Choose,% hMapRegType,% MapList[LVMap_Row,4]
  GuiControl,,% hMapComment,% MapList[LVMap_Row,5]
  GuiControl,,% hMapAddReg,Применить
  gui,7:show
  Gui,1:default
  return

MapCancelReg:
  gui,7:hide
  MapEdit:=0
  return

MapChangeDEC:
  if (MapList[MapEditNum,4]=2)
  {
    tooltip,Нельзя изменить Input регистр.
    settimer,RemoveTooltip,-2000
    return
  }
  MapChangeVtmp:=1
  GuiControl,,% hMapValueNow,% LVMap_Text
  GuiControl,,% hMapValueNew,% LVMap_Text
  Gui,8:show,,Изменнение значения DEC (функция 0x10)
  return

MapChangeHEX:
  if (MapList[MapEditNum,4]=2)
  {
    tooltip,Нельзя изменить Input регистр.
    settimer,RemoveTooltip,-2000
    return
  }
  MapChangeVtmp:=2
  GuiControl,,% hMapValueNow,% LVMap_Text
  GuiControl,,% hMapValueNew,% LVMap_Text
  Gui,8:show,,Изменнение значения HEX (функция 0x10)
  return

MapChangeBIN:
  if (MapList[MapEditNum,4]=2)
  {
    tooltip,Нельзя изменить Input регистр.
    settimer,RemoveTooltip,-2000
    return
  }
  MapChangeVtmp:=3
  GuiControl,,% hMapValueNow,% LVMap_Text
  GuiControl,,% hMapValueNew,% LVMap_Text
  Gui,8:show,,Изменнение значения BIN (функция 0x10)
  return

MapEditValue:
  Gui,8:submit,nohide
  MapChangeV:=MapChangeVtmp
  gosub,SendData
  return

MapCancelValue:
  Gui,8:hide
  MapChangeVtmp:=0
  GuiControl,,% hMapValueNow,
  return

DataFilter:
  Gui,4:show,,Фильтровать данные
  if (AddCS>=4)
  {
    Gui,4:submit,nohide
    Filter_CommandFlag:=1
    if (Filter_CommandRTU>0 and Filter_CommandRTU<7)
      Filter_Command:=Filter_CommandRTU
    else if (Filter_CommandRTU>6)
      Filter_Command:=Filter_CommandRTU+8
    GuiControl,,% hFiltE1,% Filter_Command
  }
  return

DataFilterOk:
  Gui,4:submit
  return

ListGetSelected:
  if (A_GuiEvent="RightClick")
  {
    LV_Column:=LV_SubItemHitTest(E_LV)
    LV_Row:=LV_GetNext(RowNumber)
    if (LV_Column>COLUMN_SYM and !EN_Kurvendrucker)
      menu,ListRightClick,enable,Отрисовать график ;
    else
      menu,ListRightClick,disable,Отрисовать график ;
    menu,ListRightClick,show
  }
  if ((A_GuiEvent="DoubleClick" or Update=1) and Analyzer and !AnBusy)
  {
    tooltip,Загрузка таблицы...`nДвойной клик чтобы отменить.
    StopAnalyze:=0
    Update:=0
    AnBusy:=1
    GuiControl,-Redraw,% E_LV
    GuiControl,-Redraw,% E_SLV
    Gui,1:ListView,% E_LV
    LV_GetText(AnSelected,LV_GetNext(),COLUMN_HOOK)
    loop,parse,AnSelected,|
    {
      if A_LoopField
      {
        if (a_index=1)
          RS232_Bytes_Received:=A_LoopField
        else if (a_index=2)
          Received_Addres:=A_LoopField
        else if (a_index=3)
          Received_Command:=A_LoopField
        else if (a_index=4)
          Received_Byte:=A_LoopField
        else if (a_index=5)
          Received_Message_Type:=A_LoopField
        else if (a_index=6)
          CheckSummIsCorrect:=A_LoopField
      }
    }
    Gui,1:ListView,% E_SLV
    LV_Delete()
    CSArr:={}
;    skip:=0
    For bytenum, val in AnBufer[AnSelected]
    {
      if StopAnalyze
      {
        StopAnalyze:=0
        tooltip
        break
      }
      skip:=0
      GuiControl,,Progress,% a_index/AnBufer[AnSelected].maxindex()*100
      if HideSamePackages
      {
        CS:=CRC16(val,val.maxindex(),,HideSamePol)
        if (CS=0)
        {
          HideSamePol--
          CS:=CRC16(val,val.maxindex(),,HideSamePol)
        }
        loop,% CSArr.maxindex()
        {
          if (CSArr[a_index]=CS)
          {
            skip:=1
            break
          }
        }
      }
      Packages_Recieved:=AnCufer[AnSelected,a_index]
      if !skip
      {
        Bufer:=val
        CSArr.Push(CS)
        Gui,1:ListView,% E_SLV
        gosub,ParseBuffer
      }
    }
    tooltip
    Gui,1:ListView,% E_LV
    AnBusy:=0
    GuiControl,+Redraw,% E_LV
    GuiControl,+Redraw,% E_SLV
    GuiControl,,Progress,0
  }
  return

ListCopySelected:
  tmp1:=""
  S:=LV_GetNext()
  if (LV_Column=COLUMN_#)
  {
    loop,% LV_GetCount("Selected")
    {
      row:=a_index
      loop,13
      {
        LV_GetText(tmp0,S+row-1,a_index)
        tmp1.=tmp0 "`t"
      }
      tmp1.="`n"
    }
    tooltip,% "Выделенный текст скопирован."
  }
  else
  {
    loop,% LV_GetCount("Selected")
    {
      LV_GetText(tmp0,S+a_index-1,LV_Column)
      tmp1.=tmp0 (tmp0=""?"`n":" ")
    }
    tooltip,% "Скопирован текст из колонки №" LV_Column
  }
  clipboard:=tmp1
  settimer,RemoveTooltip,-3000
  return

SecondListGetSelected:
  if UpDown_Label
    return
  Gui,1:ListView,% E_SLV
  if (A_GuiEvent="RightClick")
  {
    LV_Column:=LV_SubItemHitTest(E_SLV)
    LV_Row:=LV_GetNext(RowNumber)
    if (LV_Column>COLUMN_SYM and !EN_Kurvendrucker)
      menu,SecondListRightClick,enable,Отрисовать график ;
    else
      menu,SecondListRightClick,disable,Отрисовать график ;
    menu,SecondListRightClick,show
  }
  Gui,1:ListView,% E_LV
  return

HideSamePackages:
  HideSamePackages:=!HideSamePackages
  if HideSamePackages
  {
    menu,SecondListRightClick,check,Скрывать одинаковые пакеты
    menu,BufferMenu,check,Скрывать одинаковые пакеты
  }
  else
  {
    menu,SecondListRightClick,uncheck,Скрывать одинаковые пакеты
    menu,BufferMenu,uncheck,Скрывать одинаковые пакеты
  }
  Update:=1
  gosub,ListGetSelected
  return

SecondListCopySelected:
  Gui,1:ListView,% E_SLV
  LV_GetText(tmp1,LV_Row,LV_Column)
  clipboard:=tmp1
  Gui,1:ListView,% E_LV
  return

En_Analyzer:
  analyzer:=!analyzer
  if analyzer
  {
    menu,ListRightClick,disable,Очистить
    menu,ListRightClick,disable,Вернуть все
    menu,ListRightClick,disable,Очистить безвозвратно
    menu,ListRightClick,disable,Фильтровать данные
    menu,ListRightClick,check,Анализ пакетов
    menu,BufferMenu,disable,Фильтровать данные
    menu,BufferMenu,check,Анализ пакетов
    menu,BufferMenu,enable,Скрывать одинаковые пакеты
;  menu,ListRightClick,disable,Копировать
    menu,ListRightClick,disable,Отрисовать график
    GuiControl,show,% E_SLV
    GuiControl,Hide,% E_Text
    GuiControl,Hide,% E_111
    GuiControl,Hide,% E_112
    GuiControl,Hide,FollowSelect
    GuiControl,Hide,% hTtEButton
    GuiControl,,_InText_
    GuiControl,,Progress,0
    Gui,1:ListView,% E_SLV
    LV_Delete()
    Gui,1:ListView,% E_LV
    LV_Delete()
    LV_ModifyCol(COLUMN_#,80)
    LV_ModifyCol(COLUMN_HOOK,0)
    LV_ModifyCol(COLUMN_INFO,80)
    LV_ModifyCol(COLUMN_BIN,80)
    LV_ModifyCol(COLUMN_SYM,100)
    LV_ModifyCol(COLUMN_HEX,0)
    LV_ModifyCol(COLUMN_DEC,0)
    LV_ModifyCol(COLUMN_WORD,0)
    LV_ModifyCol(COLUMN_INT,0)
    LV_ModifyCol(COLUMN_DWORD,0)
    LV_ModifyCol(COLUMN_DINT,0)
    LV_ModifyCol(COLUMN_FLOAT,0)
    LV_ModifyCol(COLUMN_DOUBLE,0)
    gosub,Analyze
  }
  else
  {
    menu,ListRightClick,enable,Очистить
    menu,ListRightClick,enable,Вернуть все
    menu,ListRightClick,enable,Очистить безвозвратно
    menu,ListRightClick,enable,Фильтровать данные
    menu,ListRightClick,uncheck,Анализ пакетов
    menu,BufferMenu,enable,Фильтровать данные
    menu,BufferMenu,uncheck,Анализ пакетов
    menu,BufferMenu,disable,Скрывать одинаковые пакеты
    menu,ListRightClick,enable,Копировать
    menu,ListRightClick,enable,Отрисовать график
    GuiControl,Hide,% E_SLV
    GuiControl,show,% E_Text
    GuiControl,show,% E_111
    GuiControl,show,% E_112
    GuiControl,show,FollowSelect
    GuiControl,show,% hTtEButton
    Gui,1:ListView,% E_SLV
    LV_Delete()
    Gui,1:ListView,% E_LV
    LV_Delete()
    LV_ModifyCol(COLUMN_#,65)
    LV_ModifyCol(COLUMN_HOOK,0)
    LV_ModifyCol(COLUMN_INFO,45)
    LV_ModifyCol(COLUMN_BIN,60)
    LV_ModifyCol(COLUMN_SYM,20)
    LV_ModifyCol(COLUMN_HEX,37)
    LV_ModifyCol(COLUMN_DEC,32)
    LV_ModifyCol(COLUMN_WORD,50)
    LV_ModifyCol(COLUMN_INT,50)
    LV_ModifyCol(COLUMN_DWORD,20)
    LV_ModifyCol(COLUMN_DINT,20)
    LV_ModifyCol(COLUMN_FLOAT,80)
    LV_ModifyCol(COLUMN_DOUBLE,40)
    gosub,UnUpdate
  }
  return

Analyze:
  gosub,submiting
  LV_GetText(AnSelected,LV_GetNext(),COLUMN_HOOK)
  Gui,1:ListView,% E_SLV
  LV_Delete()
  Gui,1:ListView,% E_LV
  LV_Delete()
  GuiControl,-Redraw,% E_LV
  GuiControl,-Redraw,% E_SLV
  tooltip,Анализируется...
  tmp4:=0
  For anindex, AnArray in AnBufer
  {
    tmp4++
    RS232_Bytes_Received:=Received_Addres:=Received_Command:=Received_Byte:=Received_Message_Type:=CheckSummIsCorrect:=""
    loop,parse,anindex,|
    {
      if A_LoopField
      {
        if (a_index=1)
          RS232_Bytes_Received:=A_LoopField
        else if (a_index=2)
          Received_Addres:=A_LoopField
        else if (a_index=3)
          Received_Command:=A_LoopField
        else if (a_index=4)
          Received_Byte:=A_LoopField
        else if (a_index=5)
          Received_Message_Type:=A_LoopField
        else if (a_index=6)
          CheckSummIsCorrect:=A_LoopField
      }
    }
    if !CheckSummIsCorrect
      continue
    LV_Icon:=AddCS>1?(CheckSummIsCorrect?ICON_V:ICON_X):ICON_START
    LV_Hash:=AnArray.maxindex() " " Received_Message_Type
    LV_NumByte:=AddCS>1?"Адрес " . Received_Addres:"Byte1 = " . Received_Addres
    LV_Bin:=(AddCS>1?"Команда ":"Byte2 = ") . (Received_Command?Received_Command:"нет")
    LV_Sym:="Кол-во байт " RS232_Bytes_Received
    LV_HEX:=
    LV_DEC:=
    LV_WORD:=
    LV_INT:=
    LV_DWORD:=
    LV_DINT:=
    LV_Float:=
    LV_Double:=
    LV_Add("Icon" LV_Icon,LV_Hash,anindex,LV_NumByte,LV_Bin,LV_Sym,LV_HEX,LV_DEC,LV_WORD,LV_INT,LV_DWORD,LV_DINT,LV_Float,LV_Double)
  }
  For anindex, AnArray in AnBufer
  {
    RS232_Bytes_Received:=Received_Addres:=Received_Command:=Received_Byte:=Received_Message_Type:=CheckSummIsCorrect:=""
    loop,parse,anindex,|
    {
      if A_LoopField
      {
        if (a_index=1)
          RS232_Bytes_Received:=A_LoopField
        else if (a_index=2)
          Received_Addres:=A_LoopField
        else if (a_index=3)
          Received_Command:=A_LoopField
        else if (a_index=4)
          Received_Byte:=A_LoopField
        else if (a_index=5)
          Received_Message_Type:=A_LoopField
        else if (a_index=6)
          CheckSummIsCorrect:=A_LoopField
      }
    }
    if CheckSummIsCorrect
      continue
;msgbox,% AnBufer.maxindex()
    GuiControl,,Progress,% a_index/tmp4*100
    LV_Icon:=AddCS>1?(CheckSummIsCorrect?ICON_V:ICON_X):ICON_START
    LV_Hash:=AnArray.maxindex() " " Received_Message_Type
    LV_NumByte:=AddCS>1?"Адрес " . Received_Addres:"Byte1 = " . Received_Addres
    LV_Bin:=(AddCS>1?"Команда ":"Byte2 = ") . (Received_Command?Received_Command:"нет")
    LV_Sym:="Кол-во байт " RS232_Bytes_Received
    LV_HEX:=
    LV_DEC:=
    LV_WORD:=
    LV_INT:=
    LV_DWORD:=
    LV_DINT:=
    LV_Float:=
    LV_Double:=
    LV_Add("Icon" LV_Icon,LV_Hash,anindex,LV_NumByte,LV_Bin,LV_Sym,LV_HEX,LV_DEC,LV_WORD,LV_INT,LV_DWORD,LV_DINT,LV_Float,LV_Double)
  }
  Gui,1:ListView,% E_LV
  Loop % LV_GetCount()
  {
    LV_GetText(RetrievedText,A_Index,COLUMN_HOOK)
    if (InStr(RetrievedText,AnSelected) or A_Index=1000)
    {
      LV_Modify(0,"-Select")
      LV_Modify(A_Index,"Select")
      LV_Modify(A_Index,"Vis Focus")
      break
    }
  }
  GuiControl,,Progress,0
  tooltip
  GuiControl,+Redraw,% E_LV
  GuiControl,+Redraw,% E_SLV
  return

Kurvendrucker:
  if analyzer
    gosub,En_Analyzer
  if SendDataRepitedly
  {
    GuiControl,,EN_Kurvendrucker,0
    gosub,SendDataRepitedly
    return
  }
  if EN_MonitorVar
    gosub,GrafClose
  if (!Gdip_Initialised)
  {
    msgbox,Библиотека GDip++ не поддерживается в системе.
    return
  }
  gosub,submiting
  if EN_Kurvendrucker
  {
    Graf_Start_Time:=a_tickcount
    GuiControlGet,SaveT_pos,Pos,% E_Text
    GuiControlGet,SavehP2_pos,Pos,% hProgress2
    GuiControlGet,SavehG_pos,Pos,% hGrafPos
    gosub,GrafOpen
    GuiControlGet,E_pos,Pos,% E_LV
    GuiControlGet,T_pos,Pos,% E_Text
    tmp_Deltamx:=-(E_posw+20)
;    MoveControl(hProgress2,tmp_Deltamx,,-tmp_Deltamx,,0)
;    MoveControl(E_Text,tmp_Deltamx,,-tmp_Deltamx,,0)
;    MoveControl(hGrafPos,tmp_Deltamx,,-tmp_Deltamx,,0)
    if (tmp_Deltamx>0)
      Gdip_X_Offset-=tmp_Deltamx*2
    GuiControl,Hide,% E_LV
    GuiControl,Hide,% hProgress
    GuiControl,Hide,% hTtEButton
    GuiControl,Hide,% hClButton
    GuiControl,Hide,FollowSelect
    GuiControl,Choose,% hCS,1
    loop,10
      GuiControl,Hide,% hECSType%a_index%
    loop,% MaxNumOfGraf+1
    {
      i:=a_index-1
      GuiControl,Show,% hECB%i%
    }
    gosub,GrafUpdate
    gosub,GrafMenuEnable
    gosub,GrafRedraw
  }
  else
  {
    GuiControl,show,% E_LV
    GuiControl,show,% hProgress
    GuiControl,show,% hTtEButton
    GuiControl,show,% hClButton
    GuiControl,show,FollowSelect
    loop,% MaxNumOfGraf+1
    {
      i:=a_index-1
      GuiControl,Hide,% hECB%i%
    }
    gosub,GrafClose
    GuiControlGet,E_pos,Pos,% E_LV
    GuiControlGet,T_pos,Pos,% E_Text
    tmp_Deltamx:=300
;    GuiControl,Move,% E_Text,% " x" SaveT_posX " y" SaveT_posy " w" SaveT_posw " h" SaveT_posh
;    GuiControl,Move,% hProgress2,% " x" SavehP2_posX " y" SavehP2_posy " w" SavehP2_posw " h" SavehP2_posh
;    GuiControl,Move,% hGrafPos,% " x" SavehG_posX " y" SavehG_posy " w" SavehG_posw " h" SavehG_posh
    if (tmp_Deltamx>0)
      Gdip_X_Offset-=tmp_Deltamx*2
    gosub,GrafUpdate
    gosub,GrafMenuDisable
  }
  gosub,ControlSize
  return

GrafCreateMenu:
  menu,GrafRightClick,DeleteAll
  menu,GrafRightClick,add,Автоматическое максимальное значение,GrafAutoMinMax
  menu,GrafRightClick,add,Задать максимальное значение вручную	F2,GrafChangeMinMax
  menu,GrafRightClick,add
  menu,GrafRightClick,add,Сохранить текущее изображение графика,GrafSaveImage
  menu,GrafRightClick,add,Сохранить все данные графика в текстовый документ,GrafSaveData
  menu,GrafRightClick,add
  menu,GrafRightClick,add,Прорисовать старые данные (Вернуть все),UnUpdate
  menu,GrafRightClick,add
;  Menu,GrafRightClick,Icon,Автоматическое максимальное значение,shell32.dll,% ICON_FAVORITE
  Menu,GrafRightClick,Icon,Сохранить текущее изображение графика,shell32.dll,% ICON_SAVE
  Menu,GrafRightClick,Icon,Прорисовать старые данные (Вернуть все),shell32.dll,% ICON_BACK
  Menu,GrafRightClick,Icon,Задать максимальное значение вручную	F2,shell32.dll,% ICON_MANUAL
;  Menu,GrafRightClick,Icon,Сохранить все данные графика в текстовый документ,shell32.dll,% ICON_SAVE
  if EN_Kurvendrucker
  {
    menu,GrafRightClick,add,Автоматически определять разделитель графиков,GrafAutoEnding
    menu,GrafRightClick,add,Сбросить автоопределение разделителя	F5,GrafAutoEndingReset
    menu,GrafRightClick,add,Задать разделитель вручную	F3,GrafChangeEnding
    menu,GrafRightClick,add
;    Menu,GrafRightClick,Icon,Автоматически определять разделитель графиков,shell32.dll,% ICON_FAVORITE
    Menu,GrafRightClick,Icon,Сбросить автоопределение разделителя	F5,shell32.dll,% ICON_RESET
    Menu,GrafRightClick,Icon,Задать разделитель вручную	F3,shell32.dll,% ICON_MANUAL
    if AutoEnding
    {
      menu,GrafRightClick,Check,Автоматически определять разделитель графиков ;
      menu,GrafMenu,Check,Автоматически определять разделитель графиков ;
    }
  }
  menu,GrafRightClick,add,Сглаживание "ступенек"	F4,GrafSmooth
  menu,GrafRightClick,add,Остановить прокрутку	F1,GrafPause
  menu,GrafRightClick,add
  menu,GrafRightClick,add,Очистить график,GrafClear
  menu,GrafRightClick,add
  menu,GrafRightClick,add,Увеличить	Колесико мыши или Ctrl+,ZoomIn
  menu,GrafRightClick,add,Уменьшить	Колесико мыши или Ctrl-,ZoomOut
;  Menu,GrafRightClick,Icon,Сглаживание "ступенек"	F4,shell32.dll,% ICON_SMOOTH
;  Menu,GrafRightClick,Icon,Остановить прокрутку	F1,shell32.dll,% ICON_STOP_SCROLL
  Menu,GrafRightClick,Icon,Очистить график,shell32.dll,% ICON_CLEAR
  Menu,GrafRightClick,Icon,Увеличить	Колесико мыши или Ctrl+,shell32.dll,% ICON_SIZE
  Menu,GrafRightClick,Icon,Уменьшить	Колесико мыши или Ctrl-,shell32.dll,% ICON_SIZE
  if EN_MonitorVar
  {
    menu,GrafRightClick,add
    menu,GrafRightClick,add,Закрыть,GrafClose
    Menu,GrafRightClick,Icon,Закрыть,shell32.dll,% ICON_HIDE
  }
  if AutoMinMax
  {
    menu,GrafRightClick,Check,Автоматическое максимальное значение ;
    menu,GrafMenu,Check,Автоматическое максимальное значение ;
  }
  if GrafSmooth
  {
    menu,GrafRightClick,Check,Сглаживание "ступенек"	F4
    menu,GrafMenu,Check,Сглаживание "ступенек"	F4
  }
  return

GrafMenuEnable:
  if (!GrafDataExist and !EN_Kurvendrucker)
    return
  if !EN_Kurvendrucker
  {
    menu,GrafMenu,enable,Открыть
    menu,GrafMenu,enable,Закрыть
  }
  else
  {
    menu,GrafMenu,disable,Открыть
    menu,GrafMenu,disable,Закрыть
  }
  menu,GrafMenu,enable,Сохранить текущее изображение графика
  menu,GrafMenu,enable,Сохранить все данные графика в текстовый документ
  menu,GrafMenu,enable,Автоматическое максимальное значение
  menu,GrafMenu,enable,Задать максимальное значение вручную	F2
  if EN_Kurvendrucker
  {
    menu,GrafMenu,enable,Автоматически определять разделитель графиков
    menu,GrafMenu,enable,Сбросить автоопределение разделителя	F5
    menu,GrafMenu,enable,Задать разделитель вручную	F3
  }
  menu,GrafMenu,enable,Сглаживание "ступенек"	F4
  menu,GrafMenu,enable,Остановить прокрутку	F1
  menu,GrafMenu,enable,Очистить график
  menu,GrafMenu,enable,Увеличить	Колесико мыши или Ctrl+
  menu,GrafMenu,enable,Уменьшить	Колесико мыши или Ctrl-
  return

GrafMenuDisable:
  if !GrafDataExist
    menu,GrafMenu,disable,Открыть
  else
    menu,GrafMenu,enable,Открыть
  menu,GrafMenu,disable,Закрыть
  menu,GrafMenu,disable,Сохранить текущее изображение графика
  menu,GrafMenu,disable,Сохранить все данные графика в текстовый документ
  menu,GrafMenu,disable,Автоматическое максимальное значение
  menu,GrafMenu,disable,Задать максимальное значение вручную	F2
  if !EN_Kurvendrucker
  {
    menu,GrafMenu,disable,Автоматически определять разделитель графиков
    menu,GrafMenu,disable,Сбросить автоопределение разделителя	F5
    menu,GrafMenu,disable,Задать разделитель вручную	F3
  }
  menu,GrafMenu,disable,Сглаживание "ступенек"	F4
  menu,GrafMenu,disable,Остановить прокрутку	F1
  menu,GrafMenu,disable,Очистить график
  menu,GrafMenu,disable,Увеличить	Колесико мыши или Ctrl+
  menu,GrafMenu,disable,Уменьшить	Колесико мыши или Ctrl-
  return

GrafSaveImage:
;  if !OldGrafSaveFile
  OldGrafSaveFile:=COMPort " " a_DD "." a_MM "." a_YYYY " " a_hour "." a_min ".png"
  Thread,NoTimers
  FileSelectFile,GrafSaveFile,S16,% OldGrafSaveFile,Сохранить текущее изображение графика,Image file (*.BMP, *.DIB, *.RLE, *.JPG, *.JPEG, *.JPE, *.JFIF, *.GIF, *.TIF, *.TIFF, *.PNG)
  Thread,NoTimers,false
  if errorlevel
  {
    tooltip,Отменено
    settimer,RemoveTooltip,-2000
    return
  }
  gosub,GrafRedraw
  pBitmap:=Gdip_CreateBitmapFromHBITMAP(Gdip_hbm2)
  pNewBitmap:=Gdip_CreateBitmap(Gdip_W,Gdip_H)
  G:=Gdip_GraphicsFromImage(pNewBitmap)
  Gdip_DrawImage(G,pBitmap,0,0,Gdip_W,Gdip_H,0,0,Gdip_W,Gdip_H)
  Gdip_SaveBitmapToFile(pNewBitmap,GrafSaveFile)
  Gdip_DisposeImage(pBitmap)
  Gdip_DisposeImage(pNewBitmap)
  msgbox,Сохранено.
  return

GrafSaveData:
  if EN_MonitorVar
  {
    msgbox,UnderConstruct
  }
  if EN_Kurvendrucker
  {
    msgbox,UnderConstruct
  }
  return

GrafDrawFromSelected:
  if (!Gdip_Initialised)
  {
    msgbox,Библиотека GDip++ не поддерживается в системе.
    return
  }
  if AutoMinMax
    hi_lim:=1
  EN_MonitorVar:=1
  gosub,GrafOpen

  if Analyzer
    Gui,1:ListView,% E_SLV
  else
    Gui,1:ListView,% E_LV
  Graf_Start_Time:=a_tickcount
  LV_GetText(Slave_Start_Byte,LV_Row,COLUMN_HOOK)
  LV_GetText(Slave_Addres,LV_Row-Slave_Start_Byte,COLUMN_DEC)
  LV_GetText(Slave_Command,LV_Row-Slave_Start_Byte+1,COLUMN_DEC)
  LV_GetText(Slave_ByteNum,LV_Row-Slave_Start_Byte+2,COLUMN_DEC)
  Slave_ByteNum+=5
  if (LV_Column=COLUMN_HEX)
  {
    Slave_Data_Type:="char"
    Slave_Len:=1
  }
  if (LV_Column=COLUMN_DEC)
  {
    Slave_Data_Type:="uchar"
    Slave_Len:=1
  }
  if (LV_Column=COLUMN_WORD)
  {
    Slave_Data_Type:="ushort"
    Slave_Len:=2
  }
  if (LV_Column=COLUMN_INT)
  {
    Slave_Data_Type:="short"
    Slave_Len:=2
  }
  if (LV_Column=COLUMN_DWORD)
  {
    Slave_Data_Type:="uint"
    Slave_Len:=4
  }
  if (LV_Column=COLUMN_DINT)
  {
    Slave_Data_Type:="int"
    Slave_Len:=4
  }
  if (LV_Column=COLUMN_FLOAT)
  {
    Slave_Data_Type:="float"
    Slave_Len:=4
  }
  if (LV_Column=COLUMN_DOUBLE)
  {
    Slave_Data_Type:="Double"
    Slave_Len:=8
  }
  if Analyzer
  {
    Update:=1
    gosub,ListGetSelected
  }
  return

GrafClose:
  if EN_MonitorVar
    EN_MonitorVar:=0
  GuiControlGet,E_pos,Pos,% E_Text
  GuiControlGet,T_pos,Pos,% hGrafPos
  tmpy:=T_posh-10
;  MoveControl(E_Text,,,,tmpy,0)
;  MoveControl(E_SLV,,,,tmpy,0)
;  MoveControl(hProgress2,,tmpy,,,0)
;  MoveControl(hGrafPos,,tmpy,,-tmpy,0)
  ShiftSpaceY:=950
  gosub,ControlSize
  gosub,GrafRedraw
  gosub,GrafMenuDisable
  return

GrafOpen:
  if !EN_Kurvendrucker
  {
    EN_MonitorVar:=1
    GrafDataExist:=1
  }
  gosub,GrafMenuEnable
  GuiControlGet,E_pos,Pos,% E_Text
  tmpy:=-E_posh+70
;  MoveControl(E_Text,,,,tmpy,0)
;  MoveControl(E_SLV,,,,tmpy,0)
;  MoveControl(hProgress2,,tmpy,,,0)
;  MoveControl(hGrafPos,,tmpy,,-tmpy,0)
  ShiftSpaceY:=150
  gosub,ControlSize
  gosub,GrafCreateMenu
  gosub,GrafRedraw
  return

GrafClear:
  ObjClear(Monitor_value)
  ObjClear(Monitor_value_T)
  loop,% MaxNumOfGraf
    ObjClear(Monitor_value%a_index%)
  gosub,GrafRedraw
  return

HotkeysOn:
  Hotkey,IfWinActive,% "ahk_id " hMainWin
  Hotkey,F1,F1_Label
  Hotkey,F2,F2_Label
  Hotkey,F3,F3_Label
  Hotkey,F4,F4_Label
  Hotkey,F5,F5_Label
  Hotkey,~$LButton,LButton_Label
  Hotkey,~$LButton up,LButton_up_Label
  Hotkey,~$RButton,RButton_Label
  Hotkey,*~$Right,Right_Label
  Hotkey,*~$Left,Left_Label
  Hotkey,^sc00D,sc00D_Label
  Hotkey,~$WheelUp,WheelUp_Label
  Hotkey,^sc00C,sc00C_Label
  Hotkey,~$WheelDown,WheelDown_Label
  Hotkey,^!+#vk4c,vk4c_Label
;  Hotkey,Esc,Esc_Label
  Hotkey,~$Up,Up_Label
  Hotkey,~$Down,Down_Label

  Hotkey,IfWinActive,% "ahk_id " hGraf
  Hotkey,F1,F1_Label
  Hotkey,F2,F2_Label
  Hotkey,F3,F3_Label
  Hotkey,F4,F4_Label
  Hotkey,F5,F5_Label
  Hotkey,~$LButton,LButton_Label
  Hotkey,~$LButton up,LButton_up_Label
  Hotkey,~$RButton,RButton_Label
  Hotkey,*~$Right,Right_Label
  Hotkey,*~$Left,Left_Label
  Hotkey,^sc00D,sc00D_Label
  Hotkey,~$WheelUp,WheelUp_Label
  Hotkey,^sc00C,sc00C_Label
  Hotkey,~$WheelDown,WheelDown_Label
  Hotkey,^!+#vk4c,vk4c_Label
  Hotkey,Esc,Esc_Label

  return

HotkeysOff:
  return

F4_Label:
GrafSmooth:
  if GrafSmooth:=!GrafSmooth
  {
    menu,GrafRightClick,Check,Сглаживание "ступенек"	F4
    menu,GrafMenu,Check,Сглаживание "ступенек"	F4
  }
  else
  {
    menu,GrafRightClick,UnCheck,Сглаживание "ступенек"	F4
    menu,GrafMenu,UnCheck,Сглаживание "ступенек"	F4
  }
  if (a_tickcount-StartDrawTime>500)
    gosub,GrafRedraw
  return

GrafAutoMinMax:
  if AutoMinMax:=!AutoMinMax
  {
    hi_lim:=1
    menu,GrafRightClick,Check,Автоматическое максимальное значение ;
    menu,GrafMenu,Check,Автоматическое максимальное значение ;
  }
  else
  {
    menu,GrafRightClick,UnCheck,Автоматическое максимальное значение ;
    menu,GrafMenu,UnCheck,Автоматическое максимальное значение ;
  }
  if (a_tickcount-StartDrawTime>500)
    gosub,GrafRedraw
  return

F2_Label:
GrafChangeMinMax:
  InputBox,tmp1,Задать максимальное значение вручную	F2,Введите положительное число,,240,130,,,,12,% hi_lim
  if !ErrorLevel
  {
    menu,GrafRightClick,UnCheck,Автоматическое максимальное значение ;
    menu,GrafMenu,UnCheck,Автоматическое максимальное значение ;
    AutoMinMax:=0
    hi_lim:=abs(tmp1)
  }
  if (a_tickcount-StartDrawTime>500)
    gosub,GrafRedraw
  return

GrafAutoEnding:
  AutoEnding:=!AutoEnding
  if AutoEnding
  {
    EndSymbol:=""
    TryFind:=0
;    Monitorintext:=""
    menu,GrafRightClick,Check,Автоматически определять разделитель графиков ;
    menu,GrafMenu,Check,Автоматически определять разделитель графиков ;
    settimer,GrafAutoEndingReset,1000
  }
  else
  {
    menu,GrafRightClick,UnCheck,Автоматически определять разделитель графиков ;
    menu,GrafMenu,UnCheck,Автоматически определять разделитель графиков ;
    settimer,GrafAutoEndingReset,off
  }
  return

F5_Label:
if !(EN_Kurvendrucker)
  return
GrafAutoEndingReset:
  EndSymbol:=""
  TryFind:=0
  Monitorintext:=""
  return

F3_Label:
if !(EN_Kurvendrucker)
  return
GrafChangeEnding:
  InputBox,tmp1,Задать разделитель вручную,Введите разделительный символ,,240,130,,,,12,% EndSymbol
  if !ErrorLevel
  {
    menu,GrafRightClick,UnCheck,Автоматически определять разделитель графиков ;
    menu,GrafMenu,UnCheck,Автоматически определять разделитель графиков ;
    AutoEnding:=0
    settimer,GrafAutoEndingReset,off
    EndSymbol:=tmp1
  }
  return

F1_Label:
if !(Monitorvalue or EN_Kurvendrucker)
  return
GrafPause:
  if GrafPause:=!GrafPause
  {
    GuiControl,,GrafPause,% GrafPause
    menu,GrafRightClick,Check,Остановить прокрутку	F1 ;
    menu,GrafMenu,Check,Остановить прокрутку	F1 ;
  }
  else
  {
    GuiControl,,GrafPause,% GrafPause
    menu,GrafRightClick,UnCheck,Остановить прокрутку	F1 ;
    menu,GrafMenu,UnCheck,Остановить прокрутку	F1 ;
  }
  return

GrafDraw:
  StartDrawTime:=a_tickcount
GrafRedraw:
  if (Monitor_value_T.maxindex()<Gdip_W)
    Gdip_Points_Num:=Gdip_W
  else
    Gdip_Points_Num:=Monitor_value_T.maxindex()
  tmp0:=((Gdip_H//Gdip_ZoomY)//2)
  if (Gdip_Y_Offset<tmp0) ; верхний край
    Gdip_Y_Offset:=tmp0
  else if (Gdip_Y_Offset>Gdip_H-tmp0) ; нижний край
    Gdip_Y_Offset:=Gdip_H-tmp0
  if (Gdip_X_Offset>Gdip_Points_Num-Gdip_W/Gdip_ZoomX) ; левый край
    Gdip_X_Offset:=Gdip_Points_Num-Gdip_W/Gdip_ZoomX
  if (Gdip_X_Offset<0) ; правый край
    Gdip_X_Offset:=0
  Gdip_X_Offset:=round(Gdip_X_Offset)
  if AutoMinMax
  {
    if EN_Kurvendrucker
    {
      hi_lim:=0
      loop,% NumOfGraf
      {
        tmp1:=Ceil(ArrGetMax(Monitor_value%a_index%,Gdip_X_Offset,round(Gdip_W/Gdip_ZoomX)))
        if (hi_lim<abs(tmp1))
          hi_lim:=abs(tmp1)
      }
    }
    else
      hi_lim:=abs(Ceil(ArrGetMax(Monitor_value,Gdip_X_Offset,round(Gdip_W/Gdip_ZoomX))))
  }

  GuiControl,,Progress2,% round(100-Gdip_X_Offset/(Gdip_Points_Num-Gdip_W/Gdip_ZoomX)*100)
  ; очистить поле
  Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush,0,0,Gdip_Points_Num,Gdip_H)
  ; отрисовать сетку
  Gdip_DrawLine(pMainHolst_G,Gdip_GrayPen,0,Gdip_M
			  ,Gdip_Points_Num,Gdip_M)
  ; Отрисовать горизонтальные линии величин с текстом
  Grad:=10**NumOfSymbols:=(strlen(round(hi_lim))-1)
  GdipCoordX:=Gdip_Font_W ;Gdip_Points_Num-Gdip_Font_W*(NumOfSymbols+1)-10
  Gdip_Text_W:=Gdip_Font_W*NumOfSymbols
  loop,% round(hi_lim)//Grad
  {
    Val:=A_index*Grad
    PlussCoordY:=abs(round((Val/hi_lim)*Gdip_M)+Gdip_M-Gdip_H)
    MinusCoordY:=abs(round((-Val/hi_lim)*Gdip_M)+Gdip_M-Gdip_H)
    Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,GdipCoordX,PlussCoordY+1,Gdip_Text_W,Gdip_Font_H)
    Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,GdipCoordX,MinusCoordY-Gdip_Font_H,Gdip_Text_W,Gdip_Font_H)
    Gdip_TextToGraphics(pMainHolst_G,Val,"x" GdipCoordX " y" PlussCoordY+1 " Left cff000000 " Gdip_Font_Size,Gdip_Font)
    Gdip_TextToGraphics(pMainHolst_G,-Val,"x" GdipCoordX-10 " y" MinusCoordY-Gdip_Font_H " Left cff000000 " Gdip_Font_Size,Gdip_Font)
    Gdip_DrawLine(pMainHolst_G,Gdip_GrayPen2,0,PlussCoordY
			     ,Gdip_Points_Num,PlussCoordY)
    Gdip_DrawLine(pMainHolst_G,Gdip_GrayPen2,0,MinusCoordY
			     ,Gdip_Points_Num,MinusCoordY)
  }
; линия нуля
  Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,GdipCoordX,Gdip_M,Gdip_Font_W,Gdip_Font_H)
  Gdip_TextToGraphics(pMainHolst_G,0,"x" GdipCoordX " y" Gdip_M " Left cff000000 " Gdip_Font_Size,Gdip_Font)

; отрисовать синий график
  if EN_MonitorVar
  {
    Gdip_Time_Stamp:=0
    Old_j:=0
    Smooth_GdipCoordX:=0
    Old_GdipCoordY:=abs(round((Monitorvalue/hi_lim)*(Gdip_M))+Gdip_M-Gdip_H) ;Gdip_M
    Old_Time:=Average_time_summ:=Average_time_Devider:=0
    Val_UnderGraf_Drawed:=0
    loop,% round(Gdip_W/Gdip_ZoomX)
    {
      i:=a_index
      j:=Monitor_value[i+Gdip_X_Offset]
      ; out=(inval/max)*(hi_lim-lo_lim)+lo_lim
      GdipCoordX:=round(Gdip_W-(i*Gdip_ZoomX))
      if (abs(j)<=hi_lim)
        GdipCoordY:=abs(round((j/hi_lim)*(Gdip_M))+Gdip_M-Gdip_H)
      else if (j>0)
        GdipCoordY:=0
      else if (j<0)
        GdipCoordY:=Gdip_H
      if tmp0:=Monitor_value_T[i+Gdip_X_Offset]
      {
        RegExMatch(tmp0,"([\d]+).([\d]+).([\d]+).([\d]+)",tmp)
        Calc_Time:=(tmp1*3600+tmp2*60+tmp3)*1000+tmp4
        if Old_Time
        {
          Delta_Time:=Old_Time-Calc_Time
          Average_time_Devider++
          Average_time_summ:=Average_time_summ+Delta_Time
; отметить точки с задержкой
;          if (Average_time and Delta_Time>Average_time and Old_GdipCoordX!=GdipCoordX)
;            Gdip_DrawLine(pMainHolst_G,Gdip_GrayPen3,GdipCoordX+1,0
;				      ,GdipCoordX+1,Gdip_H)
        }
        Old_Time:=Calc_Time
      }
      if (!GrafSmooth or (Old_j!=j))
      {
; линия синего графика
        Gdip_DrawLine(pMainHolst_G,Gdip_BluePen,round(GdipCoordX+Smooth_GdipCoordX+Gdip_ZoomX),Old_GdipCoordY,GdipCoordX,GdipCoordY)
        if (Gdip_ZoomX>7)
          Gdip_DrawEllipse(pMainHolst_G,Gdip_BluePen,GdipCoordX-1,GdipCoordY-1,2,2)
        if (Gdip_ZoomX<0.5)
          Gdip_DrawEllipse(pMainHolst_G,Gdip_BluePen,GdipCoordX-1,GdipCoordY-1,1,1)
        Old_GdipCoordY:=GdipCoordY
        Old_j:=j
        Smooth_GdipCoordX:=0
      }
      else
        Smooth_GdipCoordX+=1*Gdip_ZoomX
      Gdip_Time_Stamp+=Gdip_ZoomX
      if (Gdip_Time_Stamp>10*Gdip_Font_W)
      {
        Gdip_Time_Stamp:=0
; линия и текст временой шкалы
        Gdip_DrawLine(pMainHolst_G,Gdip_GrayPen2,GdipCoordX+1,0
				  ,GdipCoordX+1,Gdip_H)
        tmp0:=Monitor_value_T[i+Gdip_X_Offset]
        if tmp0
        {
          StringTrimRight,tmp0,tmp0,4
          Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,GdipCoordX+1,Gdip_H-Gdip_Font_H
      						  ,Gdip_Font_W*(strlen(tmp0)),Gdip_Font_H)
          Gdip_TextToGraphics(pMainHolst_G,tmp0,"x" GdipCoordX+1 " y" Gdip_H-Gdip_Font_H " Left cff000000 " Gdip_Font_Size,Gdip_Font)
        }
      }
; текст значения под курсором
      if (!Val_UnderGraf_Drawed)
      if (UnderGraf_mx>=GdipCoordX and UnderGraf_mx<=GdipCoordX+10)
      if (UnderGraf_my>GdipCoordY-10 and UnderGraf_my<GdipCoordY+10)
      {
        Val_UnderGraf_Drawed:=1
        tmp1:=(GdipCoordY-Gdip_Font_H)<0?0:(GdipCoordY-Gdip_Font_H)
        Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,GdipCoordX,tmp1,Gdip_Font_W*(strlen(j)),Gdip_Font_H)
        Gdip_TextToGraphics(pMainHolst_G,j,"x" GdipCoordX " y" tmp1 " Left c" Gdip_Font_Selected_Colour " " Gdip_Font_Size,Gdip_Font)
      }
      Old_GdipCoordX:=GdipCoordX
    }
    Average_time:=Average_time_summ//Average_time_Devider*2

    if (Monitorvalue>=0)
      addY:=0
    else
      addY:=-Gdip_Font_H
    Old_GdipCoordY:=abs(round((Monitorvalue/hi_lim)*(Gdip_M))+Gdip_M-Gdip_H) ;Gdip_M
    GdipCoordX:=Gdip_W-Gdip_Font_W*(strlen(Monitorvalue))
; текст текущего значения
    Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,GdipCoordX,Old_GdipCoordY+1+addY,Gdip_Font_W*(strlen(Monitorvalue)),Gdip_Font_H)
    Gdip_TextToGraphics(pMainHolst_G,Monitorvalue,"x" GdipCoordX " y" Old_GdipCoordY+1+addY " Left cff000000 " Gdip_Font_Size,Gdip_Font)

    if (Gdip_Marker_mx>0 and Gdip_Marker_mx<=Gdip_W and Gdip_Marker_my>0 and Gdip_Marker_my<=Gdip_H)
    {
      j:=Monitor_value[round((Gdip_W-Gdip_Marker_mx)/Gdip_ZoomX+Gdip_X_Offset)]
      if (j>=0)
        addY:=0
      else
        addY:=-Gdip_Font_H
      if (abs(j)<=hi_lim)
        GdipCoordY:=abs(round((j/hi_lim)*(Gdip_M))+Gdip_M-Gdip_H)
      else if (j>0)
        GdipCoordY:=0
      else if (j<0)
        GdipCoordY:=Gdip_H
; время значения выбранного мышкой
      t:=Monitor_value_T[round((Gdip_W-Gdip_Marker_mx)/Gdip_ZoomX+Gdip_X_Offset)]
      Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,Gdip_Marker_mx,1,Gdip_Font_W*(strlen(t)),Gdip_Font_H)
      Gdip_TextToGraphics(pMainHolst_G,t,"x" round(Gdip_Marker_mx) " y" 1 " Left c" Gdip_Font_Selected_Colour " " Gdip_Font_Size,Gdip_Font)
; линия и значение выбранное мышкой
      Gdip_DrawLine(pMainHolst_G,Gdip_GrayPen,Gdip_Marker_mx,0,Gdip_Marker_mx,Gdip_H)
      tmp1:=GdipCoordY+addY<Gdip_Font_H?Gdip_Font_H:GdipCoordY+addY
      Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,Gdip_Marker_mx,tmp1,Gdip_Font_W*(strlen(j)),Gdip_Font_H)
      Gdip_TextToGraphics(pMainHolst_G,j,"x" round(Gdip_Marker_mx) " y" tmp1 " Left c" Gdip_Font_Selected_Colour " " Gdip_Font_Size,Gdip_Font)
    }
  }
; отрисовать остальные графики
  if EN_Kurvendrucker
  {
    loop,% NumOfGraf
    {
      CurGN:=a_index
      if !EnableGraf%CurGN%
        continue
      Old_j:=0
      Smooth_GdipCoordX:=0
      Old_GdipCoordY:=abs(round((Monitorvalue/hi_lim)*(Gdip_M))+Gdip_M-Gdip_H) ;Gdip_M
      Val_UnderGraf_Drawed:=0
      loop,% round(Gdip_W/Gdip_ZoomX)
      {
        i:=a_index
        j:=Monitor_value%CurGN%[i+Gdip_X_Offset]
        ; out=(inval/max)*(hi_lim-lo_lim)+lo_lim
        GdipCoordX:=round(Gdip_W-(i*Gdip_ZoomX))
        if (abs(j)<=hi_lim)
          GdipCoordY:=abs(round((j/hi_lim)*(Gdip_M))+Gdip_M-Gdip_H)
        else if (j>0)
          GdipCoordY:=0
        else if (j<0)
          GdipCoordY:=Gdip_H
        if (!GrafSmooth or (Old_j!=j))
        {
  ; линия остальных графиков
          Gdip_DrawLine(pMainHolst_G,Gdip_Pen%CurGN%,round(GdipCoordX+Smooth_GdipCoordX+Gdip_ZoomX),Old_GdipCoordY,GdipCoordX,GdipCoordY)
          if (Gdip_ZoomX>7)
            Gdip_DrawEllipse(pMainHolst_G,Gdip_Pen%CurGN%,GdipCoordX-2,GdipCoordY-2,4,4)
          Old_GdipCoordY:=GdipCoordY
          Old_j:=j
          Smooth_GdipCoordX:=0
        }
        else
          Smooth_GdipCoordX+=1*Gdip_ZoomX
; текст значения под курсором
        if (!Val_UnderGraf_Drawed)
        if (UnderGraf_mx>=GdipCoordX and UnderGraf_mx<=GdipCoordX+10)
        if (UnderGraf_my>GdipCoordY-10 and UnderGraf_my<GdipCoordY+10)
        {
          Val_UnderGraf_Drawed:=1
          tmp1:=(GdipCoordY-Gdip_Font_H)<0?0:(GdipCoordY-Gdip_Font_H)
          Gdip_FillRectangle(pMainHolst_G,Gdip_Brush%CurGN%,GdipCoordX,tmp1,Gdip_Font_W*(strlen(j)),Gdip_Font_H)
          Gdip_TextToGraphics(pMainHolst_G,j,"x" GdipCoordX " y" tmp1 " Left c" Gdip_Font_Selected_Colour " " Gdip_Font_Size,Gdip_Font)
        }
      }
      if (Monitorvalue>=0)
        addY:=0
      else
        addY:=-Gdip_Font_H
      j:=Monitor_value%CurGN%[Gdip_X_Offset]
      Old_GdipCoordY:=abs(round((j/hi_lim)*(Gdip_M))+Gdip_M-Gdip_H) ;Gdip_M
      GdipCoordX:=Gdip_W-Gdip_Font_W*(strlen(j))
  ; текст текущего значения
;      Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,GdipCoordX,Old_GdipCoordY+1+addY,Gdip_Font_W*(strlen(j)),Gdip_Font_H)
;      Gdip_TextToGraphics(pMainHolst_G,j,"x" GdipCoordX " y" Old_GdipCoordY+1+addY " Left cff000000 " Gdip_Font_Size,Gdip_Font)

      if (Gdip_Marker_mx>0 and Gdip_Marker_mx<=Gdip_W and Gdip_Marker_my>0 and Gdip_Marker_my<=Gdip_H)
      {
        j:=Monitor_value%CurGN%[round((Gdip_W-Gdip_Marker_mx)/Gdip_ZoomX+Gdip_X_Offset)]
        if (j>=0)
          addY:=0
        else
          addY:=-Gdip_Font_H
        if (abs(j)<=hi_lim)
          GdipCoordY:=abs(round((j/hi_lim)*(Gdip_M))+Gdip_M-Gdip_H)
        else if (j>0)
          GdipCoordY:=0
        else if (j<0)
          GdipCoordY:=Gdip_H
; время значения выбранного мышкой
        t:=Monitor_value_T[round((Gdip_W-Gdip_Marker_mx)/Gdip_ZoomX+Gdip_X_Offset)]
        Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,Gdip_Marker_mx,1,Gdip_Font_W*(strlen(t)),Gdip_Font_H)
        Gdip_TextToGraphics(pMainHolst_G,t,"x" round(Gdip_Marker_mx) " y" 1 " Left c" Gdip_Font_Selected_Colour " " Gdip_Font_Size,Gdip_Font)
  ; линия и значение выбранное мышкой
        if (j!="")
          tmp1:=CurGN "=" j
        else
          tmp1:=""
        Gdip_DrawLine(pMainHolst_G,Gdip_GrayPen,Gdip_Marker_mx,0,Gdip_Marker_mx,Gdip_H)
        Gdip_FillRectangle(pMainHolst_G,Gdip_Brush%CurGN%,Gdip_Marker_mx,Gdip_Font_H*CurGN,Gdip_Font_W*(strlen(tmp1)),Gdip_Font_H)
        Gdip_TextToGraphics(pMainHolst_G,tmp1,"x" round(Gdip_Marker_mx) " y" Gdip_Font_H*CurGN " Left c" Gdip_Font_Selected_Colour " " Gdip_Font_Size,Gdip_Font)
      }
    }
      Gdip_Time_Stamp:=0
      loop,% round(Gdip_W/Gdip_ZoomX)
      {
        i:=a_index
        Gdip_Time_Stamp+=Gdip_ZoomX
        if (Gdip_Time_Stamp>10*Gdip_Font_W)
        {
          GdipCoordX:=round(Gdip_W-(i*Gdip_ZoomX))
          Gdip_Time_Stamp:=0
          Gdip_DrawLine(pMainHolst_G,Gdip_GrayPen2,GdipCoordX+1,0
  				  ,GdipCoordX+1,Gdip_H)
          tmp0:=Monitor_value_T[i+Gdip_X_Offset]
          if tmp0
          {
  ; текст временой шкалы
            StringTrimRight,tmp0,tmp0,4
            Gdip_FillRectangle(pMainHolst_G,Gdip_WhiteBrush2,GdipCoordX+1,Gdip_H-Gdip_Font_H
        						  ,Gdip_Font_W*(strlen(tmp0)),Gdip_Font_H)
            Gdip_TextToGraphics(pMainHolst_G,tmp0,"x" GdipCoordX+1 " y" Gdip_H-Gdip_Font_H " Left cff000000 " Gdip_Font_Size,Gdip_Font)
          }
        }
      }
  }

GrafUpdate:
  ; Обновить отображение
  ; Скопировать отрисованный график
;  if (a_osversion~="10")                ???????????????????try on real system?????????????????????????????????
;    GuiControlGet,Gdip_,Pos,% hGrafPos
;  else
  Gdip_X:=Gdip_Y:=Gdip_W:=Gdip_H:=0
  wingetpos,Gdip_X,Gdip_Y,Gdip_W,Gdip_H,% "ahk_id" hGrafPos
  if (Gdip_X=0 or Gdip_Y=0 or Gdip_W=0 or Gdip_H=0)
    GuiControlGet,Gdip_,Pos,% hGrafPos
  Gdip_M:=Gdip_H//2
  if (EN_MonitorVar or EN_Kurvendrucker)
    UpdateLayeredWindow(hGraf,pMainHolst,Gdip_X,Gdip_Y,Gdip_W,Gdip_H+1)
  else
    UpdateLayeredWindow(hGraf,pMainHolst,Gdip_X,Gdip_Y,Gdip_W,Gdip_H+1,0)
  DrawTime:=a_tickcount-StartDrawTime
;tooltip,% DrawTime "`n" Monitor_value_T.maxindex(),500,0,9
  UnderGraf_MouseMove:=0
  return

GuiSize:
  if 2ndThread
    return
  if (!Old_GuiWidth or !Old_GuiHeight)
  {
    Old_GuiWidth:=A_GuiWidth
    Old_GuiHeight:=A_GuiHeight
  }
  if (A_EventInfo=1)
    return
  DeltaGuiWidth:=A_GuiWidth-Old_GuiWidth
  DeltaGuiHeight:=A_GuiHeight-Old_GuiHeight
  MoveControl(hGroupBufer,,,DeltaGuiWidth,DeltaGuiHeight)
  MoveControl(hClButton,,DeltaGuiHeight,DeltaGuiWidth)
  MoveControl(hTtEButton,,DeltaGuiHeight)
  loop,% MaxNumOfGraf+1
  {
    i:=a_index-1
    MoveControl(hECB%i%,,DeltaGuiHeight)
  }
  gosub,ControlSize
  if (EN_Kurvendrucker or EN_MonitorVar)
    gosub,GrafRedraw
;tooltip,% "dw" DeltaGuiWidth "`ndh" DeltaGuiHeight "`nw" A_GuiWidth "`nh" A_GuiHeight
  Old_GuiWidth:=A_GuiWidth
  Old_GuiHeight:=A_GuiHeight
  return

ControlSize:
  GuiControlGet,G_pos,Pos,% hGroupBufer
  if (EN_Kurvendrucker)
  {
;    hGrafPos_over:=MoveControl(hGrafPos,,,DeltaGuiWidth,DeltaGuiHeight)
;    E_Text_over:=MoveControl(E_Text,,,DeltaGuiWidth,((hGrafPos_over&2)?DeltaGuiHeight:0))
;    MoveControl(E_SLV,,,DeltaGuiWidth,((hGrafPos_over&2)?DeltaGuiHeight:0))
;    hProgress2_over:=MoveControl(hProgress2,,((hGrafPos_over&2)?DeltaGuiHeight:0),DeltaGuiWidth)
;
;    MoveControl(E_LV,0xffff,0xffff,((E_Text_over&1)?DeltaGuiWidth:0),DeltaGuiHeight)
;    MoveControl(hProgress,((E_Text_over&1)?DeltaGuiWidth:0),,,DeltaGuiHeight)

    GuiControlGet,P_pos,Pos,% hProgress
    tmp0:=(G_posw-20)
    tmp1:=P_posy+((P_posh/1000*ShiftSpaceY))
    GuiControl,Move,% hProgress2,% " x" (G_posx+10) " y" tmp1 " w" tmp0
    GuiControlGet,S_pos,Pos,% hProgress2

    GuiControl,Move,% E_Text,% " x" (G_posx+10) " y" P_posy " w" tmp0 " h" (S_posy-P_posy)
    GuiControl, Move,% E_SLV,% " x" (G_posx+10) " y" P_posy " w" tmp0 " h" (S_posy-P_posy)
    GuiControl,Move,% hGrafPos,% " x" (G_posx+10) " y" (tmp1+20) " w" tmp0 " h" (P_posy+(G_posh-107)-S_posy-S_posh)

    gosub,GrafUpdate
  }
  else
  {
    GuiControl,Move,% hProgress,% " x" (G_posx+(G_posw/1000*ShiftSpaceX)) " y" (G_posy+65) " h" (G_posh-107)
    GuiControlGet,P_pos,Pos,% hProgress
    tmp0:=(P_posx+P_posw)
    tmp1:=P_posy+((P_posh/1000*ShiftSpaceY))
    tmp2:=(G_posx+G_posw-P_posx-30)
    GuiControl,Move,% hProgress2,% " x" tmp0 " y" tmp1 " w" tmp2
    GuiControlGet,S_pos,Pos,% hProgress2
    tmp3:=(S_posy-P_posy)
    GuiControl,Move,% E_LV,% " x" (G_posx+10) " y" P_posy " w" (P_posx-G_posx-10) " h" P_posh
    GuiControl,Move,% E_Text,% " x" tmp0 " y" P_posy " w" tmp2 " h" tmp3
    GuiControl, Move,% E_SLV,% " x" tmp0 " y" P_posy " w" tmp2 " h" tmp3
    GuiControl,Move,% hGrafPos,% " x" tmp0 " y" (tmp1+20) " w" tmp2 " h" (P_posy+P_posh-S_posy-S_posh)
    if (EN_Kurvendrucker or EN_MonitorVar)
      gosub,GrafRedraw
;;    E_Text_over:=MoveControl(E_Text,,0xffff,DeltaGuiWidth,DeltaGuiHeight)
;;    MoveControl(E_SLV,,0xffff,DeltaGuiWidth,DeltaGuiHeight)
;;    hProgress2_over:=MoveControl(hProgress2,,(!(E_Text_over&2)?DeltaGuiHeight:0),DeltaGuiWidth)
;;    hGrafPos_over:=MoveControl(hGrafPos,,(!(E_Text_over&2)?DeltaGuiHeight:0),DeltaGuiWidth,((E_Text_over&2)?DeltaGuiHeight:0),0)
;;    MoveControl(E_LV,0xffff,0xffff,((E_Text_over&1)?DeltaGuiWidth:0),DeltaGuiHeight)
;;    MoveControl(hProgress,((E_Text_over&1)?DeltaGuiWidth:0),,,DeltaGuiHeight)
  }
  return

RemoveTooltip:
  tooltip
  return

___________Hotkeys:
return

LButton_Label:
  mousegetpos,mx,my,mw,mc
  oldmx:=mx
  oldmy:=my
  oldmc:=mc
  oldmw:=mw
  if (mw=hGraf)
  {
    Gdip_Marker_mx:=mx-Gdip_X
    Gdip_Marker_my:=my-Gdip_Y
  }
  if (MouseAbovehMain)
  {
    ControlGet,hElement,hwnd,,%mc%,ahk_id %mw%
    if (hElement=hProgress)
      EditResize:=1
    if (hElement=hProgress2)
      EditResize:=2
    if (hElement=E_LV or hElement=E_LV2)
      ControlFocus,,ahk_id %hElement%
  }
  if ((Analyzer and AnBusy) or TurnBack or FileLoading or FileSaving)
  if (substr(a_priorhotkey,1,9)=a_thishotkey and a_timesincepriorhotkey<DllCall("GetDoubleClickTime"))
    StopAnalyze:=1
  return

LButton_up_Label:
  EndResizing:
  EditResize:=0
  if (a_tickcount-StartDrawTime>500)
    gosub,GrafRedraw
  return

RButton_Label:
  mousegetpos,mx,my,mw,mc
  ControlGet,hElement,hwnd,,%mc%,ahk_id %mw%
  if (mw=hGraf)
    menu,GrafRightClick,show
  if (hElement=hBufinfo)
    menu,ListRightClick2,show
  if (hElement=hProgress2)
  {
    if (a_osversion~="10")
      GuiControlGet,E_pos,Pos,% hProgress2
    else
      wingetpos,E_posX,E_posY,E_posW,E_posH,% "ahk_id" hProgress2
    ClickPos:=(E_posw-(mx-E_posx))/E_posw*100
    tmp1:=round(Gdip_Points_Num-Gdip_W/Gdip_ZoomX)
    Gdip_X_Offset:=ClickPos*tmp1/100
    if (a_tickcount-StartDrawTime>500)
      gosub,GrafRedraw
  }
  loop,% MaxNumOfGraf
  {
    i:=a_index
    if (hElement=hECB%i%)
    {
      if (EnableGraf%i%)
      {
        loop,% MaxNumOfGraf
          if (a_index!=i)
            GuiControl,,EnableGraf%a_index%,0
      }
      else
      {
        loop,% MaxNumOfGraf
          GuiControl,,EnableGraf%a_index%,1
      }
      gosub,submiting
      break
    }
  }
  return


Right_Label:
  if WinActive("Ahk_id " hGraf)
  {
    if getkeystate("Ctrl","P")
      Gdip_X_Offset-=20
    Gdip_Marker_mx+=20
    if (a_tickcount-StartDrawTime>500)
      gosub,GrafRedraw
  }
  return

Left_Label:
  if WinActive("Ahk_id " hGraf)
  {
    if getkeystate("Ctrl","P")
      Gdip_X_Offset+=5
    Gdip_Marker_mx-=5
    if (a_tickcount-StartDrawTime>500)
      gosub,GrafRedraw
  }
  return

ZoomIn:
sc00D_Label:
WheelUp_Label:
  if !((MouseAbovehGraf or a_thislabel="ZoomIn") and (EN_MonitorVar or EN_Kurvendrucker))
    return
  if (Gdip_ZoomX>=1)
    Gdip_ZoomX+=1
  else
    Gdip_ZoomX+=0.1
  if (Gdip_ZoomX<0.5)
  {
    SaveGrafSmooth:=GrafSmooth
    GrafSmooth:=false
  }
  else
    GrafSmooth:=SaveGrafSmooth
  if (Gdip_ZoomX>10)
    Gdip_ZoomX:=10
  Gdip_ZoomY+=1
  if (Gdip_ZoomY>10)
    Gdip_ZoomY:=10
  if (a_tickcount-StartDrawTime>500)
    gosub,GrafRedraw
  return

ZoomOut:
sc00C_Label:
WheelDown_Label:
  if !((MouseAbovehGraf or a_thislabel="ZoomOut") and (EN_MonitorVar or EN_Kurvendrucker))
    return
  if (Gdip_ZoomX>1)
    Gdip_ZoomX-=1
  else
    Gdip_ZoomX-=0.1
  if (Gdip_ZoomX<0.1)
    Gdip_ZoomX:=0.1
  if (Gdip_ZoomX<0.5)
  {
    SaveGrafSmooth:=GrafSmooth
    GrafSmooth:=false
  }
  else
    GrafSmooth:=SaveGrafSmooth
  Gdip_ZoomY-=1
  if (Gdip_ZoomY<1)
    Gdip_ZoomY:=1
  if (a_tickcount-StartDrawTime>500)
    gosub,GrafRedraw
  return

Up_Label:
Down_Label:
  if (a_timesincepriorhotkey>100)
  {
    GuiControlGet,F,FocusV
    if (F="_SecondList_" or F="_List_")
    {
      tmp0:=F="_List_"?E_LV:E_SLV
      UpDown_Label:=1
      Gui,1:ListView,% tmp0
;      GuiControl,-Redraw,% tmp0
      SelBlockNow:=LV_GetNext()
      loop,30000
      {
        Gui,1:ListView,% tmp0
        tmp1:=(a_thislabel="Up_Label")?SelBlockNow-a_index:SelBlockNow+a_index
        if (tmp1>LV_GetCount() or a_timesincepriorhotkey<=100)
          break
        LV_GetText(SelBlock,tmp1,COLUMN_HOOK)
        if (SelBlock=" " or tmp1=1)
        {
          sleep,10
          keywait,% (a_thislabel="Up_Label")?"Up":"Down",L
          LV_Modify(0,"-Select")
          LV_Modify(tmp1,"Select")
          LV_Modify((a_thislabel="Up_Label")?tmp1:tmp1+30,"Vis Focus")
          LV_Modify(tmp1+1,"Vis Focus")
;tooltip,% a_timesincepriorhotkey
          break
        }
      }
      GuiControl,+Redraw,% tmp0
      Gui,1:ListView,% E_LV
      UpDown_Label:=0
    }
  }
  return

CheckPIDExist(PID)
{
  process,exist,% PID
  if !errorlevel
    gosub,Exit
}

Esc_Label:
EXIT:
GuiClose:
GuiCancel:
GuiEscape:
  DllCall("SystemParametersInfo","UInt",0x57,"UInt",0,Ptr,"","UInt",0)
  DC_ARROW := DllCall("CopyImage",Ptr,IDC_ARROW,"UInt",0x2,"Int",0,"Int",0,"UInt",0)
  DllCall("SetSystemCursor",Ptr,DC_ARROW,"UInt",32512)
  if Gdip_Initialised
  {
    Gdip_DeletePen(Gdip_WhitePen)
    Gdip_DeletePen(Gdip_GrayPen)
    Gdip_DeletePen(Gdip_GrayPen2)
    Gdip_DeletePen(Gdip_BluePen)
    Gdip_DeletePen(Gdip_BlackPen)
    loop,% MaxNumOfGraf
      Gdip_DeletePen(Gdip_Pen%a_index%)
    Gdip_DeleteBrush(Gdip_WhiteBrush)
    Gdip_DeleteBrush(Gdip_WhiteBrush2)
    Gdip_DeleteBrush(Gdip_BlackBrush)
    SelectObject(pMainHolst,Gdip_obm2), DeleteObject(Gdip_hbm2), DeleteDC(pMainHolst), Gdip_DeleteGraphics(pMainHolst_G)
    Gdip_Shutdown(Gdip_Initialised)
  }
  RS232_Close(RS232_FileHandle)
  exitapp

; ////////// отработанные линии________ctrl+Shift+win+alt+L ____________________HOTKEYS_________________________
vk4c_Label:
  if !((WinActive("ahk_id " hMainWin) or WinActive("ahk_id " hGraf)))
    return
  ListLines
  return
#if

GetPointer(wParam,lParam)
{
  global
  slPointer:=lParam
}

WM_MOVING()
{
  global
  if 2ndThread
    return
  if (EN_MonitorVar or EN_Kurvendrucker)
    gosub,GrafUpdate
}

WM_MOUSEMOVE:
  if 2ndThread
    return
  mousegetpos,mx,my,mw,mc
  if (mx!=oldmx or my!=oldmy)
  {
    Deltamx:=mx-oldmx
    Deltamy:=my-oldmy
    wingetpos,Gdip_X_,Gdip_Y_,Gdip_W_,Gdip_H_,% "ahk_id" hGrafPos
    UnderGraf_mx:=mx-Gdip_X_
    UnderGraf_my:=my-Gdip_Y_
    UnderGraf_MouseMove:=1
    MouseAbovehGraf:=(mw=hGraf)
    MouseAbovehMain:=(mw=hMainWin)
    if (MouseAbovehMain and oldmc!=mc)
    {
      ControlGet,hElement,hwnd,,%mc%,ahk_id %mw%
      if !E%hElement%
        SB_SetText(sended)
      else
        SB_SetText(E%hElement%)
    }
    if (MouseAbovehMain or MouseAbovehGraf)
    {
      ControlGet,hElement,hwnd,,%mc%,ahk_id %mw%
      if (!ChangeCursorOnce and hElement=hProgress)
      {
        ChangeCursorOnce=1
        DllCall("SystemParametersInfo","UInt",0x57,"UInt",0,"UInt","","UInt",0)
        DllCall("SetSystemCursor",Ptr,IDC_SIZEWE,"UInt",32512)
      }
      if (!ChangeCursorOnce2 and hElement=hProgress2)
      {
        ChangeCursorOnce2=1
        DllCall("SystemParametersInfo","UInt",0x57,"UInt",0,"UInt","","UInt",0)
        DllCall("SetSystemCursor",Ptr,IDC_SIZEALL,"UInt",32512)
      }
    }
    if (ChangeCursorOnce or ChangeCursorOnce2) and ((hElement!=hProgress and hElement!=hProgress2) or !MouseAbovehMain)
    {
      ChangeCursorOnce=0
      ChangeCursorOnce2=0
      DllCall("SystemParametersInfo","UInt",0x57,"UInt",0,Ptr,"","UInt",0)
      DC_ARROW := DllCall("CopyImage",Ptr,IDC_ARROW,"UInt",0x2,"Int",0,"Int",0,"UInt",0)
      DllCall("SetSystemCursor",Ptr,DC_ARROW,"UInt",32512)
    }
    if (EditResize)
    {
      if EN_Kurvendrucker
        Deltamx:=0
      GuiControlGet,E_pos,Pos,% E_LV
      GuiControlGet,T_pos,Pos,% E_Text
      if !((E_posw+Deltamx)<10 or (T_posw-Deltamx)<10)
      {
        MoveControl(E_LV,,,Deltamx,,0)
        MoveControl(hProgress,Deltamx,,,,0)
        MoveControl(hProgress2,Deltamx,,-Deltamx,,0)
        MoveControl(E_Text,Deltamx,,-Deltamx,,0)
        MoveControl(E_SLV,Deltamx,,-Deltamx,,0)
        MoveControl(hGrafPos,Deltamx,,-Deltamx,,0)
        if (Deltamx>0)
          Gdip_X_Offset-=Deltamx*2
;        gosub,GrafUpdate
      }
      if (EditResize=2)
      {
        GuiControlGet,T_pos,Pos,% hGrafPos
        if !((E_posh+Deltamy)<10 or (T_posh-Deltamy)<2)
        {
          ov:=MoveControl(E_Text,,0xffff,,Deltamy,2)
          if !ov
          {
            MoveControl(E_SLV,,,,Deltamy,0)
            MoveControl(hProgress2,,Deltamy,,,0)
            MoveControl(hGrafPos,,Deltamy,,-Deltamy,2)
;            gosub,GrafUpdate
          }
        }
      }
      GuiControlGet,G_pos,Pos,% hGroupBufer
      GuiControlGet,P_pos,Pos,% hProgress
      GuiControlGet,S_pos,Pos,% hProgress2
      ShiftSpaceX:=round((P_posx-G_posx)/(G_posw)*1000)
      ShiftSpaceY:=round((S_posy-P_posy)/(P_posh)*1000)
      gosub,ControlSize
    }
    Deltamx:=(mx-oldmx)*((3-Gdip_ZoomX<1) ? 1 : 3-Gdip_ZoomX)
    Deltamy:=(my-oldmy)*((3-Gdip_ZoomY<1) ? 1 : 3-Gdip_ZoomY)
    if (MouseAbovehGraf and getkeystate("LButton","P"))
    {
      Gdip_Y_Offset-=Deltamy
      Gdip_X_Offset+=Deltamx
      Gdip_Marker_mx+=Deltamx
      if (a_tickcount-StartDrawTime>500)
        gosub,GrafRedraw
    }
    if (MouseAbovehGraf and (oldmx!=mx or oldmy!=my) and a_tickcount-StartDrawTime>500)
    {
      SB_SetText("Дополнительное меню правой кнопкой мыши. Масштаб = " round(Gdip_ZoomX,1) ". Максимальное значение = " hi_lim ". Время отрисовки кадра = " DrawTime "ms. Число точек = " Monitor_value_T.maxindex() ".")
      gosub,GrafRedraw
    }
  }
  oldmx:=mx
  oldmy:=my
  oldmc:=mc
  oldmw:=mw
  if (MouseAbovehGraf and (EN_MonitorVar or EN_Kurvendrucker))
    SB_SetText("Дополнительное меню правой кнопкой мыши. Масштаб = " round(Gdip_ZoomX,1) ". Максимальное значение = " hi_lim ". Время отрисовки кадра = " DrawTime "ms. Число точек = " Monitor_value_T.maxindex() ".")
  return

GetClientPos(hwnd)
{
  VarSetCapacity(pwi, 60, 0), NumPut(60, pwi, 0, "UInt")
  DllCall("GetWindowInfo", Ptr, hwnd, "UInt", &pwi)
  top := NumGet(pwi, 24, "Int") - NumGet(pwi, 8, "Int")
  left := NumGet(pwi, 52, "Int")
  w := NumGet(pwi, 28, "Int") - NumGet(pwi, 20, "Int")
  h := NumGet(pwi, 32, "Int") - NumGet(pwi, 24, "Int")
  return {"top":top,"left":left,"w":w,"h":h}
}

CRC16(byref data,size,summ=0,Polynom=0x8005,Init=0xFFFF,RefIn=1,RefOut=1,XorOut=0,ReversPolynom=0)
{
  if !ReversPolynom
    Polynom:=ReverseWord(Polynom)
  if !summ
  {
    z:=Init
    loop,% size
    {
      In:=isobject(data)?data[a_index]:numget(data,a_index-1,"uchar")
      if !RefIn
        In:=ReverseBits(In)
      z^=In
      loop,8
      {
        r:=z&1
        z>>=1
        if r
          z^=Polynom
      }
    }
    if !RefOut
      z:=ReverseWord(z)
    return z^XorOut
  }
  else
  {
    loop,% size
      sum+=isobject(data)?data[a_index]:numget(data,a_index-1,"uchar")
    return sum
  }
}
ReverseBits(byte)
{
  tmp0:=0
  loop,8
  {
    tmp0<<=1
    tmp0|=byte&1
    byte>>=1
  }
  byte:=tmp0
  return byte
}
ReverseWord(word)
{
  return (ReverseBits(word&0xFF)<<8)|ReverseBits((word&0xFF00)>>8)
}

CRC_RTU(byref data,size)
{
  z:=0xFFFF
  loop,% size
  {
    In:=isobject(data)?data[a_index]:numget(data,a_index-1,"uchar")
    z^=In
    loop,8
    {
      r:=z&1
      z>>=1
      if r
        z^=0xA001
    }
  }
  a:=z//256
  b:=mod(z,256)
  return (a<<8)|b
  ;1) y=65535;
  ;2) загрузить в переменную x первый байт из потока данных;
  ;3) вычислить переменную z = ИСКЛЮЧАЮЩЕЕ ИЛИ y c байтом потока данных x.
  ;4) сдвинуть переменную z на один бит вправо
  ;5) если сдвинутый бит равен 1 то вычислить переменную z:
  ;z= ИСКЛЮЧАЮЩЕЕ ИЛИ z с числом 0xA001
  ;6) повторять шаг 4 и шаг 5 еще 7 раз.
  ;7) y=z
  ;8) если все байты из потока данных обработаны перейти на шаг 10
  ;9) перейти на шаг 2
  ;10) вычислить первый и второй байты CRC16-кода a и b.
  ;a=z/256(нацело)
  ;b= остаток от деления z/256.
}

SwapByte(var)
{
  tmp1:=(var<<8)&0xff00
  tmp2:=(var>>8)
  return (tmp1|tmp2)
}

SearchCOM(name,list=0)
{
  Loop,HKLM,HARDWARE\DEVICEMAP\SERIALCOMM\
  {
    RegRead,COMPort
    FriendlyName:=""
    Loop,HKLM,SYSTEM\CurrentControlSet\Enum,1,1
    {
      if (A_LoopRegName="PortName")
      {
        RegRead,Outputvar
        if (Outputvar=COMPort)
        {
          RegRead,FriendlyName,% A_LoopRegKey,% RegExReplace(A_LoopRegSubKey, "(.*)\\Device Parameters", "$1"),FriendlyName
          if (!list and InStr(FriendlyName,name))
            return COMPort
        }
      }
    }
    if (FriendlyName="")
      FriendlyName:=A_LoopRegName
    if list
      out.=COMPort "`t" FriendlyName "`n"
    else if (InStr(A_LoopRegName,name))
      return COMPort
  }
  if list
    return out
}

RS232_Get_FileHandle(RS232_Port="COM1",RS232_Baud=9600,RS232_Parity="N",RS232_Data=8,RS232_Stop=1,ReadIntervalTimeout="")
{
; COMx[:][baud=b][parity=p][data=d][stop=s][to={on|off}][xon={on|off}][odsr={on|off}][octs={on|off}][dtr={on|off|hs}][rts={on|off|hs|tg}][idsr={on|off}]
  RS232_Settings=%RS232_Port%:baud=%RS232_Baud% parity=%RS232_Parity% data=%RS232_Data% stop=%RS232_Stop% to=off xon=off odsr=off octs=off dtr=Off rts=off idsr=off

  ;###### Extract/Format the RS232 COM Port Number ######
  ;7/23/08 Thanks krisky68 for finding/solving the bug in which RS232 COM Ports greater than 9 didn't work.
  StringSplit, RS232_Temp, RS232_Settings, `:
  RS232_Temp1_Len := StrLen(RS232_Temp1)  ;For COM Ports > 9 \\.\ needs to prepended to the COM Port name.
  If (RS232_Temp1_Len > 4)                ;So the valid names are
    RS232_COM = \\.\%RS232_Temp1%         ; ... COM8  COM9   \\.\COM10  \\.\COM11  \\.\COM12 and so on...
  Else
    RS232_COM = %RS232_Temp1%

  ;8/10/09 A BIG Thanks to trenton_xavier for figuring out how to make COM Ports greater than 9 work for USB-Serial Dongles.
  StringTrimLeft, RS232_Settings, RS232_Settings, RS232_Temp1_Len+1 ;Remove the COM number (+1 for the semicolon) for BuildCommDCB.
  ;MsgBox, RS232_COM=%RS232_COM% `nRS232_Settings=%RS232_Settings%

  ;###### Build RS232 COM DCB ######
  ;Creates the structure that contains the RS232 COM Port number, baud rate,...
  VarSetCapacity(DCB, 28, 0)
  BCD_Result := DllCall("BuildCommDCB"
       ,"str" , RS232_Settings ;lpDef
       ,Ptr, &DCB)        ;lpDCB
;  If (!BCD_Result)
;  {
;    MsgBox, There is a problem with Serial Port communication. `nFailed Dll BuildCommDCB, BCD_Result=%BCD_Result% `nThe Script Will Now COMFail.`n %DCB%
;    COMFail=1
;    return
;  }

  ;###### Create RS232 COM File ######
  ;Creates the RS232 COM Port File Handle
  RS232_FileHandle := DllCall("CreateFile"
       ,"Str" , RS232_COM     ;File Name
       ,"UInt", 0xC0000000   ;Desired Access
       ,"UInt", 3            ;Safe Mode
       ,"UInt", 0            ;Security Attributes
       ,"UInt", 3            ;Creation Disposition
       ,"UInt", 0            ;Flags And Attributes
       ,Ptr, 0            ;Template File
       ,Ptr)

  If (!RS232_FileHandle)
  {
;;    MsgBox, There is a problem with Serial Port communication. `nFailed Dll CreateFile, RS232_FileHandle=%RS232_FileHandle% `nThe Script Will Now COMFail.
    COMFail=1
    return
  }

  ;###### Set COM State ######
  ;Sets the RS232 COM Port number, baud rate,...
  SCS_Result := DllCall("SetCommState"
       ,Ptr, RS232_FileHandle ;File Handle
       ,Ptr, &DCB)          ;Pointer to DCB structure
  If (!SCS_Result)
  {
;;    MsgBox, There is a problem with Serial Port communication. `nFailed Dll SetCommState, SCS_Result=%SCS_Result% `nThe Script Will Now COMFail.
    RS232_Close(RS232_FileHandle)
    COMFail=1
    return
  }
  Timeout:=Floor(((RS232_Data+4)/RS232_Baud)*1000)
  if (Timeout=0)
    Timeout:=1
  ;###### Create the SetCommTimeouts Structure ######
  if (ReadIntervalTimeout=0)
    ReadIntervalTimeout:=1
  if (ReadIntervalTimeout="")
    ReadIntervalTimeout      := Timeout    ; ms, max Interval betwen characters
  ReadTotalTimeoutMultiplier := 0          ; ms * reqested number of bytes
  ReadTotalTimeoutConstant   := ReadIntervalTimeout          ; ms
  WriteTotalTimeoutMultiplier:= 0          ; ms * sended number of bytes
  WriteTotalTimeoutConstant  := 0          ; ms
;tooltip,% RS232_Baud "`n" ReadIntervalTimeout "`n" (((RS232_Data+4)/RS232_Baud)*1000) "`n" ReadTotalTimeoutConstant,0,500,5

  if (ReadTotalTimeoutConstant>500)
    ReadTotalTimeoutConstant:=500

  VarSetCapacity(Data, 20, 0) ; 5 * sizeof(DWORD)
  NumPut(ReadIntervalTimeout,         Data,  0, "UInt")
  NumPut(ReadTotalTimeoutMultiplier,  Data,  4, "UInt")
  NumPut(ReadTotalTimeoutConstant,    Data,  8, "UInt")
  NumPut(WriteTotalTimeoutMultiplier, Data, 12, "UInt")
  NumPut(WriteTotalTimeoutConstant,   Data, 16, "UInt")

  ;###### Set the RS232 COM Timeouts ######
  SCT_result := DllCall("SetCommTimeouts"
     ,Ptr, RS232_FileHandle ;File Handle
     ,Ptr, &Data)         ;Pointer to the data structure
  If !SCT_result
  {
;;    MsgBox, There is a problem with Serial Port communication. `nFailed Dll SetCommState, SCT_result=%SCT_result% `nThe Script Will Now COMFail.
    RS232_Close(RS232_FileHandle)
    COMFail=1
    return
  }
  Return RS232_FileHandle
}

RS232_Close(ByRef RS232_FileHandle)
{
  DllCall("CloseHandle",Ptr,RS232_FileHandle)
  RS232_FileHandle:=""
  return
}

RS232_Write(RS232_FileHandle,Data,Data_Length)
{
  if RS232_FileHandle
  WF_Result := DllCall("WriteFile"
       ,Ptr , RS232_FileHandle ;File Handle
       ,Ptr , Data          ;Pointer to string to send
       ,"UInt", Data_Length    ;Data Length
       ,A_PtrSize ? "UPtr*" : "uint*", Bytes_Sent     ;Returns pointer to num bytes sent
       ,"Int" , "NULL")
;  RS232_WaitCommEvent(RS232_FileHandle,0x4)
}

RS232_Read(RS232_FileHandle,Data_Length,Pointer,ByRef RS232_Bytes_Received)
{
  if RS232_FileHandle
  Read_Result := DllCall("ReadFile"
       ,Ptr , RS232_FileHandle   ; hFile
       ,Ptr  , Pointer             ; lpBuffer
       ,"Int"  , Data_Length        ; nNumberOfBytesToRead
       ,A_PtrSize ? "UPtr*" : "uint*", RS232_Bytes_Received   ; lpNumberOfBytesReceived
       ,"Int"  , 0)               ; lpOverlapped
  If (!Read_Result)
  {
    RS232_Close(RS232_FileHandle)
    COMFail=1
    return
  }
}

RS232_WaitCommEvent(RS232_FileHandle,Event=0x3ff)
{
/*
Events:
EV_BREAK:=0x0040 ;A break was detected on input.
EV_CTS:=0x0008 ;The CTS (clear-to-send) signal changed state.
EV_DSR:=0x0010 ;The DSR (data-set-ready) signal changed state.
EV_ERR:=0x0080 ;A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
EV_RING:=0x0100 ;A ring indicator was detected.
EV_RLSD:=0x0020 ;The RLSD (receive-line-signal-detect) signal changed state.
EV_RXCHAR:=0x0001 ;A character was received and placed in the input buffer.
EV_RXFLAG:=0x0002 ;The event character was received and placed in the input buffer. The event character is specified in the device's DCB structure, which is applied to a serial port by using the SetCommState function.
EV_TXEMPTY:=0x0004 ;The last character in the output buffer was sent.
*/
  MaskSet := DllCall("SetCommMask"
       ,Ptr , RS232_FileHandle ;File Handle
       ,"int" , Event)          ;Mask
  if !MaskSet
  {
    RS232_Close(RS232_FileHandle)
    COMFail=1
    return
  }
  VarSetCapacity(Data,4,0)
  if RS232_FileHandle
  Success := DllCall("WaitCommEvent"
       ,Ptr, RS232_FileHandle ;File Handle
       ,Ptr, &Data          ;Pointer to Event
       ,Ptr, 0)
  if !Success
  {
    RS232_Close(RS232_FileHandle)
    COMFail=1
    return
  }
;  tooltip,% Errorlevel "`n" a_lasterror "`n" Success "`nEvent = " format("0x{:04x}",numget(Data,0,"Int64"))
}

RS232_ClearRXBuffer(RS232_FileHandle)
{
  Success := DllCall("PurgeComm"
       ,Ptr, RS232_FileHandle ;File Handle
       ,"Int", 0x8)
  return Success
}

LV_SubitemHitTest(HLV) {
   ; To run this with AHK_Basic change all DllCall types Ptr to "UInt", please.
   ; HLV - ListView's HWND
   LVM_SUBITEMHITTEST := 0x1039
   VarSetCapacity(POINT, 8, 0)
   ; Get the current cursor position in screen coordinates
   DllCall("User32.dll\GetCursorPos", Ptr, &POINT)
   ; Convert them to client coordinates related to the ListView
   DllCall("User32.dll\ScreenToClient", Ptr, HLV, Ptr, &POINT)
   ; Create a LVHITTESTINFO structure (see below)
   VarSetCapacity(LVHITTESTINFO, 24, 0)
   ; Store the relative mouse coordinates
   NumPut(NumGet(POINT, 0, "Int"), LVHITTESTINFO, 0, "Int")
   NumPut(NumGet(POINT, 4, "Int"), LVHITTESTINFO, 4, "Int")
   ; Send a LVM_SUBITEMHITTEST to the ListView
   SendMessage, LVM_SUBITEMHITTEST, 0, &LVHITTESTINFO, , ahk_id %HLV%
   ; If no item was found on this position, the return value is -1
   If (ErrorLevel = -1)
      Return 0
   ; Get the corresponding subitem (column)
   Subitem := NumGet(LVHITTESTINFO, 16, "Int") + 1
   Return Subitem
}

SetParent(hwnd,hNewParent,ClassNN="",x=0,y=0)
{
  if ClassNN
    ControlGet,hNewParent,Hwnd,,%ClassNN%,ahk_id %hNewParent%
  oldParent:=DllCall("SetParent",Ptr,hwnd,Ptr,hNewParent)
  WinMove,ahk_id %hwnd%,,%x%,%y%
  return oldParent
}

StrCut(byref string,sym)
{
  if Pos:=instr(string,sym)
  {
    stringleft,var,string,Pos-1
    stringtrimleft,string,string,Pos
    return var
  }
}

findMetaSbl(intext,byref Splitter,byref Ending)
{
  a:={}
  loop,parse,intext
  {
    if !(a_loopfield+1 or a_loopfield="-" or a_loopfield=".")
    {
      SmblExist:=0
      for i,n in a
      {
        if (i=a_loopfield)
        {
          SmblExist:=1
          a[i]++
          break
        }
      }
      if !SmblExist
        a[a_loopfield]:=1
    }
  }
  countS:=0
  countE:=32000
  for i,n in a
  {
    if (n>countS)
    {
      Splitter:=i
      countS:=n
    }
    if (n<countE)
    {
      Ending:=i
      countE:=n
    }
  }
}

CheckMetaSym(sym)
{
  static Meta:="\`n.*?+[{|()^$"
  if instr(Meta,sym)
    loop,parse,Meta
    {
      if (a_loopfield="`n" and instr(sym,a_loopfield))
        sym:="\n"
      else
        stringreplace,sym,sym,%a_loopfield%,\%a_loopfield%,all
    }
  return sym
}

AddAdd(byref var)
{
  if (var>=0)
    var:="+"var
}

SwapData(var_in,StartByte,NumBytes,byref var_out)
{
  varsetcapacity(var_out,NumBytes)
  loop,% NumBytes
    numput(numget(var_in,StartByte+a_index-1,"uchar"),var_out,NumBytes-a_index,"uchar")
}

CopyData(var_in,StartByte,NumBytes,var_out,out_StartByte)
{
  loop,% NumBytes
    numput(numget(var_in,StartByte+a_index-1,"uchar"),var_out,out_StartByte+a_index-1,"uchar")
}

ArrGetMax(arr,start=0,weight=1)
{
  start:=round(start)
  weight:=round(weight)
  loop,% weight
  {
    if (var<abs(arr[a_index+start]))
      var:=abs(arr[a_index+start])
  }
  return var
}

ObjClear(byref obj)
{
  obj:=""
  obj:={}
}

MoveControl(hwnd,x=0,y=0,w=0,h=0,min=10)
{
  overf:=0
  GuiControlGet,E_pos,Pos,% hwnd
  if (E_posw+w<min)
  {
    overf|=1
    if (x!=0xffff)
      x:=w
    w:=0
  }
  if (E_posh+h<min)
  {
    overf|=2
    if (y!=0xffff)
      y:=h
    h:=0
  }
  xx:= x=0xffff ? E_posx:E_posx+x
  yy:= y=0xffff ? E_posy:E_posy+y
  ww:= w=0xffff ? E_posw:E_posw+w
  hh:= h=0xffff ? E_posh:E_posh+h
  GuiControl,Move,% hwnd,% " x" xx " y" yy " w" ww " h" hh
  return overf
}

DECtoBIN(h)
{
  stringreplace,h,h,%a_space%,,all
  h:=h+0
  a:=0
  while(h)
  {
    if (a_index>1 and !a)
      r:=" " . r
    a++
    r:= (h&1) . r
    h>>=1
    if (a=8)
      a:=0
  }
  if (a!=0)
  while(a!=8)
  {
    r:= 0 . r
    a++
  }
  return r
}

BINtoDEC(b)
{
  stringreplace,b,b,%a_space%,,all
  r:=0
  loop,parse,b
    r|=(a_loopfield<<(strlen(b)-a_index))
Ceil(strlen(b)/8)
  return r
}

BINtoHEX(b)
{
  stringreplace,b,b,%a_space%,,all
  r:=0
  loop,% strlen(b)
    bb.=SubStr(b,strlen(b)-(a_index-1),1)
  loop,parse,bb
  {
    r|=(a_loopfield<<(Mod(a_index-1,8)))
    if (a_index!=0 and !Mod(a_index,8))
    {
      o:=format("{:02X}",r) . o
      r:=0
    }
    d:=a_index
  }
  if Mod(d,8)
    o:=format("{:02X}",r) . o
  return o
}

DATAtoSTR(var,st,size,sw=0)
{
  loop,% size
    str.=chr(numget(var+0,(sw?(size-a_index):(st+a_index-1)),"uchar"))
  return str
}

uDATAtoSTR(var,st,size,sw=0)
{
  loop,% size
    str.=chr(numget(var+0,(sw?(size-a_index*2):(st+(a_index-1)*2)),"ushort"))
  return str
}

DATAtoHEX(var,st,size,sw=0,sp=0)
{
  loop,% size
    hex.=format("{:02X}",numget(var+0,(sw?(size-a_index):(st+a_index-1)),"uchar")) . ((sp and a_index!=size)?" ":"")
  return hex
}

DATAtoBIN(var,st,size,sw=0)
{
  static bin
  bin:=""
  loop,% size
  {
    h:=numget(var+0,(sw?(size-a_index):(st+a_index-1)),"uchar")
    r:=(a_index=size)?"":" "
    loop,8
    {
      r:=(h&1) . r
      h>>=1
    }
    bin.= r
  }
  return bin
}

STRToDATA(str,byref var,swap=0)
{
  size:=strlen(str)
  varsetcapacity(var,size+2)
  loop,parse,str
  {
    if !swap
      NumPut(asc(a_loopfield),var,a_index-1,"Uchar")
    else
      NumPut(asc(a_loopfield),var,size-a_index,"Uchar")
  }
  return size
}

uSTRToDATA(str,byref var,swap=0)
{
  size:=strlen(str)
  varsetcapacity(var,size*2+2)
  loop,parse,str
  {
    if !swap
      NumPut(asc(a_loopfield),var,(a_index-1)*2,"ushort")
    else
      NumPut(asc(a_loopfield),var,size-a_index*2,"ushort")
  }
  return size
}

DECToDATA(str,byref var,swap=0,dtype="Uint64",size=8)
{
  varsetcapacity(var,size+2)
  NumPut(str+0,var,0,dtype)
  if swap
  {
    varsetcapacity(tmpvar,size)
    loop,% size
      numput(numget(var,a_index-1,"uchar"),tmpvar,size-a_index,"uchar")
    loop,% size
      numput(numget(tmpvar,a_index-1,"uchar"),var,a_index-1,"uchar")
  }
  return size
}

HEXstrToDATA(str,byref var,swap=0)
{
  stringreplace,str,str,%a_space%,,all
  size:=ceil(strlen(str)/2)
  varsetcapacity(var,size+2)
  loop,% size
  {
    stringright,tmp2,str,2
    if !swap
      NumPut("0x"tmp2,var,size-a_index,"Uchar")
    else
      NumPut("0x"tmp2,var,a_index-1,"Uchar")
    StringTrimRight,str,str,2
  }
  return size
}

BINstrToDATA(str,byref var,swap=0)
{
  stringreplace,str,str,%a_space%,,all
  size:=ceil(strlen(str)/8)
  varsetcapacity(var,size+2)
  loop,% size
  {
    stringright,tmp2,str,8
    if !swap
      NumPut(BINtoDEC(tmp2),var,size-a_index,"Uchar")
    else
      NumPut(BINtoDEC(tmp2),var,a_index-1,"Uchar")
    StringTrimRight,str,str,8
  }
  return size
}

ClearVar(byref var,symbol=" ")
{
  stringreplace,var,var,%symbol%,,all
}
_______________________________:
return

; Gdip standard library v1.45 by tic (Tariq Porter) 07/09/11
; Modifed by Rseding91 using fincs 64 bit compatible Gdip library 5/1/2013
; Supports: Basic, _L ANSi, _L Unicode x86 and _L Unicode x64
;
; Updated 2/20/2014 - fixed Gdip_CreateRegion() and Gdip_GetClipRegion() on AHK Unicode x86
; Updated 5/13/2013 - fixed Gdip_SetBitmapToClipboard() on AHK Unicode x64
;
;#####################################################################################
;#####################################################################################
; STATUS ENUMERATION
; Return values for functions specified to have status enumerated return type
;#####################################################################################
;
; Ok =						= 0
; GenericError				= 1
; InvalidParameter			= 2
; OutOfMemory				= 3
; ObjectBusy				= 4
; InsufficientBuffer		= 5
; NotImplemented			= 6
; Win32Error				= 7
; WrongState				= 8
; Aborted					= 9
; FileNotFound				= 10
; ValueOverflow				= 11
; AccessDenied				= 12
; UnknownImageFormat		= 13
; FontFamilyNotFound		= 14
; FontStyleNotFound			= 15
; NotTrueTypeFont			= 16
; UnsupportedGdiplusVersion	= 17
; GdiplusNotInitialized		= 18
; PropertyNotFound			= 19
; PropertyNotSupported		= 20
; ProfileNotFound			= 21
;
;#####################################################################################
;#####################################################################################
; FUNCTIONS
;#####################################################################################
;
; UpdateLayeredWindow(hwnd, hdc, x="", y="", w="", h="", Alpha=255)
; BitBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, Raster="")
; StretchBlt(dDC, dx, dy, dw, dh, sDC, sx, sy, sw, sh, Raster="")
; SetImage(hwnd, hBitmap)
; Gdip_BitmapFromScreen(Screen=0, Raster="")
; CreateRectF(ByRef RectF, x, y, w, h)
; CreateSizeF(ByRef SizeF, w, h)
; CreateDIBSection
;
;#####################################################################################

; Function:     			UpdateLayeredWindow
; Description:  			Updates a layered window with the handle to the DC of a gdi bitmap
;
; hwnd        				Handle of the layered window to update
; hdc           			Handle to the DC of the GDI bitmap to update the window with
; Layeredx      			x position to place the window
; Layeredy      			y position to place the window
; Layeredw      			Width of the window
; Layeredh      			Height of the window
; Alpha         			Default = 255 : The transparency (0-255) to set the window transparency
;
; return      				If the function succeeds, the return value is nonzero
;
; notes						If x or y omitted, then layered window will use its current coordinates
;							If w or h omitted then current width and height will be used

UpdateLayeredWindow(hwnd, hdc, x="", y="", w="", h="", Alpha=255)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	if ((x != "") && (y != ""))
		VarSetCapacity(pt, 8), NumPut(x, pt, 0, "UInt"), NumPut(y, pt, 4, "UInt")

	if (w = "") ||(h = "")
		WinGetPos,,, w, h, ahk_id %hwnd%

	return DllCall("UpdateLayeredWindow"
					, Ptr, hwnd
					, Ptr, 0
					, Ptr, ((x = "") && (y = "")) ? 0 : &pt
					, "int64*", w|h<<32
					, Ptr, hdc
					, "int64*", 0
					, "uint", 0
					, "UInt*", Alpha<<16|1<<24
					, "uint", 2)
}

;#####################################################################################

; Function				BitBlt
; Description			The BitBlt function performs a bit-block transfer of the color data corresponding to a rectangle
;						of pixels from the specified source device context into a destination device context.
;
; dDC					handle to destination DC
; dx					x-coord of destination upper-left corner
; dy					y-coord of destination upper-left corner
; dw					width of the area to copy
; dh					height of the area to copy
; sDC					handle to source DC
; sx					x-coordinate of source upper-left corner
; sy					y-coordinate of source upper-left corner
; Raster				raster operation code
;
; return				If the function succeeds, the return value is nonzero
;
; notes					If no raster operation is specified, then SRCCOPY is used, which copies the source directly to the destination rectangle
;
; BLACKNESS				= 0x00000042
; NOTSRCERASE			= 0x001100A6
; NOTSRCCOPY			= 0x00330008
; SRCERASE				= 0x00440328
; DSTINVERT				= 0x00550009
; PATINVERT				= 0x005A0049
; SRCINVERT				= 0x00660046
; SRCAND				= 0x008800C6
; MERGEPAINT			= 0x00BB0226
; MERGECOPY				= 0x00C000CA
; SRCCOPY				= 0x00CC0020
; SRCPAINT				= 0x00EE0086
; PATCOPY				= 0x00F00021
; PATPAINT				= 0x00FB0A09
; WHITENESS				= 0x00FF0062
; CAPTUREBLT			= 0x40000000
; NOMIRRORBITMAP		= 0x80000000

BitBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, Raster="")
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdi32\BitBlt"
					, Ptr, dDC
					, "int", dx
					, "int", dy
					, "int", dw
					, "int", dh
					, Ptr, sDC
					, "int", sx
					, "int", sy
					, "uint", Raster ? Raster : 0x00CC0020)
}

;#####################################################################################

; Function				StretchBlt
; Description			The StretchBlt function copies a bitmap from a source rectangle into a destination rectangle,
;						stretching or compressing the bitmap to fit the dimensions of the destination rectangle, if necessary.
;						The system stretches or compresses the bitmap according to the stretching mode currently set in the destination device context.
;
; ddc					handle to destination DC
; dx					x-coord of destination upper-left corner
; dy					y-coord of destination upper-left corner
; dw					width of destination rectangle
; dh					height of destination rectangle
; sdc					handle to source DC
; sx					x-coordinate of source upper-left corner
; sy					y-coordinate of source upper-left corner
; sw					width of source rectangle
; sh					height of source rectangle
; Raster				raster operation code
;
; return				If the function succeeds, the return value is nonzero
;
; notes					If no raster operation is specified, then SRCCOPY is used. It uses the same raster operations as BitBlt

StretchBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, sw, sh, Raster="")
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdi32\StretchBlt"
					, Ptr, ddc
					, "int", dx
					, "int", dy
					, "int", dw
					, "int", dh
					, Ptr, sdc
					, "int", sx
					, "int", sy
					, "int", sw
					, "int", sh
					, "uint", Raster ? Raster : 0x00CC0020)
}

;#####################################################################################

; Function				SetStretchBltMode
; Description			The SetStretchBltMode function sets the bitmap stretching mode in the specified device context
;
; hdc					handle to the DC
; iStretchMode			The stretching mode, describing how the target will be stretched
;
; return				If the function succeeds, the return value is the previous stretching mode. If it fails it will return 0
;
; STRETCH_ANDSCANS 		= 0x01
; STRETCH_ORSCANS 		= 0x02
; STRETCH_DELETESCANS 	= 0x03
; STRETCH_HALFTONE 		= 0x04

SetStretchBltMode(hdc, iStretchMode=4)
{
	return DllCall("gdi32\SetStretchBltMode"
					, A_PtrSize ? "UPtr" : "UInt", hdc
					, "int", iStretchMode)
}

;#####################################################################################

; Function				SetImage
; Description			Associates a new image with a static control
;
; hwnd					handle of the control to update
; hBitmap				a gdi bitmap to associate the static control with
;
; return				If the function succeeds, the return value is nonzero

SetImage(hwnd, hBitmap)
{
	SendMessage, 0x172, 0x0, hBitmap,, ahk_id %hwnd%
	E := ErrorLevel
	DeleteObject(E)
	return E
}

;#####################################################################################

; Function				SetSysColorToControl
; Description			Sets a solid colour to a control
;
; hwnd					handle of the control to update
; SysColor				A system colour to set to the control
;
; return				If the function succeeds, the return value is zero
;
; notes					A control must have the 0xE style set to it so it is recognised as a bitmap
;						By default SysColor=15 is used which is COLOR_3DFACE. This is the standard background for a control
;
; COLOR_3DDKSHADOW				= 21
; COLOR_3DFACE					= 15
; COLOR_3DHIGHLIGHT				= 20
; COLOR_3DHILIGHT				= 20
; COLOR_3DLIGHT					= 22
; COLOR_3DSHADOW				= 16
; COLOR_ACTIVEBORDER			= 10
; COLOR_ACTIVECAPTION			= 2
; COLOR_APPWORKSPACE			= 12
; COLOR_BACKGROUND				= 1
; COLOR_BTNFACE					= 15
; COLOR_BTNHIGHLIGHT			= 20
; COLOR_BTNHILIGHT				= 20
; COLOR_BTNSHADOW				= 16
; COLOR_BTNTEXT					= 18
; COLOR_CAPTIONTEXT				= 9
; COLOR_DESKTOP					= 1
; COLOR_GRADIENTACTIVECAPTION	= 27
; COLOR_GRADIENTINACTIVECAPTION	= 28
; COLOR_GRAYTEXT				= 17
; COLOR_HIGHLIGHT				= 13
; COLOR_HIGHLIGHTTEXT			= 14
; COLOR_HOTLIGHT				= 26
; COLOR_INACTIVEBORDER			= 11
; COLOR_INACTIVECAPTION			= 3
; COLOR_INACTIVECAPTIONTEXT		= 19
; COLOR_INFOBK					= 24
; COLOR_INFOTEXT				= 23
; COLOR_MENU					= 4
; COLOR_MENUHILIGHT				= 29
; COLOR_MENUBAR					= 30
; COLOR_MENUTEXT				= 7
; COLOR_SCROLLBAR				= 0
; COLOR_WINDOW					= 5
; COLOR_WINDOWFRAME				= 6
; COLOR_WINDOWTEXT				= 8

SetSysColorToControl(hwnd, SysColor=15)
{
   WinGetPos,,, w, h, ahk_id %hwnd%
   bc := DllCall("GetSysColor", "Int", SysColor, "UInt")
   pBrushClear := Gdip_BrushCreateSolid(0xff000000 | (bc >> 16 | bc & 0xff00 | (bc & 0xff) << 16))
   pBitmap := Gdip_CreateBitmap(w, h), G := Gdip_GraphicsFromImage(pBitmap)
   Gdip_FillRectangle(G, pBrushClear, 0, 0, w, h)
   hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap)
   SetImage(hwnd, hBitmap)
   Gdip_DeleteBrush(pBrushClear)
   Gdip_DeleteGraphics(G), Gdip_DisposeImage(pBitmap), DeleteObject(hBitmap)
   return 0
}

;#####################################################################################

; Function				Gdip_BitmapFromScreen
; Description			Gets a gdi+ bitmap from the screen
;
; Screen				0 = All screens
;						Any numerical value = Just that screen
;						x|y|w|h = Take specific coordinates with a width and height
; Raster				raster operation code
;
; return      			If the function succeeds, the return value is a pointer to a gdi+ bitmap
;						-1:		one or more of x,y,w,h not passed properly
;
; notes					If no raster operation is specified, then SRCCOPY is used to the returned bitmap

Gdip_BitmapFromScreen(Screen=0, Raster="")
{
	if (Screen = 0)
	{
		Sysget, x, 76
		Sysget, y, 77
		Sysget, w, 78
		Sysget, h, 79
	}
	else if (SubStr(Screen, 1, 5) = "hwnd:")
	{
		Screen := SubStr(Screen, 6)
		if !WinExist( "ahk_id " Screen)
			return -2
		WinGetPos,,, w, h, ahk_id %Screen%
		x := y := 0
		hhdc := GetDCEx(Screen, 3)
	}
	else if (Screen&1 != "")
	{
		Sysget, M, Monitor, %Screen%
		x := MLeft, y := MTop, w := MRight-MLeft, h := MBottom-MTop
	}
	else
	{
		StringSplit, S, Screen, |
		x := S1, y := S2, w := S3, h := S4
	}

	if (x = "") || (y = "") || (w = "") || (h = "")
		return -1

	chdc := CreateCompatibleDC(), hbm := CreateDIBSection(w, h, chdc), obm := SelectObject(chdc, hbm), hhdc := hhdc ? hhdc : GetDC()
	BitBlt(chdc, 0, 0, w, h, hhdc, x, y, Raster)
	ReleaseDC(hhdc)

	pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm)
	SelectObject(chdc, obm), DeleteObject(hbm), DeleteDC(hhdc), DeleteDC(chdc)
	return pBitmap
}

;#####################################################################################

; Function				Gdip_BitmapFromHWND
; Description			Uses PrintWindow to get a handle to the specified window and return a bitmap from it
;
; hwnd					handle to the window to get a bitmap from
;
; return				If the function succeeds, the return value is a pointer to a gdi+ bitmap
;
; notes					Window must not be not minimised in order to get a handle to it's client area

Gdip_BitmapFromHWND(hwnd)
{
	WinGetPos,,, Width, Height, ahk_id %hwnd%
	hbm := CreateDIBSection(Width, Height), hdc := CreateCompatibleDC(), obm := SelectObject(hdc, hbm)
	PrintWindow(hwnd, hdc)
	pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm)
	SelectObject(hdc, obm), DeleteObject(hbm), DeleteDC(hdc)
	return pBitmap
}

;#####################################################################################

; Function    			CreateRectF
; Description			Creates a RectF object, containing a the coordinates and dimensions of a rectangle
;
; RectF       			Name to call the RectF object
; x            			x-coordinate of the upper left corner of the rectangle
; y            			y-coordinate of the upper left corner of the rectangle
; w            			Width of the rectangle
; h            			Height of the rectangle
;
; return      			No return value

CreateRectF(ByRef RectF, x, y, w, h)
{
   VarSetCapacity(RectF, 16)
   NumPut(x, RectF, 0, "float"), NumPut(y, RectF, 4, "float"), NumPut(w, RectF, 8, "float"), NumPut(h, RectF, 12, "float")
}

;#####################################################################################

; Function    			CreateRect
; Description			Creates a Rect object, containing a the coordinates and dimensions of a rectangle
;
; RectF       			Name to call the RectF object
; x            			x-coordinate of the upper left corner of the rectangle
; y            			y-coordinate of the upper left corner of the rectangle
; w            			Width of the rectangle
; h            			Height of the rectangle
;
; return      			No return value

CreateRect(ByRef Rect, x, y, w, h)
{
	VarSetCapacity(Rect, 16)
	NumPut(x, Rect, 0, "uint"), NumPut(y, Rect, 4, "uint"), NumPut(w, Rect, 8, "uint"), NumPut(h, Rect, 12, "uint")
}
;#####################################################################################

; Function		    	CreateSizeF
; Description			Creates a SizeF object, containing an 2 values
;
; SizeF         		Name to call the SizeF object
; w            			w-value for the SizeF object
; h            			h-value for the SizeF object
;
; return      			No Return value

CreateSizeF(ByRef SizeF, w, h)
{
   VarSetCapacity(SizeF, 8)
   NumPut(w, SizeF, 0, "float"), NumPut(h, SizeF, 4, "float")
}
;#####################################################################################

; Function		    	CreatePointF
; Description			Creates a SizeF object, containing an 2 values
;
; SizeF         		Name to call the SizeF object
; w            			w-value for the SizeF object
; h            			h-value for the SizeF object
;
; return      			No Return value

CreatePointF(ByRef PointF, x, y)
{
   VarSetCapacity(PointF, 8)
   NumPut(x, PointF, 0, "float"), NumPut(y, PointF, 4, "float")
}
;#####################################################################################

; Function				CreateDIBSection
; Description			The CreateDIBSection function creates a DIB (Device Independent Bitmap) that applications can write to directly
;
; w						width of the bitmap to create
; h						height of the bitmap to create
; hdc					a handle to the device context to use the palette from
; bpp					bits per pixel (32 = ARGB)
; ppvBits				A pointer to a variable that receives a pointer to the location of the DIB bit values
;
; return				returns a DIB. A gdi bitmap
;
; notes					ppvBits will receive the location of the pixels in the DIB

CreateDIBSection(w, h, hdc="", bpp=32, ByRef ppvBits=0)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	hdc2 := hdc ? hdc : GetDC()
	VarSetCapacity(bi, 40, 0)

	NumPut(w, bi, 4, "uint")
	, NumPut(h, bi, 8, "uint")
	, NumPut(40, bi, 0, "uint")
	, NumPut(1, bi, 12, "ushort")
	, NumPut(0, bi, 16, "uInt")
	, NumPut(bpp, bi, 14, "ushort")

	hbm := DllCall("CreateDIBSection"
					, Ptr, hdc2
					, Ptr, &bi
					, "uint", 0
					, A_PtrSize ? "UPtr*" : "uint*", ppvBits
					, Ptr, 0
					, "uint", 0, Ptr)

	if !hdc
		ReleaseDC(hdc2)
	return hbm
}

;#####################################################################################

; Function				PrintWindow
; Description			The PrintWindow function copies a visual window into the specified device context (DC), typically a printer DC
;
; hwnd					A handle to the window that will be copied
; hdc					A handle to the device context
; Flags					Drawing options
;
; return				If the function succeeds, it returns a nonzero value
;
; PW_CLIENTONLY			= 1

PrintWindow(hwnd, hdc, Flags=0)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("PrintWindow", Ptr, hwnd, Ptr, hdc, "uint", Flags)
}

;#####################################################################################

; Function				DestroyIcon
; Description			Destroys an icon and frees any memory the icon occupied
;
; hIcon					Handle to the icon to be destroyed. The icon must not be in use
;
; return				If the function succeeds, the return value is nonzero

DestroyIcon(hIcon)
{
	return DllCall("DestroyIcon", A_PtrSize ? "UPtr" : "UInt", hIcon)
}

;#####################################################################################

PaintDesktop(hdc)
{
	return DllCall("PaintDesktop", A_PtrSize ? "UPtr" : "UInt", hdc)
}

;#####################################################################################

CreateCompatibleBitmap(hdc, w, h)
{
	return DllCall("gdi32\CreateCompatibleBitmap", A_PtrSize ? "UPtr" : "UInt", hdc, "int", w, "int", h)
}

;#####################################################################################

; Function				CreateCompatibleDC
; Description			This function creates a memory device context (DC) compatible with the specified device
;
; hdc					Handle to an existing device context
;
; return				returns the handle to a device context or 0 on failure
;
; notes					If this handle is 0 (by default), the function creates a memory device context compatible with the application's current screen

CreateCompatibleDC(hdc=0)
{
   return DllCall("CreateCompatibleDC", A_PtrSize ? "UPtr" : "UInt", hdc)
}

;#####################################################################################

; Function				SelectObject
; Description			The SelectObject function selects an object into the specified device context (DC). The new object replaces the previous object of the same type
;
; hdc					Handle to a DC
; hgdiobj				A handle to the object to be selected into the DC
;
; return				If the selected object is not a region and the function succeeds, the return value is a handle to the object being replaced
;
; notes					The specified object must have been created by using one of the following functions
;						Bitmap - CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, CreateDIBitmap, CreateDIBSection (A single bitmap cannot be selected into more than one DC at the same time)
;						Brush - CreateBrushIndirect, CreateDIBPatternBrush, CreateDIBPatternBrushPt, CreateHatchBrush, CreatePatternBrush, CreateSolidBrush
;						Font - CreateFont, CreateFontIndirect
;						Pen - CreatePen, CreatePenIndirect
;						Region - CombineRgn, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreateRectRgn, CreateRectRgnIndirect
;
; notes					If the selected object is a region and the function succeeds, the return value is one of the following value
;
; SIMPLEREGION			= 2 Region consists of a single rectangle
; COMPLEXREGION			= 3 Region consists of more than one rectangle
; NULLREGION			= 1 Region is empty

SelectObject(hdc, hgdiobj)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("SelectObject", Ptr, hdc, Ptr, hgdiobj)
}

;#####################################################################################

; Function				DeleteObject
; Description			This function deletes a logical pen, brush, font, bitmap, region, or palette, freeing all system resources associated with the object
;						After the object is deleted, the specified handle is no longer valid
;
; hObject				Handle to a logical pen, brush, font, bitmap, region, or palette to delete
;
; return				Nonzero indicates success. Zero indicates that the specified handle is not valid or that the handle is currently selected into a device context

DeleteObject(hObject)
{
   return DllCall("DeleteObject", A_PtrSize ? "UPtr" : "UInt", hObject)
}

;#####################################################################################

; Function				GetDC
; Description			This function retrieves a handle to a display device context (DC) for the client area of the specified window.
;						The display device context can be used in subsequent graphics display interface (GDI) functions to draw in the client area of the window.
;
; hwnd					Handle to the window whose device context is to be retrieved. If this value is NULL, GetDC retrieves the device context for the entire screen
;
; return				The handle the device context for the specified window's client area indicates success. NULL indicates failure

GetDC(hwnd=0)
{
	return DllCall("GetDC", A_PtrSize ? "UPtr" : "UInt", hwnd)
}

;#####################################################################################

; DCX_CACHE = 0x2
; DCX_CLIPCHILDREN = 0x8
; DCX_CLIPSIBLINGS = 0x10
; DCX_EXCLUDERGN = 0x40
; DCX_EXCLUDEUPDATE = 0x100
; DCX_INTERSECTRGN = 0x80
; DCX_INTERSECTUPDATE = 0x200
; DCX_LOCKWINDOWUPDATE = 0x400
; DCX_NORECOMPUTE = 0x100000
; DCX_NORESETATTRS = 0x4
; DCX_PARENTCLIP = 0x20
; DCX_VALIDATE = 0x200000
; DCX_WINDOW = 0x1

GetDCEx(hwnd, flags=0, hrgnClip=0)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

    return DllCall("GetDCEx", Ptr, hwnd, Ptr, hrgnClip, "int", flags)
}

;#####################################################################################

; Function				ReleaseDC
; Description			This function releases a device context (DC), freeing it for use by other applications. The effect of ReleaseDC depends on the type of device context
;
; hdc					Handle to the device context to be released
; hwnd					Handle to the window whose device context is to be released
;
; return				1 = released
;						0 = not released
;
; notes					The application must call the ReleaseDC function for each call to the GetWindowDC function and for each call to the GetDC function that retrieves a common device context
;						An application cannot use the ReleaseDC function to release a device context that was created by calling the CreateDC function; instead, it must use the DeleteDC function.

ReleaseDC(hdc, hwnd=0)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("ReleaseDC", Ptr, hwnd, Ptr, hdc)
}

;#####################################################################################

; Function				DeleteDC
; Description			The DeleteDC function deletes the specified device context (DC)
;
; hdc					A handle to the device context
;
; return				If the function succeeds, the return value is nonzero
;
; notes					An application must not delete a DC whose handle was obtained by calling the GetDC function. Instead, it must call the ReleaseDC function to free the DC

DeleteDC(hdc)
{
   return DllCall("DeleteDC", A_PtrSize ? "UPtr" : "UInt", hdc)
}
;#####################################################################################

; Function				Gdip_LibraryVersion
; Description			Get the current library version
;
; return				the library version
;
; notes					This is useful for non compiled programs to ensure that a person doesn't run an old version when testing your scripts

Gdip_LibraryVersion()
{
	return 1.45
}

;#####################################################################################

; Function				Gdip_LibrarySubVersion
; Description			Get the current library sub version
;
; return				the library sub version
;
; notes					This is the sub-version currently maintained by Rseding91
Gdip_LibrarySubVersion()
{
	return 1.47
}

;#####################################################################################

; Function:    			Gdip_BitmapFromBRA
; Description: 			Gets a pointer to a gdi+ bitmap from a BRA file
;
; BRAFromMemIn			The variable for a BRA file read to memory
; File					The name of the file, or its number that you would like (This depends on alternate parameter)
; Alternate				Changes whether the File parameter is the file name or its number
;
; return      			If the function succeeds, the return value is a pointer to a gdi+ bitmap
;						-1 = The BRA variable is empty
;						-2 = The BRA has an incorrect header
;						-3 = The BRA has information missing
;						-4 = Could not find file inside the BRA

Gdip_BitmapFromBRA(ByRef BRAFromMemIn, File, Alternate=0)
{
	Static FName = "ObjRelease"

	if !BRAFromMemIn
		return -1
	Loop, Parse, BRAFromMemIn, `n
	{
		if (A_Index = 1)
		{
			StringSplit, Header, A_LoopField, |
			if (Header0 != 4 || Header2 != "BRA!")
				return -2
		}
		else if (A_Index = 2)
		{
			StringSplit, Info, A_LoopField, |
			if (Info0 != 3)
				return -3
		}
		else
			break
	}
	if !Alternate
		StringReplace, File, File, \, \\, All
	RegExMatch(BRAFromMemIn, "mi`n)^" (Alternate ? File "\|.+?\|(\d+)\|(\d+)" : "\d+\|" File "\|(\d+)\|(\d+)") "$", FileInfo)
	if !FileInfo
		return -4

	hData := DllCall("GlobalAlloc", "uint", 2, Ptr, FileInfo2, Ptr)
	pData := DllCall("GlobalLock", Ptr, hData, Ptr)
	DllCall("RtlMoveMemory", Ptr, pData, Ptr, &BRAFromMemIn+Info2+FileInfo1, Ptr, FileInfo2)
	DllCall("GlobalUnlock", Ptr, hData)
	DllCall("ole32\CreateStreamOnHGlobal", Ptr, hData, "int", 1, A_PtrSize ? "UPtr*" : "UInt*", pStream)
	DllCall("gdiplus\GdipCreateBitmapFromStream", Ptr, pStream, A_PtrSize ? "UPtr*" : "UInt*", pBitmap)
	If (A_PtrSize)
		%FName%(pStream)
	Else
		DllCall(NumGet(NumGet(1*pStream)+8), "uint", pStream)
	return pBitmap
}

;#####################################################################################

; Function				Gdip_DrawRectangle
; Description			This function uses a pen to draw the outline of a rectangle into the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pPen					Pointer to a pen
; x						x-coordinate of the top left of the rectangle
; y						y-coordinate of the top left of the rectangle
; w						width of the rectanlge
; h						height of the rectangle
;
; return				status enumeration. 0 = success
;
; notes					as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width

Gdip_DrawRectangle(pGraphics, pPen, x, y, w, h)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipDrawRectangle", Ptr, pGraphics, Ptr, pPen, "float", x, "float", y, "float", w, "float", h)
}

;#####################################################################################

; Function				Gdip_DrawRoundedRectangle
; Description			This function uses a pen to draw the outline of a rounded rectangle into the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pPen					Pointer to a pen
; x						x-coordinate of the top left of the rounded rectangle
; y						y-coordinate of the top left of the rounded rectangle
; w						width of the rectanlge
; h						height of the rectangle
; r						radius of the rounded corners
;
; return				status enumeration. 0 = success
;
; notes					as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width

Gdip_DrawRoundedRectangle(pGraphics, pPen, x, y, w, h, r)
{
	Gdip_SetClipRect(pGraphics, x-r, y-r, 2*r, 2*r, 4)
	Gdip_SetClipRect(pGraphics, x+w-r, y-r, 2*r, 2*r, 4)
	Gdip_SetClipRect(pGraphics, x-r, y+h-r, 2*r, 2*r, 4)
	Gdip_SetClipRect(pGraphics, x+w-r, y+h-r, 2*r, 2*r, 4)
	E := Gdip_DrawRectangle(pGraphics, pPen, x, y, w, h)
	Gdip_ResetClip(pGraphics)
	Gdip_SetClipRect(pGraphics, x-(2*r), y+r, w+(4*r), h-(2*r), 4)
	Gdip_SetClipRect(pGraphics, x+r, y-(2*r), w-(2*r), h+(4*r), 4)
	Gdip_DrawEllipse(pGraphics, pPen, x, y, 2*r, 2*r)
	Gdip_DrawEllipse(pGraphics, pPen, x+w-(2*r), y, 2*r, 2*r)
	Gdip_DrawEllipse(pGraphics, pPen, x, y+h-(2*r), 2*r, 2*r)
	Gdip_DrawEllipse(pGraphics, pPen, x+w-(2*r), y+h-(2*r), 2*r, 2*r)
	Gdip_ResetClip(pGraphics)
	return E
}

;#####################################################################################

; Function				Gdip_DrawEllipse
; Description			This function uses a pen to draw the outline of an ellipse into the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pPen					Pointer to a pen
; x						x-coordinate of the top left of the rectangle the ellipse will be drawn into
; y						y-coordinate of the top left of the rectangle the ellipse will be drawn into
; w						width of the ellipse
; h						height of the ellipse
;
; return				status enumeration. 0 = success
;
; notes					as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width

Gdip_DrawEllipse(pGraphics, pPen, x, y, w, h)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipDrawEllipse", Ptr, pGraphics, Ptr, pPen, "float", x, "float", y, "float", w, "float", h)
}

;#####################################################################################

; Function				Gdip_DrawBezier
; Description			This function uses a pen to draw the outline of a bezier (a weighted curve) into the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pPen					Pointer to a pen
; x1					x-coordinate of the start of the bezier
; y1					y-coordinate of the start of the bezier
; x2					x-coordinate of the first arc of the bezier
; y2					y-coordinate of the first arc of the bezier
; x3					x-coordinate of the second arc of the bezier
; y3					y-coordinate of the second arc of the bezier
; x4					x-coordinate of the end of the bezier
; y4					y-coordinate of the end of the bezier
;
; return				status enumeration. 0 = success
;
; notes					as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width

Gdip_DrawBezier(pGraphics, pPen, x1, y1, x2, y2, x3, y3, x4, y4)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipDrawBezier"
					, Ptr, pgraphics
					, Ptr, pPen
					, "float", x1
					, "float", y1
					, "float", x2
					, "float", y2
					, "float", x3
					, "float", y3
					, "float", x4
					, "float", y4)
}

;#####################################################################################

; Function				Gdip_DrawArc
; Description			This function uses a pen to draw the outline of an arc into the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pPen					Pointer to a pen
; x						x-coordinate of the start of the arc
; y						y-coordinate of the start of the arc
; w						width of the arc
; h						height of the arc
; StartAngle			specifies the angle between the x-axis and the starting point of the arc
; SweepAngle			specifies the angle between the starting and ending points of the arc
;
; return				status enumeration. 0 = success
;
; notes					as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width

Gdip_DrawArc(pGraphics, pPen, x, y, w, h, StartAngle, SweepAngle)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipDrawArc"
					, Ptr, pGraphics
					, Ptr, pPen
					, "float", x
					, "float", y
					, "float", w
					, "float", h
					, "float", StartAngle
					, "float", SweepAngle)
}

;#####################################################################################

; Function				Gdip_DrawPie
; Description			This function uses a pen to draw the outline of a pie into the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pPen					Pointer to a pen
; x						x-coordinate of the start of the pie
; y						y-coordinate of the start of the pie
; w						width of the pie
; h						height of the pie
; StartAngle			specifies the angle between the x-axis and the starting point of the pie
; SweepAngle			specifies the angle between the starting and ending points of the pie
;
; return				status enumeration. 0 = success
;
; notes					as all coordinates are taken from the top left of each pixel, then the entire width/height should be specified as subtracting the pen width

Gdip_DrawPie(pGraphics, pPen, x, y, w, h, StartAngle, SweepAngle)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipDrawPie", Ptr, pGraphics, Ptr, pPen, "float", x, "float", y, "float", w, "float", h, "float", StartAngle, "float", SweepAngle)
}

;#####################################################################################

; Function				Gdip_DrawLine
; Description			This function uses a pen to draw a line into the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pPen					Pointer to a pen
; x1					x-coordinate of the start of the line
; y1					y-coordinate of the start of the line
; x2					x-coordinate of the end of the line
; y2					y-coordinate of the end of the line
;
; return				status enumeration. 0 = success

Gdip_DrawLine(pGraphics, pPen, x1, y1, x2, y2)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipDrawLine"
					, Ptr, pGraphics
					, Ptr, pPen
					, "float", x1
					, "float", y1
					, "float", x2
					, "float", y2)
}

;#####################################################################################

; Function				Gdip_DrawLines
; Description			This function uses a pen to draw a series of joined lines into the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pPen					Pointer to a pen
; Points				the coordinates of all the points passed as x1,y1|x2,y2|x3,y3.....
;
; return				status enumeration. 0 = success

Gdip_DrawLines(pGraphics, pPen, Points)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"
	StringSplit, Points, Points, |
	VarSetCapacity(PointF, 8*Points0)
	Loop, %Points0%
	{
		StringSplit, Coord, Points%A_Index%, `,
		NumPut(Coord1, PointF, 8*(A_Index-1), "float"), NumPut(Coord2, PointF, (8*(A_Index-1))+4, "float")
	}
	return DllCall("gdiplus\GdipDrawLines", Ptr, pGraphics, Ptr, pPen, Ptr, &PointF, "int", Points0)
}

;#####################################################################################

; Function				Gdip_FillRectangle
; Description			This function uses a brush to fill a rectangle in the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pBrush				Pointer to a brush
; x						x-coordinate of the top left of the rectangle
; y						y-coordinate of the top left of the rectangle
; w						width of the rectanlge
; h						height of the rectangle
;
; return				status enumeration. 0 = success

Gdip_FillRectangle(pGraphics, pBrush, x, y, w, h)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipFillRectangle"
					, Ptr, pGraphics
					, Ptr, pBrush
					, "float", x
					, "float", y
					, "float", w
					, "float", h)
}

;#####################################################################################

; Function				Gdip_FillRoundedRectangle
; Description			This function uses a brush to fill a rounded rectangle in the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pBrush				Pointer to a brush
; x						x-coordinate of the top left of the rounded rectangle
; y						y-coordinate of the top left of the rounded rectangle
; w						width of the rectanlge
; h						height of the rectangle
; r						radius of the rounded corners
;
; return				status enumeration. 0 = success

Gdip_FillRoundedRectangle(pGraphics, pBrush, x, y, w, h, r)
{
	Region := Gdip_GetClipRegion(pGraphics)
	Gdip_SetClipRect(pGraphics, x-r, y-r, 2*r, 2*r, 4)
	Gdip_SetClipRect(pGraphics, x+w-r, y-r, 2*r, 2*r, 4)
	Gdip_SetClipRect(pGraphics, x-r, y+h-r, 2*r, 2*r, 4)
	Gdip_SetClipRect(pGraphics, x+w-r, y+h-r, 2*r, 2*r, 4)
	E := Gdip_FillRectangle(pGraphics, pBrush, x, y, w, h)
	Gdip_SetClipRegion(pGraphics, Region, 0)
	Gdip_SetClipRect(pGraphics, x-(2*r), y+r, w+(4*r), h-(2*r), 4)
	Gdip_SetClipRect(pGraphics, x+r, y-(2*r), w-(2*r), h+(4*r), 4)
	Gdip_FillEllipse(pGraphics, pBrush, x, y, 2*r, 2*r)
	Gdip_FillEllipse(pGraphics, pBrush, x+w-(2*r), y, 2*r, 2*r)
	Gdip_FillEllipse(pGraphics, pBrush, x, y+h-(2*r), 2*r, 2*r)
	Gdip_FillEllipse(pGraphics, pBrush, x+w-(2*r), y+h-(2*r), 2*r, 2*r)
	Gdip_SetClipRegion(pGraphics, Region, 0)
	Gdip_DeleteRegion(Region)
	return E
}

;#####################################################################################

; Function				Gdip_FillPolygon
; Description			This function uses a brush to fill a polygon in the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pBrush				Pointer to a brush
; Points				the coordinates of all the points passed as x1,y1|x2,y2|x3,y3.....
;
; return				status enumeration. 0 = success
;
; notes					Alternate will fill the polygon as a whole, wheras winding will fill each new "segment"
; Alternate 			= 0
; Winding 				= 1

Gdip_FillPolygon(pGraphics, pBrush, Points, FillMode=0)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	StringSplit, Points, Points, |
	VarSetCapacity(PointF, 8*Points0)
	Loop, %Points0%
	{
		StringSplit, Coord, Points%A_Index%, `,
		NumPut(Coord1, PointF, 8*(A_Index-1), "float"), NumPut(Coord2, PointF, (8*(A_Index-1))+4, "float")
	}
	return DllCall("gdiplus\GdipFillPolygon", Ptr, pGraphics, Ptr, pBrush, Ptr, &PointF, "int", Points0, "int", FillMode)
}

;#####################################################################################

; Function				Gdip_FillPie
; Description			This function uses a brush to fill a pie in the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pBrush				Pointer to a brush
; x						x-coordinate of the top left of the pie
; y						y-coordinate of the top left of the pie
; w						width of the pie
; h						height of the pie
; StartAngle			specifies the angle between the x-axis and the starting point of the pie
; SweepAngle			specifies the angle between the starting and ending points of the pie
;
; return				status enumeration. 0 = success

Gdip_FillPie(pGraphics, pBrush, x, y, w, h, StartAngle, SweepAngle)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipFillPie"
					, Ptr, pGraphics
					, Ptr, pBrush
					, "float", x
					, "float", y
					, "float", w
					, "float", h
					, "float", StartAngle
					, "float", SweepAngle)
}

;#####################################################################################

; Function				Gdip_FillEllipse
; Description			This function uses a brush to fill an ellipse in the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pBrush				Pointer to a brush
; x						x-coordinate of the top left of the ellipse
; y						y-coordinate of the top left of the ellipse
; w						width of the ellipse
; h						height of the ellipse
;
; return				status enumeration. 0 = success

Gdip_FillEllipse(pGraphics, pBrush, x, y, w, h)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipFillEllipse", Ptr, pGraphics, Ptr, pBrush, "float", x, "float", y, "float", w, "float", h)
}

;#####################################################################################

; Function				Gdip_FillRegion
; Description			This function uses a brush to fill a region in the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pBrush				Pointer to a brush
; Region				Pointer to a Region
;
; return				status enumeration. 0 = success
;
; notes					You can create a region Gdip_CreateRegion() and then add to this

Gdip_FillRegion(pGraphics, pBrush, Region)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipFillRegion", Ptr, pGraphics, Ptr, pBrush, Ptr, Region)
}

;#####################################################################################

; Function				Gdip_FillPath
; Description			This function uses a brush to fill a path in the Graphics of a bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pBrush				Pointer to a brush
; Region				Pointer to a Path
;
; return				status enumeration. 0 = success

Gdip_FillPath(pGraphics, pBrush, Path)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipFillPath", Ptr, pGraphics, Ptr, pBrush, Ptr, Path)
}

;#####################################################################################

; Function				Gdip_DrawImagePointsRect
; Description			This function draws a bitmap into the Graphics of another bitmap and skews it
;
; pGraphics				Pointer to the Graphics of a bitmap
; pBitmap				Pointer to a bitmap to be drawn
; Points				Points passed as x1,y1|x2,y2|x3,y3 (3 points: top left, top right, bottom left) describing the drawing of the bitmap
; sx					x-coordinate of source upper-left corner
; sy					y-coordinate of source upper-left corner
; sw					width of source rectangle
; sh					height of source rectangle
; Matrix				a matrix used to alter image attributes when drawing
;
; return				status enumeration. 0 = success
;
; notes					if sx,sy,sw,sh are missed then the entire source bitmap will be used
;						Matrix can be omitted to just draw with no alteration to ARGB
;						Matrix may be passed as a digit from 0 - 1 to change just transparency
;						Matrix can be passed as a matrix with any delimiter

Gdip_DrawImagePointsRect(pGraphics, pBitmap, Points, sx="", sy="", sw="", sh="", Matrix=1)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	StringSplit, Points, Points, |
	VarSetCapacity(PointF, 8*Points0)
	Loop, %Points0%
	{
		StringSplit, Coord, Points%A_Index%, `,
		NumPut(Coord1, PointF, 8*(A_Index-1), "float"), NumPut(Coord2, PointF, (8*(A_Index-1))+4, "float")
	}

	if (Matrix&1 = "")
		ImageAttr := Gdip_SetImageAttributesColorMatrix(Matrix)
	else if (Matrix != 1)
		ImageAttr := Gdip_SetImageAttributesColorMatrix("1|0|0|0|0|0|1|0|0|0|0|0|1|0|0|0|0|0|" Matrix "|0|0|0|0|0|1")

	if (sx = "" && sy = "" && sw = "" && sh = "")
	{
		sx := 0, sy := 0
		sw := Gdip_GetImageWidth(pBitmap)
		sh := Gdip_GetImageHeight(pBitmap)
	}

	E := DllCall("gdiplus\GdipDrawImagePointsRect"
				, Ptr, pGraphics
				, Ptr, pBitmap
				, Ptr, &PointF
				, "int", Points0
				, "float", sx
				, "float", sy
				, "float", sw
				, "float", sh
				, "int", 2
				, Ptr, ImageAttr
				, Ptr, 0
				, Ptr, 0)
	if ImageAttr
		Gdip_DisposeImageAttributes(ImageAttr)
	return E
}

;#####################################################################################

; Function				Gdip_DrawImage
; Description			This function draws a bitmap into the Graphics of another bitmap
;
; pGraphics				Pointer to the Graphics of a bitmap
; pBitmap				Pointer to a bitmap to be drawn
; dx					x-coord of destination upper-left corner
; dy					y-coord of destination upper-left corner
; dw					width of destination image
; dh					height of destination image
; sx					x-coordinate of source upper-left corner
; sy					y-coordinate of source upper-left corner
; sw					width of source image
; sh					height of source image
; Matrix				a matrix used to alter image attributes when drawing
;
; return				status enumeration. 0 = success
;
; notes					if sx,sy,sw,sh are missed then the entire source bitmap will be used
;						Gdip_DrawImage performs faster
;						Matrix can be omitted to just draw with no alteration to ARGB
;						Matrix may be passed as a digit from 0 - 1 to change just transparency
;						Matrix can be passed as a matrix with any delimiter. For example:
;						MatrixBright=
;						(
;						1.5		|0		|0		|0		|0
;						0		|1.5	|0		|0		|0
;						0		|0		|1.5	|0		|0
;						0		|0		|0		|1		|0
;						0.05	|0.05	|0.05	|0		|1
;						)
;
; notes					MatrixBright = 1.5|0|0|0|0|0|1.5|0|0|0|0|0|1.5|0|0|0|0|0|1|0|0.05|0.05|0.05|0|1
;						MatrixGreyScale = 0.299|0.299|0.299|0|0|0.587|0.587|0.587|0|0|0.114|0.114|0.114|0|0|0|0|0|1|0|0|0|0|0|1
;						MatrixNegative = -1|0|0|0|0|0|-1|0|0|0|0|0|-1|0|0|0|0|0|1|0|0|0|0|0|1

Gdip_DrawImage(pGraphics, pBitmap, dx="", dy="", dw="", dh="", sx="", sy="", sw="", sh="", Matrix=1)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	if (Matrix&1 = "")
		ImageAttr := Gdip_SetImageAttributesColorMatrix(Matrix)
	else if (Matrix != 1)
		ImageAttr := Gdip_SetImageAttributesColorMatrix("1|0|0|0|0|0|1|0|0|0|0|0|1|0|0|0|0|0|" Matrix "|0|0|0|0|0|1")

	if (sx = "" && sy = "" && sw = "" && sh = "")
	{
		if (dx = "" && dy = "" && dw = "" && dh = "")
		{
			sx := dx := 0, sy := dy := 0
			sw := dw := Gdip_GetImageWidth(pBitmap)
			sh := dh := Gdip_GetImageHeight(pBitmap)
		}
		else
		{
			sx := sy := 0
			sw := Gdip_GetImageWidth(pBitmap)
			sh := Gdip_GetImageHeight(pBitmap)
		}
	}

	E := DllCall("gdiplus\GdipDrawImageRectRect"
				, Ptr, pGraphics
				, Ptr, pBitmap
				, "float", dx
				, "float", dy
				, "float", dw
				, "float", dh
				, "float", sx
				, "float", sy
				, "float", sw
				, "float", sh
				, "int", 2
				, Ptr, ImageAttr
				, Ptr, 0
				, Ptr, 0)
	if ImageAttr
		Gdip_DisposeImageAttributes(ImageAttr)
	return E
}

;#####################################################################################

; Function				Gdip_SetImageAttributesColorMatrix
; Description			This function creates an image matrix ready for drawing
;
; Matrix				a matrix used to alter image attributes when drawing
;						passed with any delimeter
;
; return				returns an image matrix on sucess or 0 if it fails
;
; notes					MatrixBright = 1.5|0|0|0|0|0|1.5|0|0|0|0|0|1.5|0|0|0|0|0|1|0|0.05|0.05|0.05|0|1
;						MatrixGreyScale = 0.299|0.299|0.299|0|0|0.587|0.587|0.587|0|0|0.114|0.114|0.114|0|0|0|0|0|1|0|0|0|0|0|1
;						MatrixNegative = -1|0|0|0|0|0|-1|0|0|0|0|0|-1|0|0|0|0|0|1|0|0|0|0|0|1

Gdip_SetImageAttributesColorMatrix(Matrix)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	VarSetCapacity(ColourMatrix, 100, 0)
	Matrix := RegExReplace(RegExReplace(Matrix, "^[^\d-\.]+([\d\.])", "$1", "", 1), "[^\d-\.]+", "|")
	StringSplit, Matrix, Matrix, |
	Loop, 25
	{
		Matrix := (Matrix%A_Index% != "") ? Matrix%A_Index% : Mod(A_Index-1, 6) ? 0 : 1
		NumPut(Matrix, ColourMatrix, (A_Index-1)*4, "float")
	}
	DllCall("gdiplus\GdipCreateImageAttributes", A_PtrSize ? "UPtr*" : "uint*", ImageAttr)
	DllCall("gdiplus\GdipSetImageAttributesColorMatrix", Ptr, ImageAttr, "int", 1, "int", 1, Ptr, &ColourMatrix, Ptr, 0, "int", 0)
	return ImageAttr
}

;#####################################################################################

; Function				Gdip_GraphicsFromImage
; Description			This function gets the graphics for a bitmap used for drawing functions
;
; pBitmap				Pointer to a bitmap to get the pointer to its graphics
;
; return				returns a pointer to the graphics of a bitmap
;
; notes					a bitmap can be drawn into the graphics of another bitmap

Gdip_GraphicsFromImage(pBitmap)
{
	DllCall("gdiplus\GdipGetImageGraphicsContext", A_PtrSize ? "UPtr" : "UInt", pBitmap, A_PtrSize ? "UPtr*" : "UInt*", pGraphics)
	return pGraphics
}

;#####################################################################################

; Function				Gdip_GraphicsFromHDC
; Description			This function gets the graphics from the handle to a device context
;
; hdc					This is the handle to the device context
;
; return				returns a pointer to the graphics of a bitmap
;
; notes					You can draw a bitmap into the graphics of another bitmap

Gdip_GraphicsFromHDC(hdc)
{
    DllCall("gdiplus\GdipCreateFromHDC", A_PtrSize ? "UPtr" : "UInt", hdc, A_PtrSize ? "UPtr*" : "UInt*", pGraphics)
    return pGraphics
}

;#####################################################################################

; Function				Gdip_GetDC
; Description			This function gets the device context of the passed Graphics
;
; hdc					This is the handle to the device context
;
; return				returns the device context for the graphics of a bitmap

Gdip_GetDC(pGraphics)
{
	DllCall("gdiplus\GdipGetDC", A_PtrSize ? "UPtr" : "UInt", pGraphics, A_PtrSize ? "UPtr*" : "UInt*", hdc)
	return hdc
}

;#####################################################################################

; Function				Gdip_ReleaseDC
; Description			This function releases a device context from use for further use
;
; pGraphics				Pointer to the graphics of a bitmap
; hdc					This is the handle to the device context
;
; return				status enumeration. 0 = success

Gdip_ReleaseDC(pGraphics, hdc)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipReleaseDC", Ptr, pGraphics, Ptr, hdc)
}

;#####################################################################################

; Function				Gdip_GraphicsClear
; Description			Clears the graphics of a bitmap ready for further drawing
;
; pGraphics				Pointer to the graphics of a bitmap
; ARGB					The colour to clear the graphics to
;
; return				status enumeration. 0 = success
;
; notes					By default this will make the background invisible
;						Using clipping regions you can clear a particular area on the graphics rather than clearing the entire graphics

Gdip_GraphicsClear(pGraphics, ARGB=0x00ffffff)
{
    return DllCall("gdiplus\GdipGraphicsClear", A_PtrSize ? "UPtr" : "UInt", pGraphics, "int", ARGB)
}

;#####################################################################################

; Function				Gdip_BlurBitmap
; Description			Gives a pointer to a blurred bitmap from a pointer to a bitmap
;
; pBitmap				Pointer to a bitmap to be blurred
; Blur					The Amount to blur a bitmap by from 1 (least blur) to 100 (most blur)
;
; return				If the function succeeds, the return value is a pointer to the new blurred bitmap
;						-1 = The blur parameter is outside the range 1-100
;
; notes					This function will not dispose of the original bitmap

Gdip_BlurBitmap(pBitmap, Blur)
{
	if (Blur > 100) || (Blur < 1)
		return -1

	sWidth := Gdip_GetImageWidth(pBitmap), sHeight := Gdip_GetImageHeight(pBitmap)
	dWidth := sWidth//Blur, dHeight := sHeight//Blur

	pBitmap1 := Gdip_CreateBitmap(dWidth, dHeight)
	G1 := Gdip_GraphicsFromImage(pBitmap1)
	Gdip_SetInterpolationMode(G1, 7)
	Gdip_DrawImage(G1, pBitmap, 0, 0, dWidth, dHeight, 0, 0, sWidth, sHeight)

	Gdip_DeleteGraphics(G1)

	pBitmap2 := Gdip_CreateBitmap(sWidth, sHeight)
	G2 := Gdip_GraphicsFromImage(pBitmap2)
	Gdip_SetInterpolationMode(G2, 7)
	Gdip_DrawImage(G2, pBitmap1, 0, 0, sWidth, sHeight, 0, 0, dWidth, dHeight)

	Gdip_DeleteGraphics(G2)
	Gdip_DisposeImage(pBitmap1)
	return pBitmap2
}

;#####################################################################################

; Function:     		Gdip_SaveBitmapToFile
; Description:  		Saves a bitmap to a file in any supported format onto disk
;
; pBitmap				Pointer to a bitmap
; sOutput      			The name of the file that the bitmap will be saved to. Supported extensions are: .BMP,.DIB,.RLE,.JPG,.JPEG,.JPE,.JFIF,.GIF,.TIF,.TIFF,.PNG
; Quality      			If saving as jpg (.JPG,.JPEG,.JPE,.JFIF) then quality can be 1-100 with default at maximum quality
;
; return      			If the function succeeds, the return value is zero, otherwise:
;						-1 = Extension supplied is not a supported file format
;						-2 = Could not get a list of encoders on system
;						-3 = Could not find matching encoder for specified file format
;						-4 = Could not get WideChar name of output file
;						-5 = Could not save file to disk
;
; notes					This function will use the extension supplied from the sOutput parameter to determine the output format

Gdip_SaveBitmapToFile(pBitmap, sOutput, Quality=75)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	SplitPath, sOutput,,, Extension
	if Extension not in BMP,DIB,RLE,JPG,JPEG,JPE,JFIF,GIF,TIF,TIFF,PNG
		return -1
	Extension := "." Extension

	DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", nCount, "uint*", nSize)
	VarSetCapacity(ci, nSize)
	DllCall("gdiplus\GdipGetImageEncoders", "uint", nCount, "uint", nSize, Ptr, &ci)
	if !(nCount && nSize)
		return -2

	If (A_IsUnicode){
		StrGet_Name := "StrGet"
		Loop, %nCount%
		{
			sString := %StrGet_Name%(NumGet(ci, (idx := (48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize), "UTF-16")
			if !InStr(sString, "*" Extension)
				continue

			pCodec := &ci+idx
			break
		}
	} else {
		Loop, %nCount%
		{
			Location := NumGet(ci, 76*(A_Index-1)+44)
			nSize := DllCall("WideCharToMultiByte", "uint", 0, "uint", 0, "uint", Location, "int", -1, "uint", 0, "int",  0, "uint", 0, "uint", 0)
			VarSetCapacity(sString, nSize)
			DllCall("WideCharToMultiByte", "uint", 0, "uint", 0, "uint", Location, "int", -1, "str", sString, "int", nSize, "uint", 0, "uint", 0)
			if !InStr(sString, "*" Extension)
				continue

			pCodec := &ci+76*(A_Index-1)
			break
		}
	}

	if !pCodec
		return -3

	if (Quality != 75)
	{
		Quality := (Quality < 0) ? 0 : (Quality > 100) ? 100 : Quality
		if Extension in .JPG,.JPEG,.JPE,.JFIF
		{
			DllCall("gdiplus\GdipGetEncoderParameterListSize", Ptr, pBitmap, Ptr, pCodec, "uint*", nSize)
			VarSetCapacity(EncoderParameters, nSize, 0)
			DllCall("gdiplus\GdipGetEncoderParameterList", Ptr, pBitmap, Ptr, pCodec, "uint", nSize, Ptr, &EncoderParameters)
			Loop, % NumGet(EncoderParameters, "UInt")      ;%
			{
				elem := (24+(A_PtrSize ? A_PtrSize : 4))*(A_Index-1) + 4 + (pad := A_PtrSize = 8 ? 4 : 0)
				if (NumGet(EncoderParameters, elem+16, "UInt") = 1) && (NumGet(EncoderParameters, elem+20, "UInt") = 6)
				{
					p := elem+&EncoderParameters-pad-4
					NumPut(Quality, NumGet(NumPut(4, NumPut(1, p+0)+20, "UInt")), "UInt")
					break
				}
			}
		}
	}

	if (!A_IsUnicode)
	{
		nSize := DllCall("MultiByteToWideChar", "uint", 0, "uint", 0, Ptr, &sOutput, "int", -1, Ptr, 0, "int", 0)
		VarSetCapacity(wOutput, nSize*2)
		DllCall("MultiByteToWideChar", "uint", 0, "uint", 0, Ptr, &sOutput, "int", -1, Ptr, &wOutput, "int", nSize)
		VarSetCapacity(wOutput, -1)
		if !VarSetCapacity(wOutput)
			return -4
		E := DllCall("gdiplus\GdipSaveImageToFile", Ptr, pBitmap, Ptr, &wOutput, Ptr, pCodec, "uint", p ? p : 0)
	}
	else
		E := DllCall("gdiplus\GdipSaveImageToFile", Ptr, pBitmap, Ptr, &sOutput, Ptr, pCodec, "uint", p ? p : 0)
	return E ? -5 : 0
}

;#####################################################################################

; Function				Gdip_GetPixel
; Description			Gets the ARGB of a pixel in a bitmap
;
; pBitmap				Pointer to a bitmap
; x						x-coordinate of the pixel
; y						y-coordinate of the pixel
;
; return				Returns the ARGB value of the pixel

Gdip_GetPixel(pBitmap, x, y)
{
	DllCall("gdiplus\GdipBitmapGetPixel", A_PtrSize ? "UPtr" : "UInt", pBitmap, "int", x, "int", y, "uint*", ARGB)
	return ARGB
}

;#####################################################################################

; Function				Gdip_SetPixel
; Description			Sets the ARGB of a pixel in a bitmap
;
; pBitmap				Pointer to a bitmap
; x						x-coordinate of the pixel
; y						y-coordinate of the pixel
;
; return				status enumeration. 0 = success

Gdip_SetPixel(pBitmap, x, y, ARGB)
{
   return DllCall("gdiplus\GdipBitmapSetPixel", A_PtrSize ? "UPtr" : "UInt", pBitmap, "int", x, "int", y, "int", ARGB)
}

;#####################################################################################

; Function				Gdip_GetImageWidth
; Description			Gives the width of a bitmap
;
; pBitmap				Pointer to a bitmap
;
; return				Returns the width in pixels of the supplied bitmap

Gdip_GetImageWidth(pBitmap)
{
   DllCall("gdiplus\GdipGetImageWidth", A_PtrSize ? "UPtr" : "UInt", pBitmap, "uint*", Width)
   return Width
}

;#####################################################################################

; Function				Gdip_GetImageHeight
; Description			Gives the height of a bitmap
;
; pBitmap				Pointer to a bitmap
;
; return				Returns the height in pixels of the supplied bitmap

Gdip_GetImageHeight(pBitmap)
{
   DllCall("gdiplus\GdipGetImageHeight", A_PtrSize ? "UPtr" : "UInt", pBitmap, "uint*", Height)
   return Height
}

;#####################################################################################

; Function				Gdip_GetDimensions
; Description			Gives the width and height of a bitmap
;
; pBitmap				Pointer to a bitmap
; Width					ByRef variable. This variable will be set to the width of the bitmap
; Height				ByRef variable. This variable will be set to the height of the bitmap
;
; return				No return value
;						Gdip_GetDimensions(pBitmap, ThisWidth, ThisHeight) will set ThisWidth to the width and ThisHeight to the height

Gdip_GetImageDimensions(pBitmap, ByRef Width, ByRef Height)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"
	DllCall("gdiplus\GdipGetImageWidth", Ptr, pBitmap, "uint*", Width)
	DllCall("gdiplus\GdipGetImageHeight", Ptr, pBitmap, "uint*", Height)
}

;#####################################################################################

Gdip_GetDimensions(pBitmap, ByRef Width, ByRef Height)
{
	Gdip_GetImageDimensions(pBitmap, Width, Height)
}

;#####################################################################################

Gdip_GetImagePixelFormat(pBitmap)
{
	DllCall("gdiplus\GdipGetImagePixelFormat", A_PtrSize ? "UPtr" : "UInt", pBitmap, A_PtrSize ? "UPtr*" : "UInt*", Format)
	return Format
}

;#####################################################################################

; Function				Gdip_GetDpiX
; Description			Gives the horizontal dots per inch of the graphics of a bitmap
;
; pBitmap				Pointer to a bitmap
; Width					ByRef variable. This variable will be set to the width of the bitmap
; Height				ByRef variable. This variable will be set to the height of the bitmap
;
; return				No return value
;						Gdip_GetDimensions(pBitmap, ThisWidth, ThisHeight) will set ThisWidth to the width and ThisHeight to the height

Gdip_GetDpiX(pGraphics)
{
	DllCall("gdiplus\GdipGetDpiX", A_PtrSize ? "UPtr" : "uint", pGraphics, "float*", dpix)
	return Round(dpix)
}

;#####################################################################################

Gdip_GetDpiY(pGraphics)
{
	DllCall("gdiplus\GdipGetDpiY", A_PtrSize ? "UPtr" : "uint", pGraphics, "float*", dpiy)
	return Round(dpiy)
}

;#####################################################################################

Gdip_GetImageHorizontalResolution(pBitmap)
{
	DllCall("gdiplus\GdipGetImageHorizontalResolution", A_PtrSize ? "UPtr" : "uint", pBitmap, "float*", dpix)
	return Round(dpix)
}

;#####################################################################################

Gdip_GetImageVerticalResolution(pBitmap)
{
	DllCall("gdiplus\GdipGetImageVerticalResolution", A_PtrSize ? "UPtr" : "uint", pBitmap, "float*", dpiy)
	return Round(dpiy)
}

;#####################################################################################

Gdip_BitmapSetResolution(pBitmap, dpix, dpiy)
{
	return DllCall("gdiplus\GdipBitmapSetResolution", A_PtrSize ? "UPtr" : "uint", pBitmap, "float", dpix, "float", dpiy)
}

;#####################################################################################

Gdip_CreateBitmapFromFile(sFile, IconNumber=1, IconSize="")
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"
	, PtrA := A_PtrSize ? "UPtr*" : "UInt*"

	SplitPath, sFile,,, ext
	if ext in exe,dll
	{
		Sizes := IconSize ? IconSize : 256 "|" 128 "|" 64 "|" 48 "|" 32 "|" 16
		BufSize := 16 + (2*(A_PtrSize ? A_PtrSize : 4))

		VarSetCapacity(buf, BufSize, 0)
		Loop, Parse, Sizes, |
		{
			DllCall("PrivateExtractIcons", "str", sFile, "int", IconNumber-1, "int", A_LoopField, "int", A_LoopField, PtrA, hIcon, PtrA, 0, "uint", 1, "uint", 0)

			if !hIcon
				continue

			if !DllCall("GetIconInfo", Ptr, hIcon, Ptr, &buf)
			{
				DestroyIcon(hIcon)
				continue
			}

			hbmMask  := NumGet(buf, 12 + ((A_PtrSize ? A_PtrSize : 4) - 4))
			hbmColor := NumGet(buf, 12 + ((A_PtrSize ? A_PtrSize : 4) - 4) + (A_PtrSize ? A_PtrSize : 4))
			if !(hbmColor && DllCall("GetObject", Ptr, hbmColor, "int", BufSize, Ptr, &buf))
			{
				DestroyIcon(hIcon)
				continue
			}
			break
		}
		if !hIcon
			return -1

		Width := NumGet(buf, 4, "int"), Height := NumGet(buf, 8, "int")
		hbm := CreateDIBSection(Width, -Height), hdc := CreateCompatibleDC(), obm := SelectObject(hdc, hbm)
		if !DllCall("DrawIconEx", Ptr, hdc, "int", 0, "int", 0, Ptr, hIcon, "uint", Width, "uint", Height, "uint", 0, Ptr, 0, "uint", 3)
		{
			DestroyIcon(hIcon)
			return -2
		}

		VarSetCapacity(dib, 104)
		DllCall("GetObject", Ptr, hbm, "int", A_PtrSize = 8 ? 104 : 84, Ptr, &dib) ; sizeof(DIBSECTION) = 76+2*(A_PtrSize=8?4:0)+2*A_PtrSize
		Stride := NumGet(dib, 12, "Int"), Bits := NumGet(dib, 20 + (A_PtrSize = 8 ? 4 : 0)) ; padding
		DllCall("gdiplus\GdipCreateBitmapFromScan0", "int", Width, "int", Height, "int", Stride, "int", 0x26200A, Ptr, Bits, PtrA, pBitmapOld)
		pBitmap := Gdip_CreateBitmap(Width, Height)
		G := Gdip_GraphicsFromImage(pBitmap)
		, Gdip_DrawImage(G, pBitmapOld, 0, 0, Width, Height, 0, 0, Width, Height)
		SelectObject(hdc, obm), DeleteObject(hbm), DeleteDC(hdc)
		Gdip_DeleteGraphics(G), Gdip_DisposeImage(pBitmapOld)
		DestroyIcon(hIcon)
	}
	else
	{
		if (!A_IsUnicode)
		{
			VarSetCapacity(wFile, 1024)
			DllCall("kernel32\MultiByteToWideChar", "uint", 0, "uint", 0, Ptr, &sFile, "int", -1, Ptr, &wFile, "int", 512)
			DllCall("gdiplus\GdipCreateBitmapFromFile", Ptr, &wFile, PtrA, pBitmap)
		}
		else
			DllCall("gdiplus\GdipCreateBitmapFromFile", Ptr, &sFile, PtrA, pBitmap)
	}

	return pBitmap
}

;#####################################################################################

Gdip_CreateBitmapFromHBITMAP(hBitmap, Palette=0)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	DllCall("gdiplus\GdipCreateBitmapFromHBITMAP", Ptr, hBitmap, Ptr, Palette, A_PtrSize ? "UPtr*" : "uint*", pBitmap)
	return pBitmap
}

;#####################################################################################

Gdip_CreateHBITMAPFromBitmap(pBitmap, Background=0xffffffff)
{
	DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", A_PtrSize ? "UPtr" : "UInt", pBitmap, A_PtrSize ? "UPtr*" : "uint*", hbm, "int", Background)
	return hbm
}

;#####################################################################################

Gdip_CreateBitmapFromHICON(hIcon)
{
	DllCall("gdiplus\GdipCreateBitmapFromHICON", A_PtrSize ? "UPtr" : "UInt", hIcon, A_PtrSize ? "UPtr*" : "uint*", pBitmap)
	return pBitmap
}

;#####################################################################################

Gdip_CreateHICONFromBitmap(pBitmap)
{
	DllCall("gdiplus\GdipCreateHICONFromBitmap", A_PtrSize ? "UPtr" : "UInt", pBitmap, A_PtrSize ? "UPtr*" : "uint*", hIcon)
	return hIcon
}

;#####################################################################################

Gdip_CreateBitmap(Width, Height, Format=0x26200A)
{
    DllCall("gdiplus\GdipCreateBitmapFromScan0", "int", Width, "int", Height, "int", 0, "int", Format, A_PtrSize ? "UPtr" : "UInt", 0, A_PtrSize ? "UPtr*" : "uint*", pBitmap)
    Return pBitmap
}

;#####################################################################################

Gdip_CreateBitmapFromClipboard()
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	if !DllCall("OpenClipboard", Ptr, 0)
		return -1
	if !DllCall("IsClipboardFormatAvailable", "uint", 8)
		return -2
	if !hBitmap := DllCall("GetClipboardData", "uint", 2, Ptr)
		return -3
	if !pBitmap := Gdip_CreateBitmapFromHBITMAP(hBitmap)
		return -4
	if !DllCall("CloseClipboard")
		return -5
	DeleteObject(hBitmap)
	return pBitmap
}

;#####################################################################################

Gdip_SetBitmapToClipboard(pBitmap)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"
	off1 := A_PtrSize = 8 ? 52 : 44, off2 := A_PtrSize = 8 ? 32 : 24
	hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap)
	DllCall("GetObject", Ptr, hBitmap, "int", VarSetCapacity(oi, A_PtrSize = 8 ? 104 : 84, 0), Ptr, &oi)
	hdib := DllCall("GlobalAlloc", "uint", 2, Ptr, 40+NumGet(oi, off1, "UInt"), Ptr)
	pdib := DllCall("GlobalLock", Ptr, hdib, Ptr)
	DllCall("RtlMoveMemory", Ptr, pdib, Ptr, &oi+off2, Ptr, 40)
	DllCall("RtlMoveMemory", Ptr, pdib+40, Ptr, NumGet(oi, off2 - (A_PtrSize ? A_PtrSize : 4), Ptr), Ptr, NumGet(oi, off1, "UInt"))
	DllCall("GlobalUnlock", Ptr, hdib)
	DllCall("DeleteObject", Ptr, hBitmap)
	DllCall("OpenClipboard", Ptr, 0)
	DllCall("EmptyClipboard")
	DllCall("SetClipboardData", "uint", 8, Ptr, hdib)
	DllCall("CloseClipboard")
}

;#####################################################################################

Gdip_CloneBitmapArea(pBitmap, x, y, w, h, Format=0x26200A)
{
	DllCall("gdiplus\GdipCloneBitmapArea"
					, "float", x
					, "float", y
					, "float", w
					, "float", h
					, "int", Format
					, A_PtrSize ? "UPtr" : "UInt", pBitmap
					, A_PtrSize ? "UPtr*" : "UInt*", pBitmapDest)
	return pBitmapDest
}

;#####################################################################################
; Create resources
;#####################################################################################

Gdip_CreatePen(ARGB, w)
{
   DllCall("gdiplus\GdipCreatePen1", "UInt", ARGB, "float", w, "int", 2, A_PtrSize ? "UPtr*" : "UInt*", pPen)
   return pPen
}

;#####################################################################################

Gdip_CreatePenFromBrush(pBrush, w)
{
	DllCall("gdiplus\GdipCreatePen2", A_PtrSize ? "UPtr" : "UInt", pBrush, "float", w, "int", 2, A_PtrSize ? "UPtr*" : "UInt*", pPen)
	return pPen
}

;#####################################################################################

Gdip_BrushCreateSolid(ARGB=0xff000000)
{
	DllCall("gdiplus\GdipCreateSolidFill", "UInt", ARGB, A_PtrSize ? "UPtr*" : "UInt*", pBrush)
	return pBrush
}

;#####################################################################################

; HatchStyleHorizontal = 0
; HatchStyleVertical = 1
; HatchStyleForwardDiagonal = 2
; HatchStyleBackwardDiagonal = 3
; HatchStyleCross = 4
; HatchStyleDiagonalCross = 5
; HatchStyle05Percent = 6
; HatchStyle10Percent = 7
; HatchStyle20Percent = 8
; HatchStyle25Percent = 9
; HatchStyle30Percent = 10
; HatchStyle40Percent = 11
; HatchStyle50Percent = 12
; HatchStyle60Percent = 13
; HatchStyle70Percent = 14
; HatchStyle75Percent = 15
; HatchStyle80Percent = 16
; HatchStyle90Percent = 17
; HatchStyleLightDownwardDiagonal = 18
; HatchStyleLightUpwardDiagonal = 19
; HatchStyleDarkDownwardDiagonal = 20
; HatchStyleDarkUpwardDiagonal = 21
; HatchStyleWideDownwardDiagonal = 22
; HatchStyleWideUpwardDiagonal = 23
; HatchStyleLightVertical = 24
; HatchStyleLightHorizontal = 25
; HatchStyleNarrowVertical = 26
; HatchStyleNarrowHorizontal = 27
; HatchStyleDarkVertical = 28
; HatchStyleDarkHorizontal = 29
; HatchStyleDashedDownwardDiagonal = 30
; HatchStyleDashedUpwardDiagonal = 31
; HatchStyleDashedHorizontal = 32
; HatchStyleDashedVertical = 33
; HatchStyleSmallConfetti = 34
; HatchStyleLargeConfetti = 35
; HatchStyleZigZag = 36
; HatchStyleWave = 37
; HatchStyleDiagonalBrick = 38
; HatchStyleHorizontalBrick = 39
; HatchStyleWeave = 40
; HatchStylePlaid = 41
; HatchStyleDivot = 42
; HatchStyleDottedGrid = 43
; HatchStyleDottedDiamond = 44
; HatchStyleShingle = 45
; HatchStyleTrellis = 46
; HatchStyleSphere = 47
; HatchStyleSmallGrid = 48
; HatchStyleSmallCheckerBoard = 49
; HatchStyleLargeCheckerBoard = 50
; HatchStyleOutlinedDiamond = 51
; HatchStyleSolidDiamond = 52
; HatchStyleTotal = 53
Gdip_BrushCreateHatch(ARGBfront, ARGBback, HatchStyle=0)
{
	DllCall("gdiplus\GdipCreateHatchBrush", "int", HatchStyle, "UInt", ARGBfront, "UInt", ARGBback, A_PtrSize ? "UPtr*" : "UInt*", pBrush)
	return pBrush
}

;#####################################################################################

Gdip_CreateTextureBrush(pBitmap, WrapMode=1, x=0, y=0, w="", h="")
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"
	, PtrA := A_PtrSize ? "UPtr*" : "UInt*"

	if !(w && h)
		DllCall("gdiplus\GdipCreateTexture", Ptr, pBitmap, "int", WrapMode, PtrA, pBrush)
	else
		DllCall("gdiplus\GdipCreateTexture2", Ptr, pBitmap, "int", WrapMode, "float", x, "float", y, "float", w, "float", h, PtrA, pBrush)
	return pBrush
}

;#####################################################################################

; WrapModeTile = 0
; WrapModeTileFlipX = 1
; WrapModeTileFlipY = 2
; WrapModeTileFlipXY = 3
; WrapModeClamp = 4
Gdip_CreateLineBrush(x1, y1, x2, y2, ARGB1, ARGB2, WrapMode=1)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	CreatePointF(PointF1, x1, y1), CreatePointF(PointF2, x2, y2)
	DllCall("gdiplus\GdipCreateLineBrush", Ptr, &PointF1, Ptr, &PointF2, "Uint", ARGB1, "Uint", ARGB2, "int", WrapMode, A_PtrSize ? "UPtr*" : "UInt*", LGpBrush)
	return LGpBrush
}

;#####################################################################################

; LinearGradientModeHorizontal = 0
; LinearGradientModeVertical = 1
; LinearGradientModeForwardDiagonal = 2
; LinearGradientModeBackwardDiagonal = 3
Gdip_CreateLineBrushFromRect(x, y, w, h, ARGB1, ARGB2, LinearGradientMode=1, WrapMode=1)
{
	CreateRectF(RectF, x, y, w, h)
	DllCall("gdiplus\GdipCreateLineBrushFromRect", A_PtrSize ? "UPtr" : "UInt", &RectF, "int", ARGB1, "int", ARGB2, "int", LinearGradientMode, "int", WrapMode, A_PtrSize ? "UPtr*" : "UInt*", LGpBrush)
	return LGpBrush
}

;#####################################################################################

Gdip_CloneBrush(pBrush)
{
	DllCall("gdiplus\GdipCloneBrush", A_PtrSize ? "UPtr" : "UInt", pBrush, A_PtrSize ? "UPtr*" : "UInt*", pBrushClone)
	return pBrushClone
}

;#####################################################################################
; Delete resources
;#####################################################################################

Gdip_DeletePen(pPen)
{
   return DllCall("gdiplus\GdipDeletePen", A_PtrSize ? "UPtr" : "UInt", pPen)
}

;#####################################################################################

Gdip_DeleteBrush(pBrush)
{
   return DllCall("gdiplus\GdipDeleteBrush", A_PtrSize ? "UPtr" : "UInt", pBrush)
}

;#####################################################################################

Gdip_DisposeImage(pBitmap)
{
   return DllCall("gdiplus\GdipDisposeImage", A_PtrSize ? "UPtr" : "UInt", pBitmap)
}

;#####################################################################################

Gdip_DeleteGraphics(pGraphics)
{
   return DllCall("gdiplus\GdipDeleteGraphics", A_PtrSize ? "UPtr" : "UInt", pGraphics)
}

;#####################################################################################

Gdip_DisposeImageAttributes(ImageAttr)
{
	return DllCall("gdiplus\GdipDisposeImageAttributes", A_PtrSize ? "UPtr" : "UInt", ImageAttr)
}

;#####################################################################################

Gdip_DeleteFont(hFont)
{
   return DllCall("gdiplus\GdipDeleteFont", A_PtrSize ? "UPtr" : "UInt", hFont)
}

;#####################################################################################

Gdip_DeleteStringFormat(hFormat)
{
   return DllCall("gdiplus\GdipDeleteStringFormat", A_PtrSize ? "UPtr" : "UInt", hFormat)
}

;#####################################################################################

Gdip_DeleteFontFamily(hFamily)
{
   return DllCall("gdiplus\GdipDeleteFontFamily", A_PtrSize ? "UPtr" : "UInt", hFamily)
}

;#####################################################################################

Gdip_DeleteMatrix(Matrix)
{
   return DllCall("gdiplus\GdipDeleteMatrix", A_PtrSize ? "UPtr" : "UInt", Matrix)
}

;#####################################################################################
; Text functions
;#####################################################################################

Gdip_TextToGraphics(pGraphics, Text, Options, Font="Arial", Width="", Height="", Measure=0)
{
	IWidth := Width, IHeight:= Height

	RegExMatch(Options, "i)X([\-\d\.]+)(p*)", xpos)
	RegExMatch(Options, "i)Y([\-\d\.]+)(p*)", ypos)
	RegExMatch(Options, "i)W([\-\d\.]+)(p*)", Width)
	RegExMatch(Options, "i)H([\-\d\.]+)(p*)", Height)
	RegExMatch(Options, "i)C(?!(entre|enter))([a-f\d]+)", Colour)
	RegExMatch(Options, "i)Top|Up|Bottom|Down|vCentre|vCenter", vPos)
	RegExMatch(Options, "i)NoWrap", NoWrap)
	RegExMatch(Options, "i)R(\d)", Rendering)
	RegExMatch(Options, "i)S(\d+)(p*)", Size)

	if !Gdip_DeleteBrush(Gdip_CloneBrush(Colour2))
		PassBrush := 1, pBrush := Colour2

	if !(IWidth && IHeight) && (xpos2 || ypos2 || Width2 || Height2 || Size2)
		return -1

	Style := 0, Styles := "Regular|Bold|Italic|BoldItalic|Underline|Strikeout"
	Loop, Parse, Styles, |
	{
		if RegExMatch(Options, "\b" A_loopField)
		Style |= (A_LoopField != "StrikeOut") ? (A_Index-1) : 8
	}

	Align := 0, Alignments := "Near|Left|Centre|Center|Far|Right"
	Loop, Parse, Alignments, |
	{
		if RegExMatch(Options, "\b" A_loopField)
			Align |= A_Index//2.1      ; 0|0|1|1|2|2
	}

	xpos := (xpos1 != "") ? xpos2 ? IWidth*(xpos1/100) : xpos1 : 0
	ypos := (ypos1 != "") ? ypos2 ? IHeight*(ypos1/100) : ypos1 : 0
	Width := Width1 ? Width2 ? IWidth*(Width1/100) : Width1 : IWidth
	Height := Height1 ? Height2 ? IHeight*(Height1/100) : Height1 : IHeight
	if !PassBrush
		Colour := "0x" (Colour2 ? Colour2 : "ff000000")
	Rendering := ((Rendering1 >= 0) && (Rendering1 <= 5)) ? Rendering1 : 4
	Size := (Size1 > 0) ? Size2 ? IHeight*(Size1/100) : Size1 : 12

	hFamily := Gdip_FontFamilyCreate(Font)
	hFont := Gdip_FontCreate(hFamily, Size, Style)
	FormatStyle := NoWrap ? 0x4000 | 0x1000 : 0x4000
	hFormat := Gdip_StringFormatCreate(FormatStyle)
	pBrush := PassBrush ? pBrush : Gdip_BrushCreateSolid(Colour)
	if !(hFamily && hFont && hFormat && pBrush && pGraphics)
		return !pGraphics ? -2 : !hFamily ? -3 : !hFont ? -4 : !hFormat ? -5 : !pBrush ? -6 : 0

	CreateRectF(RC, xpos, ypos, Width, Height)
	Gdip_SetStringFormatAlign(hFormat, Align)
	Gdip_SetTextRenderingHint(pGraphics, Rendering)
	ReturnRC := Gdip_MeasureString(pGraphics, Text, hFont, hFormat, RC)

	if vPos
	{
		StringSplit, ReturnRC, ReturnRC, |

		if (vPos = "vCentre") || (vPos = "vCenter")
			ypos += (Height-ReturnRC4)//2
		else if (vPos = "Top") || (vPos = "Up")
			ypos := 0
		else if (vPos = "Bottom") || (vPos = "Down")
			ypos := Height-ReturnRC4

		CreateRectF(RC, xpos, ypos, Width, ReturnRC4)
		ReturnRC := Gdip_MeasureString(pGraphics, Text, hFont, hFormat, RC)
	}

	if !Measure
		E := Gdip_DrawString(pGraphics, Text, hFont, hFormat, pBrush, RC)

	if !PassBrush
		Gdip_DeleteBrush(pBrush)
	Gdip_DeleteStringFormat(hFormat)
	Gdip_DeleteFont(hFont)
	Gdip_DeleteFontFamily(hFamily)
	return E ? E : ReturnRC
}

;#####################################################################################

Gdip_DrawString(pGraphics, sString, hFont, hFormat, pBrush, ByRef RectF)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	if (!A_IsUnicode)
	{
		nSize := DllCall("MultiByteToWideChar", "uint", 0, "uint", 0, Ptr, &sString, "int", -1, Ptr, 0, "int", 0)
		VarSetCapacity(wString, nSize*2)
		DllCall("MultiByteToWideChar", "uint", 0, "uint", 0, Ptr, &sString, "int", -1, Ptr, &wString, "int", nSize)
	}

	return DllCall("gdiplus\GdipDrawString"
					, Ptr, pGraphics
					, Ptr, A_IsUnicode ? &sString : &wString
					, "int", -1
					, Ptr, hFont
					, Ptr, &RectF
					, Ptr, hFormat
					, Ptr, pBrush)
}

;#####################################################################################

Gdip_MeasureString(pGraphics, sString, hFont, hFormat, ByRef RectF)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	VarSetCapacity(RC, 16)
	if !A_IsUnicode
	{
		nSize := DllCall("MultiByteToWideChar", "uint", 0, "uint", 0, Ptr, &sString, "int", -1, "uint", 0, "int", 0)
		VarSetCapacity(wString, nSize*2)
		DllCall("MultiByteToWideChar", "uint", 0, "uint", 0, Ptr, &sString, "int", -1, Ptr, &wString, "int", nSize)
	}

	DllCall("gdiplus\GdipMeasureString"
					, Ptr, pGraphics
					, Ptr, A_IsUnicode ? &sString : &wString
					, "int", -1
					, Ptr, hFont
					, Ptr, &RectF
					, Ptr, hFormat
					, Ptr, &RC
					, "uint*", Chars
					, "uint*", Lines)

	return &RC ? NumGet(RC, 0, "float") "|" NumGet(RC, 4, "float") "|" NumGet(RC, 8, "float") "|" NumGet(RC, 12, "float") "|" Chars "|" Lines : 0
}

; Near = 0
; Center = 1
; Far = 2
Gdip_SetStringFormatAlign(hFormat, Align)
{
   return DllCall("gdiplus\GdipSetStringFormatAlign", A_PtrSize ? "UPtr" : "UInt", hFormat, "int", Align)
}

; StringFormatFlagsDirectionRightToLeft    = 0x00000001
; StringFormatFlagsDirectionVertical       = 0x00000002
; StringFormatFlagsNoFitBlackBox           = 0x00000004
; StringFormatFlagsDisplayFormatControl    = 0x00000020
; StringFormatFlagsNoFontFallback          = 0x00000400
; StringFormatFlagsMeasureTrailingSpaces   = 0x00000800
; StringFormatFlagsNoWrap                  = 0x00001000
; StringFormatFlagsLineLimit               = 0x00002000
; StringFormatFlagsNoClip                  = 0x00004000
Gdip_StringFormatCreate(Format=0, Lang=0)
{
   DllCall("gdiplus\GdipCreateStringFormat", "int", Format, "int", Lang, A_PtrSize ? "UPtr*" : "UInt*", hFormat)
   return hFormat
}

; Regular = 0
; Bold = 1
; Italic = 2
; BoldItalic = 3
; Underline = 4
; Strikeout = 8
Gdip_FontCreate(hFamily, Size, Style=0)
{
   DllCall("gdiplus\GdipCreateFont", A_PtrSize ? "UPtr" : "UInt", hFamily, "float", Size, "int", Style, "int", 0, A_PtrSize ? "UPtr*" : "UInt*", hFont)
   return hFont
}

Gdip_FontFamilyCreate(Font)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	if (!A_IsUnicode)
	{
		nSize := DllCall("MultiByteToWideChar", "uint", 0, "uint", 0, Ptr, &Font, "int", -1, "uint", 0, "int", 0)
		VarSetCapacity(wFont, nSize*2)
		DllCall("MultiByteToWideChar", "uint", 0, "uint", 0, Ptr, &Font, "int", -1, Ptr, &wFont, "int", nSize)
	}

	DllCall("gdiplus\GdipCreateFontFamilyFromName"
					, Ptr, A_IsUnicode ? &Font : &wFont
					, "uint", 0
					, A_PtrSize ? "UPtr*" : "UInt*", hFamily)

	return hFamily
}

;#####################################################################################
; Matrix functions
;#####################################################################################

Gdip_CreateAffineMatrix(m11, m12, m21, m22, x, y)
{
   DllCall("gdiplus\GdipCreateMatrix2", "float", m11, "float", m12, "float", m21, "float", m22, "float", x, "float", y, A_PtrSize ? "UPtr*" : "UInt*", Matrix)
   return Matrix
}

Gdip_CreateMatrix()
{
   DllCall("gdiplus\GdipCreateMatrix", A_PtrSize ? "UPtr*" : "UInt*", Matrix)
   return Matrix
}

;#####################################################################################
; GraphicsPath functions
;#####################################################################################

; Alternate = 0
; Winding = 1
Gdip_CreatePath(BrushMode=0)
{
	DllCall("gdiplus\GdipCreatePath", "int", BrushMode, A_PtrSize ? "UPtr*" : "UInt*", Path)
	return Path
}

Gdip_AddPathEllipse(Path, x, y, w, h)
{
	return DllCall("gdiplus\GdipAddPathEllipse", A_PtrSize ? "UPtr" : "UInt", Path, "float", x, "float", y, "float", w, "float", h)
}

Gdip_AddPathPolygon(Path, Points)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	StringSplit, Points, Points, |
	VarSetCapacity(PointF, 8*Points0)
	Loop, %Points0%
	{
		StringSplit, Coord, Points%A_Index%, `,
		NumPut(Coord1, PointF, 8*(A_Index-1), "float"), NumPut(Coord2, PointF, (8*(A_Index-1))+4, "float")
	}

	return DllCall("gdiplus\GdipAddPathPolygon", Ptr, Path, Ptr, &PointF, "int", Points0)
}

Gdip_DeletePath(Path)
{
	return DllCall("gdiplus\GdipDeletePath", A_PtrSize ? "UPtr" : "UInt", Path)
}

;#####################################################################################
; Quality functions
;#####################################################################################

; SystemDefault = 0
; SingleBitPerPixelGridFit = 1
; SingleBitPerPixel = 2
; AntiAliasGridFit = 3
; AntiAlias = 4
Gdip_SetTextRenderingHint(pGraphics, RenderingHint)
{
	return DllCall("gdiplus\GdipSetTextRenderingHint", A_PtrSize ? "UPtr" : "UInt", pGraphics, "int", RenderingHint)
}

; Default = 0
; LowQuality = 1
; HighQuality = 2
; Bilinear = 3
; Bicubic = 4
; NearestNeighbor = 5
; HighQualityBilinear = 6
; HighQualityBicubic = 7
Gdip_SetInterpolationMode(pGraphics, InterpolationMode)
{
   return DllCall("gdiplus\GdipSetInterpolationMode", A_PtrSize ? "UPtr" : "UInt", pGraphics, "int", InterpolationMode)
}

; Default = 0
; HighSpeed = 1
; HighQuality = 2
; None = 3
; AntiAlias = 4
Gdip_SetSmoothingMode(pGraphics, SmoothingMode)
{
   return DllCall("gdiplus\GdipSetSmoothingMode", A_PtrSize ? "UPtr" : "UInt", pGraphics, "int", SmoothingMode)
}

; CompositingModeSourceOver = 0 (blended)
; CompositingModeSourceCopy = 1 (overwrite)
Gdip_SetCompositingMode(pGraphics, CompositingMode=0)
{
   return DllCall("gdiplus\GdipSetCompositingMode", A_PtrSize ? "UPtr" : "UInt", pGraphics, "int", CompositingMode)
}

;#####################################################################################
; Extra functions
;#####################################################################################

Gdip_Startup()
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	if !DllCall("GetModuleHandle", "str", "gdiplus", Ptr)
		DllCall("LoadLibrary", "str", "gdiplus")
	VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0), si := Chr(1)
	DllCall("gdiplus\GdiplusStartup", A_PtrSize ? "UPtr*" : "uint*", pToken, Ptr, &si, Ptr, 0)
	return pToken
}

Gdip_Shutdown(pToken)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	DllCall("gdiplus\GdiplusShutdown", Ptr, pToken)
	if hModule := DllCall("GetModuleHandle", "str", "gdiplus", Ptr)
		DllCall("FreeLibrary", Ptr, hModule)
	return 0
}

; Prepend = 0; The new operation is applied before the old operation.
; Append = 1; The new operation is applied after the old operation.
Gdip_RotateWorldTransform(pGraphics, Angle, MatrixOrder=0)
{
	return DllCall("gdiplus\GdipRotateWorldTransform", A_PtrSize ? "UPtr" : "UInt", pGraphics, "float", Angle, "int", MatrixOrder)
}

Gdip_ScaleWorldTransform(pGraphics, x, y, MatrixOrder=0)
{
	return DllCall("gdiplus\GdipScaleWorldTransform", A_PtrSize ? "UPtr" : "UInt", pGraphics, "float", x, "float", y, "int", MatrixOrder)
}

Gdip_TranslateWorldTransform(pGraphics, x, y, MatrixOrder=0)
{
	return DllCall("gdiplus\GdipTranslateWorldTransform", A_PtrSize ? "UPtr" : "UInt", pGraphics, "float", x, "float", y, "int", MatrixOrder)
}

Gdip_ResetWorldTransform(pGraphics)
{
	return DllCall("gdiplus\GdipResetWorldTransform", A_PtrSize ? "UPtr" : "UInt", pGraphics)
}

Gdip_GetRotatedTranslation(Width, Height, Angle, ByRef xTranslation, ByRef yTranslation)
{
	pi := 3.14159, TAngle := Angle*(pi/180)

	Bound := (Angle >= 0) ? Mod(Angle, 360) : 360-Mod(-Angle, -360)
	if ((Bound >= 0) && (Bound <= 90))
		xTranslation := Height*Sin(TAngle), yTranslation := 0
	else if ((Bound > 90) && (Bound <= 180))
		xTranslation := (Height*Sin(TAngle))-(Width*Cos(TAngle)), yTranslation := -Height*Cos(TAngle)
	else if ((Bound > 180) && (Bound <= 270))
		xTranslation := -(Width*Cos(TAngle)), yTranslation := -(Height*Cos(TAngle))-(Width*Sin(TAngle))
	else if ((Bound > 270) && (Bound <= 360))
		xTranslation := 0, yTranslation := -Width*Sin(TAngle)
}

Gdip_GetRotatedDimensions(Width, Height, Angle, ByRef RWidth, ByRef RHeight)
{
	pi := 3.14159, TAngle := Angle*(pi/180)
	if !(Width && Height)
		return -1
	RWidth := Ceil(Abs(Width*Cos(TAngle))+Abs(Height*Sin(TAngle)))
	RHeight := Ceil(Abs(Width*Sin(TAngle))+Abs(Height*Cos(Tangle)))
}

; RotateNoneFlipNone   = 0
; Rotate90FlipNone     = 1
; Rotate180FlipNone    = 2
; Rotate270FlipNone    = 3
; RotateNoneFlipX      = 4
; Rotate90FlipX        = 5
; Rotate180FlipX       = 6
; Rotate270FlipX       = 7
; RotateNoneFlipY      = Rotate180FlipX
; Rotate90FlipY        = Rotate270FlipX
; Rotate180FlipY       = RotateNoneFlipX
; Rotate270FlipY       = Rotate90FlipX
; RotateNoneFlipXY     = Rotate180FlipNone
; Rotate90FlipXY       = Rotate270FlipNone
; Rotate180FlipXY      = RotateNoneFlipNone
; Rotate270FlipXY      = Rotate90FlipNone

Gdip_ImageRotateFlip(pBitmap, RotateFlipType=1)
{
	return DllCall("gdiplus\GdipImageRotateFlip", A_PtrSize ? "UPtr" : "UInt", pBitmap, "int", RotateFlipType)
}

; Replace = 0
; Intersect = 1
; Union = 2
; Xor = 3
; Exclude = 4
; Complement = 5
Gdip_SetClipRect(pGraphics, x, y, w, h, CombineMode=0)
{
   return DllCall("gdiplus\GdipSetClipRect",  A_PtrSize ? "UPtr" : "UInt", pGraphics, "float", x, "float", y, "float", w, "float", h, "int", CombineMode)
}

Gdip_SetClipPath(pGraphics, Path, CombineMode=0)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"
	return DllCall("gdiplus\GdipSetClipPath", Ptr, pGraphics, Ptr, Path, "int", CombineMode)
}

Gdip_ResetClip(pGraphics)
{
   return DllCall("gdiplus\GdipResetClip", A_PtrSize ? "UPtr" : "UInt", pGraphics)
}

Gdip_GetClipRegion(pGraphics)
{
	Region := Gdip_CreateRegion()
	DllCall("gdiplus\GdipGetClip", A_PtrSize ? "UPtr" : "UInt", pGraphics, "UInt*", Region)
	return Region
}

Gdip_SetClipRegion(pGraphics, Region, CombineMode=0)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("gdiplus\GdipSetClipRegion", Ptr, pGraphics, Ptr, Region, "int", CombineMode)
}

Gdip_CreateRegion()
{
	DllCall("gdiplus\GdipCreateRegion", "UInt*", Region)
	return Region
}

Gdip_DeleteRegion(Region)
{
	return DllCall("gdiplus\GdipDeleteRegion", A_PtrSize ? "UPtr" : "UInt", Region)
}

;#####################################################################################
; BitmapLockBits
;#####################################################################################

Gdip_LockBits(pBitmap, x, y, w, h, ByRef Stride, ByRef Scan0, ByRef BitmapData, LockMode = 3, PixelFormat = 0x26200a)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	CreateRect(Rect, x, y, w, h)
	VarSetCapacity(BitmapData, 16+2*(A_PtrSize ? A_PtrSize : 4), 0)
	E := DllCall("Gdiplus\GdipBitmapLockBits", Ptr, pBitmap, Ptr, &Rect, "uint", LockMode, "int", PixelFormat, Ptr, &BitmapData)
	Stride := NumGet(BitmapData, 8, "Int")
	Scan0 := NumGet(BitmapData, 16, Ptr)
	return E
}

;#####################################################################################

Gdip_UnlockBits(pBitmap, ByRef BitmapData)
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"

	return DllCall("Gdiplus\GdipBitmapUnlockBits", Ptr, pBitmap, Ptr, &BitmapData)
}

;#####################################################################################

Gdip_SetLockBitPixel(ARGB, Scan0, x, y, Stride)
{
	Numput(ARGB, Scan0+0, (x*4)+(y*Stride), "UInt")
}

;#####################################################################################

Gdip_GetLockBitPixel(Scan0, x, y, Stride)
{
	return NumGet(Scan0+0, (x*4)+(y*Stride), "UInt")
}

;#####################################################################################

Gdip_PixelateBitmap(pBitmap, ByRef pBitmapOut, BlockSize)
{
	static PixelateBitmap

	Ptr := A_PtrSize ? "UPtr" : "UInt"

	if (!PixelateBitmap)
	{
		if A_PtrSize != 8 ; x86 machine code
		MCode_PixelateBitmap =
		(LTrim Join
		558BEC83EC3C8B4514538B5D1C99F7FB56578BC88955EC894DD885C90F8E830200008B451099F7FB8365DC008365E000894DC88955F08945E833FF897DD4
		397DE80F8E160100008BCB0FAFCB894DCC33C08945F88945FC89451C8945143BD87E608B45088D50028BC82BCA8BF02BF2418945F48B45E02955F4894DC4
		8D0CB80FAFCB03CA895DD08BD1895DE40FB64416030145140FB60201451C8B45C40FB604100145FC8B45F40FB604020145F883C204FF4DE475D6034D18FF
		4DD075C98B4DCC8B451499F7F98945148B451C99F7F989451C8B45FC99F7F98945FC8B45F899F7F98945F885DB7E648B450C8D50028BC82BCA83C103894D
		C48BC82BCA41894DF48B4DD48945E48B45E02955E48D0C880FAFCB03CA895DD08BD18BF38A45148B7DC48804178A451C8B7DF488028A45FC8804178A45F8
		8B7DE488043A83C2044E75DA034D18FF4DD075CE8B4DCC8B7DD447897DD43B7DE80F8CF2FEFFFF837DF0000F842C01000033C08945F88945FC89451C8945
		148945E43BD87E65837DF0007E578B4DDC034DE48B75E80FAF4D180FAFF38B45088D500203CA8D0CB18BF08BF88945F48B45F02BF22BFA2955F48945CC0F
		B6440E030145140FB60101451C0FB6440F010145FC8B45F40FB604010145F883C104FF4DCC75D8FF45E4395DE47C9B8B4DF00FAFCB85C9740B8B451499F7
		F9894514EB048365140033F63BCE740B8B451C99F7F989451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB
		038975F88975E43BDE7E5A837DF0007E4C8B4DDC034DE48B75E80FAF4D180FAFF38B450C8D500203CA8D0CB18BF08BF82BF22BFA2BC28B55F08955CC8A55
		1488540E038A551C88118A55FC88540F018A55F888140183C104FF4DCC75DFFF45E4395DE47CA68B45180145E0015DDCFF4DC80F8594FDFFFF8B451099F7
		FB8955F08945E885C00F8E450100008B45EC0FAFC38365DC008945D48B45E88945CC33C08945F88945FC89451C8945148945103945EC7E6085DB7E518B4D
		D88B45080FAFCB034D108D50020FAF4D18034DDC8BF08BF88945F403CA2BF22BFA2955F4895DC80FB6440E030145140FB60101451C0FB6440F010145FC8B
		45F40FB604080145F883C104FF4DC875D8FF45108B45103B45EC7CA08B4DD485C9740B8B451499F7F9894514EB048365140033F63BCE740B8B451C99F7F9
		89451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB038975F88975103975EC7E5585DB7E468B4DD88B450C
		0FAFCB034D108D50020FAF4D18034DDC8BF08BF803CA2BF22BFA2BC2895DC88A551488540E038A551C88118A55FC88540F018A55F888140183C104FF4DC8
		75DFFF45108B45103B45EC7CAB8BC3C1E0020145DCFF4DCC0F85CEFEFFFF8B4DEC33C08945F88945FC89451C8945148945103BC87E6C3945F07E5C8B4DD8
		8B75E80FAFCB034D100FAFF30FAF4D188B45088D500203CA8D0CB18BF08BF88945F48B45F02BF22BFA2955F48945C80FB6440E030145140FB60101451C0F
		B6440F010145FC8B45F40FB604010145F883C104FF4DC875D833C0FF45108B4DEC394D107C940FAF4DF03BC874068B451499F7F933F68945143BCE740B8B
		451C99F7F989451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB038975F88975083975EC7E63EB0233F639
		75F07E4F8B4DD88B75E80FAFCB034D080FAFF30FAF4D188B450C8D500203CA8D0CB18BF08BF82BF22BFA2BC28B55F08955108A551488540E038A551C8811
		8A55FC88540F018A55F888140883C104FF4D1075DFFF45088B45083B45EC7C9F5F5E33C05BC9C21800
		)
		else ; x64 machine code
		MCode_PixelateBitmap =
		(LTrim Join
		4489442418488954241048894C24085355565741544155415641574883EC28418BC1448B8C24980000004C8BDA99488BD941F7F9448BD0448BFA8954240C
		448994248800000085C00F8E9D020000418BC04533E4458BF299448924244C8954241041F7F933C9898C24980000008BEA89542404448BE889442408EB05
		4C8B5C24784585ED0F8E1A010000458BF1418BFD48897C2418450FAFF14533D233F633ED4533E44533ED4585C97E5B4C63BC2490000000418D040A410FAF
		C148984C8D441802498BD9498BD04D8BD90FB642010FB64AFF4403E80FB60203E90FB64AFE4883C2044403E003F149FFCB75DE4D03C748FFCB75D0488B7C
		24188B8C24980000004C8B5C2478418BC59941F7FE448BE8418BC49941F7FE448BE08BC59941F7FE8BE88BC69941F7FE8BF04585C97E4048639C24900000
		004103CA4D8BC1410FAFC94863C94A8D541902488BCA498BC144886901448821408869FF408871FE4883C10448FFC875E84803D349FFC875DA8B8C249800
		0000488B5C24704C8B5C24784183C20448FFCF48897C24180F850AFFFFFF8B6C2404448B2424448B6C24084C8B74241085ED0F840A01000033FF33DB4533
		DB4533D24533C04585C97E53488B74247085ED7E42438D0C04418BC50FAF8C2490000000410FAFC18D04814863C8488D5431028BCD0FB642014403D00FB6
		024883C2044403D80FB642FB03D80FB642FA03F848FFC975DE41FFC0453BC17CB28BCD410FAFC985C9740A418BC299F7F98BF0EB0233F685C9740B418BC3
		99F7F9448BD8EB034533DB85C9740A8BC399F7F9448BD0EB034533D285C9740A8BC799F7F9448BC0EB034533C033D24585C97E4D4C8B74247885ED7E3841
		8D0C14418BC50FAF8C2490000000410FAFC18D04814863C84A8D4431028BCD40887001448818448850FF448840FE4883C00448FFC975E8FFC2413BD17CBD
		4C8B7424108B8C2498000000038C2490000000488B5C24704503E149FFCE44892424898C24980000004C897424100F859EFDFFFF448B7C240C448B842480
		000000418BC09941F7F98BE8448BEA89942498000000896C240C85C00F8E3B010000448BAC2488000000418BCF448BF5410FAFC9898C248000000033FF33
		ED33F64533DB4533D24533C04585FF7E524585C97E40418BC5410FAFC14103C00FAF84249000000003C74898488D541802498BD90FB642014403D00FB602
		4883C2044403D80FB642FB03F00FB642FA03E848FFCB75DE488B5C247041FFC0453BC77CAE85C9740B418BC299F7F9448BE0EB034533E485C9740A418BC3
		99F7F98BD8EB0233DB85C9740A8BC699F7F9448BD8EB034533DB85C9740A8BC599F7F9448BD0EB034533D24533C04585FF7E4E488B4C24784585C97E3541
		8BC5410FAFC14103C00FAF84249000000003C74898488D540802498BC144886201881A44885AFF448852FE4883C20448FFC875E941FFC0453BC77CBE8B8C
		2480000000488B5C2470418BC1C1E00203F849FFCE0F85ECFEFFFF448BAC24980000008B6C240C448BA4248800000033FF33DB4533DB4533D24533C04585
		FF7E5A488B7424704585ED7E48418BCC8BC5410FAFC94103C80FAF8C2490000000410FAFC18D04814863C8488D543102418BCD0FB642014403D00FB60248
		83C2044403D80FB642FB03D80FB642FA03F848FFC975DE41FFC0453BC77CAB418BCF410FAFCD85C9740A418BC299F7F98BF0EB0233F685C9740B418BC399
		F7F9448BD8EB034533DB85C9740A8BC399F7F9448BD0EB034533D285C9740A8BC799F7F9448BC0EB034533C033D24585FF7E4E4585ED7E42418BCC8BC541
		0FAFC903CA0FAF8C2490000000410FAFC18D04814863C8488B442478488D440102418BCD40887001448818448850FF448840FE4883C00448FFC975E8FFC2
		413BD77CB233C04883C428415F415E415D415C5F5E5D5BC3
		)

		VarSetCapacity(PixelateBitmap, StrLen(MCode_PixelateBitmap)//2)
		Loop % StrLen(MCode_PixelateBitmap)//2		;%
			NumPut("0x" SubStr(MCode_PixelateBitmap, (2*A_Index)-1, 2), PixelateBitmap, A_Index-1, "UChar")
		DllCall("VirtualProtect", Ptr, &PixelateBitmap, Ptr, VarSetCapacity(PixelateBitmap), "uint", 0x40, A_PtrSize ? "UPtr*" : "UInt*", 0)
	}

	Gdip_GetImageDimensions(pBitmap, Width, Height)

	if (Width != Gdip_GetImageWidth(pBitmapOut) || Height != Gdip_GetImageHeight(pBitmapOut))
		return -1
	if (BlockSize > Width || BlockSize > Height)
		return -2

	E1 := Gdip_LockBits(pBitmap, 0, 0, Width, Height, Stride1, Scan01, BitmapData1)
	E2 := Gdip_LockBits(pBitmapOut, 0, 0, Width, Height, Stride2, Scan02, BitmapData2)
	if (E1 || E2)
		return -3

	E := DllCall(&PixelateBitmap, Ptr, Scan01, Ptr, Scan02, "int", Width, "int", Height, "int", Stride1, "int", BlockSize)

	Gdip_UnlockBits(pBitmap, BitmapData1), Gdip_UnlockBits(pBitmapOut, BitmapData2)
	return 0
}

;#####################################################################################

Gdip_ToARGB(A, R, G, B)
{
	return (A << 24) | (R << 16) | (G << 8) | B
}

;#####################################################################################

Gdip_FromARGB(ARGB, ByRef A, ByRef R, ByRef G, ByRef B)
{
	A := (0xff000000 & ARGB) >> 24
	R := (0x00ff0000 & ARGB) >> 16
	G := (0x0000ff00 & ARGB) >> 8
	B := 0x000000ff & ARGB
}

;#####################################################################################

Gdip_AFromARGB(ARGB)
{
	return (0xff000000 & ARGB) >> 24
}

;#####################################################################################

Gdip_RFromARGB(ARGB)
{
	return (0x00ff0000 & ARGB) >> 16
}

;#####################################################################################

Gdip_GFromARGB(ARGB)
{
	return (0x0000ff00 & ARGB) >> 8
}

;#####################################################################################

Gdip_BFromARGB(ARGB)
{
	return 0x000000ff & ARGB
}

;#####################################################################################

StrGetB(Address, Length=-1, Encoding=0)
{
	; Flexible parameter handling:
	if Length is not integer
	Encoding := Length,  Length := -1

	; Check for obvious errors.
	if (Address+0 < 1024)
		return

	; Ensure 'Encoding' contains a numeric identifier.
	if Encoding = UTF-16
		Encoding = 1200
	else if Encoding = UTF-8
		Encoding = 65001
	else if SubStr(Encoding,1,2)="CP"
		Encoding := SubStr(Encoding,3)

	if !Encoding ; "" or 0
	{
		; No conversion necessary, but we might not want the whole string.
		if (Length == -1)
			Length := DllCall("lstrlen", "uint", Address)
		VarSetCapacity(String, Length)
		DllCall("lstrcpyn", "str", String, "uint", Address, "int", Length + 1)
	}
	else if Encoding = 1200 ; UTF-16
	{
		char_count := DllCall("WideCharToMultiByte", "uint", 0, "uint", 0x400, "uint", Address, "int", Length, "uint", 0, "uint", 0, "uint", 0, "uint", 0)
		VarSetCapacity(String, char_count)
		DllCall("WideCharToMultiByte", "uint", 0, "uint", 0x400, "uint", Address, "int", Length, "str", String, "int", char_count, "uint", 0, "uint", 0)
	}
	else if Encoding is integer
	{
		; Convert from target encoding to UTF-16 then to the active code page.
		char_count := DllCall("MultiByteToWideChar", "uint", Encoding, "uint", 0, "uint", Address, "int", Length, "uint", 0, "int", 0)
		VarSetCapacity(String, char_count * 2)
		char_count := DllCall("MultiByteToWideChar", "uint", Encoding, "uint", 0, "uint", Address, "int", Length, "uint", &String, "int", char_count * 2)
		String := StrGetB(&String, char_count, 1200)
	}

	return String
}

class GlobalContainer  {
   __New(name)  {
      for wnd in ComObjCreate("Shell.Application").Windows  {
         if (wnd.GetProperty("container_name") = name)
            this._obj := wnd
      }
      if !this._obj  {
         this._obj := ComObjGet("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}")
         this._obj.PutProperty("container_name", name)
      }
   }

   __Set(prop, value)  {
      if prop not in _obj,_userFunc
        try
          Return this._obj.PutProperty(prop, value)
        catch{}
   }

   __Get(prop)  {
      if (prop != "_obj")
        try
           Return this._obj.GetProperty(prop)
        catch{}
   }

   Connect(userFunc)  {
      this._userFunc := userFunc.Bind(this)
      ComObjConnect(this._obj, this)
   }

   PropertyChange(prop, obj)  {
      this._userFunc.Call(prop)
   }

   Quit()  {
        try
          this._obj.Quit()
        catch{}
   }
}

/*
    A basic memory class by RHCP:
https://github.com/Kalamity/classMemory
    This is a wrapper for commonly used read and write memory functions.
    It also contains a variety of pattern scan functions.
    This class allows scripts to read/write integers and strings of various types.
    Pointer addresses can easily be read/written by passing the base address and offsets to the various read/write functions.

    Process handles are kept open between reads. This increases speed.
    However, if a program closes/restarts then the process handle will become invalid
    and you will need to re-open another handle (blank/destroy the object and recreate it)
    isHandleValid() can be used to check if a handle is still active/valid.

    read(), readString(), write(), and writeString() can be used to read and write memory addresses respectively.

    readRaw() can be used to dump large chunks of memory, this is considerably faster when
    reading data from a large structure compared to repeated calls to read().
    For example, reading a single UInt takes approximately the same amount of time as reading 1000 bytes via readRaw().
    Although, most people wouldn't notice the performance difference. This does however require you
    to retrieve the values using AHK's numget()/strGet() from the dumped memory.

    In a similar fashion writeRaw() allows a buffer to be be written in a single operation.

    When the new operator is used this class returns an object which can be used to read that process's
    memory space.To read another process simply create another object.

    Process handles are automatically closed when the script exits/restarts or when you free the object.

    **Notes:
        This was initially written for 32 bit target processes, however the various read/write functions
        should now completely support pointers in 64 bit target applications. The only caveat is that the AHK exe must also be 64 bit.
        If AHK is 32 bit and the target application is 64 bit you can still read, write, and use pointers, so long as the addresses
        fit inside a 4 byte pointer, i.e. The maximum address is limited to the 32 bit range.

        The various pattern scan functions are intended to be used on 32 bit target applications, however:
            - A 32 bit AHK script can perform pattern scans on a 32 bit target application.
            - A 32 bit AHK script may be able to perform pattern scans on a 64 bit process, providing the addresses fall within the 32 bit range.
            - A 64 bit AHK script should be able to perform pattern scans on a 32 or 64 bit target application without issue.

        If the target process has admin privileges, then the AHK script will also require admin privileges.

        AHK doesn't support unsigned 64bit ints, you can however read them as Int64 and interpret negative values as large numbers.


    Commonly used methods:
        read()
        readString()
        readRaw()
        write()
        writeString()
        writeBytes()
        writeRaw()
        isHandleValid()
        getModuleBaseAddress()

    Less commonly used methods:
        getProcessBaseAddress()
        hexStringToPattern()
        stringToPattern()
        modulePatternScan()
        processPatternScan()
        addressPatternScan()
        rawPatternScan()
        getModules()
        numberOfBytesRead()
        numberOfBytesWritten()
        suspend()
        resume()

    Internal methods: (some may be useful when directly called)
        getAddressFromOffsets() ; This will return the final memory address of a pointer. This is useful if the pointed address only changes on startup or map/level change and you want to eliminate the overhead associated with pointers.
        isTargetProcess64Bit()
        pointer()
        GetModuleFileNameEx()
        EnumProcessModulesEx()
        GetModuleInformation()
        getNeedleFromAOBPattern()
        virtualQueryEx()
        patternScan()
        bufferScanForMaskedPattern()
        openProcess()
        closeHandle()

    Useful properties:  (Do not modify the values of these properties - they are set automatically)
        baseAddress             ; The base address of the target process
        hProcess                ; The handle to the target process
        PID                     ; The PID of the target process
        currentProgram          ; The string the user used to identify the target process e.g. "ahk_exe calc.exe"
        isTarget64bit           ; True if target process is 64 bit, otherwise false
        readStringLastError     ; Used to check for success/failure when reading a string

     Useful editable properties:
        insertNullTerminator    ; Determines if a null terminator is inserted when writing strings.


    Usage:

        ; **Note: If you wish to try this calc example, consider using the 32 bit version of calc.exe -
        ;         which is in C:\Windows\SysWOW64\calc.exe on win7 64 bit systems.

        ; The contents of this file can be copied directly into your script. Alternately, you can copy the classMemory.ahk file into your library folder,
        ; in which case you will need to use the #include directive in your script i.e.
            #Include <classMemory>

        ; You can use this code to check if you have installed the class correctly.
            if (_ClassMemory.__Class != "_ClassMemory")
            {
                msgbox class memory not correctly installed. Or the (global class) variable "_ClassMemory" has been overwritten
                ExitApp
            }

        ; Open a process with sufficient access to read and write memory addresses (this is required before you can use the other functions)
        ; You only need to do this once. But if the process closes/restarts, then you will need to perform this step again. Refer to the notes section below.
        ; Also, if the target process is running as admin, then the script will also require admin rights!
        ; Note: The program identifier can be any AHK windowTitle i.e.ahk_exe, ahk_class, ahk_pid, or simply the window title.
        ; hProcessCopy is an optional variable in which the opened handled is stored.

            calc := new _ClassMemory("ahk_exe calc.exe", "", hProcessCopy)

        ; Check if the above method was successful.
            if !isObject(calc)
            {
                msgbox failed to open a handle
                if (hProcessCopy = 0)
                    msgbox The program isn't running (not found) or you passed an incorrect program identifier parameter. In some cases _ClassMemory.setSeDebugPrivilege() may be required.
                else if (hProcessCopy = "")
                    msgbox OpenProcess failed. If the target process has admin rights, then the script also needs to be ran as admin. _ClassMemory.setSeDebugPrivilege() may also be required. Consult A_LastError for more information.
                ExitApp
            }

        ; Get the process's base address.
        ; When using the new operator this property is automatically set to the result of getModuleBaseAddress() or getProcessBaseAddress();
        ; the specific method used depends on the bitness of the target application and AHK.
        ; If the returned address is incorrect and the target application is 64 bit, but AHK is 32 bit, try using the 64 bit version of AHK.
            msgbox % calc.BaseAddress

        ; Get the base address of a specific module.
            msgbox % calc.getModuleBaseAddress("GDI32.dll")

        ; The rest of these examples are just for illustration (the addresses specified are probably not valid).
        ; You can use cheat engine to find real addresses to read and write for testing purposes.

        ; Write 1234 as a UInt at address 0x0016CB60.
            calc.write(0x0016CB60, 1234, "UInt")

        ; Read a UInt.
            value := calc.read(0x0016CB60, "UInt")

        ; Read a pointer with offsets 0x20 and 0x15C which points to a UChar.
            value := calc.read(pointerBase, "UChar", 0x20, 0x15C)

        ; Note: read(), readString(), readRaw(), write(), writeString(), and writeRaw() all support pointers/offsets.
        ; An array of pointers can be passed directly, i.e.
            arrayPointerOffsets := [0x20, 0x15C]
            value := calc.read(pointerBase, "UChar", arrayPointerOffsets*)
        ; Or they can be entered manually.
            value := calc.read(pointerBase, "UChar", 0x20, 0x15C)
        ; You can also pass all the parameters directly, i.e.
            aMyPointer := [pointerBase, "UChar", 0x20, 0x15C]
            value := calc.read(aMyPointer*)


        ; Read a utf-16 null terminated string of unknown size at address 0x1234556 - the function will read until the null terminator is found or something goes wrong.
            string := calc.readString(0x1234556, length := 0, encoding := "utf-16")

        ; Read a utf-8 encoded string which is 12 bytes long at address 0x1234556.
            string := calc.readString(0x1234556, 12)

        ; By default a null terminator is included at the end of written strings for writeString().
        ; The nullterminator property can be used to change this.
            _ClassMemory.insertNullTerminator := False ; This will change the property for all processes
            calc.insertNullTerminator := False ; Changes the property for just this process


    Notes:
        If the target process exits and then starts again (or restarts) you will need to free the derived object and then use the new operator to create a new object i.e.
        calc := [] ; or calc := "" ; free the object. This is actually optional if using the line below, as the line below would free the previous derived object calc prior to initialising the new copy.
        calc := new _ClassMemory("ahk_exe calc.exe") ; Create a new derived object to read calc's memory.
        isHandleValid() can be used to check if a target process has closed or restarted.
*/

class _ClassMemory
{
    ; List of useful accessible values. Some of these inherited values (the non objects) are set when the new operator is used.
    static baseAddress, hProcess, PID, currentProgram
    , insertNullTerminator := True
    , readStringLastError := False
    , isTarget64bit := False
    , ptrType := "UInt"
    , aTypeSize := {    "UChar":    1,  "Char":     1
                    ,   "UShort":   2,  "Short":    2
                    ,   "UInt":     4,  "Int":      4
                    ,   "UFloat":   4,  "Float":    4
                    ,   "Int64":    8,  "Double":   8}
    , aRights := {  "PROCESS_ALL_ACCESS": 0x001F0FFF
                ,   "PROCESS_CREATE_PROCESS": 0x0080
                ,   "PROCESS_CREATE_THREAD": 0x0002
                ,   "PROCESS_DUP_HANDLE": 0x0040
                ,   "PROCESS_QUERY_INFORMATION": 0x0400
                ,   "PROCESS_QUERY_LIMITED_INFORMATION": 0x1000
                ,   "PROCESS_SET_INFORMATION": 0x0200
                ,   "PROCESS_SET_QUOTA": 0x0100
                ,   "PROCESS_SUSPEND_RESUME": 0x0800
                ,   "PROCESS_TERMINATE": 0x0001
                ,   "PROCESS_VM_OPERATION": 0x0008
                ,   "PROCESS_VM_READ": 0x0010
                ,   "PROCESS_VM_WRITE": 0x0020
                ,   "SYNCHRONIZE": 0x00100000}


    ; Method:    __new(program, dwDesiredAccess := "", byRef handle := "", windowMatchMode := 3)
    ; Example:  derivedObject := new _ClassMemory("ahk_exe calc.exe")
    ;           This is the first method which should be called when trying to access a program's memory.
    ;           If the process is successfully opened, an object is returned which can be used to read that processes memory space.
    ;           [derivedObject].hProcess stores the opened handle.
    ;           If the target process closes and re-opens, simply free the derived object and use the new operator again to open a new handle.
    ; Parameters:
    ;   program             The program to be opened. This can be any AHK windowTitle identifier, such as
    ;                       ahk_exe, ahk_class, ahk_pid, or simply the window title. e.g. "ahk_exe calc.exe" or "Calculator".
    ;                       It's safer not to use the window title, as some things can have the same window title e.g. an open folder called "Starcraft II"
    ;                       would have the same window title as the game itself.
    ;                       *'DetectHiddenWindows, On' is required for hidden windows*
    ;   dwDesiredAccess     The access rights requested when opening the process.
    ;                       If this parameter is null the process will be opened with the following rights
    ;                       PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, & SYNCHRONIZE
    ;                       This access level is sufficient to allow all of the methods in this class to work.
    ;                       Specific process access rights are listed here http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
    ;   handle (Output)     Optional variable in which a copy of the opened processes handle will be stored.
    ;                       Values:
    ;                           Null    OpenProcess failed. The script may need to be run with admin rights admin,
    ;                                   and/or with the use of _ClassMemory.setSeDebugPrivilege(). Consult A_LastError for more information.
    ;                           0       The program isn't running (not found) or you passed an incorrect program identifier parameter.
    ;                                   In some cases _ClassMemory.setSeDebugPrivilege() may be required.
    ;                           Positive Integer    A handle to the process. (Success)
    ;   windowMatchMode -   Determines the matching mode used when finding the program (windowTitle).
    ;                       The default value is 3 i.e. an exact match. Refer to AHK's setTitleMathMode for more information.
    ; Return Values:
    ;   Object  On success an object is returned which can be used to read the processes memory.
    ;   Null    Failure. A_LastError and the optional handle parameter can be consulted for more information.


    __new(program, dwDesiredAccess := "", byRef handle := "", windowMatchMode := 3)
    {
        if this.PID := handle := this.findPID(program, windowMatchMode) ; set handle to 0 if program not found
        {
            ; This default access level is sufficient to read and write memory addresses, and to perform pattern scans.
            ; if the program is run using admin privileges, then this script will also need admin privileges
            if dwDesiredAccess is not integer
                dwDesiredAccess := this.aRights.PROCESS_QUERY_INFORMATION | this.aRights.PROCESS_VM_OPERATION | this.aRights.PROCESS_VM_READ | this.aRights.PROCESS_VM_WRITE
            dwDesiredAccess |= this.aRights.SYNCHRONIZE ; add SYNCHRONIZE to all handles to allow isHandleValid() to work

            if this.hProcess := handle := this.OpenProcess(this.PID, dwDesiredAccess) ; NULL/Blank if failed to open process for some reason
            {
                this.pNumberOfBytesRead := DllCall("GlobalAlloc", "UInt", 0x0040, "Ptr", A_PtrSize, "Ptr") ; 0x0040 initialise to 0
                this.pNumberOfBytesWritten := DllCall("GlobalAlloc", "UInt", 0x0040, "Ptr", A_PtrSize, "Ptr") ; initialise to 0

                this.readStringLastError := False
                this.currentProgram := program
                if this.isTarget64bit := this.isTargetProcess64Bit(this.PID, this.hProcess, dwDesiredAccess)
                    this.ptrType := "Int64"
                else this.ptrType := "UInt" ; If false or Null (fails) assume 32bit

                ; if script is 64 bit, getModuleBaseAddress() should always work
                ; if target app is truly 32 bit, then getModuleBaseAddress()
                ; will work when script is 32 bit
                if (A_PtrSize != 4 || !this.isTarget64bit)
                    this.BaseAddress := this.getModuleBaseAddress()

                ; If the above failed or wasn't called, fall back to alternate method
                if this.BaseAddress < 0 || !this.BaseAddress
                    this.BaseAddress := this.getProcessBaseAddress(program, windowMatchMode)

                return this
            }
        }
        return
    }

    __delete()
    {
        this.closeHandle(this.hProcess)
        if this.pNumberOfBytesRead
            DllCall("GlobalFree", "Ptr", this.pNumberOfBytesRead)
        if this.pNumberOfBytesWritten
            DllCall("GlobalFree", "Ptr", this.pNumberOfBytesWritten)
        return
    }

    version()
    {
        return 2.92
    }

    findPID(program, windowMatchMode := "3")
    {
        ; If user passes an AHK_PID, don't bother searching. There are cases where searching windows for PIDs
        ; wont work - console apps
        if RegExMatch(program, "i)\s*AHK_PID\s+(0x[[:xdigit:]]+|\d+)", pid)
            return pid1
        if windowMatchMode
        {
            ; This is a string and will not contain the 0x prefix
            mode := A_TitleMatchMode
            ; remove hex prefix as SetTitleMatchMode will throw a run time error. This will occur if integer mode is set to hex and user passed an int (unquoted)
            StringReplace, windowMatchMode, windowMatchMode, 0x
            SetTitleMatchMode, %windowMatchMode%
        }
        WinGet, pid, pid, %program%
        if windowMatchMode
            SetTitleMatchMode, %mode%    ; In case executed in autoexec

        ; If use 'ahk_exe test.exe' and winget fails (which can happen when setSeDebugPrivilege is required),
        ; try using the process command. When it fails due to setSeDebugPrivilege, setSeDebugPrivilege will still be required to openProcess
        ; This should also work for apps without windows.
        if (!pid && RegExMatch(program, "i)\bAHK_EXE\b\s*(.*)", fileName))
        {
            ; remove any trailing AHK_XXX arguments
            filename := RegExReplace(filename1, "i)\bahk_(class|id|pid|group)\b.*", "")
            filename := trim(filename)    ; extra spaces will make process command fail
            ; AHK_EXE can be the full path, so just get filename
            SplitPath, fileName , fileName
            if (fileName) ; if filename blank, scripts own pid is returned
            {
                process, Exist, %fileName%
                pid := ErrorLevel
            }
        }

        return pid ? pid : 0 ; PID is null on fail, return 0
    }
    ; Method:   isHandleValid()
    ;           This method provides a means to check if the internal process handle is still valid
    ;           or in other words, the specific target application instance (which you have been reading from)
    ;           has closed or restarted.
    ;           For example, if the target application closes or restarts the handle will become invalid
    ;           and subsequent calls to this method will return false.
    ;
    ; Return Values:
    ;   True    The handle is valid.
    ;   False   The handle is not valid.
    ;
    ; Notes:
    ;   This operation requires a handle with SYNCHRONIZE access rights.
    ;   All handles, even user specified ones are opened with the SYNCHRONIZE access right.

    isHandleValid()
    {
        return 0x102 = DllCall("WaitForSingleObject", "Ptr", this.hProcess, "UInt", 0)
        ; WaitForSingleObject return values
        ; -1 if called with null hProcess (sets lastError to 6 - invalid handle)
        ; 258 / 0x102 WAIT_TIMEOUT - if handle is valid (process still running)
        ; 0  WAIT_OBJECT_0 - if process has terminated
    }

    ; Method:   openProcess(PID, dwDesiredAccess)
    ;           ***Note:    This is an internal method which shouldn't be called directly unless you absolutely know what you are doing.
    ;                       This is because the new operator, in addition to calling this method also sets other values
    ;                       which are required for the other methods to work correctly.
    ; Parameters:
    ;   PID                 The Process ID of the target process.
    ;   dwDesiredAccess     The access rights requested when opening the process.
    ;                       Specific process access rights are listed here http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
    ; Return Values:
    ;   Null/blank          OpenProcess failed. If the target process has admin rights, then the script also needs to be ran as admin.
    ;                       _ClassMemory.setSeDebugPrivilege() may also be required.
    ;   Positive integer    A handle to the process.

    openProcess(PID, dwDesiredAccess)
    {
        r := DllCall("OpenProcess", "UInt", dwDesiredAccess, "Int", False, "UInt", PID, "Ptr")
        ; if it fails with 0x5 ERROR_ACCESS_DENIED, try enabling privilege ... lots of users never try this.
        ; there may be other errors which also require DebugPrivilege....
        if (!r && A_LastError = 5)
        {
            this.setSeDebugPrivilege(true) ; no harm in enabling it if it is already enabled by user
            if (r2 := DllCall("OpenProcess", "UInt", dwDesiredAccess, "Int", False, "UInt", PID, "Ptr"))
                return r2
            DllCall("SetLastError", "UInt", 5) ; restore original error if it doesnt work
        }
        ; If fails with 0x5 ERROR_ACCESS_DENIED (when setSeDebugPrivilege() is req.), the func. returns 0 rather than null!! Set it to null.
        ; If fails for another reason, then it is null.
        return r ? r : ""
    }

    ; Method:   closeHandle(hProcess)
    ;           Note:   This is an internal method which is automatically called when the script exits or the derived object is freed/destroyed.
    ;                   There is no need to call this method directly. If you wish to close the handle simply free the derived object.
    ;                   i.e. derivedObject := [] ; or derivedObject := ""
    ; Parameters:
    ;   hProcess        The handle to the process, as returned by openProcess().
    ; Return Values:
    ;   Non-Zero        Success
    ;   0               Failure

    closeHandle(hProcess)
    {
        return DllCall("CloseHandle", "Ptr", hProcess)
    }

    ; Methods:      numberOfBytesRead() / numberOfBytesWritten()
    ;               Returns the number of bytes read or written by the last ReadProcessMemory or WriteProcessMemory operation.
    ;
    ; Return Values:
    ;   zero or positive value      Number of bytes read/written
    ;   -1                          Failure. Shouldn't occur

    numberOfBytesRead()
    {
        return !this.pNumberOfBytesRead ? -1 : NumGet(this.pNumberOfBytesRead+0, "Ptr")
    }
    numberOfBytesWritten()
    {
        return !this.pNumberOfBytesWritten ? -1 : NumGet(this.pNumberOfBytesWritten+0, "Ptr")
    }


    ; Method:   read(address, type := "UInt", aOffsets*)
    ;           Reads various integer type values
    ; Parameters:
    ;       address -   The memory address of the value or if using the offset parameter,
    ;                   the base address of the pointer.
    ;       type    -   The integer type.
    ;                   Valid types are UChar, Char, UShort, Short, UInt, Int, Float, Int64 and Double.
    ;                   Note: Types must not contain spaces i.e. " UInt" or "UInt " will not work.
    ;                   When an invalid type is passed the method returns NULL and sets ErrorLevel to -2
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which holds the integer.
    ; Return Values:
    ;       integer -   Indicates success.
    ;       Null    -   Indicates failure. Check ErrorLevel and A_LastError for more information.
    ;       Note:       Since the returned integer value may be 0, to check for success/failure compare the result
    ;                   against null i.e. if (result = "") then an error has occurred.
    ;                   When reading doubles, adjusting "SetFormat, float, totalWidth.DecimalPlaces"
    ;                   may be required depending on your requirements.

    read(address, type := "UInt", aOffsets*)
    {
        ; If invalid type RPM() returns success (as bytes to read resolves to null in dllCall())
        ; so set errorlevel to invalid parameter for DLLCall() i.e. -2
        if !this.aTypeSize.hasKey(type)
            return "", ErrorLevel := -2
        if DllCall("ReadProcessMemory", "Ptr", this.hProcess, "Ptr", aOffsets.maxIndex() ? this.getAddressFromOffsets(address, aOffsets*) : address, type "*", result, "Ptr", this.aTypeSize[type], "Ptr", this.pNumberOfBytesRead)
            return result
        return
    }

    ; Method:   readRaw(address, byRef buffer, bytes := 4, aOffsets*)
    ;           Reads an area of the processes memory and stores it in the buffer variable
    ; Parameters:
    ;       address  -  The memory address of the area to read or if using the offsets parameter
    ;                   the base address of the pointer which points to the memory region.
    ;       buffer   -  The unquoted variable name for the buffer. This variable will receive the contents from the address space.
    ;                   This method calls varsetCapcity() to ensure the variable has an adequate size to perform the operation.
    ;                   If the variable already has a larger capacity (from a previous call to varsetcapcity()), then it will not be shrunk.
    ;                   Therefore it is the callers responsibility to ensure that any subsequent actions performed on the buffer variable
    ;                   do not exceed the bytes which have been read - as these remaining bytes could contain anything.
    ;       bytes   -   The number of bytes to be read.
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be read
    ; Return Values:
    ;       Non Zero -   Indicates success.
    ;       Zero     -   Indicates failure. Check errorLevel and A_LastError for more information
    ;
    ; Notes:            The contents of the buffer may then be retrieved using AHK's NumGet() and StrGet() functions.
    ;                   This method offers significant (~30% and up) performance boost when reading large areas of memory.
    ;                   As calling ReadProcessMemory for four bytes takes a similar amount of time as it does for 1,000 bytes.

    readRaw(address, byRef buffer, bytes := 4, aOffsets*)
    {
        VarSetCapacity(buffer, bytes)
        return DllCall("ReadProcessMemory", "Ptr", this.hProcess, "Ptr", aOffsets.maxIndex() ? this.getAddressFromOffsets(address, aOffsets*) : address, "Ptr", &buffer, "Ptr", bytes, "Ptr", this.pNumberOfBytesRead)
    }

    ; Method:   readString(address, sizeBytes := 0, encoding := "utf-8", aOffsets*)
    ;           Reads string values of various encoding types
    ; Parameters:
    ;       address -   The memory address of the value or if using the offset parameter,
    ;                   the base address of the pointer.
    ;       sizeBytes - The size (in bytes) of the string to be read.
    ;                   If zero is passed, then the function will read each character until a null terminator is found
    ;                   and then returns the entire string.
    ;       encoding -  This refers to how the string is stored in the program's memory.
    ;                   UTF-8 and UTF-16 are common. Refer to the AHK manual for other encoding types.
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which holds the string.
    ;
    ;  Return Values:
    ;       String -    On failure an empty (null) string is always returned. Since it's possible for the actual string
    ;                   being read to be null (empty), then a null return value should not be used to determine failure of the method.
    ;                   Instead the property [derivedObject].ReadStringLastError can be used to check for success/failure.
    ;                   This property is set to 0 on success and 1 on failure. On failure ErrorLevel and A_LastError should be consulted
    ;                   for more information.
    ; Notes:
    ;       For best performance use the sizeBytes parameter to specify the exact size of the string.
    ;       If the exact size is not known and the string is null terminated, then specifying the maximum
    ;       possible size of the string will yield the same performance.
    ;       If neither the actual or maximum size is known and the string is null terminated, then specifying
    ;       zero for the sizeBytes parameter is fine. Generally speaking for all intents and purposes the performance difference is
    ;       inconsequential.

    readString(address, sizeBytes := 0, encoding := "UTF-8", aOffsets*)
    {
        bufferSize := VarSetCapacity(buffer, sizeBytes ? sizeBytes : 100, 0)
        this.ReadStringLastError := False
        if aOffsets.maxIndex()
            address := this.getAddressFromOffsets(address, aOffsets*)
        if !sizeBytes  ; read until null terminator is found or something goes wrong
        {
            ; Even if there are multi-byte-characters (bigger than the encodingSize i.e. surrogates) in the string, when reading in encodingSize byte chunks they will never register as null (as they will have bits set on those bytes)
            if (encoding = "utf-16" || encoding = "cp1200")
                encodingSize := 2, charType := "UShort", loopCount := 2
            else encodingSize := 1, charType := "Char", loopCount := 4
            Loop
            {   ; Lets save a few reads by reading in 4 byte chunks
                if !DllCall("ReadProcessMemory", "Ptr", this.hProcess, "Ptr", address + ((outterIndex := A_index) - 1) * 4, "Ptr", &buffer, "Ptr", 4, "Ptr", this.pNumberOfBytesRead) || ErrorLevel
                    return "", this.ReadStringLastError := True
                else loop, %loopCount%
                {
                    if NumGet(buffer, (A_Index - 1) * encodingSize, charType) = 0 ; NULL terminator
                    {
                        if (bufferSize < sizeBytes := outterIndex * 4 - (4 - A_Index * encodingSize))
                            VarSetCapacity(buffer, sizeBytes)
                        break, 2
                    }
                }
            }
        }
        if DllCall("ReadProcessMemory", "Ptr", this.hProcess, "Ptr", address, "Ptr", &buffer, "Ptr", sizeBytes, "Ptr", this.pNumberOfBytesRead)
            return StrGet(&buffer,, encoding)
        return "", this.ReadStringLastError := True
    }

    ; Method:  writeString(address, string, encoding := "utf-8", aOffsets*)
    ;          Encodes and then writes a string to the process.
    ; Parameters:
    ;       address -   The memory address to which data will be written or if using the offset parameter,
    ;                   the base address of the pointer.
    ;       string -    The string to be written.
    ;       encoding -  This refers to how the string is to be stored in the program's memory.
    ;                   UTF-8 and UTF-16 are common. Refer to the AHK manual for other encoding types.
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be written to.
    ; Return Values:
    ;       Non Zero -   Indicates success.
    ;       Zero     -   Indicates failure. Check errorLevel and A_LastError for more information
    ; Notes:
    ;       By default a null terminator is included at the end of written strings.
    ;       This behaviour is determined by the property [derivedObject].insertNullTerminator
    ;       If this property is true, then a null terminator will be included.

    writeString(address, string, encoding := "utf-8", aOffsets*)
    {
        encodingSize := (encoding = "utf-16" || encoding = "cp1200") ? 2 : 1
        requiredSize := StrPut(string, encoding) * encodingSize - (this.insertNullTerminator ? 0 : encodingSize)
        VarSetCapacity(buffer, requiredSize)
        StrPut(string, &buffer, StrLen(string) + (this.insertNullTerminator ?  1 : 0), encoding)
        return DllCall("WriteProcessMemory", "Ptr", this.hProcess, "Ptr", aOffsets.maxIndex() ? this.getAddressFromOffsets(address, aOffsets*) : address, "Ptr", &buffer, "Ptr", requiredSize, "Ptr", this.pNumberOfBytesWritten)
    }

    ; Method:   write(address, value, type := "Uint", aOffsets*)
    ;           Writes various integer type values to the process.
    ; Parameters:
    ;       address -   The memory address to which data will be written or if using the offset parameter,
    ;                   the base address of the pointer.
    ;       type    -   The integer type.
    ;                   Valid types are UChar, Char, UShort, Short, UInt, Int, Float, Int64 and Double.
    ;                   Note: Types must not contain spaces i.e. " UInt" or "UInt " will not work.
    ;                   When an invalid type is passed the method returns NULL and sets ErrorLevel to -2
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be written to.
    ; Return Values:
    ;       Non Zero -  Indicates success.
    ;       Zero     -  Indicates failure. Check errorLevel and A_LastError for more information
    ;       Null    -   An invalid type was passed. Errorlevel is set to -2

    write(address, value, type := "Uint", aOffsets*)
    {
        if !this.aTypeSize.hasKey(type)
            return "", ErrorLevel := -2
        return DllCall("WriteProcessMemory", "Ptr", this.hProcess, "Ptr", aOffsets.maxIndex() ? this.getAddressFromOffsets(address, aOffsets*) : address, type "*", value, "Ptr", this.aTypeSize[type], "Ptr", this.pNumberOfBytesWritten)
    }

    ; Method:   writeRaw(address, pBuffer, sizeBytes, aOffsets*)
    ;           Writes a buffer to the process.
    ; Parameters:
    ;   address -       The memory address to which the contents of the buffer will be written
    ;                   or if using the offset parameter, the base address of the pointer.
    ;   pBuffer -       A pointer to the buffer which is to be written.
    ;                   This does not necessarily have to be the beginning of the buffer itself e.g. pBuffer := &buffer + offset
    ;   sizeBytes -     The number of bytes which are to be written from the buffer.
    ;   aOffsets* -     A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be written to.
    ; Return Values:
    ;       Non Zero -  Indicates success.
    ;       Zero     -  Indicates failure. Check errorLevel and A_LastError for more information

    writeRaw(address, pBuffer, sizeBytes, aOffsets*)
    {
        return DllCall("WriteProcessMemory", "Ptr", this.hProcess, "Ptr", aOffsets.maxIndex() ? this.getAddressFromOffsets(address, aOffsets*) : address, "Ptr", pBuffer, "Ptr", sizeBytes, "Ptr", this.pNumberOfBytesWritten)
    }

    ; Method:   writeBytes(address, hexStringOrByteArray, aOffsets*)
    ;           Writes a sequence of byte values to the process.
    ; Parameters:
    ;   address -       The memory address to where the bytes will be written
    ;                   or if using the offset parameter, the base address of the pointer.
    ;   hexStringOrByteArray -  This can either be either a string (A) or an object/array (B) containing the values to be written.
    ;
    ;               A) HexString -      A string of hex bytes.  The '0x' hex prefix is optional.
    ;                                   Bytes can optionally be separated using the space or tab characters.
    ;                                   Each byte must be two characters in length i.e. '04' or '0x04' (not '4' or '0x4')
    ;               B) Object/Array -   An array containing hex or decimal byte values e.g. array := [10, 29, 0xA]
    ;
    ;   aOffsets* -     A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be written to.
    ; Return Values:
    ;       -1, -2, -3, -4  - Error with the hexstring. Refer to hexStringToPattern() for details.
    ;       Other Non Zero  - Indicates success.
    ;       Zero            - Indicates write failure. Check errorLevel and A_LastError for more information
    ;
    ;   Examples:
    ;                   writeBytes(0xAABBCC11, "DEADBEEF")          ; Writes the bytes DE AD BE EF starting at address  0xAABBCC11
    ;                   writeBytes(0xAABBCC11, [10, 20, 0xA, 2])

    writeBytes(address, hexStringOrByteArray, aOffsets*)
    {
        if !IsObject(hexStringOrByteArray)
        {
            if !IsObject(hexStringOrByteArray := this.hexStringToPattern(hexStringOrByteArray))
                return hexStringOrByteArray
        }
        sizeBytes := this.getNeedleFromAOBPattern("", buffer, hexStringOrByteArray*)
        return this.writeRaw(address, &buffer, sizeBytes, aOffsets*)
    }

    ; Method:           pointer(address, finalType := "UInt", offsets*)
    ;                   This is an internal method. Since the other various methods all offer this functionality, they should be used instead.
    ;                   This will read integer values of both pointers and non-pointers (i.e. a single memory address)
    ; Parameters:
    ;   address -       The base address of the pointer or the memory address for a non-pointer.
    ;   finalType -     The type of integer stored at the final address.
    ;                   Valid types are UChar, Char, UShort, Short, UInt, Int, Float, Int64 and Double.
    ;                   Note: Types must not contain spaces i.e. " UInt" or "UInt " will not work.
    ;                   When an invalid type is passed the method returns NULL and sets ErrorLevel to -2
    ;   aOffsets* -     A variadic list of offsets used to calculate the pointers final address.
    ; Return Values: (The same as the read() method)
    ;       integer -   Indicates success.
    ;       Null    -   Indicates failure. Check ErrorLevel and A_LastError for more information.
    ;       Note:       Since the returned integer value may be 0, to check for success/failure compare the result
    ;                   against null i.e. if (result = "") then an error has occurred.
    ;                   If the target application is 64bit the pointers are read as an 8 byte Int64 (this.PtrType)

    pointer(address, finalType := "UInt", offsets*)
    {
        For index, offset in offsets
            address := this.Read(address, this.ptrType) + offset
        Return this.Read(address, finalType)
    }

    ; Method:               getAddressFromOffsets(address, aOffsets*)
    ;                       Returns the final address of a pointer.
    ;                       This is an internal method used by various methods however, this method may be useful if you are
    ;                       looking to eliminate the overhead overhead associated with reading pointers which only change
    ;                       on startup or map/level change. In other words you can cache the final address and
    ;                       read from this address directly.
    ; Parameters:
    ;   address             The base address of the pointer.
    ;   aOffsets*           A variadic list of offsets used to calculate the pointers final address.
    ;                       At least one offset must be present.
    ; Return Values:
    ;   Positive integer    The final memory address pointed to by the pointer.
    ;   Negative integer    Failure
    ;   Null                Failure
    ; Note:                 If the target application is 64bit the pointers are read as an 8 byte Int64 (this.PtrType)

    getAddressFromOffsets(address, aOffsets*)
    {
        return  aOffsets.Remove() + this.pointer(address, this.ptrType, aOffsets*) ; remove the highest key so can use pointer() to find final memory address (minus the last offset)
    }

    ; Interesting note:
    ; Although handles are 64-bit pointers, only the less significant 32 bits are employed in them for the purpose
    ; of better compatibility (for example, to enable 32-bit and 64-bit processes interact with each other)
    ; Here are examples of such types: HANDLE, HWND, HMENU, HPALETTE, HBITMAP, etc.
    ; http://www.viva64.com/en/k/0005/



    ; Method:   getProcessBaseAddress(WindowTitle, windowMatchMode := 3)
    ;           Returns the base address of a process. In most cases this will provide the same result as calling getModuleBaseAddress() (when passing
    ;           a null value as the module parameter), however getProcessBaseAddress() will usually work regardless of the bitness
    ;           of both the AHK exe and the target process.
    ;           *This method relies on the target process having a window and will not work for console apps*
    ;           *'DetectHiddenWindows, On' is required for hidden windows*
    ;           ***If this returns an incorrect value, try using (the MORE RELIABLE) getModuleBaseAddress() instead.***
    ; Parameters:
    ;   windowTitle         This can be any AHK windowTitle identifier, such as
    ;                       ahk_exe, ahk_class, ahk_pid, or simply the window title. e.g. "ahk_exe calc.exe" or "Calculator".
    ;                       It's safer not to use the window title, as some things can have the same window title e.g. an open folder called "Starcraft II"
    ;                       would have the same window title as the game itself.
    ;   windowMatchMode     Determines the matching mode used when finding the program's window (windowTitle).
    ;                       The default value is 3 i.e. an exact match. The current matchmode will be used if the parameter is null or 0.
    ;                       Refer to AHK's setTitleMathMode for more information.
    ; Return Values:
    ;   Positive integer    The base address of the process (success).
    ;   Null                The process's window couldn't be found.
    ;   0                   The GetWindowLong or GetWindowLongPtr call failed. Try getModuleBaseAddress() instead.


    getProcessBaseAddress(windowTitle, windowMatchMode := "3")
    {
        if (windowMatchMode && A_TitleMatchMode != windowMatchMode)
        {
            mode := A_TitleMatchMode ; This is a string and will not contain the 0x prefix
            StringReplace, windowMatchMode, windowMatchMode, 0x ; remove hex prefix as SetTitleMatchMode will throw a run time error. This will occur if integer mode is set to hex and matchmode param is passed as an number not a string.
            SetTitleMatchMode, %windowMatchMode%    ;mode 3 is an exact match
        }
        WinGet, hWnd, ID, %WindowTitle%
        if mode
            SetTitleMatchMode, %mode%    ; In case executed in autoexec
        if !hWnd
            return ; return blank failed to find window
       ; GetWindowLong returns a Long (Int) and GetWindowLongPtr return a Long_Ptr
        return DllCall(A_PtrSize = 4     ; If DLL call fails, returned value will = 0
            ? "GetWindowLong"
            : "GetWindowLongPtr"
            , "Ptr", hWnd, "Int", -6, A_Is64bitOS ? "Int64" : "UInt")
            ; For the returned value when the OS is 64 bit use Int64 to prevent negative overflow when AHK is 32 bit and target process is 64bit
            ; however if the OS is 32 bit, must use UInt, otherwise the number will be huge (however it will still work as the lower 4 bytes are correct)
            ; Note - it's the OS bitness which matters here, not the scripts/AHKs
    }

    ; http://winprogger.com/getmodulefilenameex-enumprocessmodulesex-failures-in-wow64/
    ; http://stackoverflow.com/questions/3801517/how-to-enum-modules-in-a-64bit-process-from-a-32bit-wow-process

    ; Method:            getModuleBaseAddress(module := "", byRef aModuleInfo := "")
    ; Parameters:
    ;   moduleName -    The file name of the module/dll to find e.g. "calc.exe", "GDI32.dll", "Bass.dll" etc
    ;                   If no module (null) is specified, the address of the base module - main()/process will be returned
    ;                   e.g. for calc.exe the following two method calls are equivalent getModuleBaseAddress() and getModuleBaseAddress("calc.exe")
    ;   aModuleInfo -   (Optional) A module Info object is returned in this variable. If method fails this variable is made blank.
    ;                   This object contains the keys: name, fileName, lpBaseOfDll, SizeOfImage, and EntryPoint
    ; Return Values:
    ;   Positive integer - The module's base/load address (success).
    ;   -1 - Module not found
    ;   -3 - EnumProcessModulesEx failed
    ;   -4 - The AHK script is 32 bit and you are trying to access the modules of a 64 bit target process. Or the target process has been closed.
    ; Notes:    A 64 bit AHK can enumerate the modules of a target 64 or 32 bit process.
    ;           A 32 bit AHK can only enumerate the modules of a 32 bit process
    ;           This method requires PROCESS_QUERY_INFORMATION + PROCESS_VM_READ access rights. These are included by default with this class.

    getModuleBaseAddress(moduleName := "", byRef aModuleInfo := "")
    {
        aModuleInfo := ""
        if (moduleName = "")
            moduleName := this.GetModuleFileNameEx(0, True) ; main executable module of the process - get just fileName no path
        if r := this.getModules(aModules, True) < 0
            return r ; -4, -3
        return aModules.HasKey(moduleName) ? (aModules[moduleName].lpBaseOfDll, aModuleInfo := aModules[moduleName]) : -1
        ; no longer returns -5 for failed to get module info
    }


    ; Method:                   getModuleFromAddress(address, byRef aModuleInfo)
    ;                           Finds the module in which the address resides.
    ; Parameters:
    ;   address                 The address of interest.
    ;
    ;   aModuleInfo             (Optional) An unquoted variable name. If the module associated with the address is found,
    ;                           a moduleInfo object will be stored in this variable. This object has the
    ;                           following keys: name, fileName, lpBaseOfDll, SizeOfImage, and EntryPoint.
    ;                           If the address is not found to reside inside a module, the passed variable is
    ;                           made blank/null.
    ;   offsetFromModuleBase    (Optional) Stores the relative offset from the module base address
    ;                           to the specified address. If the method fails then the passed variable is set to blank/empty.
    ; Return Values:
    ;   1                       Success - The address is contained within a module.
    ;   -1                      The specified address does not reside within a loaded module.
    ;   -3                      EnumProcessModulesEx failed.
    ;   -4                      The AHK script is 32 bit and you are trying to access the modules of a 64 bit target process.

    getModuleFromAddress(address, byRef aModuleInfo, byRef offsetFromModuleBase := "")
    {
        aModuleInfo := offsetFromModule := ""
        if result := this.getmodules(aModules) < 0
            return result ; error -3, -4
        for k, module in aModules
        {
            if (address >= module.lpBaseOfDll && address < module.lpBaseOfDll + module.SizeOfImage)
                return 1, aModuleInfo := module, offsetFromModuleBase := address - module.lpBaseOfDll
        }
        return -1
    }

    ; SeDebugPrivileges is required to read/write memory in some programs.
    ; This only needs to be called once when the script starts,
    ; regardless of the number of programs being read (or if the target programs restart)
    ; Call this before attempting to call any other methods in this class
    ; i.e. call _ClassMemory.setSeDebugPrivilege() at the very start of the script.

    setSeDebugPrivilege(enable := True)
    {
        h := DllCall("OpenProcess", "UInt", 0x0400, "Int", false, "UInt", DllCall("GetCurrentProcessId"), "Ptr")
        ; Open an adjustable access token with this process (TOKEN_ADJUST_PRIVILEGES = 32)
        DllCall("Advapi32.dll\OpenProcessToken", "Ptr", h, "UInt", 32, "PtrP", t)
        VarSetCapacity(ti, 16, 0)  ; structure of privileges
        NumPut(1, ti, 0, "UInt")  ; one entry in the privileges array...
        ; Retrieves the locally unique identifier of the debug privilege:
        DllCall("Advapi32.dll\LookupPrivilegeValue", "Ptr", 0, "Str", "SeDebugPrivilege", "Int64P", luid)
        NumPut(luid, ti, 4, "Int64")
        if enable
            NumPut(2, ti, 12, "UInt")  ; enable this privilege: SE_PRIVILEGE_ENABLED = 2
        ; Update the privileges of this process with the new access token:
        r := DllCall("Advapi32.dll\AdjustTokenPrivileges", "Ptr", t, "Int", false, "Ptr", &ti, "UInt", 0, "Ptr", 0, "Ptr", 0)
        DllCall("CloseHandle", "Ptr", t)  ; close this access token handle to save memory
        DllCall("CloseHandle", "Ptr", h)  ; close this process handle to save memory
        return r
    }


    ; Method:  isTargetProcess64Bit(PID, hProcess := "", currentHandleAccess := "")
    ;          Determines if a process is 64 bit.
    ; Parameters:
    ;   PID                     The Process ID of the target process. If required this is used to open a temporary process handle.
    ;   hProcess                (Optional) A handle to the process, as returned by openProcess() i.e. [derivedObject].hProcess
    ;   currentHandleAccess     (Optional) The dwDesiredAccess value used when opening the process handle which has been
    ;                           passed as the hProcess parameter. If specifying hProcess, you should also specify this value.
    ; Return Values:
    ;   True    The target application is 64 bit.
    ;   False   The target application is 32 bit.
    ;   Null    The method failed.
    ; Notes:
    ;   This is an internal method which is called when the new operator is used. It is used to set the pointer type for 32/64 bit applications so the pointer methods will work.
    ;   This operation requires a handle with PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access rights.
    ;   If the currentHandleAccess parameter does not contain these rights (or not passed) or if the hProcess (process handle) is invalid (or not passed)
    ;   a temporary handle is opened to perform this operation. Otherwise if hProcess and currentHandleAccess appear valid
    ;   the passed hProcess is used to perform the operation.

    isTargetProcess64Bit(PID, hProcess := "", currentHandleAccess := "")
    {
        if !A_Is64bitOS
            return False
        ; If insufficient rights, open a temporary handle
        else if !hProcess || !(currentHandleAccess & (this.aRights.PROCESS_QUERY_INFORMATION | this.aRights.PROCESS_QUERY_LIMITED_INFORMATION))
            closeHandle := hProcess := this.openProcess(PID, this.aRights.PROCESS_QUERY_INFORMATION)
        if (hProcess && DllCall("IsWow64Process", "Ptr", hProcess, "Int*", Wow64Process))
            result := !Wow64Process
        return result, closeHandle ? this.CloseHandle(hProcess) : ""
    }
    /*
        _Out_  PBOOL Wow64Proces value set to:
        True if the process is running under WOW64 - 32bit app on 64bit OS.
        False if the process is running under 32-bit Windows!
        False if the process is a 64-bit application running under 64-bit Windows.
    */

    ; Method: suspend() / resume()
    ; Notes:
    ;   These are undocumented Windows functions which suspend and resume the process. Here be dragons.
    ;   The process handle must have PROCESS_SUSPEND_RESUME access rights.
    ;   That is, you must specify this when using the new operator, as it is not included.
    ;   Some people say it requires more rights and just use PROCESS_ALL_ACCESS, however PROCESS_SUSPEND_RESUME has worked for me.
    ;   Suspending a process manually can be quite helpful when reversing memory addresses and pointers, although it's not at all required.
    ;   As an unorthodox example, memory addresses holding pointers are often stored in a slightly obfuscated manner i.e. they require bit operations to calculate their
    ;   true stored value (address). This obfuscation can prevent Cheat Engine from finding the true origin of a pointer or links to other memory regions. If there
    ;   are no static addresses between the obfuscated address and the final destination address then CE wont find anything (there are ways around this in CE). One way around this is to
    ;   suspend the process, write the true/deobfuscated value to the address and then perform your scans. Afterwards write back the original values and resume the process.

    suspend()
    {
        return DllCall("ntdll\NtSuspendProcess", "Ptr", this.hProcess)
    }

    resume()
    {
        return DllCall("ntdll\NtResumeProcess", "Ptr", this.hProcess)
    }

    ; Method:               getModules(byRef aModules, useFileNameAsKey := False)
    ;                       Stores the process's loaded modules as an array of (object) modules in the aModules parameter.
    ; Parameters:
    ;   aModules            An unquoted variable name. The loaded modules of the process are stored in this variable as an array of objects.
    ;                       Each object in this array has the following keys: name, fileName, lpBaseOfDll, SizeOfImage, and EntryPoint.
    ;   useFileNameAsKey    When true, the file name e.g. GDI32.dll is used as the lookup key for each module object.
    ; Return Values:
    ;   Positive integer    The size of the aModules array. (Success)
    ;   -3                  EnumProcessModulesEx failed.
    ;   -4                  The AHK script is 32 bit and you are trying to access the modules of a 64 bit target process.

    getModules(byRef aModules, useFileNameAsKey := False)
    {
        if (A_PtrSize = 4 && this.IsTarget64bit)
            return -4 ; AHK is 32bit and target process is 64 bit, this function wont work
        aModules := []
        if !moduleCount := this.EnumProcessModulesEx(lphModule)
            return -3
        loop % moduleCount
        {
            this.GetModuleInformation(hModule := numget(lphModule, (A_index - 1) * A_PtrSize), aModuleInfo)
            aModuleInfo.Name := this.GetModuleFileNameEx(hModule)
            filePath := aModuleInfo.name
            SplitPath, filePath, fileName
            aModuleInfo.fileName := fileName
            if useFileNameAsKey
                aModules[fileName] := aModuleInfo
            else aModules.insert(aModuleInfo)
        }
        return moduleCount
    }



    getEndAddressOfLastModule(byRef aModuleInfo := "")
    {
        if !moduleCount := this.EnumProcessModulesEx(lphModule)
            return -3
        hModule := numget(lphModule, (moduleCount - 1) * A_PtrSize)
        if this.GetModuleInformation(hModule, aModuleInfo)
            return aModuleInfo.lpBaseOfDll + aModuleInfo.SizeOfImage
        return -5
    }

    ; lpFilename [out]
    ; A pointer to a buffer that receives the fully qualified path to the module.
    ; If the size of the file name is larger than the value of the nSize parameter, the function succeeds
    ; but the file name is truncated and null-terminated.
    ; If the buffer is adequate the string is still null terminated.

    GetModuleFileNameEx(hModule := 0, fileNameNoPath := False)
    {
        ; ANSI MAX_PATH = 260 (includes null) - unicode can be ~32K.... but no one would ever have one that size
        ; So just give it a massive size and don't bother checking. Most coders just give it MAX_PATH size anyway
        VarSetCapacity(lpFilename, 2048 * (A_IsUnicode ? 2 : 1))
        DllCall("psapi\GetModuleFileNameEx"
                    , "Ptr", this.hProcess
                    , "Ptr", hModule
                    , "Str", lpFilename
                    , "Uint", 2048 / (A_IsUnicode ? 2 : 1))
        if fileNameNoPath
            SplitPath, lpFilename, lpFilename ; strips the path so = GDI32.dll

        return lpFilename
    }

    ; dwFilterFlag
    ;   LIST_MODULES_DEFAULT    0x0
    ;   LIST_MODULES_32BIT      0x01
    ;   LIST_MODULES_64BIT      0x02
    ;   LIST_MODULES_ALL        0x03
    ; If the function is called by a 32-bit application running under WOW64, the dwFilterFlag option
    ; is ignored and the function provides the same results as the EnumProcessModules function.
    EnumProcessModulesEx(byRef lphModule, dwFilterFlag := 0x03)
    {
        lastError := A_LastError
        size := VarSetCapacity(lphModule, 4)
        loop
        {
            DllCall("psapi\EnumProcessModulesEx"
                        , "Ptr", this.hProcess
                        , "Ptr", &lphModule
                        , "Uint", size
                        , "Uint*", reqSize
                        , "Uint", dwFilterFlag)
            if ErrorLevel
                return 0
            else if (size >= reqSize)
                break
            else size := VarSetCapacity(lphModule, reqSize)
        }
        ; On first loop it fails with A_lastError = 0x299 as its meant to
        ; might as well reset it to its previous version
        DllCall("SetLastError", "UInt", lastError)
        return reqSize // A_PtrSize ; module count  ; sizeof(HMODULE) - enumerate the array of HMODULEs
    }

    GetModuleInformation(hModule, byRef aModuleInfo)
    {
        VarSetCapacity(MODULEINFO, A_PtrSize * 3), aModuleInfo := []
        return DllCall("psapi\GetModuleInformation"
                    , "Ptr", this.hProcess
                    , "Ptr", hModule
                    , "Ptr", &MODULEINFO
                    , "UInt", A_PtrSize * 3)
                , aModuleInfo := {  lpBaseOfDll: numget(MODULEINFO, 0, "Ptr")
                                ,   SizeOfImage: numget(MODULEINFO, A_PtrSize, "UInt")
                                ,   EntryPoint: numget(MODULEINFO, A_PtrSize * 2, "Ptr") }
    }

    ; Method:           hexStringToPattern(hexString)
    ;                   Converts the hex string parameter into an array of bytes pattern (AOBPattern) that
    ;                   can be passed to the various pattern scan methods i.e.  modulePatternScan(), addressPatternScan(), rawPatternScan(), and processPatternScan()
    ;
    ; Parameters:
    ;   hexString -     A string of hex bytes.  The '0x' hex prefix is optional.
    ;                   Bytes can optionally be separated using the space or tab characters.
    ;                   Each byte must be two characters in length i.e. '04' or '0x04' (not '4' or '0x4')
    ;                   ** Unlike the other methods, wild card bytes MUST be denoted using '??' (two question marks)**
    ;
    ; Return Values:
    ;   Object          Success - The returned object contains the AOB pattern.
    ;   -1              An empty string was passed.
    ;   -2              Non hex character present.  Acceptable characters are A-F, a-F, 0-9, ?, space, tab, and 0x (hex prefix).
    ;   -3              Non-even wild card character count. One of the wild card bytes is missing a '?' e.g. '?' instead of '??'.
    ;   -4              Non-even character count. One of the hex bytes is probably missing a character e.g. '4' instead of '04'.
    ;
    ;   Examples:
    ;                   pattern := hexStringToPattern("DEADBEEF02")
    ;                   pattern := hexStringToPattern("0xDE0xAD0xBE0xEF0x02")
    ;                   pattern := hexStringToPattern("DE AD BE EF 02")
    ;                   pattern := hexStringToPattern("0xDE 0xAD 0xBE 0xEF 0x02")
    ;
    ;                   This will mark the third byte as wild:
    ;                   pattern := hexStringToPattern("DE AD ?? EF 02")
    ;                   pattern := hexStringToPattern("0xDE 0xAD ?? 0xEF 0x02")
    ;
    ;                   The returned pattern can then be passed to the various pattern scan methods, for example:
    ;                   pattern := hexStringToPattern("DE AD BE EF 02")
    ;                   memObject.processPatternScan(,, pattern*)   ; Note the '*'

    hexStringToPattern(hexString)
    {
        AOBPattern := []
        hexString := RegExReplace(hexString, "(\s|0x)")
        StringReplace, hexString, hexString, ?, ?, UseErrorLevel
        wildCardCount := ErrorLevel

        if !length := StrLen(hexString)
            return -1 ; no str
        else if RegExMatch(hexString, "[^0-9a-fA-F?]")
            return -2 ; non hex character and not a wild card
        else if Mod(wildCardCount, 2)
            return -3 ; non-even wild card character count
        else if Mod(length, 2)
            return -4 ; non-even character count
        loop, % length/2
        {
            value := "0x" SubStr(hexString, 1 + 2 * (A_index-1), 2)
            AOBPattern.Insert(value + 0 = "" ? "?" : value)
        }
        return AOBPattern
    }

    ; Method:           stringToPattern(string, encoding := "UTF-8", insertNullTerminator := False)
    ;                   Converts a text string parameter into an array of bytes pattern (AOBPattern) that
    ;                   can be passed to the various pattern scan methods i.e.  modulePatternScan(), addressPatternScan(), rawPatternScan(), and processPatternScan()
    ;
    ; Parameters:
    ;   string                  The text string to convert.
    ;   encoding                This refers to how the string is stored in the program's memory.
    ;                           UTF-8 and UTF-16 are common. Refer to the AHK manual for other encoding types.
    ;   insertNullTerminator    Includes the null terminating byte(s) (at the end of the string) in the AOB pattern.
    ;                           This should be set to 'false' unless you are certain that the target string is null terminated and you are searching for the entire string or the final part of the string.
    ;
    ; Return Values:
    ;   Object          Success - The returned object contains the AOB pattern.
    ;   -1              An empty string was passed.
    ;
    ;   Examples:
    ;                   pattern := stringToPattern("This text exists somewhere in the target program!")
    ;                   memObject.processPatternScan(,, pattern*)   ; Note the '*'

    stringToPattern(string, encoding := "UTF-8", insertNullTerminator := False)
    {
        if !length := StrLen(string)
            return -1 ; no str
        AOBPattern := []
        encodingSize := (encoding = "utf-16" || encoding = "cp1200") ? 2 : 1
        requiredSize := StrPut(string, encoding) * encodingSize - (insertNullTerminator ? 0 : encodingSize)
        VarSetCapacity(buffer, requiredSize)
        StrPut(string, &buffer, length + (insertNullTerminator ?  1 : 0), encoding)
        loop, % requiredSize
            AOBPattern.Insert(NumGet(buffer, A_Index-1, "UChar"))
        return AOBPattern
    }


    ; Method:           modulePatternScan(module := "", aAOBPattern*)
    ;                   Scans the specified module for the specified array of bytes
    ; Parameters:
    ;   module -        The file name of the module/dll to search e.g. "calc.exe", "GDI32.dll", "Bass.dll" etc
    ;                   If no module (null) is specified, the executable file of the process will be used.
    ;                   e.g. for calc.exe it would be the same as calling modulePatternScan(, aAOBPattern*) or modulePatternScan("calc.exe", aAOBPattern*)
    ;   aAOBPattern*    A variadic list of byte values i.e. the array of bytes to find.
    ;                   Wild card bytes should be indicated by passing a non-numeric value eg "?".
    ; Return Values:
    ;   Positive int    Success. The memory address of the found pattern.
    ;   Null            Failed to find or retrieve the specified module. ErrorLevel is set to the returned error from getModuleBaseAddress()
    ;                   refer to that method for more information.
    ;   0               The pattern was not found inside the module
    ;   -9              VirtualQueryEx() failed
    ;   -10             The aAOBPattern* is invalid. No bytes were passed

    modulePatternScan(module := "", aAOBPattern*)
    {
        MEM_COMMIT := 0x1000, MEM_MAPPED := 0x40000, MEM_PRIVATE := 0x20000
        , PAGE_NOACCESS := 0x01, PAGE_GUARD := 0x100

        if (result := this.getModuleBaseAddress(module, aModuleInfo)) <= 0
             return "", ErrorLevel := result ; failed
        if !patternSize := this.getNeedleFromAOBPattern(patternMask, AOBBuffer, aAOBPattern*)
            return -10 ; no pattern
        ; Try to read the entire module in one RPM()
        ; If fails with access (-1) iterate the modules memory pages and search the ones which are readable
        if (result := this.PatternScan(aModuleInfo.lpBaseOfDll, aModuleInfo.SizeOfImage, patternMask, AOBBuffer)) >= 0
            return result  ; Found / not found
        ; else RPM() failed lets iterate the pages
        address := aModuleInfo.lpBaseOfDll
        endAddress := address + aModuleInfo.SizeOfImage
        loop
        {
            if !this.VirtualQueryEx(address, aRegion)
                return -9
            if (aRegion.State = MEM_COMMIT
            && !(aRegion.Protect & (PAGE_NOACCESS | PAGE_GUARD)) ; can't read these areas
            ;&& (aRegion.Type = MEM_MAPPED || aRegion.Type = MEM_PRIVATE) ;Might as well read Image sections as well
            && aRegion.RegionSize >= patternSize
            && (result := this.PatternScan(address, aRegion.RegionSize, patternMask, AOBBuffer)) > 0)
                return result
        } until (address += aRegion.RegionSize) >= endAddress
        return 0
    }

    ; Method:               addressPatternScan(startAddress, sizeOfRegionBytes, aAOBPattern*)
    ;                       Scans a specified memory region for an array of bytes pattern.
    ;                       The entire memory area specified must be readable for this method to work,
    ;                       i.e. you must ensure the area is readable before calling this method.
    ; Parameters:
    ;   startAddress        The memory address from which to begin the search.
    ;   sizeOfRegionBytes   The numbers of bytes to scan in the memory region.
    ;   aAOBPattern*        A variadic list of byte values i.e. the array of bytes to find.
    ;                       Wild card bytes should be indicated by passing a non-numeric value eg "?".
    ; Return Values:
    ;   Positive integer    Success. The memory address of the found pattern.
    ;   0                   Pattern not found
    ;   -1                  Failed to read the memory region.
    ;   -10                 An aAOBPattern pattern. No bytes were passed.

    addressPatternScan(startAddress, sizeOfRegionBytes, aAOBPattern*)
    {
        if !this.getNeedleFromAOBPattern(patternMask, AOBBuffer, aAOBPattern*)
            return -10
        return this.PatternScan(startAddress, sizeOfRegionBytes, patternMask, AOBBuffer)
    }

    ; Method:       processPatternScan(startAddress := 0, endAddress := "", aAOBPattern*)
    ;               Scan the memory space of the current process for an array of bytes pattern.
    ;               To use this in a loop (scanning for multiple occurrences of the same pattern),
    ;               simply call it again passing the last found address + 1 as the startAddress.
    ; Parameters:
    ;   startAddress -      The memory address from which to begin the search.
    ;   endAddress -        The memory address at which the search ends.
    ;                       Defaults to 0x7FFFFFFF for 32 bit target processes.
    ;                       Defaults to 0xFFFFFFFF for 64 bit target processes when the AHK script is 32 bit.
    ;                       Defaults to 0x7FFFFFFFFFF for 64 bit target processes when the AHK script is 64 bit.
    ;                       0x7FFFFFFF and 0x7FFFFFFFFFF are the maximum process usable virtual address spaces for 32 and 64 bit applications.
    ;                       Anything higher is used by the system (unless /LARGEADDRESSAWARE and 4GT have been modified).
    ;                       Note: The entire pattern must be occur inside this range for a match to be found. The range is inclusive.
    ;   aAOBPattern* -      A variadic list of byte values i.e. the array of bytes to find.
    ;                       Wild card bytes should be indicated by passing a non-numeric value eg "?".
    ; Return Values:
    ;   Positive integer -  Success. The memory address of the found pattern.
    ;   0                   The pattern was not found.
    ;   -1                  VirtualQueryEx() failed.
    ;   -2                  Failed to read a memory region.
    ;   -10                 The aAOBPattern* is invalid. (No bytes were passed)

    processPatternScan(startAddress := 0, endAddress := "", aAOBPattern*)
    {
        address := startAddress
        if endAddress is not integer
            endAddress := this.isTarget64bit ? (A_PtrSize = 8 ? 0x7FFFFFFFFFF : 0xFFFFFFFF) : 0x7FFFFFFF

        MEM_COMMIT := 0x1000, MEM_MAPPED := 0x40000, MEM_PRIVATE := 0x20000
        PAGE_NOACCESS := 0x01, PAGE_GUARD := 0x100
        if !patternSize := this.getNeedleFromAOBPattern(patternMask, AOBBuffer, aAOBPattern*)
            return -10
        while address <= endAddress ; > 0x7FFFFFFF - definitely reached the end of the useful area (at least for a 32 target process)
        {
            if !this.VirtualQueryEx(address, aInfo)
                return -1
            if A_Index = 1
                aInfo.RegionSize -= address - aInfo.BaseAddress
            if (aInfo.State = MEM_COMMIT)
            && !(aInfo.Protect & (PAGE_NOACCESS | PAGE_GUARD)) ; can't read these areas
            ;&& (aInfo.Type = MEM_MAPPED || aInfo.Type = MEM_PRIVATE) ;Might as well read Image sections as well
            && aInfo.RegionSize >= patternSize
            && (result := this.PatternScan(address, aInfo.RegionSize, patternMask, AOBBuffer))
            {
                if result < 0
                    return -2
                else if (result + patternSize - 1 <= endAddress)
                    return result
                else return 0
            }
            address += aInfo.RegionSize
        }
        return 0
    }

    ; Method:           rawPatternScan(byRef buffer, sizeOfBufferBytes := "", aAOBPattern*)
    ;                   Scans a binary buffer for an array of bytes pattern.
    ;                   This is useful if you have already dumped a region of memory via readRaw()
    ; Parameters:
    ;   buffer              The binary buffer to be searched.
    ;   sizeOfBufferBytes   The size of the binary buffer. If null or 0 the size is automatically retrieved.
    ;   startOffset         The offset from the start of the buffer from which to begin the search. This must be >= 0.
    ;   aAOBPattern*        A variadic list of byte values i.e. the array of bytes to find.
    ;                       Wild card bytes should be indicated by passing a non-numeric value eg "?".
    ; Return Values:
    ;   >= 0                The offset of the pattern relative to the start of the haystack.
    ;   -1                  Not found.
    ;   -2                  Parameter incorrect.

    rawPatternScan(byRef buffer, sizeOfBufferBytes := "", startOffset := 0, aAOBPattern*)
    {
        if !this.getNeedleFromAOBPattern(patternMask, AOBBuffer, aAOBPattern*)
            return -10
        if (sizeOfBufferBytes + 0 = "" || sizeOfBufferBytes <= 0)
            sizeOfBufferBytes := VarSetCapacity(buffer)
        if (startOffset + 0 = "" || startOffset < 0)
            startOffset := 0
        return this.bufferScanForMaskedPattern(&buffer, sizeOfBufferBytes, patternMask, &AOBBuffer, startOffset)
    }

    ; Method:           getNeedleFromAOBPattern(byRef patternMask, byRef needleBuffer, aAOBPattern*)
    ;                   Converts an array of bytes pattern (aAOBPattern*) into a binary needle and pattern mask string
    ;                   which are compatible with patternScan() and bufferScanForMaskedPattern().
    ;                   The modulePatternScan(), addressPatternScan(), rawPatternScan(), and processPatternScan() methods
    ;                   allow you to directly search for an array of bytes pattern in a single method call.
    ; Parameters:
    ;   patternMask -   (output) A string which indicates which bytes are wild/non-wild.
    ;   needleBuffer -  (output) The array of bytes passed via aAOBPattern* is converted to a binary needle and stored inside this variable.
    ;   aAOBPattern* -  (input) A variadic list of byte values i.e. the array of bytes from which to create the patternMask and needleBuffer.
    ;                   Wild card bytes should be indicated by passing a non-numeric value eg "?".
    ; Return Values:
    ;  The number of bytes in the binary needle and hence the number of characters in the patternMask string.

    getNeedleFromAOBPattern(byRef patternMask, byRef needleBuffer, aAOBPattern*)
    {
        patternMask := "", VarSetCapacity(needleBuffer, aAOBPattern.MaxIndex())
        for i, v in aAOBPattern
            patternMask .= (v + 0 = "" ? "?" : "x"), NumPut(round(v), needleBuffer, A_Index - 1, "UChar")
        return round(aAOBPattern.MaxIndex())
    }

    ; The handle must have been opened with the PROCESS_QUERY_INFORMATION access right
    VirtualQueryEx(address, byRef aInfo)
    {

        if (aInfo.__Class != "_ClassMemory._MEMORY_BASIC_INFORMATION")
            aInfo := new this._MEMORY_BASIC_INFORMATION()
        return aInfo.SizeOfStructure = DLLCall("VirtualQueryEx"
                                                , "Ptr", this.hProcess
                                                , "Ptr", address
                                                , "Ptr", aInfo.pStructure
                                                , "Ptr", aInfo.SizeOfStructure
                                                , "Ptr")
    }

    /*
    // The c++ function used to generate the machine code
    int scan(unsigned char* haystack, unsigned int haystackSize, unsigned char* needle, unsigned int needleSize, char* patternMask, unsigned int startOffset)
    {
        for (unsigned int i = startOffset; i <= haystackSize - needleSize; i++)
        {
            for (unsigned int j = 0; needle[j] == haystack[i + j] || patternMask[j] == '?'; j++)
            {
                if (j + 1 == needleSize)
                    return i;
            }
        }
        return -1;
    }
    */

    ; Method:               PatternScan(startAddress, sizeOfRegionBytes, patternMask, byRef needleBuffer)
    ;                       Scans a specified memory region for a binary needle pattern using a machine code function
    ;                       If found it returns the memory address of the needle in the processes memory.
    ; Parameters:
    ;   startAddress -      The memory address from which to begin the search.
    ;   sizeOfRegionBytes - The numbers of bytes to scan in the memory region.
    ;   patternMask -       This string indicates which bytes must match and which bytes are wild. Each wildcard byte must be denoted by a single '?'.
    ;                       Non wildcards can use any other single character e.g 'x'. There should be no spaces.
    ;                       With the patternMask 'xx??x', the first, second, and fifth bytes must match. The third and fourth bytes are wild.
    ;    needleBuffer -     The variable which contains the binary needle. This needle should consist of UChar bytes.
    ; Return Values:
    ;   Positive integer    The address of the pattern.
    ;   0                   Pattern not found.
    ;   -1                  Failed to read the region.

    patternScan(startAddress, sizeOfRegionBytes, byRef patternMask, byRef needleBuffer)
    {
        if !this.readRaw(startAddress, buffer, sizeOfRegionBytes)
            return -1
        if (offset := this.bufferScanForMaskedPattern(&buffer, sizeOfRegionBytes, patternMask, &needleBuffer)) >= 0
            return startAddress + offset
        else return 0
    }
    ; Method:               bufferScanForMaskedPattern(byRef hayStack, sizeOfHayStackBytes, byRef patternMask, byRef needle)
    ;                       Scans a binary haystack for binary needle against a pattern mask string using a machine code function.
    ; Parameters:
    ;   hayStackAddress -   The address of the binary haystack which is to be searched.
    ;   sizeOfHayStackBytes The total size of the haystack in bytes.
    ;   patternMask -       A string which indicates which bytes must match and which bytes are wild. Each wildcard byte must be denoted by a single '?'.
    ;                       Non wildcards can use any other single character e.g 'x'. There should be no spaces.
    ;                       With the patternMask 'xx??x', the first, second, and fifth bytes must match. The third and fourth bytes are wild.
    ;   needleAddress -     The address of the binary needle to find. This needle should consist of UChar bytes.
    ;   startOffset -       The offset from the start of the haystack from which to begin the search. This must be >= 0.
    ; Return Values:
    ;   >= 0                Found. The pattern begins at this offset - relative to the start of the haystack.
    ;   -1                  Not found.
    ;   -2                  Invalid sizeOfHayStackBytes parameter - Must be > 0.

    ; Notes:
    ;       This is a basic function with few safeguards. Incorrect parameters may crash the script.

    bufferScanForMaskedPattern(hayStackAddress, sizeOfHayStackBytes, byRef patternMask, needleAddress, startOffset := 0)
    {
        static p
        if !p
        {
            if A_PtrSize = 4
                p := this.MCode("1,x86:8B44240853558B6C24182BC5568B74242489442414573BF0773E8B7C241CBB010000008B4424242BF82BD8EB038D49008B54241403D68A0C073A0A740580383F750B8D0C033BCD74174240EBE98B442424463B74241876D85F5E5D83C8FF5BC35F8BC65E5D5BC3")
            else
                p := this.MCode("1,x64:48895C2408488974241048897C2418448B5424308BF2498BD8412BF1488BF9443BD6774A4C8B5C24280F1F800000000033C90F1F400066660F1F840000000000448BC18D4101418D4AFF03C80FB60C3941380C18740743803C183F7509413BC1741F8BC8EBDA41FFC2443BD676C283C8FF488B5C2408488B742410488B7C2418C3488B5C2408488B742410488B7C2418418BC2C3")
        }
        if (needleSize := StrLen(patternMask)) + startOffset > sizeOfHayStackBytes
            return -1 ; needle can't exist inside this region. And basic check to prevent wrap around error of the UInts in the machine function
        if (sizeOfHayStackBytes > 0)
            return DllCall(p, "Ptr", hayStackAddress, "UInt", sizeOfHayStackBytes, "Ptr", needleAddress, "UInt", needleSize, "AStr", patternMask, "UInt", startOffset, "cdecl int")
        return -2
    }

    ; Notes:
    ; Other alternatives for non-wildcard buffer comparison.
    ; Use memchr to find the first byte, then memcmp to compare the remainder of the buffer against the needle and loop if it doesn't match
    ; The function FindMagic() by Lexikos uses this method.
    ; Use scanInBuf() machine code function - but this only supports 32 bit ahk. I could check if needle contains wild card and AHK is 32bit,
    ; then call this function. But need to do a speed comparison to see the benefits, but this should be faster. Although the benefits for
    ; the size of the memory regions be dumped would most likely be inconsequential as it's already extremely fast.

    MCode(mcode)
    {
        static e := {1:4, 2:1}, c := (A_PtrSize=8) ? "x64" : "x86"
        if !regexmatch(mcode, "^([0-9]+),(" c ":|.*?," c ":)([^,]+)", m)
            return
        if !DllCall("crypt32\CryptStringToBinary", "str", m3, "uint", 0, "uint", e[m1], "ptr", 0, "uint*", s, "ptr", 0, "ptr", 0)
            return
        p := DllCall("GlobalAlloc", "uint", 0, "ptr", s, "ptr")
        ; if (c="x64") ; Virtual protect must always be enabled for both 32 and 64 bit. If DEP is set to all applications (not just systems), then this is required
        DllCall("VirtualProtect", "ptr", p, "ptr", s, "uint", 0x40, "uint*", op)
        if DllCall("crypt32\CryptStringToBinary", "str", m3, "uint", 0, "uint", e[m1], "ptr", p, "uint*", s, "ptr", 0, "ptr", 0)
            return p
        DllCall("GlobalFree", "ptr", p)
        return
    }

    ; This link indicates that the _MEMORY_BASIC_INFORMATION32/64 should be based on the target process
    ; http://stackoverflow.com/questions/20068219/readprocessmemory-on-a-64-bit-proces-always-returns-error-299
    ; The msdn documentation is unclear, and suggests that a debugger can pass either structure - perhaps there is some other step involved.
    ; My tests seem to indicate that you must pass _MEMORY_BASIC_INFORMATION i.e. structure is relative to the AHK script bitness.
    ; Another post on the net also agrees with my results.

    ; Notes:
    ; A 64 bit AHK script can call this on a target 64 bit process. Issues may arise at extremely high memory addresses as AHK does not support UInt64 (but these addresses should never be used anyway).
    ; A 64 bit AHK can call this on a 32 bit target and it should work.
    ; A 32 bit AHk script can call this on a 64 bit target and it should work providing the addresses fall inside the 32 bit range.

    class _MEMORY_BASIC_INFORMATION
    {
        __new()
        {
            if !this.pStructure := DllCall("GlobalAlloc", "UInt", 0, "Ptr", this.SizeOfStructure := A_PtrSize = 8 ? 48 : 28, "Ptr")
                return ""
            return this
        }
        __Delete()
        {
            DllCall("GlobalFree", "Ptr", this.pStructure)
        }
        ; For 64bit the int64 should really be unsigned. But AHK doesn't support these
        ; so this won't work correctly for higher memory address areas
        __get(key)
        {
            static aLookUp := A_PtrSize = 8
                                ?   {   "BaseAddress": {"Offset": 0, "Type": "Int64"}
                                    ,    "AllocationBase": {"Offset": 8, "Type": "Int64"}
                                    ,    "AllocationProtect": {"Offset": 16, "Type": "UInt"}
                                    ,    "RegionSize": {"Offset": 24, "Type": "Int64"}
                                    ,    "State": {"Offset": 32, "Type": "UInt"}
                                    ,    "Protect": {"Offset": 36, "Type": "UInt"}
                                    ,    "Type": {"Offset": 40, "Type": "UInt"} }
                                :   {  "BaseAddress": {"Offset": 0, "Type": "UInt"}
                                    ,   "AllocationBase": {"Offset": 4, "Type": "UInt"}
                                    ,   "AllocationProtect": {"Offset": 8, "Type": "UInt"}
                                    ,   "RegionSize": {"Offset": 12, "Type": "UInt"}
                                    ,   "State": {"Offset": 16, "Type": "UInt"}
                                    ,   "Protect": {"Offset": 20, "Type": "UInt"}
                                    ,   "Type": {"Offset": 24, "Type": "UInt"} }

            if aLookUp.HasKey(key)
                return numget(this.pStructure+0, aLookUp[key].Offset, aLookUp[key].Type)
        }
        __set(key, value)
        {
             static aLookUp := A_PtrSize = 8
                                ?   {   "BaseAddress": {"Offset": 0, "Type": "Int64"}
                                    ,    "AllocationBase": {"Offset": 8, "Type": "Int64"}
                                    ,    "AllocationProtect": {"Offset": 16, "Type": "UInt"}
                                    ,    "RegionSize": {"Offset": 24, "Type": "Int64"}
                                    ,    "State": {"Offset": 32, "Type": "UInt"}
                                    ,    "Protect": {"Offset": 36, "Type": "UInt"}
                                    ,    "Type": {"Offset": 40, "Type": "UInt"} }
                                :   {  "BaseAddress": {"Offset": 0, "Type": "UInt"}
                                    ,   "AllocationBase": {"Offset": 4, "Type": "UInt"}
                                    ,   "AllocationProtect": {"Offset": 8, "Type": "UInt"}
                                    ,   "RegionSize": {"Offset": 12, "Type": "UInt"}
                                    ,   "State": {"Offset": 16, "Type": "UInt"}
                                    ,   "Protect": {"Offset": 20, "Type": "UInt"}
                                    ,   "Type": {"Offset": 24, "Type": "UInt"} }

            if aLookUp.HasKey(key)
            {
                NumPut(value, this.pStructure+0, aLookUp[key].Offset, aLookUp[key].Type)
                return value
            }
        }
        Ptr()
        {
            return this.pStructure
        }
        sizeOf()
        {
            return this.SizeOfStructure
        }
    }

}

В архиве исходник и скомпилированный вариант.

Post's attachments

COMTerminal.zip 484.83 kb, 2 downloads since 2023-11-19 

You don't have the permssions to download the attachments of this post.
Win 10 x64
AHK v1.1.33.02
                       Справка тебе в помощь.