0

refac: PackFile

This commit is contained in:
2026-02-11 23:18:27 +03:00
parent 3fc318dee2
commit b9d31e6fbe
4 changed files with 204 additions and 143 deletions

View File

@@ -23,6 +23,9 @@ import lwjake2.Globals;
import lwjake2.game.Cmd; import lwjake2.game.Cmd;
import lwjake2.game.cvar_t; import lwjake2.game.cvar_t;
import lwjake2.sys.Sys; 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.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@@ -30,12 +33,11 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.Hashtable;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Optional;
/** /**
* FS * 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<String, packfile_t> files; // with packfile_t entries
}
public static String fs_gamedir; public static String fs_gamedir;
private static String fs_userdir; private static String fs_userdir;
@@ -103,7 +79,7 @@ public final class FS extends Globals {
public static class searchpath_t { public static class searchpath_t {
String filename; 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; searchpath_t next;
} }
@@ -166,7 +142,7 @@ public final class FS extends Globals {
public static int FileLength(String filename) { public static int FileLength(String filename) {
searchpath_t search; searchpath_t search;
String netpath; String netpath;
pack_t pak; PackFile pak;
filelink_t link; filelink_t link;
file_from_pak = 0; file_from_pak = 0;
@@ -194,20 +170,20 @@ public final class FS extends Globals {
// look through all the pak file elements // look through all the pak file elements
pak = search.pack; pak = search.pack;
filename = filename.toLowerCase(); filename = filename.toLowerCase();
packfile_t entry = pak.files.get(filename); PackFileEntry entry = pak.getFiles().get(filename);
if (entry != null) { if (entry != null) {
// found it! // found it!
file_from_pak = 1; file_from_pak = 1;
Com.DPrintf("PackFile: " + pak.filename + " : " + filename Com.DPrintf("PackFile: " + pak.getFilename() + " : " + filename
+ '\n'); + '\n');
// open a new file on the pakfile // open a new file on the pakfile
File file = new File(pak.filename); File file = new File(pak.getFilename());
if (!file.canRead()) { if (!file.canRead()) {
Com.Error(Defines.ERR_FATAL, "Couldn't reopen " Com.Error(Defines.ERR_FATAL, "Couldn't reopen "
+ pak.filename); + pak.getFilename());
} }
return entry.filelen; return entry.getFileLen();
} }
} else { } else {
// check a file in the directory tree // check a file in the directory tree
@@ -238,7 +214,7 @@ public final class FS extends Globals {
throws IOException { throws IOException {
searchpath_t search; searchpath_t search;
String netpath; String netpath;
pack_t pak; PackFile pak;
filelink_t link; filelink_t link;
File file = null; File file = null;
@@ -269,25 +245,25 @@ public final class FS extends Globals {
// look through all the pak file elements // look through all the pak file elements
pak = search.pack; pak = search.pack;
filename = filename.toLowerCase(); filename = filename.toLowerCase();
packfile_t entry = pak.files.get(filename); PackFileEntry entry = pak.getFiles().get(filename);
if (entry != null) { if (entry != null) {
// found it! // found it!
file_from_pak = 1; file_from_pak = 1;
//Com.DPrintf ("PackFile: " + pak.filename + " : " + //Com.DPrintf ("PackFile: " + pak.filename + " : " +
// filename + '\n'); // filename + '\n');
file = new File(pak.filename); file = new File(pak.getFilename());
if (!file.canRead()) if (!file.canRead())
Com.Error(Defines.ERR_FATAL, "Couldn't reopen " Com.Error(Defines.ERR_FATAL, "Couldn't reopen "
+ pak.filename); + pak.getFilename());
if (pak.handle == null || !pak.handle.getFD().valid()) { if (pak.getHandle() == null || !pak.getHandle().getFD().valid()) {
// hold the pakfile handle open // 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 // open a new file on the pakfile
RandomAccessFile raf = new RandomAccessFile(file, "r"); RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(entry.filepos); raf.seek(entry.getFilePos());
return raf; return raf;
} }
@@ -390,7 +366,7 @@ public final class FS extends Globals {
public static ByteBuffer LoadMappedFile(String filename) { public static ByteBuffer LoadMappedFile(String filename) {
searchpath_t search; searchpath_t search;
String netpath; String netpath;
pack_t pak; PackFile pak;
filelink_t link; filelink_t link;
File file = null; File file = null;
@@ -431,32 +407,32 @@ public final class FS extends Globals {
// look through all the pak file elements // look through all the pak file elements
pak = search.pack; pak = search.pack;
filename = filename.toLowerCase(); filename = filename.toLowerCase();
packfile_t entry = pak.files.get(filename); PackFileEntry entry = pak.getFiles().get(filename);
if (entry != null) { if (entry != null) {
// found it! // found it!
file_from_pak = 1; file_from_pak = 1;
//Com.DPrintf ("PackFile: " + pak.filename + " : " + //Com.DPrintf ("PackFile: " + pak.filename + " : " +
// filename + '\n'); // filename + '\n');
file = new File(pak.filename); file = new File(pak.getFilename());
if (!file.canRead()) if (!file.canRead())
Com.Error(Defines.ERR_FATAL, "Couldn't reopen " Com.Error(Defines.ERR_FATAL, "Couldn't reopen "
+ pak.filename); + pak.getFilename());
if (pak.handle == null || !pak.handle.getFD().valid()) { if (pak.getHandle() == null || !pak.getHandle().getFD().valid()) {
// hold the pakfile handle open // 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 // open a new file on the pakfile
if (pak.backbuffer == null) { if (pak.getBackBuffer() == null) {
channel = pak.handle.getChannel(); channel = pak.getHandle().getChannel();
pak.backbuffer = channel.map( pak.setBackBuffer(channel.map(
FileChannel.MapMode.READ_ONLY, 0, FileChannel.MapMode.READ_ONLY, 0,
pak.handle.length()); pak.getHandle().length()));
channel.close(); channel.close();
} }
pak.backbuffer.position(entry.filepos); pak.getBackBuffer().position(entry.getFilePos());
buffer = pak.backbuffer.slice(); buffer = pak.getBackBuffer().slice();
buffer.limit(entry.filelen); buffer.limit(entry.getFileLen());
return buffer; return buffer;
} }
} else { } else {
@@ -508,86 +484,6 @@ public final class FS extends Globals {
static final int MAX_FILES_IN_PACK = 4096; 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<String, packfile_t> 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<String, packfile_t>(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 * AddGameDirectory
* *
@@ -597,7 +493,7 @@ public final class FS extends Globals {
static void AddGameDirectory(String dir) { static void AddGameDirectory(String dir) {
int i; int i;
searchpath_t search; searchpath_t search;
pack_t pak; PackFile pak;
String pakfile; String pakfile;
fs_gamedir = new String(dir); fs_gamedir = new String(dir);
@@ -622,9 +518,17 @@ public final class FS extends Globals {
if (!(new File(pakfile).canRead())) if (!(new File(pakfile).canRead()))
continue; continue;
pak = LoadPackFile(pakfile); try {
if (pak == null) Optional<PackFile> opt = PackLoader.INSTANCE.loadFromFile(pakfile);
if (opt.isEmpty()) {
continue; 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 = new searchpath_t();
search.pack = pak; search.pack = pak;
@@ -695,13 +599,13 @@ public final class FS extends Globals {
while (fs_searchpaths != fs_base_searchpaths) { while (fs_searchpaths != fs_base_searchpaths) {
if (fs_searchpaths.pack != null) { if (fs_searchpaths.pack != null) {
try { try {
fs_searchpaths.pack.handle.close(); fs_searchpaths.pack.getHandle().close();
} catch (IOException e) { } catch (IOException e) {
Com.DPrintf(e.getMessage() + '\n'); Com.DPrintf(e.getMessage() + '\n');
} }
// clear the hashtable // clear the hashtable
fs_searchpaths.pack.files.clear(); fs_searchpaths.pack.getFiles().clear();
fs_searchpaths.pack.files = null; fs_searchpaths.pack.setFiles(null);
fs_searchpaths.pack = null; fs_searchpaths.pack = null;
} }
next = fs_searchpaths.next; next = fs_searchpaths.next;
@@ -840,7 +744,7 @@ public final class FS extends Globals {
if (s == fs_base_searchpaths) if (s == fs_base_searchpaths)
Com.Printf("----------\n"); Com.Printf("----------\n");
if (s.pack != null) if (s.pack != null)
Com.Printf(s.pack.filename + " (" + s.pack.numfiles Com.Printf(s.pack.getFilename() + " (" + s.pack.getNumFiles()
+ " files)\n"); + " files)\n");
else else
Com.Printf(s.filename + '\n'); Com.Printf(s.filename + '\n');

View File

@@ -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<String, PackFileEntry> files;
private RandomAccessFile handle;
private ByteBuffer backBuffer;
public PackFile(String filename, RandomAccessFile handle, int numFiles, Map<String, PackFileEntry> 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<String, PackFileEntry> getFiles() {
return files;
}
public void setFiles(Map<String, PackFileEntry> files) {
this.files = files;
}
public ByteBuffer getBackBuffer() {
return backBuffer;
}
public void setBackBuffer(ByteBuffer backBuffer) {
this.backBuffer = backBuffer;
}
}

View File

@@ -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 + " ]";
}
}

View File

@@ -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<PackFile> 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<String, PackFileEntry> 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));
}
}