1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module iv
.vfs
.arc
.bsa
/*is aliced*/;
20 import iv
.vfs
.types
: Seek
;
25 import iv
.vfs
.arc
.internal
;
28 // ////////////////////////////////////////////////////////////////////////// //
29 mixin(VFSSimpleArchiveDetectorMixin
!"BSA");
32 // ////////////////////////////////////////////////////////////////////////// //
33 public final class VFSDriverBSA
: VFSDriver
{
34 mixin VFSSimpleArchiveDriverMixin
;
37 static struct FileInfo
{
39 long ofs
; // offset in archive
40 string name
; // with path
45 /** query various file properties; driver-specific.
46 * properties of interest:
47 * "packed" -- is file packed?
48 * "pksize" -- packed file size (for archives)
49 * "offset" -- offset in wad
50 * "size" -- file size (so we can get size without opening the file)
52 public override VFSVariant
stat (usize idx
, const(char)[] propname
) {
53 if (idx
>= dir
.length
) return VFSVariant();
54 if (propname
== "arcname") return VFSVariant("bsa");
55 if (propname
== "packed") return VFSVariant(dir
[idx
].packed
);
56 if (propname
== "pksize") return VFSVariant(dir
[idx
].pksize
);
57 if (propname
== "offset") return VFSVariant(dir
[idx
].ofs
);
58 if (propname
== "size") return VFSVariant(dir
[idx
].size
);
62 VFile
wrap (usize idx
) {
63 //{ import core.stdc.stdio; printf("[%.*s]; packed=%s; pksize=%u; size=%u; ofs=0x%08x\n", cast(uint)dir[idx].name.length, dir[idx].name.ptr, (dir[idx].packed ? "tan".ptr : "ona".ptr) , cast(uint)dir[idx].pksize, cast(uint)dir[idx].size, cast(uint)dir[idx].ofs); }
64 if (dir
[idx
].packed
) return wrapZLibStreamRO(st
, VFSZLibMode
.ZLib
, dir
[idx
].size
, dir
[idx
].ofs
, dir
[idx
].pksize
, dir
[idx
].name
);
65 return wrapStreamRO(st
, dir
[idx
].ofs
, dir
[idx
].size
, dir
[idx
].name
);
69 void open (VFile fl, const(char)[] prefixpath) {
71 open1(fl, prefixpath);
72 } catch (Exception e) {
73 import core.stdc.stdio;
75 printf("\n-----------------\n%.*s\n", cast(uint)s.length, s.ptr);
81 void open (VFile fl
, const(char)[] prefixpath
) {
82 string
loadBCStr(bool asdir
) () {
83 auto len
= fl
.readNum
!ubyte;
84 if (len
== 0) return null; // oops
85 auto res
= new char[](len
+1); // for '/'
86 fl
.rawReadExact(res
[0..$-1]);
87 res
[$-1] = 0; // for now
88 foreach (immutable idx
, ref char ch
; res
) {
92 ch
= '/'; // 'cause dir name should end with slash
99 //if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
100 if (ch
== '\\') ch
= '/'; // fuck off, shitdoze
102 while (res
.length
> 1 && res
[$-1] == '/' && res
[$-2] == '/') res
= res
[0..$-1];
103 if (res
== "/") return null; // just in case
104 return cast(string
)res
; // it is safe to cast here
107 ulong flsize
= fl
.size
;
108 if (flsize
> 0xffff_ffffu
) throw new /*VFSNamedException!"BSAArchive"*/VFSExceptionArc("file too big");
110 fl
.rawReadExact(sign
[]);
111 if (sign
!= "BSA\x00") throw new /*VFSNamedException!"BSAArchive"*/VFSExceptionArc("not a BSA file");
112 //{ import core.stdc.stdio; printf("TRYING BSA! [%.*s]\n", cast(uint)fl.name.length, fl.name.ptr); }
113 auto ver
= fl
.readNum
!uint;
114 if (ver
!= 0x67 && ver
!= 0x68) throw new /*VFSNamedException!"BSAArchive"*/VFSExceptionArc("invalid BSA version");
115 auto fatofs
= fl
.readNum
!uint;
116 auto flags
= fl
.readNum
!uint;
119 if (flags
&0x01) writeln(" has names for directories");
120 if (flags
&0x02) writeln(" has names for files");
121 if (flags
&0x04) writeln(" compressed by default");
122 if (flags
&0x40) writeln(" shitbox archive");
124 if ((flags
&0x03) != 0x03) throw new /*VFSNamedException!"BSAArchive"*/VFSExceptionArc("invalid BSA flags (no names)");
125 if (flags
&0x40) throw new /*VFSNamedException!"BSAArchive"*/VFSExceptionArc("no support for shitbox BSA archives yet");
126 auto dircount
= fl
.readNum
!uint;
127 auto filecount
= fl
.readNum
!uint;
128 auto dirnmsize
= fl
.readNum
!uint;
129 auto filenmsize
= fl
.readNum
!uint;
130 auto fileflags
= fl
.readNum
!uint;
132 version(bsa_dump
) writefln("dirs=%u; files=%u; dirnmsize=%u; filenmsize=%u; fileflags=0x%08x", dircount
, filecount
, dirnmsize
, filenmsize
, fileflags
);
137 foreach (immutable didx
; 0..dircount
) {
138 fl
.readNum
!ulong; // name hash, skip it
139 uint fcount
= fl
.readNum
!uint;
140 fl
.readNum
!uint; // dir name offset
141 dircnt
.arrayAppendUnsafe(fcount
);
145 //bool xxdmp = false;
146 foreach (uint defcount
; dircnt
) {
147 string dirname
= loadBCStr
!true();
148 version(bsa_dump
) writeln("directory [", dirname
, "]: ", defcount
, " files");
149 //if (!xxdmp) { /*xxdmp = true;*/ import iv.vfs.io; writefln("ffofs: 0x%08x\n", cast(uint)fl.tell); }
150 // load actual file entries
151 foreach (immutable _
; 0..defcount
) {
153 fl
.readNum
!ulong; // name hash, skip it
154 fe
.size
= fl
.readNum
!uint;
155 fe
.ofs
= fl
.readNum
!uint;
156 fe
.name
= dirname
; // will be fixed later
157 if (fe
.size
&0x8000_0000U) assert(0, "wtf?!");
158 fe
.packed
= ((fe
.size
&0x4000_0000U) != 0);
159 if (flags
&0x04) fe
.packed
= !fe
.packed
;
160 fe
.size
&= 0x3fff_ffffU
;
161 dir
.arrayAppendUnsafe(fe
);
166 foreach (ref FileInfo fe
; dir
) {
170 fl
.rawReadExact((&ch
)[0..1]);
172 //if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
173 if (ch
== '\\' || ch
== '/') ch
= '_'; // fuck off, shitdoze
178 // load rest of the info
179 foreach (immutable fidx
, ref FileInfo fe
; dir
) {
188 assert(fe
.size
>= 4);
189 fe
.pksize
= fe
.size
-4;
191 fe
.size
= fl
.readNum
!uint;
192 if (fe
.pksize
== 0) {
193 assert(fe
.size
== 0);
200 buildNameHashTable();