Created
January 6, 2016 03:28
-
-
Save JohnEarnest/4cdf1f1c425bdcadec37 to your computer and use it in GitHub Desktop.
Quake WAD3 texture unpacker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.*; | |
import java.util.*; | |
import java.awt.image.*; | |
import javax.imageio.*; | |
// a very basic texture unpacker for the Quake WAD3 file format | |
// see http://hlbsp.sourceforge.net/index.php?content=waddef | |
public class WAD3 { | |
static int index = 0; | |
static byte[] data; | |
public static void main(String[] args) throws IOException { | |
// load the entire file into memory for convenient seeking: | |
File file = new File(args[0]); | |
DataInputStream in = new DataInputStream(new FileInputStream(file)); | |
data = new byte[(int)file.length()]; | |
in.readFully(data); | |
// read and display the header: | |
String m = readString(4); | |
if (m.equals("WAD3") || m.equals("WAD2")) { System.out.println(m+" format"); } | |
else { | |
System.err.println("Not a WAD file."); | |
System.exit(1); | |
} | |
int dirEntries = read32(); | |
int dirOffset = read32(); | |
System.out.println("File Size: "+data.length); | |
System.out.println("Directory Entries: "+dirEntries); | |
System.out.println("Directory Offset: "+dirOffset); | |
// load the directory listing | |
index = dirOffset; | |
List<DirectoryEntry> dir = new ArrayList<DirectoryEntry>(); | |
for(int z=0; z<dirEntries; z++) { | |
DirectoryEntry d = new DirectoryEntry(); | |
dir.add(d); | |
} | |
// unpack each texture | |
System.out.println("-----------------------------"); | |
File basedir = new File("unpacked_"+args[0].split("\\.")[0]); | |
basedir.mkdir(); | |
System.out.println("Unpacking into "+basedir+"/..."); | |
for(DirectoryEntry d : dir) { | |
if (d.type != 67) { continue; } // skip non-textures | |
unpackTexture(d, new File(basedir+"/"+d.name+".png")); | |
} | |
System.out.println("...Done."); | |
} | |
static void unpackTexture(DirectoryEntry d, File destination) throws IOException { | |
index = d.offset; | |
readString(16); // name; we already have this. | |
int w = read32(); | |
int h = read32(); | |
int[] offsets = new int[]{read32(), read32(), read32(), read32()}; | |
// texture data is packed as the first mipmap, | |
// at a location relative to the start of this record: | |
int[] texture = new int[w * h]; | |
index = d.offset + offsets[0]; | |
for(int z=0; z<w*h; z++) { texture[z] = read8(); } | |
// color lookup table is just past the end of the fourth mipmap. | |
// each mipmap divides the w/h by 2. We also skip 2 dummy bytes: | |
index = d.offset + offsets[3] + ((w/8) * (h/8)) + 2; | |
int[] clut = new int[256]; | |
for(int z=0; z<256; z++) { | |
int cr = read8(); | |
int cg = read8(); | |
int cb = read8(); | |
clut[z] = (0xFF << 24) | (cr << 16) | (cg << 8) | (cb); | |
} | |
// index through the CLUT and write the image data: | |
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); | |
for(int x = 0; x < w; x++) { | |
for(int y = 0; y < h; y++) { | |
img.setRGB(x, y, clut[texture[x+(y*w)]]); | |
} | |
} | |
System.out.println(destination); | |
ImageIO.write(img, "PNG", destination); | |
} | |
static String readString(int len) { | |
String r = ""; | |
for(int z=0; z<len; z++) { r += (char)read8(); } | |
return r.trim(); // trim will remove any null bytes too. | |
} | |
static int read8() { | |
return data[index++] & 0xFF; | |
} | |
static int read32() { | |
return read8() | (read8() << 8) | (read8() << 16) | (read8() << 24); | |
} | |
private static class DirectoryEntry { | |
final int offset; | |
final int size; | |
final byte type; | |
String name; | |
DirectoryEntry() { | |
offset = read32(); | |
size = read32(); | |
read32(); // uncompressed size; don't care | |
type = (byte)read8(); | |
boolean compression = read8() != 0; | |
read8(); // dummy byte | |
read8(); // dummy byte | |
name = readString(16); | |
if (compression) { | |
System.err.println("Compressed textures are not currently supported."); | |
System.exit(1); | |
} | |
} | |
public String toString() { | |
return String.format("%d\t%s (%d bytes)", offset, name, size); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment