/* ** 2013 July 3 3 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. */ package info.ata4.bspunprotect; import info.ata4.bsplib.BspFile; import info.ata4.bsplib.lump.Lump; import info.ata4.bsplib.lump.LumpFile; import info.ata4.bsplib.lump.LumpType; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.file.Path; import java.nio.file.Paths; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; import org.apache.commons.io.IOUtils; /** * BSPProtect map decrypter. * * @author Nico Bergemann <barracuda415 at yahoo.de> */ public class BspUnprotect { public static final String VERSION = "1.0"; public static final String BSPPROTECT_FILE = "entities.dat"; public static final String BSPPROTECT_KEY = "EhZT36ErlQlZpLm7"; /** * @param args the command line arguments */ public static void main(String[] args) { if (args.length == 0) { System.out.println("BspUnprotect " + VERSION); System.out.println("Usage: bspunprotect.jar <BSP file> [key]"); System.exit(0); } Path file = Paths.get(args[0]); byte[] key = args.length >= 2 ? args[1].getBytes() : BSPPROTECT_KEY.getBytes(); try { BspUnprotect unprot = new BspUnprotect(); unprot.setKey(key); unprot.decrypt(file); } catch (Exception ex) { System.err.println(ex.getMessage()); } } private BspFile bspFile; private byte[] key; public void setKey(byte[] key) { if (key.length % 8 != 0) { throw new IllegalArgumentException("Invalid key length, must be multiple of 8"); } this.key = key; } public byte[] getKey() { return key; } public void decrypt(Path file) { System.out.println("Loading BSP file " + file.getFileName()); try { bspFile = new BspFile(); bspFile.load(file); } catch (IOException ex) { throw new RuntimeException("Couldn't load BSP file", ex); } System.out.println("Reading pakfile lump"); byte[] encEnt = readEncryptedEntities(); if (encEnt == null) { throw new RuntimeException("This map wasn't protected by BSPProtect"); } System.out.println("Restoring entities"); Lump entLump = bspFile.getLump(LumpType.LUMP_ENTITIES); int capacity = encEnt.length; if (entLump.getLength() > 0) { capacity += entLump.getLength(); } ByteBuffer entBuf = ByteBuffer.allocateDirect(capacity); entBuf.order(bspFile.getByteOrder()); // copy the worldspawn into the new entity lump if (entLump.getLength() > 0) { ByteBuffer entBufOld = entLump.getBuffer(); entBufOld.rewind(); entBufOld.limit(entBufOld.limit() - 1); // decrease limit to skip NUL entBuf.put(entBufOld); } // write decypted entity data into the new buffer try { InputStream is = new ByteArrayInputStream(encEnt); // init ICE cipher IceKey ice = new IceKey(key.length / 8 - 1); ice.set(key); final int blockSize = ice.blockSize(); byte[] cipher = new byte[blockSize]; byte[] plain = new byte[blockSize]; for (int read = 0; read != -1; read = is.read(cipher)) { // decrypt block ice.decrypt(cipher, plain); // the last block is not encrypted if not equal to block size entBuf.put(read == blockSize ? plain : cipher, 0, read); } // NUL terminator entBuf.put((byte) 0); } catch (IOException ex) { throw new RuntimeException("Couldn't decrypt entity data", ex); } System.out.println("Writing lump file"); // write lump file try { Lump entLumpNew = new Lump(LumpType.LUMP_ENTITIES); entLumpNew.setBuffer(entBuf); LumpFile lump = new LumpFile(bspFile); lump.setLump(entLumpNew); lump.save(bspFile.getNextLumpFile()); } catch (IOException ex) { throw new RuntimeException("Couldn't write decrypted entity lump file", ex); } } private byte[] readEncryptedEntities() { try (ZipArchiveInputStream zis = bspFile.getPakFile().getArchiveInputStream()) { ZipArchiveEntry ze; while ((ze = zis.getNextZipEntry()) != null) { if (ze.getName().equals(BSPPROTECT_FILE)) { ByteArrayOutputStream os = new ByteArrayOutputStream(); IOUtils.copy(zis, os); return os.toByteArray(); } } } catch (IOException ex) { throw new RuntimeException("Couldn't read pakfile", ex); } return null; } }