/* * 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 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);} 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() {} } 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; } }