/*
Copyright (C) 1997-2001 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/* Modifications
Copyright 2003-2004 Bytonic Software
Copyright 2010 Google Inc.
*/
package com.googlecode.gwtquake.shared.common;
import java.io.*;
import java.nio.ByteBuffer;
//import java.nio.ByteOrder;
//import java.nio.channels.FileChannel;
import java.util.*;
import com.googlecode.gwtquake.shared.game.Commands;
import com.googlecode.gwtquake.shared.game.ConsoleVariable;
import com.googlecode.gwtquake.shared.sys.Sys;
/**
* FS
*
* @author cwei
*/
public final class QuakeFileSystem {
/*
* ==================================================
*
* QUAKE FILESYSTEM
*
* ==================================================
*/
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;
public static ConsoleVariable fs_basedir;
public static ConsoleVariable fs_cddir;
public static ConsoleVariable fs_gamedirvar;
public static class filelink_t {
String from;
int fromlength;
String to;
}
// with filelink_t entries
public static List fs_links = new LinkedList();
public static class searchpath_t {
String filename;
pack_t pack; // only one of filename or pack will be used
searchpath_t next;
}
public static searchpath_t fs_searchpaths;
// without gamedirs
public static searchpath_t fs_base_searchpaths;
/*
* All of Quake's data access is through a hierchal file system, but the
* contents of the file system can be transparently merged from several
* sources.
*
* The "base directory" is the path to the directory holding the quake.exe
* and all game directories. The sys_* files pass this to host_init in
* quakeparms_t->basedir. This can be overridden with the "-basedir" command
* line parm to allow code debugging in a different directory. The base
* directory is only used during filesystem initialization.
*
* The "game directory" is the first tree on the search path and directory
* that all generated files (savegames, screenshots, demos, config files)
* will be saved to. This can be overridden with the "-game" command line
* parameter. The game directory can never be changed while quake is
* executing. This is a precacution against having a malicious server
* instruct clients to write files over areas they shouldn't.
*
*/
/*
* CreatePath
*
* Creates any directories needed to store the given filename.
*/
public static void CreatePath(String path) {
File f = new File(path).getParentFile();
if (f != null && !f.mkdirs() && !f.isDirectory()) {
Com.Printf("can't create path \"" + path + '"' + "\n");
}
}
/*
* FCloseFile
*
* For some reason, other dll's can't just call fclose() on files returned
* by FS_FOpenFile...
*/
public static void FCloseFile(RandomAccessFile file) throws IOException {
file.close();
}
public static void FCloseFile(InputStream stream) throws IOException {
stream.close();
}
public static int FileLength(String filename) {
searchpath_t search;
String netpath;
pack_t pak;
int i;
filelink_t link;
file_from_pak = 0;
// check for links first
for (Iterator it = fs_links.iterator(); it.hasNext();) {
link = (filelink_t) 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 = (packfile_t) 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;
}
public static int file_from_pak = 0;
/*
* 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.
*/
public static RandomAccessFile FOpenFile(String filename)
throws IOException {
searchpath_t search;
String netpath;
pack_t pak;
int i;
filelink_t link;
File file = null;
file_from_pak = 0;
// check for links first
for (Iterator it = fs_links.iterator(); it.hasNext();) {
link = (filelink_t) 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 = (packfile_t) 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;
}
// read in blocks of 64k
public static final int MAX_READ = 0x10000;
/**
* Read
*
* Properly handles partial reads
*/
public static void Read(byte[] buffer, int len, RandomAccessFile f) {
int block, remaining;
int offset = 0;
int read = 0;
boolean tries = true;
// read in chunks for progress bar
remaining = len;
while (remaining != 0) {
block = Math.min(remaining, MAX_READ);
try {
read = f.read(buffer, offset, block);
} catch (IOException e) {
Com.Error(Constants.ERR_FATAL, e.toString());
}
if (read == 0) {
Com.Error(Constants.ERR_FATAL, "FS_Read: 0 bytes read");
} else if (read == -1) {
Com.Error(Constants.ERR_FATAL, "FS_Read: -1 bytes read");
}
//
// do some progress bar thing here...
//
remaining -= read;
offset += read;
}
}
/*
* LoadFile
*
* Filename are reletive to the quake search path a null buffer will just
* return the file content as byte[]
*/
public static 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(Constants.ERR_FATAL, e.toString());
}
return buf;
}
//
// /*
// * LoadMappedFile
// *
// * Filename are reletive to the quake search path a null buffer will just
// * return the file content as ByteBuffer (memory mapped)
// */
// public static ByteBuffer LoadMappedFile(String filename) {
// searchpath_t search;
// String netpath;
// pack_t pak;
// int i;
// filelink_t link;
// File file = null;
//
// int fileLength = 0;
// FileChannel channel = null;
// FileInputStream input = null;
// ByteBuffer buffer = null;
//
// file_from_pak = 0;
//
// try {
// // check for links first
// for (Iterator it = fs_links.iterator(); it.hasNext();) {
// link = (filelink_t) it.next();
//
// if (filename.regionMatches(0, link.from, 0, link.fromlength)) {
// netpath = link.to + filename.substring(link.fromlength);
// file = new File(netpath);
// if (file.canRead()) {
// input = new FileInputStream(file);
// channel = input.getChannel();
// fileLength = (int) channel.size();
// buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
// fileLength);
// input.close();
// return buffer;
// }
// 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 = (packfile_t) 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
// if (pak.backbuffer == null) {
// channel = pak.handle.getChannel();
// pak.backbuffer = channel.map(
// FileChannel.MapMode.READ_ONLY, 0,
// pak.handle.length());
// channel.close();
// }
// pak.backbuffer.position(entry.filepos);
// buffer = pak.backbuffer.slice();
// buffer.limit(entry.filelen);
// return buffer;
// }
// } 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');
// input = new FileInputStream(file);
// channel = input.getChannel();
// fileLength = (int) channel.size();
// buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
// fileLength);
// input.close();
// return buffer;
// }
// }
// } catch (Exception e) {
// }
// try {
// if (input != null)
// input.close();
// else if (channel != null && channel.isOpen())
// channel.close();
// } catch (IOException ioe) {
// }
// return null;
// }
/*
* FreeFile
*/
public static void FreeFile(byte[] buffer) {
buffer = null;
}
static final int IDPAKHEADER = (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P');
static class dpackheader_t {
int ident; // IDPAKHEADER
int dirofs;
int dirlen;
}
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
*
* Sets fs_gamedir, adds the directory to the head of the path, then loads
* and adds pak1.pak pak2.pak ...
*/
static 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;
// }
}
/*
* Gamedir
*
* Called to find where to write a file (demos, savegames, etc)
* this is modified to <user.home>/.jake2
*/
public static String Gamedir() {
return (fs_userdir != null) ? fs_userdir : Constants.BASEDIRNAME;
}
/*
* BaseGamedir
*
* Called to find where to write a downloaded file
*/
public static String BaseGamedir() {
return (fs_gamedir != null) ? fs_gamedir : Constants.BASEDIRNAME;
}
/*
* ExecAutoexec
*/
public static void ExecAutoexec() {
String dir = fs_userdir;
String name;
if (dir != null && dir.length() > 0) {
name = dir + "/autoexec.cfg";
} else {
name = fs_basedir.string + '/' + Constants.BASEDIRNAME
+ "/autoexec.cfg";
}
int canthave = Constants.SFF_SUBDIR | Constants.SFF_HIDDEN
| Constants.SFF_SYSTEM;
if (Sys.FindAll(name, 0, canthave) != null) {
CommandBuffer.AddText("exec autoexec.cfg\n");
}
}
/*
* SetGamedir
*
* Sets the gamedir and path to a different directory.
*/
public static void SetGamedir(String dir) {
searchpath_t next;
if (dir.indexOf("..") != -1 || dir.indexOf("/") != -1
|| dir.indexOf("\\") != -1 || dir.indexOf(":") != -1) {
Com.Printf("Gamedir should be a single filename, not a path\n");
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))
CommandBuffer.AddText("vid_restart\nsnd_restart\n");
fs_gamedir = fs_basedir.string + '/' + dir;
if (dir.equals(Constants.BASEDIRNAME) || (dir.length() == 0)) {
ConsoleVariables.FullSet("gamedir", "", Constants.CVAR_SERVERINFO | Constants.CVAR_NOSET);
ConsoleVariables.FullSet("game", "", Constants.CVAR_LATCH | Constants.CVAR_SERVERINFO);
} else {
ConsoleVariables.FullSet("gamedir", dir, Constants.CVAR_SERVERINFO | Constants.CVAR_NOSET);
if (fs_cddir.string != null && fs_cddir.string.length() > 0)
AddGameDirectory(fs_cddir.string + '/' + dir);
AddGameDirectory(fs_basedir.string + '/' + dir);
}
}
/*
* Link_f
*
* Creates a filelink_t
*/
public static void Link_f() {
filelink_t entry = null;
if (Commands.Argc() != 3) {
Com.Printf("USAGE: link <from> <to>\n");
return;
}
// see if the link already exists
for (Iterator it = fs_links.iterator(); it.hasNext();) {
entry = (filelink_t) it.next();
if (entry.from.equals(Commands.Argv(1))) {
if (Commands.Argv(2).length() < 1) {
// delete it
it.remove();
return;
}
entry.to = new String(Commands.Argv(2));
return;
}
}
// create a new link if the <to> is not empty
if (Commands.Argv(2).length() > 0) {
entry = new filelink_t();
entry.from = new String(Commands.Argv(1));
entry.fromlength = entry.from.length();
entry.to = new String(Commands.Argv(2));
fs_links.add(entry);
}
}
/*
* ListFiles
*/
public static String[] ListFiles(String findname, int musthave, int canthave) {
String[] list = null;
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;
}
/*
* Dir_f
*/
public static void Dir_f() {
String path = null;
String findname = null;
String wildcard = "*.*";
String[] dirnames;
if (Commands.Argc() != 1) {
wildcard = Commands.Argv(1);
}
while ((path = NextPath(path)) != null) {
String tmp = findname;
findname = path + '/' + wildcard;
if (tmp != null)
tmp.replaceAll("\\\\", "/");
Com.Printf("Directory of " + findname + '\n');
Com.Printf("----\n");
dirnames = ListFiles(findname, 0, 0);
if (dirnames != null) {
int index = 0;
for (int i = 0; i < dirnames.length; i++) {
if ((index = dirnames[i].lastIndexOf('/')) > 0) {
Com.Printf(dirnames[i].substring(index + 1, dirnames[i]
.length()) + '\n');
} else {
Com.Printf(dirnames[i] + '\n');
}
}
}
Com.Printf("\n");
}
}
/*
* Path_f
*/
public static void Path_f() {
searchpath_t s;
filelink_t link;
Com.Printf("Current search path:\n");
for (s = fs_searchpaths; s != null; s = s.next) {
if (s == fs_base_searchpaths)
Com.Printf("----------\n");
if (s.pack != null)
Com.Printf(s.pack.filename + " (" + s.pack.numfiles
+ " files)\n");
else
Com.Printf(s.filename + '\n');
}
Com.Printf("\nLinks:\n");
for (Iterator it = fs_links.iterator(); it.hasNext();) {
link = (filelink_t) it.next();
Com.Printf(link.from + " : " + link.to + '\n');
}
}
/*
* NextPath
*
* Allows enumerating all of the directories in the search path
*/
public static 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;
}
/*
* InitFilesystem
*/
public static void InitFilesystem() {
Commands.addCommand("path", new ExecutableCommand() {
public void execute() {
Path_f();
}
});
Commands.addCommand("link", new ExecutableCommand() {
public void execute() {
Link_f();
}
});
Commands.addCommand("dir", new ExecutableCommand() {
public void execute() {
Dir_f();
}
});
fs_userdir = System.getProperty("user.home") + "/.jake2";
QuakeFileSystem.CreatePath(fs_userdir + "/");
QuakeFileSystem.AddGameDirectory(fs_userdir);
//
// basedir <path>
// allows the game to run from outside the data tree
//
fs_basedir = ConsoleVariables.Get("basedir", ".", Constants.CVAR_NOSET);
//
// cddir <path>
// 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 + '/' + Constants.BASEDIRNAME);
// any set gamedirs will be freed up to here
markBaseSearchPaths();
// check for game override
fs_gamedirvar = ConsoleVariables.Get("game", "", Constants.CVAR_LATCH | Constants.CVAR_SERVERINFO);
if (fs_gamedirvar.string.length() > 0)
SetGamedir(fs_gamedirvar.string);
}
/**
* set baseq2 directory
*/
static void setCDDir() {
fs_cddir = ConsoleVariables.Get("cddir", "", Constants.CVAR_ARCHIVE);
if (fs_cddir.string.length() > 0)
AddGameDirectory(fs_cddir.string);
}
static void markBaseSearchPaths() {
// any set gamedirs will be freed up to here
fs_base_searchpaths = fs_searchpaths;
}
// RAFAEL
/*
* Developer_searchpath
*/
public static int Developer_searchpath(int who) {
int ch;
// PMM - warning removal
// char *start;
searchpath_t s;
if (who == 1) // xatrix
ch = 'x';
else if (who == 2)
ch = 'r';
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;
}
}