library lazHarpoonEventsMQTT; {$mode delphi}{$H+} uses Classes { you can add units after this }, uHCDefines, mainform, uHCGCI, uHCGlobals, uHCTypeDefs, uHCEventHelpers, uHCDateTime, uHCAnnexDefs, uHCAnnexHelpers, uLazHarpoonEventsMQTTGlobals, Windows, Forms, ShellAPI, SysUtils, Types, Dialogs, Interfaces, Controls, indylaz, DateUtils, IniFiles, TMS.MQTT.Global; type { TExportDLLMainThread } TExportDLLMainThread = class(TThread) private ThreadApplication: TApplication; fHCGEThreadId: DWORD; protected procedure Execute; override; public constructor Create(const pHCGEThreadID: DWORD; pCreateSuspended: Boolean); end; const DLLVERN = 20; DLLVERINTN = 23; SIDES = 3; SLIVER360 = 134217728; var frmMain: TfrmMain; thdExportDLLMain: TExportDLLMainThread; {*** Return DLL internal version, i.e. revision 5 of my DLL ***} function DLLVer(): word; export; cdecl; begin Result := DLLVERN; end; {*** Return DLL interface version, i.e. revision 1 of my HCE interface spec ***} function DLLVerInt(): word; export; cdecl; begin Result := DLLVERINTN; end; {*** Return DLL description, 255 characters max ***} function DLLDesc(): PAnsiChar; export; cdecl; begin Result := 'lazarus Harpoon Event pub via MQTT DLL by Tony Eischens'; end; {*** Free DLL ***} procedure DLLFree(); export; cdecl; begin // Do any cleanup here, this is the end frmMain.Free; end; {*** Init DLL ***} procedure DLLInit(hwndHC: HWND; pathHC: PAnsiChar; verHC: PAnsiChar; verSaveGame: longword; funcRefs: PHCDLL_FUNC_REF; PGEClosing: Pointer; PDLLBusy: Pointer; verInterface: longword); export; cdecl; var funcTmp: PHCDLL_FUNC_REF; lIniHC: TIniFile; lGUID: TGUID; begin // here we initialize globals and such for the life of the DLL // ALE 20200914 not for threaded DLL Application.Initialize; // ALE 20120410 Freezes HC Application.Run; ulazHarpoonEventsMQTTGlobals.PGEClosing := PGEClosing; ulazHarpoonEventsMQTTGlobals.PDLLBusy := PDLLBusy; // ALE 20200914 not for threaded DLL frmMain := TfrmMain.Create(nil); //thdExportDLLMain: TExportDLLMainThread; gHCGEThreadID:=GetCurrentThreadID; // ALE 20200914 to feed to DLL Form's application thread thdExportDLLMain := TExportDLLMainThread.Create(gHCGEThreadID, False); { frmMain.tmrMQTTConnect.Enabled:=True; //frmMain.ParentWindow := hwndHC; frmMain.Caption := 'lazarus GUI Export DLL v' + IntToStr(DLLVERN) + ', ExportDLL Interface v' + IntToStr(DLLVERINTN); frmMain.memDLLInit.Lines.Add('lazarus Harpoon Event pub via MQTT Export DLL v' + IntToStr(DLLVERN)); frmMain.memDLLInit.Lines.Add('ExportDLL Interface v' + IntToStr(DLLVERINTN)); frmMain.memDLLInit.Lines.Add('hwndHC = 0x' + IntToHex(hwndHC, 8)); frmMain.memDLLInit.Lines.Add('pathHC = ' + pathHC); frmMain.memDLLInit.Lines.Add('verHC = ' + verHC); frmMain.memDLLInit.Lines.Add('verSaveGame = 0x' + IntToHex(verSaveGame, 8)); frmMain.memDLLInit.Lines.Add(Format('funcRefs = 0x%p', [funcRefs])); frmMain.memDLLInit.Lines.Add(Format('GE ExportDLL Interface Version = %d', [verInterface])); } // ALE 20200826 Read or Create the Harpoon installation UUID // ALE 20200120 we need a GUID, try to load it from main game dir lIniHC := TIniFile.Create(ExtractFilePath(Application.ExeName) + HCINIFILENAME); gGUID := lIniHC.ReadString('MAIN', 'InstallUUID', ''); if (gGUID = '') then begin CreateGUID(lGUID); gGUID := Copy(SysUtils.GUIDToString(lGUID), 2, 36); lIniHC.WriteString('MAIN', 'InstallUUID', gGUID); end; { frmMain.memDLLInit.Lines.Add('InstallUUID = ' + gGUID); frmMain.edtUsername.Text:=lIniHC.ReadString('ExportDLL_lazHarpoonEventsMQTT', 'MQTT_Username', ''); frmMain.edtPassword.Text:=frmMain.DecodeString(lIniHC.ReadString('ExportDLL_lazHarpoonEventsMQTT', 'MQTT_Password', '')); if ((frmMain.edtUsername.Text <> '') AND (frmMain.edtPassword.Text <> '')) then frmMain.ckbCredentials.Checked:=True; } lIniHC.Free; ulazHarpoonEventsMQTTGlobals.intVerInterface := DLLVERINTN; ulazHarpoonEventsMQTTGlobals.intVerSaveGame := verSaveGame; // ALE 20170224 Alert the user that the GE is too old for this ExportDLL if (verInterface < 23) then begin MessageDlg('GE too old', GetModuleName(hInstance) + ' is designed for HC2020.009+. Please ' + ' update your GE or disable this ExportDLL.', mtWarning, [mbOK], 0); end; { // ALE 20130908 List the function pointers sent over by the GE // then capture one of them for example usage funcTmp := funcRefs; while (funcTmp <> nil) do begin frmMain.memDLLInit.Lines.Add(#9 + Format('funcRef: 0x%p %s', [funcTmp^.funcPtr, funcTmp^.funcName])); if (funcTmp^.funcName = 'NewPtr') then frmMain.NewPtr := funcTmp^.funcPtr else if (funcTmp^.funcName = 'DispPtr') then frmMain.DisposPtr := THCDisposPtr(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'QueueHarpoonEvent') then frmMain.HCQueueHarpoonEvent := THCQueueHarpoonEvent(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'dllCheckLatLonDepth') then frmMain.HCdllCheckLatLonDepth := THCdllCheckLatLonDepth(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'annex_id_to_disk_id') then frmMain.HCannex_id_to_disk_id := THCannex_id_to_disk_id(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'disk_id_to_annex_id') then frmMain.HCdisk_id_to_annex_id := THCdisk_id_to_annex_id(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'MessageLogLine') then frmMain.HCMessageLogLine := THCMessageLogLine(funcTmp^.funcPtr); funcTmp := funcTmp^.Next; end; frmMain.Show; } end; procedure UnitLatLon(UnitID: byte; GroupID: PAnsiChar; Lat: double; Lon: double); export; cdecl; begin end; procedure UnitShowing(UnitID: byte; GroupID: PAnsiChar; Showing: byte); export; cdecl; begin end; procedure UnitURegion(UnitID: byte; GroupID: PAnsiChar; pUR: PTURegion); export; cdecl; begin end; procedure UnitCourse(UnitID: byte; GroupID: PAnsiChar; course: smallint); export; cdecl; begin end; procedure UnitSpeed(UnitID: byte; GroupID: PAnsiChar; speed: smallint); export; cdecl; begin end; procedure UnitAlt(UnitID: byte; GroupID: PAnsiChar; alt: byte); export; cdecl; begin end; procedure UnitShowingSide(UnitID: byte; GroupID: PAnsiChar; showing: PTSidesUChar); export; cdecl; begin end; procedure UnitSink(UnitID: byte; GroupID: PAnsiChar); export; cdecl; begin end; procedure UnitToGroup(UnitID: byte; GroupID, GroupIDold: PAnsiChar); export; cdecl; begin end; procedure UnitNew(UnitID: byte; GroupID: PAnsiChar; utype: shortint; side: byte; annexID: word; Name: PAnsiChar); export; cdecl; begin end; procedure UnitFree(UnitID: byte; GroupID: PAnsiChar); export; cdecl; begin end; procedure ScenLoad(strHarpPath: PAnsiChar; intScenNum: smallint; strScenFPath: PAnsiChar; intBSNum: shortint; strBSFName: PAnsiChar; intSide: byte; intGameTimeIn: Long; intGameTimeLeftIn: Long; AnnexDataIn: PHCAnnexes_disk; DatabaseVersionDBIn: PHCDATABASE_VERSION; ScenDataIn: PHCSCENARIO_DATA; ScenSaveDataIn: PHCSCENARIO_SAVE_DATA) export; cdecl; var lLLI: PHCLINEAGE_LIST_ITEM; lLineageStr: String; lRow: NativeInt; begin intGameTime := intGameTimeIn; // ALE 20150712 Now that we receive the game time here, store it intGameTimeLeft := intGameTimeLeftIn; // ALE 20150712 Now that we receive the game time left here, store it // ALE 20170923 replaced in interface version 21 with ScenData dt_scen_start := dt_scen_start_in; // ALE 20150712 Scenario Starting date and time AnnexData := AnnexDataIn; // ALE 20150813 Database/Platform information HCAnnexHelper := THCAnnexHelper.Create(AnnexDataIn, frmMain.HCannex_id_to_disk_id, ulazHarpoonEventsMQTTGlobals.intVerInterface, ulazHarpoonEventsMQTTGlobals.intVerSaveGame); DatabaseVersionDB := DatabaseVersionDBIn; // ALE 20170923 current database ScenData := ScenDataIn; // ALE 20170923 unique identifiers, lineage, etc. ScenSaveData := ScenSaveDataIn; // ALE 20170923 save UUIDs and Scenario Orders { if (frmMain.SLckbEnabled.Checked = True) then begin frmMain.SLmemMsgLog.Lines.Insert(0, Format('ScenLoad HC Path=%s, Scen#=%d, ScenFNP=%s, BattleSet=%d, BattleSet=%s, PlayerSide=%d, GameTime=%d, ScenStart %4.4d-%2.2d-%2.2d, %2.2d:%2.2d:%2.2d', [strHarpPath, intScenNum, strScenFPath, intBSNum, strBSFName, intSide, intGameTimeIn, ScenData^.dt_scen_start.year, ScenData^.dt_scen_start.month, ScenData^.dt_scen_start.day, ScenData^.dt_scen_start.hour, ScenData^.dt_scen_start.minute, ScenData^.dt_scen_start.second])); if (frmMain.SLmemMsgLog.Lines.Count > frmMain.SLedtLines.Value) then begin frmMain.SLmemMsgLog.Lines.Delete(frmMain.SLedtLines.Value); end; end; // ALE 20170923 Enumerate the scenario lineage if (ScenData^.Lineage.Count > 0) then begin lRow := 1; frmMain.SLsgLineage.RowCount := ScenData^.Lineage.Count + 1; frmMain.SLsgLineage.Cells[0, 0] := ''; frmMain.SLsgLineage.Cells[1, 0] := 'Date & Time Saved'; frmMain.SLsgLineage.Cells[2, 0] := 'Scen Format Ver'; frmMain.SLsgLineage.Cells[3, 0] := 'SE version'; frmMain.SLsgLineage.Cells[4, 0] := 'DB flavor'; frmMain.SLsgLineage.Cells[5, 0] := 'uuid'; lLLI := @ScenData^.Lineage.Top; while (lLLI <> nil) do begin frmMain.SLsgLineage.Cells[0, lRow] := IntToStr(lRow); frmMain.SLsgLineage.Cells[1, lRow] := FormatDateTime('yyyy-MM-dd hh:nn:ss', UnixToDateTime(lLLI^.dtstamp)); frmMain.SLsgLineage.Cells[2, lRow] := IntToStr(lLLI^.ScenarioVersion); frmMain.SLsgLineage.Cells[3, lRow] := lLLI^.SEVersion; frmMain.SLsgLineage.Cells[4, lRow] := lLLI^.DatabaseVersion.RESFile.flavor; frmMain.SLsgLineage.Cells[5, lRow] := lLLI^.uuid; Inc(lRow); lLLI := lLLI^.next; end; end; } end; procedure TimeFactor(TimeFactor: longint); export; cdecl; begin end; procedure MsgLog(MsgLine: PAnsiChar); export; cdecl; begin end; procedure GameTime(GameTime: Long; GameTimeLeft: Long); export; cdecl; var PEvent: PHCHarpoonEvent; PGroup: PHCGroup; EventType: uchar; strGroupID: string; dtNow: THCDateTimeRec; begin intGameTime := GameTime; intGameTimeLeft := GameTimeLeft; // ALE 20150712 store remaining time { // ALE 20200827 publish even if not connected as they queue up // if (frmMain.MQTTClient.IsConnected) then begin frmMain.MQTTPublish('GameTime', IntToStr(intGameTime)); end; } end; procedure StartGroup(StartGroup: PHCGroup); export; cdecl; begin { // ALE 20130317 Fill up the SGlbxGroups frmMain.PStartGroup := StartGroup; } end; procedure HarpoonEventHead(HarpEventHead: PHCHarpoonEvent); export; cdecl; begin { frmMain.PHarpEventHead := HarpEventHead; } end; procedure HarpoonEventQueued(HarpEvent: PHCHarpoonEvent); export; cdecl; begin { if (HarpEvent <> nil) then begin //if (frmMain.MQTTClient.ConnectionStatus = csConnected) then begin frmMain.MQTTPublish('HarpoonEvents', HCEventToJSON(HarpEvent)); end; end; //frmMain.PHarpEvent := HarpEvent; } end; procedure SonobuoyRegionHead(SonobuoyRegionHead: PHCSonobuoyRegion); export; cdecl; begin { frmMain.PSonobuoyRegionHead := SonobuoyRegionHead; } end; procedure AllDeadUnitsPtr(AllDeadUnitsPtr: PHCDeadListItem); export; cdecl; begin { frmMain.PAllDeadUnitsPtr := AllDeadUnitsPtr; } end; procedure WeatherGroups(WeatherGroups: PHCGroup); export; cdecl; begin { frmMain.PWeatherGroups := WeatherGroups; } end; procedure VictoryConditionsList(VictoryConditionsList: PHCVictoryConditions); export; cdecl; begin { frmMain.PVictoryConditionsList := VictoryConditionsList; } end; procedure ActGroupPtr(ActGroup: PHCGroup); export; cdecl; begin end; procedure ActUnitPtr(ActUnit: PHCUnit); export; cdecl; begin end; procedure SaveGame(FileNameIn: PAnsiChar; ScenSaveDataIn: PHCSCENARIO_SAVE_DATA); export; cdecl; begin end; procedure TExportDLLMainThread.Execute; begin if not Terminated then begin try ThreadApplication:=TApplication.Create(nil); try ThreadApplication.Initialize; MainThreadID:=fHCGEThreadId; ThreadApplication.CreateForm(TfrmMain, frmMain); ThreadApplication.Run; finally ThreadApplication.Free; end; finally Terminate; end; end; end; constructor TExportDLLMainThread.Create(const pHCGEThreadID: DWORD; pCreateSuspended: Boolean); begin fHCGEThreadId:=pHCGEThreadID; inherited Create(pCreateSuspended); end; exports DLLVer, DLLVerInt, DLLDesc, DLLFree, DLLInit, UnitLatLon, UnitShowing, UnitURegion, UnitCourse, UnitSpeed, UnitAlt, UnitShowingSide, UnitSink, UnitToGroup, UnitNew, UnitFree, ScenLoad, TimeFactor, MsgLog, GameTime , StartGroup // ALE 20130205 Sends updated GroupPtr whenever that changes , HarpoonEventHead // ALE 20130805 Sends updated soonest HarpoonEvent pointer whenever it changes , HarpoonEventQueued // ALE 20200825 Sends HarpoonEvent pointer when an event is added , SonobuoyRegionHead // ALE 20130812 Sends updated pointer to all sonobuoys when it changes , AllDeadUnitsPtr // ALE 20130812 Sends updated pointer to the list of killed units used for victory condition evaluation , WeatherGroups // ALE 20130812 Sends update GroupPtr whenever the top weather group changes , VictoryConditionsList // ALE 20130812 Sends a pointer to the list of victory conditions when a scenario is loaded , ActGroupPtr // ALE 20160305 Sends pointer to the Group the player just selected or NULL , ActUnitPtr // ALE 20160305 Sends pointer to the Unit the player just selected or NULL , SaveGame // ALE 20170923 UUIDs to associate any data with the right saved game file ; end.