poormansresource.pas 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. unit poormansresource;
  2. {$mode objfpc}{$H+}
  3. // Alternative way to store data in an .exe file
  4. // doesn't use resources, but just adds stuff behind exe proper
  5. // Adapted from UPayload at http://www.delphidabbler.com/articles?article=7
  6. // This is the base class; there's apparently also a class that implements
  7. // stream-based access to the payload data
  8. interface
  9. type
  10. { TPayload }
  11. TPayload = class(TObject)
  12. private
  13. {the name of the executable file we are manipulating.}
  14. fFileName: string;
  15. {Preserves the current Pascal file mode}
  16. fOldFileMode: integer;
  17. {Pascal file descriptor that records the details of an open file.}
  18. fFile: file;
  19. {Open payload for read or write}
  20. procedure Open(Mode: integer);
  21. procedure Close;
  22. public
  23. {Creates payload object; if the Filename executable already has a payload, it reads it in.}
  24. constructor Create(const ExecutableName: string);
  25. {Whether exe has payload}
  26. function HasPayload: boolean;
  27. {Payload size in bytes}
  28. function PayloadSize: integer;
  29. {Writes payload to exe, overwrites any existing payload}
  30. procedure SetPayload(const Data; const DataSize: integer);
  31. {Saves file contents into payload, overwrites any existing payload}
  32. procedure FileIntoPayload(const FileName: string);
  33. {Retrieves payload from exe into buffer Data.
  34. Buffer must be big enough, see PayloadSize}
  35. procedure GetPayload(var Data);
  36. {Retrieves payload from exe, saves it to file.}
  37. procedure PayloadIntoFile(const FileName: string);
  38. {Removes payload from exe}
  39. procedure RemovePayload;
  40. end;
  41. implementation
  42. uses
  43. Classes, SysUtils;
  44. type
  45. TPayloadFooter = packed record
  46. WaterMark: TGUID; //magic number that identifies there is a payload attached
  47. ExeSize: longint; //size of original executable before payload added
  48. DataSize: longint; //size of payload data (excluding footer)
  49. end;
  50. const
  51. cWaterMarkGUID: TGUID =
  52. '{9FABA105-EDA8-45C3-89F4-369315A947EB}';
  53. cReadOnlyMode = 0;
  54. cReadWriteMode = 2;
  55. procedure InitFooter(out Footer: TPayloadFooter);
  56. begin
  57. FillChar(Footer, SizeOf(Footer), 0);
  58. Footer.WaterMark := cWaterMarkGUID;
  59. end;
  60. function ReadFooter(var F: file; out Footer: TPayloadFooter): boolean;
  61. var
  62. FileLen: integer;
  63. begin
  64. // Check that file is large enough for a footer!
  65. FileLen := FileSize(F);
  66. if FileLen > SizeOf(Footer) then
  67. begin
  68. // Big enough: move to start of footer and read it
  69. Seek(F, FileLen - SizeOf(Footer));
  70. BlockRead(F, Footer, SizeOf(Footer));
  71. end
  72. else
  73. // File not large enough for footer: zero it
  74. // .. this ensures watermark is invalid
  75. FillChar(Footer, SizeOf(Footer), 0);
  76. // Return if watermark is valid
  77. Result := IsEqualGUID(Footer.WaterMark, cWaterMarkGUID);
  78. end;
  79. procedure TPayload.Close;
  80. begin
  81. // close file and restores previous file mode
  82. CloseFile(fFile);
  83. FileMode := fOldFileMode;
  84. end;
  85. constructor TPayload.Create(const ExecutableName: string);
  86. begin
  87. inherited Create;
  88. fFileName := ExecutableName;
  89. end;
  90. procedure TPayload.GetPayload(var Data);
  91. var
  92. Footer: TPayloadFooter;
  93. begin
  94. // open file as read only
  95. Open(cReadOnlyMode);
  96. try
  97. // read footer
  98. if ReadFooter(fFile, Footer) and (Footer.DataSize > 0) then
  99. begin
  100. // move to end of exe code and read data
  101. Seek(fFile, Footer.ExeSize);
  102. BlockRead(fFile, Data, Footer.DataSize);
  103. end;
  104. finally
  105. // close file
  106. Close;
  107. end;
  108. end;
  109. procedure Tpayload.PayloadIntoFile(const Filename: string);
  110. var
  111. Buffer: string;
  112. begin
  113. // Fail silently if no payload
  114. if HasPayload then
  115. begin
  116. Setlength(Buffer, PayloadSize);
  117. GetPayload(Buffer[1]);
  118. //Get payload into buffer. Pass memory location, not pointer on stack
  119. if FileExists(FileName) then
  120. raise Exception.Create('Resource output file already exists.');
  121. with TFileStream.Create(FileName, fmCreate or fmOpenWrite or fmShareDenyWrite) do
  122. begin
  123. try
  124. Write(Pointer(Buffer)^, Length(Buffer));
  125. except
  126. Free;
  127. raise;
  128. end;
  129. Free;
  130. end;
  131. end;
  132. end;
  133. function TPayload.HasPayload: boolean;
  134. begin
  135. // we have a payload if size is greater than 0
  136. Result := PayloadSize > 0;
  137. end;
  138. procedure TPayload.Open(Mode: integer);
  139. begin
  140. // open file with given mode, recording current one
  141. fOldFileMode := FileMode;
  142. AssignFile(fFile, fFileName);
  143. FileMode := Mode;
  144. Reset(fFile, 1); //Open with record size 1
  145. end;
  146. function TPayload.PayloadSize: integer;
  147. var
  148. Footer: TPayloadFooter;
  149. begin
  150. // open file and assume no data
  151. Result := 0;
  152. Open(cReadOnlyMode);
  153. try
  154. // read footer and if valid return data size
  155. if ReadFooter(fFile, Footer) then
  156. Result := Footer.DataSize;
  157. finally
  158. Close;
  159. end;
  160. end;
  161. procedure TPayload.RemovePayload;
  162. var
  163. PLSize: integer;
  164. FileLen: integer;
  165. begin
  166. // get size of payload
  167. PLSize := PayloadSize;
  168. if PLSize > 0 then
  169. begin
  170. // we have payload: open file and get size
  171. Open(cReadWriteMode);
  172. FileLen := FileSize(fFile);
  173. try
  174. // seek to end of exec code and truncate file there
  175. Seek(fFile, FileLen - PLSize - SizeOf(TPayloadFooter));
  176. Truncate(fFile);
  177. finally
  178. Close;
  179. end;
  180. end;
  181. end;
  182. procedure TPayload.SetPayload(const Data; const DataSize: integer);
  183. var
  184. Footer: TPayloadFooter;
  185. begin
  186. // remove any existing payload
  187. RemovePayload;
  188. if DataSize > 0 then
  189. begin
  190. // we have some data: open file for writing
  191. Open(cReadWriteMode);
  192. try
  193. // create a new footer with required data
  194. InitFooter(Footer);
  195. Footer.ExeSize := FileSize(fFile);
  196. Footer.DataSize := DataSize;
  197. // write data and footer at end of exe code
  198. Seek(fFile, Footer.ExeSize);
  199. BlockWrite(fFile, Data, DataSize);
  200. BlockWrite(fFile, Footer, SizeOf(Footer));
  201. finally
  202. Close;
  203. end;
  204. end;
  205. end;
  206. procedure TPayload.FileIntoPayload(const FileName: string);
  207. var
  208. Filesize: integer;
  209. Buffer: string;
  210. begin
  211. if FileExists(FileName) = False then
  212. begin
  213. raise Exception.Create('File not found trying to write file to resource.');
  214. end;
  215. with TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite) do
  216. begin
  217. try
  218. FileSize := Size;
  219. SetLength(Buffer, FileSize);
  220. Read(Pointer(Buffer)^, Size);
  221. // Write/overwrite resource:
  222. SetPayload(Buffer[1], Length(Buffer));
  223. except
  224. Free;
  225. Buffer := ''; // Deallocates memory
  226. raise;
  227. end;
  228. Free;
  229. end;
  230. end;
  231. end.