📄 小巧磁盘编辑器.txt
字号:
Window2000/Xp下的小巧磁盘编辑器
提起磁盘编辑器,我们就会想到Winhex,Advanced Disk Catalog ,Edittool等常用软件.如果能自己动手制作一个类似的磁盘工具,那将是很有趣的.这里就使用Dephi7在Window2000/2003/Xp环境下设计一款小巧的磁盘编辑器,可以在Fat32,Ntfs文件系统在正常工作.可以实现磁盘扇区的读取,修改,复制等常用功能.程序运行效果如图1所示.
在Dephi新建一个项目,在窗体上放置一个stringgrid控件,用来显示和修改磁盘扇区的内容.两个按钮,分别用来读写相应扇区.两个spinedit控件,用来设置起始扇区和读写扇区的数目.一个DriveComboBox控件,用来选择磁盘.statusbar状态条用来显示当前状态.
程序的关键是利用CreateFile(drive, GENERIC_ALL, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,0, 0)函数来打开需要读写的驱动器,然后利用ReadFile,WriteFile来进行磁盘读写。程序的核心是如何动态显示和修改磁盘扇区的内容.这里使用了两个整数型的数组seca和secb分别用来记录扇区的原始内容和修改后的内容.不管是否修改了扇区内容,在对磁盘进行写操作时,把数组secb作为数据源来提供扇区写入所需的数据.当然不点击"写扇区"按钮的话,一切操作都是安全的.
程序中用来在stringgrid中显示扇区内容的代码如下:
procedure tform1.BytesToGrid; //此过程将扇区内容显示在stringgrid中.这里的stringgrid的option属性应包括:goFixedVertLine, goFixedHorzLine, goRangeSelect, goEditing, goTabs, goAlwaysShowEditor.
var
i,j,k:Integer;
c: Char;
fbuf:pchar;
begin
n:=nsectors.Value; //读写的扇区数
s:=startsector.Value;//起始扇区数
setlength(seca,n*bytepersectors);
setlength(secb,n*bytepersectors); //动态设置数组的长度,seca和secb分别记录原始和变化后的扇区数据
StatusBar1.Panels[1].Text := '';
hDeviceHandle := CreateFile(driver, GENERIC_ALL, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,0, 0);//打开磁盘
if (hDeviceHandle<> INVALID_HANDLE_VALUE) then //是否打开成功
begin
fbuf:=allocmem(n*bytepersectors);
FileSeek(hDevicehandle,s*bytepersectors,0); //定位扇区
if FileRead(hDevicehandle,fbuf[0],n*bytepersectors)<>n*bytepersectors then
raise exception.create('读磁盘错误!');
stringgrid1.Rowcount:=n*32+1;//根据选择的扇区数动态的改变网格的行数
for i:=0 to ((n*bytepersectors) div 16)-1 do
StringGrid1.Cells[0,i+1] := IntToHex(i*16,4)+':'; //格式化显示stringgrid的0行表头信息
for i:=0 to ((n*bytepersectors) div 16)-1 do
for j:=1 to 16 do
begin
seca[j-1+16*i]:=integer(fbuf[j-1+16*i]);//将扇区数据转换为整型
secb[j-1+16*i]:=seca[j-1+16*i]; //初始扇区数组数据
end;
for i:=0 to ((n*bytepersectors) div 16)-1 do begin
StringGrid1.Cells[0,i+1] := IntToHex(i*16,4)+':'; //格式化显示stringgrid的0列内容
for j:=1 to 16 do begin
K := seca[j-1+16*i];
StringGrid1.Cells[j,i+1]:=format('%.2x',[integer(fbuf[j-1+16*i])]);//在stringgrid的1至16列格式化数据显示
C :=chr(k);
if c<' ' then c:='.';
StringGrid1.Cells[j+17,i+1] := c; //在stringgrid18至33列显示对应的asc码
StatusBar1.Panels[1].Text := Format('逻辑磁盘 '+DriveComboBox1.Drive+'第 %D 扇区起连续'+inttostr(n)+'个扇区',[s]);
end;
end;
freemem(fbuf);//释放内存
closehandle(hDeviceHandle); //关闭句柄
end;
end;
程序的完整源代码如下:
unit diskedirunt;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Spin, shellapi,FileCtrl, Grids, ComCtrls;
type
TForm1 = class(TForm)
DriveComboBox1: TDriveComboBox;
Button1: TButton; //写扇区按钮
startsector: TSpinEdit;
nsectors: TSpinEdit;
Label1: TLabel;
Label2: TLabel;
StatusBar1: TStatusBar;
StringGrid1: TStringGrid;
Button2: TButton; //读扇区按钮
Label3: TLabel;
procedure FormShow(Sender: TObject);
procedure DriveComboBox1Change(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure StringGrid1KeyPress(Sender: TObject; var Key: Char);
procedure StringGrid1GetEditText(Sender: TObject; ACol, ARow: Integer;
var Value: String);
procedure StringGrid1Exit(Sender: TObject);
procedure StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
var CanSelect: Boolean);
procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure nsectorsChange(Sender: TObject);
procedure startsectorChange(Sender: TObject);
private
driver:pchar; //驱动器名称
bytepersectors:integer; //每扇区字节数,默认为512
s,n:integer;
seca,secb:array of integer; //分别保存原始和变化的扇区数据
procedure BytesToGrid;
procedure OnFocusChange;
{ Private declarations }
public
{ Public declarations }
end;
var
ColOld: Integer = 1; //根据用户的在stringgrid的选择改变光标所在的行列数
RowOld: Integer = 1;
CellKeyPress: Integer; //判断在stringgrid中的按键
CharSellsCount:integer =17;
hdevicehandle:thandle; //文件句柄
Form1: TForm1;
implementation
uses wlt;
{$R *.dfm}
procedure TForm1.OnFocusChange; //在stringgrid里改变光标位置时动态刷新数组的内容
var
I: Integer;
C: Char;
begin
I := ColOld-1+(RowOld-1)*16;
with StringGrid1 do
if Length(Cells[ColOld,RowOld])>2 then begin //没有修改则显示seca中的原始数据
Cells[ColOld,RowOld] := IntToHex(seca[i],2);
end else
try
secb[i] := StrToInt('$'+Cells[ColOld,RowOld]); //保存修改后的扇区数据到secb数组中
C := Chr(secb[i]);
if c<' ' then c:='.';
Cells[ColOld+17,RowOld] := C;
except; end;
end;
procedure tform1.BytesToGrid; //在stringgrid中动态显示扇区的内容
var
i,j,k:Integer;
c: Char;
fbuf:pchar;
begin
n:=nsectors.Value;
s:=startsector.Value;
setlength(seca,n*bytepersectors);
setlength(secb,n*bytepersectors);
StatusBar1.Panels[1].Text := '';
hDeviceHandle := CreateFile(driver, GENERIC_ALL, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,0, 0);
if (hDeviceHandle<> INVALID_HANDLE_VALUE) then
begin
fbuf:=allocmem(n*bytepersectors);
FileSeek(hDevicehandle,s*bytepersectors,0);
if FileRead(hDevicehandle,fbuf[0],n*bytepersectors)<>n*bytepersectors then
raise exception.create('读磁盘错误!');
stringgrid1.Rowcount:=n*32+1;
for i:=0 to ((n*bytepersectors) div 16)-1 do
StringGrid1.Cells[0,i+1] := IntToHex(i*16,4)+':';
for i:=0 to ((n*bytepersectors) div 16)-1 do
for j:=1 to 16 do
begin
seca[j-1+16*i]:=integer(fbuf[j-1+16*i]);
secb[j-1+16*i]:=seca[j-1+16*i];
end;
for i:=0 to ((n*bytepersectors) div 16)-1 do begin
StringGrid1.Cells[0,i+1] := IntToHex(i*16,4)+':';
for j:=1 to 16 do begin
K := seca[j-1+16*i];
StringGrid1.Cells[j,i+1]:=format('%.2x',[integer(fbuf[j-1+16*i])]);
C :=chr(k);
if c<' ' then c:='.';
StringGrid1.Cells[j+17,i+1] := c;
StatusBar1.Panels[1].Text := Format('逻辑磁盘 '+DriveComboBox1.Drive+'第 %D 扇区起连续'+inttostr(n)+'个扇区',[s]);
end;
end;
freemem(fbuf);
closehandle(hDeviceHandle);
end;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
driver:=pchar('\\.\'+drivecombobox1.Drive+':');//在formshow中设置初始驱动器值
BytesToGrid;
end;
procedure TForm1.DriveComboBox1Change(Sender: TObject);
begin
Button2Click(self);//改变驱动器时,刷新网格显示
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i:integer;
begin
startsector.Value:=0;
nsectors.Value:=1;
bytepersectors:=512;
with StringGrid1 do begin //网格分两部分,一边以16进制显示扇区数据,一边显示相应的asc值.
ColWidths[0] := 50;
for i := 17 to 33 do begin
TabStops[i] := False;
ColWidths[i] := 11; //设置网格列的宽度
end;
for i := 1 to 16 do Cells[i,0] := IntToHex(i-1,2); //设置表头信息
for i := 18 to 33 do Cells[i,0] := IntToHex(i-18,1);
Cells[0,0] := '偏移';
end;
end;
procedure TForm1.StringGrid1KeyPress(Sender: TObject; var Key: Char);
begin //根据在网格中的按键,进行光标的定位
if Key<>#8 then begin
Key := UpCase(Key);
if not (Key in ['0'..'9','A'..'F']) then Key := #0 else //只允许输入特定字符
with StringGrid1 do
if CellKeyPress>1 then begin
if Length(Cells[Col,Row])>1 then begin
if (Col+1+CharSellsCount)<ColCount then begin
Col := Col+1; //光标换列
CellKeyPress := 1;
end else begin
if (Row+1)<RowCount then begin
Col := 1; Row := Row+1; //光标换行
CellKeyPress := 1;
end else Key := #0;
end;
end;
end else Inc(CellKeyPress);
end;
if Key<>#0 then StatusBar1.Panels[1].Text := Format('修改逻辑磁盘 '+DriveComboBox1.Drive+'第 %D 扇区',[startsector.Value]); //,如进行扇区数据的修改,则在状态栏中显示修改信息
end;
procedure TForm1.StringGrid1GetEditText(Sender: TObject; ACol,
ARow: Integer; var Value: String); //根据在网格中选择的位置改变记录行列的ColOld ,
RowOld 变量的值.
var
S: String;
L: Integer;
begin
with StringGrid1 do begin
S := Cells[ColOld,RowOld];
L := Length(S);
case L of
0: Cells[ColOld,RowOld] := '00';
1: Cells[ColOld,RowOld] := '0'+S;
end;
end;
ColOld := ACol;
RowOld := ARow;
end;
procedure TForm1.StringGrid1Exit(Sender: TObject);
begin
with StringGrid1 do
if Col<16 then
Col := Col+1
else begin
Col := 1; Row := Row+1; //改变行列的值
end;
OnFocusChange; //当光标所在的行列变化时,刷新网格显示.
end;
procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol,
ARow: Integer; var CanSelect: Boolean); //选择网格数据时动态刷新网格内容
begin
if aCol>16 then CanSelect := False;
CellKeyPress := 0;
OnFocusChange;
end;
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState); //当修改扇区数据时,对修改的部分进行醒目的显示
var
i:integer;
begin
with StringGrid1.Canvas do
if (aCol>0) and (aRow>0) then begin
case aCol of
1..16: I := aCol-1+(aRow-1)*16;
18..33: I := aCol-18+(aRow-1)*16;
else I := -1;
end;
if I>=0 then
if seca[I]<>secb[I] then begin //对原始seca数据和变化的secb数据进行对比,不同则表示修改了扇区数据
Brush.Color := clred;
FillRect(Rect);
TextOut(Rect.Left+2,Rect.Top+2,StringGrid1.Cells[aCol,aRow]);
end; //重新以红色显示修改的数据
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
driver:=pchar('\\.\'+drivecombobox1.Drive+':');
BytesToGrid; //读取扇区数据
end;
procedure TForm1.Button1Click(Sender: TObject); //将指定的扇区写至选择的逻辑盘的对应扇区位置
var
p:pchar;
i,j:integer;
writesec:string;
begin
if Form2.ShowModal <> mrOk then Exit; //显示磁盘写入的窗体
driver:=pchar('\\.\'+form2.DriveComboBox1.Drive+':'); //取得指定逻辑盘
p:=allocmem(n*bytepersectors);
setlength(writesec,n*bytepersectors); //动态设置字符串长度,用以保存扇区数据
writesec:='';
for i:=0 to ((n*bytepersectors) div 16)-1 do
for j:=1 to 16 do
writesec := writesec + chr(secb[j-1+16*i]); //将secb中的保存的扇区数据转移至特定字符串中
p:=pchar(writesec);//将数据转化为pchar类型
hDeviceHandle := CreateFile(driver, GENERIC_ALL,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,0, 0); //打开目标盘
if (hDeviceHandle <> INVALID_HANDLE_VALUE) then
begin
FileSeek(hDevicehandle,form2.SpinEdit1.Value*bytepersectors,0); //扇区定位
if FileWrite(hDevicehandle,p[0],n*bytepersectors)<>n*bytepersectors then //写入扇区数据,完成扇区写操作1
raise exception.create('Write错误%d');
StatusBar1.Panels[1].Text :='写逻辑磁盘 '+form2.DriveComboBox1.Drive+'成功!';
closehandle(hDeviceHandle); //关闭句柄
end;
end;
procedure TForm1.nsectorsChange(Sender: TObject);
begin
if nsectors.Value<1 then //改变扇区数量时进行判断
begin
MessageBox(0, '扇区数不能为0!','小巧磁盘编辑器', MB_OK + MB_ICONERROR + MB_DEFBUTTON2 +
MB_TOPMOST);
nsectors.Value:=1;
end;
Button2Click(self);//改变网格数据
end;
procedure TForm1.startsectorChange(Sender: TObject);
begin
Button2Click(self); //起始扇区改变时,改变网格的内容
end;
end.
写扇区的源码如下:
unit wlt;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Spin, FileCtrl;
type
TForm2 = class(TForm) //写扇区窗体
Label1: TLabel;
Button1: TButton;
Button2: TButton;
Label2: TLabel;
SpinEdit1: TSpinEdit;
DriveComboBox1: TDriveComboBox;
procedure FormShow(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.FormShow(Sender: TObject);
begin
spinedit1.Value:=0; //起始扇区数置0
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
if MessageBox(0, '磁盘扇区写入可能存在危险,是否继续?',
'小巧磁盘编辑器', MB_YESNO + MB_ICONERROR + MB_DEFBUTTON2 +
MB_TOPMOST) = IDNO then
close;
end;
end.
当然这只是在windows2000/xp下读写扇区的的一般方法.还有一些需要进一步完善的地方.例如判断磁盘的总扇区数,进行必要的约束等.限于篇幅就不一一列举了.
测试环境: Dephi7+Windows2000,Ntfs文件系统.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -