/*
* This file is part of the Haven & Hearth game client.
* Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and
* Björn Johannessen <johannessen.bjorn@gmail.com>
*
* Redistribution and/or modification of this file is subject to the
* terms of the GNU Lesser General Public License, version 3, as
* published by the Free Software Foundation.
*
* 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.
*
* Other parts of this source tree adhere to other copying
* rights. Please see the file `COPYING' in the root directory of the
* source tree for details.
*
* A copy the GNU Lesser General Public License is distributed along
* with the source tree of which this file is a part in the file
* `doc/LPGL-3'. If it is missing for any reason, please see the Free
* Software Foundation's website at <http://www.fsf.org/>, or write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package haven;
import haven.error.ErrorLogFormatter;
import haven.error.LoggingOutputStream;
import java.lang.reflect.*;
import java.awt.Dimension;
import java.awt.DisplayMode;
import java.awt.Frame;
import java.awt.GraphicsDevice;
import java.awt.Image;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.Writer;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class MainFrame extends Frame implements Runnable, Console.Directory {
private static final String TITLE = String.format("Salem (modified by Ender v%s)", Config.version);
public static MainFrame instance;
HavenPanel p;
private final ThreadGroup g;
public final Thread mt;
DisplayMode fsmode = null, prefs = null;
static {
try {
javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
} catch(Exception e) {}
}
DisplayMode findmode(int w, int h) {
GraphicsDevice dev = getGraphicsConfiguration().getDevice();
if(!dev.isFullScreenSupported())
return(null);
DisplayMode b = null;
for(DisplayMode m : dev.getDisplayModes()) {
int d = m.getBitDepth();
if((m.getWidth() == w) && (m.getHeight() == h) && ((d == 24) || (d == 32) || (d == DisplayMode.BIT_DEPTH_MULTI))) {
if((b == null) || (d > b.getBitDepth()) || ((d == b.getBitDepth()) && (m.getRefreshRate() > b.getRefreshRate())))
b = m;
}
}
return(b);
}
public void setfs() {
GraphicsDevice dev = getGraphicsConfiguration().getDevice();
if(prefs != null)
return;
prefs = dev.getDisplayMode();
try {
setVisible(false);
dispose();
setUndecorated(true);
setVisible(true);
dev.setFullScreenWindow(this);
dev.setDisplayMode(fsmode);
pack();
} catch(Exception e) {
throw(new RuntimeException(e));
}
}
public void setwnd() {
GraphicsDevice dev = getGraphicsConfiguration().getDevice();
if(prefs == null)
return;
try {
dev.setDisplayMode(prefs);
dev.setFullScreenWindow(null);
setVisible(false);
dispose();
setUndecorated(false);
setVisible(true);
} catch(Exception e) {
throw(new RuntimeException(e));
}
prefs = null;
}
public boolean hasfs() {
return(prefs != null);
}
private Map<String, Console.Command> cmdmap = new TreeMap<String, Console.Command>();
{
cmdmap.put("sz", new Console.Command() {
public void run(Console cons, String[] args) {
if(args.length == 3) {
int w = Integer.parseInt(args[1]),
h = Integer.parseInt(args[2]);
p.setSize(w, h);
pack();
Utils.setprefc("wndsz", new Coord(w, h));
} else if(args.length == 2) {
if(args[1].equals("dyn")) {
setResizable(true);
Utils.setprefb("wndlock", false);
} else if(args[1].equals("lock")) {
setResizable(false);
Utils.setprefb("wndlock", true);
}
}
}
});
cmdmap.put("fsmode", new Console.Command() {
public void run(Console cons, String[] args) throws Exception {
if(args.length == 3) {
DisplayMode mode = findmode(Integer.parseInt(args[1]), Integer.parseInt(args[2]));
if(mode == null)
throw(new Exception("No such mode is available"));
fsmode = mode;
Utils.setprefc("fsmode", new Coord(mode.getWidth(), mode.getHeight()));
}
}
});
cmdmap.put("fs", new Console.Command() {
public void run(Console cons, String[] args) {
if(args.length >= 2) {
Runnable r;
if(Utils.atoi(args[1]) != 0) {
r = new Runnable() {
public void run() {
setfs();
}
};
} else {
r = new Runnable() {
public void run() {
setwnd();
}
};
}
getToolkit().getSystemEventQueue().invokeLater(r);
}
}
});
}
public Map<String, Console.Command> findcmds() {
return(cmdmap);
}
private void seticon() {
Image icon;
try {
InputStream data = MainFrame.class.getResourceAsStream("icon.gif");
icon = javax.imageio.ImageIO.read(data);
data.close();
} catch(IOException e) {
throw(new Error(e));
}
setIconImage(icon);
}
public MainFrame(Coord isz) {
super(TITLE);
instance = this;
Coord sz;
if(isz == null) {
sz = Utils.getprefc("wndsz", new Coord(800, 600));
if(sz.x < 640) sz.x = 640;
if(sz.y < 480) sz.y = 480;
} else {
sz = isz;
}
this.g = new ThreadGroup(HackThread.tg(), "Haven client");
this.mt = new HackThread(this.g, this, "Haven main thread");
p = new HavenPanel(sz.x, sz.y);
if(fsmode == null) {
Coord pfm = Utils.getprefc("fsmode", null);
if(pfm != null)
fsmode = findmode(pfm.x, pfm.y);
}
if(fsmode == null) {
DisplayMode cm = getGraphicsConfiguration().getDevice().getDisplayMode();
fsmode = findmode(cm.getWidth(), cm.getHeight());
}
if(fsmode == null)
fsmode = findmode(800, 600);
add(p);
pack();
setResizable(!Utils.getprefb("wndlock", false));
p.requestFocus();
seticon();
setVisible(true);
p.init();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
g.interrupt();
}
});
if((isz == null) && Utils.getprefb("wndmax", false))
setExtendedState(getExtendedState() | MAXIMIZED_BOTH);
}
@Override
public void setTitle(String charname) {
String str = TITLE;
if(charname != null){
str = charname+" - "+str;
}
super.setTitle(str);
}
private void savewndstate() {
if(prefs == null) {
if(getExtendedState() == NORMAL)
/* Apparent, getSize attempts to return the "outer
* size" of the window, including WM decorations, even
* though setSize sets the "inner size" of the
* window. Therefore, use the Panel's size instead; it
* ought to correspond to the inner size at all
* times. */{
Dimension dim = p.getSize();
Utils.setprefc("wndsz", new Coord(dim.width, dim.height));
}
Utils.setprefb("wndmax", (getExtendedState() & MAXIMIZED_BOTH) != 0);
}
}
public void run() {
if(Thread.currentThread() != this.mt)
throw(new RuntimeException("MainFrame is being run from an invalid context"));
Thread ui = new HackThread(p, "Haven UI thread");
ui.start();
try {
try {
Session sess = null;
while(true) {
UI.Runner fun;
if(sess == null) {
Bootstrap bill = new Bootstrap(Config.defserv, Config.mainport);
if((Config.authuser != null) && (Config.authck != null)) {
bill.setinitcookie(Config.authuser, Config.authck);
Config.authck = null;
}
fun = bill;
} else {
fun = new RemoteUI(sess);
}
sess = fun.run(p.newui(sess));
}
} catch(InterruptedException e) {}
savewndstate();
} finally {
ui.interrupt();
dispose();
}
}
public static void setupres() {
if(ResCache.global != null)
Resource.addcache(ResCache.global);
if(Config.resurl != null)
Resource.addurl(Config.resurl);
if(ResCache.global != null) {
try {
Resource.loadlist(ResCache.global.fetch("tmp/allused"), -10);
} catch(IOException e) {}
}
if(!Config.nopreload) {
try {
InputStream pls;
pls = Resource.class.getResourceAsStream("res-preload");
if(pls != null)
Resource.loadlist(pls, -5);
pls = Resource.class.getResourceAsStream("res-bgload");
if(pls != null)
Resource.loadlist(pls, -10);
} catch(IOException e) {
throw(new Error(e));
}
}
}
static {
if((WebBrowser.self = JnlpBrowser.create()) == null)
WebBrowser.self = DesktopBrowser.create();
}
private static void netxsurgery() throws Exception {
/* Force off NetX codebase classloading. */
Class<?> nxc;
try {
nxc = Class.forName("net.sourceforge.jnlp.runtime.JNLPClassLoader");
} catch(ClassNotFoundException e1) {
try {
nxc = Class.forName("netx.jnlp.runtime.JNLPClassLoader");
} catch(ClassNotFoundException e2) {
throw(new Exception("No known NetX on classpath"));
}
}
ClassLoader cl = MainFrame.class.getClassLoader();
if(!nxc.isInstance(cl)) {
throw(new Exception("Not running from a NetX classloader"));
}
Field cblf, lf;
try {
cblf = nxc.getDeclaredField("codeBaseLoader");
lf = nxc.getDeclaredField("loaders");
} catch(NoSuchFieldException e) {
throw(new Exception("JNLPClassLoader does not conform to its known structure"));
}
cblf.setAccessible(true);
lf.setAccessible(true);
Set<Object> loaders = new HashSet<Object>();
Stack<Object> open = new Stack<Object>();
open.push(cl);
while(!open.empty()) {
Object cur = open.pop();
if(loaders.contains(cur))
continue;
loaders.add(cur);
Object curl;
try {
curl = lf.get(cur);
} catch(IllegalAccessException e) {
throw(new Exception("Reflection accessibility not available even though set"));
}
for(int i = 0; i < Array.getLength(curl); i++) {
Object other = Array.get(curl, i);
if(nxc.isInstance(other))
open.push(other);
}
}
for(Object cur : loaders) {
try {
cblf.set(cur, null);
} catch(IllegalAccessException e) {
throw(new Exception("Reflection accessibility not available even though set"));
}
}
}
private static void javabughack() throws InterruptedException {
/* Work around a stupid deadlock bug in AWT. */
try {
javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
PrintStream bitbucket = new PrintStream(new ByteArrayOutputStream());
bitbucket.print(LoginScreen.textf);
bitbucket.print(LoginScreen.textfs);
}
});
} catch(java.lang.reflect.InvocationTargetException e) {
/* Oh, how I love Swing! */
throw(new Error(e));
}
/* Work around another deadl bug in Sun's JNLP client. */
javax.imageio.spi.IIORegistry.getDefaultInstance();
try {
netxsurgery();
} catch(Exception e) {
}
}
private static void main2(String[] args) {
Config.cmdline(args);
try {
javabughack();
} catch(InterruptedException e) {
return;
}
setupres();
MainFrame f = new MainFrame(null);
if(Utils.getprefb("fullscreen", false))
f.setfs();
f.mt.start();
try {
f.mt.join();
} catch(InterruptedException e) {
f.g.interrupt();
return;
}
dumplist(Resource.loadwaited, Config.loadwaited);
dumplist(Resource.cached(), Config.allused);
if(ResCache.global != null) {
try {
Collection<Resource> used = new LinkedList<Resource>();
for(Resource res : Resource.cached()) {
if(res.prio >= 0) {
try {
res.checkerr();
} catch(Exception e) {
continue;
}
used.add(res);
}
}
Writer w = new OutputStreamWriter(ResCache.global.store("tmp/allused"), "UTF-8");
try {
Resource.dumplist(used, w);
} finally {
w.close();
}
} catch(IOException e) {}
}
System.exit(0);
}
public static void main(final String[] args) {
/* Set up the error handler as early as humanly possible. */
ThreadGroup g = new ThreadGroup("Haven main group");
String ed;
if(!(ed = Utils.getprop("haven.errorurl", "")).equals("")) {
try {
final haven.error.ErrorHandler hg = new haven.error.ErrorHandler(new java.net.URL(ed));
hg.sethandler(new haven.error.ErrorGui(null) {
public void errorsent() {
hg.interrupt();
}
});
g = hg;
} catch(java.net.MalformedURLException e) {
}
} else {
final haven.error.ErrorHandler hg = new haven.error.ErrorHandler();
hg.sethandler(new haven.error.ErrorGui(null) {
public void errorsent() {
hg.interrupt();
}
});
g = hg;
}
initErrorLogs();
Thread main = new HackThread(g, new Runnable() {
public void run() {
main2(args);
}
}, "Haven main thread");
main.start();
}
private static void initErrorLogs() {
// initialize logging to go to rolling log file
LogManager logManager = LogManager.getLogManager();
logManager.reset();
// log file max size 10K, 3 rolling files, append-on-open
Handler fileHandler;
try {
fileHandler = new FileHandler("%h/Salem/error_%g.log", 10000, 3, true);
fileHandler.setFormatter(new ErrorLogFormatter());
Logger.getLogger("").addHandler(fileHandler);
OutputStream los = new LoggingOutputStream(Logger.getLogger("stderr"), Level.SEVERE);
System.setErr(new PrintStream(los, true));
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void dumplist(Collection<Resource> list, String fn) {
try {
if(fn != null) {
Writer w = new OutputStreamWriter(new FileOutputStream(fn), "UTF-8");
try {
Resource.dumplist(list, w);
} finally {
w.close();
}
}
} catch(IOException e) {
throw(new RuntimeException(e));
}
}
}