14.01.2026, 16:14
I posted this on the new community run PB Users Forum: https://pbusers.org/forum/viewtopic.php?t=239
Sharing here as well as I think it is useful for those that might not have joined there yet.
============================
It always bothered me that PowerBASIC never implemented a #RESOURCE AVI metastatement. If you want to include an AVI for the animation control via #RESOURCE metastatements it's impossible as you cannot animate directly from RCDATA (or other resource sections available via the #RESOURCE metastatement). It can only be done from an AVI resource section (or from a supplied file path). Yes, you can compile the AVI into an AVI resource section using an RC file compiler and add it as a PBR (which still works but was technically deprecated) or RES, but "you cannot add any other #RESOURCE metastatements in your program" as per the PowerBASIC documentation.
What this code does is allow you to embed AVIs into your code using #RESOURCE RCDATA. After you compile your code to DLL or EXE you run this patcher by supplying the DLL/EXE file path on the command line (or via drag-and-drop). It proceeds to enumerate the AVIs and moves them to an AVI resource section using UpdateResource.
Be aware that you will need to repatch the DLL/EXE each time you re-compile your code.
As such, this could be included in a tool chain to always run after a compile.
Processing is written to a log (AVIRscPtch_Log.csv) in the directory the targeted module is located. To facilitate running in a toolchain it runs silently if no errors occur, but uses MESSAGEBOX to report any errors (in addition to the log).
Sharing here as well as I think it is useful for those that might not have joined there yet.
============================
It always bothered me that PowerBASIC never implemented a #RESOURCE AVI metastatement. If you want to include an AVI for the animation control via #RESOURCE metastatements it's impossible as you cannot animate directly from RCDATA (or other resource sections available via the #RESOURCE metastatement). It can only be done from an AVI resource section (or from a supplied file path). Yes, you can compile the AVI into an AVI resource section using an RC file compiler and add it as a PBR (which still works but was technically deprecated) or RES, but "you cannot add any other #RESOURCE metastatements in your program" as per the PowerBASIC documentation.
What this code does is allow you to embed AVIs into your code using #RESOURCE RCDATA. After you compile your code to DLL or EXE you run this patcher by supplying the DLL/EXE file path on the command line (or via drag-and-drop). It proceeds to enumerate the AVIs and moves them to an AVI resource section using UpdateResource.
Be aware that you will need to repatch the DLL/EXE each time you re-compile your code.
As such, this could be included in a tool chain to always run after a compile.
Processing is written to a log (AVIRscPtch_Log.csv) in the directory the targeted module is located. To facilitate running in a toolchain it runs silently if no errors occur, but uses MESSAGEBOX to report any errors (in addition to the log).
Code:
' Compiles under PBWIN or PBCC without changes
#COMPILE EXE "AVIRscPtch.exe"
#DIM ALL
'----------------------------------------------------------------------------(')
%UNICODE = 1
'----------------------------------------------------------------------------(')
$DDQ = $DQ + $DQ
'----------------------------------------------------------------------------(')
#INCLUDE "Win32API.inc" ' Roca Headers III_107
'----------------------------------------------------------------------------(')
GLOBAL v_ghMod AS DWORD
GLOBAL v_ghUpd AS DWORD
GLOBAL v_gsExe AS STRING
'----------------------------------------------------------------------------(')
FUNCTION fn_sCSV(BYVAL v_sText AS WSTRING) AS STRING
IF INSTR(v_sText, ANY CHR$(34, 44)) THEN
REPLACE $DQ WITH $DDQ IN v_sText
FUNCTION = $DQ + v_sText + $DQ
ELSE
FUNCTION = v_sText
END IF
END FUNCTION
'----------------------------------------------------------------------------(')
FUNCTION fn_sTimestamp() AS WSTRING
LOCAL v_uST AS SYSTEMTIME
GETSYSTEMTIME v_uST
FUNCTION = _
FORMAT$(v_uST.wYear, "0000") + "-" + _
FORMAT$(v_uST.wMonth, "00") + "-" + _
FORMAT$(v_uST.wDay, "00") + " " + _
FORMAT$(v_uST.wHour, "00") + ":" + _
FORMAT$(v_uST.wMinute, "00") + ":" + _
FORMAT$(v_uST.wSecond, "00") + "Z"
END FUNCTION
'----------------------------------------------------------------------------(')
FUNCTION fn_sGetErrorText(BYVAL v_lError AS LONG) AS WSTRING
LOCAL v_lReturn AS LONG
LOCAL v_szBuffer AS WSTRINGZ * 256
IF v_lError = %ERROR_SUCCESS THEN EXIT FUNCTION
v_lReturn = FORMATMESSAGE(%FORMAT_MESSAGE_FROM_SYSTEM, BYVAL 0&, v_lError, BYVAL 0&, v_szBuffer, SIZEOF(v_szBuffer), BYVAL 0&)
IF v_lReturn > 0 THEN FUNCTION = RTRIM$(LEFT$(v_szBuffer, v_lReturn), ANY $CRLF)
END FUNCTION
'----------------------------------------------------------------------------(')
SUB sub_Log(BYVAL v_bError AS LONG, v_sText AS WSTRING)
LOCAL v_hFile AS LONG
LOCAL v_lError AS LONG
LOCAL v_sLogPath AS WSTRING
LOCAL v_sTimestamp AS WSTRING
v_sTimestamp = fn_sTimestamp
v_sLogPath = PATHNAME$(PATH, v_gsExe) + "\AVIRscPtch_Log.csv"
TRY
OPEN v_sLogPath FOR APPEND AS # v_hFile
PRINT # v_hFile, v_sTimestamp + "," + fn_sCSV(v_sText)
CLOSE # v_hFile
IF v_bError THEN MESSAGEBOX %HWND_DESKTOP, "Error during patching:" + $LF + v_sText, EXE.NAMEX$, %MB_ICONEXCLAMATION
CATCH
v_lError = ERR
MESSAGEBOX %HWND_DESKTOP, _
"Could not record message to the log file." + $LF + $LF + _
"Timestamp:" + $LF + v_sTimestamp + $LF + $LF + _
"Log Path:" + $LF + v_sLogPath + $LF + $LF + _
"Error:" + ERROR$(v_lError) + $LF + $LF + _
"Msg:" + $LF + v_sText, _
EXE.NAMEX$, %MB_ICONEXCLAMATION
IF v_hFile THEN CLOSE # v_hFile
END TRY
END SUB
'----------------------------------------------------------------------------(')
FUNCTION fn_bIsAVI(BYVAL v_pData AS DWORD, BYVAL v_cbData AS DWORD) AS LONG
IF v_pData = 0 THEN sub_Log 0, "pdata = 0" : EXIT FUNCTION
IF v_cbData < 12 THEN sub_Log 0, "cbData < 12" : EXIT FUNCTION
IF PEEK$(v_pData, 4) <> "RIFF" THEN sub_Log 0, "Not a RIFF" : EXIT FUNCTION
IF PEEK$(v_pData + 8, 4) <> "AVI " THEN sub_Log 0, "Not an AVI" : EXIT FUNCTION
FUNCTION = %TRUE
END FUNCTION
'----------------------------------------------------------------------------(')
FUNCTION fn_lEnumLangProc( _
BYVAL v_hModule AS DWORD, _
BYVAL v_lpType AS DWORD, _
BYVAL v_lpName AS DWORD, _
BYVAL v_wLang AS WORD, _
BYVAL v_lParam AS LONG) AS LONG
LOCAL v_bOK AS LONG
LOCAL v_cbData AS DWORD
LOCAL v_hData AS DWORD
LOCAL v_hRes AS DWORD
LOCAL v_lError AS LONG
LOCAL v_pData AS DWORD
LOCAL v_wzTypeAVI AS WSTRINGZ * 4
' FindResourceEx
v_hRes = FINDRESOURCEEX(v_hModule, BYVAL %RT_RCDATA, BYVAL v_lpName, v_wLang)
v_lError = GETLASTERROR
IF v_hRes = 0 THEN
sub_Log 0, "Error calling FindResourceEx: " + fn_sGetErrorText(v_lError)
FUNCTION = %TRUE
EXIT FUNCTION
END IF
v_cbData = SIZEOFRESOURCE(v_hModule, v_hRes)
v_hData = LOADRESOURCE(v_hModule, v_hRes)
v_pData = LOCKRESOURCE(v_hData)
IF fn_bIsAVI(v_pData, v_cbData) THEN
v_wzTypeAVI = "AVI"
' UpdateResource - Copy resource to new resource type
v_bOK = UPDATERESOURCE(v_ghUpd, BYVAL VARPTR(v_wzTypeAVI), BYVAL v_lpName, v_wLang, BYVAL v_pData, v_cbData)
v_lError = GETLASTERROR
IF v_bOK THEN
' UpdateResource - Remove resource from old resource type
v_bOK = UPDATERESOURCE(v_ghUpd, BYVAL %RT_RCDATA, BYVAL v_lpName, v_wLang, BYVAL %NULL, 0)
v_lError = GETLASTERROR
IF v_bOK = %FALSE THEN sub_Log 1, "Error calling UpdateResource (delete old): " + fn_sGetErrorText(v_lError)
ELSE
sub_Log 1, "Error calling UpdateResource (copy to new): " + fn_sGetErrorText(v_lError)
END IF
END IF
FUNCTION = %TRUE
END FUNCTION
'----------------------------------------------------------------------------(')
FUNCTION fn_lEnumNameProc( _
BYVAL v_hModule AS DWORD, _
BYVAL v_lpType AS DWORD, _
BYVAL v_lpName AS DWORD, _
BYVAL v_lParam AS LONG) AS LONG
ENUMRESOURCELANGUAGES(v_hModule, BYVAL %RT_RCDATA, BYVAL v_lpName, CODEPTR(fn_lEnumLangProc), 0)
FUNCTION = %TRUE
END FUNCTION
'----------------------------------------------------------------------------(')
FUNCTION PBMAIN() AS LONG
LOCAL v_bOK AS LONG
LOCAL v_lError AS LONG
LOCAL v_sCmd AS STRING
LOCAL v_wzExe AS WSTRINGZ * %MAX_PATH
v_sCmd = TRIM$(COMMAND$, ANY CHR$(32, 34))
IF LEN(v_sCmd) THEN
v_gsExe = v_sCmd
ELSE
sub_Log 1, "Please supply a module (DLL/EXE) path to patch"
EXIT FUNCTION
END IF
IF ISFILE(v_gsExe) = %FALSE THEN
sub_Log 1, "Module (DLL/EXE) path does not exist"
EXIT FUNCTION
END IF
v_wzExe = v_gsExe
sub_Log 0, "Attemping to patch module: " + $DQ + v_gsExe + $DQ
v_ghMod = LOADLIBRARYEX(BYVAL VARPTR(v_wzExe), BYVAL %NULL, %LOAD_LIBRARY_AS_DATAFILE)
v_lError = GETLASTERROR
IF v_ghMod = 0 THEN
sub_Log 1, "Error loading module: " + fn_sGetErrorText(v_lError)
EXIT FUNCTION
END IF
' BeginUpdateResource
v_ghUpd = BEGINUPDATERESOURCE(BYVAL VARPTR(v_wzExe), %FALSE)
v_lError = GETLASTERROR
IF v_ghUpd = 0 THEN
sub_Log 1, "Error calling BeginUpdateResource: " + fn_sGetErrorText(v_lError)
FREELIBRARY v_ghMod
EXIT FUNCTION
END IF
' EnumResourceNames
v_bOK = ENUMRESOURCENAMES(v_ghMod, BYVAL %RT_RCDATA, CODEPTR(fn_lEnumNameProc), 0)
v_lError = GETLASTERROR
IF v_bOK = %FALSE THEN
sub_Log 1, "Error calling EnumResourceNames: " + fn_sGetErrorText(v_lError)
FREELIBRARY v_ghMod
EXIT FUNCTION
END IF
' Release the module
FREELIBRARY v_ghMod
' EndUpdateResource
v_bOK = ENDUPDATERESOURCE(v_ghUpd, %FALSE)
v_lError = GETLASTERROR
IF v_bOK = %FALSE THEN
sub_Log 1, "Error calling EndUpdateResource: " + fn_sGetErrorText(v_lError)
EXIT FUNCTION
END IF
sub_Log 0, "Module patched"
END FUNCTION