actors
[dd2d.git] / wadarc.d
blob3aaeac4fda54f02a5c7a66bc0c6302f60d683c08
1 module wadarc is aliced;
2 private:
4 static import core.sync.mutex;
5 import std.stdio : File;
7 import console;
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();
39 try {
40 return File(dataPath~fname);
41 } catch (Exception) {}
42 // now try wads
43 foreach_reverse (WadArchive wad; wadList) {
44 try {
45 return wad.fopen(fname);
46 } catch (Exception) {}
48 return File(fname); // to throw a correct exception, lol
50 string ofname = fname;
51 import std.path;
52 fname = fname.baseName.setExtension("");
53 foreach_reverse (WadArchive wad; wadList) {
54 try {
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);
65 auto sz = fl.size;
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 {
77 public:
78 import std.stdio : File;
80 private:
81 static struct FileInfo {
82 uint ofs;
83 uint size;
84 string path;
85 string name;
88 // for dir range
89 public static struct DirEntry {
90 string path;
91 string name;
92 uint size;
95 private:
96 File zfl;
97 ulong flstpos;
98 FileInfo[] dir;
100 public:
101 this (string fname) {
102 import std.stdio : File;
103 initLock();
104 zfl = File(fname);
105 open(zfl);
106 scope(failure) { zfl.close; zfl = zfl.init; }
109 // it now owns the file (if no exception was thrown)
110 this (File fl) {
111 initLock();
112 open(fl);
113 scope(success) zfl = fl;
116 @property auto files () {
117 static struct Range {
118 private:
119 WadArchive me;
120 uint curindex;
122 nothrow @safe @nogc:
123 this (WadArchive ame, uint aidx=0) { me = ame; curindex = aidx; }
125 public:
126 @property bool empty () const { return (curindex >= me.dir.length); }
127 @property DirEntry front () const {
128 return DirEntry(
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; }
140 return Range(this);
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) {
151 DirEntry de;
152 auto pos = fname.length;
153 while (pos > 0 && fname[pos-1] != '/') --pos;
154 if (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, "]");
158 } else {
159 de.name = cast(string)fname; // it's safe here
161 return fopen(de);
164 private:
165 void cleanup () {
166 dir.length = 0;
167 if (zfl.isOpen) zfl.close;
168 zfl = zfl.init;
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"
179 string curpath;
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";
188 switch (name) {
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";
206 default:
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";
214 return name.idup;
217 string fixPath (const(char)[] name) {
218 switch (name) {
219 case "m_therml":
220 case "m_thermm":
221 case "m_thermo":
222 case "m_thermr":
223 case "m_lscntr":
224 case "m_lsleft":
225 case "m_lsrght":
226 return "menugfx/";
227 default:
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/";
260 default:
263 return curpath;
266 import core.stdc.stdio : SEEK_CUR, SEEK_END;
267 scope(failure) cleanup();
269 uint readU32 () {
270 ubyte[4] data;
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]);
275 uint lmpofs;
276 uint lmpsize;
277 char[8] lmpname;
279 flstpos = fl.tell;
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) {
289 lmpofs = readU32();
290 lmpsize = readU32();
291 if (fl.rawRead(lmpname[]).length != 8) throw new NamedException!"WAD"("reading error");
292 int pos = 0;
293 while (pos < 8 && lmpname[pos] != 0) {
294 if (lmpname[pos] >= 'A' && lmpname[pos] <= 'Z') lmpname[pos] += 32;
295 ++pos;
297 if (pos == 0) continue;
298 auto nm = lmpname[0..pos];
299 if (nm.length > 6 && nm[$-6..$] == "_start") {
300 fixPath(nm);
301 continue;
303 if (nm.length > 4 && nm[$-4..$] == "_end") {
304 curpath = null;
305 continue;
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;
311 dir ~= FileInfo();
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;
328 void initLock () {
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;
337 import etc.c.zlib;
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);
347 if (fc is null) {
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;
354 (*fc).lock = lock;
355 GC.addRange(fc, InnerFileCookied.sizeof);
356 (*fc).xfl = zfl;
357 // open `cooked` file
358 FILE* fres = fopencookie(cast(void*)fc, "r", fcdatpkCallbacks);
359 if (fres is null) {
360 // alas
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");
365 // ok
366 fc = null;
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;
378 enum ibsize = 32768;
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
390 bool eoz;
392 @disable this (this);
394 nothrow:
395 ~this () { close(); }
397 @property bool isOpen () @safe /*@nogc*/ { return (fl !is null || xfl.isOpen); }
399 void close () {
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();
405 if (fl !is null) {
406 import core.stdc.stdio : fclose;
407 fclose(fl);
408 fl = null;
410 try { xfl.detach(); } catch (Exception) {} // it's safe to detach closed File
412 eoz = true;
415 ssize_t read (void* buf, size_t count) {
416 if (buf is null) return -1;
417 if (count == 0 || size == 0) return 0;
418 lock.lock();
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);
426 if (fl !is null) {
427 // `FILE*`
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;
432 return rd;
433 } else {
434 // std.stdio.File
435 try {
436 xfl.seek(stpos+pos, 0);
437 auto rd = xfl.rawRead(buf[0..count]);
438 pos += rd.length;
439 return (rd.length == count ? rd.length : -1);
440 } catch (Exception) {} //BAD DOGGY!
441 return -1;
446 long seek (long ofs, int whence) {
447 lock.lock();
448 scope(exit) lock.unlock();
449 if (!isOpen) return -1;
450 //TODO: overflow checks
451 switch (whence) {
452 case 0: // SEEK_SET
453 break;
454 case 1: // SEEK_CUR
455 ofs += pos;
456 break;
457 case 2: // SEEK_END
458 if (ofs > 0) ofs = 0;
459 ofs += size;
460 break;
461 default:
462 return -1;
464 if (ofs < 0) return -1;
465 if (ofs > size) ofs = size;
466 pos = cast(uint)ofs;
467 return ofs;
472 static:
473 // ////////////////////////////////////////////////////////////////////// //
474 extern(C) nothrow {
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; }
484 return res;
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;
501 return 0;
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;
509 //fc.close();
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) {}
514 //fc.xfl.__dtor();
515 free(cookie);
516 //conwriteln("closed");
517 return 0;
521 __gshared cookie_io_functions_t fcdatpkCallbacks = cookie_io_functions_t(
522 /*.read =*/ &fcdatpkRead,
523 /*.write =*/ &fcdatpkWrite,
524 /*.seek =*/ &fcdatpkSeek,
525 /*.close =*/ &fcdatpkClose,
528 static:
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);
533 if (res is null) {
534 import core.exception : onOutOfMemoryErrorNoGC;
535 onOutOfMemoryErrorNoGC();
537 res[0..len] = T.init;
538 return res[0..len];
541 void xfree(T) (ref T[] slc) {
542 if (slc.ptr !is null) {
543 import core.stdc.stdlib : free;
544 free(slc.ptr);
546 slc = null;