Copyright (c) 2015 - 2026 Matt Tropiano
This library is an offshoot/rewrite of Black Rook Software's Doom Struct Project, of which I have been granted rights to rewrite (the rights were granted to myself BY myself).
All deprecated classes from the origin project will not be in this one. All end users are encouraged to switch to this one, as this project will be actively maintained.
NONE
The master branch contains stable code (I hope). Until a release is cut, the master branch will be shifting.
The purpose of the Doom Struct project is to provide a means to read/write data structures for the Doom Engine and similar derivatives.
There are several libraries out there for reading data from Doom in many different programming languages, but there isn't a decent one for Java. And by decent, I mean:
- Useful
- Documented
- Performant
- Efficient
The goal with this library is to get end-users up-and-running quickly with little boilerplate code, and to ensure clarity within documentation and with written code, and future-proofing to a reasonable extent.
- Reads/edits WAD files.
- Reads PK3 files (zips).
- Full UDMF parsing/writing support.
- Reads/edits all Doom data structures in Doom, Hexen/ZDoom, or Strife formats. This includes textures, patches, lines, vertices, things, sectors, nodes, palettes, colormaps, text, PNG data, music data, sound data, flats, blockmaps, reject, and even ENDOOM-type VGA lumps.
- Supports PNGs with offset data (grAb).
- Reads/edits Boom-engine data lumps like ANIMATED and SWITCHES.
- Contains a utility class for converting Doom graphics to standard Java graphics structures, and vice-versa.
- Contains a utility class for converting Doom DMX sound data to other formats via the Java SPI, and vice-versa.
- Contains a utility class for managing texture sets without needing to care too much about Doom's nightmarish texture data setup.
- Reading/converting PC Speaker effects.
- Supporting additional BSP node types.
- Stuff for drawing maps more easily (graphic-wise).
- Better ENDOOM rendering options.
- Turning this project into a Maven Repository Compatible one (currently it's Ant-compatible; it gets the job done).
- Better Unit tests (or automatable ones).
Contained in this release is a series of libraries that allow reading, writing, and extracting data in Doom Engine structures, found in the net.mtrop.doom packages.
Unless otherwise specified, assume that all methods are not thread-safe.
Open a WAD file (and close it).
WadFilewad = newWadFile("doom2.wad");
wad.close();Open DOOM2.WAD and read MAP01 into a Doom Map.
WadFilewad = newWadFile("doom2.wad");
DoomMapmap = MapUtils.createDoomMap(wad, "map01");
wad.close();Open DOOM2.WAD and fetch all sectors in MAP29 with the BLOOD1 floor texture.
WadFilewad = newWadFile("doom2.wad");
Set<DoomSector> set = wad.getDataAsList("sectors", "map29", DoomSector.class, DoomSector.LENGTH).stream()
.filter((sector) -> sector.getFloorTexture().equals("BLOOD1"))
.collect(Collectors.toSet());
wad.close();Open DOOM.WAD and fetch all things in E1M1 that appear in multiplayer.
WadFilewad = newWadFile("doom.wad");
Set<DoomThing> set = wad.getDataAsList("things", "e1m1", DoomThing.class, DoomThing.LENGTH).stream()
.filter((thing) -> thing.isFlagSet(DoomThingFlags.NOT_SINGLEPLAYER))
.collect(Collectors.toSet());
wad.close();Open HEXEN.WAD and fetch all things in MAP01 that have a special.
WadFilewad = newWadFile("hexen.wad");
Set<HexenThing> set = wad.getDataAsList("things", "map01", HexenThing.class, HexenThing.LENGTH).stream()
.filter((thing) -> thing.getSpecial() > 0)
.collect(Collectors.toSet());
wad.close();Open DOOM.WAD and fetch all maps that have less than 1000 linedefs.
finalWadFilewad = newWadFile("doom.wad");
Set<String> set = Arrays.asList(MapUtils.getAllMapHeaders(wad)).stream()
.filter((header) -> wad.getEntry("linedefs", header).getSize() / DoomLinedef.LENGTH < 1000)
.collect(Collectors.toSet());
wad.close();Open SQUARE1.PK3, fetch maps/E1A1.WAD and read it into a UDMF Map.
DoomPK3pk3 = newDoomPK3("square1.pk3");
WadBufferwad = pk3.getDataAsWadBuffer("maps/e1a1.wad");
UDMFMapmap = wad.getTextDataAs("textmap", Charset.forName("UTF-8"), UDMFMap.class);
pk3.close();Open DOOM2.WAD, read DEMO2 and figure out how many tics that player 1 was pushing "fire".
WadFilewad = newWadFile("doom2.wad");
Demodemo = wad.getDataAs("demo2", Demo.class);
inttics = 0;
for (inti = 0; i < demo.getTicCount(); i++) {
tics += (demo.getTic(i).getAction() & Demo.Tic.ACTION_FIRE) != 0 ? 1 : 0;
}
wad.close();Open DOOM.WAD and get MD5 hashes of each entry.
WadFilewad = newWadFile("doom.wad");
byte[][] hashes = newbyte[wad.getEntryCount()][];
inti = 0;
for (WadEntryentry : wad)
hashes[i++] = MessageDigest.getInstance("MD5").digest(wad.getData(entry));
wad.close();Open DOOM2.WAD, fetch all TROO* (graphic) entries and export them as PNGs.
WadFilewad = newWadFile("doom2.wad");
finalPalettepal = wad.getDataAs("playpal", Palette.class);
for (WadEntryentry : wad) {
if (entry.getName().startsWith("TROO")) {
Picturep = wad.getDataAs(entry, Picture.class);
ImageIO.write(GraphicUtils.createImage(p, pal), "PNG", newFile(entry.getName()+".png"));
}
}
wad.close();Open DOOM.WAD, fetch all DS* (audio) entries, upsample them to 22kHz (cosine interpolation), and export them as WAVs.
WadFilewad = newWadFile("doom.wad");
for (WadEntryentry : wad) {
if (entry.getName().startsWith("DS")) {
DMXSoundsound = wad.getDataAs(entry, DMXSound.class)
.resample(InterpolationType.COSINE, DMXSound.SAMPLERATE_22KHZ);
SoundUtils.writeSoundToFile(sound, AudioFileFormat.Type.WAVE, newFile(entry.getName() + ".wav"));
}
}
wad.close();Open DOOM.WAD, fetch all map headers, and extract those maps' entries to separate WADs.
WadFilewad = newWadFile("doom.wad");
for (intheaderIndex : MapUtils.getAllMapIndices(wad)) {
StringheaderName = wad.getEntry(headerIndex).getName();
WadBuffer.extract(wad, MapUtils.getMapEntries(wad, headerName))
.writeToFile(newFile(headerName + ".wad"));
}
wad.close();Open DOOM2.WAD, extract MAP01 from it into a file called MAP01.WAD, and replace any wall texture that starts with TEKGREN to FIREBLU1.
WadFilewad = newWadFile("doom2.wad");
WadFiletemp = WadFile.extract(newFile("MAP01.WAD"), wad, MapUtils.getMapEntries(wad, "MAP01"));
temp.transformData("sidedefs", DoomSidedef.class, DoomSidedef.LENGTH, (sidedef, n) -> {
if (sidedef.getTextureMiddle().startsWith("TEKGREN"))
sidedef.setTextureMiddle("FIREBLU1");
});
temp.close();
wad.close();Open DOOM2.WAD, assemble a TextureSet, remove every texture that begins with R, and write it to a new WAD.
WadFilewad = newWadFile("doom2.wad");
TextureSettextureSet = newTextureSet(
wad.getDataAs("pnames", PatchNames.class),
wad.getDataAs("texture1", DoomTextureList.class)
);
wad.close();
Iterator<TextureSet.Texture> it = textureSet.iterator();
while (it.hasNext()) {
TextureSet.Texturet = it.next();
if (t.getName().startsWith("R"))
it.remove();
}
PatchNamespout = newPatchNames();
DoomTextureListtout = newDoomTextureList();
textureSet.export(pout, tout);
WadFilenewwad = WadFile.createWadFile("new.wad");
newwad.addData("PNAMES", pout.toBytes());
newwad.addData("TEXTURE1", tout.toBytes());
newwad.close();Open DOOM.WAD, get the first map of each episode and write it to a new WAD called OUT.wad and close both (with one statement!).
WadUtils.openWadAndExtractTo("DOOM.WAD", "OUT.wad", (wad)->
WadUtils.withEntries(MapUtils.getMapEntries(wad, "E1M1"))
.and(MapUtils.getMapEntries(wad, "E2M1"))
.and(MapUtils.getMapEntries(wad, "E3M1"))
.and(MapUtils.getMapEntries(wad, "E4M1"))
.get()
);Open DOOM2.WAD, create a new Wad called TEXTURES.wad and copy over only a few textures and flats (and also-close them):
try (WadFilesource = newWadFile("DOOM2.WAD")) {
try (WadFiletarget = WadFile.createWadFile("TEXTURES.wad")) {
try (TextureCopiercopier = TextureUtils.createTextureCopier(source, target)) {
copier.copyFlat("FLOOR7_1");
copier.copyFlat("CEIL5_2");
copier.copyTexture("AASHITTY"); // first texture is "null" texturecopier.copyTexture("SUPPORT3");
copier.copyTexture("SUPPORT2");
copier.copyTexture("BIGDOOR1");
copier.copyTexture("BIGDOOR2");
copier.copyTexture("BIGDOOR3");
copier.copyTexture("BIGDOOR4");
copier.copyTexture("BIGDOOR5");
}
}
}To install/compile this library and make all artifacts with Apache Maven, type:
mvn install
To compile this library, type:
mvn compile
To make Maven-compatible JARs of this library, type:
mvn jar:jar
To make Javadocs:
mvn javadoc:javadoc
To run tests, type:
mvn test
To generate a coverage report, type:
mvn test jacoco:report
To clean up everything:
mvn clean
Online Javadocs can be found at: https://mtrop.github.io/DoomStruct/javadoc/
This program/library and the accompanying materials are made available under the terms of the GNU Lesser Public License v2.1 which accompanies this distribution, and is available at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
A copy of the LGPL should have been included in this release (LICENSE.txt). If it was not, please contact me for a copy, or to notify me of a distribution that has not included it.
This contains code copied from Black Rook Base, under the terms of the MIT License (docs/LICENSE-BlackRookBase.txt).
Eat your heart out, OMGIFOL!