DEADSOFTWARE

wadcvt: no more zipper, we can create zip manually!
[d2df-sdl.git] / src / sfs / wadcvt.dpr
1 {$MODE OBJFPC}
2 {$IFDEF WINDOWS}
3 {$APPTYPE CONSOLE}
4 {$ENDIF}
5 program __wadcvt__;
7 uses
8 SysUtils,
9 Classes,
10 utils in '../shared/utils.pas',
11 xstreams in '../shared/xstreams.pas',
12 crc,
13 sfs,
14 sfsPlainFS,
15 sfsZipFS,
16 paszlib;
19 procedure processed (count: Cardinal);
20 begin
21 //writeln(' read ', count, ' bytes');
22 end;
25 // returs crc
26 function zpack (ds: TStream; ss: TStream): LongWord;
27 const
28 IBSize = 65536;
29 OBSize = 65536;
30 var
31 zst: TZStream;
32 ib, ob: PByte;
33 ibpos: Cardinal;
34 err: Integer;
35 rd, f: Integer;
36 eof: Boolean;
37 crc: LongWord;
38 begin
39 result := 0;
40 crc := crc32(0, nil, 0);
41 GetMem(ib, IBSize);
42 GetMem(ob, OBSize);
43 try
44 zst.next_out := ob;
45 zst.avail_out := OBSize;
46 err := deflateInit2(zst, Z_BEST_COMPRESSION, Z_DEFLATED, -15, 9, 0);
47 if err <> Z_OK then raise Exception.Create(zerror(err));
48 try
49 ibpos := 0;
50 zst.next_out := ob;
51 zst.avail_out := OBSize;
52 eof := false;
53 while true do
54 begin
55 while not eof and (ibpos < IBSize) do
56 begin
57 rd := ss.read((ib+ibpos)^, IBSize-ibpos);
58 if rd < 0 then raise Exception.Create('reading error');
59 eof := (rd <> IBSize-ibpos);
60 if rd <> 0 then begin crc := crc32(crc, Pointer(ib+ibpos), rd); result := crc; end;
61 Inc(ibpos, rd);
62 if rd <> 0 then processed(rd);
63 end;
64 zst.next_in := ib;
65 zst.avail_in := ibpos;
66 if eof then break;
67 err := deflate(zst, Z_NO_FLUSH);
68 if (err <> Z_OK) and (err <> Z_STREAM_END) then raise Exception.Create(zerror(err));
69 if zst.avail_out < OBSize then
70 begin
71 writeln(' written ', OBSize-zst.avail_out, ' bytes');
72 ds.writeBuffer(ob^, OBSize-zst.avail_out);
73 zst.next_out := ob;
74 zst.avail_out := OBSize;
75 end;
76 // shift input buffer
77 if zst.avail_in < ibpos then
78 begin
79 rd := 0;
80 for f := ibpos-zst.avail_in to ibpos-1 do
81 begin
82 ib[rd] := ib[f];
83 Inc(rd);
84 end;
85 ibpos := rd;
86 //writeln(' rd: ', zst.avail_in);
87 end;
88 end;
89 // pack leftovers
90 while zst.avail_in > 0 do
91 begin
92 err := deflate(zst, Z_NO_FLUSH);
93 if (err <> Z_OK) and (err <> Z_STREAM_END) then raise Exception.Create(zerror(err));
94 if zst.avail_out < OBSize then
95 begin
96 //writeln(' written ', OBSize-zst.avail_out, ' bytes');
97 ds.writeBuffer(ob^, OBSize-zst.avail_out);
98 zst.next_out := ob;
99 zst.avail_out := OBSize;
100 end;
101 end;
102 // stream compressed, flush zstream
103 while true do
104 begin
105 zst.avail_in := 0;
106 zst.next_out := ob;
107 zst.avail_out := OBSize;
108 err := deflate(zst, Z_FINISH);
109 if zst.avail_out < OBSize then
110 begin
111 //writeln(' written ', OBSize-zst.avail_out, ' bytes');
112 ds.writeBuffer(ob^, OBSize-zst.avail_out);
113 end;
114 if err <> Z_OK then break;
115 end;
116 // succesfully flushed?
117 if (err <> Z_STREAM_END) then raise Exception.Create(zerror(err));
118 finally
119 deflateEnd(zst);
120 end;
121 finally
122 FreeMem(ob);
123 FreeMem(ib);
124 end;
125 end;
129 procedure TProg.putStr (const s: string; newline: Boolean=false);
130 begin
131 write(#13, s);
132 while lastlen > length(s) do
133 begin
134 write(' ');
135 Dec(lastlen);
136 end;
137 if newline then
138 begin
139 writeln;
140 lastlen := 0;
141 end
142 else
143 begin
144 lastlen := length(s);
145 end;
146 end;
148 procedure TProg.onProgress (sender: TObject; const percent: double);
149 var
150 prc: Integer;
151 begin
152 prc := trunc(percent*100.0);
153 putStr(Format('compressing %-33s %3d%%', [lastname, prc]));
154 end;
156 procedure TProg.onFileStart (sender: TObject; const fileName: string);
157 begin
158 lastname := fileName;
159 putStr(Format('compressing %-33s %3d%%', [lastname, 0]));
160 end;
162 procedure TProg.onFileEnd (sender: TObject; const ratio: double);
163 begin
164 putStr(Format('compressed %-33s %f', [lastname, ratio]), true);
165 end;
169 // returns new file name
170 function detectExt (fpath, fname: string; fs: TStream): string;
171 var
172 buf: PChar;
173 buflen: Integer;
174 f: Integer;
175 st: string[24];
176 begin
177 result := fname;
178 if length(ExtractFileExt(fname)) <> 0 then exit;
179 if fs.size < 16 then exit;
180 buflen := Integer(fs.size);
181 GetMem(buf, buflen);
182 try
183 fs.ReadBuffer(buf^, buflen);
184 // xm
185 Move(buf^, (PChar(@st[1]))^, 16);
186 st[0] := #16;
187 if (st = 'Extended Module:') then
188 begin
189 result := result+'.xm';
190 exit;
191 end;
192 if (buf[0] = 'D') and (buf[1] = 'F') and (buf[2] = 'W') and
193 (buf[3] = 'A') and (buf[4] = 'D') and (buf[5] = #$1) then
194 begin
195 result := result+'.wad';
196 exit;
197 end;
198 if (buf[0] = 'M') and (buf[1] = 'A') and (buf[2] = 'P') and (buf[3] = #$1) then
199 begin
200 result := result+'.dfmap';
201 exit;
202 end;
203 if (buf[0] = 'M') and (buf[1] = 'T') and (buf[2] = 'h') and (buf[3] = 'd') then
204 begin
205 result := result+'.mid';
206 exit;
207 end;
208 if (buf[0] = 'R') and (buf[1] = 'I') and (buf[2] = 'F') and (buf[3] = 'F') and
209 (buf[8] = 'W') and (buf[9] = 'A') and (buf[10] = 'V') and (buf[11] = 'E') then
210 begin
211 result := result+'.wav';
212 exit;
213 end;
214 // mp3 (stupid hack)
215 for f := 0 to 128-6 do
216 begin
217 if (buf[f+0] = #$4) and (buf[f+1] = 'L') and
218 (buf[f+2] = 'A') and (buf[f+3] = 'M') and
219 (buf[f+4] = 'E') and (buf[f+5] = '3') then
220 begin
221 result := result+'.mp3';
222 exit;
223 end;
224 end;
225 // more mp3 hacks
226 if (buf[0] = 'I') and (buf[1] = 'D') and (buf[2] = '3') and (buf[3] <= #4) then
227 begin
228 result := result+'.mp3';
229 exit;
230 end;
231 if buflen > 128 then
232 begin
233 if (buf[buflen-128] = 'T') and (buf[buflen-127] = 'A') and (buf[buflen-126] = 'G') then
234 begin
235 result := result+'.mp3';
236 exit;
237 end;
238 end;
239 // targa (stupid hack; this "signature" is not required by specs)
240 if buflen >= 18 then
241 begin
242 Move((buf+buflen-18)^, (PChar(@st[1]))^, 16);
243 st[0] := #16;
244 if st = 'TRUEVISION-XFILE' then
245 begin
246 result := result+'.tga';
247 exit;
248 end;
249 end;
250 finally
251 FreeMem(buf);
252 end;
253 end;
256 type
257 TFileInfo = class
258 public
259 name: AnsiString;
260 pkofs: Int64; // offset of file header
261 size: Int64;
262 pksize: Int64;
263 crc: LongWord;
264 method: Word;
266 constructor Create ();
267 end;
269 constructor TFileInfo.Create ();
270 begin
271 name := '';
272 pkofs := 0;
273 size := 0;
274 pksize := 0;
275 crc := crc32(0, nil, 0);
276 method := 0;
277 end;
280 function ZipOne (ds: TStream; fname: string; st: TStream): TFileInfo;
281 var
282 oldofs, nfoofs, pkdpos: Int64;
283 sign: packed array [0..3] of Char;
284 begin
285 result := TFileInfo.Create();
286 result.pkofs := ds.position;
287 result.size := st.size;
288 result.name := fname;
289 if result.size > 0 then result.method := 8 else result.method := 0;
290 // write local header
291 sign := 'PK'#3#4;
292 ds.writeBuffer(sign, 4);
293 writeInt(ds, Word($10)); // version to extract
294 writeInt(ds, Word(0)); // flags
295 writeInt(ds, Word(result.method)); // compression method
296 writeInt(ds, Word(0)); // file time
297 writeInt(ds, Word(0)); // file date
298 nfoofs := ds.position;
299 writeInt(ds, LongWord(result.crc)); // crc32
300 writeInt(ds, LongWord(result.pksize)); // packed size
301 writeInt(ds, LongWord(result.size)); // unpacked size
302 writeInt(ds, Word(length(fname))); // name length
303 writeInt(ds, Word(0)); // extra field length
304 ds.writeBuffer(fname[1], length(fname));
305 // now write packed data
306 if result.size > 0 then
307 begin
308 pkdpos := ds.position;
309 st.position := 0;
310 result.crc := zpack(ds, st);
311 result.pksize := ds.position-pkdpos;
312 // fix header
313 oldofs := ds.position;
314 ds.position := nfoofs;
315 writeInt(ds, LongWord(result.crc)); // crc32
316 writeInt(ds, LongWord(result.pksize)); // crc32
317 ds.position := oldofs;
318 end;
319 end;
322 procedure writeCentralDir (ds: TStream; files: array of TFileInfo);
323 var
324 cdofs, cdend: Int64;
325 sign: packed array [0..3] of Char;
326 f: Integer;
327 begin
328 cdofs := ds.position;
329 for f := 0 to high(files) do
330 begin
331 sign := 'PK'#1#2;
332 ds.writeBuffer(sign, 4);
333 writeInt(ds, Word($10)); // version made by
334 writeInt(ds, Word($10)); // version to extract
335 writeInt(ds, Word(0)); // flags
336 writeInt(ds, Word(files[f].method)); // compression method
337 writeInt(ds, Word(0)); // file time
338 writeInt(ds, Word(0)); // file date
339 writeInt(ds, LongWord(files[f].crc));
340 writeInt(ds, LongWord(files[f].pksize));
341 writeInt(ds, LongWord(files[f].size));
342 writeInt(ds, Word(length(files[f].name))); // name length
343 writeInt(ds, Word(0)); // extra field length
344 writeInt(ds, Word(0)); // comment length
345 writeInt(ds, Word(0)); // disk start
346 writeInt(ds, Word(0)); // internal attributes
347 writeInt(ds, LongWord(0)); // external attributes
348 writeInt(ds, LongWord(files[f].pkofs)); // header offset
349 ds.writeBuffer(files[f].name[1], length(files[f].name));
350 end;
351 cdend := ds.position;
352 // write end of central dir
353 sign := 'PK'#5#6;
354 ds.writeBuffer(sign, 4);
355 writeInt(ds, Word(0)); // disk number
356 writeInt(ds, Word(0)); // disk with central dir
357 writeInt(ds, Word(length(files))); // number of files on this dist
358 writeInt(ds, Word(length(files))); // number of files total
359 writeInt(ds, LongWord(cdend-cdofs)); // size of central directory
360 writeInt(ds, LongWord(cdofs)); // central directory offset
361 writeInt(ds, Word(0)); // archive comment length
362 end;
365 var
366 fs, fo: TStream;
367 fl: TSFSFileList;
368 f: Integer;
369 infname: string;
370 outfname: string;
371 dvfn: string;
372 newname: string;
373 files: array of TFileInfo;
374 nfo: TFileInfo;
375 begin
376 if ParamCount() < 1 then
377 begin
378 WriteLn('usage: wadcvt file.wad');
379 Halt(1);
380 end;
382 infname := ParamStr(1);
383 if not StrEquCI1251(ExtractFileExt(infname), '.wad') and not StrEquCI1251(ExtractFileExt(infname), '.dfwad') then
384 begin
385 writeln('wtf?!');
386 Halt(1);
387 end;
389 if ParamCount() > 1 then
390 begin
391 outfname := ParamStr(2);
392 end
393 else
394 begin
395 outfname := ChangeFileExt(infname, '.pk3');
396 end;
398 if not SFSAddDataFile(infname) then begin WriteLn('shit!'); Halt(1); end;
399 dvfn := SFSGetLastVirtualName(infname);
401 files := nil;
403 fl := SFSFileList(dvfn);
404 if fl = nil then
405 begin
406 writeln('wtf?!');
407 Halt(1);
408 end;
410 fo := TFileStream.Create(outfname, fmCreate);
411 try
412 for f := 0 to fl.Count-1 do
413 begin
414 if length(fl[f].fName) = 0 then continue;
415 fs := SFSFileOpen(dvfn+'::'+fl[f].fPath+fl[f].fName);
416 newname := detectExt(fl[f].fPath, fl[f].fName, fs);
417 fs.position := 0;
418 writeln('[', f+1, '/', fl.Count, ']: ', fl[f].fPath+newname, ' ', fs.size);
419 //ZEntries.AddFileEntry(fs, fl[f].fPath+newname);
420 nfo := ZipOne(fo, fl[f].fPath+fl[f].fName, fs);
421 SetLength(files, length(files)+1);
422 files[high(files)] := nfo;
423 end;
424 writeCentralDir(fo, files);
425 except
426 fo.Free();
427 fo := nil;
428 DeleteFile(outfname);
429 end;
430 if fo <> nil then fo.Free();
431 end.