InitializeSecurityContext and Delphi - delphi

I'm trying to use SSPI authentication to connect to Sql Server. There is working (I hope) C example with FreeTds sspi.c that using InitializeSecurityContext.
The problem is it calling InitializeSecurityContext twice. At first (tds_sspi_get_auth) function called to make auth to put it into login packet. There Service principal name (SPN) created as (Project JEDI JwaSspi used)
FSPN := WideString(Format('MSSQLSvc/%s:%d', [FHostName, FPort]));
status := InitializeSecurityContext(#FCred, nil, PSecWChar(FSPN),
ISC_REQ_CONFIDENTIALITY or ISC_REQ_REPLAY_DETECT or ISC_REQ_CONNECTION,
0, SECURITY_NETWORK_DREP, nil, 0, #FCredCtx, #desc, attrs, #ts);
where FSPN: WideString;
Second call (tds_sspi_handle_next) to InitializeSecurityContext uses same FSPN and response from server
status := InitializeSecurityContext(#FCred, #FCredCtx, PSecWChar(FSPN),
ISC_REQ_CONFIDENTIALITY or ISC_REQ_REPLAY_DETECT or ISC_REQ_CONNECTION,
0, SECURITY_NETWORK_DREP, #in_desc, 0, #FCredCtx, #out_desc, attrs, #ts);
Now hard part: on C SPN created with asprintf, after first call to InitializeSecurityContext it changed (was $4D $00 $53 $00 $53 $00 ... , after $08 $04 $01 $00 $4E ...) and I guess replaced by Digest or similar. By using like that I have Access Violation somewhere in oleaut32.dll.

It is "Project JEDI" bug. SecHandle declared as
_SecHandle = record
dwLower: ULONG_PTR;
dwUpper: ULONG_PTR;
end;
where
INT_PTR = Integer;
{$EXTERNALSYM INT_PTR}
PINT_PTR = ^INT_PTR;
{$EXTERNALSYM PINT_PTR}
UINT_PTR = Longword;
{$EXTERNALSYM UINT_PTR}
PUINT_PTR = ^UINT_PTR;
{$EXTERNALSYM PUINT_PTR}
LONG_PTR = Longint;
{$EXTERNALSYM LONG_PTR}
PLONG_PTR = ^LONG_PTR;
{$EXTERNALSYM PLONG_PTR}
ULONG_PTR = Longword;
{$EXTERNALSYM ULONG_PTR}
PULONG_PTR = ^ULONG_PTR;
{$EXTERNALSYM PULONG_PTR}
by Microsoft ULONG_PTR is
typedef unsigned __int3264 ULONG_PTR;
and
2.2.1 __int3264
An alias that is resolved to either:
An __int32 in a 32-bit translation and execution environment, or
An __int64 in a 64-bit translation and execution environment. For backward compatibility, it is 32-bit on the wire. The higher 4 bytes MUST be truncated on the sender side during marshaling and MUST be extended appropriately (signed or unsigned), as specified in [C706] section 14.2.5, on the receiving side during unmarshaling.
So when I declared in my class
private
FCred: CredHandle;
FCredCtx: CtxtHandle;
FSPN: WideString;
InitializeSecurityContext with 64 bit executable smashed my class variables by writing larger structure into FCredCtx ruining FSPN. Using NativeInt or NativeUInt instead of Integer/Longword etc fixed issue.

Related

Generating OVH SHA1 signature

I'm trying to utilize the OVH API in Delphi using the REST client. For this OVH requires me to generate a signature, but their documentation does not provide much info on this other than:
"$1$" + SHA1_HEX(AS+"+"+CK+"+"+METHOD+"+"+QUERY+"+"+BODY+"+"+TSTAMP)
They do provide thin wrappers for other languages so I thought I could take a look at those and try to replicate it. I found the following for generating the signature in C# and have extracted the function to be used in a test application.
Test app C# code:
textBox1.Text = GenerateSignature("appSecret", "consKey", 123456789, "PUT", "/path/to/api", "TEST DATA");
The C# result is:
$1$8336ecc5d03640b976e0b3ba005234a3046ab695
I attempted the rewrite the function in Delphi and came up with the following function:
function GenerateSignature(const appSecret, consKey: string;
const currentTimeStamp: LongInt; const method, target: string;
const data: string = ''): string;
begin
var
toSign := string.Join('+', [appSecret, consKey, method, target, data,
currentTimeStamp]);
var
binaryHash := THashSHA1.GetHashBytes(toSign);
var
signature := '';
for var byte in binaryHash do
begin
signature := signature + byte.ToHexString.ToLower;
end;
Result := '$1$' + signature;
end;
And to test it:
procedure Main;
const
APP_SECRET = 'appSecret';
CONSUMER_KEY = 'consKey';
method = 'PUT';
target = '/path/to/api';
data = 'TEST DATA';
CURRENT_TIMESTAMP = 123456789;
begin
Writeln(GenerateSignature(APP_SECRET, CONSUMER_KEY, CURRENT_TIMESTAMP,
method, data));
end;
Both test applications in C# and in Delphi use the same data but produce different outputs. My expected output is:
$1$8336ecc5d03640b976e0b3ba005234a3046ab695
But I end up getting the following output from delphi:
$1$d99fd5086853e388056d6fe37a9e2d0723de151b
I do not know C# very well but it seems to get the hashbytes then convert it to hex and stitch it together. How can I modify the Delphi function I wrote so that I can get my expected result?
Thanks to the last parameter being optional you didn't notice (because no compiler error/warning) that you actually missed one parameter when calling/testing your function, resulting in a text of
'appSecret+consKey+PUT+TEST DATA++123456789' to be hashed. Which is indeed
d99fd5086853e388056d6fe37a9e2d0723de151b
Let me reformat your test to make it more obvious:
const
APP_SECRET = 'appSecret';
CONSUMER_KEY = 'consKey';
CURRENT_TIMESTAMP = 123456789;
method = 'PUT';
target = '/path/to/api'; // Where is this used?
data = 'TEST DATA';
begin
GenerateSignature
( APP_SECRET
, CONSUMER_KEY
, CURRENT_TIMESTAMP
, method
// Forgotten parameter
, data // becomes "target"
);
end;
Consider making const data: string = '' mandatory, too, instead of optional.

Copying an address from a pointer to a different memory address

I have a C DLL with a number of functions I'm calling from Delphi. One of the functions (say Func1) returns a pointer to a struct - this all works fine. The structs created by calling Func1 are stored in a global pool within the DLL. Using a second function (Func2) I get a pointer to a block of memory containing an array of pointers, and I can access the array elements using an offset.
I need to be able copy the address in the returned pointer for a struct (from Func1) to any of the memory locations in the array (from Func2). The idea is that I can build arrays of pointers to pre-defined structs and access the elements directly from Delphi using pointer offsets.
I tried using:
CopyMemory(Pointer(NativeUInt(DataPointer) + offset), PStruct, DataSize);
where DataPointer is the start of my array and PStruct is returned from Func1, but that doesn't copy the address I need.
In .NET it works using Marshal.WriteIntPtr and looking at the underlying code for this using Reflector I think I need something trickier than CopyMemory. Anyone got any ideas for doing this in Delphi?
Edit: This is part of a wrapper around vector structures returned from the R language DLL. I have a base vector class from which I derive specific vector types. I've got the wrapper for the numeric vector working, so my base class looks fine and this is where I get DataPointer:
function TRVector<T>.GetDataPointer: PSEXPREC;
var
offset: integer;
h: PSEXPREC;
begin
// TVECTOR_SEXPREC is the vector header, with the actual data behind it.
offset := SizeOf(TVECTOR_SEXPREC);
h := Handle;
result := PSEXPREC(NativeUInt(h) + offset);
end;
Setting a value in a numeric vector is easy (ignoring error handling):
procedure TNumericVector.SetValue(ix: integer; value: double);
var
PData: PDouble;
offset: integer;
begin
offset := GetOffset(ix); // -- Offset from DataPointer
PData := PDouble(NativeUInt(DataPointer) + offset);
PData^ := value;
end;
For a string vector I need to (i) create a base vector of pointers with a pre-specified length as for the numeric vector (ii) convert each string in my input array to an R internal character string (CHARSXP) using the R mkChar function (iii) assign the address of the character string struct to the appropriate element in the base vector. The string array gets passed into the constructor of my vector class (TCharacterVector) and I then call SetValue (see below) for each string in the array.
I should have thought of PPointer as suggested by Remy but neither that or the array approach seem to work either. Below is the code using the array approach from Remy and with some pointer vars for checking addresses. I'm just using old-fashioned pointer arithmetic and have shown addresses displayed for a run when debugging:
procedure TCharacterVector.SetValue(ix: integer; value: string);
var
PData: PSEXPREC;
offset: integer;
offset2: integer;
PTest: PSEXPREC;
PPtr: Pointer;
PPtr2: Pointer;
begin
offset := GetOffset(ix);
PPtr := PPointer(NativeUInt(DataPointer) + offset); // $89483D8
PData := mkChar(value); // $8850258
// -- Use the following code to check that mkChar is working.
offset2 := SizeOf(TVECTOR_SEXPREC);
PTest := PSEXPREC(NativeUInt(PData) + offset);
FTestString := FTestString + AnsiString(PAnsiChar(PTest));
//PPointerList(DataPointer)^[ix] := PData;
//PPtr2 := PPointer(NativeUInt(DataPointer) + offset); // Wrong!
PPointerArray(DataPointer)^[ix] := PData;
PPtr2 := PPointerArray(DataPointer)^[ix]; // $8850258 - correct
end;
I'd have thought the address in PData ($8850258) would now be in PPtr2 but I've been staring at this so long I'm sure I'm missing something obvious.
Edit2: The code for SetValue used in R.NET is as follows (ignoring test for null string):
private void SetValue(int index, string value)
{
int offset = GetOffset(index);
IntPtr stringPointer = mkChar(value);
Marshal.WriteIntPtr(DataPointer, offset, stringPointer);
}
From reflector, Marshal.WriteIntPtr uses the following C:
public static unsafe void WriteInt32(IntPtr ptr, int ofs, int val)
{
try
{
byte* numPtr = (byte*) (((void*) ptr) + ofs);
if ((((int) numPtr) & 3) == 0)
{
*((int*) numPtr) = val;
}
else
{
byte* numPtr2 = (byte*) &val;
numPtr[0] = numPtr2[0];
numPtr[1] = numPtr2[1];
numPtr[2] = numPtr2[2];
numPtr[3] = numPtr2[3];
}
}
catch (NullReferenceException)
{
throw new AccessViolationException();
}
}
You say you want to copy the struct pointer itself into the array, but the code you have shown is trying to copy the struct data that the pointer is pointing at. If you really want to copy just the pointer itself, don't use CopyMemory() at all. Just assign the pointer as-is:
const
MaxPointerList = 255; // whatever max array count that Func2() allocates
type
TPointerList = array[0..MaxPointerList-1] of Pointer;
PPointerList = ^TPointerList;
PPointerList(DataPointer)^[index] := PStruct;
Your use of NativeUInt reveals that you are using a version of Delphi that likely supports the {$POINTERMATH} directive, so you can take advantage of that instead, eg:
{$POINTERMATH ON}
PPointer(DataPointer)[index] := PStruct;
Or, use the pre-existing PPointerArray type in the System unit:
{$POINTERMATH ON}
PPointerArray(DataPointer)[index] := PStruct;

Delphi: WSAAddressToString returns error code 10022 (WSAEINVAL)

I am trying to scan nearby Bluetooth device for their MAC address using Winsock2 API interface.
Using code below I can found devices. But when I try to get their address using WSAAddressToString get a 10022 (WSAEINVAL) error say "An invalid argument was supplied".
The code is:
uses
winsock2, bt_helper;
procedure test;
var
ulFlags: u_long;
QuerySet: WSAQUERYSET;
QuerySize: u_long;
HLookup: THandle;
Result: Integer;
pCSAddr: pCSADDR_INFO;
pDeviceInfo: PBTH_DEVICE_INFO;
pResults: lpWSAQUERYSET;
Buffer: array [0..999] of Byte;
ProtocolInfo: WSAPROTOCOL_INFO;
ProtocolInfoSize: Integer;
BufferLength, AddressSize: LongWord;
addressAsString: array [0..1999] of Char;
begin
WSAStartup ($0202, Data);
ulFlags:=
LUP_CONTAINERS or //device inquiry
LUP_RETURN_NAME or //Friendly device name (if available) will be returned in lpszServiceInstanceName
LUP_RETURN_ADDR or //BTH_ADDR will be returned in lpcsaBuffer member of WSAQUERYSET
LUP_FLUSHCACHE ; //Flush the device cache for all inquiries, except for the first inquiry
QuerySize:= SizeOf(WSAQuerySet);
ZeroMemory (#QuerySet, SizeOf(QuerySet));
QuerySet.dwNameSpace:= NS_BTH;
QuerySet.dwSize:= QuerySize;
Result:= WSALookupServiceBegin(#QuerySet, ulFlags, HLookup);
if Result = 0 then
begin
while true do
begin
bufferLength:= sizeof(buffer);
pResults:= lpWSAQUERYSET(#buffer);
Result:= WSALookupServiceNext (HLOOKUP, ulFlags, bufferLength, pResults);
if Result = 0 then
begin
// Get the device info, name, address, etc.
Memo1.Lines.Add(Format('The service instance name is %s', [pResults.lpszServiceInstanceName]));
//pCSAddr.LocalAddr.lpSockaddr.sa_family:= AF_INET;
pCSAddr:= PCSADDR_INFO(pResults.lpcsaBuffer);
pDeviceInfo:= PBTH_DEVICE_INFO(pResults.lpBlob);
// Print the local Bluetooth device address ...
AddressSize:= sizeof(addressAsString);
if WSAAddressToString(pCSAddr.LocalAddr.lpSockaddr^, pCSAddr.LocalAddr.iSockaddrLength,
#ProtocolInfo, #AddressAsString, AddressSize) = 0
then
Memo1.Lines.Add(Format ('The localAddress: %s', [AddressAsString]))
else
Memo1.Lines.Add(Format ('WSAAddressToString for localAddress failed with error code %d: %s',
[WSAGetLastError, SysErrorMessage (WSAGetLastError)]));
// Print the remote Bluetooth device address ...
AddressSize:= sizeof(addressAsString);
IF WSAAddressToString(pCSAddr.RemoteAddr.lpSockaddr^, pCSAddr.RemoteAddr.iSockaddrLength,
#ProtocolInfo, #AddressAsString, Addresssize) = 0
then
Memo1.Lines.Add (Format ('The remote device address: %s', [AddressAsString]))
else
Memo1.Lines.Add (Format ('WSAAddressToString for remoteAddress failed with error code %d: %s',
[WSAGetLastError, SysErrorMessage(WSAGetLastError)]));
end
else
begin
Memo1.Lines.Add(SysErrorMessage(WSAGetLastError));
break;
end;
end;
end;
WSALookupServiceEnd(HLookup);
Here is the result inside memo:
The service instance name is BTDevice1
WSAAddressToString for localAddress failed with error code 10022: An invalid argument was supplied
WSAAddressToString for remoteAddress failed with error code 10022: An invalid argument was supplied
---------------------------------
No more results can be returned by WSALookupServiceNext
Use the following unit in order to compile:
unit bt_helper;
interface
uses
winsock2, Winapi.Windows;
const
BTH_MAX_NAME_SIZE = 248;
BTHPROTO_RFCOMM= 3;
BT_PORT_ANY = -1;
type
BTH_ADDR = int64;
SOCKADDR_BTH = packed record
addressFamily :word; // Always AF_BTH
btAddr :BTH_ADDR; // Bluetooth device address
serviceClassId :TGUID; // [OPTIONAL] system will query SDP for port
port :dword; // RFCOMM channel or L2CAP PSM
end;
BTH_COD = ULONG;
_BTH_DEVICE_INFO = record
flags: ULONG; // Combination BDIF_Xxx flags
address: BTH_ADDR; // Address of remote device.
classOfDevice: BTH_COD; // Class Of Device.
name: array [0..BTH_MAX_NAME_SIZE - 1] of CHAR; // name of the device
end;
{$EXTERNALSYM _BTH_DEVICE_INFO}
BTH_DEVICE_INFO = _BTH_DEVICE_INFO;
{$EXTERNALSYM BTH_DEVICE_INFO}
PBTH_DEVICE_INFO = ^BTH_DEVICE_INFO;
{$EXTERNALSYM PBTH_DEVICE_INFO}
TBthDeviceInfo = BTH_DEVICE_INFO;
PBthDeviceInfo = PBTH_DEVICE_INFO;
implementation
end.
If you read the documentation of WSAAddressToString closely you would have noticed this paragraph:
lpProtocolInfo [in, optional] A pointer to the WSAPROTOCOL_INFO
structure for a particular provider. If this is parameter is NULL, the
call is routed to the provider of the first protocol supporting the
address family indicated in the lpsaAddress parameter.
so instead of supplying a fake WSA_PROTOCOL info structure, you should pass in nil. The second problem is that you use SizeOf() to determine the length of the String buffer, this is incorrect and you should use Length():
AddressSize:= Length(addressAsString);
if WSAAddressToString(pCSAddr.LocalAddr.lpSockaddr^, pCSAddr.LocalAddr.iSockaddrLength,
nil, #AddressAsString, AddressSize) = 0 then
begin
SetLength(AddressAsString, AddressSize-1);// resize to returned length minus last null character
...

Delphi and using Teamspeak SDK read returned multi dim arrays

I'm trying to read returned arrays from the TeamSpeak3 SDK, some of the methods returns arrays that are null terminated and multi dimensional with a mix of data types.
What "delhpi" structure should I pass as parameter and how can I read the returned values back in the a matching structure? a la.
type
TDeviceInfo = record
DeviceId : string; // maybe an integer
DeviceName : string;
end;
TDeviceInfoArr = array of TDeviceInfo
// or maybe
TDeviceInfoArr = array of array[0..1] of string;
var
DeviceArr : array of TDeviceInfoArr;
This is what the SDK Documentation says.
To get a list of all available playback and capture devices for the specified mode, call
unsigned int ts3client_getPlaybackDeviceList(modeID, result);
const char* modeID;
char**** result;
unsigned int ts3client_getCaptureDeviceList(modeID, result);
const char* modeID;
char**** result;
Parameters
• modeID
Defines the playback/capture mode to use. For different modes there might be different device lists. Valid modes are returned by
ts3client_getDefaultPlayBackMode / s3client_getDefaultCaptureMode and ts3client_getPlaybackModeList / ts3client_getCaptureModeList.
• result
Address of a variable that receives a NULL-terminated array { { char* deviceName, char* deviceID }, { char* deviceName, char* deviceID }, ... , NULL }.
Unless the function returns an error, the elements of the array and the array itself need to be freed using ts3client_freeMemory.
Returns ERROR_ok on success, otherwise an error code as defined in public_errors.h. In case of an error, the result array is uninitialized and must not be released.
Example to query all available playback devices:
char * defaultMode;
if (ts3client_getDefaultPlayBackMode( & defaultMode) == ERROR_ok) {
char * * * array;
if (ts3client_getPlaybackDeviceList(defaultMode, & array) == ERROR_ok) {
for (int i = 0; array[i] != NULL; ++i) {
printf("Playback device name: %s\n", array[i][0]); /* First element: Device name */
printf("Playback device ID: %s\n", array[i][1]); /* Second element: Device ID */
/* Free element */
ts3client_freeMemory(array[i][0]);
ts3client_freeMemory(array[i][1]);
ts3client_freeMemory(array[i]);
}
ts3client_freeMemory(array); /* Free complete array */
} else {
printf("Error getting playback device list\n");
}
} else {
printf("Error getting default playback mode\n");
}
First of all, I'm going to ignore error handling because I think we handled that in your last question. And I'm going to assume that ts3client_getDefaultPlayBackMode presents no problems.
So that leaves ts3client_getPlaybackDeviceList. Import it like this:
function ts3client_getPlaybackDeviceList(modeID: PAnsiChar;
out result: PPPAnsiChar): Cardinal; cdecl; external '...';
You will likely need to define PPPAnsiChar.
type
PPPAnsiChar = ^PPAnsiChar;
PPAnsiChar = ^PAnsiChar;
You might find that the RTL already defines PPAnsiChar.
So, next to calling the function. First of all declare a variable to hold the array, and so others to help iterate:
var
arr, myarr: PPPAnsiChar;
p: PPAnsiChar;
Then call the function:
ts3client_getPlaybackDeviceList(modeID, arr);
myarr := arr;
while myarr^ <> nil do
begin
p := myarr^;
Writeln('Playback device name: ', p^);
ts3client_freeMemory(p^);
inc(p);
Writeln('Playback device ID: ', p^);
ts3client_freeMemory(p^);
ts3client_freeMemory(myarr^);
inc(myarr);
end;
ts3client_freeMemory(arr);
This code is really quite vile I'm sure that you will agree. If you have a modern version of Delphi then you can enable pointer math to make it read better.
{$POINTERMATH ON}
ts3client_getPlaybackDeviceList(modeID, arr);
i := 0;
while arr[i] <> nil do
begin
Writeln('Playback device name: ', arr[i][0]);
Writeln('Playback device ID: ', arr[i][1]);
ts3client_freeMemory(arr[i][0]);
ts3client_freeMemory(arr[i][1]);
ts3client_freeMemory(arr[i]);
inc(i);
end;
ts3client_freeMemory(arr);
Although this code is better, it will never win a beauty contest.
Remember that I've neglected all error checking. You'll need to add that.
Based on David's suggesstions I found the following code working, thanks David!
{$POINTERMATH ON}
procedure TfrmMain.RequestPlaybackDevices;
var
arr, myarr: PPPAnsiChar;
p: PPAnsiChar;
defaultmode : PAnsiChar;
i : Integer;
begin
try
ts3check(ts3client_getDefaultPlayBackMode(#defaultmode));
ts3check(ts3client_getPlaybackDeviceList(defaultMode, #arr));
try
i := 0;
while arr[i] <> nil do
begin
LogMsg(format('Playback device name: %s',[UTF8ToUnicodeString(arr[i][0])]));
LogMsg(format('Playback device ID: %s',[UTF8ToUnicodeString(arr[i][1])]));
ts3client_freeMemory(arr[i][0]);
ts3client_freeMemory(arr[i][1]);
ts3client_freeMemory(arr[i]);
inc(i);
end;
finally
ts3client_freeMemory(arr);
end;
except
on e: exception do LogMsg(Format('Error RequestPlaybackDevices: %s', [e.Message]));
end;
end;
{$POINTERMATH OFF}

WinAPI: GetFontUnicodeRanges - I do not understand the result

I am trying to get Unicode font glyph ranges (Delphi 6):
var GS:PGlyphSet;
GSSize:LongWord;
rng:TWCRange;
begin
GSSize := GetFontUnicodeRanges(Canvas.Handle, nil);
GetMem(Pointer(GS), GSSize);
try
GS.cbThis:=GSSize;
GS.flAccel:=0;
GS.cGlyphsSupported:=0;
GS.cRanges:=0;
if GetFontUnicodeRanges(Canvas.Handle, GS)<>0 then begin
for i:=0 to GS.cRanges-1 do begin
rng := GS.ranges[i];
The strange thing is that Length(GS.ranges) is 1, but GS.cRanges is 309 and when I try to access the second range GS.ranges[1] I get, of course, a range check error. Before I turned range checking on it has worked in some magical way.
Types for reference (from Windows module):
PWCRange = ^TWCRange;
{$EXTERNALSYM tagWCRANGE}
tagWCRANGE = packed record
wcLow: WCHAR;
cGlyphs: SHORT;
end;
TWCRange = tagWCRANGE;
PGlyphSet = ^TGlyphSet;
{$EXTERNALSYM tagGLYPHSET}
tagGLYPHSET = packed record
cbThis: DWORD;
flAccel: DWORD;
cGlyphsSupported: DWORD;
cRanges: DWORD;
ranges: array[0..0] of TWCRange;
end;
TGlyphSet = tagGLYPHSET;
This struct makes use of the so-called struct hack:
http://c-faq.com/struct/structhack.html
http://tonywearme.wordpress.com/2011/07/26/c-struct-hack/
The ranges member is a variable length array, placed inline in the struct. But you cannot actually encode that in a static C type. That's why you call the function to find out how much memory to allocate, and then heap allocate the struct. If you allocated it on the stack, or using SizeOf(...) then the struct would be too small.
The simplest thing to do is to disable range checking for the code that accesses ranges. Although the type declaration says that only 0 is a valid index for ranges, in fact 0..cRanges-1 are valid.
If you don't want to disable range checking for the relevant code, then take a pointer the element 0, and then use pointer arithmetic in your loop.
var
rng: PWCRange;
....
rng := #GS.ranges[0];
for i:=0 to GS.cRanges-1 do begin
// use rng^
inc(rng);
end;
This is, in my view, the cleanest way to write code for sequential access. For random access, and with range checking in force, you'd be compelled to declare some extra types to defeat range checking:
type
TWCRangeArray = array [0..(MaxInt div SizeOf(TWCRange))-1] of TWCRange;
PWCRangeArray = ^TWCRangeArray;
And then use type casting to access individual elements:
rng := PWCRangeArray(#GS.ranges)[i];

Resources