I'm trying to call NtOpenFile, and it's failing with error:
STATUS_OBJECT_PATH_SYNTAX_BAD = NTSTATUS($C000003B);
Object Path Component was not a directory object.
The basic gist is:
//The file we'll test with
filename: UnicodeString := 'C:\Windows\Explorer.exe'; //23 characters
//Convert the filename to counted UNICODE_STRING
cs: UNICODE_STRING;
cs.Length := Length(filename) * sizeof(WideChar); //46 bytes
cs.MaximumLength := cs.Length + 2; //48 bytes
cs.Buffer := PWideChar(Filename); //"C:\Windows\Explorer.exe"
//Define the OBJECT_ATTRIBUTES
oa: OBJECT_ATTRIBUTES := Default(OBJECT_ATTRIBUTES);
oa.Length := sizeof(OBJECT_ATTRIBUTES); //24 bytes
oa.Attributes := OBJ_CASE_INSENSITIVE;
oa.ObjectName := #cs; //UNICODE_STRING
//Open the file (by Object Attributes) and get a file handle
hFile: THandle;
iosb: IO_STATUS_BLOCK;
status: NTSTATUS := NtOpenFile(#hFile, FILE_READ_ATTRIBUTES, #oa, #iosb, FILE_SHARE_READ, 0);
What am I doing wrong?
Basic gist (C#-style psuedocode)
//The file we'll test with
UnicodeString filename = "C:\Windows\Explorer.exe"; //23 characters
//Convert the filename to counted UNICODE_STRING
UNICODE_STRING cs;
cs.Length = Length(filename) * sizeof(WideChar); //46 bytes
cs.MaximumLength = cs.Length + 2; //48 bytes
cs.Buffer = Filename; //"C:\Windows\Explorer.exe"
//Define the OBJECT_ATTRIBUTES
OBJECT_ATTRIBUTES oa = Default(OBJECT_ATTRIBUTES);
oa.Length = sizeof(OBJECT_ATTRIBUTES); //24 bytes
oa.Attributes = OBJ_CASE_INSENSITIVE;
oa.ObjectName = cs; //UNICODE_STRING
//Open the file (by Object Attributes) and get a file handle
THandle hFile;
IO_STATUS_BLOCK iosb;
NTSTATUS status = NtOpenFile(out hFile, FILE_READ_ATTRIBUTES, ref oa, out iosb, FILE_SHARE_READ, 0);
Other styles of filenames
Filename
Result
Description
"C:\Windows\Explorer.exe"
STATUS_OBJECT_PATH_SYNTAX_BAD
Object Path Component was not a directory object.
"\global??\C:\Windows\Explorer.exe"
0xC0000033
Object Name invalid
"\??\C:\Windows\Explorer.exe"
0xC0000033
Object Name invalid
OBJECT_ATTRIBUTES raw memory dump
Because there can be issues of memory layout, alignment, padding, and missing members, lets dump the raw oa memory:
18 00 00 00 ;Length. 0x00000018 = 24 bytes (sizeof)
00 00 00 00 ;RootDirectory. 0x00000000 = NULL
28 FF 19 00 ;ObjectName. PUNICODE_STRING 0x0019FF28
40 00 00 00 ;Attributes. (0x00000040 = OBJ_CASE_INSENSITIVE)
00 00 00 00 ;SecurityDescriptor 0x0000000 = NULL
00 00 00 00 ;SecurityQualityOfService 0x00000000 = NULL
**0x0019FF28:**
3C FF 19 00 ;PUNICODE_STRING 0x0019FF3C
**0x0019FF3C**
36 00 ; String length in bytes 0x0036
38 00 ; Buffer size in bytes 0x0038
E8 B6 4E 00 ; PWideChar 0x004EB6E8 ==> "C:\Windows\Explorer.exe"
Which, now I see my problem: PUNICODE_STRING -> PUNICODE_STRING.
CMRE
program NtOpenFileDemo;
{$APPTYPE CONSOLE}
{$R *.res}
{$ALIGN 8}
{$MINENUMSIZE 4}
uses
SysUtils, Windows, ComObj;
type
NTSTATUS = Cardinal;
const
STATUS_SUCCESS = 0;
type
UNICODE_STRING = packed record
Length: Word; // Length of the string, in bytes (not including the null terminator)
MaximumLength: Word; // Size of the buffer, in bytes
Buffer: PWideChar; //really a PWideChar
end;
PUNICODE_STRING = ^UNICODE_STRING;
type
IO_STATUS_BLOCK = packed record
Status: NTSTATUS;
Information: Pointer;
end;
PIO_STATUS_BLOCK = ^IO_STATUS_BLOCK;
OBJECT_ATTRIBUTES = packed record
Length: Cardinal;
RootDirectory: THandle;
ObjectName: PUNICODE_STRING;
Attributes: Cardinal;
SecurityDescriptor: Pointer;
SecurityQualityOfService: Pointer;
end;
POBJECT_ATTRIBUTES = ^OBJECT_ATTRIBUTES;
const
// Define share access rights to files and directories
FILE_SHARE_READ = $00000001;
FILE_SHARE_WRITE = $00000002;
FILE_SHARE_DELETE = $00000004;
FILE_SHARE_VALID_FLAGS = $00000007;
// Valid values for the Attributes field
const
OBJ_INHERIT = $00000002;
OBJ_PERMANENT = $00000010;
OBJ_EXCLUSIVE = $00000020;
OBJ_CASE_INSENSITIVE = $00000040;
OBJ_OPENIF = $00000080;
OBJ_OPENLINK = $00000100;
OBJ_KERNEL_HANDLE = $00000200;
OBJ_VALID_ATTRIBUTES = $000003F2;
function NtOpenFile(FileHandle: PHandle; DesiredAccess: ACCESS_MASK; ObjectAttributes: POBJECT_ATTRIBUTES;
IoStatusBlock: PIO_STATUS_BLOCK; ShareAccess: DWORD; OpenOptions: DWORD): NTSTATUS; stdcall; external 'ntdll.dll';
function NtClose(Handle: THandle): NTSTATUS; stdcall; external 'ntdll.dll';
function FormatNTStatusMessage(const NTStatusMessage: NTSTATUS): string;
var
Buffer: PChar;
Len: Integer;
hMod: HMODULE;
function MAKELANGID(p, s: WORD): WORD;
begin
Result := WORD(s shl 10) or p;
end;
begin
{
KB259693: How to translate NTSTATUS error codes to message strings
Let the OS initialize the Buffer variable. Need to LocalFree it afterward.
}
hMod := SafeLoadLibrary('ntdll.dll');
Len := FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER or
FORMAT_MESSAGE_FROM_SYSTEM or
// FORMAT_MESSAGE_IGNORE_INSERTS or
// FORMAT_MESSAGE_ARGUMENT_ARRAY or
FORMAT_MESSAGE_FROM_HMODULE,
Pointer(hMod),
NTStatusMessage,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
#Buffer, 0, nil);
try
//Remove the undesired line breaks and '.' char
while (Len > 0) and (CharInSet(Buffer[Len - 1], [#0..#32, '.'])) do Dec(Len);
//Convert to Delphi string
SetString(Result, Buffer, Len);
finally
//Free the OS allocated memory block
LocalFree(HLOCAL(Buffer));
end;
FreeLibrary(hMod);
end;
procedure TestCase;
var
filename: UnicodeString;
cs: PUNICODE_STRING;
hFile: THandle;
oa: OBJECT_ATTRIBUTES;
iosb: IO_STATUS_BLOCK;
status: NTSTATUS;
begin
filename := 'C:\Windows\Explorer.exe'; //23 characters
{
Convert the filename to an "Object Attributes" structure
OBJECT_ATTRIBUTES.Length <-- 24
OBJECT_ATTRIBUTES.Attributes <-- OBJ_CASE_INSENSITIVE
OBJECT_ATTRIBUTES.ObjectName.Length <-- 46
OBJECT_ATTRIBUTES.ObjectName.MaximumLength <-- 48
OBJECT_ATTRIBUTES.ObjectName.Buffer <-- "C:\Windows\Explorer.exe"
}
cs.Length := Length(Filename) * sizeof(WideChar);
cs.MaximumLength := cs.Length + 2; //the null terminator
cs.Buffer := PWideChar(Filename);
oa := Default(OBJECT_ATTRIBUTES);
oa.Length := sizeof(OBJECT_ATTRIBUTES);
oa.Attributes := OBJ_CASE_INSENSITIVE;
oa.ObjectName := #cs;
//Open the file (by Object Attributes) and get a file handle
status := NtOpenFile(#hFile, FILE_READ_ATTRIBUTES, #oa, #iosb, FILE_SHARE_READ, 0);
if status <> STATUS_SUCCESS then
begin
WriteLn('Error opening file "'+Filename+'": '+FormatNTStatusMessage(status)+' (0x'+IntToHex(status, 8)+')');
Exit;
end;
try
WriteLn('Successfully opened file');
finally
NtClose(hFile);
end;
end;
begin
try
TestCase;
WriteLn('Press enter to close...');
ReadLn;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Bonus Reading
MSDN: RtlInitUnicodeString
MSDN: NtOpenFile function
Technet: How to open a file from a kernel mode device driver and how to read from or write to the file archive
MSDN: InitializeObjectAttributes macro
MSDN: OBJECT_ATTRIBUTES structure
Google Project Zero: The Definitive Guide on Win32 to NT Path Conversionarchive
Found it.
Two things:
I didn't know "NT Paths" are different from "DOS Paths"
"C:\Windows\Notepad.exe" → "\??\C:\Windows\Notepad.exe"
To OBJECT_ATTRIBUTES.ObjectName I was assigning #PUNICODE_STRING, rather than #UNICODE_STRING
Use Case - Processes using file
I was using NtOpenFile in order to get the Process IDs that are using a file.
The short version is:
call NtOpenFile to open the file you're interested in
call NtQueryInformationFile with the FileProcessIdsUsingFileInformation constant
iterate each returned Process ID (pid)
call OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid) to get the process name
function GetProcessIDsUsingFile(Filename: UnicodeString; out ProcessIDs: array of DWORD): string;
var
hFile: THandle;
oa: OBJECT_ATTRIBUTES;
iosb: TIOStatusBlock;
status: NTSTATUS;
fi: FILE_PROCESS_IDS_USING_FILE_INFORMATION;
i: Integer;
pid: DWORD;
hProcess: THandle;
s: string;
dw: DWORD;
cs: UNICODE_STRING;
ntFilename: UnicodeString;
const
PROCESS_QUERY_LIMITED_INFORMATION = $1000;
begin
Result := '';
(*
Decription
Get list of processes that are using a file.
Sample usage
GetProcessIDsUsingFile('C:\Windows\Notepad.exe', {out}pids);
Returns
Result: a text description of the processes using the file
"PID:12345 (Explorer.exe)"
ProcessIDs: an array of process IDs (PIDs)
[12345]
*)
ntFilename := Filename;
{
Convert the "DOS path" into an "NT Path". NT Paths are not the same as DOS paths.
DOS Path: C:\Windows\Explorer.exe
NT Path: \??\C:\Windows\Explorer.exe
The short version is:
\DosDevices is a symbolic link for \??
\?? is a special value understood by the object manager's parser to mean the global \DosDevices directory
\GLOBAL?? is a folder that is home to global device.
And in the end all 3 (eventually) end up in the same place.
But you want to use "??" rather than "\GLOBAL??" because the former understands per-user (i.e. local) drive mappings.
Bonus Reading:
- Google Project Zero: The Definitive Guide on Win32 to NT Path Conversion (Google Project Zero)
https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
- Google Project Zero: Windows Drivers are True’ly Tricky
https://googleprojectzero.blogspot.com/2015/10/windows-drivers-are-truely-tricky.html
- Nynaeve: The kernel object namespace and Win32, part 3
http://www.nynaeve.net/?p=92
Apparently you can also use some undocumented functions (exported by name) to convert a DOS path to an NT path; there are like 7 variations
- RtlGetFullPathName_U
- RtlDosPathNameToRelativeNtPathName_U
- RtlDosPathNameToNtPathName_U_WithStatus
But i'll just prefix it with "\??\"
}
if Copy(ntFilename, 1, 4) <> '\??\' then
ntFilename := '\??\'+Filename;
{
Convert the filename to an "Object Attributes" structure
OBJECT_ATTRIBUTES.Length <-- 24
OBJECT_ATTRIBUTES.Attributes <-- OBJ_CASE_INSENSITIVE
OBJECT_ATTRIBUTES.ObjectName.Length <-- 46
OBJECT_ATTRIBUTES.ObjectName.MaximumLength <-- 48
OBJECT_ATTRIBUTES.ObjectName.Buffer <-- "C:\Windows\Explorer.exe"
}
cs.Length := Length(ntFilename) * sizeof(WideChar);
cs.MaximumLength := cs.Length + 2; //the null terminator
cs.Buffer := PWideChar(ntFilename);
oa := Default(OBJECT_ATTRIBUTES);
oa.Length := sizeof(OBJECT_ATTRIBUTES);
oa.Attributes := OBJ_CASE_INSENSITIVE;
oa.ObjectName := #cs;
//Open the file (by Object Attributes) and get a file handle
status := NtOpenFile(#hFile, FILE_READ_ATTRIBUTES, #oa, #iosb, FILE_SHARE_READ, 0);
if status <> STATUS_SUCCESS then
raise EOleSysError.Create('Error opening file "'+Filename+'": '+FormatNTStatusMessage(status)+' (0x'+IntToHex(status, 8)+')', status, 0);
try
//Query for information about the file (by handle)
status := NtQueryInformationFile(hFile, #iosb, #fi, sizeof(fi), FileProcessIdsUsingFileInformation);
if status <> STATUS_SUCCESS then
raise EOleSysError.Create('Error querying file "'+Filename+'" information: '+FormatNTStatusMessage(status), status, 0);
for i := 0 to fi.NumberOfProcessIdsInList-1 do
begin
pid := fi.ProcessIdList[i];
if Result <> '' then
Result := Result+#13#10;
Result := Result+'PID: '+IntToStr(pid);
hProcess := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid);
if hProcess = 0 then
RaiseLastOSError;
try
SetLength(s, 32767);
dw := GetModuleFileNameEx(hProcess, 0, PChar(s), Length(s));
if dw <= 0 then
RaiseLastOSError;
SetLength(s, dw); //returns characters (not including null terminator)
Result := Result+' ('+s+')';
finally
CloseHandle(hProcess);
end;
end;
finally
NtClose(hFile);
end;
end;
Related
I got the task to encrypt a firmware file that is decrypted on a small device using AES.
The firmware library comes with an AES framework, so I need to get that one in sync with my Delphi code....
To get the task done I used the DEC cipher suite which we also use for different purposes in the software... now the thing is that I cannot sync the output of the firmware tool set with DEC...
Here is the code I use:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
DECBaseClass,
DECCipherBase,
DECHash,
DECCiphers,
DECTypes,
DECFormatBase,
DECFormat;
type
Binary = RawByteString;
var
ACipherClass: TDECCipherClass = TCipher_AES;
ACipherMode: TCipherMode = cmCBCx;
ATextFormat: TDECFormatClass = TFormat_Mime64;
InvertUINT32 : boolean = False;
InvertText : boolean = False;
type
//TMK6AESTextSize = Array[0..15] of UInt32;
TMK6AESTextSize = Array[0..3] of UInt32;
TMK6Key = Array[0..3] of UINT32;
const mk6Key : TMK6Key = ($1, $0, $0, $0);
//mk6Key : TMK6Key = ($09CF4F3C, $ABF71588, $28AED2A6, $2B7E1516 ); // rotated
//mk6IV : TMK6Key = ($00010203 , $04050607 , $08090A0B , $0C0D0E0F);
mk6IV : TMK6Key = ($0 , $0 , $0 , $0);
//mk6IV : TMK6Key = ($0C0D0E0F, $08090A0B, $04050607, $00010203);
// mk6Plaintext : TMK6AESTextSize =
// ( $E2BEC16B ,$969F402E ,$117E3DE9 ,$2A179373 ,
// $578A2DAE ,$9CAC031E ,$AC6FB79E ,$518EAF45 ,
// $461CC830 ,$11E45CA3 ,$19C1FBE5 ,$EF520A1A ,
// $45249FF6 ,$179B4FDF ,$7B412BAD ,$10376CE6 );
// mk6Plaintext : TMK6AESTextSize =
// ( $E2BEC16B ,$969F402E ,$117E3DE9 ,$2A179373 );
//mk6Plaintext : TMK6AESTextSize =
// ( $2A179373, $117E3DE9, $969F402E, $E2BEC16B );
mk6Plaintext : TMK6AESTextSize = ( $0, $0, $0, $0 );
type
TUINT32Byte = Array[0..3] of Byte;
PUINT32Byte = ^TUINT32Byte;
function InvUINT32( value : UINT32 ) : UINT32;
var v1, v2 : PUINT32Byte;
begin
v1 := #value;
v2 := #Result;
v2^[3] := v1^[0];
v2^[2] := v1^[1];
v2^[1] := v1^[2];
v2^[0] := v1^[3];
end;
function GenMK6Text( const txt : TMK6AESTextSize ) : TMK6AESTextSize;
var i : integer;
begin
if InvertText then
begin
for i := 0 to High(txt) do
Result[i] := InvUINT32(txt[i]);
end
else
Result := txt;
end;
function GenMK6Key( const aKey : TMK6Key ) : TMK6Key;
var i : integer;
begin
Result := aKey;
if InvertUINT32 then
begin
for i := 0 to High(aKey) do
Result[i] := InvUINT32(aKey[i]);
end;
end;
function EncryptBin(const buf : TMK6AESTextSize): Binary;
var AData: Binary;
cipher : TCipher_AES128; //AES
aKey : TMK6Key;
aIV : TMK6Key;
aBuf : TMK6AESTextSize;
begin
cipher := TCipher_AES128.Create;
try
cipher.Mode := ACipherMode;
aKey := GenMK6Key(mk6Key);
aIV := GenMK6Key(mk6IV);
aBuf := GenMK6Text(buf);
cipher.Init( aKey, sizeof(mk6key), aIV, sizeof(mk6IV) );
SetLength(AData, sizeof(buf));
cipher.Encode(aBuf, AData[1], Length(AData));
Result := AData;
finally
cipher.Free;
end;
end;
function DecryptBin(buf : Binary): TMK6AESTextSize;
var AData: Binary;
//ACheck: Binary;
//APass: Binary;
ALen: Integer;
ARes : Binary;
cipher : TCipher_AES128;
aKey : TMK6Key;
aIV : TMK6Key;
begin
cipher := TCipher_AES128.Create;
try
//AData := ValidFormat(ATextFormat).Decode(AText);
AData := buf;
ALen := Length(AData); // - cipher.Context.BufferSize;
//ALen := Length(AData); // - cipher.Context.BufferSize;
//ACheck := System.Copy(Adata, ALen + 1, cipher.Context.BufferSize);
cipher.Mode := ACipherMode;
aKey := GenMK6Key(mk6Key);
aIV := GenMK6Key(mk6IV);
cipher.Init(aKey, sizeof(mk6key), aIV, sizeof(mk6IV));
SetLength(ARes, ALen);
cipher.Decode(AData[1], ARes[1], ALen);
//if ACheck <> cipher.CalcMAC then
// raise Exception.Create('Invalid data');
Assert(Length(ARes) >= sizeof(Result), 'WTF');
Move(ARes[1], Result, sizeof(Result));
Result := GenMK6Text(Result);
finally
cipher.Free;
end;
end;
var binBuf : Binary;
decBuf : TMK6AESTextSize;
i : integer;
begin
try
binBuf := EncryptBin(mk6Plaintext);
for i := 1 to Length(binBuf) do
begin
Write( IntToHex( Ord(binBuf[i]), 2 ) + ' ' );
end;
Writeln;
decBuf := DecryptBin(binBuf);
for i := 0 to Length(decBuf) - 1 do
Writeln( IntToHex( decBuf[i], 4 ) );
readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
The output of the firmware tool is D5 AA 45 05 7C A9 A2 6D 43 D1 63 36 84 1C 3D 2A
(which actually also is the output if I use this input on http://aes.online-domain-tools.com/ ) so I guess I miss something here....
Please note that if all input data (Key, IV data) is set to zero, I get the output
66 E9 4B D4 EF 8A 2C 3B 88 4C FA 59 CA 34 2B 2E which is close to the one I get from the other tool:
D4 4B E9 66 3B 2C 8A EF 59 FA 4C 88 2E 2B 34 CA ... so basically only the UINT32 byte order is different.
The thing is that I cannot get any close if just one bit is different from 0 in the key vector (hence the one value of 1.) ... I tried various things... change the position of the "1", changed the lowest to the highest bit - nothing worked...
What am I missing here?
You are using older DEC library that was written to be compatible with Delphi 2009.
Instead of this version you should be using newer DEC library that is fully compatible with with newer Delphi versions including Delphi 11 that you are using.
Switching to newer version of DEC library should solve all of your problems.
I have a resource file named ELEMENTS.RC that starts like this (it contains instructions in several languages).
STRINGTABLE {
1, "רגיל"
32001,"Instructions"
32002,"La situation"
32003,"Vos actions"
32004,"Explication de vos actions"
33001,"Инструкция"
}
The above RC file is compiled to RES format with GORC and is linked into my executable.
I have created a simple form containing one Tlabel. Here is the code that I am trying to use
procedure TForm1.FormShow(Sender: TObject);
var
len: word;
buffer: pwidechar;
begin
len:= loadstringw (hinstance, 32001, buffer, length (buffer));
if len > 0
then label1.caption:= buffer
else label1.caption:= inttostr (GetLastError);
end;
// Edit that changes the question!
The following code does read id 32001 from the resource file. But I want it to read id 1 from the file (there is supposed to be a value) and this is failing. Is there a tool that can read compiled resource files (res)? Hopefully the use of such a tool will find the problem with some of the strings.
Code that works:
function LoadResW (id: integer): WideString;
const
maxlength = 1024;
var
len, i: integer;
begin;
setlength (result, maxlength);
len:= loadstringw (hinstance, id, pwidechar (result), length (result));
if len > 0 then
begin
setlength (result, len - 1);
if pos ('~', result) > 0 then
for i:= 1 to len - 1 do
if result[i] = '~'
then result[i]:= '"';
end
else result:= '';
end;
Modern Warefare 2 (MWF2): is a Video game.
RCON TOOL: is a tool used to send commands to Game servers using UDP.
i am trying to send a command using indy to a server, its easy to send some string using idUDPclient but my Problem is that
i supposed to send it in this format:
ÿÿÿÿrcon "1234" kick cheater101
where :-
password quoted: "1234"
and command is : kick cheater101
as byte like this
FFFFFFFF72636F6E2020223132333422206B69636B2063686561746572313031
notice that anything sent must start with FFFFFFFF
and that how it should look in Wireshark..
the problem is i couldn't send it like the above .. i just sent like a string ..
i need to make it with indyUDP because I'm planning to test it on android.
here is my code for i am trying:
function rcon(const IP: String; Port: TIdPort; const Pass, Command: String): String;
var
Query: TIdBytes;
Buffer, Data:
TIdBytes;
Len: Integer;
begin
SetLength(Query, 4);
Query[0] := $FF;
Query[1] := $FF;
Query[2] := $FF;
Query[3] := $FF;
AppendString(Query, 'rcon "' + Pass + '" ' + Command);
SetLength(Data, 0);
with TIdUDPClient.Create do try ReceiveTimeout := 2000;
SendBuffer(IP, Port, Query);
repeat SetLength(Buffer, 10000);
Len := ReceiveBuffer(Buffer);
if Len < 1 then Break;
SetLength(Buffer, Len);
AppendBytes(Data, Buffer);
until False; finally Free;
end; // preprocess Data as needed... Result := BytesToString(Data);
end
usage!
rcon('10.0.0.4', 28961, '1234', 'kick cheater101');
Wireshark:
You are encountering a DISPLAY issue, not a DATA issue. In your Wireshark screenshot, you are looking at the raw bytes as-is, not at the hex formatted representation of those same bytes. Wireshark can show you both.
The actual bytes you are sending are fine - except that you are missing a 2nd space character between rcon and the quoted password.
function rcon(const IP: String; Port: TIdPort; const Pass, Command: String): String;
var
Query: TIdBytes;
Buffer, Data: TIdBytes;
Len: Integer;
begin
SetLength(Query, 4);
FillBytes(Query, 4, $FF);
AppendString(Query, 'rcon "' + Pass + '" ' + Command);
SetLength(Data, 0);
with TIdUDPClient.Create do
try
SendBuffer(IP, Port, Query);
ReceiveTimeout := 2000;
SetLength(Buffer, 10000);
repeat
Len := ReceiveBuffer(Buffer);
if Len < 1 then Break;
AppendBytes(Data, Buffer, 0, Len);
until False;
finally
Free;
end;
// preprocess Data as needed...
Result := BytesToString(Data);
end;
Also, in your Wireshark screenshot, it clearly shows 70 bytes being sent for each command, where the command itself is 32 bytes and the rest is 38 bytes of padding. If the server is expecting that padding, you need to add it to your outgoing Query:
function rcon(const IP: String; Port: TIdPort; const Pass, Command: String): String;
var
Query: TIdBytes;
Buffer, Data: TIdBytes;
Len: Integer;
begin
SetLength(Query, 4);
FillBytes(Query, 4, $FF);
AppendString(Query, 'rcon "' + Pass + '" ' + Command);
if (Length(Query) < 70) then
ExpandBytes(Query, Length(Query), 70-Length(Query));
SetLength(Data, 0);
with TIdUDPClient.Create do
try
SendBuffer(IP, Port, Query);
ReceiveTimeout := 2000;
SetLength(Buffer, 10000);
repeat
Len := ReceiveBuffer(Buffer);
if Len < 1 then Break;
AppendBytes(Data, Buffer, 0, Len);
until False;
finally
Free;
end;
// preprocess Data as needed...
Result := BytesToString(Data);
end;
this works, well it will return the proper byte array (as an AnsiString, anyways)!. I used format to combine the inputs, and prepended the FFFFFFFFs
Function MakeKickCommand(pass, user: LPCSTR): AnsiString; //is wide strings okay
var
Temp : AnsiString;
len, I: integer;
a: integer;
Begin
Temp:= AnsiString( format('rcon "%s" kick %s', [pass, user]) );
len := length(Temp);
//set length of the result
SetLength(Result, 4+len);
//add FF FF FF FF
FillChar (Result[1], 4, $FF);
//copy the formatted string
Move(Temp[1], Result[5], len);
End;
i need to get paper status information from a printer. I have a list of esc/pos commands.
I'm trying to send these comands with escape function
http://msdn.microsoft.com/en-us/library/windows/desktop/dd162701%28v=vs.85%29.aspx
This is my code
type
TPrnBuffRec = record
bufflength: Word;
Buff_1: array[0..255] of Char;
end;
procedure TFTestStampa.SpeedButton2Click(Sender: TObject);
var
Buff: TPrnBuffRec;
BuffOut: TPrnBuffRec;
TestInt: Integer;
cmd : string;
begin
printer.BeginDoc;
try
TestInt := PassThrough;
if Escape(Printer.Handle, QUERYESCSUPPORT, SizeOf(TESTINT),
#testint, nil) > 0 then
begin
cmd := chr(10) + chr(04) + '4';
StrPCopy(Buff.Buff_1, cmd);
Buff.bufflength := StrLen(Buff.Buff_1);
Escape(Printer.Canvas.Handle, Passthrough, 0, #buff,
#buffOut);
ShowMessage( conver(strPas(buffOut.Buff_1)) );
end
finally
printer.EndDoc;
end;
function TFTestStampa.Conver(s: string): String;
var
i: Byte;
t : String;
begin
t := '';
for i := 1 to Length(s) do
t := t + IntToHex(Ord(s[i]), 2) + ' ';
Result := t;
end;
Problem is with different cmds I obtain always the same string ....
Can you give me an example of escape function with last parameter not nill ?
Alternatives to obtain paper status ?
I suppose you are using Delphi 2009 above and you used this source for your example, so your problem might be caused by Unicode parameters. In Delphi since version 2009, string type is defined as UnicodeString whilst in Delphi 2009 below as AnsiString, the same stands also for Char which is WideChar in Delphi 2009 up and AnsiChar below.
If so, then I think you have a problem at least with your buffer data length, because Char = WideChar takes 2 bytes and you were using StrLen function which returns the number of chars what cannot correspond to the data size of number of chars * 2 bytes.
I hope this will fix your problem, but I can't verify it, because I don't have your printer :)
type
TPrinterData = record
DataLength: Word;
Data: array [0..255] of AnsiChar; // let's use 1 byte long AnsiChar
end;
function Convert(const S: AnsiString): string;
var
I: Integer; // 32-bit integer is more efficient than 8-bit byte type
T: string; // here we keep the native string data type
begin
T := '';
for I := 1 to Length(S) do
T := T + IntToHex(Ord(S[I]), 2) + ' ';
Result := T;
end;
procedure TFTestStampa.SpeedButton2Click(Sender: TObject);
var
TestInt: Integer;
Command: AnsiString;
BufferIn: TPrinterData;
BufferOut: TPrinterData;
begin
Printer.BeginDoc;
try
TestInt := PASSTHROUGH;
if Escape(Printer.Handle, QUERYESCSUPPORT, SizeOf(TestInt), #TestInt, nil) > 0 then
begin
Command := Chr(10) + Chr(04) + '4';
StrPCopy(BufferIn.Data, Command);
BufferIn.DataLength := StrLen(Command);
FillChar(BufferOut.Data, Length(BufferOut.Data), #0);
BufferOut.DataLength := 0;
Escape(Printer.Canvas.Handle, PASSTHROUGH, 0, #BufferIn, #BufferOut);
ShowMessage(Convert(StrPas(BufferOut.Data)));
end
finally
Printer.EndDoc;
end;
end;
Hi
I want to retrieve HDD unique (hardware) serial number.
I use some functions but in Windows Seven or Vista they don't work correctly because of admin right.
Is it possible retrieve it without run as Administrator?
Following the links in the question comments Sertac posted, I came across this interesting C++ question, where Fredou answered with a nice link to a codeproject example showing how to do this in .NET, which in turn was based on a link to Borland C++ code and article.
The cool thing is that this C++ code works as a non-administrator user too!
Now you need someone to help you translate this C++ code to Delphi.
Edit: Found a Delphi unit that does this for you.
I wrote some sample use for it:
program DiskDriveSerialConsoleProject;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils,
hddinfo in 'hddinfo.pas';
const
// Max number of drives assuming primary/secondary, master/slave topology
MAX_IDE_DRIVES = 16;
procedure ReadPhysicalDriveInNTWithZeroRights ();
var
DriveNumber: Byte;
HDDInfo: THDDInfo;
begin
HDDInfo := THDDInfo.Create();
try
for DriveNumber := 0 to MAX_IDE_DRIVES - 1 do
try
HDDInfo.DriveNumber := DriveNumber;
if HDDInfo.IsInfoAvailable then
begin
Writeln('VendorId: ', HDDInfo.VendorId);
Writeln('ProductId: ', HDDInfo.ProductId);
Writeln('ProductRevision: ', HDDInfo.ProductRevision);
Writeln('SerialNumber: ', HDDInfo.SerialNumber);
Writeln('SerialNumberInt: ', HDDInfo.SerialNumberInt);
Writeln('SerialNumberText: ', HDDInfo.SerialNumberText);
end;
except
on E: Exception do
Writeln(Format('DriveNumber %d, %s: %s', [DriveNumber, E.ClassName, E.Message]));
end;
finally
HDDInfo.Free;
end;
end;
begin
ReadPhysicalDriveInNTWithZeroRights;
Write('Press <Enter>');
Readln;
end.
Unit from http://www.delphipraxis.net/564756-post28.html
// http://www.delphipraxis.net/564756-post28.html
unit hddinfo;
interface
uses Windows, SysUtils, Classes;
const
IOCTL_STORAGE_QUERY_PROPERTY = $2D1400;
type
THDDInfo = class (TObject)
private
FDriveNumber: Byte;
FFileHandle: Cardinal;
FInfoAvailable: Boolean;
FProductRevision: string;
FProductId: string;
FSerialNumber: string;
FVendorId: string;
procedure ReadInfo;
procedure SetDriveNumber(const Value: Byte);
public
constructor Create;
property DriveNumber: Byte read FDriveNumber write SetDriveNumber;
property VendorId: string read FVendorId;
property ProductId: string read FProductId;
property ProductRevision: string read FProductRevision;
property SerialNumber: string read FSerialNumber;
function SerialNumberInt: Cardinal;
function SerialNumberText: string;
function IsInfoAvailable: Boolean;
end;
implementation
type
STORAGE_PROPERTY_QUERY = packed record
PropertyId: DWORD;
QueryType: DWORD;
AdditionalParameters: array[0..3] of Byte;
end;
STORAGE_DEVICE_DESCRIPTOR = packed record
Version: ULONG;
Size: ULONG;
DeviceType: Byte;
DeviceTypeModifier: Byte;
RemovableMedia: Boolean;
CommandQueueing: Boolean;
VendorIdOffset: ULONG;
ProductIdOffset: ULONG;
ProductRevisionOffset: ULONG;
SerialNumberOffset: ULONG;
STORAGE_BUS_TYPE: DWORD;
RawPropertiesLength: ULONG;
RawDeviceProperties: array[0..511] of Byte;
end;
function ByteToChar(const B: Byte): Char;
begin
Result := Chr(B + $30)
end;
function SerialNumberToCardinal (SerNum: String): Cardinal;
begin
HexToBin(PChar(SerNum), PChar(#Result), SizeOf(Cardinal));
end;
function SerialNumberToString(SerNum: String): String;
var
I, StrLen: Integer;
Pair: string;
B: Byte;
Ch: Char absolute B;
begin
Result := '';
StrLen := Length(SerNum);
if Odd(StrLen) then Exit;
I := 1;
while I < StrLen do
begin
Pair := Copy (SerNum, I, 2);
HexToBin(PChar(Pair), PChar(#B), 1);
Result := Result + Chr(B);
Inc(I, 2);
end;
I := 1;
while I < Length(Result) do
begin
Ch := Result[I];
Result[I] := Result[I + 1];
Result[I + 1] := Ch;
Inc(I, 2);
end;
end;
constructor THddInfo.Create;
begin
inherited;
SetDriveNumber(0);
end;
function THDDInfo.IsInfoAvailable: Boolean;
begin
Result := FInfoAvailable
end;
procedure THDDInfo.ReadInfo;
type
PCharArray = ^TCharArray;
TCharArray = array[0..32767] of Char;
var
Returned: Cardinal;
Status: LongBool;
PropQuery: STORAGE_PROPERTY_QUERY;
DeviceDescriptor: STORAGE_DEVICE_DESCRIPTOR;
PCh: PChar;
begin
FInfoAvailable := False;
FProductRevision := '';
FProductId := '';
FSerialNumber := '';
FVendorId := '';
try
FFileHandle := CreateFile(
PChar('\\.\PhysicalDrive' + ByteToChar(FDriveNumber)),
0,
FILE_SHARE_READ or FILE_SHARE_WRITE,
nil,
OPEN_EXISTING,
0,
0
);
if FFileHandle = INVALID_HANDLE_VALUE then
RaiseLastOSError;
ZeroMemory(#PropQuery, SizeOf(PropQuery));
ZeroMemory(#DeviceDescriptor, SizeOf(DeviceDescriptor));
DeviceDescriptor.Size := SizeOf(DeviceDescriptor);
Status := DeviceIoControl(
FFileHandle,
IOCTL_STORAGE_QUERY_PROPERTY,
#PropQuery,
SizeOf(PropQuery),
#DeviceDescriptor,
DeviceDescriptor.Size,
Returned,
nil
);
if not Status then
RaiseLastOSError;
if DeviceDescriptor.VendorIdOffset <> 0 then
begin
PCh := #PCharArray(#DeviceDescriptor)^[DeviceDescriptor.VendorIdOffset];
FVendorId := PCh;
end;
if DeviceDescriptor.ProductIdOffset <> 0 then
begin
PCh := #PCharArray(#DeviceDescriptor)^[DeviceDescriptor.ProductIdOffset];
FProductId := PCh;
end;
if DeviceDescriptor.ProductRevisionOffset <> 0 then
begin
PCh := #PCharArray(#DeviceDescriptor)^[DeviceDescriptor.ProductRevisionOffset];
FProductRevision := PCh;
end;
if DeviceDescriptor.SerialNumberOffset <> 0 then
begin
PCh := #PCharArray(#DeviceDescriptor)^[DeviceDescriptor.SerialNumberOffset];
FSerialNumber := PCh;
end;
FInfoAvailable := True;
finally
if FFileHandle <> INVALID_HANDLE_VALUE then
CloseHandle(FFileHandle);
end;
end;
function THDDInfo.SerialNumberInt: Cardinal;
begin
Result := 0;
if ((IsInfoAvailable = True) and (FSerialNumber <> '')) then Result := SerialNumberToCardinal(FSerialNumber)
end;
function THDDInfo.SerialNumberText: string;
begin
Result := '';
if ((IsInfoAvailable = True) and (FSerialNumber <> '')) then Result := SerialNumberToString(FSerialNumber)
end;
procedure THDDInfo.SetDriveNumber(const Value: Byte);
begin
FDriveNumber := Value;
ReadInfo;
end;
end.
Edit: RAID configurations require special provisions.
For instance, I got a RAID system with multiple RAID 5 array; only the first one displays, and it does not show the drive serial numbers, but the serial number of the RAID array:
VendorId: AMCC
ProductId: 9550SXU-16ML
ProductRevision: 3.08
SerialNumber: 006508296D6A2A00DE82
SerialNumberInt: 688416000
--jeroen
You can use the WMI (Windows Management Instrumentation) to get information related to windows hardware.
Exist two wmi classes wich exposes a property called SerialNumber which store the Number allocated by the manufacturer to identify the physical media. these classes are Win32_DiskDrive and Win32_PhysicalMedia.to access the SerialNumber property of these classes you must know the DeviceId of the Disk which is something like this \\.\PHYSICALDRIVE0. Another way is use a association class which link the Physical drive with the logical drive (C,D,E)
so you must find this link previous to obtain the serial number. the sequence to find this association is like this.
Win32_DiskPartition -> Win32_LogicalDiskToPartition -> Win32_DiskDrive
Note 1 : The SerialNumber property for the Win32_DiskDrive class does not exist in Windows Server 2003, Windows XP, Windows 2000, and Windows NT 4.0, so how you are talking about use Windows Vista or Windows 7, will work ok for you.
Note 2 : The code does not require a administrator account to run.
check this code
{$APPTYPE CONSOLE}
uses
SysUtils,
ActiveX,
ComObj,
Variants;
function GetDiskSerial(const Drive:AnsiChar):string;
var
FSWbemLocator : OLEVariant;
objWMIService : OLEVariant;
colDiskDrives : OLEVariant;
colLogicalDisks: OLEVariant;
colPartitions : OLEVariant;
objDiskDrive : OLEVariant;
objPartition : OLEVariant;
objLogicalDisk : OLEVariant;
oEnumDiskDrive : IEnumvariant;
oEnumPartition : IEnumvariant;
oEnumLogical : IEnumvariant;
iValue : LongWord;
DeviceID : string;
begin;
Result:='';
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
objWMIService := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', ''); //Connect to the WMI
colDiskDrives := objWMIService.ExecQuery('SELECT * FROM Win32_DiskDrive','WQL',0);
oEnumDiskDrive:= IUnknown(colDiskDrives._NewEnum) as IEnumVariant;
while oEnumDiskDrive.Next(1, objDiskDrive, iValue) = 0 do
begin
DeviceID := StringReplace(objDiskDrive.DeviceID,'\','\\',[rfReplaceAll]); //Escape the `\` chars in the DeviceID value because the '\' is a reserved character in WMI.
colPartitions := objWMIService.ExecQuery(Format('ASSOCIATORS OF {Win32_DiskDrive.DeviceID="%s"} WHERE AssocClass = Win32_DiskDriveToDiskPartition',[DeviceID]));//link the Win32_DiskDrive class with the Win32_DiskDriveToDiskPartition class
oEnumPartition := IUnknown(colPartitions._NewEnum) as IEnumVariant;
while oEnumPartition.Next(1, objPartition, iValue) = 0 do
begin
colLogicalDisks := objWMIService.ExecQuery('ASSOCIATORS OF {Win32_DiskPartition.DeviceID="'+objPartition.DeviceID+'"} WHERE AssocClass = Win32_LogicalDiskToPartition'); //link the Win32_DiskPartition class with theWin32_LogicalDiskToPartition class.
oEnumLogical := IUnknown(colLogicalDisks._NewEnum) as IEnumVariant;
while oEnumLogical.Next(1, objLogicalDisk, iValue) = 0 do
begin
if objLogicalDisk.DeviceID=(Drive+':') then //compare the device id
begin
Result:=objDiskDrive.SerialNumber;
Exit;
end;
objLogicalDisk:=Unassigned;
end;
objPartition:=Unassigned;
end;
end;
end;
begin
try
CoInitialize(nil);
try
Writeln(GetDiskSerial('C'));
Readln;
finally
CoUninitialize;
end;
except
on E:Exception do
begin
Writeln(E.Classname, ':', E.Message);
Readln;
end;
end;
end.
Here is another DiskId32 translation from C++ to Delphi by Victor Derevyanko
project:
http://code.google.com/p/dvsrc/
Because the first method (WithZeroRights) doesn't work for me, I wrote another for ReadIdeDriveAsScsiDriveInNT method:
unit HDScsiInfo;
interface
uses
Windows, SysUtils;
const
IDENTIFY_BUFFER_SIZE = 512;
FILE_DEVICE_SCSI = $0000001b;
IOCTL_SCSI_MINIPORT_IDENTIFY = ((FILE_DEVICE_SCSI shl 16) + $0501);
IDE_ATA_IDENTIFY = $EC; // Returns ID sector for ATA.
IOCTL_SCSI_MINIPORT = $0004D008; // see NTDDSCSI.H for definition
type
TDiskData = array [0..256-1] of DWORD;
TDriveInfo = record
ControllerType: Integer; //0 - primary, 1 - secondary, 2 - Tertiary, 3 - Quaternary
DriveMS: Integer; //0 - master, 1 - slave
DriveModelNumber: String;
DriveSerialNumber: String;
DriveControllerRevisionNumber: String;
ControllerBufferSizeOnDrive: Int64;
DriveType: String; //fixed or removable or unknown
DriveSizeBytes: Int64;
end;
THDScsiInfo = class (TObject)
private
FDriveNumber: Byte;
FFileHandle: Cardinal;
FInfoAvailable: Boolean;
FProductRevision: string;
FSerialNumber: string;
FControllerType: Integer;
FDriveMS: Integer;
FDriveModelNumber: string;
FControllerBufferSizeOnDrive: Int64;
FDriveType: string;
FDriveSizeBytes: Int64;
procedure ReadInfo;
procedure SetDriveNumber(const Value: Byte);
procedure PrintIdeInfo(DiskData: TDiskData);
public
constructor Create;
property DriveNumber: Byte read FDriveNumber write SetDriveNumber;
property ProductRevision: string read FProductRevision;
property SerialNumber: string read FSerialNumber;
property ControllerType: Integer read FControllerType;
property DriveMS: Integer read FDriveMS;
property DriveModelNumber: string read FDriveModelNumber;
property ControllerBufferSizeOnDrive: Int64 read FControllerBufferSizeOnDrive;
property DriveType: string read FDriveType;
property DriveSizeBytes: Int64 read FDriveSizeBytes;
function IsInfoAvailable: Boolean;
end;
implementation
type
SRB_IO_CONTROL = record
HeaderLength: Cardinal;
Signature: array [0..8-1] of Byte;
Timeout: Cardinal;
ControlCode: Cardinal;
ReturnCode: Cardinal;
Length: Cardinal;
end;
PSRB_IO_CONTROL = ^SRB_IO_CONTROL;
DRIVERSTATUS = record
bDriverError: Byte;// Error code from driver, or 0 if no error.
bIDEStatus: Byte;// Contents of IDE Error register.
// Only valid when bDriverError is SMART_IDE_ERROR.
bReserved: array [0..1] of Byte;// Reserved for future expansion.
dwReserved: array [0..1] of Longword;// Reserved for future expansion.
end;
SENDCMDOUTPARAMS = record
cBufferSize: Longword;// Size of bBuffer in bytes
DriverStatus: DRIVERSTATUS;// Driver status structure.
bBuffer: array [0..0] of Byte;// Buffer of arbitrary length in which to store the data read from the // drive.
end;
IDEREGS = record
bFeaturesReg: Byte;// Used for specifying SMART "commands".
bSectorCountReg: Byte;// IDE sector count register
bSectorNumberReg: Byte;// IDE sector number register
bCylLowReg: Byte;// IDE low order cylinder value
bCylHighReg: Byte;// IDE high order cylinder value
bDriveHeadReg: Byte;// IDE drive/head register
bCommandReg: Byte;// Actual IDE command.
bReserved: Byte;// reserved for future use. Must be zero.
end;
SENDCMDINPARAMS = record
cBufferSize: Longword;// Buffer size in bytes
irDriveRegs: IDEREGS; // Structure with drive register values.
bDriveNumber: Byte;// Physical drive number to send
// command to (0,1,2,3).
bReserved: array[0..2] of Byte;// Reserved for future expansion.
dwReserved: array [0..3] of Longword;// For future use.
bBuffer: array [0..0] of Byte;// Input buffer. //!TODO: this is array of single element
end;
PSENDCMDINPARAMS = ^SENDCMDINPARAMS;
PSENDCMDOUTPARAMS = ^SENDCMDOUTPARAMS;
IDSECTOR = record
wGenConfig: Word;
wNumCyls: Word;
wReserved: Word;
wNumHeads: Word;
wBytesPerTrack: Word;
wBytesPerSector: Word;
wSectorsPerTrack: Word;
wVendorUnique: array [0..3-1] of Word;
sSerialNumber: array [0..20-1] of AnsiChar;
wBufferType: Word;
wBufferSize: Word;
wECCSize: Word;
sFirmwareRev: array [0..8-1] of AnsiChar;
sModelNumber: array [0..40-1] of AnsiChar;
wMoreVendorUnique: Word;
wDoubleWordIO: Word;
wCapabilities: Word;
wReserved1: Word;
wPIOTiming: Word;
wDMATiming: Word;
wBS: Word;
wNumCurrentCyls: Word;
wNumCurrentHeads: Word;
wNumCurrentSectorsPerTrack: Word;
ulCurrentSectorCapacity: Cardinal;
wMultSectorStuff: Word;
ulTotalAddressableSectors: Cardinal;
wSingleWordDMA: Word;
wMultiWordDMA: Word;
bReserved: array [0..128-1] of Byte;
end;
PIDSECTOR = ^IDSECTOR;
TArrayDriveInfo = array of TDriveInfo;
type
DeviceQuery = record
HeaderLength: Cardinal;
Signature: array [0..8-1] of Byte;
Timeout: Cardinal;
ControlCode: Cardinal;
ReturnCode: Cardinal;
Length: Cardinal;
cBufferSize: Longword;// Buffer size in bytes
irDriveRegs: IDEREGS; // Structure with drive register values.
bDriveNumber: Byte;// Physical drive number to send
bReserved: array[0..2] of Byte;// Reserved for future expansion.
dwReserved: array [0..3] of Longword;// For future use.
bBuffer: array [0..0] of Byte;// Input buffer. //!TODO: this is array of single element
end;
function ConvertToString (diskdata: TDiskData;
firstIndex: Integer;
lastIndex: Integer;
buf: PAnsiChar): PAnsiChar;
var
index: Integer;
position: Integer;
begin
position := 0;
// each integer has two characters stored in it backwards
for index := firstIndex to lastIndex do begin
// get high byte for 1st character
buf[position] := AnsiChar(Chr(diskdata [index] div 256));
inc(position);
// get low byte for 2nd character
buf [position] := AnsiChar(Chr(diskdata [index] mod 256));
inc(position);
end;
// end the string
buf[position] := Chr(0);
// cut off the trailing blanks
index := position - 1;
while (index >0) do begin
// if not IsSpace(AnsiChar(buf[index]))
if (AnsiChar(buf[index]) <> ' ')
then break;
buf [index] := Chr(0);
dec(index);
end;
Result := buf;
end;
constructor THDScsiInfo.Create;
begin
inherited;
SetDriveNumber(0);
end;
function THDScsiInfo.IsInfoAvailable: Boolean;
begin
Result := FInfoAvailable
end;
procedure THDScsiInfo.PrintIdeInfo (DiskData: TDiskData);
var
nSectors: Int64;
serialNumber: array [0..1024-1] of AnsiChar;
modelNumber: array [0..1024-1] of AnsiChar;
revisionNumber: array [0..1024-1] of AnsiChar;
begin
// copy the hard drive serial number to the buffer
ConvertToString (DiskData, 10, 19, #serialNumber);
ConvertToString (DiskData, 27, 46, #modelNumber);
ConvertToString (DiskData, 23, 26, #revisionNumber);
FControllerType := FDriveNumber div 2;
FDriveMS := FDriveNumber mod 2;
FDriveModelNumber := modelNumber;
FSerialNumber := serialNumber;
FProductRevision := revisionNumber;
FControllerBufferSizeOnDrive := DiskData [21] * 512;
if ((DiskData [0] and $0080) <> 0)
then FDriveType := 'Removable'
else if ((DiskData [0] and $0040) <> 0)
then FDriveType := 'Fixed'
else FDriveType := 'Unknown';
// calculate size based on 28 bit or 48 bit addressing
// 48 bit addressing is reflected by bit 10 of word 83
if ((DiskData[83] and $400) <> 0) then begin
nSectors := DiskData[103] * Int64(65536) * Int64(65536) * Int64(65536) +
DiskData[102] * Int64(65536) * Int64(65536) +
DiskData[101] * Int64(65536) +
DiskData[100];
end else begin
nSectors := DiskData [61] * 65536 + DiskData [60];
end;
// there are 512 bytes in a sector
FDriveSizeBytes := nSectors * 512;
end;
procedure THDScsiInfo.ReadInfo;
type
DataArry = array [0..256-1] of WORD;
PDataArray = ^DataArry;
const
SENDIDLENGTH = sizeof (SENDCMDOUTPARAMS) + IDENTIFY_BUFFER_SIZE;
var
I: Integer;
buffer: array [0..sizeof (SRB_IO_CONTROL) + SENDIDLENGTH - 1] of AnsiChar;
dQuery: DeviceQuery;
dummy: DWORD;
pOut: PSENDCMDOUTPARAMS;
pId: PIDSECTOR;
DiskData: TDiskData;
pIdSectorPtr: PWord;
begin
FInfoAvailable := False;
FFileHandle := CreateFile (PChar(Format('\\.\Scsi%d:', [FDriveNumber])),
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil,
OPEN_EXISTING, 0, 0);
if (FFileHandle <> INVALID_HANDLE_VALUE) then begin
ZeroMemory(#dQuery, SizeOf(dQuery));
dQuery.HeaderLength := sizeof (SRB_IO_CONTROL);
dQuery.Timeout := 10000;
dQuery.Length := SENDIDLENGTH;
dQuery.ControlCode := IOCTL_SCSI_MINIPORT_IDENTIFY;
StrLCopy(#dQuery.Signature, 'SCSIDISK', 8);
dQuery.irDriveRegs.bCommandReg := IDE_ATA_IDENTIFY;
dQuery.bDriveNumber := FDriveNumber;
if (DeviceIoControl (FFileHandle, IOCTL_SCSI_MINIPORT,
#dQuery,
SizeOf(dQuery),
#buffer,
sizeof (SRB_IO_CONTROL) + SENDIDLENGTH,
dummy, nil))
then begin
pOut := PSENDCMDOUTPARAMS(buffer + sizeof (SRB_IO_CONTROL)); //!TOCHECK
pId := PIDSECTOR(#pOut^.bBuffer[0]);
if (pId^.sModelNumber[0] <> Chr(0) ) then begin
pIdSectorPtr := PWord(pId);
for I := 0 to 256-1 do
DiskData[I] := PDataArray(pIdSectorPtr)[I];
PrintIdeInfo (DiskData);
FInfoAvailable := True;
end;
end;
CloseHandle(FFileHandle);
end;
end;
procedure THDScsiInfo.SetDriveNumber(const Value: Byte);
begin
FDriveNumber := Value;
ReadInfo;
end;
end.
Sample usage:
procedure ReadIdeDriveAsScsiDriveInNT;
var
DriveNumber: Byte;
HDDInfo: THDScsiInfo;
begin
HDDInfo := THDScsiInfo.Create();
try
for DriveNumber := 0 to MAX_IDE_DRIVES - 1 do
try
HDDInfo.DriveNumber := DriveNumber;
if HDDInfo.IsInfoAvailable then begin
Writeln('Available Drive: ', HDDInfo.DriveNumber);
Writeln('ControllerType: ', HDDInfo.ControllerType);
Writeln('DriveMS: ', HDDInfo.DriveMS);
Writeln('DriveModelNumber: ', HDDInfo.DriveModelNumber);
Writeln('ControllerBufferSizeOnDrive: ', HDDInfo.ControllerBufferSizeOnDrive);
Writeln('DriveType: ', HDDInfo.DriveType);
Writeln('DriveSizeBytes: ', HDDInfo.DriveSizeBytes);
Writeln('ProductRevision: ', HDDInfo.ProductRevision);
Writeln('SerialNumber: ', HDDInfo.SerialNumber);
end;
except
on E: Exception do
Writeln(Format('DriveNumber %d, %s: %s', [DriveNumber, E.ClassName, E.Message]));
end;
finally
HDDInfo.Free;
end;
end;
begin
ReadIdeDriveAsScsiDriveInNT;
Write('Press <Enter>');
end.
This works great with my WD disk.
I found this code, it is fixed one and working fine with me on windows 7 64
https://code.google.com/p/dvsrc/downloads/detail?name=20120116DiskId32Port_fixed.7z&can=2&q=
and this his all work
https://code.google.com/p/dvsrc/downloads/list
Posting this purely for completeness sake, and to possibly satisfy those interested or die hard hardware fanatics.
I do not have a Pascal compiler at my disposal to test these routines on current Windows systems, but I do know this code worked back in the DOS era. Maybe it still works from a command prompt window.
Pascal code:
uses
Dos, Crt;
type
SerNoType = record
case Integer of
0: (SerNo1, SerNo2: Word);
1: (SerNo: Longint);
end;
DiskSerNoInfoType = record
Infolevel: Word;
VolSerNo: SerNoType;
VolLabel: array[1..11] of Char;
FileSys: array[1..8] of Char;
end;
function HexDigit(N: Byte): Char;
begin
if N < 10 then
HexDigit := Chr(Ord('0') + N)
else
HexDigit := Chr(Ord('A') + (N - 10));
end;
function GetVolSerialNo(DriveNo: Byte): String;
var
ReturnArray: DiskSerNoInfoType;
Regs: Registers;
begin
with Regs do
begin
AX := $440d;
BL := DriveNo;
CH := $08;
CL := $66;
DS := Seg(ReturnArray);
DX := Ofs(ReturnArray);
Intr($21, Regs);
if (Flags and FCarry) <> 0 then
GetVolSerialNo := ''
else
with ReturnArray.VolSerNo do
GetVolSerialNo :=
HexDigit(Hi(SerNo2) div 16) + HexDigit(Hi(SerNo2) mod 16) +
HexDigit(Lo(SerNo2) div 16) + HexDigit(Lo(SerNo2) mod 16) +
HexDigit(Hi(SerNo1) div 16) + HexDigit(Hi(SerNo1) mod 16) +
HexDigit(Lo(SerNo1) div 16) + HexDigit(Lo(SerNo1) mod 16);
end;
end;
procedure PutVolSerialNo(DriveNo: Byte; SerialNo: Longint);
var
ReturnArray: DiskSerNoInfoType;
Regs: Registers;
begin
with Regs do
begin
AX := $440d;
BL := DriveNo;
CH := $08;
CL := $66;
DS := Seg(ReturnArray);
DX := Ofs(ReturnArray);
Intr($21, Regs);
if (Flags and FCarry) = 0 then
begin
ReturnArray.VolSerNo.SerNo := SerialNo;
AH := $69;
BL := DriveNo;
AL := $01;
DS := Seg(ReturnArray);
DX := Ofs(ReturnArray);
Intr($21, Regs);
end;
end;
end;
Please feel free to update this answer in order to get it working (if possible at all) in Delphi.