From a570d6be813e73fdbb26feb19080ce087da4c7be Mon Sep 17 00:00:00 2001 From: DmitriyMX Date: Tue, 6 Mar 2018 01:24:28 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D1=8B=D0=B9=20=D1=8D?= =?UTF-8?q?=D1=82=D0=B0=D0=BF=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD=D1=8B=20FS?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5?= =?UTF-8?q?=D0=B9=D1=81=20FileSystem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lwjake2/client/CL.java | 20 +- src/lwjake2/client/CL_ents.java | 8 +- src/lwjake2/client/CL_parse.java | 18 +- src/lwjake2/client/Console.java | 8 +- src/lwjake2/qcommon/BaseQ2FileSystem.java | 766 ++++++++++++++++++++++ src/lwjake2/qcommon/CM.java | 7 +- src/lwjake2/qcommon/Cvar.java | 5 +- src/lwjake2/qcommon/FileSystem.java | 24 + src/lwjake2/qcommon/Qcommon.java | 3 +- 9 files changed, 827 insertions(+), 32 deletions(-) create mode 100644 src/lwjake2/qcommon/BaseQ2FileSystem.java create mode 100644 src/lwjake2/qcommon/FileSystem.java diff --git a/src/lwjake2/client/CL.java b/src/lwjake2/client/CL.java index 3fabe5d..cbcf094 100644 --- a/src/lwjake2/client/CL.java +++ b/src/lwjake2/client/CL.java @@ -29,7 +29,8 @@ import lwjake2.qcommon.CM; import lwjake2.qcommon.Cbuf; import lwjake2.qcommon.Com; import lwjake2.qcommon.Cvar; -import lwjake2.qcommon.FS; +import lwjake2.qcommon.FileSystem; +import lwjake2.qcommon.BaseQ2FileSystem; import lwjake2.qcommon.MSG; import lwjake2.qcommon.Netchan; import lwjake2.qcommon.SZ; @@ -59,7 +60,8 @@ import java.nio.ByteOrder; */ public final class CL { private static final Logger logger = LoggerFactory.getLogger(CL.class); - + private static final FileSystem fileSystem = BaseQ2FileSystem.getInstance(); + static int precache_check; // for autodownload of precache items static int precache_spawncount; @@ -163,10 +165,10 @@ public final class CL { // // open the demo file // - name = FS.Gamedir() + "/demos/" + Cmd.Argv(1) + ".dm2"; + name = fileSystem.getGamedir() + "/demos/" + Cmd.Argv(1) + ".dm2"; logger.info("recording to {}", name); - FS.CreatePath(name); + fileSystem.createPath(name); Globals.cls.demofile = new RandomAccessFile(name, "rw"); if (Globals.cls.demofile == null) { logger.error("ERROR: couldn't open."); @@ -977,8 +979,7 @@ public final class CL { // checking for skins in the model if (CL.precache_model == null) { - CL.precache_model = FS - .LoadFile(Globals.cl.configstrings[CL.precache_check]); + CL.precache_model = fileSystem.loadFile(Globals.cl.configstrings[CL.precache_check]); if (CL.precache_model == null) { CL.precache_model_skin = 0; CL.precache_check++; @@ -990,8 +991,6 @@ public final class CL { int header = bb.getInt(); if (header != qfiles.IDALIASHEADER) { - // not an alias model - FS.FreeFile(CL.precache_model); CL.precache_model = null; CL.precache_model_skin = 0; CL.precache_check++; @@ -1029,7 +1028,6 @@ public final class CL { CL.precache_model_skin++; } if (CL.precache_model != null) { - FS.FreeFile(CL.precache_model); CL.precache_model = null; } CL.precache_model_skin = 0; @@ -1412,7 +1410,7 @@ public final class CL { // if (Globals.cls.state == Defines.ca_uninitialized) // return; - path = FS.Gamedir() + "/config.cfg"; + path = fileSystem.getGamedir() + "/config.cfg"; f = Lib.fopen(path, "rw"); if (f == null) { logger.warn("Couldn't write config.cfg."); @@ -1613,7 +1611,7 @@ public final class CL { InitLocal(); IN.Init(); - FS.ExecAutoexec(); + fileSystem.execAutoexec(); Cbuf.Execute(); } diff --git a/src/lwjake2/client/CL_ents.java b/src/lwjake2/client/CL_ents.java index b0b6f8a..a71fb5a 100644 --- a/src/lwjake2/client/CL_ents.java +++ b/src/lwjake2/client/CL_ents.java @@ -24,7 +24,8 @@ import lwjake2.game.entity_state_t; import lwjake2.game.player_state_t; import lwjake2.game.pmove_t; import lwjake2.qcommon.Com; -import lwjake2.qcommon.FS; +import lwjake2.qcommon.FileSystem; +import lwjake2.qcommon.BaseQ2FileSystem; import lwjake2.qcommon.MSG; import lwjake2.util.Math3D; @@ -40,6 +41,7 @@ import lwjake2.util.Math3D; * ========================================================================= */ public class CL_ents { + private static final FileSystem fileSystem = BaseQ2FileSystem.getInstance(); static int bitcounts[] = new int[32]; /// just for protocol profiling @@ -815,7 +817,7 @@ public class CL_ents { * */ if ((renderfx & Defines.RF_SHELL_HALF_DAM) != 0) { - if (FS.Developer_searchpath(2) == 2) { + if (fileSystem.developer_searchpath(2) == 2) { // ditch the half damage shell if any of red, blue, or // double are on if ((renderfx & (Defines.RF_SHELL_RED | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_DOUBLE)) != 0) @@ -824,7 +826,7 @@ public class CL_ents { } if ((renderfx & Defines.RF_SHELL_DOUBLE) != 0) { - if (FS.Developer_searchpath(2) == 2) { + if (fileSystem.developer_searchpath(2) == 2) { // lose the yellow shell if we have a red, blue, or // green shell if ((renderfx & (Defines.RF_SHELL_RED | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_GREEN)) != 0) diff --git a/src/lwjake2/client/CL_parse.java b/src/lwjake2/client/CL_parse.java index 3b87594..446ba19 100644 --- a/src/lwjake2/client/CL_parse.java +++ b/src/lwjake2/client/CL_parse.java @@ -26,7 +26,8 @@ import lwjake2.qcommon.CM; import lwjake2.qcommon.Cbuf; import lwjake2.qcommon.Com; import lwjake2.qcommon.Cvar; -import lwjake2.qcommon.FS; +import lwjake2.qcommon.FileSystem; +import lwjake2.qcommon.BaseQ2FileSystem; import lwjake2.qcommon.MSG; import lwjake2.qcommon.SZ; import lwjake2.qcommon.xcommand_t; @@ -45,6 +46,7 @@ import java.io.RandomAccessFile; */ public class CL_parse { private static final Logger logger = LoggerFactory.getLogger(CL_parse.class); + private static final FileSystem fileSystem = BaseQ2FileSystem.getInstance(); //// cl_parse.c -- parse a message received from the server @@ -59,7 +61,7 @@ public class CL_parse { // ============================================================================= public static String DownloadFileName(String fn) { - return FS.Gamedir() + "/" + fn; + return fileSystem.getGamedir() + "/" + fn; } /* @@ -77,7 +79,7 @@ public class CL_parse { return true; } - if (FS.FileLength(filename) > 0) { // it exists, no need to download + if (fileSystem.fileLength(filename) > 0) { // it exists, no need to download return true; } @@ -150,7 +152,7 @@ public class CL_parse { return; } - if (FS.LoadFile(filename) != null) { // it exists, no need to + if (fileSystem.loadFile(filename) != null) { // it exists, no need to // download Com.Printf("File already exists.\n"); return; @@ -224,7 +226,7 @@ public class CL_parse { if (Globals.cls.download == null) { String name = DownloadFileName(Globals.cls.downloadtempname).toLowerCase(); - FS.CreatePath(name); + fileSystem.createPath(name); Globals.cls.download = Lib.fopen(name, "rw"); if (Globals.cls.download == null) { @@ -323,10 +325,10 @@ public class CL_parse { // set gamedir if (str.length() > 0 - && (FS.fs_gamedirvar.string == null - || FS.fs_gamedirvar.string.length() == 0 || FS.fs_gamedirvar.string + && (fileSystem.getGamedirVar().string == null + || fileSystem.getGamedirVar().string.length() == 0 || fileSystem.getGamedirVar().string .equals(str)) - || (str.length() == 0 && (FS.fs_gamedirvar.string != null || FS.fs_gamedirvar.string + || (str.length() == 0 && (fileSystem.getGamedirVar().string != null || fileSystem.getGamedirVar().string .length() == 0))) Cvar.Set("game", str); diff --git a/src/lwjake2/client/Console.java b/src/lwjake2/client/Console.java index 41a561d..6f2da3e 100644 --- a/src/lwjake2/client/Console.java +++ b/src/lwjake2/client/Console.java @@ -24,7 +24,8 @@ import lwjake2.game.Cmd; import lwjake2.qcommon.Cbuf; import lwjake2.qcommon.Com; import lwjake2.qcommon.Cvar; -import lwjake2.qcommon.FS; +import lwjake2.qcommon.FileSystem; +import lwjake2.qcommon.BaseQ2FileSystem; import lwjake2.qcommon.xcommand_t; import lwjake2.util.Lib; import lwjake2.util.Vargs; @@ -40,6 +41,7 @@ import java.util.Arrays; */ public final class Console extends Globals { private static final Logger logger = LoggerFactory.getLogger(Console.class); + private static final FileSystem fileSystem = BaseQ2FileSystem.getInstance(); public static xcommand_t ToggleConsole_f = new xcommand_t() { public void execute() { SCR.EndLoadingPlaque(); // get rid of loading plaque @@ -95,10 +97,10 @@ public final class Console extends Globals { //Com_sprintf (name, sizeof(name), "%s/%s.txt", FS_Gamedir(), // Cmd_Argv(1)); - name = FS.Gamedir() + "/" + Cmd.Argv(1) + ".txt"; + name = fileSystem.getGamedir() + "/" + Cmd.Argv(1) + ".txt"; logger.info("Dumped console text to {}", name); - FS.CreatePath(name); + fileSystem.createPath(name); f = Lib.fopen(name, "rw"); if (f == null) { logger.error("ERROR: couldn't open."); diff --git a/src/lwjake2/qcommon/BaseQ2FileSystem.java b/src/lwjake2/qcommon/BaseQ2FileSystem.java new file mode 100644 index 0000000..d2160d0 --- /dev/null +++ b/src/lwjake2/qcommon/BaseQ2FileSystem.java @@ -0,0 +1,766 @@ +/* + * DmitriyMX + * 2018-03-06 + */ +package lwjake2.qcommon; + +import lwjake2.Defines; +import lwjake2.Globals; +import lwjake2.game.Cmd; +import lwjake2.game.cvar_t; +import lwjake2.sys.Sys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +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 static lwjake2.Defines.*; +import static lwjake2.qcommon.FS.MAX_READ; + +/* + * ================================================== + * + * QUAKE FILESYSTEM + * + * ================================================== + */ +public class BaseQ2FileSystem implements FileSystem { + private static final Logger logger = LoggerFactory.getLogger(BaseQ2FileSystem.class); + private static final BaseQ2FileSystem instance = new BaseQ2FileSystem(); + private static final int IDPAKHEADER = (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P'); + private static final int MAX_FILES_IN_PACK = 4096; + + public static BaseQ2FileSystem getInstance() { + return instance; + } + + private BaseQ2FileSystem() {} + + private 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 + " ]"; + } + } + + private class pack_t { + String filename; + RandomAccessFile handle; + ByteBuffer backbuffer; + int numfiles; + Hashtable files; // with packfile_t entries + } + + private class searchpath_t { + String filename; + pack_t pack; // only one of filename or pack will be used + searchpath_t next; + } + + private class filelink_t { + String from; + int fromlength; + String to; + } + + private class dpackheader_t { + int ident; // IDPAKHEADER + int dirofs; + int dirlen; + } + + // with filelink_t entries + private List fs_links = new LinkedList<>(); + + private searchpath_t fs_searchpaths; + // without gamedirs + private searchpath_t fs_base_searchpaths; + private String fs_gamedir; + private String fs_userdir; + private cvar_t fs_basedir; + private cvar_t fs_cddir; + private cvar_t fs_gamedirvar; + + // buffer for C-Strings char[56] + private byte[] tmpText = new byte[packfile_t.NAME_SIZE]; + private int file_from_pak = 0; + + private void path_f() { + searchpath_t s; + filelink_t link; + + logger.info("Current search path:"); + for (s = fs_searchpaths; s != null; s = s.next) { + if (s == fs_base_searchpaths) + logger.info("----------"); + if (s.pack != null) + logger.info("{} ({} files)", s.pack.filename, s.pack.numfiles); + else + logger.info(s.filename); + } + + logger.info("Links:"); + for (Iterator it = fs_links.iterator(); it.hasNext();) { + link = it.next(); + logger.info("{} : {}", link.from, link.to); + } + } + + /** + * Creates a filelink_t + */ + private void link_f() { + filelink_t entry; + + if (Cmd.Argc() != 3) { + logger.info("USAGE: link "); + return; + } + + // see if the link already exists + for (Iterator it = fs_links.iterator(); it.hasNext();) { + entry = it.next(); + + if (entry.from.equals(Cmd.Argv(1))) { + if (Cmd.Argv(2).length() < 1) { + // delete it + it.remove(); + return; + } + entry.to = new String(Cmd.Argv(2)); + return; + } + } + + // create a new link if the is not empty + if (Cmd.Argv(2).length() > 0) { + entry = new filelink_t(); + entry.from = new String(Cmd.Argv(1)); + entry.fromlength = entry.from.length(); + entry.to = new String(Cmd.Argv(2)); + fs_links.add(entry); + } + } + + /* + * nextPath + * + * Allows enumerating all of the directories in the search path + */ + private String nextPath(String prevpath) { + searchpath_t s; + String prev; + + if (prevpath == null || prevpath.length() == 0) + return fs_gamedir; + + prev = fs_gamedir; + for (s = fs_searchpaths; s != null; s = s.next) { + if (s.pack != null) + continue; + + if (prevpath == prev) + return s.filename; + + prev = s.filename; + } + + return null; + } + + private String[] listFiles(String findname, int musthave, int canthave) { + String[] list = new String[0]; + + File[] files = Sys.FindAll(findname, musthave, canthave); + + if (files != null) { + list = new String[files.length]; + for (int i = 0; i < files.length; i++) { + list[i] = files[i].getPath(); + } + } + + return list; + } + + private void dir_f() { + String path = null; + String findname = null; + String wildcard = "*.*"; + String[] dirnames; + + if (Cmd.Argc() != 1) { + wildcard = Cmd.Argv(1); + } + + while ((path = nextPath(path)) != null) { + String tmp = findname; + + findname = path + '/' + wildcard; + + if (tmp != null) + tmp.replaceAll("\\\\", "/"); + + logger.info("Directory of {}", findname); + logger.info("----"); + + dirnames = listFiles(findname, 0, 0); + + if (dirnames.length != 0) { + int index = 0; + for (int i = 0; i < dirnames.length; i++) { + if ((index = dirnames[i].lastIndexOf('/')) > 0) { + logger.info(dirnames[i].substring(index + 1, dirnames[i].length())); + } else { + logger.info(dirnames[i]); + } + } + } + + } + } + + /* + * CreatePath + * + * Creates any directories needed to store the given filename. + */ + @Override + public void createPath(String path) { + int index = path.lastIndexOf('/'); + // -1 if not found and 0 means write to root + if (index > 0) { + File f = new File(path.substring(0, index)); + if (!f.mkdirs() && !f.isDirectory()) { + logger.warn("can't create path \"{}\"", path); + } + } + } + + // RAFAEL + /* + * Developer_searchpath + */ + @Override + public int developer_searchpath(int who) { + // PMM - warning removal + // char *start; + searchpath_t s; + + for (s = fs_searchpaths; s != null; s = s.next) { + if (s.filename.indexOf("xatrix") != -1) + return 1; + + if (s.filename.indexOf("rogue") != -1) + return 2; + } + + return 0; + } + + @Override + public cvar_t getGamedirVar() { + return fs_gamedirvar; + } + + /* + * 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. + */ + private 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; + + logger.info("Added packfile {} ({} files)", packfile, numpackfiles); + + return pack; + } + + /* + * AddGameDirectory + * + * Sets fs_gamedir, adds the directory to the head of the path, then loads + * and adds pak1.pak pak2.pak ... + */ + private void addGameDirectory(String dir) { + int i; + searchpath_t search; + pack_t pak; + String pakfile; + + fs_gamedir = new String(dir); + + // + // add the directory to the search path + // ensure fs_userdir is first in searchpath + search = new searchpath_t(); + search.filename = new String(dir); + if (fs_searchpaths != null) { + search.next = fs_searchpaths.next; + fs_searchpaths.next = search; + } else { + fs_searchpaths = search; + } + + // + // add any pak files in the format pak0.pak pak1.pak, ... + // + for (i = 0; i < 10; i++) { + pakfile = dir + "/pak" + i + ".pak"; + if (!(new File(pakfile).canRead())) + continue; + + pak = loadPackFile(pakfile); + if (pak == null) + continue; + + search = new searchpath_t(); + search.pack = pak; + search.filename = ""; + search.next = fs_searchpaths; + fs_searchpaths = search; + } + } + + /** + * set baseq2 directory + */ + private void setCDDir() { + fs_cddir = Cvar.Get("cddir", "", CVAR_ARCHIVE); + if (fs_cddir.string.length() > 0) + addGameDirectory(fs_cddir.string + '/' + Globals.BASEDIRNAME); + } + + private void markBaseSearchPaths() { + // any set gamedirs will be freed up to here + fs_base_searchpaths = fs_searchpaths; + } + + /* + * SetGamedir + * + * Sets the gamedir and path to a different directory. + */ + @Override + public void setGamedir(String dir) { + searchpath_t next; + + if (dir.indexOf("..") != -1 || dir.indexOf("/") != -1 + || dir.indexOf("\\") != -1 || dir.indexOf(":") != -1) { + logger.warn("Gamedir should be a single filename, not a path"); + return; + } + + // + // free up any current game dir info + // + while (fs_searchpaths != fs_base_searchpaths) { + if (fs_searchpaths.pack != null) { + try { + fs_searchpaths.pack.handle.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 = null; + } + next = fs_searchpaths.next; + fs_searchpaths = null; + fs_searchpaths = next; + } + + // + // flush all data, so it will be forced to reload + // + if ((Globals.dedicated != null) && (Globals.dedicated.value == 0.0f)) + Cbuf.AddText("vid_restart\nsnd_restart\n"); + + fs_gamedir = fs_basedir.string + '/' + dir; + + if (dir.equals(Globals.BASEDIRNAME) || (dir.length() == 0)) { + Cvar.FullSet("gamedir", "", CVAR_SERVERINFO | CVAR_NOSET); + Cvar.FullSet("game", "", CVAR_LATCH | CVAR_SERVERINFO); + } else { + Cvar.FullSet("gamedir", dir, CVAR_SERVERINFO | CVAR_NOSET); + if (fs_cddir.string != null && fs_cddir.string.length() > 0) + addGameDirectory(fs_cddir.string + '/' + dir); + + addGameDirectory(fs_basedir.string + '/' + dir); + } + } + + @Override + public String getGamedir() { + return (fs_userdir != null) ? fs_userdir : Globals.BASEDIRNAME; + } + + @Override + public void execAutoexec() { + String dir = fs_userdir; + + String name; + if (dir != null && dir.length() > 0) { + name = dir + "/autoexec.cfg"; + } else { + name = fs_basedir.string + '/' + Globals.BASEDIRNAME + + "/autoexec.cfg"; + } + + int canthave = Defines.SFF_SUBDIR | Defines.SFF_HIDDEN + | Defines.SFF_SYSTEM; + + if (Sys.FindAll(name, 0, canthave) != null) { + Cbuf.AddText("exec autoexec.cfg\n"); + } + } + + /** + * Check for "+set game" override - Used to properly set gamedir + */ + private void checkOverride() { + fs_gamedirvar = Cvar.Get("game", "", CVAR_LATCH | CVAR_SERVERINFO); + + if (fs_gamedirvar.string.length() > 0) + setGamedir(fs_gamedirvar.string); + } + + @Override + public void init() { + Cmd.AddCommand("path", new xcommand_t() { + public void execute() { + path_f(); + } + }); + Cmd.AddCommand("link", new xcommand_t() { + public void execute() { + link_f(); + } + }); + Cmd.AddCommand("dir", new xcommand_t() { + public void execute() { + dir_f(); + } + }); + + fs_userdir = System.getProperty("user.home") + "/.lwjake2"; + createPath(fs_userdir + "/"); + addGameDirectory(fs_userdir); + + // + // basedir + // allows the game to run from outside the data tree + // + fs_basedir = Cvar.Get("basedir", ".", CVAR_NOSET); + + // + // cddir + // Logically concatenates the cddir after the basedir for + // allows the game to run from outside the data tree + // + + setCDDir(); + + // + // start up with baseq2 by default + // + addGameDirectory(fs_basedir.string + '/' + Globals.BASEDIRNAME); + + // any set gamedirs will be freed up to here + markBaseSearchPaths(); + + // check for game override + checkOverride(); + } + + public int fileLength(String filename) { + searchpath_t search; + String netpath; + pack_t pak; + filelink_t link; + + file_from_pak = 0; + + // check for links first + for (Iterator it = fs_links.iterator(); it.hasNext();) { + link = it.next(); + + if (filename.regionMatches(0, link.from, 0, link.fromlength)) { + netpath = link.to + filename.substring(link.fromlength); + File file = new File(netpath); + if (file.canRead()) { + Com.DPrintf("link file: " + netpath + '\n'); + return (int) file.length(); + } + return -1; + } + } + + // search through the path, one element at a time + + for (search = fs_searchpaths; search != null; search = search.next) { + // is the element a pak file? + if (search.pack != null) { + // look through all the pak file elements + pak = search.pack; + filename = filename.toLowerCase(); + packfile_t entry = pak.files.get(filename); + + if (entry != null) { + // found it! + file_from_pak = 1; + Com.DPrintf("PackFile: " + pak.filename + " : " + filename + + '\n'); + // open a new file on the pakfile + File file = new File(pak.filename); + if (!file.canRead()) { + Com.Error(Defines.ERR_FATAL, "Couldn't reopen " + + pak.filename); + } + return entry.filelen; + } + } else { + // check a file in the directory tree + netpath = search.filename + '/' + filename; + + File file = new File(netpath); + if (!file.canRead()) + continue; + + Com.DPrintf("FindFile: " + netpath + '\n'); + + return (int) file.length(); + } + } + Com.DPrintf("FindFile: can't find " + filename + '\n'); + return -1; + } + + /* + * FOpenFile + * + * Finds the file in the search path. returns a RadomAccesFile. Used for + * streaming data out of either a pak file or a seperate file. + */ + private RandomAccessFile fOpenFile(String filename) throws IOException { + searchpath_t search; + String netpath; + pack_t pak; + filelink_t link; + File file = null; + + file_from_pak = 0; + + // check for links first + for (Iterator it = fs_links.iterator(); it.hasNext();) { + link = it.next(); + + // if (!strncmp (filename, link->from, link->fromlength)) + if (filename.regionMatches(0, link.from, 0, link.fromlength)) { + netpath = link.to + filename.substring(link.fromlength); + file = new File(netpath); + if (file.canRead()) { + //Com.DPrintf ("link file: " + netpath +'\n'); + return new RandomAccessFile(file, "r"); + } + return null; + } + } + + // + // search through the path, one element at a time + // + for (search = fs_searchpaths; search != null; search = search.next) { + // is the element a pak file? + if (search.pack != null) { + // look through all the pak file elements + pak = search.pack; + filename = filename.toLowerCase(); + packfile_t entry = pak.files.get(filename); + + if (entry != null) { + // found it! + file_from_pak = 1; + //Com.DPrintf ("PackFile: " + pak.filename + " : " + + // filename + '\n'); + file = new File(pak.filename); + if (!file.canRead()) + Com.Error(Defines.ERR_FATAL, "Couldn't reopen " + + pak.filename); + if (pak.handle == null || !pak.handle.getFD().valid()) { + // hold the pakfile handle open + pak.handle = new RandomAccessFile(pak.filename, "r"); + } + // open a new file on the pakfile + + RandomAccessFile raf = new RandomAccessFile(file, "r"); + raf.seek(entry.filepos); + + return raf; + } + } else { + // check a file in the directory tree + netpath = search.filename + '/' + filename; + + file = new File(netpath); + if (!file.canRead()) + continue; + + //Com.DPrintf("FindFile: " + netpath +'\n'); + + return new RandomAccessFile(file, "r"); + } + } + //Com.DPrintf ("FindFile: can't find " + filename + '\n'); + return null; + } + + /* + * LoadFile + * + * Filename are reletive to the quake search path a null buffer will just + * return the file content as byte[] + */ + @Override + public byte[] loadFile(String path) { + RandomAccessFile file; + + byte[] buf = null; + int len = 0; + + // TODO hack for bad strings (fuck \0) + int index = path.indexOf('\0'); + if (index != -1) + path = path.substring(0, index); + + // look for it in the filesystem or pack files + len = fileLength(path); + + if (len < 1) + return null; + + try { + file = fOpenFile(path); + //Read(buf = new byte[len], len, h); + buf = new byte[len]; + file.readFully(buf); + file.close(); + } catch (IOException e) { + Com.Error(Defines.ERR_FATAL, e.toString()); + } + return buf; + } + + /** + * Read + * + * Properly handles partial reads + */ + @Override + public void read(byte[] buffer, int len, RandomAccessFile file) { + int block, remaining; + int offset = 0; + int read = 0; + + // read in chunks for progress bar + remaining = len; + + while (remaining != 0) { + block = Math.min(remaining, MAX_READ); + try { + read = file.read(buffer, offset, block); + } catch (IOException e) { + logger.error(e.toString()); + throw new RuntimeException("Look log file"); + } + + if (read == 0) { + logger.error("FS_Read: 0 bytes read"); + throw new RuntimeException("Look log file"); + } else if (read == -1) { + logger.error("FS_Read: -1 bytes read"); + throw new RuntimeException("Look log file"); + } + // + // do some progress bar thing here... + // + remaining -= read; + offset += read; + } + } +} diff --git a/src/lwjake2/qcommon/CM.java b/src/lwjake2/qcommon/CM.java index 875ab25..8cbc237 100644 --- a/src/lwjake2/qcommon/CM.java +++ b/src/lwjake2/qcommon/CM.java @@ -37,6 +37,7 @@ import java.nio.IntBuffer; import java.util.Arrays; public class CM { + private static final FileSystem fileSystem = BaseQ2FileSystem.getInstance(); public static class cnode_t { cplane_t plane; // ptr @@ -248,7 +249,7 @@ public class CM { // // load the file // - buf = FS.LoadFile(name); + buf = fileSystem.loadFile(name); if (buf == null) Com.Error(Defines.ERR_DROP, "Couldn't load " + name); @@ -283,8 +284,6 @@ public class CM { CMod_LoadAreaPortals(header.lumps[Defines.LUMP_AREAPORTALS]); CMod_LoadVisibility(header.lumps[Defines.LUMP_VISIBILITY]); CMod_LoadEntityString(header.lumps[Defines.LUMP_ENTITIES]); - - FS.FreeFile(buf); CM_InitBoxHull(); @@ -1787,7 +1786,7 @@ public class CM { byte buf[] = new byte[len]; - FS.Read(buf, len, f); + fileSystem.read(buf, len, f); ByteBuffer bb = ByteBuffer.wrap(buf); IntBuffer ib = bb.asIntBuffer(); diff --git a/src/lwjake2/qcommon/Cvar.java b/src/lwjake2/qcommon/Cvar.java index 8e4ddd8..e03f029 100644 --- a/src/lwjake2/qcommon/Cvar.java +++ b/src/lwjake2/qcommon/Cvar.java @@ -33,6 +33,7 @@ import java.util.Vector; * Cvar implements console variables. The original code is located in cvar.c */ public class Cvar extends Globals { + private static final FileSystem fileSystem = BaseQ2FileSystem.getInstance(); /** * @param var_name @@ -384,8 +385,8 @@ public class Cvar extends Globals { var.value = 0.0f; } if (var.name.equals("game")) { - FS.SetGamedir(var.string); - FS.ExecAutoexec(); + fileSystem.setGamedir(var.string); + fileSystem.execAutoexec(); } } } diff --git a/src/lwjake2/qcommon/FileSystem.java b/src/lwjake2/qcommon/FileSystem.java new file mode 100644 index 0000000..477246f --- /dev/null +++ b/src/lwjake2/qcommon/FileSystem.java @@ -0,0 +1,24 @@ +/* + * DmitriyMX + * 2018-03-06 + */ +package lwjake2.qcommon; + +import lwjake2.game.cvar_t; + +import java.io.RandomAccessFile; + +public interface FileSystem { + void init(); // FS.InitFilesystem(); + byte[] loadFile(String path); + void read(byte[] buffer, int len, RandomAccessFile file); + void execAutoexec(); + void createPath(String path); + int fileLength(String filename); + + int developer_searchpath(int who); + cvar_t getGamedirVar(); + + void setGamedir(String dir); + String getGamedir(); +} diff --git a/src/lwjake2/qcommon/Qcommon.java b/src/lwjake2/qcommon/Qcommon.java index edb3a1b..8295ece 100644 --- a/src/lwjake2/qcommon/Qcommon.java +++ b/src/lwjake2/qcommon/Qcommon.java @@ -40,6 +40,7 @@ import java.io.IOException; */ public final class Qcommon extends Globals { private static final Logger logger = LoggerFactory.getLogger(Qcommon.class); + private static final FileSystem fileSystem = BaseQ2FileSystem.getInstance(); public static final String BUILDSTRING = "Java " + System.getProperty("java.version");; public static final String CPUSTRING = System.getProperty("os.arch"); @@ -70,7 +71,7 @@ public final class Qcommon extends Globals { Cbuf.AddEarlyCommands(false); Cbuf.Execute(); - FS.InitFilesystem(); + fileSystem.init(); reconfigure(false);