Is there a function or procedure in clarion to convert a txt file containing information encoded in ASCII85 to binary
Thanks in advance
Is there a function or procedure in clarion to convert a txt file containing information encoded in ASCII85 to binary
Thanks in advance
Hi,
I’m not sure there is one built in, but 3rd party products support this function.
Our Chilkat wrapper for the Chilkat library includes that functionality - it can be accomplished in 2 or 3 lines of code - so nice and simple
Happy to demonstrate it to you if you like - feel free to pop in on our Monday webinar and I’ll knock up an example using your file.
Regards,
Andy
www.noyantis.com
Hi Francisco
it looks like you have a 3rd party option with Andy’s Noyantis wrapper for Chilkat library, but if you need to write it yourself, have a look at
as it looks quite straightforward to code. So, assuming you are a programmer then give it a go and if you get stuck then yell out as many people here can no doubt help.
cheers
Geoff R
Hi there, thanks for your answers, ill check 3rd party product Andy. And absolutely ill give it a try to my brain to writing myself
How did you go Francisco?
While I thought you would learn more doing it yourself rather than seeing someone else’s code, I did feel that perhaps I could have been more helpful.
So I have spent a couple of hours and written both Encode and Decode functions for ASCII85.
I always use StringTheory for all handling of strings and hence have done so here, but the code should be easy to adapt if you use the inbuilt SystemStringClass or something else.
so to encode a text file to ASCII85
st stringTheory
CODE
if ~st.loadFile('whatever.txt')
Message('Failed to load file whatever.txt||Error: '& st.LastError)
! error handling
end
Ascii85Encode(st)
st.SaveFile('whatever.A85')
and similarly to decode an Ascii85 encoded file:
st.loadFile('whatever.A85')
Ascii85Decode(st)
st.SaveFile('whatever.txt')
I’ve kept it simple and the two functions take a ST object and then either encode or decode it.
StringTheory has lots of builtin encoding methods (eg for Base64 and various unicodes etc) but doesn’t currently (vers 3.37) have ones for Ascii85 so I will send these to Bruce at Capesoft in case he wants to include them in a future version.
Note that there are a few variations of Ascii85 so you may have to tweak this code if your files are using a different variation. But I have allowed for the short form of zeroes (low-values) and spaces which are represented by ‘z’ and ‘y’ respectively as a form of compression.
The two functions are quite compact and seem quite fast on my testing. Any questions or problems yell out and any problems provide a sample file that is failing (input and expected output) and I will have a look when I get a chance. Obviously this code is provided “AS IS” so do your own testing!
Ascii85Encode PROCEDURE (StringTheory pSt)
st StringTheory
myLong long,auto
myStr String(4),over(myLong)
tempStr String(4),auto
x long,auto
y long,auto
padChars long,auto
out5 String(5),auto
CODE
if ~pSt._DataEnd then return.
padChars = pSt._DataEnd % 4
if padChars then padChars = 4 - padChars; pSt.append(all('<0>',padChars)). ! pad with null chars
st.SetLength(pSt._DataEnd * 5 / 4) ! preallocate output memory
st.free()
loop x = 1 to pSt._DataEnd by 4
! swap endian-ness as we go...
myStr[1] = pSt.valueptr[x+3]
myStr[2] = pSt.valueptr[x+2]
myStr[3] = pSt.valueptr[x+1]
myStr[4] = pSt.valueptr[x]
if ~myLong then st.append('z'); cycle. ! short form for 0 (low-values)
if ~myStr then st.append('y'); cycle. ! short form for spaces
loop y = 1 to 4
out5[6-y] = chr(int(myLong%85) + 33)
mylong /= 85
end
out5[1] = chr(myLong + 33)
st.append(out5)
end
if padChars then st.setLength(st._DataEnd - padChars).
pSt._StealValue(st) ! point our passed object to our output
Ascii85Decode PROCEDURE (StringTheory pSt)
st StringTheory
myLong long
myStr String(4),over(myLong)
x long,auto
y long
padChars long,auto
CODE
if pSt._DataEnd > 3 and pSt.startsWith('<<~') and pSt.endsWith('~>')
pSt.crop(3, pSt._DataEnd-2) ! remove Adobe begin/end chars
end
padChars = pSt._DataEnd % 5
if padChars then padChars = 5 - padChars; pSt.append(all('u',padChars)). ! pad with u chars
loop x = 1 to pSt._DataEnd
case val(pSt.valueptr[x])
of 122; st.append('<0,0,0,0>') !z used for zeroes (low-values)
of 121; st.append('<32,32,32,32>') !y used for spaces
of 33 to 117
y += 1
myLong = (myLong * 85) + val(pSt.valueptr[x]) - 33
if y = 5
st.append(myStr[4])
st.append(myStr[3])
st.append(myStr[2])
st.append(myStr[1])
y = 0
myLong = 0
end
end !case
end
if padChars then st.setLength(st._DataEnd - padChars).
pSt._StealValue(st) ! point our passed object to our output
That’s impressively small Geoff!
Jeff Atwood is one of the main guys behind this Discourse software and Coding Horror. He had a C# class that looked good to me, so I converted that to Clarion last night. It handles Whitespace, the Prefix / Suffix, line breaking on output. It did not support the ‘y’ for spaces, but that’s not part of the Adobe Ascii85 standard. Would be easy to add, but needs to be optional.
I know it converts the Wikipedia ASCII 85 example:
Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.
<~9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKF<GL>[email protected]$d7F!,[email protected]<[email protected])/0JDEF<G%<+EV:2F!,
O<DJ+*[email protected]<*[email protected]<6L(Df-\0Ec5e;DffZ(EZee.Bl.9pF"AGXBPCsi+DGm>@3BB/F*&OCAfu2/AKY
i(DIb:@FD,*)+C][email protected]#[email protected]?d$AftVqCh[NqF<G:8+EV:.+Cf>-FD5W8ARlolDIa
l(DId<[email protected]<[email protected]:F%a+D58'[email protected]:,-DJs`8ARoFb/[email protected]^F!,R<AKZ&-DfTqBG%G
>uD.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c~>
Thanks Carl! It is amazing that there previously wasn’t a Clarion language implementation of ASCII85 and now there are two the same day!
And it is interesting to compare the different approaches.
I have had some private discussions with Carl, who pointed out that my “myLong” was dangerous where the top bit could have been on and so should have been “myUlong”. Anyway I have changed the code to use a ULONG where necessary and a LONG where possible as LONG’s are faster for arithmatic without possible intermediate conversion to Decimal or Real.
Carl also mentioned that the space compression using ‘y’ was not supported by Adobe so I have added a parameter for Adobe (which defaults to true). The space compression (where four spaces goes to a single character ‘y’ rather than 5 characters) is off if Adobe encoding is requested - Adobe also wraps the encoded string in <~ and ~>.
I have added line wrapping as an option and defaulted it to 75.
The padding on the decoding has also changed a bit as I realized that it couldn’t be done so early when we didn’t know how many line feeds or other excluded characters there were. Sure we could have done something like pSt.KeepChars() with a list of OK characters but that would have meant traversing the string twice - once in KeepChars and once in our code so I decided against that.
Talking of such things I was a bit lazy with the line wrapping in the encode. I used st.SplitEvery() as it is just too easy to do that then re-join with the line breaks (being careful as Carl also advised not to split a line amidst the final ~> in Adobe mode). This does roughly double the output memory requirement as it briefly has the string split up in the lines queue. I may later revisit that and put in the line breaks as it goes along but this will certainly do for the moment - believe me splitting and joining in ST are incredibly fast.
anyway enough for now - updated code follows
cheers
Geoff R
Ascii85Encode PROCEDURE (StringTheory pSt,Long pWrapLen=75,Bool pAdobe=1)
st StringTheory
myLong long,auto
myULong ulong,over(myLong)
myStr String(4),over(myLong)
tempStr String(4),auto
x long,auto
y long,auto
padChars long,auto
outLen long,auto
out5 String(5),auto
CODE
if ~pSt._DataEnd then return.
if pWrapLen < 0 then pWrapLen = 0.
padChars = pSt._DataEnd % 4
if padChars then padChars = 4 - padChars; pSt.append(all('<0>',padChars)). ! pad with null chars
outLen = (pSt._DataEnd * 5 / 4) + choose(~pAdobe,0,4) ! adobe has 4 extra chars
if pWraplen then outlen += 2 * int(outlen / pWrapLen). ! add 2 chars for each line break
st.SetLength(outlen) ! preallocate output memory (optional)
if pAdobe
st.setValue('<<~')
else
st.free()
end
loop x = 1 to pSt._DataEnd by 4
! swap endian-ness as we go...
myStr[1] = pSt.valueptr[x+3]
myStr[2] = pSt.valueptr[x+2]
myStr[3] = pSt.valueptr[x+1]
myStr[4] = pSt.valueptr[x]
if ~myLong then st.append('z'); cycle. ! short form for 0 (low-values)
if ~pAdobe and ~myStr then st.append('y'); cycle. ! short form for spaces - not supported by Adobe
out5[5] = chr(myULong%85 + 33) ! use Ulong first time in case top bit is on
myUlong /= 85
loop y = 4 to 2 by -1
out5[y] = chr(myLong%85 + 33) ! use long where possible as faster
mylong /= 85
end
out5[1] = chr(myLong + 33)
st.append(out5)
end
if padChars then st.setLength(st._DataEnd - padChars).
if pAdobe then st.append('~>').
if pWrapLen
st.splitEvery(pWrapLen)
if pAdobe and len(st.getLine(st.records())) = 1
! do NOT split ending ~>
st.deleteLine(st.records())
st.setLine(st.records(),st.getLine(st.records())&'>')
end
st.join('<13,10>')
end
pSt._StealValue(st) ! point our passed object to our output
Ascii85Decode PROCEDURE (StringTheory pSt)
st StringTheory
myLong long
myUlong ulong,over(myLong)
myStr String(4),over(myLong)
x long
y long
padChars long
CODE
if pSt._DataEnd > 3 and pSt.startsWith('<<~') and pSt.endsWith('~>')
pSt.crop(3, pSt._DataEnd-2) ! remove Adobe begin/end chars
end
x = 1
loop 5 times
loop x = x to pSt._DataEnd
case val(pSt.valueptr[x])
of 122; st.append('<0,0,0,0>') !z used for zeroes (low-values)
of 121; st.append('<32,32,32,32>') !y used for spaces
of 33 to 117
y += 1
if y < 5
myLong = (myLong * 85) + val(pSt.valueptr[x]) - 33
else
myUlong = (myUlong * 85) + val(pSt.valueptr[x]) - 33
st.append(myStr[4])
st.append(myStr[3])
st.append(myStr[2])
st.append(myStr[1])
y = 0
myLong = 0
end
end !case
end
if y ! padding required?
padChars += 1
pSt.append('u') ! pad with u char
else
break
end
end
if padChars then st.setLength(st._DataEnd - padChars).
pSt._StealValue(st) ! point our passed object to our output
I also had a LONG problem with High ASCII in Decode I fixed. In this calculation _Tuple is a ULONG but the right-side was being calculated as a LONG and going negative.
SELF._tuple += (cb - _asciiOffset) * Pow85[count]
Changing to Pow85 from LONG to ULONG is one fix.
I have been reading and understanding code and behavior of Adobe decoding. And I know about StringTheory but I am not currently using it, so I implemented Charles’s solution, it has left me very close to the goal, I will try to see what is the problem that happens to me when I decode Adobe Ascii85 string since it does not finish closing the file that is supposed to be a .zip and it appears to be incomplete or corrupted.
But it has been very useful. If i found the reason ill share it, and again thanks a lot. Even having code translation from C++ this code blows my brain out
I found the problem, originally Charles code calculate size as follows
DecodedSize = Len_s85 / 5 * 4 + 5
The size of DecodedStr was not enough (thats why didnt complete binary file and stays corrupt),
i’ve replace it with
DecodedSize = LEN (SUB (CLIP (s85), S1, S2))
this worked for the first test. but i think that formula is for a reason… maybe is missing
parenthesis priority?
The problem was ASCII 85 compresses <0,0,0,0> as a 1 ‘z’ and not 5 letters. I uploaded the corrected the calculation. I had made a few more improvements over the past few days.
DecodedSize = (Len_s85 - Zeros) / 5 * 4 + Zeros * 4 + 5
Hello again Francisco
glad you have got a solution - and also learnt a bit studying the code. While you didn’t opt to use StringTheory this time round I strongly recommend ST to anyone who will listen It can save you a lot of time! (Disclaimer: I am probably a bit biased as while ST is a Capesoft product I have spent a lot of time working on it over the last decade or so.)
anyway I have had some further discussions with Carl and I have made a few changes and optimisations to my code (and Carl has made some to his code too).
the changes made to encode didn’t really result in any significant speed up, but those on decode did make it a bit faster. The encode time is generally much greater than decode.
I also have added a return code on the decode function. Previously it just ignored any errors but now I am detecting invalid ASCII85 files and return the position of the first character in error. Note that there is no such thing as an invalid file when doing an encode - it will encode anything, but on decode you can have unexpected characters if the file is corrupted.
I have chosen to leave the input unchanged when the decoding finds an error.
I guess there were three choices:
I opted for #3 although you could mount an argument for any of them.
anyway where previously we simply had:
st.loadFile('whatever.A85')
Ascii85Decode(st)
st.SaveFile('whatever.txt')
you should now check for errors - a return value of 0 means all good - no errors detected.
failed long
code
st.loadFile('whatever.A85')
failed = Ascii85Decode(st)
if failed
message('ASCII85 decode failed at byte ' & retval & ' of ' & st._DataEnd)
! error handling
else
! all good - decoded without any problems
end
anyway my latest versions follow:
Ascii85Encode PROCEDURE (StringTheory pSt,Long pWrapLen=75,Bool pAdobe=1)
st StringTheory
myLong long,auto
myULong ulong,over(myLong)
myGrp group,pre(),over(myLong)
myStr1 string(1)
myStr2 string(1)
myStr3 string(1)
myStr4 string(1)
end ! group
x long,auto
y long,auto
padChars long,auto
outLen long,auto
outstr String(5),auto
group,pre(),over(outStr)
out1 byte
out2 byte
out3 byte
out4 byte
out5 byte
end ! group
CODE
if ~pSt._DataEnd then return.
if pWrapLen < 0 then pWrapLen = 0.
padChars = pSt._DataEnd % 4
if padChars then padChars = 4 - padChars; pSt.append(all('<0>',padChars)). ! pad with null chars
outLen = (pSt._DataEnd * 5 / 4) + choose(~pAdobe,0,4) ! adobe has 4 extra chars
if pWraplen then outlen += 2 * int(outlen / pWrapLen). ! add 2 chars for each line break
st.SetLength(outlen) ! optional: preallocate output memory
if pAdobe
st.setValue('<<~')
else
st.free()
end
loop x = 1 to pSt._DataEnd by 4
! swap endian-ness as we go...
myStr1 = pSt.valueptr[x+3]
myStr2 = pSt.valueptr[x+2]
myStr3 = pSt.valueptr[x+1]
myStr4 = pSt.valueptr[x]
if ~myLong then st.append('z'); cycle. ! short form for 0 (low-values)
if ~pAdobe and myLong=20202020h then st.append('y'); cycle. ! short form for spaces - not supported by Adobe
out5 = myULong%85 + 33 ! use Ulong first time in case top bit is on
myUlong /= 85
! unrolled the loop - we use long from here as faster than ulong
out4 = myLong%85 + 33
mylong /= 85
out3 = myLong%85 + 33
mylong /= 85
out2 = myLong%85 + 33
mylong /= 85
out1 = myLong%85 + 33
st.append(outStr)
end
if padChars then st.setLength(st._DataEnd - padChars).
if pAdobe then st.append('~>').
if pWrapLen
st.splitEvery(pWrapLen)
if pAdobe and len(st.getLine(st.records())) = 1
! do NOT split ending ~>
st.deleteLine(st.records())
st.setLine(st.records(),st.getLine(st.records())&'>')
end
st.join('<13,10>')
end
pSt._StealValue(st) ! point our passed object to our output
Ascii85Decode PROCEDURE (StringTheory pSt) !,long
st StringTheory
myLong long
myUlong ulong,over(myLong)
myStr String(4),over(myLong)
group,pre(),over(myLong)
myStr1 string(1)
myStr2 string(1)
myStr3 string(1)
myStr4 string(1)
end ! group
CurValue long,auto ! value of current character
swapStr string(1)
x long
y long
padChars long
AdobePrfx long
CODE
if pSt._DataEnd > 3 and pSt.startsWith('<<~') and pSt.endsWith('~>')
AdobePrfx = 2
pSt.crop(3, pSt._DataEnd-2) ! remove Adobe begin/end chars
end
st.setLength(pSt._DataEnd); free(st) ! preallocate some space (optional)
x = 1
loop !5 times
loop x = x to pSt._DataEnd
curValue = val(pSt.valueptr[x])
case curValue
of 33 to 117
y += 1
if y < 5
myLong = myLong*85 + curValue - 33
else
myUlong = myUlong*85 + curValue - 33
! swap endian-ness (reverse byte order)
swapStr = myStr1; myStr1 = myStr4; myStr4 = swapStr ! swap chars
swapStr = myStr2; myStr2 = myStr3; myStr3 = swapStr ! swap chars
st.append(myStr)
y = 0
myLong = 0
end
of 122
if y then return x+AdobePrfx. ! error - 'z' within group of 5 chars
st.append('<0,0,0,0>') ! z used for zeroes (low-values)
of 121
if y then return x+AdobePrfx. ! error - 'y' within group of 5 chars
st.append('<32,32,32,32>') ! y used for spaces
of 8 to 13
orof 32
! valid formating character so just ignore it
else
! error - dud/unexpected character so return position
return x + AdobePrfx
end !case
end
if y ! padding required?
padChars += 1
pSt.append('u') ! pad with u char
else
break
end
end
if padChars then st.setLength(st._DataEnd - padChars).
pSt._StealValue(st) ! point our passed object to our output
return 0 ! all is well in the world (valid input decoded without error)
cheers for now
Geoff R
In my Post 6 GubHub Repo I have ST_85est.clw that has Geoff’s code and an example of using it. I updated it with his most recent post. You will need StringTheory.
I’m trying ASCII 85 to obfuscate a text file I write to disk that I would prefer not be read, but does not really need encryption. The file had a lot of spaces so with the “y” = 4 spaces it actually got smaller. My class allows specifying the Prefix / Suffix strings to be different than the Adobe <~ ~>. I used Curly Braces {xxx{ and }xxx} that are not used by the encoding so I’m sure it’s my file and I’m not jumping into the middle.
This Code Project suggests using ASCII 85 to obfuscate text placed on the clipboard. It was interesting he also used the Jeff Atwood ASCII 85 code
Spurred on by Carl’s efforts, I have done some more optimising on the decode function - basically previously I was following the instructions in Wikipedia where you added padding if necessary and later removed some of the characters. This seemed unnecessary so I have managed to remove some of it… and done a few other speed ups as well.
Carl mentioned to me that his latest changes to his ASCII85 decode had made it 10x faster which is amazing - my changes here yielded much smaller benefits and I think I am getting well in the area of diminishing returns so perhaps this will be my final version for now…
For completeness I am including both my ASCII85 encode and decodes, but only the decode has been further optimised.
Ascii85Encode PROCEDURE (StringTheory pSt,Long pWrapLen=75,Bool pAdobe=1)
st StringTheory
myLong long,auto
myULong ulong,over(myLong)
group,pre(),over(myLong)
myStr1 string(1)
myStr2 string(1)
myStr3 string(1)
myStr4 string(1)
end ! group
x long,auto
y long,auto
padChars long,auto
outLen long,auto
outstr String(5),auto
group,pre(),over(outStr)
out1 byte
out2 byte
out3 byte
out4 byte
out5 byte
end ! group
CODE
if ~pSt._DataEnd then return.
if pWrapLen < 0 then pWrapLen = 0.
padChars = pSt._DataEnd % 4
if padChars then padChars = 4 - padChars; pSt.append(all('<0>',padChars)). ! pad with null chars
outLen = (pSt._DataEnd * 5 / 4) + choose(~pAdobe,0,4) ! adobe has 4 extra chars
if pWraplen then outlen += 2 * int(outlen / pWrapLen). ! add 2 chars for each line break
st.SetLength(outlen) ! optional: preallocate output memory
if pAdobe
st.setValue('<<~')
else
st.free()
end
loop x = 1 to pSt._DataEnd by 4
! swap endian-ness as we go...
myStr1 = pSt.valueptr[x+3]
myStr2 = pSt.valueptr[x+2]
myStr3 = pSt.valueptr[x+1]
myStr4 = pSt.valueptr[x]
if ~myLong then st.append('z'); cycle. ! short form for 0 (low-values)
if ~pAdobe and myLong=20202020h then st.append('y'); cycle. ! short form for spaces - not supported by Adobe
out5 = myULong%85 + 33 ! use Ulong first time in case top bit is on
myUlong /= 85
! unrolled the loop - we use long from here as faster than ulong
out4 = myLong%85 + 33
mylong /= 85
out3 = myLong%85 + 33
mylong /= 85
out2 = myLong%85 + 33
mylong /= 85
out1 = myLong%85 + 33
st.append(outStr)
end
if padChars then st.setLength(st._DataEnd - padChars).
if pAdobe then st.append('~>').
if pWrapLen
st.splitEvery(pWrapLen)
if pAdobe and len(st.getLine(st.records())) = 1
! do NOT split ending ~>
st.deleteLine(st.records())
st.setLine(st.records(),st.getLine(st.records())&'>')
end
st.join('<13,10>')
end
pSt._StealValue(st) ! point our passed object to our output
Ascii85Decode PROCEDURE (StringTheory pSt) !,long ! Declare Procedure
st StringTheory
myGroup group,pre()
myStr1 string(1)
myStr2 string(1)
myStr3 string(1)
myStr4 string(1)
myStr5 string(1)
myStr6 string(1)
myStr7 string(1)
end ! group
myLong long, over(myGroup)
myUlong ulong,over(myGroup)
CurValue long,auto ! value of current character
x long,auto
y long
AdobePrfx long
CODE
if pSt._DataEnd > 3 and pSt.valuePtr[1 : 2] = '<<~' and pSt.valuePtr[pSt._dataEnd-1 : pSt._dataEnd] = '~>'
AdobePrfx = 2
pSt.setLength(pSt._dataEnd - 2)
x = 3
else
x = 1
end
st.setLength(pSt._DataEnd); free(st) ! preallocate some space (optional)
myLong = 0
loop x = x to pSt._DataEnd
curValue = val(pSt.valueptr[x])
case curValue
of 33 to 117
y += 1
if y < 5
myLong = myLong*85 + curValue - 33
else
myUlong = myUlong*85 + curValue - 33
myStr5 = myStr3; myStr6 = myStr2; myStr7 = myStr1 ! swap endian-ness (reverse byte order)
st.CatAddr(address(myStr4),4)
y = 0
myLong = 0
end
of 122
if y then return x+AdobePrfx. ! error - 'z' within group of 5 chars
st.append('<0,0,0,0>') ! z used for zeroes (low-values)
of 121
if y then return x+AdobePrfx. ! error - 'y' within group of 5 chars
st.append('<32,32,32,32>') ! y used for spaces
of 8 to 13
orof 32
! valid formating character so just ignore it
else
! error - dud/unexpected character so return position
return x + AdobePrfx
end !case
end
if y > 1 ! padding required?
loop 4-y times
myLong = myLong*85 + 84 ! 84 = val('u') - 33 = 117 - 33
end
myUlong = myUlong*85 + 84
case y
of 2
st.append(myStr4)
of 3 ! myStr4 & myStr3
myStr5 = myStr3
st.CatAddr(address(myStr4),2)
of 4 ! myStr4 & myStr3 & myStr2
myStr5 = myStr3; myStr6 = myStr2
st.CatAddr(address(myStr4),3)
end
end
pSt._StealValue(st) ! point our passed object to our output
return 0 ! all is well in the world (valid input decoded without error)
Hi Carl -
Just FYI - the latest Ascii85.cwproj uploaded does not compile with C10; .compiles okay with C11.
Regards,
-Rex
I added COMPILE / OMIT _C110_ to make the Ascii85 and FileTo85 examples work under C10. The class had no issues.
The C11 System String class added method .GetStringRef(),*STRING
. if you are using that class in C10 I would add that same method to save pushing a giant string on the string stack:
SystemStringClass.GetStringRef PROCEDURE()
CODE
RETURN SELF.s
Thanks Rex for the heads up!
just in case anyone is looking for this who has StringTheory, as of version 3.45 this is now included:
Version 3.45 - 11 April 2022