1 module wadarc
is aliced
;
4 static import core
.sync
.mutex
;
5 import std
.stdio
: File
;
10 // ////////////////////////////////////////////////////////////////////////// //
11 public __gshared WadArchive
[] wadList
;
12 __gshared core
.sync
.mutex
.Mutex glock
;
13 __gshared string dataPath
;
16 shared static this () {
17 glock
= new core
.sync
.mutex
.Mutex
;
21 public void setDataPath (const(char)[] path
) {
22 while (path
.length
> 1 && path
[0] == '/' && path
[1] == '/') path
= path
[1..$];
23 if (path
!= "/") while (path
.length
&& path
[$-1] == '/') path
= path
[0..$-1];
24 dataPath
= path
.idup
~"/";
28 public void addWad (string fname
) {
29 if (glock
!is null) glock
.lock();
30 scope(exit
) if (glock
!is null) glock
.unlock();
31 conwriteln("adding '", fname
, "'...");
32 wadList
~= new WadArchive(fname
);
36 public File
openFile (string fname
) {
37 if (glock
!is null) glock
.lock();
38 scope(exit
) if (glock
!is null) glock
.unlock();
40 return File(dataPath
~fname
);
41 } catch (Exception
) {}
43 foreach_reverse (WadArchive wad
; wadList
) {
45 return wad
.fopen(fname
);
46 } catch (Exception
) {}
48 return File(fname
); // to throw a correct exception, lol
50 string ofname = fname;
52 fname = fname.baseName.setExtension("");
53 foreach_reverse (WadArchive wad; wadList) {
55 return wad.fopen(fname);
56 } catch (Exception) {}
58 return File(ofname); // to throw a correct exception, lol
63 public string
loadTextFile (string fname
) {
64 auto fl
= openFile(fname
);
66 if (sz
< 0 || sz
> 1024*1024) throw new Exception("invalid text file size: '"~fname
~"'");
67 if (sz
== 0) return null;
68 auto res
= new char[](cast(uint)sz
);
69 if (fl
.rawRead(res
[]).length
!= res
.length
) throw new Exception("error reading text file '"~fname
~"'");
70 import std
.exception
: assumeUnique
;
71 return res
.assumeUnique
;
75 // ////////////////////////////////////////////////////////////////////////// //
76 final class WadArchive
{
78 import std
.stdio
: File
;
81 static struct FileInfo
{
89 public static struct DirEntry
{
101 this (string fname
) {
102 import std
.stdio
: File
;
106 scope(failure
) { zfl
.close
; zfl
= zfl
.init
; }
109 // it now owns the file (if no exception was thrown)
113 scope(success
) zfl
= fl
;
116 @property auto files () {
117 static struct Range
{
123 this (WadArchive ame
, uint aidx
=0) { me
= ame
; curindex
= aidx
; }
126 @property bool empty () const { return (curindex
>= me
.dir
.length
); }
127 @property DirEntry
front () const {
129 (curindex
< me
.dir
.length ? me
.dir
[cast(usize
)curindex
].path
: null),
130 (curindex
< me
.dir
.length ? me
.dir
[cast(usize
)curindex
].name
: null),
131 (curindex
< me
.dir
.length ? me
.dir
[cast(usize
)curindex
].size
: 0));
133 @property Range
save () { return Range(me
, curindex
); }
134 void popFront () { if (curindex
< me
.dir
.length
) ++curindex
; }
135 @property uint length () const { return me
.dir
.length
; }
136 @property uint position () const { return curindex
; } // current position
137 @property void position (uint np
) { curindex
= np
; }
138 void rewind () { curindex
= 0; }
143 File
fopen (ref in DirEntry
de) {
144 foreach (immutable idx
, ref fi
; dir
) {
145 if (fi
.path
== de.path
&& fi
.name
== de.name
) return openDirEntry(idx
, fi
.name
);
147 throw new NamedException
!"WadArchive"("file not found");
150 File
fopen (const(char)[] fname
) {
152 auto pos
= fname
.length
;
153 while (pos
> 0 && fname
[pos
-1] != '/') --pos
;
155 de.path
= cast(string
)fname
[0..pos
]; // it's safe here
156 de.name
= cast(string
)fname
[pos
..$]; // it's safe here
157 //conwriteln("[", de.path, "] [", de.name, "]");
159 de.name
= cast(string
)fname
; // it's safe here
167 if (zfl
.isOpen
) zfl
.close
;
171 void open (File fl
) {
172 import std
.uni
: icmp
;
174 immutable string
[$] msn
= [
175 "SARG", "TROO", "POSS", "SPOS", "CYBR", "CPOS", "BOSS", "BOS2", "HEAD", "SKUL",
176 "PAIN", "SPID", "BSPI", "FATT", "SKEL", "VILE", "FISH", "BAR1", "ROBO", "PLAY"
181 string
fixName (const(char)[] name
) {
182 if (name
.length
>= 4 && name
[0..4] == "stcf") return name
.idup
~".vga";
183 if (name
.length
>= 4 && name
[0..4] == "stbf") return name
.idup
~".vga";
184 if (name
.length
>= 5 && name
[0..5] == "winum") return name
.idup
~".vga";
185 if (name
== "wicolon" || name
== "wiminus" || name
== "wipcnt") return name
.idup
~".vga";
186 if (name
.length
> 3 && name
[0..3] == "map") return name
.idup
~".d2m";
187 if (name
== "playpal") return "playpal.pal";
189 case "endoom": return "endoom.b80";
190 case "endanim": return "endanim.a8";
191 case "end2anim": return "end2anim.a8";
192 case "darts": return "darts.a8";
193 case "colormap": return "colormap.tbl";
194 case "mixmap": return "mixmap.tbl";
195 case "titlepic": return "titlepic.vga";
196 case "interpic": return "interpic.vga";
197 case "cd1pic": return "cd1pic.vga";
198 case "endpic": return "endpic.vraw";
199 case "m_therml": return "m_therml.vga";
200 case "m_thermm": return "m_thermm.vga";
201 case "m_thermo": return "m_thermo.vga";
202 case "m_thermr": return "m_thermr.vga";
203 case "m_lscntr": return "m_lscntr.vga";
204 case "m_lsleft": return "m_lsleft.vga";
205 case "m_lsrght": return "m_lsrght.vga";
209 import std
.algorithm
;
210 if (curpath
== "sounds/") return name
.idup
~".snd";
211 if (curpath
== "music/") return (name
.length
> 3 && name
[0..3] == "dmi" ? name
.idup
~".dmi" : name
.idup
~".dmm");
212 if (curpath
== "tilegfx/") return name
.idup
~".vga";
213 if (curpath
.startsWith("sprites/")) return name
.idup
~".vga";
217 string
fixPath (const(char)[] name
) {
229 if (name
.length
>= 4 && name
[0..4] == "stcf") return "fonts/stcf/";
230 if (name
.length
>= 4 && name
[0..4] == "stbf") return "fonts/stbf/";
231 if (name
.length
>= 5 && name
[0..5] == "winum") return "fonts/winum/";
232 if (name
== "wicolon" || name
== "wiminus" || name
== "wipcnt") return "fonts/winum/";
233 if (name
.length
> 3 && name
[0..3] == "map") return "maps/";
234 if (name
== "d_start") curpath
= "sounds/";
235 if (name
== "m_start") curpath
= "music/";
236 if (name
== "w_start") curpath
= "tilegfx/";
237 if (name
== "s_start") curpath
= "sprites/";
238 if (curpath
== "sprites/" && name
.length
> 4) {
239 switch (name
[0..4]) {
240 case "sarg": return "sprites/monsters/demon/";
241 case "troo": return "sprites/monsters/imp/";
242 case "poss": return "sprites/monsters/zombie/";
243 case "spos": return "sprites/monsters/sergeant/";
244 case "cybr": return "sprites/monsters/cyberdemon/";
245 case "cpos": return "sprites/monsters/chaingunner/";
246 case "boss": return "sprites/monsters/baron/";
247 case "bos2": return "sprites/monsters/knight/";
248 case "head": return "sprites/monsters/cacodemon/";
249 case "skul": return "sprites/monsters/soul/";
250 case "pain": return "sprites/monsters/painel/";
251 case "spid": return "sprites/monsters/mastermind/";
252 case "bspi": return "sprites/monsters/arachnotron/";
253 case "fatt": return "sprites/monsters/mancubus/";
254 case "skel": return "sprites/monsters/revenant/";
255 case "vile": return "sprites/monsters/archvile/";
256 case "fish": return "sprites/monsters/fish/";
257 case "bar1": return "sprites/monsters/barrel/";
258 case "robo": return "sprites/monsters/robot/";
259 case "play": return "sprites/monsters/player/";
266 import core
.stdc
.stdio
: SEEK_CUR
, SEEK_END
;
267 scope(failure
) cleanup();
271 if (fl
.rawRead(data
[]).length
!= data
.length
) throw new NamedException
!"WadArchive"("reading error");
272 return cast(uint)(data
[0]+0x100*data
[1]+0x10000*data
[2]+0x1000000*data
[3]);
281 if (fl
.rawRead(lmpname
[0..4]).length
!= 4) throw new NamedException
!"WadArchive"("reading error");
282 if (lmpname
[0..4] != "PWAD" && lmpname
[0..4] != "IWAD") throw new NamedException
!"WadArchive"("not a WAD file");
283 auto count
= readU32();
284 auto dofs
= readU32();
285 if (count
== 0) return;
286 if (dofs
< 3*4 || count
== uint.max
) throw new NamedException
!"WadArchive"("invalid WAD file");
287 fl
.seek(dofs
-3*4, SEEK_CUR
);
288 while (count
-- > 0) {
291 if (fl
.rawRead(lmpname
[]).length
!= 8) throw new NamedException
!"WAD"("reading error");
293 while (pos
< 8 && lmpname
[pos
] != 0) {
294 if (lmpname
[pos
] >= 'A' && lmpname
[pos
] <= 'Z') lmpname
[pos
] += 32;
297 if (pos
== 0) continue;
298 auto nm
= lmpname
[0..pos
];
299 if (nm
.length
> 6 && nm
[$-6..$] == "_start") {
303 if (nm
.length
> 4 && nm
[$-4..$] == "_end") {
307 uint fidx
= uint.max
;
308 foreach (immutable idx
, ref de; dir
) if (de.name
== lmpname
[0..pos
]) { fidx
= cast(uint)idx
; break; }
309 if (fidx
>= dir
.length
) {
310 fidx
= cast(uint)dir
.length
;
313 dir
[fidx
].ofs
= lmpofs
;
314 dir
[fidx
].size
= lmpsize
;
315 dir
[fidx
].name
= fixName(nm
);
316 dir
[fidx
].path
= fixPath(nm
);
317 //debug conwriteln(dir[fidx].path, " : ", dir[fidx].name);
319 debug conwriteln(dir
.length
, " files found");
323 // ////////////////////////////////////////////////////////////////////// //
324 static import core
.sync
.mutex
;
326 core
.sync
.mutex
.Mutex
lock;
329 lock = new core
.sync
.mutex
.Mutex
;
332 auto openDirEntry (uint idx
, string filename
) {
333 import core
.sys
.linux
.stdio
: fopencookie
;
334 import core
.stdc
.stdio
: FILE
;
335 import core
.stdc
.stdio
: fopen
, fclose
;
336 import core
.stdc
.stdlib
: calloc
, free
;
338 import std
.internal
.cstring
: tempCString
;
339 import core
.memory
: GC
;
341 if (!zfl
.isOpen
) throw new NamedException
!"WadArchive"("archive wasn't opened");
342 if (idx
>= dir
.length
) throw new NamedException
!"WadArchive"("invalid dir index");
344 // create cookied `FILE*`
345 auto fc
= cast(InnerFileCookied
*)calloc(1, InnerFileCookied
.sizeof
);
346 scope(exit
) if (fc
!is null) free(fc
);
348 import core
.exception
: onOutOfMemoryErrorNoGC
;
349 onOutOfMemoryErrorNoGC();
351 (*fc
) = InnerFileCookied
.init
;
352 (*fc
).stpos
= flstpos
+dir
[idx
].ofs
;
353 (*fc
).size
= cast(uint)dir
[idx
].size
;
355 GC
.addRange(fc
, InnerFileCookied
.sizeof
);
357 // open `cooked` file
358 FILE
* fres
= fopencookie(cast(void*)fc
, "r", fcdatpkCallbacks
);
361 if ((*fc
).fl
!is null) fclose((*fc
).fl
);
362 try { (*fc
).xfl
.detach(); } catch (Exception
) {}
363 throw new NamedException
!"WadArchive"("can't open cookied file");
367 return File(fres
, filename
);
371 // ////////////////////////////////////////////////////////////////////// //
372 // "inner" file processor; processes both packed and unpacked files
373 // can be used as normal disk file processor too
374 static struct InnerFileCookied
{
375 private import core
.sys
.posix
.sys
.types
: ssize_t
, off64_t
= off_t
;
376 private import core
.stdc
.stdio
: FILE
;
380 core
.sync
.mutex
.Mutex
lock;
381 // note that either one of `fl` or `xfl` must be opened and operational
382 FILE
* fl
; // disk file, can be `null`
383 File xfl
; // disk file, can be closed
384 long stpos
; // starting position
385 uint size
; // unpacked size
386 uint pos
; // current file position
387 //uint prpos; // previous file position
388 //uint pkpos; // current position in DAT
389 //ubyte[] pkb; // packed data
392 @disable this (this);
395 ~this () { close(); }
397 @property bool isOpen () @safe /*@nogc*/ { return (fl
!is null || xfl
.isOpen
); }
400 import core
.memory
: GC
;
401 import core
.stdc
.stdlib
: free
;
403 if (lock !is null) lock.lock();
404 scope(exit
) if (lock !is null) lock.unlock();
406 import core
.stdc
.stdio
: fclose
;
410 try { xfl
.detach(); } catch (Exception
) {} // it's safe to detach closed File
415 ssize_t
read (void* buf
, size_t count
) {
416 if (buf
is null) return -1;
417 if (count
== 0 || size
== 0) return 0;
419 scope(exit
) lock.unlock();
420 if (!isOpen
) return -1; // read error
421 if (pos
>= size
) return 0; // EOF
423 import core
.stdc
.stdio
: ferror
, fread
;
424 import core
.sys
.posix
.stdio
: fseeko
;
425 if (size
-pos
< count
) count
= cast(size_t
)(size
-pos
);
428 if (fseeko(fl
, stpos
+pos
, 0) < 0) return -1;
429 auto rd
= fread(buf
, 1, count
, fl
);
430 if (rd
!= count
&& (rd
< 0 ||
ferror(fl
))) rd
= -1;
431 if (rd
> 0) pos
+= rd
;
436 xfl
.seek(stpos
+pos
, 0);
437 auto rd
= xfl
.rawRead(buf
[0..count
]);
439 return (rd
.length
== count ? rd
.length
: -1);
440 } catch (Exception
) {} //BAD DOGGY!
446 long seek (long ofs
, int whence
) {
448 scope(exit
) lock.unlock();
449 if (!isOpen
) return -1;
450 //TODO: overflow checks
458 if (ofs
> 0) ofs
= 0;
464 if (ofs
< 0) return -1;
465 if (ofs
> size
) ofs
= size
;
473 // ////////////////////////////////////////////////////////////////////// //
475 import core
.sys
.linux
.stdio
: cookie_io_functions_t
;
476 import core
.sys
.posix
.sys
.types
: ssize_t
, off64_t
= off_t
;
478 ssize_t
fcdatpkRead (void* cookie
, char* buf
, size_t count
) {
479 //conwriteln("reading ", count, " bytes");
480 import core
.stdc
.errno
;
481 auto fc
= cast(InnerFileCookied
*)cookie
;
482 auto res
= fc
.read(buf
, count
);
483 if (res
< 0) { errno
= EIO
; return -1; }
487 ssize_t
fcdatpkWrite (void* cookie
, const(char)* buf
, size_t count
) {
488 //conwriteln("writing ", count, " bytes");
489 import core
.stdc
.errno
;
490 errno
= EIO
; //FIXME: find better code
491 return 0; // error; write should not return `-1`
494 int fcdatpkSeek (void* cookie
, off64_t
* offset
, int whence
) {
495 //conwriteln("seeking ", *offset, " bytes, whence=", whence);
496 import core
.stdc
.errno
;
497 auto fc
= cast(InnerFileCookied
*)cookie
;
498 auto res
= fc
.seek(*offset
, whence
);
499 if (res
< 0) { errno
= EIO
; return -1; }
500 *offset
= cast(off64_t
)res
;
504 int fcdatpkClose (void* cookie
) {
505 import core
.memory
: GC
;
506 import core
.stdc
.stdlib
: free
;
507 //conwriteln("closing");
508 auto fc
= cast(InnerFileCookied
*)cookie
;
510 GC
.removeRange(cookie
);
511 try { fc
.__dtor(); } catch (Exception
) {}
512 // no need to run finalizers, we SHOULD NOT have any
513 //try { GC.runFinalizers(cookie[0..InnerFileCookied.sizeof]); } catch (Exception) {}
516 //conwriteln("closed");
521 __gshared cookie_io_functions_t fcdatpkCallbacks
= cookie_io_functions_t(
522 /*.read =*/ &fcdatpkRead
,
523 /*.write =*/ &fcdatpkWrite
,
524 /*.seek =*/ &fcdatpkSeek
,
525 /*.close =*/ &fcdatpkClose
,
529 T
[] xalloc(T
) (size_t len
) {
530 import core
.stdc
.stdlib
: malloc
;
531 if (len
< 1) return null;
532 auto res
= cast(T
*)malloc(len
*T
.sizeof
);
534 import core
.exception
: onOutOfMemoryErrorNoGC
;
535 onOutOfMemoryErrorNoGC();
537 res
[0..len
] = T
.init
;
541 void xfree(T
) (ref T
[] slc
) {
542 if (slc
.ptr
!is null) {
543 import core
.stdc
.stdlib
: free
;