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 // simple extfs for midnight commander
18 /* add the following to mc.ext:
19 # wad, pak, abuse .spe
20 regex/\.([Ww][Aa][Dd]|[Pp][Aa][Kk]|[Ss][Pp][Ee])$
22 View=%view{ascii} /opt/mc/libexec/mc/extfs.d/uwad list %f
26 import iv
.vfs
.arc
.abuse
;
27 import iv
.vfs
.arc
.arcanum
;
28 import iv
.vfs
.arc
.arcz
;
29 import iv
.vfs
.arc
.bsa
;
30 import iv
.vfs
.arc
.dfwad
; // just get lost
31 //import iv.vfs.arc.dunepak; no signature
32 //import iv.vfs.arc.f2dat; // no signature
33 //import iv.vfs.arc.toeedat; // conflicts with arcanum
34 import iv
.vfs
.arc
.wad2
;
35 import iv
.vfs
.arc
.feararch
;
40 version = dfwad_deep_scan
;
43 // ////////////////////////////////////////////////////////////////////////// //
44 uint getfmodtime (const(char)[] fname
) {
46 import core
.sys
.posix
.sys
.stat
;
47 import std
.internal
.cstring
: tempCString
;
49 if (stat(fname
.tempCString
, &st
) != 0) return 0;
50 return st
.st_mtime
/*.tv_sec*/;
58 const(char)[] removeExtension (const(char)[] fn
) {
59 foreach_reverse (immutable cidx
, char ch
; fn
) {
61 if (ch
== '.') { fn
= fn
[0..cidx
]; break; }
67 // ////////////////////////////////////////////////////////////////////////// //
68 // try to guess targa by validating some header fields
69 bool guessTarga (const(ubyte)[] buf
) {
70 if (buf
.length
< 45) return false; // minimal 1x1 tga
71 immutable ubyte idlength
= buf
.ptr
[0];
72 immutable ubyte bColorMapType
= buf
.ptr
[1];
73 immutable ubyte type
= buf
.ptr
[2];
74 immutable ushort wColorMapFirstEntryIndex
= cast(ushort)(buf
.ptr
[3]|
(buf
.ptr
[4]<<8));
75 immutable ushort wColorMapLength
= cast(ushort)(buf
.ptr
[5]|
(buf
.ptr
[6]<<8));
76 immutable ubyte bColorMapEntrySize
= buf
.ptr
[7];
77 immutable ushort wOriginX
= cast(ushort)(buf
.ptr
[8]|
(buf
.ptr
[9]<<8));
78 immutable ushort wOriginY
= cast(ushort)(buf
.ptr
[10]|
(buf
.ptr
[11]<<8));
79 immutable ushort wImageWidth
= cast(ushort)(buf
.ptr
[12]|
(buf
.ptr
[13]<<8));
80 immutable ushort wImageHeight
= cast(ushort)(buf
.ptr
[14]|
(buf
.ptr
[15]<<8));
81 immutable ubyte bPixelDepth
= buf
.ptr
[16];
82 immutable ubyte bImageDescriptor
= buf
.ptr
[17];
83 if (wImageWidth
< 1 || wImageHeight
< 1 || wImageWidth
> 32000 || wImageHeight
> 32000) return false; // arbitrary limit
84 immutable uint pixelsize
= (bPixelDepth
>>3);
86 case 2: // truecolor, raw
87 case 10: // truecolor, rle
89 case 2: case 3: case 4: break;
90 default: return false;
93 case 1: // paletted, raw
94 case 9: // paletted, rle
95 if (pixelsize
!= 1) return false;
99 if (pixelsize
!= 1 && pixelsize
!= 2) return false;
101 default: // invalid type
104 // check for valid colormap
105 switch (bColorMapType
) {
107 if (wColorMapFirstEntryIndex
!= 0 || wColorMapLength
!= 0) return 0;
110 if (bColorMapEntrySize
!= 15 && bColorMapEntrySize
!= 16 && bColorMapEntrySize
!= 24 && bColorMapEntrySize
!= 32) return false;
111 if (wColorMapLength
== 0) return false;
113 default: // invalid colormap type
116 if (((bImageDescriptor
>>6)&3) != 0) return false;
117 // this *looks* like a tga
122 // ////////////////////////////////////////////////////////////////////////// //
124 AAAAAAA NNN OOOOOOOO GGGGGGGG SSSSSSSS DATETIME [PATH/]FILENAME [-> [PATH/]FILENAME[/]]]
126 where (things in [] are optional):
128 AAAAAAA is the permission string like in ls -l
129 NNN is the number of links
130 OOOOOOOO is the owner (either UID or name)
131 GGGGGGGG is the group (either GID or name)
132 SSSSSSSS is the file size
133 FILENAME is the filename
134 PATH is the path from the archive's root without the leading slash (/)
135 DATETIME has one of the following formats:
136 Mon DD hh:mm, Mon DD YYYY, Mon DD YYYY hh:mm, MM-DD-YYYY hh:mm
138 where Mon is a three letter English month name, DD is day
139 1-31, MM is month 01-12, YYYY is four digit year, hh hour is
142 If the -> [PATH/]FILENAME part is present, it means:
144 If permissions start with an l (ell), then it is the name that symlink
145 points to. (If this PATH starts with a MC vfs prefix, then it is a symlink
146 somewhere to the other virtual filesystem (if you want to specify path from
147 the local root, use local:/path_name instead of /path_name, since /path_name
148 means from root of the archive listed).
150 If permissions do not start with l, but number of links is greater than one,
151 then it says that this file should be a hardlinked with the other file.
154 void doList(bool extended
=false) (string
[] args
) {
155 if (args
.length
!= 1) assert(0, "'list' command expect one arg");
156 vfsAddPak(args
[0], "\x00");
158 auto arctime
= getfmodtime(args
[0]);
160 vfsForEachFile((in ref de) {
161 //writefln("%10s %10s %s", de.size, de.stat("pksize").get!long, de.name);
162 if (de.name
.length
< 2 ||
de.name
[0] != '\x00') return;
163 //AAAAAAA NNN OOOOOOOO GGGGGGGG SSSSSSSS DATETIME [PATH/]FILENAME [-> [PATH/]FILENAME[/]]]
164 auto timevar
= de.stat("modtime");
165 uint timev
= (timevar
.isInteger ? timevar
.get
!uint : arctime
);
166 import core
.stdc
.time
;
168 auto tm
= localtime(cast(int*)&timev
);
169 auto len
= strftime(tbuf
.ptr
, tbuf
.length
, "%m/%d/%Y %H:%M:%S", tm
);
171 string name
= de.name
[1..$]/*.recode("utf-8", "koi8-u")*/;
173 bool allupper
= true;
174 foreach (char ch
; name
) if (koi8toupperTable
[ch
] != ch
) { allupper
= false; break; }
177 foreach (char ch
; name
) t
~= koi8tolowerTable
[ch
];
182 // brain-damaged dfwad
184 version(dfwad_deep_scan
) {
186 auto fl
= VFile(de.name
);
191 if (name
== "interscript" || name
== "text/anim") {
194 auto rd
= fl
.rawRead(xbuf
[]);
195 if (rd
.length
== 0) break;
196 foreach (char ch
; rd
) {
198 if (ch
!= '\t' && ch
!= '\n' && ch
!= '\r') { good
= false; break iniloop
; }
199 } else if (ch
== 127) { good
= false; break iniloop
; }
202 if (good
) { name
~= ".ini"; break; }
205 auto buf
= xbuf
[0..6];
207 fl
.rawReadExact(buf
[]);
208 if (buf
== "DFWAD\x01") { name
~= ".wad"; break; }
209 if (buf
[] == "\x89PNG\x0d\x0a") { name
~= ".png"; break; }
210 if (buf
[0..4] == "OggS") { name
~= ".ogg"; break; }
211 if (buf
[0..4] == "fLaC") { name
~= ".flac"; break; }
212 if (buf
[0..4] == "RIFF" && size
> 16) {
213 fl
.rawReadExact(xbuf
[0..10]);
214 if (xbuf
[2..10] == "WAVEfmt ") { name
~= ".wav"; break; }
216 if (buf
[0..4] == "MAP\x01") { name
~= ".map"; break; }
217 if (buf
[0..4] == "ID3\x02") { name
~= ".mp3"; break; }
218 if (buf
[0..4] == "ID3\x03") { name
~= ".mp3"; break; }
219 if (buf
[0..4] == "ID3\x04") { name
~= ".mp3"; break; }
220 if (buf
[0..4] == "IMPM") { name
~= ".it"; break; } // impulse tracker
221 if (buf
[0..4] == "MThd") { name
~= ".mid"; break; }
224 auto buf
= xbuf
[0..16];
226 fl
.rawReadExact(buf
[]);
227 if (buf
== "Extended Module:") { name
~= ".xm"; break; }
230 auto buf
= xbuf
[0..18];
231 fl
.seek(-18, Seek
.End
);
232 fl
.rawReadExact(buf
[]);
233 if (buf
== "TRUEVISION-XFILE\x2e\x00") { name
~= ".tga"; break; }
236 auto buf
= xbuf
[0..640];
237 fl
.seek(-640, Seek
.End
);
238 fl
.rawReadExact(buf
[]);
239 if (buf
.indexOf("LAME3.") >= 0) { name
~= ".mp3"; break; }
240 if (buf
[$-128..$-128+3] == "TAG") { name
~= ".mp3"; break; }
242 // try hard to guess targa
244 auto buf
= cast(ubyte[])xbuf
[0..45];
246 fl
.rawReadExact(buf
[]);
247 if (guessTarga(buf
[])) { name
~= ".tga"; break; }
250 } catch (Exception e
) {}
255 auto arcname = de.stat("arcname");
256 if (arcname.isString && arcname.get!string == "dfwad") {
259 foreach (char ch; name) t ~= koi8tolowerTable[ch];
263 static if (!extended
) {
264 writefln("-rw-r--r-- 1 1000 100 %8s %s %s", size
, tbuf
[0..len
], name
);
266 writefln("[%s] -rw-r--r-- 1 1000 100 %8s %s %s", de.stat("arcname"), size
, tbuf
[0..len
], name
);
272 // ////////////////////////////////////////////////////////////////////////// //
273 // archivename storedfilename extractto
274 void doExtract (string
[] args
) {
275 auto buf
= new ubyte[](1024*1024);
277 if (args
.length
!= 3) assert(0, "'copyout' command expect three args");
278 vfsAddPak(args
[0], "\x00");
281 fi
= VFile("\x00"~args
[1]);
282 } catch (Exception e
) {
283 version(dfwad_deep_scan
) {
285 fi
= VFile("\x00"~args
[1].removeExtension
);
290 auto fo
= VFile(args
[2], "w");
292 auto rd
= fi
.rawRead(buf
);
293 if (rd
.length
== 0) break;
294 fo
.rawWriteExact(rd
);
299 // ////////////////////////////////////////////////////////////////////////// //
300 int main (string
[] args
) {
301 //if (args.length == 1) args ~= ["list", "Twzone.wad"];
303 if (args
.length
== 1) assert(0, "command?");
306 case "list": // list archivename
309 case "list_ex": // list archivename
310 doList
!true(args
[2..$]);
312 case "copyout": // copyout archivename storedfilename extractto
313 doExtract(args
[2..$]);
315 case "copyin": // copyin archivename storedfilename sourcefile
316 case "rm": // rm archivename storedfilename
317 case "mkdir": // mkdir archivename dirname
318 case "rmdir": // rmdir archivename dirname
320 return -1; // not implemented
321 default: assert(0, "invalid command: '"~args
[1]~"'");