/* * 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.pathfinder.Node; import java.awt.Color; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.TreeMap; import javax.imageio.ImageIO; import ender.SkillAvailability; public class Resource implements Comparable<Resource>, Prioritized, Serializable { private static final Color skillUnavailableColor = new Color(255, 64, 98, 225); private static final Color skillAvailableColor = new Color(160, 255, 64, 255); private static Map<String, Resource> cache = new TreeMap<String, Resource>(); private static Loader loader; private static CacheSource prscache; public static ThreadGroup loadergroup = null; private static Map<String, Class<? extends Layer>> ltypes = new TreeMap<String, Class<? extends Layer>>(); static Set<Resource> loadwaited = new HashSet<Resource>(); public static Class<Image> imgc = Image.class; public static Class<Tile> tile = Tile.class; public static Class<Neg> negc = Neg.class; public static Class<Anim> animc = Anim.class; public static Class<Tileset> tileset = Tileset.class; public static Class<Pagina> pagina = Pagina.class; public static Class<AButton> action = AButton.class; public static Class<Audio> audio = Audio.class; public static Class<Tooltip> tooltip = Tooltip.class; public boolean hide = false, once = false, skiphighlight = false, skiphide = false; static { try { chainloader(new Loader(new FileSource(new File("./custom_res")))); } catch (Exception e) { /* * Ignore these. We don't want to be crashing the client for users * just because of errors in development aids. */ } try { chainloader(new Loader(new FileSource(new File("./res")))); } catch (Exception e) { /* * Ignore these. We don't want to be crashing the client for users * just because of errors in development aids. */ } try { String dir = Config.resdir; if (dir == null) dir = System.getenv("HAVEN_RESDIR"); if (dir != null) chainloader(new Loader(new FileSource(new File(dir)))); } catch (Exception e) { /* * Ignore these. We don't want to be crashing the client for users * just because of errors in development aids. */ } if (!Config.nolocalres) chainloader(new Loader(new JarSource())); } private LoadException error; private Collection<? extends Layer> layers = new LinkedList<Layer>(); public final String name; public int ver; public boolean loading; public ResSource source; private transient Indir<Resource> indir = null; int prio = 0; private Color skillColorCache; private long cachedTime; private Resource(String name, int ver) { this.name = name; this.ver = ver; error = null; loading = true; skiphighlight = name.contains("wald") || name.contains("flavobjs"); skiphide = name.contains("door"); checkhidden(); } public static void checkhide() { synchronized (cache) { for (Resource res : cache.values()) { res.checkhidden(); } } } public void checkhidden() { hide = false; for (String item : Config.hideObjectList) { if (name.contains(item)) { hide = true; break; } } } public static void addcache(ResCache cache) { CacheSource src = new CacheSource(cache); prscache = src; chainloader(new Loader(src)); } public static void addurl(URL url) { ResSource src = new HttpSource(url); final CacheSource mc = prscache; if (mc != null) { src = new TeeSource(src) { public OutputStream fork(String name) throws IOException { return (mc.cache.store("res/" + name)); } }; } chainloader(new Loader(src)); } private static void chainloader(Loader nl) { synchronized (Resource.class) { if (loader == null) { loader = nl; } else { Loader l; for (l = loader; l.next != null; l = l.next) ; l.chain(nl); } } } public static Resource load(String name, int ver, int prio) { Resource res; synchronized (cache) { res = cache.get(name); if (res != null) { if ((res.ver != -1) && (ver != -1)) { if (res.ver < ver) { res = null; cache.remove(name); } else if (res.ver > ver) { throw (new RuntimeException(String.format("Weird version number on %s (%d > %d), loaded from %s", res.name, res.ver, ver, res.source))); } } else if (ver == -1) { if (res.error != null) { res = null; cache.remove(name); } } } if (res != null) { res.boostprio(prio); return (res); } res = new Resource(name, ver); res.prio = prio; cache.put(name, res); } loader.load(res); return (res); } public static int numloaded() { return (cache.size()); } public static Collection<Resource> cached() { return (cache.values()); } public static Resource load(String name, int ver) { return (load(name, ver, 0)); } public static int qdepth() { int ret = 0; for (Loader l = loader; l != null; l = l.next) ret += l.queue.size(); return (ret); } public static Resource load(String name) { return (load(name, -1)); } public void boostprio(int newprio) { if (prio < newprio) prio = newprio; } public void loadwaitint() throws InterruptedException { synchronized (this) { boostprio(10); while (loading) { wait(); } } } public String basename() { int p = name.lastIndexOf('/'); if (p < 0) return (name); return (name.substring(p + 1)); } public void loadwait() { boolean i = false; synchronized (loadwaited) { loadwaited.add(this); } synchronized (this) { boostprio(10); while (loading) { try { wait(); } catch (InterruptedException e) { i = true; } } } if (i) Thread.currentThread().interrupt(); } public static interface ResSource { public InputStream get(String name) throws IOException; } public static abstract class TeeSource implements ResSource, Serializable { public ResSource back; public TeeSource(ResSource back) { this.back = back; } public InputStream get(String name) throws IOException { StreamTee tee = new StreamTee(back.get(name)); tee.setncwe(); tee.attach(fork(name)); return (tee); } public abstract OutputStream fork(String name) throws IOException; public String toString() { return ("forking source backed by " + back); } } public static class CacheSource implements ResSource, Serializable { public transient ResCache cache; public CacheSource(ResCache cache) { this.cache = cache; } public InputStream get(String name) throws IOException { return (cache.fetch("res/" + name)); } public String toString() { return ("cache source backed by " + cache); } } public static class FileSource implements ResSource, Serializable { File base; public FileSource(File base) { this.base = base; } public InputStream get(String name) { File cur = base; String[] parts = name.split("/"); for (int i = 0; i < parts.length - 1; i++) cur = new File(cur, parts[i]); cur = new File(cur, parts[parts.length - 1] + ".res"); try { return (new FileInputStream(cur)); } catch (FileNotFoundException e) { throw ((LoadException) (new LoadException("Could not find resource in filesystem: " + name, this).initCause(e))); } } public String toString() { return ("filesystem res source (" + base + ")"); } } public static class JarSource implements ResSource, Serializable { public InputStream get(String name) { InputStream s = Resource.class.getResourceAsStream("/res/" + name + ".res"); if (s == null) throw (new LoadException("Could not find resource locally: " + name, JarSource.this)); return (s); } public String toString() { return ("local res source"); } } public static class HttpSource implements ResSource, Serializable { private final transient SslHelper ssl; public URL baseurl; { ssl = new SslHelper(); try { ssl.trust(ssl.loadX509(Resource.class.getResourceAsStream("ressrv.crt"))); } catch (java.security.cert.CertificateException e) { throw (new Error("Invalid built-in certificate", e)); } catch (IOException e) { throw (new Error(e)); } ssl.ignoreName(); } public HttpSource(URL baseurl) { this.baseurl = baseurl; } private URL encodeurl(URL raw) throws IOException { /* * This is "kinda" ugly. It is, actually, how the Java documentation * recommend that it be done, though... */ try { return (new URL(new URI(raw.getProtocol(), raw.getHost(), raw.getPath(), raw.getRef()).toASCIIString())); } catch (URISyntaxException e) { throw (new IOException(e)); } } public InputStream get(String name) throws IOException { URL resurl = encodeurl(new URL(baseurl, name + ".res")); URLConnection c; if (resurl.getProtocol().equals("https")) c = ssl.connect(resurl); else c = resurl.openConnection(); c.addRequestProperty("User-Agent", "Haven/1.0"); return (c.getInputStream()); } public String toString() { return ("HTTP res source (" + baseurl + ")"); } } private static class Loader implements Runnable { private ResSource src; private Loader next = null; private Queue<Resource> queue = new PrioQueue<Resource>(); private transient Thread th = null; public Loader(ResSource src) { this.src = src; } public void chain(Loader next) { this.next = next; } public void load(Resource res) { synchronized (queue) { queue.add(res); queue.notifyAll(); } synchronized (Loader.this) { if (th == null) { th = new HackThread(loadergroup, Loader.this, "Haven resource loader"); th.setDaemon(true); th.start(); } } } public void run() { try { while (true) { Resource cur; synchronized (queue) { while ((cur = queue.poll()) == null) queue.wait(); } synchronized (cur) { handle(cur); } cur = null; } } catch (InterruptedException e) { } finally { synchronized (Loader.this) { /* Yes, I know there's a race condition. */ th = null; } } } private void handle(Resource res) { InputStream in = null; try { res.error = null; res.source = src; try { try { in = src.get(res.name); res.load(in); res.loading = false; res.notifyAll(); return; } catch (IOException e) { throw (new LoadException(e, res)); } } catch (LoadException e) { if (next == null) { res.error = e; res.loading = false; res.notifyAll(); } else { next.load(res); } } catch (RuntimeException e) { throw (new LoadException(e, res)); } } finally { try { if (in != null) in.close(); } catch (IOException e) { } } } } public static class LoadException extends RuntimeException { public Resource res; public ResSource src; public LoadException(String msg, ResSource src) { super(msg); this.src = src; } public LoadException(String msg, Resource res) { super(msg); this.res = res; } public LoadException(String msg, Throwable cause, Resource res) { super(msg, cause); this.res = res; } public LoadException(Throwable cause, Resource res) { super("Load error in resource " + res.toString() + ", from " + res.source, cause); this.res = res; } } public static Coord cdec(byte[] buf, int off) { return (new Coord(Utils.int16d(buf, off), Utils.int16d(buf, off + 2))); } public abstract class Layer implements Serializable { public abstract void init(); } public class Image extends Layer implements Comparable<Image> { public transient BufferedImage img; transient private Tex tex; public final int z, subz; public final boolean nooff; public final int id; private int gay = -1; public Coord sz; public Coord o; public Image(byte[] buf) { z = Utils.int16d(buf, 0); subz = Utils.int16d(buf, 2); /* Obsolete flag 1: Layered */ nooff = (buf[4] & 2) != 0; id = Utils.int16d(buf, 5); o = cdec(buf, 7); try { img = ImageIO.read(new ByteArrayInputStream(buf, 11, buf.length - 11)); } catch (IOException e) { throw (new LoadException(e, Resource.this)); } if (img == null) throw (new LoadException("Invalid image data in " + name, Resource.this)); sz = Utils.imgsz(img); } public synchronized Tex tex() { if (tex != null) return (tex); tex = new TexI(img); return (tex); } private boolean detectgay() { for (int y = 0; y < sz.y; y++) { for (int x = 0; x < sz.x; x++) { if ((img.getRGB(x, y) & 0x00ffffff) == 0x00ff0080) return (true); } } return (false); } public boolean gayp() { if (gay == -1) gay = detectgay() ? 1 : 0; return (gay == 1); } public int compareTo(Image other) { return (z - other.z); } public void init() { } } static { ltypes.put("image", Image.class); } public class Tooltip extends Layer { public final String t; public Tooltip(byte[] buf) { try { t = new String(buf, "UTF-8"); } catch (UnsupportedEncodingException e) { throw (new LoadException(e, Resource.this)); } } public void init() { } } static { ltypes.put("tooltip", Tooltip.class); } private Node.Type tileType; public class Tile extends Layer { transient BufferedImage img; transient private Tex tex; int id; int w; char t; public Tile(byte[] buf) { t = (char) Utils.ub(buf[0]); id = Utils.ub(buf[1]); w = Utils.uint16d(buf, 2); try { img = ImageIO.read(new ByteArrayInputStream(buf, 4, buf.length - 4)); } catch (IOException e) { throw (new LoadException(e, Resource.this)); } if (img == null) throw (new LoadException("Invalid image data in " + name, Resource.this)); } public synchronized Tex tex() { if (tex == null) tex = new TexI(img); return (tex); } public void init() { } public Resource getOuter() { return Resource.this; } public Node.Type resolveTileType() { if (tileType != null) return tileType; // remove "gfx/" prefix String resnameStripped = name.substring(4); if (!Config.obTypes.containsKey(resnameStripped)) tileType = Node.Type.NOT_IMPLEMENTED; else tileType = Config.obTypes.get(resnameStripped); return tileType; } } static { ltypes.put("tile", Tile.class); } public class Neg extends Layer { public Coord cc; public Coord bc, bs; public Coord sz; public Coord[][] ep; public Neg(byte[] buf) { int off; cc = cdec(buf, 0); bc = cdec(buf, 4); bs = cdec(buf, 8); sz = cdec(buf, 12); bc = MapView.s2m(bc); bs = MapView.s2m(bs).add(bc.inv()); ep = new Coord[8][0]; int en = buf[16]; off = 17; for (int i = 0; i < en; i++) { int epid = buf[off]; int cn = Utils.uint16d(buf, off + 1); off += 3; ep[epid] = new Coord[cn]; for (int o = 0; o < cn; o++) { ep[epid][o] = cdec(buf, off); off += 4; } } } public void init() { } } static { ltypes.put("neg", Neg.class); } public class Anim extends Layer { private int[] ids; public int id, d; public Image[][] f; public Anim(byte[] buf) { id = Utils.int16d(buf, 0); d = Utils.uint16d(buf, 2); ids = new int[Utils.uint16d(buf, 4)]; if (buf.length - 6 != ids.length * 2) throw (new LoadException("Invalid anim descriptor in " + name, Resource.this)); for (int i = 0; i < ids.length; i++) ids[i] = Utils.int16d(buf, 6 + (i * 2)); } public void init() { f = new Image[ids.length][]; Image[] typeinfo = new Image[0]; for (int i = 0; i < ids.length; i++) { LinkedList<Image> buf = new LinkedList<Image>(); for (Image img : layers(Image.class)) { if (img.id == ids[i]) buf.add(img); } f[i] = buf.toArray(typeinfo); } } } static { ltypes.put("anim", Anim.class); } public class Tileset extends Layer { private int fl; private String[] fln; private int[] flv; private int[] flw; WeightList<Resource> flavobjs; WeightList<Tile> ground; WeightList<Tile>[] ctrans, btrans; int flavprob; public Tileset(byte[] buf) { int[] off = new int[1]; off[0] = 0; fl = Utils.ub(buf[off[0]++]); int flnum = Utils.uint16d(buf, off[0]); off[0] += 2; flavprob = Utils.uint16d(buf, off[0]); off[0] += 2; fln = new String[flnum]; flv = new int[flnum]; flw = new int[flnum]; for (int i = 0; i < flnum; i++) { fln[i] = Utils.strd(buf, off); flv[i] = Utils.uint16d(buf, off[0]); off[0] += 2; flw[i] = Utils.ub(buf[off[0]++]); } } private void packtiles(Collection<Tile> tiles, Coord tsz) { int min = -1, minw = -1, minh = -1; int nt = tiles.size(); for (int i = 1; i <= nt; i++) { int w = Tex.nextp2(tsz.x * i); int h; if ((nt % i) == 0) h = nt / i; else h = (nt / i) + 1; h = Tex.nextp2(tsz.y * h); int a = w * h; if ((min == -1) || (a < min)) { min = a; minw = w; minh = h; } } TexIM packbuf = new TexIM(new Coord(minw, minh)); Graphics g = packbuf.graphics(); int x = 0, y = 0; for (Tile t : tiles) { g.drawImage(t.img, x, y, null); t.tex = new TexSI(packbuf, new Coord(x, y), tsz); if ((x += tsz.x) > (minw - tsz.x)) { x = 0; if ((y += tsz.y) >= minh) throw (new LoadException("Could not pack tiles into calculated minimum texture", Resource.this)); } } packbuf.update(); } @SuppressWarnings("unchecked") public void init() { flavobjs = new WeightList<Resource>(); for (int i = 0; i < flw.length; i++) { try { flavobjs.add(load(fln[i], flv[i]), flw[i]); } catch (RuntimeException e) { throw (new LoadException("Illegal resource dependency", e, Resource.this)); } } Collection<Tile> tiles = new LinkedList<Tile>(); ground = new WeightList<Tile>(); boolean hastrans = (fl & 1) != 0; if (hastrans) { ctrans = new WeightList[15]; btrans = new WeightList[15]; for (int i = 0; i < 15; i++) { ctrans[i] = new WeightList<Tile>(); btrans[i] = new WeightList<Tile>(); } } Coord tsz = null; for (Tile t : layers(Tile.class)) { if (t.t == 'g') ground.add(t, t.w); else if (t.t == 'b' && hastrans) btrans[t.id - 1].add(t, t.w); else if (t.t == 'c' && hastrans) ctrans[t.id - 1].add(t, t.w); tiles.add(t); if (tsz == null) { tsz = Utils.imgsz(t.img); } else { if (!Utils.imgsz(t.img).equals(tsz)) { throw (new LoadException("Different tile sizes within set", Resource.this)); } } } packtiles(tiles, tsz); } } static { ltypes.put("tileset", Tileset.class); } public class Pagina extends Layer { public final String text; public Pagina(byte[] buf) { try { text = new String(buf, "UTF-8"); } catch (UnsupportedEncodingException e) { throw (new LoadException(e, Resource.this)); } } public void init() { } } static { ltypes.put("pagina", Pagina.class); } public class AButton extends Layer { public final String name; public final Resource parent; public final char hk; public final String[] ad; public AButton(byte[] buf) { int[] off = new int[1]; off[0] = 0; String pr = Utils.strd(buf, off); int pver = Utils.uint16d(buf, off[0]); off[0] += 2; if (pr.length() == 0) { parent = null; } else { try { parent = load(pr, pver); } catch (RuntimeException e) { throw (new LoadException("Illegal resource dependency", e, Resource.this)); } } name = Utils.strd(buf, off); Utils.strd(buf, off); /* Prerequisite skill */ hk = (char) Utils.uint16d(buf, off[0]); off[0] += 2; ad = new String[Utils.uint16d(buf, off[0])]; off[0] += 2; for (int i = 0; i < ad.length; i++) ad[i] = Utils.strd(buf, off); } public void init() { } } static { ltypes.put("action", AButton.class); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface PublishedCode { String name(); Class<? extends Instancer> instancer() default Instancer.class; public interface Instancer { public Object make(Class<?> cl) throws InstantiationException, IllegalAccessException; } } public class Code extends Layer { public final String name; transient public final byte[] data; public Code(byte[] buf) { int[] off = new int[1]; off[0] = 0; name = Utils.strd(buf, off); data = new byte[buf.length - off[0]]; System.arraycopy(buf, off[0], data, 0, data.length); } public void init() { } } static { ltypes.put("code", Code.class); } public class ResClassLoader extends ClassLoader { public ResClassLoader(ClassLoader parent) { super(parent); } public Resource getres() { return (Resource.this); } }; public class CodeEntry extends Layer { private String clnm; private Map<String, Code> clmap = new TreeMap<String, Code>(); private Map<String, String> pe = new TreeMap<String, String>(); transient private ClassLoader loader; transient private Map<String, Class<?>> lpe = new TreeMap<String, Class<?>>(); transient private Map<Class<?>, Object> ipe = new HashMap<Class<?>, Object>(); public CodeEntry(byte[] buf) { int[] off = new int[1]; off[0] = 0; while (off[0] < buf.length) { pe.put(Utils.strd(buf, off), Utils.strd(buf, off)); } } public void init() { for (Code c : layers(Code.class)) clmap.put(c.name, c); loader = new ResClassLoader(Resource.class.getClassLoader()) { public Class<?> findClass(String name) throws ClassNotFoundException { Code c = clmap.get(name); if (c == null) throw (new ClassNotFoundException("Could not find class " + name + " in resource (" + Resource.this + ")")); return (defineClass(name, c.data, 0, c.data.length)); } }; try { for (Map.Entry<String, String> e : pe.entrySet()) { String name = e.getKey(); String clnm = e.getValue(); Class<?> cl = loader.loadClass(clnm); lpe.put(name, cl); } } catch (ClassNotFoundException e) { throw (new LoadException(e, Resource.this)); } } public <T> T get(Class<T> cl) { PublishedCode entry = cl.getAnnotation(PublishedCode.class); if (entry == null) throw (new RuntimeException("Tried to fetch non-published res-loaded class " + cl.getName() + " from " + Resource.this.name)); Class<?> acl; synchronized (lpe) { if (lpe.get(entry.name()) == null) { throw (new RuntimeException("Tried to fetch non-present res-loaded class " + cl.getName() + " from " + Resource.this.name)); } else { acl = lpe.get(entry.name()); } } try { synchronized (ipe) { if (ipe.get(acl) != null) { return (cl.cast(ipe.get(acl))); } else { T inst; if (entry.instancer() != PublishedCode.Instancer.class) inst = cl.cast(entry.instancer().newInstance().make(acl)); else inst = cl.cast(acl.newInstance()); ipe.put(acl, inst); return (inst); } } } catch (InstantiationException e) { throw (new RuntimeException(e)); } catch (IllegalAccessException e) { throw (new RuntimeException(e)); } } } static { ltypes.put("codeentry", CodeEntry.class); } public class Audio extends Layer { transient public byte[] clip; public Audio(byte[] buf) { try { clip = Utils.readall(new VorbisDecoder(new ByteArrayInputStream(buf))); } catch (IOException e) { throw (new LoadException(e, Resource.this)); } } public void init() { } } static { ltypes.put("audio", Audio.class); } public class Music extends Resource.Layer { transient javax.sound.midi.Sequence seq; public Music(byte[] buf) { try { seq = javax.sound.midi.MidiSystem.getSequence(new ByteArrayInputStream(buf)); } catch (javax.sound.midi.InvalidMidiDataException e) { throw (new LoadException("Invalid MIDI data", Resource.this)); } catch (IOException e) { throw (new LoadException(e, Resource.this)); } } public void init() { } } static { ltypes.put("midi", Music.class); } private void readall(InputStream in, byte[] buf) throws IOException { int ret, off = 0; while (off < buf.length) { ret = in.read(buf, off, buf.length - off); if (ret < 0) throw (new LoadException("Incomplete resource at " + name, this)); off += ret; } } public <L extends Layer> Collection<L> layers(Class<L> cl) { checkerr(); Collection<L> ret = new LinkedList<L>(); for (Layer l : layers) { if (cl.isInstance(l)) ret.add(cl.cast(l)); } return (ret); } public <L extends Layer> L layer(Class<L> cl) { checkerr(); for (Layer l : layers) { if (cl.isInstance(l)) return (cl.cast(l)); } return (null); } public int compareTo(Resource other) { checkerr(); int nc = name.compareTo(other.name); if (nc != 0) return (nc); if (ver != other.ver) return (ver - other.ver); if (other != this) throw (new RuntimeException("Resource identity crisis!")); return (0); } public boolean equals(Object other) { if (!(other instanceof Resource) || (other == null)) return (false); return (compareTo((Resource) other) == 0); } private void load(InputStream in) throws IOException { String sig = "Haven Resource 1"; byte buf[] = new byte[sig.length()]; readall(in, buf); if (!sig.equals(new String(buf))) throw (new LoadException("Invalid res signature", this)); buf = new byte[2]; readall(in, buf); int ver = Utils.uint16d(buf, 0); List<Layer> layers = new LinkedList<Layer>(); if (this.ver == -1) { this.ver = ver; } else { if (ver != this.ver) throw (new LoadException("Wrong res version (" + ver + " != " + this.ver + ")", this)); } outer: while (true) { StringBuilder tbuf = new StringBuilder(); while (true) { byte bb; int ib; if ((ib = in.read()) == -1) { if (tbuf.length() == 0) break outer; throw (new LoadException("Incomplete resource at " + name, this)); } bb = (byte) ib; if (bb == 0) break; tbuf.append((char) bb); } buf = new byte[4]; readall(in, buf); int len = Utils.int32d(buf, 0); buf = new byte[len]; readall(in, buf); Class<? extends Layer> lc = ltypes.get(tbuf.toString()); if (lc == null) continue; Constructor<? extends Layer> cons; try { cons = lc.getConstructor(Resource.class, byte[].class); } catch (NoSuchMethodException e) { throw (new LoadException(e, Resource.this)); } Layer l; try { l = cons.newInstance(this, buf); } catch (InstantiationException e) { throw (new LoadException(e, Resource.this)); } catch (InvocationTargetException e) { Throwable c = e.getCause(); if (c instanceof RuntimeException) throw ((RuntimeException) c); else throw (new LoadException(c, Resource.this)); } catch (IllegalAccessException e) { throw (new LoadException(e, Resource.this)); } layers.add(l); } this.layers = layers; for (Layer l : layers) l.init(); } public Indir<Resource> indir() { if (indir != null) return (indir); indir = new Indir<Resource>() { public Resource res = Resource.this; public Resource get() { if (loading) return (null); return (Resource.this); } public void set(Resource r) { throw (new RuntimeException()); } public int compareTo(Indir<Resource> x) { return (Resource.this.compareTo(this.getClass().cast(x).res)); } }; return (indir); } private void checkerr() { if (error != null) throw (new RuntimeException("Delayed error in resource " + name + " (v" + ver + "), from " + source, error)); } public int priority() { return (prio); } public static BufferedImage loadimg(String name) { Resource res = load(name); res.loadwait(); return (res.layer(imgc).img); } public static Tex loadtex(String name) { Resource res = load(name); res.loadwait(); return (res.layer(imgc).tex()); } public String toString() { return (name + "(v" + ver + ")"); } public static void loadlist(InputStream list, int prio) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(list, "us-ascii")); String ln; while ((ln = in.readLine()) != null) { int pos = ln.indexOf(':'); if (pos < 0) continue; String nm = ln.substring(0, pos); int ver; try { ver = Integer.parseInt(ln.substring(pos + 1)); } catch (NumberFormatException e) { continue; } try { load(nm, ver, prio); } catch (RuntimeException e) { } } in.close(); } public static void dumplist(Collection<Resource> list, Writer dest) { PrintWriter out = new PrintWriter(dest); List<Resource> sorted = new ArrayList<Resource>(list); Collections.sort(sorted); for (Resource res : sorted) { if (res.loading) continue; out.println(res.name + ":" + res.ver); } } public static void updateloadlist(File file) throws Exception { BufferedReader r = new BufferedReader(new FileReader(file)); Map<String, Integer> orig = new HashMap<String, Integer>(); String ln; while ((ln = r.readLine()) != null) { int pos = ln.indexOf(':'); if (pos < 0) { System.err.println("Weird line: " + ln); continue; } String nm = ln.substring(0, pos); int ver = Integer.parseInt(ln.substring(pos + 1)); orig.put(nm, ver); } r.close(); for (String nm : orig.keySet()) load(nm); while (true) { int d = qdepth(); if (d == 0) break; System.out.print("\033[1GLoading... " + d + "\033[K"); Thread.sleep(500); } System.out.println(); Collection<Resource> cur = new LinkedList<Resource>(); for (Map.Entry<String, Integer> e : orig.entrySet()) { String nm = e.getKey(); int ver = e.getValue(); Resource res = load(nm); res.loadwait(); res.checkerr(); if (res.ver != ver) System.out.println(nm + ": " + ver + " -> " + res.ver); cur.add(res); } Writer w = new OutputStreamWriter(new FileOutputStream(file), "UTF-8"); try { dumplist(cur, w); } finally { w.close(); } } public static void main(String[] args) throws Exception { String cmd = args[0].intern(); if (cmd == "update") { updateloadlist(new File(args[1])); } } public Color getStateColor() { if ((skillColorCache != null) && (cachedTime == Fightview.changed)) { return skillColorCache; } SkillAvailability sa; if (((sa = Config.skills.get(name)) != null) && (sa.isActive())) { cachedTime = Fightview.changed; return (skillColorCache = sa.isAvailable() ? skillAvailableColor : skillUnavailableColor); } return Color.WHITE; } }