1 /*
2 ===========================================================================
3 Copyright (C) 1997-2006 Id Software, Inc.
4 
5 This file is part of Quake 2 Tools source code.
6 
7 Quake 2 Tools source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Quake 2 Tools source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Quake 2 Tools source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 
23 /*
24  * Unpack -- a completely non-object oriented utility...
25  *
26  */
27 
28 import java.io.*;
29 
30 class Unpack {
31 	static final int IDPAKHEADER	= (('K'<<24)+('C'<<16)+('A'<<8)+'P');
32 
intSwap(int i)33 	static int intSwap(int i) {
34 		int		a, b, c, d;
35 
36 		a = i & 255;
37 		b = (i >> 8) & 255;
38 		c = (i >> 16) & 255;
39 		d = (i >> 24) & 255;
40 
41 		return (a << 24) + (b << 16) + (c << 8) + d;
42 	}
43 
patternMatch(String pattern, String s)44 	static boolean	patternMatch (String pattern, String s) {
45 		int		index;
46 		int		remaining;
47 
48 		if (pattern.equals(s)) {
49 			return true;
50 		}
51 
52 		// fairly lame single wildcard matching
53 		index = pattern.indexOf('*');
54 		if (index == -1) {
55 			return false;
56 		}
57 		if (!pattern.regionMatches(0, s, 0, index)) {
58 			return false;
59 		}
60 
61 		index += 1;	// skip the *
62 		remaining = pattern.length() - index;
63 		if (s.length() < remaining) {
64 			return false;
65 		}
66 
67 		if (!pattern.regionMatches(index, s, s.length()-remaining, remaining)) {
68 			return false;
69 		}
70 
71 		return true;
72 	}
73 
usage()74 	static void usage() {
75 		System.out.println ("Usage: unpack <packfile> <match> <basedir>");
76 		System.out.println ("   or: unpack -list <packfile>");
77 		System.out.println ("<match> may contain a single * wildcard");
78 		System.exit (1);
79 	}
80 
main(String[] args)81 	public static void main (String[] args) {
82 		int			ident;
83 		int			dirofs;
84 		int			dirlen;
85 		int			i;
86 		int			numLumps;
87 		byte[]		name = new byte[56];
88 		String		nameString;
89 		int			filepos;
90 		int			filelen;
91 		RandomAccessFile	readLump;
92 		DataInputStream		directory;
93 		String		pakName;
94 		String		pattern;
95 
96 		if (args.length == 2) {
97 			if (!args[0].equals("-list")) {
98 				usage();
99 			}
100 			pakName = args[1];
101 			pattern = null;
102 		} else if (args.length == 3) {
103 			pakName = args[0];
104 			pattern = args[1];
105 		} else {
106 			pakName = null;
107 			pattern = null;
108 			usage ();
109 		}
110 
111 		try	{
112 			// one stream to read the directory
113 			directory = new DataInputStream(new FileInputStream(pakName));
114 
115 			// another to read lumps
116 			readLump = new RandomAccessFile(pakName, "r");
117 
118 			// read the header
119 			ident = intSwap(directory.readInt());
120 			dirofs = intSwap(directory.readInt());
121 			dirlen = intSwap(directory.readInt());
122 
123 			if (ident != IDPAKHEADER) {
124 				System.out.println ( pakName + " is not a pakfile.");
125 				System.exit (1);
126 			}
127 
128 			// read the directory
129 			directory.skipBytes (dirofs - 12);
130 			numLumps = dirlen / 64;
131 
132 			System.out.println (numLumps + " lumps in " + pakName);
133 
134 			for (i = 0 ; i < numLumps ; i++) {
135 				directory.readFully(name);
136 				filepos = intSwap(directory.readInt());
137 				filelen = intSwap(directory.readInt());
138 
139 				nameString = new String (name, 0);
140 				// chop to the first 0 byte
141 				nameString = nameString.substring (0, nameString.indexOf(0));
142 
143 				if (pattern == null) {
144 					// listing mode
145 					System.out.println (nameString + " : " + filelen + "bytes");
146 				} else if (patternMatch (pattern, nameString) ) {
147 					File				writeFile;
148 					DataOutputStream	writeLump;
149 					byte[]				buffer = new byte[filelen];
150 					StringBuffer		fixedString;
151 					String				finalName;
152 					int					index;
153 
154 					System.out.println ("Unpaking " + nameString + " " + filelen
155 						+ " bytes");
156 
157 					// load the lump
158 					readLump.seek(filepos);
159 					readLump.readFully(buffer);
160 
161 					// quake uses forward slashes, but java requires
162 					// they only by the host's seperator, which
163 					// varies from win to unix
164 					fixedString = new StringBuffer (args[2] + File.separator + nameString);
165 					for (index = 0 ; index < fixedString.length() ; index++) {
166 						if (fixedString.charAt(index) == '/') {
167 							fixedString.setCharAt(index, File.separatorChar);
168 						}
169 					}
170 					finalName = fixedString.toString ();
171 
172 					index = finalName.lastIndexOf(File.separatorChar);
173 					if (index != -1) {
174 						String		finalPath;
175 						File		writePath;
176 
177 						finalPath = finalName.substring(0, index);
178 						writePath = new File (finalPath);
179 						writePath.mkdirs();
180 					}
181 
182 					writeFile = new File (finalName);
183 					writeLump = new DataOutputStream ( new FileOutputStream(writeFile) );
184 					writeLump.write(buffer);
185 					writeLump.close();
186 
187 				}
188 			}
189 
190 			readLump.close();
191 			directory.close();
192 
193 		} catch (IOException e) {
194 			System.out.println ( e.toString() );
195 		}
196 	}
197 
198 }
199