();
+ this.numberToWords.put(wordNum, words);
+ }
+ words.add(wordText);
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/awt/EgaUtils.java b/core/src/main/java/com/sierra/agi/awt/EgaUtils.java
new file mode 100644
index 0000000..5ae1ac1
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/awt/EgaUtils.java
@@ -0,0 +1,101 @@
+/*
+ * EgaUtil.java
+ * Adventure Game Interpreter AWT Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.awt;
+
+import java.awt.*;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DirectColorModel;
+import java.awt.image.IndexColorModel;
+
+/**
+ * Misc. Utilities for EGA support in Java's AWT.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public abstract class EgaUtils {
+
+ /**
+ * EGA Colors Red Band
+ */
+ protected static final byte[] r = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0x55, (byte) 0x55, (byte) 0x55, (byte) 0x55, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+
+ /**
+ * EGA Colors Green Band
+ */
+ protected static final byte[] g = {(byte) 0x00, (byte) 0x00, (byte) 0xaa, (byte) 0xaa, (byte) 0x00, (byte) 0x00, (byte) 0x55, (byte) 0xaa, (byte) 0x55, (byte) 0x55, (byte) 0xff, (byte) 0xff, (byte) 0x55, (byte) 0x55, (byte) 0xff, (byte) 0xff};
+
+ /**
+ * EGA Colors Blue Band
+ */
+ protected static final byte[] b = {(byte) 0x00, (byte) 0xaa, (byte) 0x00, (byte) 0xaa, (byte) 0x00, (byte) 0xaa, (byte) 0x00, (byte) 0xaa, (byte) 0x55, (byte) 0xff, (byte) 0x55, (byte) 0xff, (byte) 0x55, (byte) 0xff, (byte) 0x55, (byte) 0xff};
+
+ /**
+ * EGA Color Model Cache
+ */
+ protected static IndexColorModel indexModel;
+
+ /**
+ * Native Color Model Cache
+ */
+ protected static DirectColorModel nativeModel;
+
+ /**
+ * Returns the ColorModel used by EGA Adapters.
+ *
+ * Used to convert visual resource from EGA Color Model to the
+ * Native Color Model.
+ */
+ public static synchronized IndexColorModel getIndexColorModel() {
+ int i;
+
+ if (indexModel == null) {
+ indexModel = new IndexColorModel(8, 16, r, g, b);
+ }
+
+ return indexModel;
+ }
+
+ /**
+ * Returns a ColorModel representing the nativiest ColorModel of the
+ * current system configuration.
+ *
+ * In order to reduce the number of ColorModel convertions, each visual
+ * resource is converted as soon as possible to this ColorModel.
+ */
+ public static synchronized DirectColorModel getNativeColorModel() {
+ if (nativeModel == null) {
+ ColorModel model = Toolkit.getDefaultToolkit().getColorModel();
+ DirectColorModel direct;
+
+ if ((model.getTransferType() != DataBuffer.TYPE_INT) ||
+ !(model instanceof DirectColorModel)) {
+ model = ColorModel.getRGBdefault();
+ }
+
+ if (model.getTransparency() != Transparency.OPAQUE) {
+ direct = (DirectColorModel) model;
+ model = new DirectColorModel(
+ direct.getColorSpace(),
+ direct.getPixelSize(),
+ direct.getRedMask(),
+ direct.getGreenMask(),
+ direct.getBlueMask(),
+ 0,
+ false,
+ DataBuffer.TYPE_INT);
+ }
+
+ nativeModel = (DirectColorModel) model;
+ }
+
+ return nativeModel;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/inv/InventoryObject.java b/core/src/main/java/com/sierra/agi/inv/InventoryObject.java
new file mode 100644
index 0000000..b790e4f
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/inv/InventoryObject.java
@@ -0,0 +1,32 @@
+/*
+ * InventoryObject.java
+ */
+
+package com.sierra.agi.inv;
+
+/**
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public final class InventoryObject {
+ /**
+ * Name
+ */
+ public String name;
+ /**
+ * Location
+ */
+ private final short location;
+
+ public InventoryObject(short location) {
+ this.location = location;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public short getLocation() {
+ return location;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/inv/InventoryObjects.java b/core/src/main/java/com/sierra/agi/inv/InventoryObjects.java
new file mode 100644
index 0000000..a6dfe23
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/inv/InventoryObjects.java
@@ -0,0 +1,242 @@
+/*
+ * InventoryObjects.java
+ */
+
+package com.sierra.agi.inv;
+
+import com.sierra.agi.io.ByteCasterStream;
+import com.sierra.agi.res.ResourceConfiguration;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Stores Objects of the game.
+ *
+ * Object File Format
+ * The object file stores two bits of information about the inventory items used
+ * in an AGI game. The starting room location and the name of the inventory item.
+ * It also has a byte that determines the maximum number of animated objects.
+ *
+ * File Encryption
+ * The first obstacle to overcome is the fact that most object files are
+ * encrypted. I say most because some of the earlier AGI games were not, in
+ * which case you can skip to the next section. Those that are encrypted are done
+ * so with the string "Avis Durgan" (or, in case of AGDS games, "Alex Simkin").
+ * The process of unencrypting the file is to simply taken every eleven bytes
+ * from the file and XOR each element of those eleven bytes with the corresponding
+ * element in the string "Avis Durgan". This sort of encryption is very easy to
+ * crack if you know what you are doing and is simply meant to act as a shield
+ * so as not to encourage cheating. In some games, however, the object names are
+ * clearly visible in the saved game files even when the object file is encrypted,
+ * so it's not a very effective shield.
+ *
+ * File Format
+ *
+ * Byte | Meaning |
+ *
+ * 0-1 | Offset of the start of inventory item names |
+ * 2 | Maximum number of animated objects |
+ *
+ *
+ * Following the first three bytes as a section containing a three byte entry
+ * for each inventory item all of which conform to the following format:
+ *
+ *
+ * Byte | Meaning |
+ *
+ * 0-1 | Offset of inventory item name i |
+ * 2 | Starting room number for inventory item i or 255 carried |
+ *
+ *
+ * Where i is the entry number starting at 0. All offsets are taken from the
+ * start of entry for inventory item 0 (not the start of the file).
+ *
+ * Then comes the textual names themselves. This is simply a list of NULL
+ * terminated strings. The offsets mentioned in the above section point to the
+ * first character in the string and the last character is the one before the
+ * 0x00.
+ *
+ * @author Dr. Z, Lance Ewing (Documentation)
+ * @version 0.00.00.01
+ */
+public class InventoryObjects implements InventoryProvider {
+ /**
+ * Object list.
+ */
+ protected InventoryObject[] objects = null;
+ protected int numOfAnimatedObjects;
+
+ public InventoryObjects(ResourceConfiguration config) {
+
+ }
+
+ /**
+ * Loads the String Table from a AGI Object file. Internal Uses only.
+ *
+ * @param stream AGI Object file's Stream.
+ * @param offset Starting offset.
+ * @return Returns a Hashtable containing the strings with their offset has
+ * the Hash key.
+ * @throws IOException Caller must handle IOException from his stream.
+ */
+ protected static Hashtable loadStringTable(InputStream stream, int offset) throws IOException {
+ Hashtable h = new Hashtable(64);
+ String o = "";
+ int s = offset;
+
+ while (true) {
+ int c = stream.read();
+ offset++;
+
+ if (c < 0) {
+ break;
+ }
+
+ if (c == 0) {
+ h.put(Integer.valueOf(s), o);
+ o = "";
+ s = offset;
+ } else {
+ o += (char) c;
+ }
+ }
+
+ return h;
+ }
+
+ /**
+ * Loads a AGI Object File from a stream.
+ *
+ * @param stream Stream where the Objects are contained. Must be a AGI
+ * compliant format.
+ * @return Returns the number of object contained in the stream.
+ * @throws IOException Caller must handle IOException from his stream.
+ */
+ public InventoryObjects loadInventory(InputStream stream) throws IOException {
+ ByteCasterStream rawStream = new ByteCasterStream(stream);
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(rawStream.readAllBytes());
+ ByteCasterStream bstream = new ByteCasterStream(byteArrayInputStream);
+
+ /* Calculate Inventory Object Count */
+ int padSize = 3;
+ int nobject = bstream.lohiReadUnsignedShort();
+ nobject /= padSize;
+
+ this.objects = new InventoryObject[nobject];
+ int[] offsets = new int[nobject];
+
+
+ this.numOfAnimatedObjects = bstream.readUnsignedByte();
+
+ int offset = 0;
+
+ for (int i = 0; i < nobject; i++) {
+ offsets[i] = bstream.lohiReadUnsignedShort();
+ objects[i] = new InventoryObject(bstream.readUnsignedByte());
+ offset += padSize;
+ }
+
+ Hashtable hash = loadStringTable(byteArrayInputStream, offset);
+
+ for (int i = 0; i < nobject; i++) {
+ objects[i].name = (String) hash.get(Integer.valueOf(offsets[i]));
+ }
+
+ byteArrayInputStream.close();
+ rawStream.close();
+ bstream.close();
+ return this;
+ }
+
+ /**
+ * Returns the number of objects contained in this object.
+ *
+ * @return Returns the number of objects.
+ */
+ public short getCount() {
+ return (short) objects.length;
+ }
+
+ /**
+ * Returns an Object contained in this object based on his index.
+ *
+ * @param index Index number of the wanted object.
+ * @return Returns the wanted object.
+ */
+ public InventoryObject getObject(short index) {
+ return objects[index];
+ }
+
+ public void resetLocationTable(short[] locations) {
+ int i;
+
+ for (i = 0; i < objects.length; i++) {
+ locations[i] = objects[i].getLocation();
+ }
+ }
+
+ public InventoryObject[] getObjects() {
+ return objects;
+ }
+
+ public int getNumOfAnimatedObjects() {
+ return numOfAnimatedObjects;
+ }
+
+ public byte[] encode(short[] locations) throws Exception{
+ // Recreate the Item Entries
+ // key = object name
+ // value = offset
+ Map itemEntries = new HashMap<>();
+ // We need to preserve the order of the objects in the list
+ List itemList = new ArrayList<>();
+
+ int count = objects.length;
+ int num = count * 3;
+
+ int offset = num;
+ for (int i = 0; i < count; i++) {
+ InventoryObject inventoryObject = objects[i];
+ if (!itemEntries.containsKey(inventoryObject.name)) {
+ itemEntries.put(inventoryObject.name, offset);
+ itemList.add(inventoryObject);
+ // 1 = NUL char
+ offset = offset + (inventoryObject.name.length() + 1);
+ }
+ }
+
+ // Dump of the in memory OBJECT file including updates made by get, put and drop commands
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ outputStream.write((num & 0xFF));
+ outputStream.write((num >> 8) & 0xFF);
+ outputStream.write(numOfAnimatedObjects);
+
+ for (int i = 0; i < count; i++) {
+ InventoryObject invObject = objects[i];
+ short location = locations[i];
+
+ int itemOffset = itemEntries.get(invObject.name);
+ outputStream.write(itemOffset & 0xFF);
+ outputStream.write((itemOffset >> 8) & 0xFF);
+ outputStream.write(location);
+ }
+
+ for (InventoryObject inventoryObject : itemList) {
+ byte[] nameBytes = inventoryObject.getName().getBytes();
+ outputStream.write(nameBytes);
+ outputStream.write(0);
+ }
+
+ byte[] buffer = outputStream.toByteArray();
+
+ return buffer;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/inv/InventoryProvider.java b/core/src/main/java/com/sierra/agi/inv/InventoryProvider.java
new file mode 100644
index 0000000..7dc5d7d
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/inv/InventoryProvider.java
@@ -0,0 +1,16 @@
+/**
+ * InventoryProvider.java
+ * Adventure Game Interpreter Inventory Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.inv;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface InventoryProvider {
+ InventoryObjects loadInventory(InputStream in) throws IOException;
+}
diff --git a/core/src/main/java/com/sierra/agi/io/ByteCaster.java b/core/src/main/java/com/sierra/agi/io/ByteCaster.java
new file mode 100644
index 0000000..d5a06b1
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/io/ByteCaster.java
@@ -0,0 +1,49 @@
+/**
+ * ByteCaster.java
+ * Adventure Game Interpreter I/O Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.io;
+
+/**
+ * Interprets byte arrays.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+abstract public class ByteCaster {
+ public static short hiloUnsignedByte(byte[] b, int off) {
+ return (short) (b[off] & 0xFF);
+ }
+
+ public static int hiloUnsignedShort(byte[] b, int off) {
+ return ((b[off] & 0xFF) << 8) |
+ (b[off + 1] & 0xFF);
+ }
+
+ public static long hiloUnsignedInt(byte[] b, int off) {
+ return ((long) (b[off] & 0xFF) << 24) |
+ ((b[off + 1] & 0xFF) << 16) |
+ ((b[off + 2] & 0xFF) << 8) |
+ (b[off + 3] & 0xFF);
+ }
+
+ public static short lohiUnsignedByte(byte[] b, int off) {
+ return (short) (b[off] & 0xFF);
+ }
+
+ public static int lohiUnsignedShort(byte[] b, int off) {
+ return ((b[off + 1] & 0xFF) << 8) |
+ (b[off] & 0xFF);
+ }
+
+ public static long lohiUnsignedInt(byte[] b, int off) {
+ return ((long) (b[off + 3] & 0xFF) << 24) |
+ ((b[off + 2] & 0xFF) << 16) |
+ ((b[off + 1] & 0xFF) << 8) |
+ (b[off] & 0xFF);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/io/ByteCasterStream.java b/core/src/main/java/com/sierra/agi/io/ByteCasterStream.java
new file mode 100644
index 0000000..3a07bf3
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/io/ByteCasterStream.java
@@ -0,0 +1,76 @@
+/**
+ * ByteCasterStream.java
+ * Adventure Game Interpreter I/O Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.io;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Interprets stream's data.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.02
+ */
+public class ByteCasterStream extends FilterInputStream {
+ public ByteCasterStream(InputStream in) {
+ super(in);
+ }
+
+ public short readUnsignedByte() throws IOException {
+ int v = in.read();
+
+ if (v < 0) {
+ throw new EOFException();
+ }
+
+ return (short) v;
+ }
+
+ public int hiloReadUnsignedShort() throws IOException {
+ byte[] b = new byte[2];
+
+ IOUtils.fill(in, b, 0, 2);
+
+ return ((b[0] & 0xFF) << 8) |
+ (b[1] & 0xFF);
+ }
+
+ public long hiloReadUnsignedInt() throws IOException {
+ byte[] b = new byte[4];
+
+ IOUtils.fill(in, b, 0, 4);
+
+ return ((long) (b[0] & 0xFF) << 24) |
+ ((b[1] & 0xFF) << 16) |
+ ((b[2] & 0xFF) << 8) |
+ (b[3] & 0xFF);
+ }
+
+ public int lohiReadUnsignedShort() throws IOException {
+ byte[] b = new byte[2];
+
+ IOUtils.fill(in, b, 0, 2);
+
+ return ((b[1] & 0xFF) << 8) |
+ (b[0] & 0xFF);
+ }
+
+ public long lohiReadUnsignedInt() throws IOException {
+ byte[] b = new byte[4];
+
+ IOUtils.fill(in, b, 0, 4);
+
+ return ((long) (b[3] & 0xFF) << 24) |
+ ((b[2] & 0xFF) << 16) |
+ ((b[1] & 0xFF) << 8) |
+ (b[0] & 0xFF);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/io/CryptedInputStream.java b/core/src/main/java/com/sierra/agi/io/CryptedInputStream.java
new file mode 100644
index 0000000..3811758
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/io/CryptedInputStream.java
@@ -0,0 +1,114 @@
+/**
+ * CryptedInputStream.java
+ * Adventure Game Interpreter I/O Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Layer to support decryption of sierra's resources.
+ * (simple XOR cryption)
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class CryptedInputStream extends FilterInputStream {
+ /** Decryption key. */
+ protected char[] key;
+
+ /** Current offset. */
+ protected int offset;
+
+ /** Cryption begin offset. */
+ protected int boffset;
+
+ /**
+ * Offset of the last mark
method call.
+ *
+ * @see #mark(int)
+ */
+ protected int marked;
+
+ /**
+ * Creates a new decryption layer.
+ *
+ * @param key Decryption key.
+ * @param stream InputStream
to decrypt.
+ */
+ public CryptedInputStream(InputStream in, String key) {
+ super(in);
+ this.key = key.toCharArray();
+ }
+
+ /**
+ * Creates a new decryption layer.
+ *
+ * @param key Decryption key.
+ * @param stream InputStream
to decrypt.
+ */
+ public CryptedInputStream(InputStream in, String key, int boffset) {
+ super(in);
+ this.boffset = boffset;
+ this.key = key.toCharArray();
+ }
+
+ public void mark(int readlimit) {
+ in.mark(readlimit);
+ marked = offset;
+ }
+
+ public void reset() throws IOException {
+ in.reset();
+ offset = marked;
+ }
+
+ public long skip(long n) throws IOException {
+ long r = in.skip(n);
+
+ offset += r;
+ return r;
+ }
+
+ public int read() throws IOException {
+ int r = in.read();
+
+ if (r < 0)
+ return r;
+
+ if (offset >= boffset) {
+ r ^= key[(offset - boffset) % key.length];
+ }
+
+ offset++;
+ return r;
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ int i, j, off2;
+ int r = in.read(b, off, len);
+
+ if (r < 0) {
+ return r;
+ }
+
+ off2 = off + len;
+ for (i = off; i < off2; i++) {
+ if (offset >= boffset) {
+ j = (b[i] & 0xFF);
+ j ^= key[(offset - boffset) % key.length];
+ b[i] = (byte) j;
+ }
+
+ offset++;
+ }
+
+ return r;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/io/IOUtils.java b/core/src/main/java/com/sierra/agi/io/IOUtils.java
new file mode 100644
index 0000000..50d1d69
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/io/IOUtils.java
@@ -0,0 +1,50 @@
+/**
+ * IOUtils.java
+ * Adventure Game Interpreter I/O Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.io;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class IOUtils {
+ public static int fill(InputStream in, byte[] b, int off, int len) throws IOException {
+ int c, r = 0;
+
+ while (len != 0) {
+ c = in.read(b, off, len);
+
+ if (c <= 0) {
+ throw new EOFException();
+ }
+
+ r += c;
+ off += c;
+ len -= c;
+ }
+
+ return r;
+ }
+
+ public static int skip(InputStream in, int len) throws IOException {
+ int c, r = 0;
+
+ while (len != 0) {
+ c = (int) in.skip(len);
+
+ if (c <= 0) {
+ throw new EOFException();
+ }
+
+ r += c;
+ len -= c;
+ }
+
+ return r;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/io/LZWInputStream.java b/core/src/main/java/com/sierra/agi/io/LZWInputStream.java
new file mode 100644
index 0000000..468eb35
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/io/LZWInputStream.java
@@ -0,0 +1,232 @@
+/**
+ * LZWInputStream.java
+ * Adventure Game Interpreter I/O Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.io;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * LZW Decompressor. This class is a Input Stream Layer
+ * to uncompress on-the-fly resource stream using the LZW
+ * algorithm used in AGI v3.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class LZWInputStream extends InputStream {
+ protected final static int MAX_BITS = 12;
+ protected final static int TABLE_SIZE = 18041;
+ protected final static int START_BITS = 9;
+ protected boolean endOfStream = false;
+ protected int bits;
+ protected int maxValues;
+ protected int maxCodes;
+ protected InputStream in;
+ protected byte[] appendChars = new byte[TABLE_SIZE];
+ protected byte[] decodeStack = new byte[8192];
+ protected int decodeStackSize = -1;
+ protected int[] prefixCode = new int[TABLE_SIZE];
+
+ protected int bitCount = 0;
+ protected long bitBuffer = 0;
+ protected int unext;
+ protected int unew;
+ protected int uold;
+ protected int ubits;
+ protected int uc;
+ /** Creates new LZWException */
+ public LZWInputStream(InputStream in) throws IOException {
+ this.in = in;
+ ubits = setBits(START_BITS);
+ unext = 257;
+ uold = inputCode();
+ uc = uold;
+ unew = inputCode();
+ }
+
+ protected int setBits(int value) {
+ if (value == MAX_BITS) {
+ return 1;
+ }
+
+ bits = value;
+ maxValues = (1 << bits) - 1;
+ maxCodes = maxValues - 1;
+ return 0;
+ }
+
+ protected int inputCode() throws IOException {
+ long b;
+ int r;
+
+ long q = bitBuffer;
+ int s = bitCount;
+
+ while (s <= 24) {
+ b = in.read();
+
+ if (b < 0) {
+ if (s == 0) {
+ throw new EOFException();
+ }
+
+ break;
+ }
+
+ b <<= s;
+ q |= b;
+ s += 8;
+ }
+
+ r = (int) (q & 0x7fff);
+ r %= (1 << bits);
+
+ bitBuffer = (q >> bits);
+ bitCount = (s - bits);
+
+ return r;
+ }
+
+ protected int decodeString(int offset, int code) throws IOException {
+ int i;
+
+ for (i = 0; code > 255; ) {
+ decodeStack[offset] = appendChars[code];
+ offset++;
+ code = prefixCode[code];
+
+ if (i++ >= 4000) {
+ throw new IOException("LZW: Error in Code Expansion");
+ }
+ }
+
+ decodeStack[offset] = (byte) code;
+ return offset;
+ }
+
+ protected void unpack() throws IOException {
+ if (endOfStream) {
+ return;
+ }
+
+ if (decodeStackSize > 0) {
+ return;
+ }
+
+ if (unew == 0x101) {
+ endOfStream = true;
+ return;
+ }
+
+ if (unew == 0x100) {
+ unext = 258;
+ ubits = setBits(START_BITS);
+ uold = inputCode();
+ uc = uold;
+
+ decodeStack[0] = (byte) uc;
+ decodeStackSize = 0;
+
+ unew = inputCode();
+ } else {
+ if (unew >= unext) {
+ decodeStack[0] = (byte) uc;
+ decodeStackSize = decodeString(1, uold);
+ } else {
+ decodeStackSize = decodeString(0, unew);
+ }
+
+ uc = decodeStack[decodeStackSize];
+
+ if (unext > maxCodes) {
+ ubits = setBits(bits + 1);
+ }
+
+ prefixCode[unext] = uold;
+ appendChars[unext] = (byte) uc;
+
+ unext++;
+ uold = unew;
+
+ unew = inputCode();
+ }
+ }
+
+ public int read() throws IOException {
+ int c;
+
+ while (decodeStackSize < 0) {
+ try {
+ unpack();
+ } catch (EOFException eex) {
+ endOfStream = true;
+ }
+
+ if (endOfStream) {
+ close();
+ return -1;
+ }
+ }
+
+ c = decodeStack[decodeStackSize];
+ decodeStackSize--;
+
+ return c;
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ int c = 0;
+
+ while (!endOfStream) {
+ if (decodeStackSize >= 0) {
+ while ((decodeStackSize >= 0) && (len > 0)) {
+ b[off] = decodeStack[decodeStackSize];
+ decodeStackSize--;
+ off++;
+ len--;
+ c++;
+ }
+
+ if (len == 0) {
+ break;
+ }
+ }
+
+ try {
+ unpack();
+ } catch (EOFException eex) {
+ endOfStream = true;
+ }
+ }
+
+ if (endOfStream) {
+ close();
+ }
+
+ if (c == 0)
+ c = -1;
+
+ return c;
+ }
+
+ public void close() throws IOException {
+ endOfStream = true;
+
+ if (in != null) {
+ in.close();
+ }
+
+ /** Garbage Collector Optimization */
+ in = null;
+ appendChars = null;
+ decodeStack = null;
+ prefixCode = null;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/io/LittleEndianOutputStream.java b/core/src/main/java/com/sierra/agi/io/LittleEndianOutputStream.java
new file mode 100644
index 0000000..e71f5b8
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/io/LittleEndianOutputStream.java
@@ -0,0 +1,137 @@
+package com.sierra.agi.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UTFDataFormatException;
+
+public class LittleEndianOutputStream extends FilterOutputStream {
+ protected int written;
+
+ public LittleEndianOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ public void write(int b) throws IOException {
+ out.write(b);
+ written++;
+ }
+
+ public void write(byte[] data, int offset, int length)
+ throws IOException {
+ out.write(data, offset, length);
+ written += length;
+ }
+
+ public void writeBoolean(boolean b) throws IOException {
+ if (b) this.write(1);
+ else this.write(0);
+ }
+
+ public void writeByte(int b) throws IOException {
+ out.write(b);
+ written++;
+ }
+
+ public void writeShort(int s) throws IOException {
+ out.write(s & 0xFF);
+ out.write((s >>> 8) & 0xFF);
+ written += 2;
+ }
+
+ public void writeChar(int c) throws IOException {
+ out.write(c & 0xFF);
+ out.write((c >>> 8) & 0xFF);
+ written += 2;
+ }
+
+ public void writeInt(int i) throws IOException {
+
+ out.write(i & 0xFF);
+ out.write((i >>> 8) & 0xFF);
+ out.write((i >>> 16) & 0xFF);
+ out.write((i >>> 24) & 0xFF);
+ written += 4;
+
+ }
+
+ public void writeLong(long l) throws IOException {
+
+ out.write((int) l & 0xFF);
+ out.write((int) (l >>> 8) & 0xFF);
+ out.write((int) (l >>> 16) & 0xFF);
+ out.write((int) (l >>> 24) & 0xFF);
+ out.write((int) (l >>> 32) & 0xFF);
+ out.write((int) (l >>> 40) & 0xFF);
+ out.write((int) (l >>> 48) & 0xFF);
+ out.write((int) (l >>> 56) & 0xFF);
+ written += 8;
+
+ }
+
+ public final void writeFloat(float f) throws IOException {
+ this.writeInt(Float.floatToIntBits(f));
+ }
+
+ public final void writeDouble(double d) throws IOException {
+ this.writeLong(Double.doubleToLongBits(d));
+ }
+
+ public void writeBytes(String s) throws IOException {
+ int length = s.length();
+ for (int i = 0; i < length; i++) {
+ out.write((byte) s.charAt(i));
+ }
+ written += length;
+ }
+
+ public void writeChars(String s) throws IOException {
+ int length = s.length();
+ for (int i = 0; i < length; i++) {
+ int c = s.charAt(i);
+ out.write(c & 0xFF);
+ out.write((c >>> 8) & 0xFF);
+ }
+ written += length * 2;
+ }
+
+ public void writeUTF(String s) throws IOException {
+
+ int numchars = s.length();
+ int numbytes = 0;
+
+ for (int i = 0; i < numchars; i++) {
+ int c = s.charAt(i);
+ if ((c >= 0x0001) && (c <= 0x007F)) numbytes++;
+ else if (c > 0x07FF) numbytes += 3;
+ else numbytes += 2;
+ }
+
+ if (numbytes > 65535) throw new UTFDataFormatException();
+
+ out.write((numbytes >>> 8) & 0xFF);
+ out.write(numbytes & 0xFF);
+ for (int i = 0; i < numchars; i++) {
+ int c = s.charAt(i);
+ if ((c >= 0x0001) && (c <= 0x007F)) {
+ out.write(c);
+ } else if (c > 0x07FF) {
+ out.write(0xE0 | ((c >> 12) & 0x0F));
+ out.write(0x80 | ((c >> 6) & 0x3F));
+ out.write(0x80 | (c & 0x3F));
+ written += 2;
+ } else {
+ out.write(0xC0 | ((c >> 6) & 0x1F));
+ out.write(0x80 | (c & 0x3F));
+ written += 1;
+ }
+ }
+
+ written += numchars + 2;
+
+ }
+
+ public int size() {
+ return this.written;
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/io/PictureInputStream.java b/core/src/main/java/com/sierra/agi/io/PictureInputStream.java
new file mode 100644
index 0000000..9f175c0
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/io/PictureInputStream.java
@@ -0,0 +1,117 @@
+/**
+ * PictureInputStream.java
+ * Adventure Game Interpreter I/O Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Picture Input Stream.
+ *
+ * Pictures in AGI version 3 use a simple form of compression to shrink their
+ * size by a tiny amount. It was obviously recognised by the interpreter coders
+ * that four bits were being wasted for picture codes 0xF0
and
+ * 0xF2
. These are the two codes that change the visual and the
+ * priority colour respectively. Since there are only 16 colours, there need
+ * not be a whole byte set aside for storing the colour. All the picture
+ * compression does is store these colours in 4 bits rather than 8.
+ *
+ * Example:
+ * Original picture codes: F0 06 F8 12 45 F0 07 F2 05 F8 14 67 ...
+ * Compressed picture code: F0 6F 81 24 5F 07 F2 5F 81 46 7 ...
+ *
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class PictureInputStream extends FilterInputStream {
+ /** Previous Byte */
+ protected int previous;
+
+ /** Current Byte */
+ protected int current;
+
+ protected int mode = 0;
+
+ /**
+ * Creates new Picture Input Stream
+ */
+ public PictureInputStream(InputStream in) {
+ super(in);
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ int t = 0;
+ int n;
+
+ while (len > 0) {
+ n = read();
+
+ if (n < 0) {
+ break;
+ }
+ }
+
+ return t;
+ }
+
+ public int read() throws IOException {
+ int x = 0, y;
+
+ if (in == null) {
+ return -1;
+ }
+
+ if (mode <= 1) {
+ current = in.read();
+
+ if (mode == 0) {
+ x = current;
+ } else {
+ x = (current & 0xf0);
+ x >>= 4;
+ y = (previous & 0x0f);
+ y <<= 4;
+ x |= y;
+ }
+
+ if (x == 0xff) {
+ close();
+ return -1;
+ }
+
+ if (x == 0xf0 || x == 0xf2) {
+ if (mode == 1) {
+ mode = 2;
+ } else {
+ mode = 3;
+ }
+ }
+ } else if (mode == 2) {
+ mode = 0;
+ return current & 0x0f;
+ } else if (mode == 3) {
+ mode = 1;
+ current = in.read();
+ x = current & 0xf0;
+ x >>= 4;
+ }
+
+ previous = current;
+ return x;
+ }
+
+ public void close() throws IOException {
+ if (in != null) {
+ in.close();
+ in = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/io/PublicByteArrayInputStream.java b/core/src/main/java/com/sierra/agi/io/PublicByteArrayInputStream.java
new file mode 100644
index 0000000..6a4f47b
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/io/PublicByteArrayInputStream.java
@@ -0,0 +1,50 @@
+package com.sierra.agi.io;
+
+import java.io.ByteArrayInputStream;
+
+/**
+ * A sub-class of ByteArrayInputStream that makes public some of the internal state
+ * of its super class, such as the count and pos.
+ *
+ * @author Lance Ewing
+ */
+public class PublicByteArrayInputStream extends ByteArrayInputStream {
+
+ /**
+ * Constructor for PublicByteArrayInputStream.
+ *
+ * @param buf The byte array from which the InputStream is to be created.
+ */
+ public PublicByteArrayInputStream(byte[] buf) {
+ super(buf);
+ }
+
+ /**
+ * Constructor for PublicByteArrayInputStream.
+ *
+ * @param buf The input buffer.
+ * @param offset The offset in the buffer of the first byte to read.
+ * @param length The maximum number of bytes to read from the buffer.
+ */
+ public PublicByteArrayInputStream(byte[] buf, int offset, int length) {
+ super(buf, offset, length);
+ }
+
+ /**
+ * Gets the current position within the byte array.
+ *
+ * @return The current position within the byte array.
+ */
+ public int getPosition() {
+ return this.pos;
+ }
+
+ /**
+ * Gets the number of bytes in the byte array.
+ *
+ * @return The number of bytes in the byte array.
+ */
+ public int getCount() {
+ return this.count;
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/io/SegmentedInputStream.java b/core/src/main/java/com/sierra/agi/io/SegmentedInputStream.java
new file mode 100644
index 0000000..43ccf32
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/io/SegmentedInputStream.java
@@ -0,0 +1,176 @@
+/**
+ * SegmentedInputStream.java
+ * Adventure Game Interpreter I/O Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * Implentation of InputStream
that gives access
+ * to a part of a file.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class SegmentedInputStream extends InputStream {
+ /** File pointer to the opened volume file. */
+ protected RandomAccessFile randomFile;
+
+ /** Current offset in the volume file. */
+ protected int offset;
+
+ /** Length of the resource data remaining. */
+ protected int length;
+
+ /**
+ * Offset of the last call to mark
.
+ *
+ * @see #mark(int)
+ */
+ protected int marked;
+
+ /**
+ * Creates a new SegmentedInputStream
.
+ *
+ * @param pEntry Pointer to a entry in the directory table.
+ * @throws IOException Can throw IOException.
+ */
+ public SegmentedInputStream(RandomAccessFile file, int offset, int length) throws IOException {
+ this.offset = offset;
+ this.length = length;
+ this.randomFile = file;
+ this.marked = offset;
+
+ randomFile.seek(offset);
+ }
+
+ public int available() {
+ return length;
+ }
+
+ public void close() throws IOException {
+ randomFile.close();
+ randomFile = null;
+ }
+
+ public void mark(int readlimit) {
+ marked = offset;
+ }
+
+ public void reset() throws IOException {
+ int l = offset - marked;
+
+ offset = marked;
+ length -= l;
+
+ randomFile.seek(offset);
+ }
+
+ public int diff() {
+ return offset - marked;
+ }
+
+ public int getOffset() {
+ return offset;
+ }
+
+ public RandomAccessFile getRandomAccessFile() {
+ return randomFile;
+ }
+
+ /**
+ * Tests if this input stream supports the mark
+ * and reset
methods.
+ *
+ * In this implentation of InputStream
, it always
+ * returns true
.
+ *
+ * @see #mark(int)
+ * @see #reset()
+ * @see java.io.InputStream
+ * @return Returns true
+ */
+ public boolean markSupported() {
+ return true;
+ }
+
+ public int read() throws IOException {
+ int r;
+
+ if (length <= 0)
+ return -1;
+
+ r = randomFile.read();
+
+ if (r >= 0) {
+ offset++;
+ length--;
+ }
+
+ return r;
+ }
+
+ public int read(byte[] b) throws IOException {
+ int r, l = b.length;
+
+ if (length <= 0) {
+ return -1;
+ }
+
+ if (l > length) {
+ l = length;
+ }
+
+ r = randomFile.read(b, 0, l);
+
+ if (r > 0) {
+ offset += r;
+ length -= r;
+ }
+
+ return r;
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ int r;
+
+ if (length <= 0) {
+ return -1;
+ }
+
+ if (len > length) {
+ len = length;
+ }
+
+ r = randomFile.read(b, off, len);
+
+ if (r > 0) {
+ offset += r;
+ length -= r;
+ }
+
+ return r;
+ }
+
+ public long skip(long n) throws IOException {
+ if (length <= 0) {
+ return 0;
+ }
+
+ if (n > length) {
+ n = length;
+ }
+
+ offset += n;
+ length -= n;
+ randomFile.seek(offset);
+ return n;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/logic/Logic.java b/core/src/main/java/com/sierra/agi/logic/Logic.java
new file mode 100644
index 0000000..8c8c8c6
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/logic/Logic.java
@@ -0,0 +1,12 @@
+/**
+ * Logic.java
+ * Adventure Game Interpreter Logic Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.logic;
+
+public interface Logic {
+}
diff --git a/core/src/main/java/com/sierra/agi/logic/LogicException.java b/core/src/main/java/com/sierra/agi/logic/LogicException.java
new file mode 100644
index 0000000..0e6f9e3
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/logic/LogicException.java
@@ -0,0 +1,20 @@
+package com.sierra.agi.logic;
+
+public class LogicException extends Exception {
+ /**
+ * Creates new LogicException
without detail message.
+ */
+ public LogicException() {
+ }
+
+ /**
+ * Constructs a LogicException
with the specified detail
+ * message.
+ *
+ * @param msg the detail message.
+ */
+ public LogicException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/core/src/main/java/com/sierra/agi/logic/LogicProvider.java b/core/src/main/java/com/sierra/agi/logic/LogicProvider.java
new file mode 100644
index 0000000..8b1c980
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/logic/LogicProvider.java
@@ -0,0 +1,16 @@
+/**
+ * LogicProvider.java
+ * Adventure Game Interpreter Logic Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.logic;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface LogicProvider {
+ Logic loadLogic(short logicNumber, InputStream inputStream, int size) throws IOException, LogicException;
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/CorruptedPictureException.java b/core/src/main/java/com/sierra/agi/pic/CorruptedPictureException.java
new file mode 100644
index 0000000..f521cf8
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/CorruptedPictureException.java
@@ -0,0 +1,21 @@
+/*
+ * CorruptedPictureException.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+/**
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class CorruptedPictureException extends PictureException {
+ /**
+ * Creates new CorruptedPictureException
without detail message.
+ */
+ public CorruptedPictureException() {
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/pic/Picture.java b/core/src/main/java/com/sierra/agi/pic/Picture.java
new file mode 100644
index 0000000..1fbf0ce
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/Picture.java
@@ -0,0 +1,43 @@
+/*
+ * Picture.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class Picture {
+ protected Vector entries;
+
+ /**
+ * Creates new Picture
+ */
+ public Picture(Vector entries) {
+ this.entries = entries;
+ }
+
+ public PictureContext draw() throws PictureException {
+ PictureContext pictureContext = new PictureContext();
+
+ draw(pictureContext);
+ return pictureContext;
+ }
+
+ public void draw(PictureContext pictureContext) throws PictureException {
+ Enumeration en = entries.elements();
+
+ while (en.hasMoreElements()) {
+ PictureEntry entry = (PictureEntry) en.nextElement();
+ entry.draw(pictureContext);
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureContext.java b/core/src/main/java/com/sierra/agi/pic/PictureContext.java
new file mode 100644
index 0000000..90e0648
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureContext.java
@@ -0,0 +1,310 @@
+/*
+ * PictureContext.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import com.sierra.agi.awt.EgaUtils;
+
+import java.awt.Image;
+import java.awt.Toolkit;
+import java.awt.image.MemoryImageSource;
+import java.util.Arrays;
+
+/**
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class PictureContext {
+ /**
+ * Picture Dimensions
+ */
+ public int width = 160;
+ public int height = 168;
+
+ /**
+ * Picture data.
+ */
+ public int[] picData;
+
+ /**
+ * Priority data.
+ */
+ public int[] priData;
+
+ /**
+ * Picture Picture Color.
+ */
+ public int picColor = -1;
+
+ /**
+ * Picture Priority Color.
+ */
+ public byte priColor = -1;
+
+ /**
+ * Pen Style
+ */
+ public byte penStyle = 0;
+
+ protected int[] pixel = new int[1];
+
+ protected int whitePixel;
+
+ /**
+ * Creates new Picture Context.
+ */
+ public PictureContext() {
+ picData = new int[width * height];
+ priData = new int[width * height];
+
+ whitePixel = translatePixel((byte) 15);
+
+ Arrays.fill(picData, whitePixel);
+ Arrays.fill(priData, 4);
+ }
+
+ /**
+ * Clips a variable with a maximum.
+ *
+ * @param v Variable to be clipped.
+ * @param max Maximum value that the to be clipped variable can have.
+ * @return The Variable clipped.
+ */
+ public static int clip(int v, int max) {
+ if (v > max)
+ v = max;
+
+ return v;
+ }
+
+ public int translatePixel(byte b) {
+ if (b == -1) {
+ return -1;
+ } else {
+ EgaUtils.getNativeColorModel().getDataElements(EgaUtils.getIndexColorModel().getRGB(b), pixel);
+ return pixel[0];
+ }
+ }
+
+ /**
+ * Obtain the index in the buffer where (x,y) is located.
+ *
+ * @param x X coordinate.
+ * @param y Y coordinate.
+ * @return Index in the buffer.
+ */
+ public final int getIndex(int x, int y) {
+ return (y * width) + x;
+ }
+
+ /**
+ * Obtain the color of the pixel asked.
+ *
+ * @param x X coordinate.
+ * @param y Y coordinate.
+ * @return Color at the specified pixel.
+ */
+ public final int getPixel(int x, int y) {
+ return picData[(y * width) + x];
+ }
+
+ /**
+ * Obtain the priority of the pixel asked.
+ *
+ * @param x X coordinate.
+ * @param y Y coordinate.
+ * @return Priority at the specified pixel.
+ */
+ public final int getPriorityPixel(int x, int y) {
+ return priData[(y * width) + x];
+ }
+
+ /**
+ * Set the (x,y) pixel to the current color and priority.
+ *
+ * @param x X coordinate.
+ * @param y Y coordinate.
+ * @see #picColor
+ * @see #priColor
+ */
+ public final void putPixel(int x, int y) {
+ int i;
+
+ if ((x >= width) || (y >= height)) {
+ return;
+ }
+
+ i = (y * width) + x;
+
+ if (picColor >= 0) {
+ picData[i] = picColor;
+ }
+
+ if (priColor >= 0) {
+ priData[i] = priColor;
+ }
+ }
+
+ /**
+ * Draw a line with current color and current priority.
+ *
+ * @param x1 Start X Coordinate.
+ * @param y1 Start Y Coordinate.
+ * @param x2 End X Coordinate.
+ * @param y2 End Y Coordinate.
+ * @see #picColor
+ * @see #priColor
+ * @see #putPixel(int, int)
+ */
+ public void drawLine(int x1, int y1, int x2, int y2) {
+ int x, y;
+
+ /* Clip! */
+ x1 = clip(x1, width - 1);
+ x2 = clip(x2, width - 1);
+ y1 = clip(y1, height - 1);
+ y2 = clip(y2, height - 1);
+
+ /* Vertical Line */
+ if (x1 == x2) {
+ if (y1 > y2) {
+ y = y1;
+ y1 = y2;
+ y2 = y;
+ }
+
+ for (; y1 <= y2; y1++) {
+ putPixel(x1, y1);
+ }
+ }
+ /* Horizontal Line */
+ else if (y1 == y2) {
+ if (x1 > x2) {
+ x = x1;
+ x1 = x2;
+ x2 = x;
+ }
+
+ for (; x1 <= x2; x1++) {
+ putPixel(x1, y1);
+ }
+ } else {
+ int deltaX = x2 - x1;
+ int deltaY = y2 - y1;
+ int stepX = 1;
+ int stepY = 1;
+ int detDelta;
+ int errorX;
+ int errorY;
+ int count;
+
+ if (deltaY < 0) {
+ stepY = -1;
+ deltaY = -deltaY;
+ }
+
+ if (deltaX < 0) {
+ stepX = -1;
+ deltaX = -deltaX;
+ }
+
+ if (deltaY > deltaX) {
+ count = deltaY;
+ detDelta = deltaY;
+ errorX = deltaY / 2;
+ errorY = 0;
+ } else {
+ count = deltaX;
+ detDelta = deltaX;
+ errorX = 0;
+ errorY = deltaX / 2;
+ }
+
+ x = x1;
+ y = y1;
+ putPixel(x, y);
+
+ do {
+ errorY = (errorY + deltaY);
+ if (errorY >= detDelta) {
+ errorY -= detDelta;
+ y += stepY;
+ }
+
+ errorX = (errorX + deltaX);
+ if (errorX >= detDelta) {
+ errorX -= detDelta;
+ x += stepX;
+ }
+
+ putPixel(x, y);
+ count--;
+ } while (count > 0);
+
+ putPixel(x, y);
+ }
+ }
+
+ public boolean isFillCorrect(int x, int y) {
+ if ((picColor < 0) && (priColor < 0)) {
+ return false;
+ }
+
+ if ((priColor < 0) && (picColor >= 0) && (picColor != whitePixel)) {
+ return (getPixel(x, y) == whitePixel);
+ }
+
+ if ((priColor >= 0) && (picColor < 0) && (priColor != 4)) {
+ return (getPriorityPixel(x, y) == 4);
+ }
+
+ return ((picColor >= 0) && (getPixel(x, y) == whitePixel) && (picColor != whitePixel));
+ }
+
+ protected Image loadImage(Toolkit toolkit, byte[] data) {
+ MemoryImageSource mis;
+
+ if (toolkit == null) {
+ toolkit = Toolkit.getDefaultToolkit();
+ }
+
+ mis = new MemoryImageSource(width, height, EgaUtils.getIndexColorModel(), data, 0, width);
+ return toolkit.createImage(mis);
+ }
+
+ protected Image loadImage(Toolkit toolkit, int[] data) {
+ MemoryImageSource mis;
+
+ if (toolkit == null) {
+ toolkit = Toolkit.getDefaultToolkit();
+ }
+
+ mis = new MemoryImageSource(width, height, EgaUtils.getNativeColorModel(), data, 0, width);
+ return toolkit.createImage(mis);
+ }
+
+ public Image getPictureImage(Toolkit toolkit) {
+ return loadImage(toolkit, picData);
+ }
+
+ public Image getPriorityImage(Toolkit toolkit) {
+ byte[] asByte = new byte[priData.length];
+ for (int i = 0; i < priData.length; i++) {
+ asByte[i] = (byte) priData[i];
+ }
+ return loadImage(toolkit, asByte);
+ }
+
+ public int[] getPictureData() {
+ return picData;
+ }
+
+ public int[] getPriorityData() {
+ return priData;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntry.java b/core/src/main/java/com/sierra/agi/pic/PictureEntry.java
new file mode 100644
index 0000000..5be569d
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntry.java
@@ -0,0 +1,13 @@
+/*
+ * PictureEntry.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+public abstract class PictureEntry {
+ public abstract void draw(PictureContext pictureContext);
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntryAbsLine.java b/core/src/main/java/com/sierra/agi/pic/PictureEntryAbsLine.java
new file mode 100644
index 0000000..e17f280
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntryAbsLine.java
@@ -0,0 +1,44 @@
+/*
+ * PictureEntryAbsLine.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import java.awt.*;
+import java.util.Enumeration;
+
+/**
+ *
0xF6
: Absolute line
+ *
+ * Function: Draws lines between points. The first two arguments are the
+ * starting coordinates. The remaining arguments are in groups of two which
+ * give the coordinates of the next location to draw a line to. There can be
+ * any number of arguments but there should always be an even number.
+ *
+ * Example: F6 30 50 34 51 38 53 F?
+ *
+ * This sequence draws a line from (48, 80) to (52, 81), and a line from
+ * (52, 81) to (56, 83).
+ *
+ */
+public class PictureEntryAbsLine extends PictureEntryMulti {
+ public void draw(PictureContext pictureContext) {
+ Enumeration en = points.elements();
+
+ Point p1 = (Point) en.nextElement();
+
+ if (points.size() == 1) {
+ pictureContext.drawLine(p1.x, p1.y, p1.x, p1.y);
+ } else {
+ while (en.hasMoreElements()) {
+ Point p2 = (Point) en.nextElement();
+ pictureContext.drawLine(p1.x, p1.y, p2.x, p2.y);
+ p1 = p2;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntryChangePen.java b/core/src/main/java/com/sierra/agi/pic/PictureEntryChangePen.java
new file mode 100644
index 0000000..9039f9b
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntryChangePen.java
@@ -0,0 +1,21 @@
+/*
+ * PictureEntryChangePen.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+public class PictureEntryChangePen extends PictureEntry {
+ protected byte penStyle;
+
+ public PictureEntryChangePen(byte penStyle) {
+ this.penStyle = penStyle;
+ }
+
+ public void draw(PictureContext pictureContext) {
+ pictureContext.penStyle = penStyle;
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntryChangePicColor.java b/core/src/main/java/com/sierra/agi/pic/PictureEntryChangePicColor.java
new file mode 100644
index 0000000..985e9e6
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntryChangePicColor.java
@@ -0,0 +1,21 @@
+/*
+ * PictureEntryChangePicColor.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+public class PictureEntryChangePicColor extends PictureEntry {
+ protected byte picColor;
+
+ public PictureEntryChangePicColor(byte picColor) {
+ this.picColor = picColor;
+ }
+
+ public void draw(PictureContext pictureContext) {
+ pictureContext.picColor = pictureContext.translatePixel(picColor);
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntryChangePriColor.java b/core/src/main/java/com/sierra/agi/pic/PictureEntryChangePriColor.java
new file mode 100644
index 0000000..08de391
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntryChangePriColor.java
@@ -0,0 +1,21 @@
+/*
+ * PictureEntryChangePicColor.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+public class PictureEntryChangePriColor extends PictureEntry {
+ protected byte priColor;
+
+ public PictureEntryChangePriColor(byte priColor) {
+ this.priColor = priColor;
+ }
+
+ public void draw(PictureContext pictureContext) {
+ pictureContext.priColor = priColor;
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntryDrawX.java b/core/src/main/java/com/sierra/agi/pic/PictureEntryDrawX.java
new file mode 100644
index 0000000..f592328
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntryDrawX.java
@@ -0,0 +1,60 @@
+/*
+ * PictureEntryDrawX.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import java.awt.*;
+import java.util.Enumeration;
+
+/**
+ * 0xF5
: Draw an X corner
+ *
+ * Function: The first two arguments for this action are the coordinates of
+ * the starting position on the screen in the order x and then y. The remaining
+ * arguments are in the order x1, y1, x2, y2, ...
+ *
+ * Note that the x component is the first to be changed and also note that this
+ * action does not necessarily end on either component, it just ends when the
+ * next byte of 0xF0 or above is encountered. A line is drawn after each byte
+ * is processed.
+ *
+ * Example: F5 16 16 18 12 16 F?
+ *
+ * (0x16, 0x12) (0x18, 0x12)
+ * EXX
+ * X S = Start
+ * X E = End
+ * X X = normal piXel
+ * SXX
+ * (0x16, 0x16) (0x18, 0x16)
+ */
+public class PictureEntryDrawX extends PictureEntryMulti {
+ public void draw(PictureContext pictureContext) {
+ Enumeration en = points.elements();
+ int x1, y1, x2, y2;
+ boolean b = true;
+ Point p;
+
+ p = (Point) en.nextElement();
+ x1 = x2 = p.x;
+ y1 = y2 = p.y;
+
+ while (en.hasMoreElements()) {
+ if (b) {
+ x2 = ((Integer) en.nextElement()).intValue();
+ } else {
+ y2 = ((Integer) en.nextElement()).intValue();
+ }
+
+ pictureContext.drawLine(x1, y1, x2, y2);
+ x1 = x2;
+ y1 = y2;
+ b = !b;
+ }
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntryDrawY.java b/core/src/main/java/com/sierra/agi/pic/PictureEntryDrawY.java
new file mode 100644
index 0000000..3c7243d
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntryDrawY.java
@@ -0,0 +1,58 @@
+/*
+ * PictureEntryDrawY.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import java.awt.*;
+import java.util.Enumeration;
+
+/**
+ * 0xF4
: Draw a Y corner
+ *
+ * Function: The first two arguments for this action are the coordinates of
+ * the starting position on the screen in the order x and then y. The remaining
+ * arguments are in the order y1, x1, y2, x2, ...
+ *
+ * Note that the y component is the first to be changed and also note that this
+ * action does not necessarily end on either component, it just ends when the
+ * next byte of 0xF0 or above is encountered. A line is drawn after each byte
+ * is processed.
+ *
+ * Example: F4 16 16 18 12 16 F?
+ *
+ * (0x12, 0x16) (0x16, 0x16)
+ * E S S = Start
+ * X X E = End
+ * XXXXX X = normal piXel
+ * (0x12, 0x18) (0x16, 0x18)
+ */
+public class PictureEntryDrawY extends PictureEntryMulti {
+ public void draw(PictureContext pictureContext) {
+ Enumeration en = points.elements();
+ int x1, y1, x2, y2;
+ boolean b = true;
+ Point p;
+
+ p = (Point) en.nextElement();
+ x1 = x2 = p.x;
+ y1 = y2 = p.y;
+
+ while (en.hasMoreElements()) {
+ if (b) {
+ y2 = ((Integer) en.nextElement()).intValue();
+ } else {
+ x2 = ((Integer) en.nextElement()).intValue();
+ }
+
+ pictureContext.drawLine(x1, y1, x2, y2);
+ x1 = x2;
+ y1 = y2;
+ b = !b;
+ }
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntryFill.java b/core/src/main/java/com/sierra/agi/pic/PictureEntryFill.java
new file mode 100644
index 0000000..74b0933
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntryFill.java
@@ -0,0 +1,141 @@
+/*
+ * PictureEntryFill.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import java.awt.*;
+import java.util.EmptyStackException;
+import java.util.Enumeration;
+
+/**
+ * 0xF8
: Fill
+ *
+ * Function: Flood fill from the locations given. Arguments are given in groups
+ * of two bytes which give the coordinates of the location to start the fill
+ * at. If picture drawing is enabled then it flood fills from that location on
+ * the picture screen to all pixels locations that it can reach which are white
+ * in colour. The boundary is given by any pixels which are not white.
+ *
+ * If priority drawing is enabled, and picture drawing is not enabled, then it
+ * flood fills from that location on the priority screen to all pixels that it
+ * can reach which are red in colour. The boundary in this case is given by any
+ * pixels which are not red.
+ *
+ * If both picture drawing and priority drawing are enabled, then a flood fill
+ * naturally enough takes place on both screens. In this case there is a
+ * difference in the way the fill takes place in the priority screen. The
+ * difference is that it not only looks for its own boundary, but also stops if
+ * it reaches a boundary that exists in the picture screen but does not
+ * necessarily exist in the priority screen.
+ *
+ */
+public class PictureEntryFill extends PictureEntryMulti {
+ public void draw(PictureContext pictureContext) {
+ Point current;
+ Enumeration en = points.elements();
+ PointStack stack = new PointStack(200, 200);
+ int width = pictureContext.width - 1;
+ int height = pictureContext.height - 1;
+
+ while (en.hasMoreElements()) {
+ current = (Point) en.nextElement();
+
+ stack.push(current.x, current.y);
+
+ try {
+ while (true) {
+ stack.pop(current);
+
+ if (pictureContext.isFillCorrect(current.x, current.y)) {
+ pictureContext.putPixel(current.x, current.y);
+
+ if (current.x > 0 && pictureContext.isFillCorrect(current.x - 1, current.y)) {
+ stack.push(current.x - 1, current.y);
+ }
+
+ if (current.x < width && pictureContext.isFillCorrect(current.x + 1, current.y)) {
+ stack.push(current.x + 1, current.y);
+ }
+
+ if (current.y < height && pictureContext.isFillCorrect(current.x, current.y + 1)) {
+ stack.push(current.x, current.y + 1);
+ }
+
+ if (current.y > 0 && pictureContext.isFillCorrect(current.x, current.y - 1)) {
+ stack.push(current.x, current.y - 1);
+ }
+ }
+ }
+ } catch (EmptyStackException esex) {
+ }
+ }
+ }
+
+ public static class PointStack {
+ protected int increment;
+ protected int elementCount;
+ protected short[] x;
+ protected short[] y;
+
+ /**
+ * Creates new Point Stack
+ */
+ public PointStack() {
+ increment = 15;
+ }
+
+ public PointStack(int initialSize, int increment) {
+ this.increment = increment;
+ ensureCapacity(initialSize);
+ }
+
+ public void pop(Point pt) {
+ if (elementCount == 0) {
+ throw new EmptyStackException();
+ }
+
+ elementCount--;
+ pt.x = x[elementCount];
+ pt.y = y[elementCount];
+ }
+
+ public void push(int x, int y) {
+ ensureCapacity(elementCount + 1);
+
+ this.x[elementCount] = (short) x;
+ this.y[elementCount] = (short) y;
+ elementCount++;
+ }
+
+ public void clear() {
+ elementCount = 0;
+ }
+
+ public void ensureCapacity(int minCapacity) {
+ if (x == null) {
+ x = new short[minCapacity + increment];
+ y = new short[minCapacity + increment];
+ } else if (x.length < minCapacity) {
+ short[] nx = new short[minCapacity + increment];
+ short[] ny = new short[minCapacity + increment];
+ int i, l = elementCount;
+
+ for (i = 0; i < l; i++) {
+ nx[i] = x[i];
+ }
+
+ for (i = 0; i < l; i++) {
+ ny[i] = y[i];
+ }
+
+ x = nx;
+ y = ny;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntryMulti.java b/core/src/main/java/com/sierra/agi/pic/PictureEntryMulti.java
new file mode 100644
index 0000000..40122de
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntryMulti.java
@@ -0,0 +1,28 @@
+/*
+ * PictureEntryMulti.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import java.awt.*;
+import java.util.Vector;
+
+public abstract class PictureEntryMulti extends PictureEntry {
+ protected Vector points = new Vector();
+
+ public void add(int x, int y) {
+ points.add(new Point(x, y));
+ }
+
+ public void add(int c) {
+ points.add(Integer.valueOf(c));
+ }
+
+ public void add(int[] c) {
+ points.add(c);
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntryPlot.java b/core/src/main/java/com/sierra/agi/pic/PictureEntryPlot.java
new file mode 100644
index 0000000..c4c46ad
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntryPlot.java
@@ -0,0 +1,164 @@
+/*
+ * PictureEntryPlot.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import java.awt.*;
+import java.util.Enumeration;
+
+public class PictureEntryPlot extends PictureEntryMulti {
+ /**
+ * Circle Bitmaps
+ */
+ protected static final short[][] circles = new short[][]
+ {
+ {0x80},
+ {0xfc},
+ {0x5f, 0xf4},
+ {0x66, 0xff, 0xf6, 0x60},
+ {0x23, 0xbf, 0xff, 0xff, 0xee, 0x20},
+ {0x31, 0xe7, 0x9e, 0xff, 0xff, 0xde, 0x79, 0xe3, 0x00},
+ {0x38, 0xf9, 0xf3, 0xef, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xf3, 0xe3, 0x80},
+ {0x18, 0x3c, 0x7e, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x7e, 0x7e, 0x3c, 0x18}
+ };
+
+ /**
+ * Splatter Brush Bitmaps
+ */
+ protected static final short[] splatterMap = new short[]
+ {
+ 0x20, 0x94, 0x02, 0x24, 0x90, 0x82, 0xa4, 0xa2,
+ 0x82, 0x09, 0x0a, 0x22, 0x12, 0x10, 0x42, 0x14,
+ 0x91, 0x4a, 0x91, 0x11, 0x08, 0x12, 0x25, 0x10,
+ 0x22, 0xa8, 0x14, 0x24, 0x00, 0x50, 0x24, 0x04
+ };
+
+ /**
+ * Starting Bit Position
+ */
+ protected static final short[] splatterStart = new short[]
+ {
+ 0x00, 0x18, 0x30, 0xc4, 0xdc, 0x65, 0xeb, 0x48,
+ 0x60, 0xbd, 0x89, 0x05, 0x0a, 0xf4, 0x7d, 0x7d,
+ 0x85, 0xb0, 0x8e, 0x95, 0x1f, 0x22, 0x0d, 0xdf,
+ 0x2a, 0x78, 0xd5, 0x73, 0x1c, 0xb4, 0x40, 0xa1,
+ 0xb9, 0x3c, 0xca, 0x58, 0x92, 0x34, 0xcc, 0xce,
+ 0xd7, 0x42, 0x90, 0x0f, 0x8b, 0x7f, 0x32, 0xed,
+ 0x5c, 0x9d, 0xc8, 0x99, 0xad, 0x4e, 0x56, 0xa6,
+ 0xf7, 0x68, 0xb7, 0x25, 0x82, 0x37, 0x3a, 0x51,
+ 0x69, 0x26, 0x38, 0x52, 0x9e, 0x9a, 0x4f, 0xa7,
+ 0x43, 0x10, 0x80, 0xee, 0x3d, 0x59, 0x35, 0xcf,
+ 0x79, 0x74, 0xb5, 0xa2, 0xb1, 0x96, 0x23, 0xe0,
+ 0xbe, 0x05, 0xf5, 0x6e, 0x19, 0xc5, 0x66, 0x49,
+ 0xf0, 0xd1, 0x54, 0xa9, 0x70, 0x4b, 0xa4, 0xe2,
+ 0xe6, 0xe5, 0xab, 0xe4, 0xd2, 0xaa, 0x4c, 0xe3,
+ 0x06, 0x6f, 0xc6, 0x4a, 0xa4, 0x75, 0x97, 0xe1
+ };
+
+ public void draw(PictureContext pictureContext) {
+ if ((pictureContext.penStyle & 0x20) == 0x20) {
+ drawPlot(pictureContext);
+ } else {
+ drawPoints(pictureContext);
+ }
+ }
+
+ public void drawPlot(PictureContext pictureContext) {
+ Enumeration en = points.elements();
+ int circlePos = 0;
+ int bitPos;
+ int x, y, x1, y1, penSize, penSizeTrue;
+ boolean circle;
+ int[] p;
+
+ circle = !((pictureContext.penStyle & 0x10) == 0x10);
+ penSize = (pictureContext.penStyle & 0x07);
+ penSizeTrue = penSize;
+
+ while (en.hasMoreElements()) {
+ p = (int[]) en.nextElement();
+ circlePos = 0;
+ bitPos = splatterStart[p[0]];
+ x = p[1];
+ y = p[2];
+
+ if (x < penSize) {
+ x = penSize - 1;
+ }
+
+ if (y < penSize) {
+ y = penSize;
+ }
+
+ for (y1 = y - penSize; y1 <= y + penSize; y1++) {
+ for (x1 = x - (penSize + 1) / 2; x1 <= x + penSize / 2; x1++) {
+ if (circle) {
+ if (!(((circles[penSizeTrue][circlePos >> 0x3] >> (0x7 - (circlePos & 0x7))) & 0x1) == 0x1)) {
+ circlePos++;
+ continue;
+ }
+
+ circlePos++;
+ }
+
+ if (((splatterMap[bitPos >> 3] >> (7 - (bitPos & 7))) & 1) == 1) {
+ pictureContext.putPixel(x1, y1);
+ }
+
+ bitPos++;
+
+ if (bitPos == 0xff) {
+ bitPos = 0;
+ }
+ }
+ }
+ }
+ }
+
+ public void drawPoints(PictureContext pictureContext) {
+ Enumeration en = points.elements();
+ int circlePos;
+ int x, y, x1, y1, penSize, penSizeTrue;
+ boolean circle;
+ Point p;
+
+ circle = !((pictureContext.penStyle & 0x10) == 0x10);
+ penSize = (pictureContext.penStyle & 0x07);
+ penSizeTrue = penSize;
+
+ while (en.hasMoreElements()) {
+ p = (Point) en.nextElement();
+ x = p.x;
+ y = p.y;
+ circlePos = 0;
+
+ if (x < penSize) {
+ x = penSize - 1;
+ }
+
+ if (y < penSize) {
+ y = penSize;
+ }
+
+ for (y1 = y - penSize; y1 <= y + penSize; y1++) {
+ for (x1 = x - (penSize + 1) / 2; x1 <= x + penSize / 2; x1++) {
+ if (circle) {
+ if (!(((circles[penSizeTrue][circlePos >> 0x3] >> (0x7 - (circlePos & 0x7))) & 0x1) == 0x1)) {
+ circlePos++;
+ continue;
+ }
+
+ circlePos++;
+ }
+
+ pictureContext.putPixel(x1, y1);
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureEntryRelLine.java b/core/src/main/java/com/sierra/agi/pic/PictureEntryRelLine.java
new file mode 100644
index 0000000..49df638
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureEntryRelLine.java
@@ -0,0 +1,66 @@
+/*
+ * PictureEntryRelLine.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import java.awt.*;
+import java.util.Enumeration;
+
+/**
+ * 0xF7
: Relative line
+ *
+ * Function: Draw short relative lines. By relative we mean that the data gives
+ * displacements which are relative from the current location. The first
+ * argument gives the standard starting coordinates. All the arguments which
+ * follow these first two are of the following format:
+ *
+ * +---+-----------+---+-----------+
+ * | S | Xdisp | S | Ydisp |
+ * +---+---+---+---+---+---+---+---+
+ * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ * +---+---+---+---+---+---+---+---+
+ *
+ * This gives a displacement range of between -7 and +7 for both the X and the Y
+ * direction.
+ *
+ * Example: F7 10 10 22 40 06 CC F?
+ *
+ * S
+ * + S = Start
+ * X+++X X = End of each line
+ * + + = pixels in each line
+ * E + E = End
+ * + +
+ * + + Remember that CC = (x-4, y-4).
+ * ++
+ * X
+ */
+
+public class PictureEntryRelLine extends PictureEntryMulti {
+ public void draw(PictureContext pictureContext) {
+ Enumeration en = points.elements();
+ Point p;
+ int x1, y1, x2, y2;
+
+ p = (Point) en.nextElement();
+ x1 = x2 = p.x;
+ y1 = y2 = p.y;
+
+ pictureContext.putPixel(x1, y1);
+
+ while (en.hasMoreElements()) {
+ p = (Point) en.nextElement();
+ x2 += p.x;
+ y2 += p.y;
+
+ pictureContext.drawLine(x1, y1, x2, y2);
+ x1 = x2;
+ y1 = y2;
+ }
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureException.java b/core/src/main/java/com/sierra/agi/pic/PictureException.java
new file mode 100644
index 0000000..adcdee4
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureException.java
@@ -0,0 +1,31 @@
+/*
+ * PictureException.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+/**
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class PictureException extends Exception {
+ /**
+ * Creates new PictureException
without detail message.
+ */
+ public PictureException() {
+ }
+
+ /**
+ * Constructs an PictureException
with the specified detail
+ * message.
+ *
+ * @param msg the detail message.
+ */
+ public PictureException(String msg) {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/pic/PictureProvider.java b/core/src/main/java/com/sierra/agi/pic/PictureProvider.java
new file mode 100644
index 0000000..22a742c
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/PictureProvider.java
@@ -0,0 +1,16 @@
+/*
+ * PictureProvider.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface PictureProvider {
+ Picture loadPicture(InputStream inputStream) throws IOException, PictureException;
+}
diff --git a/core/src/main/java/com/sierra/agi/pic/StandardPictureProvider.java b/core/src/main/java/com/sierra/agi/pic/StandardPictureProvider.java
new file mode 100644
index 0000000..0e555fc
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/pic/StandardPictureProvider.java
@@ -0,0 +1,414 @@
+/*
+ * StandardPictureProvider.java
+ * Adventure Game Interpreter Picture Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.pic;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
+public class StandardPictureProvider implements PictureProvider {
+ protected static final short CMD_START = (short) 0xF0;
+
+ protected static final short CMD_CHANGEPICCOLOR = (short) 0xF0;
+ protected static final short CMD_DISABLEPICDRAW = (short) 0xF1;
+ protected static final short CMD_CHANGEPRICOLOR = (short) 0xF2;
+ protected static final short CMD_DISABLEPRIDRAW = (short) 0xF3;
+ protected static final short CMD_DRAWYCORNER = (short) 0xF4;
+ protected static final short CMD_DRAWXCORNER = (short) 0xF5;
+ protected static final short CMD_DRAWABSLINE = (short) 0xF6;
+ protected static final short CMD_DRAWRELLINE = (short) 0xF7;
+ protected static final short CMD_FILL = (short) 0xF8;
+ protected static final short CMD_CHANGEPEN = (short) 0xF9;
+ protected static final short CMD_PLOT = (short) 0xFA;
+ protected static final short CMD_EOP = (short) 0xFF;
+
+ public Picture loadPicture(InputStream in) throws IOException, PictureException {
+ int command, c, x, y;
+ Vector entries = new Vector();
+ int lastPen = 0;
+ PictureEntryMulti entry;
+
+ try {
+ command = in.read();
+
+ while (true) {
+ if (command < 0) {
+ break;
+ }
+
+ switch (command) {
+ case CMD_CHANGEPICCOLOR:
+ entries.add(new PictureEntryChangePicColor((byte) in.read()));
+ command = in.read();
+ break;
+
+ case CMD_CHANGEPRICOLOR:
+ entries.add(new PictureEntryChangePriColor((byte) in.read()));
+ command = in.read();
+ break;
+
+ case CMD_DISABLEPICDRAW:
+ entries.add(new PictureEntryChangePicColor((byte) -1));
+ command = in.read();
+ break;
+
+ case CMD_DISABLEPRIDRAW:
+ entries.add(new PictureEntryChangePriColor((byte) -1));
+ command = in.read();
+ break;
+
+ case CMD_DRAWXCORNER:
+ case CMD_DRAWYCORNER:
+ if (command == CMD_DRAWXCORNER) {
+ entry = new PictureEntryDrawX();
+ } else {
+ entry = new PictureEntryDrawY();
+ }
+
+ entry.add(in.read(), in.read());
+
+ while (true) {
+ command = in.read();
+
+ if ((command >= CMD_START) || (command < 0)) {
+ break;
+ }
+
+ entry.add(command);
+ }
+
+ entries.add(entry);
+ break;
+
+ case CMD_DRAWABSLINE:
+ entry = new PictureEntryAbsLine();
+ entry.add(in.read(), in.read());
+
+ while (true) {
+ command = in.read();
+
+ if ((command >= CMD_START) || (command < 0)) {
+ break;
+ }
+
+ entry.add(command, in.read());
+ }
+
+ entries.add(entry);
+ break;
+
+ case CMD_DRAWRELLINE:
+ entry = new PictureEntryRelLine();
+ entry.add(in.read(), in.read());
+
+ while (true) {
+ command = in.read();
+
+ if ((command >= CMD_START) || (command < 0)) {
+ break;
+ }
+
+ x = (command & 0x70) >> 4;
+ y = (command & 0x07);
+
+ if ((command & 0x80) == 0x80) {
+ x = -x;
+ }
+
+ if ((command & 0x08) == 0x08) {
+ y = -y;
+ }
+
+ entry.add(x, y);
+ }
+
+ entries.add(entry);
+ break;
+
+ case CMD_FILL:
+ entry = new PictureEntryFill();
+
+ while (true) {
+ command = in.read();
+
+ if ((command >= CMD_START) || (command < 0)) {
+ break;
+ }
+
+ c = in.read();
+ entry.add(command, c);
+ }
+
+ entries.add(entry);
+ break;
+
+ case CMD_CHANGEPEN:
+ lastPen = in.read();
+ entries.add(new PictureEntryChangePen((byte) lastPen));
+ command = in.read();
+ break;
+
+ case CMD_PLOT:
+ entry = new PictureEntryPlot();
+
+ while (true) {
+ command = in.read();
+
+ if ((command < 0) || (command >= CMD_START)) {
+ break;
+ }
+
+ if ((lastPen & 0x20) == 0x20) {
+ command = (command >> 1) & 0x7f;
+ x = in.read();
+ y = in.read();
+ entry.add(new int[]{command, x, y});
+ } else {
+ x = command;
+ y = in.read();
+ entry.add(x, y);
+ }
+ }
+
+ entries.add(entry);
+ break;
+
+ case CMD_EOP:
+ command = -1;
+ break;
+
+ default:
+ throw new CorruptedPictureException();
+ }
+ }
+ } catch (EOFException eex) {
+ }
+
+ in.close();
+ return new Picture(entries);
+ }
+
+ /*
+ protected boolean next() throws PictureException
+ {
+ if (in == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ if (nextCommand < 0)
+ {
+ nextCommand = in.read();
+
+ if (nextCommand < 0)
+ {
+ if (provider != null)
+ {
+ in.close();
+ in = null;
+ }
+
+ endReached = true;
+ return false;
+ }
+ }
+
+ switch (nextCommand)
+ {
+ case CMD_CHANGEPICCOLOR:
+ picContext.picColor = (byte)in.read();
+ nextCommand = -1;
+ break;
+
+ case CMD_CHANGEPRICOLOR:
+ picContext.priColor = (byte)in.read();
+ nextCommand = -1;
+ break;
+
+ case CMD_DISABLEPICDRAW:
+ picContext.picColor = (byte)-1;
+ nextCommand = -1;
+ break;
+
+ case CMD_DISABLEPRIDRAW:
+ picContext.priColor = (byte)-1;
+ nextCommand = -1;
+ break;
+
+ case CMD_DRAWXCORNER:
+ drawXCorner();
+ break;
+
+ case CMD_DRAWYCORNER:
+ drawYCorner();
+ break;
+
+ case CMD_DRAWABSLINE:
+ drawAbsoluteLine();
+ break;
+
+ case CMD_DRAWRELLINE:
+ drawRelativeLine();
+ break;
+
+ case CMD_FILL:
+ drawFill();
+ break;
+
+ case CMD_CHANGEPEN:
+ picContext.penStyle = (byte)in.read();
+ nextCommand = -1;
+ break;
+
+ case CMD_PLOT:
+ drawPlot();
+ break;
+
+ case CMD_EOP:
+ in.close();
+ in = null;
+ break;
+
+ default:
+ throw new CorruptedPictureException();
+ }
+ }
+ catch (IOException ioex)
+ {
+ in = null;
+ }
+
+ return true;
+ }*/
+
+ /*
+ protected void drawPlot() throws IOException
+ {
+ int c, x, y;
+
+ while (true)
+ {
+ c = in.read();
+
+ if ((c < 0) || (c >= CMD_START))
+ {
+ nextCommand = c;
+ break;
+ }
+
+ if ((picContext.penStyle & 0x20) == 0x20)
+ {
+ c = (c >> 1) & 0x7f;
+ x = in.read();
+ y = in.read();
+ drawPlot(c, x, y);
+ }
+ else
+ {
+ x = c;
+ y = in.read();
+ drawPlot(x, y);
+ }
+ }
+ }
+
+ protected void drawPlot(int patternNumber, int x, int y)
+ {
+ int circlePos = 0;
+ int bitPos = splatterStart[patternNumber];
+ int x1, y1, penSize, penSizeTrue;
+ boolean circle;
+
+ circle = !((picContext.penStyle & 0x10) == 0x10);
+ penSize = (picContext.penStyle & 0x07);
+ penSizeTrue = penSize;
+
+ if (x < penSize)
+ {
+ x = penSize - 1;
+ }
+
+ if (y < penSize)
+ {
+ y = penSize;
+ }
+
+ for (y1 = y - penSize; y1 <= y + penSize; y1++)
+ {
+ for (x1 = x - (penSize + 1) / 2; x1 <= x + penSize / 2; x1++)
+ {
+ if (circle)
+ {
+ if (!(((circles[penSizeTrue][circlePos >> 0x3] >> (0x7 - (circlePos & 0x7))) & 0x1) == 0x1))
+ {
+ circlePos++;
+ continue;
+ }
+
+ circlePos++;
+ }
+
+ if (((splatterMap[bitPos >> 3] >> (7 - (bitPos & 7))) & 1) == 1)
+ {
+ picContext.putPixel(x1, y1);
+ }
+
+ bitPos++;
+
+ if (bitPos == 0xff)
+ {
+ bitPos = 0;
+ }
+ }
+ }
+ }
+
+ protected void drawPlot(int x, int y)
+ {
+ int circlePos = 0;
+ int x1, y1, penSize, penSizeTrue;
+ boolean circle;
+
+ circle = !((picContext.penStyle & 0x10) == 0x10);
+ penSize = (picContext.penStyle & 0x07);
+ penSizeTrue = penSize;
+
+ if (x < penSize)
+ {
+ x = penSize - 1;
+ }
+
+ if (y < penSize)
+ {
+ y = penSize;
+ }
+
+ for (y1 = y - penSize; y1 <= y + penSize; y1++)
+ {
+ for (x1 = x - (penSize + 1) / 2; x1 <= x + penSize / 2; x1++)
+ {
+ if (circle)
+ {
+ if (!(((circles[penSizeTrue][circlePos >> 0x3] >> (0x7 - (circlePos & 0x7))) & 0x1) == 0x1))
+ {
+ circlePos++;
+ continue;
+ }
+
+ circlePos++;
+ }
+
+ picContext.putPixel(x1, y1);
+ }
+ }
+ }*/
+}
diff --git a/core/src/main/java/com/sierra/agi/res/CorruptedResourceException.java b/core/src/main/java/com/sierra/agi/res/CorruptedResourceException.java
new file mode 100644
index 0000000..401f97d
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/CorruptedResourceException.java
@@ -0,0 +1,33 @@
+/**
+ * CorruptedResourceException.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+/**
+ * The resource is currupted.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public final class CorruptedResourceException extends ResourceException {
+ /**
+ * Creates new CorruptedResourceException
without detail message.
+ */
+ public CorruptedResourceException() {
+ super();
+ }
+
+ /**
+ * Constructs an CorruptedResourceException
with the specified detail message.
+ *
+ * @param msg Detail message.
+ */
+ public CorruptedResourceException(String msg) {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/res/NoDirectoryAvailableException.java b/core/src/main/java/com/sierra/agi/res/NoDirectoryAvailableException.java
new file mode 100644
index 0000000..89b7c9e
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/NoDirectoryAvailableException.java
@@ -0,0 +1,34 @@
+/**
+ * NoDirectoryAvailableException.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+/**
+ * There is no directory available. Throwed when a ResourceProvider is created
+ * with a folder that doesn't contain any resource directory.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public final class NoDirectoryAvailableException extends ResourceException {
+ /**
+ * Creates new NoDirectoryAvailableException
without detail message.
+ */
+ public NoDirectoryAvailableException() {
+ super();
+ }
+
+ /**
+ * Constructs an NoDirectoryAvailableException
with the specified detail message.
+ *
+ * @param msg Detail message.
+ */
+ public NoDirectoryAvailableException(String msg) {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/res/NoVolumeAvailableException.java b/core/src/main/java/com/sierra/agi/res/NoVolumeAvailableException.java
new file mode 100644
index 0000000..3803ce9
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/NoVolumeAvailableException.java
@@ -0,0 +1,34 @@
+/**
+ * NoVolumeAvailableException.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+/**
+ * There is no volume available. Throwed when a ResourceProvider is created
+ * with a folder that doesn't contain any resource directory.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public final class NoVolumeAvailableException extends ResourceException {
+ /**
+ * Creates new NoVolumeAvailableException
without detail message.
+ */
+ public NoVolumeAvailableException() {
+ super();
+ }
+
+ /**
+ * Constructs an NoVolumeAvailableException
with the specified detail message.
+ *
+ * @param msg Detail message.
+ */
+ public NoVolumeAvailableException(String msg) {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/res/ResourceCache.java b/core/src/main/java/com/sierra/agi/res/ResourceCache.java
new file mode 100644
index 0000000..e1fff83
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/ResourceCache.java
@@ -0,0 +1,370 @@
+/**
+ * ResourceCache.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+import com.sierra.agi.inv.InventoryObjects;
+import com.sierra.agi.inv.InventoryProvider;
+import com.sierra.agi.logic.Logic;
+import com.sierra.agi.logic.LogicException;
+import com.sierra.agi.logic.LogicProvider;
+import com.sierra.agi.pic.Picture;
+import com.sierra.agi.pic.PictureException;
+import com.sierra.agi.pic.PictureProvider;
+import com.sierra.agi.sound.Sound;
+import com.sierra.agi.sound.SoundProvider;
+import com.sierra.agi.view.View;
+import com.sierra.agi.view.ViewException;
+import com.sierra.agi.view.ViewProvider;
+import com.sierra.agi.word.Words;
+import com.sierra.agi.word.WordsProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+
+public class ResourceCache {
+ protected static Class[] providerParameters = {ResourceConfiguration.class};
+ protected LogicProvider logProvider;
+ protected InventoryProvider invProvider;
+ protected PictureProvider picProvider;
+ protected ResourceProvider resProvider;
+ protected SoundProvider sndProvider;
+ protected ViewProvider viwProvider;
+ protected WordsProvider wrdProvider;
+ protected Object[] logics;
+ protected int[] logicsc;
+ protected Object[] pictures;
+ protected int[] picturesc;
+ protected Object[] sounds;
+ protected int[] soundsc;
+ protected Object[] views;
+ protected int[] viewsc;
+ protected Words words;
+ protected InventoryObjects objects;
+
+ protected ResourceCache() {
+ }
+
+ public ResourceCache(ResourceProvider resProvider) {
+ this.resProvider = resProvider;
+ }
+
+ public synchronized ResourceProvider getResourceProvider() {
+ return resProvider;
+ }
+
+ protected Object getProvider(String clazzName) {
+ try {
+ try {
+ return Class.forName(clazzName).newInstance();
+ } catch (InstantiationException e) {
+ Class clazz = Class.forName(clazzName);
+ Constructor cons = clazz.getConstructor(providerParameters);
+ Object[] o = new Object[1];
+
+ o[0] = resProvider.getConfiguration();
+
+ return cons.newInstance(o);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(e.getClass().getName() + ": " + clazzName);
+ }
+ }
+
+ public synchronized SoundProvider getSoundProvider() {
+ if (sndProvider == null) {
+ sndProvider = (SoundProvider) getProvider(System.getProperty("com.sierra.agi.sound.SoundProvider", "com.sierra.agi.sound.StandardSoundProvider"));
+ }
+
+ return sndProvider;
+ }
+
+ public synchronized void setSoundProvider(SoundProvider sndProvider) {
+ this.sndProvider = sndProvider;
+ }
+
+ public synchronized InventoryProvider getInventoryProvider() {
+ if (invProvider == null) {
+ invProvider = (InventoryProvider) getProvider(System.getProperty("com.sierra.agi.inv.LogicProvider", "com.sierra.agi.inv.InventoryObjects"));
+ }
+
+ return invProvider;
+ }
+
+ public synchronized void setInventoryProvider(InventoryProvider invProvider) {
+ this.invProvider = invProvider;
+ }
+
+ public synchronized LogicProvider getLogicProvider() {
+ if (logProvider == null) {
+ logProvider = (LogicProvider) getProvider(System.getProperty("com.sierra.agi.logic.LogicProvider", "com.sierra.agi.logic.StandardLogicProvider"));
+ }
+
+ return logProvider;
+ }
+
+ public synchronized void setLogicProvider(LogicProvider logProvider) {
+ this.logProvider = logProvider;
+ }
+
+ public synchronized ViewProvider getViewProvider() {
+ if (viwProvider == null) {
+ viwProvider = (ViewProvider) getProvider(System.getProperty("com.sierra.agi.view.ViewProvider", "com.sierra.agi.view.StandardViewProvider"));
+ }
+
+ return viwProvider;
+ }
+
+ public synchronized void setViewProvider(ViewProvider viwProvider) {
+ this.viwProvider = viwProvider;
+ }
+
+ public synchronized WordsProvider getWordsProvider() {
+ if (wrdProvider == null) {
+ wrdProvider = (WordsProvider) getProvider(System.getProperty("com.sierra.agi.word.WordsProvider", "com.sierra.agi.word.Words"));
+ }
+
+ return wrdProvider;
+ }
+
+ public synchronized void setWordsProvider(WordsProvider wrdProvider) {
+ this.wrdProvider = wrdProvider;
+ }
+
+ public synchronized PictureProvider getPictureProvider() {
+ if (picProvider == null) {
+ picProvider = (PictureProvider) getProvider(System.getProperty("com.sierra.agi.pic.PictureProvider", "com.sierra.agi.pic.StandardPictureProvider"));
+ }
+
+ return picProvider;
+ }
+
+ public synchronized void setPictureProvider(PictureProvider picProvider) {
+ this.picProvider = picProvider;
+ }
+
+ protected Object obtainResource(Object[] objects, int[] objectsc, short resNumber, boolean inc) {
+ Object obj = objects[resNumber];
+
+ if (obj != null) {
+ if (obj instanceof Reference) {
+ obj = ((Reference) obj).get();
+
+ if (inc) {
+ objects[resNumber] = obj;
+ }
+
+ if (obj == null) {
+ return null;
+ }
+ }
+
+ if (inc) {
+ objectsc[resNumber]++;
+ }
+
+ return obj;
+ }
+
+ return null;
+ }
+
+ protected void flushResource(Object[] objects, int[] objectsc, short resNumber) {
+ if (objectsc[resNumber] > 0) {
+ objectsc[resNumber]--;
+ }
+
+ if (objects[resNumber] == null) {
+ return;
+ }
+
+ if (objectsc[resNumber] <= 0) {
+ if (!(objects[resNumber] instanceof Reference)) {
+ objects[resNumber] = generateReference(objects[resNumber]);
+ }
+ }
+ }
+
+ protected synchronized Sound obtainSound(short resNumber, boolean inc) throws IOException, ResourceException {
+ Object o;
+
+ if (sounds == null) {
+ sounds = new Object[256];
+ soundsc = new int[256];
+ }
+
+ o = obtainResource(sounds, soundsc, resNumber, inc);
+
+ if (o == null) {
+ o = getSoundProvider().loadSound(resProvider.open(ResourceProvider.TYPE_SOUND, resNumber));
+
+ if (inc) {
+ sounds[resNumber] = o;
+ soundsc[resNumber] = 1;
+ } else {
+ sounds[resNumber] = generateReference(o);
+ }
+ }
+
+ return (Sound) o;
+ }
+
+ public void loadSound(short resNumber) throws IOException, ResourceException {
+ obtainSound(resNumber, true);
+ }
+
+ public Sound getSound(short resNumber) throws IOException, ResourceException {
+ return obtainSound(resNumber, false);
+ }
+
+ public synchronized void unloadSound(short resNumber) {
+ flushResource(sounds, soundsc, resNumber);
+ }
+
+ protected synchronized Logic obtainLogic(short resNumber, boolean inc) throws IOException, ResourceException, LogicException {
+ Object o;
+
+ if (logics == null) {
+ logics = new Object[256];
+ logicsc = new int[256];
+ }
+
+ o = obtainResource(logics, logicsc, resNumber, inc);
+
+ if (o == null) {
+ o = getLogicProvider().loadLogic(resNumber, resProvider.open(ResourceProvider.TYPE_LOGIC, resNumber), resProvider.getSize(ResourceProvider.TYPE_LOGIC, resNumber));
+
+ if (inc) {
+ logics[resNumber] = o;
+ logicsc[resNumber] = 1;
+ } else {
+ logics[resNumber] = generateReference(o);
+ }
+ }
+
+ return (Logic) o;
+ }
+
+ public void loadLogic(short resNumber) throws IOException, ResourceException, LogicException {
+ obtainLogic(resNumber, true);
+ }
+
+ public Logic getLogic(short resNumber) throws IOException, ResourceException, LogicException {
+ return obtainLogic(resNumber, false);
+ }
+
+ public synchronized void unloadLogic(short resNumber) {
+ flushResource(logics, logicsc, resNumber);
+ }
+
+ protected synchronized Picture obtainPicture(short resNumber, boolean inc) throws IOException, ResourceException, PictureException {
+ Object o;
+
+ if (pictures == null) {
+ pictures = new Object[256];
+ picturesc = new int[256];
+ }
+
+ o = obtainResource(pictures, picturesc, resNumber, inc);
+
+ if (o == null) {
+ o = getPictureProvider().loadPicture(resProvider.open(ResourceProvider.TYPE_PICTURE, resNumber));
+
+ if (inc) {
+ pictures[resNumber] = o;
+ picturesc[resNumber] = 1;
+ } else {
+ pictures[resNumber] = generateReference(o);
+ }
+ }
+
+ return (Picture) o;
+ }
+
+ public void loadPicture(short resNumber) throws IOException, ResourceException, PictureException {
+ obtainPicture(resNumber, true);
+ }
+
+ public Picture getPicture(short resNumber) throws IOException, ResourceException, PictureException {
+ return obtainPicture(resNumber, false);
+ }
+
+ public synchronized void unloadPicture(short resNumber) {
+ flushResource(pictures, picturesc, resNumber);
+ }
+
+ protected synchronized View obtainView(short resNumber, boolean inc) throws IOException, ResourceException, ViewException {
+ if (views == null) {
+ views = new Object[256];
+ viewsc = new int[256];
+ }
+
+ Object o = obtainResource(views, viewsc, resNumber, inc);
+
+ if (o == null) {
+ o = getViewProvider().loadView(resProvider.open(ResourceProvider.TYPE_VIEW, resNumber), resProvider.getSize(ResourceProvider.TYPE_VIEW, resNumber));
+
+ if (inc) {
+ views[resNumber] = o;
+ viewsc[resNumber] = 1;
+ } else {
+ views[resNumber] = generateReference(o);
+ }
+ }
+
+ return (View) o;
+ }
+
+ public void loadView(short resNumber) throws IOException, ResourceException, ViewException {
+ obtainView(resNumber, true);
+ }
+
+ public View getView(short resNumber) throws IOException, ResourceException, ViewException {
+ return obtainView(resNumber, false);
+ }
+
+ public synchronized void unloadView(short resNumber) {
+ flushResource(views, viewsc, resNumber);
+ }
+
+ public synchronized Words getWords() throws IOException, ResourceException {
+ if (words == null) {
+ words = getWordsProvider().loadWords(resProvider.open(ResourceProvider.TYPE_WORD, (short) 0));
+ }
+
+ return words;
+ }
+
+ public synchronized InventoryObjects getObjects() throws IOException, ResourceException {
+ if (objects == null) {
+ objects = getInventoryProvider().loadInventory(resProvider.open(ResourceProvider.TYPE_OBJECT, (short) 0));
+ }
+
+ return objects;
+ }
+
+ protected Reference generateReference(Object o) {
+ return new WeakReference(o);
+ }
+
+ public File getPath() {
+ return resProvider.getPath();
+ }
+
+ public String getVersion() {
+ return this.resProvider.getVersion();
+ }
+
+ public String getV3GameSig() {
+ return this.resProvider.getV3GameSig();
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/res/ResourceCacheFile.java b/core/src/main/java/com/sierra/agi/res/ResourceCacheFile.java
new file mode 100644
index 0000000..d566c48
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/ResourceCacheFile.java
@@ -0,0 +1,44 @@
+/**
+ * ResourceCacheFile.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class ResourceCacheFile extends ResourceCache {
+ public ResourceCacheFile(File file) throws IOException, ResourceException {
+ if (!file.exists()) {
+ throw new FileNotFoundException();
+ }
+
+ if (file.isDirectory()) {
+ loadFS(file);
+ }
+
+ if (resProvider == null) {
+ String p = file.getPath();
+
+ if (p.endsWith(".zip")) {
+ //resProvider = new ResourceProviderZip(file);
+ } else {
+ loadFS(file);
+ }
+ }
+ }
+
+ private void loadFS(File file) throws IOException, ResourceException {
+ try {
+ resProvider = new com.sierra.agi.res.v2.ResourceProviderV2(file);
+ } catch (ResourceException e) {
+ System.out.println("Found AGI Version 3 Game.");
+ resProvider = new com.sierra.agi.res.v3.ResourceProviderV3(file);
+ }
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/res/ResourceCacheFileDebug.java b/core/src/main/java/com/sierra/agi/res/ResourceCacheFileDebug.java
new file mode 100644
index 0000000..bb4c890
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/ResourceCacheFileDebug.java
@@ -0,0 +1,18 @@
+/*
+ * ResourceCacheFileDebug.java
+ * Adventure Game Interpreter Logic Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+import java.io.File;
+import java.io.IOException;
+
+public class ResourceCacheFileDebug extends ResourceCacheFile {
+ public ResourceCacheFileDebug(File file) throws IOException, ResourceException {
+ super(file);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/res/ResourceConfiguration.java b/core/src/main/java/com/sierra/agi/res/ResourceConfiguration.java
new file mode 100644
index 0000000..743e3af
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/ResourceConfiguration.java
@@ -0,0 +1,16 @@
+/**
+ * ResourceConfiguration.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+public class ResourceConfiguration {
+ public short engineEmulation;
+ public boolean amiga;
+ public boolean agds;
+ public String name;
+}
diff --git a/core/src/main/java/com/sierra/agi/res/ResourceException.java b/core/src/main/java/com/sierra/agi/res/ResourceException.java
new file mode 100644
index 0000000..3f103f6
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/ResourceException.java
@@ -0,0 +1,33 @@
+/**
+ * ResourceException.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+/**
+ * Base class for Resource Exceptions.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class ResourceException extends Exception {
+ /**
+ * Creates new ResourceException
without detail message.
+ */
+ public ResourceException() {
+ super();
+ }
+
+ /**
+ * Constructs an ResourceException
with the specified detail message.
+ *
+ * @param msg Detail message.
+ */
+ public ResourceException(String msg) {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/res/ResourceNotExistingException.java b/core/src/main/java/com/sierra/agi/res/ResourceNotExistingException.java
new file mode 100644
index 0000000..6abeafc
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/ResourceNotExistingException.java
@@ -0,0 +1,33 @@
+/**
+ * ResourceNotExistingException.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+/**
+ * The resource doesn't exists.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public final class ResourceNotExistingException extends ResourceException {
+ /**
+ * Creates new ResourceNotExistingException
without detail message.
+ */
+ public ResourceNotExistingException() {
+ super();
+ }
+
+ /**
+ * Constructs an ResourceNotExistingException
with the specified detail message.
+ *
+ * @param msg Detail message.
+ */
+ public ResourceNotExistingException(String msg) {
+ super(msg);
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/res/ResourceProvider.java b/core/src/main/java/com/sierra/agi/res/ResourceProvider.java
new file mode 100644
index 0000000..108a32d
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/ResourceProvider.java
@@ -0,0 +1,114 @@
+/**
+ * ResourceProvider.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * The ResourceProvider interface is a standard
+ * way for loading resources dynamicly. Gives the
+ * interresing possibility of being able to read
+ * them from every kind of data container.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.02
+ */
+public interface ResourceProvider {
+ /** Logic Resource Type. */
+ byte TYPE_LOGIC = 0;
+
+ /** Picture Resource Type. */
+ byte TYPE_PICTURE = 1;
+
+ /** Sound Resource Type. */
+ byte TYPE_SOUND = 2;
+
+ /** View Resource Type. */
+ byte TYPE_VIEW = 3;
+
+ /** Compiled Logic Resource Type. (Reserverd for future uses) */
+ byte TYPE_LOGIC_COMPILED = 4;
+
+ /** Object File. */
+ byte TYPE_OBJECT = 10;
+
+ /** Word Tokenizer File. */
+ byte TYPE_WORD = 11;
+
+ /** Fast Provider Type. (ie. File system) */
+ byte PROVIDER_TYPE_FAST = 0;
+
+ /** Slow Provider Type. (ie. URL) */
+ byte PROVIDER_TYPE_SLOW = 1;
+
+ /**
+ * Calculate the CRC of the resources.
+ *
+ * @return CRC of the resources.
+ */
+ long getCRC();
+
+ /**
+ * Retreive the count of resources of the specified type.
+ *
+ * @param resType Resource type
+ * @return Resource count.
+ */
+ int count(byte resType) throws ResourceException;
+
+ /**
+ * Enumerate the resource numbers of the specified type.
+ *
+ * @param resType Resource type
+ * @return Returns an array containing the resource numbers.
+ */
+ short[] enumerate(byte resType) throws ResourceException;
+
+ /**
+ * Retreive the size in bytes of the specified resource.
+ *
+ * @param resType Resource type
+ * @param resNumber Resource number
+ * @return Returns the size in bytes of the specified resource.
+ */
+ int getSize(byte resType, short resNumber) throws ResourceException, IOException;
+
+ /**
+ * Open the specified resource and return a pointer
+ * to the resource. The InputStream is decrypted/decompressed,
+ * if neccessary, by this function. (So you don't have to care
+ * about them.)
+ *
+ * @param resType Resource type
+ * @param resNumber Resource number
+ * @return InputStream linked to the specified resource.
+ */
+ InputStream open(byte resType, short resNumber) throws ResourceException, IOException;
+
+ /**
+ * Return the provider type. Used has a optimization hint by
+ * the resource cache. (For example, PROVIDER_TYPE_SLOW whould
+ * mean to never ask twice for the same resource because transfert
+ * rate may be slow.)
+ */
+ byte getProviderType();
+
+ /**
+ * Return the resource configuration.
+ */
+ ResourceConfiguration getConfiguration();
+
+ File getPath();
+
+ String getVersion();
+
+ String getV3GameSig();
+}
diff --git a/core/src/main/java/com/sierra/agi/res/ResourceProviderZip.java b/core/src/main/java/com/sierra/agi/res/ResourceProviderZip.java
new file mode 100644
index 0000000..d6fd800
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/ResourceProviderZip.java
@@ -0,0 +1,61 @@
+/*
+ * ResourceProviderZip.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public abstract class ResourceProviderZip implements ResourceProvider {
+ protected ZipFile file;
+
+ protected int[] counts;
+ protected int[][] enums;
+
+ public ResourceProviderZip(File file) throws IOException {
+ this.file = new ZipFile(file);
+ }
+
+ public static void convert(File file, ResourceProvider provider) throws ResourceException, IOException {
+ ZipOutputStream outZip = new ZipOutputStream(new FileOutputStream(file));
+ DataOutputStream outData = new DataOutputStream(outZip);
+ ResourceConfiguration config = provider.getConfiguration();
+
+ outZip.setLevel(9);
+ outZip.putNextEntry(new ZipEntry("info"));
+
+ convert(outData, provider, TYPE_LOGIC);
+ convert(outData, provider, TYPE_PICTURE);
+ convert(outData, provider, TYPE_SOUND);
+ convert(outData, provider, TYPE_VIEW);
+
+ outData.writeLong(provider.getCRC());
+ outData.writeShort(config.engineEmulation);
+ outData.writeBoolean(config.amiga);
+ outData.writeBoolean(config.agds);
+ outData.writeUTF(config.name);
+
+ outZip.close();
+ }
+
+ protected static void convert(DataOutputStream outData, ResourceProvider provider, byte resType) throws ResourceException, IOException {
+ short[] en = provider.enumerate(resType);
+ int index;
+
+ outData.writeInt(en.length);
+
+ for (index = 0; index < en.length; index++) {
+ outData.writeShort(en[index]);
+ }
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/res/ResourceTypeInvalidException.java b/core/src/main/java/com/sierra/agi/res/ResourceTypeInvalidException.java
new file mode 100644
index 0000000..542e4c5
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/ResourceTypeInvalidException.java
@@ -0,0 +1,32 @@
+/**
+ * ResourceTypeInvalidException.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+/**
+ * Resource type passed by parameter is invalid.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class ResourceTypeInvalidException extends ResourceException {
+ /**
+ * Creates new ResourceTypeInvalidException
without detail message.
+ */
+ public ResourceTypeInvalidException() {
+ }
+
+ /**
+ * Constructs an ResourceTypeInvalidException
with the specified detail message.
+ *
+ * @param msg Detail message.
+ */
+ public ResourceTypeInvalidException(String msg) {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/res/VolumeNotFoundException.java b/core/src/main/java/com/sierra/agi/res/VolumeNotFoundException.java
new file mode 100644
index 0000000..7c4ff2a
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/VolumeNotFoundException.java
@@ -0,0 +1,33 @@
+/**
+ * NoVolumeAvailableException.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res;
+
+/**
+ * The volume is not found.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public final class VolumeNotFoundException extends ResourceException {
+ /**
+ * Creates new VolumeNotFoundException
without detail message.
+ */
+ public VolumeNotFoundException() {
+ super();
+ }
+
+ /**
+ * Constructs an VolumeNotFoundException
with the specified detail message.
+ *
+ * @param msg Detail message.
+ */
+ public VolumeNotFoundException(String msg) {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/res/dir/ResourceDirectory.java b/core/src/main/java/com/sierra/agi/res/dir/ResourceDirectory.java
new file mode 100644
index 0000000..daf9969
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/dir/ResourceDirectory.java
@@ -0,0 +1,127 @@
+/**
+ * ResourceDirectory.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res.dir;
+
+import com.sierra.agi.io.IOUtils;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ *
Directories
+ * Each directory file is of the same format. They contain a finite number
+ * of three byte entries, no more than 256. The size will vary depending on the
+ * number of files of the type that the directory file is pointing to. Dividing
+ * the filesize by three gives the maximum file number of that type of data
+ * file. Each entry is of the following format:
+ *
+ * Byte 1 Byte 2 Byte 3
+ * 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+ * V V V V P P P P P P P P P P P P P P P P P P P P
+ *
+ * V = VOL number.
+ * P = Position (offset into VOL file)
+ *
+ * The entry number itself gives the number of the data file that it is
+ * pointing to. For example, if the following three byte entry is entry
+ * number 45 in the SOUND directory file, 12 3D FE
+ * then SOUND.45 is located at position 0x23DFE in the VOL.1 file. The first
+ * entry number is entry 0.
+ *
+ * If the three bytes contain the value 0xFFFFFF, then the resource does not
+ * exist.
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class ResourceDirectory {
+ /** Maximum number of entries in a directory. */
+ protected static final int MAX_ENTRIES = 256;
+
+ /** Directory Entries */
+ protected int[] entries = new int[MAX_ENTRIES];
+
+ /** Directory CRC */
+ protected int crc;
+
+ /** Directory */
+ protected int count;
+
+ /** Creates a new Resource Directory */
+ public ResourceDirectory(InputStream in) throws IOException {
+ byte[] b = new byte[3];
+ int i = 0, j = 0, e;
+ int c = 0, n = 0;
+
+ try {
+ while (true) {
+ IOUtils.fill(in, b, 0, 3);
+
+ for (j = 0; j < 3; j++) {
+ c += (b[j] & 0xff);
+ }
+
+ if (b[0] != -1) {
+ entries[i] = ((b[0] & 0xf0) >> 4) << 24 | ((b[0] & 0x0f) << 16) | ((b[1] & 0xff) << 8) | (b[2] & 0xff);
+ n++;
+ } else {
+ entries[i] = -1;
+ }
+
+ i++;
+ }
+ } catch (EOFException ex) {
+ }
+
+ for (; i < MAX_ENTRIES; i++) {
+ entries[i] = -1;
+ }
+
+ crc = c;
+ count = n;
+ }
+
+ public int getCRC() {
+ return crc;
+ }
+
+ public int getVolume(int resourceNumber) {
+ if (entries[resourceNumber] == -1) {
+ return -1;
+ }
+
+ return (entries[resourceNumber] & 0xff000000) >> 24;
+ }
+
+ public int getOffset(int resourceNumber) {
+ if (entries[resourceNumber] == -1) {
+ return -1;
+ }
+
+ return (entries[resourceNumber] & 0x00ffffff);
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public short[] getNumbers() {
+ short[] numbers = new short[count];
+ short i, j;
+
+ for (i = 0, j = 0; (i < 256) && (j < count); i++) {
+ if (entries[i] != -1) {
+ numbers[j++] = i;
+ }
+ }
+
+ return numbers;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/res/v2/ResourceProviderV2.java b/core/src/main/java/com/sierra/agi/res/v2/ResourceProviderV2.java
new file mode 100644
index 0000000..30a471a
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/v2/ResourceProviderV2.java
@@ -0,0 +1,566 @@
+/**
+ * ResourceProviderV2.java
+ * Adventure Game Interpreter Resource Package
+ *
+ * Created by Dr. Z
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.res.v2;
+
+import com.sierra.agi.io.ByteCaster;
+import com.sierra.agi.io.ByteCasterStream;
+import com.sierra.agi.io.CryptedInputStream;
+import com.sierra.agi.io.SegmentedInputStream;
+import com.sierra.agi.res.CorruptedResourceException;
+import com.sierra.agi.res.NoDirectoryAvailableException;
+import com.sierra.agi.res.NoVolumeAvailableException;
+import com.sierra.agi.res.ResourceConfiguration;
+import com.sierra.agi.res.ResourceException;
+import com.sierra.agi.res.ResourceNotExistingException;
+import com.sierra.agi.res.ResourceProvider;
+import com.sierra.agi.res.ResourceTypeInvalidException;
+import com.sierra.agi.res.dir.ResourceDirectory;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Properties;
+
+/**
+ * Provide access to resources via the standard storage methods.
+ * It reads unmodified sierra's resource files.
+ *
+ * All AGI games have either one directory file, or more commonly, four.
+ * AGI version 2 games will have the files LOGDIR, PICDIR, VIEWDIR, and SNDDIR.
+ * This single file is basically the four version 2 files joined together
+ * except that it has an 8 byte header giving the position of each directory
+ * within the single file.
+ *
+ * The directory files give the location of the data types within the VOL
+ * files. The type of directory determines the type of data. For example, the
+ * LOGDIR gives the locations of the LOGIC files.
+ *
+ * Note: In this description and elsewhere in documents written by me,
+ * the AGI data called LOGIC, PICTURE, VIEW, and SOUND data are referred to by
+ * me as files even though they are part of a single VOL file. I think of
+ * the VOL file as sort of a virtual storage device in itself that holds many
+ * files. Some documents call the files contains in VOL files "resources".
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class ResourceProviderV2 implements ResourceProvider {
+ /**
+ * AGDS's Decryption Key. This key is used to decrypt
+ * AGDS games.
+ */
+ public static final String AGDS_KEY = "Alex Simkin";
+ /**
+ * Sierra's Decryption Key. This key used to decrypt
+ * original sierra games.
+ */
+ public static final String SIERRA_KEY = "Avis Durgan";
+ /**
+ * Resource's CRC.
+ */
+ protected long crc;
+ /**
+ * Resource's Entries Tables.
+ */
+ protected ResourceDirectory[] entries = new ResourceDirectory[4];
+ /**
+ * Path to resources files.
+ */
+ protected File path;
+ /**
+ * Resource Configuration
+ */
+ protected ResourceConfiguration configuration = new ResourceConfiguration();
+
+ protected String version = "unknown";
+
+ /**
+ * Initialize the ResourceProvider implementation to access
+ * resource on the file system.
+ *
+ * @param folder Resource's folder or File inside the resource's
+ * folder.
+ */
+ public ResourceProviderV2(File folder) throws IOException, ResourceException {
+ if (!folder.exists()) {
+ throw new FileNotFoundException();
+ }
+
+ if (folder.isDirectory()) {
+ path = folder.getAbsoluteFile();
+ } else {
+ path = folder.getParentFile();
+ }
+
+ readVolumes();
+ readDirectories();
+ readVersion();
+ calculateCRC();
+ calculateConfiguration();
+ }
+
+ public static String getKey(boolean agds) {
+ return System.getProperty("com.sierra.agi.res.key", agds ? AGDS_KEY : SIERRA_KEY);
+ }
+
+ public static boolean isCrypted(File file) {
+ boolean b = false;
+
+ try {
+ ByteCasterStream bstream = new ByteCasterStream(new FileInputStream(file));
+
+ if (bstream.lohiReadUnsignedShort() > file.length()) {
+ b = true;
+ }
+
+ bstream.close();
+ return b;
+ } catch (Throwable t) {
+ return false;
+ }
+ }
+
+ protected void validateType(byte resType) throws ResourceTypeInvalidException {
+ if ((resType > TYPE_WORD) || (resType < TYPE_LOGIC)) {
+ throw new ResourceTypeInvalidException();
+ }
+ }
+
+ /**
+ * Retreive the count of resources of the specified type.
+ * Only valid with Locic, Picture, Sound and View resource
+ * types.
+ *
+ * @param resType Resource type
+ * @return Resource count.
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_LOGIC
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_PICTURE
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_SOUND
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_VIEW
+ */
+ public int count(byte resType) throws ResourceException {
+ validateType(resType);
+
+ if (resType >= TYPE_OBJECT) {
+ return 1;
+ }
+
+ return entries[resType].getCount();
+ }
+
+ /**
+ * Enumerate the resource numbers of the specified type.
+ * Only valid with Locic, Picture, Sound and View resource
+ * types.
+ *
+ * @param resType Resource type
+ * @return Array containing the resource numbers.
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_LOGIC
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_PICTURE
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_SOUND
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_VIEW
+ */
+ public short[] enumerate(byte resType) throws ResourceException {
+ validateType(resType);
+
+ return entries[resType].getNumbers();
+ }
+
+ /**
+ * Open the specified resource and return a pointer
+ * to the resource. The InputStream is decrypted/decompressed,
+ * if neccessary, by this function. (So you don't have to care
+ * about them.)
+ *
+ * @param resType Resource type
+ * @param resNumber Resource number. Ignored if resource type
+ * is TYPE_OBJECT
or
+ * TYPE_WORD
+ * @return InputStream linked to the specified resource.
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_LOGIC
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_OBJECT
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_PICTURE
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_SOUND
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_VIEW
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_WORD
+ */
+ public InputStream open(byte resType, short resNumber) throws ResourceException, IOException {
+ File volf;
+
+ switch (resType) {
+ case ResourceProvider.TYPE_OBJECT:
+ volf = getDirectoryFile(resType);
+
+ if (isCrypted(volf)) {
+ return new CryptedInputStream(new FileInputStream(volf), getKey(false));
+ } else {
+ return new FileInputStream(volf);
+ }
+
+ case ResourceProvider.TYPE_WORD:
+ return new FileInputStream(getDirectoryFile(resType));
+ }
+
+ try {
+ if (entries[resType] != null) {
+ int vol, offset, length;
+
+ vol = entries[resType].getVolume(resNumber);
+ offset = entries[resType].getOffset(resNumber);
+
+ if ((vol != -1) && (offset != -1)) {
+ byte[] b;
+ RandomAccessFile file;
+ InputStream in;
+
+ b = new byte[5];
+ file = new RandomAccessFile(getVolumeFile(vol), "r");
+ file.seek(offset);
+ file.read(b, 0, 5);
+
+ if ((b[0] != 0x12) || (b[1] != 0x34)) {
+ throw new CorruptedResourceException();
+ }
+
+ length = ByteCaster.lohiUnsignedShort(b, 3);
+ in = new SegmentedInputStream(file, offset + 5, length);
+
+ if (resType == TYPE_LOGIC) {
+ int startPos, numMessages, offsetCrypted;
+
+ // Calculate the Messages Offset
+ file.read(b, 0, 2);
+ startPos = ByteCaster.lohiUnsignedShort(b, 0) + 2;
+ file.seek(offset + startPos + 5);
+ file.read(b, 0, 3);
+ numMessages = ByteCaster.lohiUnsignedByte(b, 0);
+ offsetCrypted = startPos + 3 + (numMessages * 2);
+ file.seek(offset + 5);
+
+ in = new CryptedInputStream(in, getKey(false), offsetCrypted);
+ }
+
+ return in;
+ }
+ }
+
+ throw new ResourceNotExistingException();
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResourceTypeInvalidException();
+ }
+ }
+
+ /**
+ * Calculate the CRC of the resources. In this implentation
+ * the CRC is not calculated by this function, it only return
+ * the cached CRC value.
+ *
+ * @return CRC of the resources.
+ */
+ public long getCRC() {
+ return crc;
+ }
+
+ public byte getProviderType() {
+ return PROVIDER_TYPE_FAST;
+ }
+
+ public ResourceConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ protected File getVolumeFile(int vol) throws IOException {
+ File file = getGameFile(path, "vol." + vol);
+
+ if (!file.exists()) {
+ throw new FileNotFoundException("File " + file.getPath() + " can't be found.");
+ }
+
+ return file;
+ }
+
+ /**
+ * To account for different platforms, where there may or may not be a case
+ * sensitive file system, and where the game files may or may not have been
+ * copied from another platform, we attempt here to look for the requested
+ * game file firstly as-is, then in uppercase form, and then in lowercase.
+ *
+ * @param path The File that represents the folder that contains the game files.
+ * @param fileName The name of the file to get.
+ * @return A File representing the game file.
+ */
+ protected File getGameFile(File path, String fileName) {
+ File file = new File(path, fileName);
+ if (!file.exists()) {
+ file = new File(path, fileName.toUpperCase());
+ if (!file.exists()) {
+ file = new File(path, fileName.toLowerCase());
+ }
+ }
+ return file;
+ }
+
+ protected File getDirectoryFile(byte resType) throws IOException {
+ File file;
+
+ switch (resType) {
+ case ResourceProvider.TYPE_OBJECT:
+ file = getGameFile(path, "object");
+ break;
+ case ResourceProvider.TYPE_WORD:
+ file = getGameFile(path, "words.tok");
+ break;
+ case ResourceProvider.TYPE_LOGIC:
+ file = getGameFile(path, "logdir");
+ break;
+ case ResourceProvider.TYPE_PICTURE:
+ file = getGameFile(path, "picdir");
+ break;
+ case ResourceProvider.TYPE_SOUND:
+ file = getGameFile(path, "snddir");
+ break;
+ case ResourceProvider.TYPE_VIEW:
+ file = getGameFile(path, "viewdir");
+ break;
+ default:
+ return null;
+ }
+
+ if (!file.exists()) {
+ throw new FileNotFoundException();
+ }
+
+ return file;
+ }
+
+ /**
+ * Retreive the size in bytes of the specified resource.
+ *
+ * @param resType Resource type
+ * @param resNumber Resource number. Ignored if resource type
+ * is TYPE_OBJECT
or
+ * TYPE_WORD
+ * @return Size in bytes of the specified resource.
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_LOGIC
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_OBJECT
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_PICTURE
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_SOUND
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_VIEW
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_WORD
+ */
+ public int getSize(byte resType, short resNumber) throws ResourceException, IOException {
+ switch (resType) {
+ case ResourceProvider.TYPE_OBJECT:
+ case ResourceProvider.TYPE_WORD:
+ return (int) getDirectoryFile(resType).length();
+ }
+
+ try {
+ if (entries[resType] != null) {
+ int vol, offset;
+
+ vol = entries[resType].getVolume(resNumber);
+ offset = entries[resType].getOffset(resNumber);
+
+ if ((vol != -1) && (offset != -1)) {
+ byte[] b;
+ RandomAccessFile file;
+
+ b = new byte[5];
+ file = new RandomAccessFile(getVolumeFile(vol), "r");
+ file.seek(offset);
+ file.read(b);
+ file.close();
+
+ if ((b[0] != 0x12) || (b[1] != 0x34)) {
+ throw new CorruptedResourceException();
+ }
+
+ return ByteCaster.lohiUnsignedShort(b, 3);
+ }
+ }
+
+ throw new ResourceNotExistingException();
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResourceTypeInvalidException();
+ }
+ }
+
+ /**
+ * Find volumes files
+ */
+ protected void readVolumes() throws NoVolumeAvailableException {
+ boolean founded = false;
+ int i = 0;
+ File volf;
+
+ while (true) {
+ volf = getGameFile(path, "vol." + i);
+
+ if (volf.exists()) {
+ founded = true;
+ break;
+ }
+
+ if (i > 50) {
+ break;
+ }
+
+ i++;
+ }
+
+ if (!founded) {
+ throw new NoVolumeAvailableException();
+ }
+ }
+
+ /**
+ * Read all directory files
+ */
+ protected void readDirectories() throws NoDirectoryAvailableException, IOException {
+ byte i;
+ int j;
+ File dir;
+ InputStream stream;
+
+ for (i = 0, j = 0; i < 4; i++) {
+ dir = getDirectoryFile(i);
+
+ if (dir != null) {
+ stream = new FileInputStream(dir);
+ entries[i] = new ResourceDirectory(stream);
+ stream.close();
+ j++;
+ }
+ }
+
+ if (j == 0) {
+ throw new NoDirectoryAvailableException();
+ }
+ }
+
+ /**
+ * Calculate the Resource's CRC
+ */
+ protected void calculateCRC() throws IOException {
+ File dirf = new File(path, "vol.crc");
+
+ try {
+ /* Check if the CRC has been pre-calculated */
+ DataInputStream meta = new DataInputStream(new FileInputStream(dirf));
+
+ crc = meta.readLong();
+ meta.close();
+ } catch (IOException ex) {
+ /* CRC need to be calculated from scratch */
+ crc = calculateCRCFromScratch();
+
+ /* Write down the CRC for next times */
+ DataOutputStream meta = new DataOutputStream(new FileOutputStream(dirf));
+
+ meta.writeLong(crc);
+ meta.close();
+ }
+ }
+
+ protected int calculateCRCFromScratch() throws IOException {
+ File[] dir;
+ int i, j, c;
+
+ c = 0;
+ dir = new File[2];
+ dir[0] = getGameFile(path, "object");
+ dir[1] = getGameFile(path, "words.tok");
+
+ for (i = 0; i < entries.length; i++) {
+ if (entries[i] != null) {
+ c += entries[i].getCRC();
+ }
+ }
+
+ for (i = 0; i < dir.length; i++) {
+ FileInputStream stream = new FileInputStream(dir[i]);
+
+ while (true) {
+ j = stream.read();
+
+ if (j == -1)
+ break;
+
+ c += j;
+ }
+
+ stream.close();
+ }
+
+ return c;
+ }
+
+ protected void calculateConfiguration() {
+ Properties props = new Properties();
+ String scrc = "0x" + Long.toString(crc, 16);
+
+ String ver = props.getProperty(scrc, "0x2917");
+ configuration.amiga = ver.indexOf('a') != -1;
+ configuration.agds = ver.indexOf('g') != -1;
+ ver = ver.substring(2);
+
+ while (!Character.isDigit(ver.charAt(ver.length() - 1))) {
+ ver = ver.substring(0, ver.length() - 1);
+ }
+
+ configuration.engineEmulation = (Integer.valueOf(ver, 16).shortValue());
+
+ props = new Properties();
+
+ configuration.name = props.getProperty(scrc, "Unknown Game");
+ }
+
+ public File getPath() {
+ return this.path;
+ }
+
+ private void readVersion() throws IOException {
+ File file = getGameFile(path, "agidata.ovl");
+
+ if (!file.exists()) {
+ return;
+ }
+
+ byte[] fileContent = Files.readAllBytes(file.toPath());
+
+ for (int i = 0; i < fileContent.length; i++) {
+ if (fileContent[i] == 86 && fileContent[i + 1] == 101 && fileContent[i + 2] == 114 && fileContent[i + 3] == 115 && fileContent[i + 4] == 105 && fileContent[i + 5] == 111 && fileContent[i + 6] == 110 && fileContent[i + 7] == 32) {
+ int j;
+ for (j = i + 8; fileContent[j] != 0; j++) {
+ }
+
+ this.version = new String(fileContent, i + 8, j - (i + 8), StandardCharsets.US_ASCII);
+ break;
+ }
+ }
+ }
+
+ public String getVersion() {
+ return this.version;
+ }
+
+ public String getV3GameSig() {
+ // This is V2, so it doesn't apply. Return null;
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/res/v3/ResourceProviderV3.java b/core/src/main/java/com/sierra/agi/res/v3/ResourceProviderV3.java
new file mode 100644
index 0000000..66ebda2
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/res/v3/ResourceProviderV3.java
@@ -0,0 +1,385 @@
+/*
+ * ResourceProviderV3.java
+ */
+
+package com.sierra.agi.res.v3;
+
+import com.sierra.agi.io.*;
+import com.sierra.agi.res.*;
+import com.sierra.agi.res.dir.ResourceDirectory;
+
+import java.io.*;
+import java.util.Arrays;
+
+/**
+ * Provide access to resources via the standard storage methods.
+ * It reads unmodified sierra's resource files.
+ *
+ * AGIv3 stores resources in a slightly different way from AGIv2. The first
+ * significant difference is in the length of the resource header which is
+ * now seven bytes.
+ *
+ *
+ * Byte | Meaning |
+ * 0-1 | Signature (0x12--0x34) |
+ * 2 | Vol number that the resource is contained in |
+ * 3-4 | Uncompressed resource size (LO-HI) |
+ * 5-6 | Compressed resource size (LO-HI) |
+ *
+ *
+ * Instead of one resource size as in AGIv2, there are now two sizes. Most of
+ * the resources in AGIv3 games are compressed with a form of LZW. Some of them
+ * are not though. The interpreter determines whether the resource is compressed
+ * by comparing the values of the two sizes given in the header information. If
+ * they are equal, then it knows that the resource is stored uncompressed.
+ * However, if the sizes do not match, this does not mean that the file is
+ * compressed with LZW. If the file is a PICTURE file, then it is stored with
+ * its own limited form of compression. This is why the top bit of the third
+ * byte in the header is used to tell the interpreter that the resource is a
+ * PICTURE file, otherwise it would think that the resource was compressed with
+ * LZW.
+ *
+ * As far as I can tell, none of the PICTUREs are compressed with LZW. This may
+ * well be possible though. It could also be possible for the PICTURE to be
+ * totally uncompressed (i.e. it wouldn't use the PICTURE compression method),
+ * but I haven't seen any examples of either of the above two cases.
+ *
+ *
LZW compression
+ *
+ * The compression used with version 3 games is an adaptive form of LZW. The LZW
+ * algorithm is not explained here, but it basically compresses data by
+ * representing previous strings by single codes. When these strings are
+ * encountered again, the code can be stored instead. The following information
+ * states how the AGIv3 algorithm differs from the standard LZW algorithm. There
+ * are plenty of places on the net where you can find a description of the LZW
+ * algorithm if you are not familiar with it.
+ *
+ * AGIv3 uses an adaptive form of LZW that starts by using 9 bit codes and when
+ * the code space is full, it progresses on to 10 bits and so on. As with normal
+ * LZW, codes 0-255 represent the standard ASCII characters. The next two codes
+ * have a special meaning:
+ *
+ * 256 is used as a start over code. The table is cleared, the number of bits
+ * set back to 9, and the process begins again with the next code being 258.
+ *
+ * 257 tells the interpreter that it has reached the end of the resource.
+ *
+ * Code 256 seems to be the first code stored in all compressed resources. This
+ * is probably just to make sure everything is initialized for beginning the
+ * compression process. As was mentioned above, the first code used for the LZW
+ * table itself is code 258. From there it stores pairs of prefix codes and
+ * appended characters for each table entry until it reaches code 512 at which
+ * stage it switches to storing the codes using 10 bits and then 11 and so on.
+ * It appears that it will never get to 12 bits because code 256 always seems
+ * to turn up just before it needs to switch up to 12 bits, i.e. when code 2048
+ * is required. Carl Muckenhoupt's decrypt routine for SCI games specifically
+ * prevents it from switching to 12 bits anyway. Whether there is ever a case
+ * where code 256 does not intervene, it has not yet been determined.
+ *
+ * Note: I should point out that Carl and myself both arrived at the above
+ * algorithm independently which confirms that the compression used in the early
+ * SCI games was identical to that used in AGIv3.
+ *
+ *
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class ResourceProviderV3 extends com.sierra.agi.res.v2.ResourceProviderV2 {
+ protected File[] vols;
+ protected File[] dirs;
+
+ /**
+ * Initialize the ResourceProvider implentation to access
+ * resource on the file system.
+ *
+ * @param folder Resource's folder or File inside the resource's
+ * folder.
+ */
+ public ResourceProviderV3(File folder) throws IOException, ResourceException {
+ super(folder);
+ }
+
+ /**
+ * Find volumes files
+ */
+ protected void readVolumes() throws NoVolumeAvailableException {
+ vols = path.listFiles(new VolumeFilenameFilter());
+
+ if (vols == null) {
+ throw new NoVolumeAvailableException();
+ }
+
+ if (vols.length == 0) {
+ throw new NoVolumeAvailableException();
+ }
+
+ Arrays.sort(vols, new VolumeSorter());
+ }
+
+ protected int calculateCRCFromScratch() throws IOException {
+ byte[] b = new byte[8];
+ int c, i;
+ InputStream stream;
+
+ c = super.calculateCRCFromScratch();
+
+ stream = new FileInputStream(dirs[0]);
+ stream.read(b, 0, 8);
+ stream.close();
+
+ for (i = 0; i < 8; i++) {
+ c += (b[i] & 0xff);
+ }
+
+ return c;
+ }
+
+ /**
+ * Read directory files
+ */
+ protected void readDirectories() throws NoDirectoryAvailableException, IOException {
+ byte[] b = new byte[8];
+ int[] o = new int[8];
+ InputStream stream;
+ RandomAccessFile dirfile;
+ int i, j, ax;
+
+ findDirectories();
+ stream = new FileInputStream(dirs[0]);
+ stream.read(b, 0, 8);
+ stream.close();
+
+ for (i = 0; i < 4; i++) {
+ o[i] = ByteCaster.lohiUnsignedShort(b, i * 2);
+ }
+
+ for (i = 0; i < 4; i++) {
+ ax = 0xffffff;
+
+ for (j = 0; j < 4; j++) {
+ if ((o[j] > o[i]) && (o[j] < ax)) {
+ ax = o[j];
+ }
+ }
+
+ if (ax == 0xffffff) {
+ ax = (int) dirs[0].length();
+ }
+
+ o[i + 4] = ax;
+ }
+
+ dirfile = new RandomAccessFile(dirs[0], "r");
+ o[4] -= o[0];
+ entries[0] = new ResourceDirectory(new SegmentedInputStream(dirfile, o[0], o[4]));
+
+ o[5] -= o[1];
+ entries[1] = new ResourceDirectory(new SegmentedInputStream(dirfile, o[1], o[5]));
+
+ o[6] -= o[2];
+ entries[3] = new ResourceDirectory(new SegmentedInputStream(dirfile, o[2], o[6]));
+
+ o[7] -= o[3];
+ entries[2] = new ResourceDirectory(new SegmentedInputStream(dirfile, o[3], o[7]));
+ }
+
+ /**
+ * Find all directory files
+ */
+ protected void findDirectories() throws NoDirectoryAvailableException {
+ dirs = path.listFiles(new DirectoryFilenameFilter());
+
+ if (dirs == null) {
+ throw new NoDirectoryAvailableException();
+ }
+
+ if (dirs.length == 0) {
+ throw new NoDirectoryAvailableException();
+ }
+
+ Arrays.sort(dirs, new DirectorySorter());
+ }
+
+ /**
+ * Open the specified resource and return a pointer
+ * to the resource. The InputStream is decrypted/decompressed,
+ * if neccessary, by this function. (So you don't have to care
+ * about them.)
+ *
+ * @param resType Resource type
+ * @param resNumber Resource number. Ignored if resource type
+ * is TYPE_OBJECT
or
+ * TYPE_WORD
+ * @return InputStream linked to the specified resource.
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_LOGIC
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_OBJECT
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_PICTURE
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_SOUND
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_VIEW
+ * @see com.sierra.agi.res.ResourceProvider#TYPE_WORD
+ */
+ public InputStream open(byte resType, short resNumber) throws IOException, ResourceException {
+ if (resType > TYPE_WORD) {
+ throw new ResourceTypeInvalidException();
+ }
+
+ switch (resType) {
+ case ResourceProvider.TYPE_OBJECT:
+ if (isCrypted(dirs[1])) {
+ return new CryptedInputStream(new FileInputStream(dirs[1]), getKey(false));
+ }
+
+ return new FileInputStream(dirs[1]);
+
+ case ResourceProvider.TYPE_WORD:
+ return new FileInputStream(dirs[2]);
+ }
+
+ try {
+ if (entries[resType] != null) {
+ int vol, offset, compressed, uncompressed;
+
+ vol = entries[resType].getVolume(resNumber);
+ offset = entries[resType].getOffset(resNumber);
+
+ if ((vol != -1) && (offset != -1)) {
+ byte[] b;
+ RandomAccessFile file;
+ InputStream in;
+
+ try {
+ b = new byte[7];
+ file = new RandomAccessFile(vols[vol], "r");
+ file.seek(offset);
+ file.read(b, 0, 7);
+ } catch (IndexOutOfBoundsException ioobex) {
+ throw new ResourceNotExistingException();
+ }
+
+ if ((b[0] != 0x12) || (b[1] != 0x34)) {
+ throw new CorruptedResourceException();
+ }
+
+ uncompressed = ByteCaster.lohiUnsignedShort(b, 3);
+ compressed = ByteCaster.lohiUnsignedShort(b, 5);
+ in = new SegmentedInputStream(file, offset + 7, compressed);
+
+ if (resType == TYPE_PICTURE) {
+ in = new PictureInputStream(in);
+ } else {
+ if (compressed != uncompressed) {
+ in = new LZWInputStream(in);
+ }
+ }
+
+ return in;
+ }
+ }
+ } catch (IndexOutOfBoundsException ioobex) {
+ throw new ResourceTypeInvalidException();
+ }
+
+ throw new ResourceNotExistingException();
+ }
+
+ protected File getVolumeFile(int vol) throws IOException {
+ File file = vols[vol];
+
+ if (!file.exists()) {
+ throw new FileNotFoundException();
+ }
+
+ return file;
+ }
+
+ protected File getDirectoryFile(int resType) throws IOException {
+ File file;
+
+ switch (resType) {
+ case ResourceProvider.TYPE_OBJECT:
+ file = dirs[1];
+ break;
+ case ResourceProvider.TYPE_WORD:
+ file = dirs[2];
+ break;
+ case ResourceProvider.TYPE_LOGIC:
+ case ResourceProvider.TYPE_PICTURE:
+ case ResourceProvider.TYPE_SOUND:
+ case ResourceProvider.TYPE_VIEW:
+ file = dirs[0];
+ default:
+ return null;
+ }
+
+ if (!file.exists()) {
+ throw new FileNotFoundException();
+ }
+
+ return file;
+ }
+
+ public String getV3GameSig() {
+ return dirs[0].getName().toUpperCase().replaceAll("DIR$", "");
+ }
+
+ protected static class VolumeFilenameFilter implements java.io.FilenameFilter {
+ public boolean accept(File dir, String name) {
+ int c;
+ String s;
+
+ c = name.lastIndexOf('.');
+
+ if (c == -1) {
+ return false;
+ }
+
+ if (!Character.isDigit(name.charAt(c + 1))) {
+ return false;
+ }
+
+ s = name.substring(0, c);
+ s = s.toLowerCase();
+
+ return s.endsWith("vol");
+ }
+ }
+
+ protected static class VolumeSorter implements java.util.Comparator {
+ public int compare(Object o1, Object o2) {
+ return ((File) o1).getName().compareToIgnoreCase(((File) o2).getName());
+ }
+ }
+
+ protected static class DirectoryFilenameFilter implements java.io.FilenameFilter {
+ public boolean accept(File dir, String name) {
+ if (name.equalsIgnoreCase("object")) {
+ return true;
+ }
+
+ if (name.equalsIgnoreCase("words.tok")) {
+ return true;
+ }
+
+ return name.toLowerCase().endsWith("dir");
+ }
+ }
+
+ protected static class DirectorySorter implements java.util.Comparator {
+ public int compare(Object o1, Object o2) {
+ String s1 = ((File) o1).getName();
+ String s2 = ((File) o2).getName();
+
+ if (s1.toLowerCase().endsWith("dir")) {
+ if (!s2.toLowerCase().endsWith("dir")) {
+ return -1;
+ }
+ } else {
+ if (s2.toLowerCase().endsWith("dir")) {
+ return 1;
+ }
+ }
+
+ return s1.compareToIgnoreCase(s2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/sound/Sound.java b/core/src/main/java/com/sierra/agi/sound/Sound.java
new file mode 100644
index 0000000..91f0300
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/sound/Sound.java
@@ -0,0 +1,12 @@
+/*
+ * Sound.java
+ * Adventure Game Interpreter Sound Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.sound;
+
+public interface Sound {
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/sound/SoundProvider.java b/core/src/main/java/com/sierra/agi/sound/SoundProvider.java
new file mode 100644
index 0000000..fe2b0e7
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/sound/SoundProvider.java
@@ -0,0 +1,16 @@
+/*
+ * SoundProvider.java
+ * Adventure Game Interpreter Sound Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.sound;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface SoundProvider {
+ Sound loadSound(InputStream inputStream) throws IOException;
+}
diff --git a/core/src/main/java/com/sierra/agi/view/Cel.java b/core/src/main/java/com/sierra/agi/view/Cel.java
new file mode 100644
index 0000000..7823cdb
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/view/Cel.java
@@ -0,0 +1,152 @@
+/*
+ * Cell.java
+ * Adventure Game Interpreter View Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.view;
+
+import com.sierra.agi.awt.EgaUtils;
+import com.sierra.agi.io.ByteCaster;
+
+import java.awt.*;
+import java.awt.image.ColorModel;
+import java.awt.image.DirectColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.MemoryImageSource;
+
+/**
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class Cel {
+ /**
+ * Cell's Width
+ */
+ protected short width;
+
+ /**
+ * Cell's Height
+ */
+ protected short height;
+
+ /**
+ * Cell's Data
+ */
+ protected int[] data;
+
+ /**
+ * Cell's Transparent Color
+ */
+ protected int transparent;
+
+ /**
+ * Creates new Cell
+ */
+ public Cel(byte[] b, int start, int loopNumber) {
+ width = ByteCaster.lohiUnsignedByte(b, start);
+ height = ByteCaster.lohiUnsignedByte(b, start + 1);
+ short trans = ByteCaster.lohiUnsignedByte(b, start + 2);
+
+ byte transColor = (byte) (trans & 0x0F);
+ short mirrorInfo = (short) ((trans & 0xF0) >> 4);
+
+ loadData(b, start + 3, transColor);
+
+ if ((mirrorInfo & 0x8) != 0) {
+ if ((mirrorInfo & 0x7) != loopNumber) {
+ mirror();
+ }
+ }
+ }
+
+ protected void loadData(byte[] b, int off, byte transColor) {
+ int x;
+
+ IndexColorModel indexModel = EgaUtils.getIndexColorModel();
+ ColorModel nativeModel = EgaUtils.getNativeColorModel();
+
+ int[] pixel = new int[1];
+ data = new int[width * height];
+
+ for (int j = 0, y = 0; y < height; y++) {
+ for (x = 0; b[off] != 0; off++) {
+ int color = (b[off] & 0xF0) >> 4;
+ int count = (b[off] & 0x0F);
+
+ for (int i = 0; i < count; i++, j++, x++) {
+ nativeModel.getDataElements(indexModel.getRGB(color), pixel);
+ data[j] = pixel[0];
+ }
+ }
+
+ nativeModel.getDataElements(indexModel.getRGB(transColor), pixel);
+
+ for (; x < width; j++, x++) {
+ data[j] = pixel[0];
+ }
+
+ off++;
+ }
+
+ nativeModel.getDataElements(indexModel.getRGB(transColor), pixel);
+ transparent = pixel[0];
+ }
+
+ protected void mirror() {
+ for (int y = 0; y < height; y++) {
+ for (int x1 = width - 1, x2 = 0; x1 > x2; x1--, x2++) {
+ int i1 = (y * width) + x1;
+ int i2 = (y * width) + x2;
+
+ int b = data[i1];
+ data[i1] = data[i2];
+ data[i2] = b;
+ }
+ }
+ }
+
+ public short getWidth() {
+ return width;
+ }
+
+ public short getHeight() {
+ return height;
+ }
+
+ public int[] getPixelData() {
+ return data;
+ }
+
+ public int getTransparentPixel() {
+ return transparent;
+ }
+
+ /**
+ * Obtain an standard Image object that is a graphical representation of the
+ * cell.
+ *
+ * @param context Game context used to generate the image.
+ */
+ public Image getImage() {
+ int[] data = this.data.clone();
+ DirectColorModel colorModel = (DirectColorModel) ColorModel.getRGBdefault();
+ DirectColorModel nativeModel = EgaUtils.getNativeColorModel();
+ // int mask = colorModel.getAlphaMask();
+ int[] pixel = new int[1];
+
+ for (int i = 0; i < (width * height); i++) {
+ colorModel.getDataElements(nativeModel.getRGB(data[i]), pixel);
+
+ if (data[i] != transparent) {
+ data[i] = pixel[0];
+ } else {
+ data[i] = 0x00ffffff;
+ }
+ }
+
+ return Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(width, height, colorModel, data, 0, width));
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/view/Loop.java b/core/src/main/java/com/sierra/agi/view/Loop.java
new file mode 100644
index 0000000..0c4153e
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/view/Loop.java
@@ -0,0 +1,51 @@
+/*
+ * Loop.java
+ * Adventure Game Interpreter View Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.view;
+
+import com.sierra.agi.io.ByteCaster;
+
+/**
+ * @author Dr. Z
+ * @version 0.00.00.01
+ */
+public class Loop {
+ /**
+ * Cells
+ */
+ protected Cel[] cels = null;
+
+ /**
+ * Creates new Loop
+ */
+ public Loop(Cel[] cels) {
+ this.cels = cels;
+ }
+
+ public Loop(byte[] b, int start, int loopNumber) {
+ short cellCount;
+ int i, j;
+
+ cellCount = ByteCaster.lohiUnsignedByte(b, start);
+ cels = new Cel[cellCount];
+
+ j = start + 1;
+ for (i = 0; i < cellCount; i++) {
+ cels[i] = new Cel(b, start + ByteCaster.lohiUnsignedShort(b, j), loopNumber);
+ j += 2;
+ }
+ }
+
+ public Cel getCell(int cellNumber) {
+ return cels[cellNumber];
+ }
+
+ public short getCellCount() {
+ return (short) cels.length;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sierra/agi/view/StandardViewProvider.java b/core/src/main/java/com/sierra/agi/view/StandardViewProvider.java
new file mode 100644
index 0000000..73830c3
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/view/StandardViewProvider.java
@@ -0,0 +1,62 @@
+/*
+ * ViewProvider.java
+ * Adventure Game Interpreter View Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.view;
+
+import com.sierra.agi.io.ByteCaster;
+import com.sierra.agi.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+public class StandardViewProvider implements ViewProvider {
+ public StandardViewProvider() {
+ }
+
+ public View loadView(InputStream inputStream, int size) throws IOException, ViewException {
+ byte[] b;
+ int i, j;
+ short loopCount;
+ Loop[] loops = null;
+ String description = null;
+
+ b = new byte[size];
+ IOUtils.fill(inputStream, b, 0, size);
+ inputStream.close();
+
+ loopCount = b[2];
+ if ((b[3] != 0) || (b[4] != 0)) {
+ /* Reads Description */
+ int desc = ByteCaster.lohiUnsignedShort(b, 3);
+
+ i = desc;
+ try {
+ while (true) {
+ if (b[i] == 0) {
+ break;
+ }
+
+ i++;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ description = new String(b, desc, i - desc, StandardCharsets.US_ASCII);
+ }
+
+ j = 5;
+ loops = new Loop[loopCount];
+ for (i = 0; i < loopCount; i++) {
+ loops[i] = new Loop(b, ByteCaster.lohiUnsignedShort(b, j), i);
+ j += 2;
+ }
+
+ return new View(loops, description);
+ }
+}
diff --git a/core/src/main/java/com/sierra/agi/view/View.java b/core/src/main/java/com/sierra/agi/view/View.java
new file mode 100644
index 0000000..e2b8a45
--- /dev/null
+++ b/core/src/main/java/com/sierra/agi/view/View.java
@@ -0,0 +1,214 @@
+/*
+ * View.java
+ * Adventure Game Interpreter View Package
+ *
+ * Created by Dr. Z.
+ * Copyright (c) 2001 Dr. Z. All rights reserved.
+ */
+
+package com.sierra.agi.view;
+
+/**
+ * View object.
+ *
+ * View resources contain some of the graphics for the game. Unlike the picture
+ * resources which are full-screen background images, view resources are smaller
+ * 'sprites' used in the game, such as animations and objects. They are also
+ * stored as bitmaps, whereas pictures are stored in vector format.
+ *
+ * Each view resource consists of one or more 'loops'. Each loop in the resource
+ * consists of one or more 'cells' (frames). Thus several animations can be
+ * stored in one view, or a view can just be used for a single image. The
+ * maximum number of loops supported by the interpreter is 255 (0-254) and the
+ * maximum number of cells in each is 255 (0-254).
+ *
+ * View header (7+ bytes)
+ * Note: ls,ms means that the value is a two-byte word, with the least
+ * significant byte stored first, and the most significant byte stored second,
+ * e.g. 12 07 is acually 712 (hex) or 1810 (decimal). Most word values in AGI
+ * are stored like this, but not all.
+ *
+ *
+ * Byte | Meaning |
+ * 0 | Unknown (always seems to be either 1 or 2) |
+ * 1 | Unknown (always seems to be 1) |
+ * 2 | Number of loops |
+ * 3-4 | Position of description (more on this later) (ls,ms) Both bytes are 0 if there is no description. |
+ * 5-6 | Position of first loop (ls,ms) |
+ * 7-8 | Position of second loop (if any) (ls,ms) |
+ * 9-10 | Position of third loop (if any) (ls,ms) |
+ * .... |
+ *
+ *
+ * Note: Two of these loop references CAN point to the same place. This is done
+ * when you want to use mirroring (more on this later).
+ *
+ *
+ * Loop Header (3+ bytes) |
+ * Byte | Meaning |
+ * 0 | Number of cells in this loop |
+ * 1-2 | Position of first cell, relative to start of loop (ls,ms) |
+ * 3-4 | Position of second cell (if any), relative to start of loop (ls,ms) |
+ * 5-6 | Position of third cell (if any), relative to start of loop (ls,ms) |
+ *
+ *
+ *
+ * Cell Header (3 bytes) |
+ * Byte | Meaning |
+ * 0 | Width of cell (remember that AGI pixels are 2 normal EGA pixels wide so a cel of width 12 is actually 24 pixels wide on screen) |
+ * 1 | Height of cell |
+ * 2 | Transparency and cell mirroring |
+ *
+ *