library USNI; { Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. } {$POINTERMATH ON} uses Windows, Forms, uCEFApplication, ShellAPI, SysUtils, IOUtils, Classes, Types, Dialogs, Graphics, DateUtils, System.NetEncoding, System.UITypes, uHCDefines, uHCGCI, uHCGlobals, uHCTypeDefs, uHCEventHelpers, uHCDateTime, uHCAnnexDefs, uHCAnnexHelpers, uDLLUSNI in 'uDLLUSNI.pas' {frmUSNI}, uDLLDispArt in 'uDLLDispArt.pas' {frmDLLDispArt}, uUSNIGlobals in 'uUSNIGlobals.pas'; const DLLVERN = 2; DLLVERINTN = 22; SIDES = 3; SLIVER360 = 134217728; var frmUSNI: TfrmUSNI; hwndDLL: HWND; stlLog: TStringList; // ALE 20130812 Big bad pointers that hold most of the game data PStartGroup: PHCGroup; PHarpEventHead: PHCHarpoonEvent; PSonobuoyRegionHead: PHCSonobuoyRegion; PAllDeadUnitsPtr: PHCDeadListItem; PWeatherGroups: PHCGroup; PVictoryConditionsList: PHCVictoryConditions; // ALE 20130812 transient pointers PSelectedUnit: PHCUnit; // ALE 20130812 misc globals // ALE 20130908 pointers to functions exported by the GE HCQueueHarpoonEvent: THCQueueHarpoonEvent; NewPtr: THCNewPtr; DisposPtr: THCDisposPtr; HCdllCheckLatLonDepth: THCdllCheckLatLonDepth; HCannex_id_to_disk_id: THCannex_id_to_disk_id; HCdisk_id_to_annex_id: THCdisk_id_to_annex_id; HCMessageLogLine: THCMessageLogLine; procedure LogIt(pLogText: String); begin if (stlLog = nil) then begin gLogFileNamePath := TPath.Combine(GetCurrentDir,'ExportDLLs\USNI.log'); stlLog := TStringList.Create; if FileExists(gLogFileNamePath) then begin stlLog.LoadFromFile(gLogFileNamePath); end; end; stlLog.Insert(0, FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz ', Now) + pLogText); stlLog.SaveToFile(gLogFileNamePath); end; function USNIURLMedia(pTerm: String): String; begin // https://photos.usni.org/search?keys=BB-61 result := 'https://photos.usni.org/search?keys=' + TNetEncoding.URL.Encode(pTerm) + '&' + gPromoSrc; end; function USNIURLSearch(pTerm: String): String; begin // https://www.usni.org/search?search_api_fulltext=BB-61&sort_by=search_api_relevance&sort_order=DESC result := 'https://www.usni.org/search?sort_by=search_api_relevance&sort_order=DESC&search_api_fulltext=' + TNetEncoding.URL.Encode(pTerm) + '&' + gPromoSrc; end; { *** 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 := 'USNI Promotion DLL'; end; { *** Free DLL *** } procedure DLLFree(); export; cdecl; begin // Do any cleanup here, this is the end frmDLLDispArt.Free; frmUSNI.Free; DestroyGlobalCEFApp; // GlobalCEFApp.Free; if (stlLog <> nil) then FreeAndNil(stlLog); Application.Handle := hwndDLL; end; { *** Init DLL *** } // procedure DLLInit(hwndHC: HWND);export;cdecl; 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; WinRect: TRect; begin // here we initialize globals and such for the life of the DLL hwndDLL := Application.Handle; Application.Handle := hwndHC; // ALE 20080924 parentage of windows hwndMDI := hwndHC; intVerInterface := DLLVERINTN; intVerSaveGame := verSaveGame; gPDLLBusy:=PDLLBusy; // ALE 20170224 Alert the user that the GE is too old for this ExportDLL if (verInterface < 22) then begin MessageDlg({$IFDEF FPC}'GE too old', {$ENDIF}GetModuleName(hInstance) + ' is designed for HC2019.001+. Please ' + ' update your GE or disable this ExportDLL.', mtWarning, [mbOK], 0); end; LogIt('DLLInit DLLDesc=' + DLLDesc() + ', ver=' + DLLVer().ToString); // ALE 20130908 List the function pointers sent over by the GE // then capture the ones we want funcTmp := funcRefs; while (funcTmp <> nil) do begin //LogIt(#9 + Format('funcRef: 0x%p %s', [funcTmp^.funcPtr, // funcTmp^.funcName])); if (funcTmp^.funcName = 'NewPtr') then NewPtr := funcTmp^.funcPtr else if (funcTmp^.funcName = 'DispPtr') then DisposPtr := THCDisposPtr(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'QueueHarpoonEvent') then HCQueueHarpoonEvent := THCQueueHarpoonEvent(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'dllCheckLatLonDepth') then HCdllCheckLatLonDepth := THCdllCheckLatLonDepth(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'annex_id_to_disk_id') then HCannex_id_to_disk_id := THCannex_id_to_disk_id(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'disk_id_to_annex_id') then HCdisk_id_to_annex_id := THCdisk_id_to_annex_id(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'MessageLogLine') then HCMessageLogLine := THCMessageLogLine(funcTmp^.funcPtr); funcTmp := PHCDLL_FUNC_REF(funcTmp^.Next); end; GlobalCEFApp := TCefApplication.Create; GlobalCEFApp.FrameworkDirPath := TPath.Combine(GetCurrentDir, 'ExportDLLs\USNI_DCEF'); GlobalCEFApp.ResourcesDirPath := TPath.Combine(GetCurrentDir, 'ExportDLLs\USNI_DCEF'); GlobalCEFApp.LocalesDirPath := TPath.Combine(GetCurrentDir, 'ExportDLLs\USNI_DCEF\locales'); GlobalCEFApp.cache := TPath.Combine(GetCurrentDir, 'ExportDLLs\USNI_DCEF\cache'); GlobalCEFApp.UserDataPath := TPath.Combine(GetCurrentDir, 'ExportDLLs\USNI_DCEF\userdata'); GlobalCEFApp.SingleProcess := False; // GlobalCEFApp.SingleProcess := True; // GlobalCEFApp.FastUnload := True; GlobalCEFApp.BrowserSubprocessPath := TPath.Combine(GetCurrentDir, 'ExportDLLs\USNI_DCEF\USNI_DCEF_SubProcess.exe'); GlobalCEFApp.StartMainProcess; frmUSNI := TfrmUSNI.CreateParented(hwndHC); GetWindowRect(hwndHC, WinRect); frmUSNI.Top := WinRect.Bottom - WinRect.Top - frmUSNI.Height; frmUSNI.Width := WinRect.Right - WinRect.Left; // ALE 20191230 set up some promo tracking for log scraping gPromoSrc := 'HCDLL=' + TNetEncoding.URL.Encode(DLLDesc()) + '&HCGEVer=' + verHC + '&HCDDLLVer=' + DLLVERN.ToString; frmUSNI.gPromoSrc := gPromoSrc; LogIt('Application.ExeName: ' + Application.ExeName); LogIt('CurrentDir: ' + GetCurrentDir); end; { *** Invoked when platform display unit displayed shows *** } procedure PDUnitPic(hwndPD: HWND; intPicID: SmallInt; strClassName: PAnsiChar); export; cdecl; var strHTML: String; begin strHTML := '
'; strHTML := StringReplace(strHTML, 'url', IntToStr(intPicID), [rfReplaceAll, rfIgnoreCase]); strHTML := StringReplace(strHTML, 'PlatformName', strClassName, [rfReplaceAll, rfIgnoreCase]); //frmUSNI.lblArtPlatform.HTMLText.Text := strHTML; // ALE 20090917 funky autosizing to fit { frmUSNI.lblArtPlatform.Font.Size := 10; frmUSNI.lblArtPlatform.AutoSizing := True; frmUSNI.lblArtPlatform.Refresh; // This triggers the resize while frmUSNI.lblArtPlatform.Width > 318 do begin frmUSNI.lblArtPlatform.Font.Size := frmUSNI.lblArtPlatform.Font.Size - 1; frmUSNI.lblArtPlatform.Refresh; end; frmUSNI.lblArtPlatform.AutoSizing := False; frmUSNI.lblArtPlatform.Width := 318; if (intPicID <> 0) then begin // ALE 20090919 we have art if intPicID <> 0 then begin frmUSNI.lblArtPlatform.Enabled := True; end else begin // ALE 20090917 here is where we need to do our check for associated art, // not every valid PicID has art frmUSNI.lblArtPlatform.Enabled := False; end; end else begin // ALE 20090919 we have no platform specific art frmUSNI.lblArtPlatform.Enabled := False; end; } end; { *** Invoked when active unit pointer changes *** } procedure ActUnitPtr(pPActUnit: PHCUnit); export; cdecl; begin Byte(gPDLLBusy^) := CTRUE; // ALE 20130816 don't process other events in GE if pPActUnit <> nil then begin //frmUSNI.lblActUnit.HTMLText.Text := '' + // pPActUnit^.name + ''; end; Byte(gPDLLBusy^) := CFALSE; end; { *** Invoked when active group pointer changes *** } procedure ActGroupPtr(pPActGroup: PHCGroup); export; cdecl; var lC: Integer; lPHCUnit: PHCUnit; lUnitName: String; lUnitType: ShortInt; lGroupID: String; lGroupSide: uchar; lGroupShowing: uchar; begin Byte(gPDLLBusy^) := CTRUE; // ALE 20130816 don't process other events in GE if pPActGroup <> nil then begin lGroupID := pPActGroup^.id; lGroupSide := pPActGroup^.side; lGroupShowing := pPActGroup^.showing; // ALE 20191230 would also be good to check if non-player group is a base // since we make an assumption here that any bases are ShowExact if (lGroupSide = intSide) OR ((lGroupShowing AND ShowExact) > 0) then begin if (pPActGroup^.gtype <> 'T') AND (pPActGroup^.gtype <> 'M') then begin //frmUSNI.lblActGroup.HTMLText.Text := '' + // pPActGroup^.id + ''; // ALE 20191206 enumerate units in the group and create // USNI search URLs from them lPHCUnit := pPActGroup^.Units; frmUSNI.lbxUnits.BeginUpdate; frmUSNI.lbxUnits.Items.Clear; while lPHCUnit <> nil do begin if (lGroupSide = intSide) OR ((lPHCUnit^.showing[0] AND ShowExact) > 0) then begin lUnitType := lPHCUnit^.utype; if (lUnitType <> Ord('T')) AND (lUnitType <> Ord('M')) then begin lUnitName := lPHCUnit^.name; //LogIt('Before Add to Unit List ' + lUnitName + ' GroupID=' + lGroupID); frmUSNI.lbxUnits.Items.Add(Format('%s %s' + #9 + 'Media Search', [lGroupID, lUnitName, USNIURLMedia(lUnitName), USNIURLSearch(lUnitName)])); end; end; lPHCUnit := lPHCUnit^.next; end; frmUSNI.lbxUnits.EndUpdate; end; end; end; Byte(gPDLLBusy^) := CFALSE; end; { *** Invoked every time a scenario or saved game is loaded *** } procedure ScenLoad(strHarpPath: PAnsiChar; intScenNum: SmallInt; strScenFPath: PAnsiChar; intBSNum: shortint; strBSFName: PAnsiChar; intSideIn: 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; intSide := intSideIn; // 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, HCannex_id_to_disk_id, intVerInterface, intVerSaveGame); DatabaseVersionDB := DatabaseVersionDBIn; // ALE 20170923 current database ScenData := ScenDataIn; // ALE 20170923 unique identifiers, lineage, etc. ScenSaveData := ScenSaveDataIn; // ALE 20170923 save UUIDs and Scenario Orders LogIt(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, intSideIn, 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])); // ALE 20170923 Enumerate the scenario lineage if (ScenData^.Lineage.Count > 0) then begin lRow := 1; lLLI := @ScenData^.Lineage.Top; while (lLLI <> nil) do begin LogIt(Format('Lineage Item#=%d, DateTimeSaved=%s, ScenarioVersion=%d, SEVersion=%s, DB=%s, UUID=%s,', [lRow, FormatDateTime('yyyy-MM-dd hh:nn:ss', UnixToDateTime(lLLI^.dtstamp)), lLLI^.ScenarioVersion, lLLI^.SEVersion, lLLI^.DatabaseVersion.RESFile.flavor, lLLI^.uuid])); Inc(lRow); lLLI := lLLI^.Next; end; end; end; exports DLLVer, DLLVerInt, DLLDesc, DLLFree, DLLInit, PDUnitPic, ActUnitPtr, ActGroupPtr, ScenLoad; end.