initial commit
[rofl0r-KOL.git] / tools / stripreloc / StripReloc.dpr
blob7cae95f8d4bd8be294cd27be417b9812d37fe576
1 program StripReloc;\r
2 {$APPTYPE CONSOLE}\r
3 \r
4 {\r
5   StripReloc v1.11\r
6   Strip relocation section from Win32 PE files\r
7   Copyright (C) 2000-2003 Jordan Russell. All rights reserved.\r
8 \r
9   www:    http://www.jrsoftware.org/\r
10   email:  jr AT jrsoftware.org\r
12   This program is free software; you can redistribute it and/or\r
13   modify it under the terms of the GNU General Public License\r
14   as published by the Free Software Foundation; either version 2\r
15   of the License, or (at your option) any later version.\r
17   This program is distributed in the hope that it will be useful,\r
18   but WITHOUT ANY WARRANTY; without even the implied warranty of\r
19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
20   GNU General Public License for more details.\r
22   You should have received a copy of the GNU General Public License\r
23   along with this program; if not, write to the Free Software\r
24   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r
25 }\r
27 uses\r
28   Windows, SysUtils, Classes;\r
30 {x$R *.RES}\r
32 const\r
33   Version = '1.11';\r
35 var\r
36   KeepBackups: Boolean = True;\r
37   WantValidChecksum: Boolean = False;\r
38   ForceStrip: Boolean = False;\r
40   ImageHlpHandle: THandle;\r
41   CheckSumMappedFile: function(BaseAddress: Pointer; FileLength: DWORD;\r
42     HeaderSum: PDWORD; CheckSum: PDWORD): PImageNtHeaders; stdcall;\r
44 function CalcChecksum(const FileHandle: THandle): DWORD;\r
45 var\r
46   H: THandle;\r
47   M: Pointer;\r
48   OldSum: DWORD;\r
49 begin\r
50   M := nil;\r
51   H := CreateFileMapping(FileHandle, nil, PAGE_READONLY, 0, 0, nil);\r
52   if H = 0 then\r
53     RaiseLastWin32Error;\r
54   try\r
55     M := MapViewOfFile(H, FILE_MAP_READ, 0, 0, 0);\r
56     Win32Check(CheckSumMappedFile(M, GetFileSize(FileHandle, nil), @OldSum, @Result) <> nil);\r
57   finally\r
58     if Assigned(M) then\r
59       UnmapViewOfFile(M);\r
60     CloseHandle(H);\r
61   end;\r
62 end;\r
64 procedure Strip(const Filename: String);\r
65 type\r
66   PPESectionHeaderArray = ^TPESectionHeaderArray;\r
67   TPESectionHeaderArray = array[0..$7FFFFFFF div SizeOf(TImageSectionHeader)-1] of TImageSectionHeader;\r
68 var\r
69   BackupFilename: String;\r
70   F, F2: File;\r
71   EXESig: Word;\r
72   PEHeaderOffset, PESig: Cardinal;\r
73   PEHeader: TImageFileHeader;\r
74   PEOptHeader: ^TImageOptionalHeader;\r
75   PESectionHeaders: PPESectionHeaderArray;\r
76   BytesLeft, Bytes: Cardinal;\r
77   Buf: array[0..8191] of Byte;\r
78   I: Integer;\r
79   RelocVirtualAddr, RelocPhysOffset, RelocPhysSize: Cardinal;\r
80   OldSize, NewSize: Cardinal;\r
81   TimeStamp: TFileTime;\r
82 begin\r
83   PEOptHeader := nil;\r
84   PESectionHeaders := nil;\r
85   try\r
86     RelocPhysOffset := 0;\r
87     RelocPhysSize := 0;\r
88     BackupFilename := Filename + '.bak';\r
90     Write(Filename, ': ');\r
91     AssignFile(F, Filename);\r
92     FileMode := fmOpenRead or fmShareDenyWrite;\r
93     Reset(F, 1);\r
94     try\r
95       OldSize := FileSize(F);\r
96       GetFileTime(TFileRec(F).Handle, nil, nil, @TimeStamp);\r
98       BlockRead(F, EXESig, SizeOf(EXESig));\r
99       if EXESig <> $5A4D {'MZ'} then begin\r
100         Writeln('File isn''t an EXE file (1).');\r
101         Exit;\r
102       end;\r
103       Seek(F, $3C);\r
104       BlockRead(F, PEHeaderOffset, SizeOf(PEHeaderOffset));\r
105       if PEHeaderOffset = 0 then begin\r
106         Writeln('File isn''t a PE file (1).');\r
107         Exit;\r
108       end;\r
109       Seek(F, PEHeaderOffset);\r
110       BlockRead(F, PESig, SizeOf(PESig));\r
111       if PESig <> $00004550 {'PE'#0#0} then begin\r
112         Writeln('File isn''t a PE file (2).');\r
113         Exit;\r
114       end;\r
115       BlockRead(F, PEHeader, SizeOf(PEHeader));\r
116       if not ForceStrip and (PEHeader.Characteristics and IMAGE_FILE_DLL <> 0) then begin\r
117         Writeln('Skipping; can''t strip a DLL.');\r
118         Exit;\r
119       end;\r
120       if PEHeader.Characteristics and IMAGE_FILE_RELOCS_STRIPPED <> 0 then begin\r
121         Writeln('Relocations already stripped from file (1).');\r
122         Exit;\r
123       end;\r
124       PEHeader.Characteristics := PEHeader.Characteristics or IMAGE_FILE_RELOCS_STRIPPED;\r
125       GetMem(PEOptHeader, PEHeader.SizeOfOptionalHeader);\r
126       BlockRead(F, PEOptHeader^, PEHeader.SizeOfOptionalHeader);\r
127       if (PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = 0) or\r
128          (PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size = 0) then begin\r
129         Writeln('Relocations already stripped from file (2).');\r
130         Exit;\r
131       end;\r
132       RelocVirtualAddr := PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;\r
133       PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress := 0;\r
134       PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size := 0;\r
135       if not WantValidChecksum then\r
136         PEOptHeader.CheckSum := 0;\r
137       GetMem(PESectionHeaders, PEHeader.NumberOfSections * SizeOf(TImageSectionHeader));\r
138       BlockRead(F, PESectionHeaders^, PEHeader.NumberOfSections * SizeOf(TImageSectionHeader));\r
139       for I := 0 to PEHeader.NumberOfSections-1 do\r
140         with PESectionHeaders[I] do\r
141           if (VirtualAddress = RelocVirtualAddr) and (SizeOfRawData <> 0) then begin\r
142             RelocPhysOffset := PointerToRawData;\r
143             RelocPhysSize := SizeOfRawData;\r
144             PointerToRawData := 0;\r
145             SizeOfRawData := 0;\r
146             Break;\r
147           end;\r
148       if RelocPhysOffset = 0 then begin\r
149         Writeln('Relocations already stripped from file (3).');\r
150         Exit;\r
151       end;\r
152       for I := 0 to PEHeader.NumberOfSections-1 do\r
153         with PESectionHeaders[I] do begin\r
154           if PointerToRawData >= RelocPhysOffset then\r
155             Dec(PointerToRawData, RelocPhysSize);\r
156           if PointerToLinenumbers >= RelocPhysOffset then\r
157             Dec(PointerToLinenumbers, RelocPhysSize);\r
158           if PointerToRelocations <> 0 then begin\r
159             { ^ I don't think this field is ever used in the PE format.\r
160               StripRlc doesn't handle it. }\r
161             Writeln('Cannot handle this file (1).');\r
162             Exit;\r
163           end;\r
164         end;\r
165       if PEOptHeader.ImageBase < $400000 then begin\r
166         Writeln('Cannot handle this file -- the image base address is less than 0x400000.');\r
167         Exit;\r
168       end;\r
169     finally\r
170       CloseFile(F);\r
171     end;\r
172     if FileExists(BackupFilename) then\r
173       Win32Check(DeleteFile(BackupFilename));\r
174     Rename(F, BackupFilename);\r
175     try\r
176       FileMode := fmOpenRead or fmShareDenyWrite;\r
177       Reset(F, 1);\r
178       try\r
179         AssignFile(F2, Filename);\r
180         FileMode := fmOpenWrite or fmShareExclusive;\r
181         Rewrite(F2, 1);\r
182         try\r
183           BytesLeft := RelocPhysOffset;\r
184           while BytesLeft <> 0 do begin\r
185             Bytes := BytesLeft;\r
186             if Bytes > SizeOf(Buf) then Bytes := SizeOf(Buf);\r
187             BlockRead(F, Buf, Bytes);\r
188             BlockWrite(F2, Buf, Bytes);\r
189             Dec(BytesLeft, Bytes);\r
190           end;\r
191           Seek(F, Cardinal(FilePos(F)) + RelocPhysSize);\r
192           BytesLeft := FileSize(F) - FilePos(F);\r
193           while BytesLeft <> 0 do begin\r
194             Bytes := BytesLeft;\r
195             if Bytes > SizeOf(Buf) then Bytes := SizeOf(Buf);\r
196             BlockRead(F, Buf, Bytes);\r
197             BlockWrite(F2, Buf, Bytes);\r
198             Dec(BytesLeft, Bytes);\r
199           end;\r
200           Seek(F2, PEHeaderOffset + SizeOf(PESig));\r
201           BlockWrite(F2, PEHeader, SizeOf(PEHeader));\r
202           BlockWrite(F2, PEOptHeader^, PEHeader.SizeOfOptionalHeader);\r
203           BlockWrite(F2, PESectionHeaders^, PEHeader.NumberOfSections * SizeOf(TImageSectionHeader));\r
204           if WantValidChecksum then begin\r
205             PEOptHeader.CheckSum := CalcChecksum(TFileRec(F2).Handle);\r
206             { go back and rewrite opt. header with new checksum }\r
207             Seek(F2, PEHeaderOffset + SizeOf(PESig) + SizeOf(PEHeader));\r
208             BlockWrite(F2, PEOptHeader^, PEHeader.SizeOfOptionalHeader);\r
209           end;\r
210           NewSize := FileSize(F2);\r
211           SetFileTime(TFileRec(F2).Handle, nil, nil, @TimeStamp);\r
212         finally\r
213           CloseFile(F2);\r
214         end;\r
215       finally\r
216         CloseFile(F);\r
217       end;\r
218     except\r
219       DeleteFile(Filename);\r
220       AssignFile(F, BackupFilename);\r
221       Rename(F, Filename);\r
222       raise;\r
223     end;\r
224     Writeln(OldSize, ' -> ', NewSize, ' bytes (',\r
225       OldSize - NewSize, ' difference)');\r
226     if not KeepBackups then\r
227       if not DeleteFile(BackupFilename) then\r
228         Writeln('Warning: Couldn''t delete backup file ', BackupFilename);\r
229   finally\r
230     FreeMem(PESectionHeaders);\r
231     FreeMem(PEOptHeader);\r
232   end;\r
233 end;\r
235 var\r
236   SR: TSearchRec;\r
237   S: String;\r
238   FilesList: TStringList;\r
239   P, I: Integer;\r
240   HasFileParameter: Boolean = False;\r
241   NumFiles: Integer = 0;\r
242 label 1;\r
243 begin\r
244   try\r
245     Writeln('StripReloc v' + Version + ', Copyright (C) 2000-2003 Jordan Russell, www.jrsoftware.org');\r
246     if ParamCount = 0 then begin\r
247       Writeln('Strip relocation section from Win32 PE files');\r
248       Writeln;\r
249     1:Writeln('usage:     stripreloc [switches] filename.exe');\r
250       Writeln;\r
251       Writeln('switches:  /B  don''t create .bak backup files');\r
252       Writeln('           /C  write a valid checksum in the header (instead of zero)');\r
253       Writeln('           /F  force stripping DLLs instead of skipping them. do not use!');\r
254       Halt(1);\r
255     end;\r
256     Writeln;\r
258     for P := 1 to ParamCount do begin\r
259       S := ParamStr(P);\r
260       if S[1] <> '/' then\r
261         Continue;\r
262       Delete(S, 1, 1);\r
263       I := 1;\r
264       while I <= Length(S) do begin\r
265         case UpCase(S[I]) of\r
266           '?': goto 1;\r
267           'B': begin\r
268                  KeepBackups := False;\r
269                  if I < Length(S) then begin\r
270                    { For backward compatibility, do keep backups if the character\r
271                      following 'B' is a '+'. }\r
272                    if S[I+1] = '+' then begin\r
273                      KeepBackups := True;\r
274                      Inc(I);\r
275                    end\r
276                    else if S[I+1] = '-' then\r
277                      Inc(I);\r
278                  end;\r
279                end;\r
280           'C': begin\r
281                  ImageHlpHandle := LoadLibrary('imagehlp.dll');\r
282                  if ImageHlpHandle = 0 then begin\r
283                    Writeln('Error: Unable to load imagehlp.dll.');\r
284                    Writeln('       It is required when using the /C parameter.');\r
285                    Halt(1);\r
286                  end;\r
287                  CheckSumMappedFile := GetProcAddress(ImageHlpHandle, 'CheckSumMappedFile');\r
288                  if @CheckSumMappedFile = nil then begin\r
289                    Writeln('Error: Unable to get address of CheckSumMappedFile in imagehlp.dll.');\r
290                    Writeln('       It is required when using the /C parameter.');\r
291                    Halt(1);\r
292                  end;\r
293                  WantValidChecksum := True;\r
294                end;\r
295           'F': ForceStrip := True;\r
296         else\r
297           Writeln('Invalid parameter: /', S[I]);\r
298           Halt(1);\r
299         end;\r
300         Inc(I);\r
301       end;\r
302     end;\r
304     for P := 1 to ParamCount do begin\r
305       S := ParamStr(P);\r
306       if S[1] = '/' then\r
307         Continue;\r
308       HasFileParameter := True;\r
309       FilesList := TStringList.Create;\r
310       try\r
311         FilesList.Sorted := True;\r
312         if FindFirst(S, 0, SR) <> 0 then begin\r
313           Writeln('No files matching "', S, '" found.');\r
314           Continue;\r
315         end;\r
316         try\r
317           repeat\r
318             if CompareText(ExtractFileExt(SR.Name), '.bak') <> 0 then\r
319               FilesList.Add(ExtractFilePath(S) + SR.Name);\r
320           until FindNext(SR) <> 0;\r
321         finally\r
322           FindClose(SR);\r
323         end;\r
324         for I := 0 to FilesList.Count-1 do\r
325           Strip(FilesList[I]);\r
326         Inc(NumFiles);\r
327       finally\r
328         FilesList.Free;\r
329       end;\r
330     end;\r
331     if not HasFileParameter then\r
332       goto 1;\r
333     if NumFiles = 0 then\r
334       Halt(2);\r
335   except\r
336     on E: Exception do begin\r
337       Writeln('Fatal error: ', E.Message);\r
338       Halt(3);\r
339     end;\r
340   end;\r
341 end.\r