diff --git a/src/lwjake2/qcommon/FS.java b/src/lwjake2/qcommon/FS.java index 16ed96b..a1adc6e 100644 --- a/src/lwjake2/qcommon/FS.java +++ b/src/lwjake2/qcommon/FS.java @@ -23,6 +23,9 @@ import lwjake2.Globals; import lwjake2.game.Cmd; import lwjake2.game.cvar_t; import lwjake2.sys.Sys; +import ru.di9.lwjake2.PackFile; +import ru.di9.lwjake2.PackFileEntry; +import ru.di9.lwjake2.PackLoader; import java.io.File; import java.io.FileInputStream; @@ -30,12 +33,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.channels.FileChannel; -import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Optional; /** * FS @@ -53,32 +55,6 @@ public final class FS extends Globals { * ================================================== */ - public static class packfile_t { - static final int SIZE = 64; - - static final int NAME_SIZE = 56; - - String name; // char name[56] - - int filepos, filelen; - - public String toString() { - return name + " [ length: " + filelen + " pos: " + filepos + " ]"; - } - } - - public static class pack_t { - String filename; - - RandomAccessFile handle; - - ByteBuffer backbuffer; - - int numfiles; - - Hashtable files; // with packfile_t entries - } - public static String fs_gamedir; private static String fs_userdir; @@ -103,7 +79,7 @@ public final class FS extends Globals { public static class searchpath_t { String filename; - pack_t pack; // only one of filename or pack will be used + PackFile pack; // only one of filename or pack will be used searchpath_t next; } @@ -166,7 +142,7 @@ public final class FS extends Globals { public static int FileLength(String filename) { searchpath_t search; String netpath; - pack_t pak; + PackFile pak; filelink_t link; file_from_pak = 0; @@ -194,20 +170,20 @@ public final class FS extends Globals { // look through all the pak file elements pak = search.pack; filename = filename.toLowerCase(); - packfile_t entry = pak.files.get(filename); + PackFileEntry entry = pak.getFiles().get(filename); if (entry != null) { // found it! file_from_pak = 1; - Com.DPrintf("PackFile: " + pak.filename + " : " + filename + Com.DPrintf("PackFile: " + pak.getFilename() + " : " + filename + '\n'); // open a new file on the pakfile - File file = new File(pak.filename); + File file = new File(pak.getFilename()); if (!file.canRead()) { Com.Error(Defines.ERR_FATAL, "Couldn't reopen " - + pak.filename); + + pak.getFilename()); } - return entry.filelen; + return entry.getFileLen(); } } else { // check a file in the directory tree @@ -238,7 +214,7 @@ public final class FS extends Globals { throws IOException { searchpath_t search; String netpath; - pack_t pak; + PackFile pak; filelink_t link; File file = null; @@ -269,25 +245,25 @@ public final class FS extends Globals { // look through all the pak file elements pak = search.pack; filename = filename.toLowerCase(); - packfile_t entry = pak.files.get(filename); + PackFileEntry entry = pak.getFiles().get(filename); if (entry != null) { // found it! file_from_pak = 1; //Com.DPrintf ("PackFile: " + pak.filename + " : " + // filename + '\n'); - file = new File(pak.filename); + file = new File(pak.getFilename()); if (!file.canRead()) Com.Error(Defines.ERR_FATAL, "Couldn't reopen " - + pak.filename); - if (pak.handle == null || !pak.handle.getFD().valid()) { + + pak.getFilename()); + if (pak.getHandle() == null || !pak.getHandle().getFD().valid()) { // hold the pakfile handle open - pak.handle = new RandomAccessFile(pak.filename, "r"); + pak.setHandle(new RandomAccessFile(pak.getFilename(), "r")); } // open a new file on the pakfile RandomAccessFile raf = new RandomAccessFile(file, "r"); - raf.seek(entry.filepos); + raf.seek(entry.getFilePos()); return raf; } @@ -390,7 +366,7 @@ public final class FS extends Globals { public static ByteBuffer LoadMappedFile(String filename) { searchpath_t search; String netpath; - pack_t pak; + PackFile pak; filelink_t link; File file = null; @@ -431,32 +407,32 @@ public final class FS extends Globals { // look through all the pak file elements pak = search.pack; filename = filename.toLowerCase(); - packfile_t entry = pak.files.get(filename); + PackFileEntry entry = pak.getFiles().get(filename); if (entry != null) { // found it! file_from_pak = 1; //Com.DPrintf ("PackFile: " + pak.filename + " : " + // filename + '\n'); - file = new File(pak.filename); + file = new File(pak.getFilename()); if (!file.canRead()) Com.Error(Defines.ERR_FATAL, "Couldn't reopen " - + pak.filename); - if (pak.handle == null || !pak.handle.getFD().valid()) { + + pak.getFilename()); + if (pak.getHandle() == null || !pak.getHandle().getFD().valid()) { // hold the pakfile handle open - pak.handle = new RandomAccessFile(pak.filename, "r"); + pak.setHandle(new RandomAccessFile(pak.getFilename(), "r")); } // open a new file on the pakfile - if (pak.backbuffer == null) { - channel = pak.handle.getChannel(); - pak.backbuffer = channel.map( + if (pak.getBackBuffer() == null) { + channel = pak.getHandle().getChannel(); + pak.setBackBuffer(channel.map( FileChannel.MapMode.READ_ONLY, 0, - pak.handle.length()); + pak.getHandle().length())); channel.close(); } - pak.backbuffer.position(entry.filepos); - buffer = pak.backbuffer.slice(); - buffer.limit(entry.filelen); + pak.getBackBuffer().position(entry.getFilePos()); + buffer = pak.getBackBuffer().slice(); + buffer.limit(entry.getFileLen()); return buffer; } } else { @@ -508,86 +484,6 @@ public final class FS extends Globals { static final int MAX_FILES_IN_PACK = 4096; - // buffer for C-Strings char[56] - static byte[] tmpText = new byte[packfile_t.NAME_SIZE]; - - /* - * LoadPackFile - * - * Takes an explicit (not game tree related) path to a pak file. - * - * Loads the header and directory, adding the files at the beginning of the - * list so they override previous pack files. - */ - static pack_t LoadPackFile(String packfile) { - - dpackheader_t header; - Hashtable newfiles; - RandomAccessFile file; - int numpackfiles = 0; - pack_t pack = null; - // unsigned checksum; - // - try { - file = new RandomAccessFile(packfile, "r"); - FileChannel fc = file.getChannel(); - ByteBuffer packhandle = fc.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); - packhandle.order(ByteOrder.LITTLE_ENDIAN); - - fc.close(); - - if (packhandle == null || packhandle.limit() < 1) - return null; - // - header = new dpackheader_t(); - header.ident = packhandle.getInt(); - header.dirofs = packhandle.getInt(); - header.dirlen = packhandle.getInt(); - - if (header.ident != IDPAKHEADER) - Com.Error(Defines.ERR_FATAL, packfile + " is not a packfile"); - - numpackfiles = header.dirlen / packfile_t.SIZE; - - if (numpackfiles > MAX_FILES_IN_PACK) - Com.Error(Defines.ERR_FATAL, packfile + " has " + numpackfiles - + " files"); - - newfiles = new Hashtable(numpackfiles); - - packhandle.position(header.dirofs); - - // parse the directory - packfile_t entry = null; - - for (int i = 0; i < numpackfiles; i++) { - packhandle.get(tmpText); - - entry = new packfile_t(); - entry.name = new String(tmpText).trim(); - entry.filepos = packhandle.getInt(); - entry.filelen = packhandle.getInt(); - - newfiles.put(entry.name.toLowerCase(), entry); - } - - } catch (IOException e) { - Com.DPrintf(e.getMessage() + '\n'); - return null; - } - - pack = new pack_t(); - pack.filename = new String(packfile); - pack.handle = file; - pack.numfiles = numpackfiles; - pack.files = newfiles; - - Com.Printf("Added packfile " + packfile + " (" + numpackfiles - + " files)\n"); - - return pack; - } - /* * AddGameDirectory * @@ -597,7 +493,7 @@ public final class FS extends Globals { static void AddGameDirectory(String dir) { int i; searchpath_t search; - pack_t pak; + PackFile pak; String pakfile; fs_gamedir = new String(dir); @@ -622,9 +518,17 @@ public final class FS extends Globals { if (!(new File(pakfile).canRead())) continue; - pak = LoadPackFile(pakfile); - if (pak == null) - continue; + try { + Optional opt = PackLoader.INSTANCE.loadFromFile(pakfile); + if (opt.isEmpty()) { + continue; + } + pak = opt.get(); + Com.Printf("Added packfile " + pak.getFilename() + " (" + pak.getNumFiles() + + " files)\n"); + } catch (IOException e) { + throw new RuntimeException(e); + } search = new searchpath_t(); search.pack = pak; @@ -695,13 +599,13 @@ public final class FS extends Globals { while (fs_searchpaths != fs_base_searchpaths) { if (fs_searchpaths.pack != null) { try { - fs_searchpaths.pack.handle.close(); + fs_searchpaths.pack.getHandle().close(); } catch (IOException e) { Com.DPrintf(e.getMessage() + '\n'); } // clear the hashtable - fs_searchpaths.pack.files.clear(); - fs_searchpaths.pack.files = null; + fs_searchpaths.pack.getFiles().clear(); + fs_searchpaths.pack.setFiles(null); fs_searchpaths.pack = null; } next = fs_searchpaths.next; @@ -840,7 +744,7 @@ public final class FS extends Globals { if (s == fs_base_searchpaths) Com.Printf("----------\n"); if (s.pack != null) - Com.Printf(s.pack.filename + " (" + s.pack.numfiles + Com.Printf(s.pack.getFilename() + " (" + s.pack.getNumFiles() + " files)\n"); else Com.Printf(s.filename + '\n'); diff --git a/src/ru/di9/lwjake2/PackFile.java b/src/ru/di9/lwjake2/PackFile.java new file mode 100644 index 0000000..94dc3fb --- /dev/null +++ b/src/ru/di9/lwjake2/PackFile.java @@ -0,0 +1,53 @@ +package ru.di9.lwjake2; + +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.util.Map; + +public class PackFile { + private final String filename; + private final int numFiles; + + private Map files; + private RandomAccessFile handle; + private ByteBuffer backBuffer; + + public PackFile(String filename, RandomAccessFile handle, int numFiles, Map files) { + this.filename = filename; + this.handle = handle; + this.numFiles = numFiles; + this.files = files; + } + + public String getFilename() { + return filename; + } + + public int getNumFiles() { + return numFiles; + } + + public RandomAccessFile getHandle() { + return handle; + } + + public void setHandle(RandomAccessFile handle) { + this.handle = handle; + } + + public Map getFiles() { + return files; + } + + public void setFiles(Map files) { + this.files = files; + } + + public ByteBuffer getBackBuffer() { + return backBuffer; + } + + public void setBackBuffer(ByteBuffer backBuffer) { + this.backBuffer = backBuffer; + } +} diff --git a/src/ru/di9/lwjake2/PackFileEntry.java b/src/ru/di9/lwjake2/PackFileEntry.java new file mode 100644 index 0000000..d09ecf4 --- /dev/null +++ b/src/ru/di9/lwjake2/PackFileEntry.java @@ -0,0 +1,30 @@ +package ru.di9.lwjake2; + +public class PackFileEntry { + private final String name; + private final int filePos; + private final int fileLen; + + public PackFileEntry(String name, int filePos, int fileLen) { + this.name = name; + this.filePos = filePos; + this.fileLen = fileLen; + } + + public String getName() { + return name; + } + + public int getFilePos() { + return filePos; + } + + public int getFileLen() { + return fileLen; + } + + @Override + public String toString() { + return name + " [ length: " + fileLen + " pos: " + filePos + " ]"; + } +} diff --git a/src/ru/di9/lwjake2/PackLoader.java b/src/ru/di9/lwjake2/PackLoader.java new file mode 100644 index 0000000..74a76dd --- /dev/null +++ b/src/ru/di9/lwjake2/PackLoader.java @@ -0,0 +1,74 @@ +package ru.di9.lwjake2; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class PackLoader { + public static final PackLoader INSTANCE = new PackLoader(); + + private static final String FLAG_READ_ONLY = "r"; + private static final int IDPAKHEADER = (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P'); + private static final int SIZE = 64; + private static final int MAX_FILES_IN_PACK = 4096; + private static final int NAME_SIZE = 56; + + /* + * LoadPackFile + * + * Takes an explicit (not game tree related) path to a pak file. + * + * Loads the header and directory, adding the files at the beginning of the + * list so they override previous pack files. + */ + public Optional loadFromFile(String file) throws IOException { + RandomAccessFile raf = new RandomAccessFile(file, FLAG_READ_ONLY); + ByteBuffer packHandle; + try(FileChannel channel = raf.getChannel()) { + packHandle = channel.map(FileChannel.MapMode.READ_ONLY, 0, raf.length()); + if (packHandle == null || packHandle.limit() < 1) { + raf.close(); + return Optional.empty(); + } + packHandle.order(ByteOrder.LITTLE_ENDIAN); + } + + int headerIdent = packHandle.getInt(); + if (headerIdent != IDPAKHEADER) { + raf.close(); + throw new IOException(file + " is not a packfile"); + } + + int headerDirofs = packHandle.getInt(); + int headerDirlen = packHandle.getInt(); + + int numPackFiles = headerDirlen / SIZE; + if (numPackFiles > MAX_FILES_IN_PACK) { + raf.close(); + throw new IOException(file + " has " + numPackFiles + " files"); + } + + Map newFiles = new HashMap<>(numPackFiles); + packHandle.position(headerDirofs); + + PackFileEntry entry; + byte[] tmpBuff = new byte[NAME_SIZE]; + for (int i = 0; i < numPackFiles; i++) { + packHandle.get(tmpBuff); + + String name = new String(tmpBuff).trim(); + int filePos = packHandle.getInt(); + int fileLen = packHandle.getInt(); + entry = new PackFileEntry(name, filePos, fileLen); + + newFiles.put(entry.getName().toLowerCase(), entry); + } + + return Optional.of(new PackFile(file, raf, numPackFiles, newFiles)); + } +}