diff --git a/src/main/java/com/github/manolo8/darkbot/Main.java b/src/main/java/com/github/manolo8/darkbot/Main.java index 8b2e05f47..a31c603eb 100644 --- a/src/main/java/com/github/manolo8/darkbot/Main.java +++ b/src/main/java/com/github/manolo8/darkbot/Main.java @@ -56,7 +56,7 @@ public class Main extends Thread implements PluginListener, BotAPI { - public static final Version VERSION = new Version("1.13.17 beta 105 alpha 1"); + public static final Version VERSION = new Version("1.13.17 beta 106 alpha 1"); public static final Object UPDATE_LOCKER = new Object(); public static final Gson GSON = new GsonBuilder() .setPrettyPrinting() diff --git a/src/main/java/com/github/manolo8/darkbot/core/IDarkBotAPI.java b/src/main/java/com/github/manolo8/darkbot/core/IDarkBotAPI.java index f88cd0eaf..f5eddbe17 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/IDarkBotAPI.java +++ b/src/main/java/com/github/manolo8/darkbot/core/IDarkBotAPI.java @@ -1,12 +1,13 @@ package com.github.manolo8.darkbot.core; import com.github.manolo8.darkbot.core.api.GameAPI; - +import com.github.manolo8.darkbot.core.api.util.DataBuffer; import eu.darkbot.api.game.other.Locatable; import eu.darkbot.api.managers.MemoryAPI; import eu.darkbot.api.managers.OreAPI; import eu.darkbot.api.managers.WindowAPI; +import java.util.function.Consumer; import java.util.function.Predicate; public interface IDarkBotAPI extends WindowAPI, MemoryAPI { @@ -85,6 +86,26 @@ default void readMemory(long address, byte[] buffer) { } void readMemory(long address, byte[] buffer, int length); + /** + * Reads data at given address, use always with try-with-resources + * Length limit is {@link DataBuffer#MAX_CHUNK_SIZE} + * + * @param address to read + * @param length the length of the data to read + * @return {@link DataBuffer} + */ + DataBuffer readData(long address, int length); + + /** + * Length limit is {@link DataBuffer#MAX_CHUNK_SIZE} + * + * @param address to read + * @param length the length of the data to read + * @param reader consumer which will be used if read was success + * @return false if read failed, true otherwise + */ + boolean readData(long address, int length, Consumer reader); + void writeMemoryInt(long address, int value); void writeMemoryLong(long address, long value); void writeMemoryDouble(long address, double value); diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatAdapter.java b/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatAdapter.java index 629d8c20a..1b7910d6b 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatAdapter.java +++ b/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatAdapter.java @@ -1,5 +1,7 @@ package com.github.manolo8.darkbot.core.api; +import com.github.manolo8.darkbot.core.api.util.ByteBufferReader; +import com.github.manolo8.darkbot.core.api.util.DataReader; import com.github.manolo8.darkbot.core.BotInstaller; import com.github.manolo8.darkbot.core.utils.ByteUtils; import com.github.manolo8.darkbot.utils.StartupParams; @@ -46,4 +48,39 @@ public void setMaxFps(int maxFps) { } } + @Override + protected DataReader createReader(int idx) { + if (window.getVersion() >= 9) + return new DarkBoatByteBufferReader(idx, memory, extraMemoryReader); + + return super.createReader(idx); + } + + static class DarkBoatByteBufferReader extends ByteBufferReader implements DataReader { + + private final int idx; + private final DarkBoat darkBoat; + + public DarkBoatByteBufferReader(int idx, DarkBoat darkBoat, GameAPI.ExtraMemoryReader reader) { + super(darkBoat.getBuffer(idx), reader); + this.idx = idx; + this.darkBoat = darkBoat; + } + + @Override + public Result read(long address, int length) { + if (!inUse.compareAndSet(false, true)) return DataReader.Result.BUSY; + + boolean res = darkBoat.readToBuffer(idx, address, length); + if (!res) return Result.ERROR; + + reset(length); + return Result.OK; + } + + @Override + public byte[] toArray() { + return getArray(new byte[getAvailable()], 0, getAvailable()); + } + } } \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatHookAdapter.java b/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatHookAdapter.java index c251a76ac..9fbc08e7d 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatHookAdapter.java +++ b/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatHookAdapter.java @@ -1,5 +1,6 @@ package com.github.manolo8.darkbot.core.api; +import com.github.manolo8.darkbot.core.api.util.DataReader; import com.github.manolo8.darkbot.core.BotInstaller; import com.github.manolo8.darkbot.core.manager.HookAdapter; import com.github.manolo8.darkbot.core.utils.ByteUtils; @@ -52,4 +53,11 @@ public boolean hasCapability(GameAPI.Capability capability) { return super.hasCapability(capability); } + @Override + protected DataReader createReader(int idx) { + if (window.getVersion() >= 9) + return new DarkBoatAdapter.DarkBoatByteBufferReader(idx, memory, extraMemoryReader); + + return super.createReader(idx); + } } \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/GameAPIImpl.java b/src/main/java/com/github/manolo8/darkbot/core/api/GameAPIImpl.java index 19c33019f..fe42b7480 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/api/GameAPIImpl.java +++ b/src/main/java/com/github/manolo8/darkbot/core/api/GameAPIImpl.java @@ -1,6 +1,9 @@ package com.github.manolo8.darkbot.core.api; import com.github.manolo8.darkbot.core.IDarkBotAPI; +import com.github.manolo8.darkbot.core.api.util.DataBuffer; +import com.github.manolo8.darkbot.core.api.util.DataReader; +import com.github.manolo8.darkbot.core.api.util.DefaultByteBufferReader; import com.github.manolo8.darkbot.core.manager.HeroManager; import com.github.manolo8.darkbot.gui.utils.PidSelector; import com.github.manolo8.darkbot.gui.utils.Popups; @@ -42,7 +45,8 @@ public class GameAPIImpl< protected final String version; - private final Consumer fpsLimitListener; // Needs to be kept as a strong reference to avoid GC + @SuppressWarnings("FieldCanBeLocal") // Needs to be kept as a strong reference to avoid GC + private final Consumer fpsLimitListener; protected final LoginData loginData; // Used only if api supports LOGIN protected int pid; // Used only if api supports ATTACH @@ -51,6 +55,8 @@ public class GameAPIImpl< protected long lastFailedLogin; + protected DataReader[] dataReaders = new DataReader[10]; + public GameAPIImpl(StartupParams params, W window, H handler, M memory, E extraMemoryReader, I interaction, D direct, GameAPI.Capability... capabilityArr) { @@ -279,6 +285,41 @@ public void readMemory(long address, byte[] buffer, int length) { memory.readBytes(address, buffer, length); } + @Override + public DataBuffer readData(long address, int length) throws RuntimeException { + if (length <= 0 || length > DataBuffer.MAX_CHUNK_SIZE) + throw new ArrayIndexOutOfBoundsException("Length is <= 0 or exceeds max chunk size: " + DataBuffer.MAX_CHUNK_SIZE); + + for (int i = 0; i < dataReaders.length; i++) { + DataReader reader = dataReaders[i]; + if (reader == null) reader = (dataReaders[i] = createReader(i)); + + switch (reader.read(address, length)) { + case BUSY: continue; + case ERROR: + reader.reset(0); + case OK: + return reader; + } + } + + throw new RuntimeException("All DataReaders are in use. Some code is calling readData and not closing the resource!"); + } + + @Override + public boolean readData(long address, int length, Consumer reader) { + try (DataBuffer r = readData(address, length)) { + if (r.getLimit() != length) return false; + + reader.accept(r); + } + return true; + } + + protected DataReader createReader(int idx) { + return DefaultByteBufferReader.of(memory, extraMemoryReader); + } + @Override public void replaceInt(long address, int oldValue, int newValue) { memory.replaceInt(address, oldValue, newValue); diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/util/ByteBufferReader.java b/src/main/java/com/github/manolo8/darkbot/core/api/util/ByteBufferReader.java new file mode 100644 index 000000000..f15a21536 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/api/util/ByteBufferReader.java @@ -0,0 +1,149 @@ +package com.github.manolo8.darkbot.core.api.util; + +import com.github.manolo8.darkbot.core.api.GameAPI; +import com.github.manolo8.darkbot.core.utils.ByteUtils; + +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Generic DataReader implementation backed by a byte buffer. + * May be extended to provide reading in different ways. + */ +public class ByteBufferReader implements DataBuffer { + + protected final ByteBuffer buffer; + protected final GameAPI.ExtraMemoryReader reader; + + protected final AtomicBoolean inUse = new AtomicBoolean(); + + public ByteBufferReader(ByteBuffer buffer, GameAPI.ExtraMemoryReader reader) { + this.buffer = buffer; + this.reader = reader; + } + + public ByteBuffer getByteBuffer() { + return buffer; + } + + public void reset(int limit) { + buffer.clear(); + buffer.limit(limit); + } + + @Override + public int getPosition() { + return buffer.position(); + } + + @Override + public void setPosition(int pos) { + buffer.position(pos); + } + + @Override + public int getLimit() { + return buffer.limit(); + } + + @Override + public int getAvailable() { + return buffer.remaining(); + } + + @Override + public byte getByte() { + return buffer.get(); + } + + @Override + public byte getByte(int idx) { + return buffer.get(idx); + } + + @Override + public boolean getBoolean() { + return getInt() == 1; + } + + @Override + public boolean getBoolean(int idx) { + return getByte(idx) == 1; + } + + @Override + public short getShort() { + return buffer.getShort(); + } + + @Override + public short getShort(int idx) { + return buffer.getShort(idx); + } + + @Override + public int getInt() { + return buffer.getInt(); + } + + @Override + public int getInt(int idx) { + return buffer.getInt(idx); + } + + @Override + public long getLong() { + return buffer.getLong(); + } + + @Override + public long getLong(int idx) { + return buffer.getLong(idx); + } + + @Override + public long getPointer() { + return buffer.getLong() & ByteUtils.ATOM_MASK; + } + + @Override + public long getPointer(int idx) { + return buffer.getLong(idx) & ByteUtils.ATOM_MASK; + } + + @Override + public double getDouble() { + return buffer.getDouble(); + } + + @Override + public double getDouble(int idx) { + return buffer.getDouble(idx); + } + + @Override + public String getString() { + return reader.readString(getLong()); + } + + @Override + public String getString(int idx) { + return reader.readString(getLong(idx)); + } + + @Override + public byte[] toArray() { + return buffer.array(); + } + + @Override + public byte[] getArray(byte[] dst, int offset, int length) { + buffer.get(dst, offset, length); + return dst; + } + + @Override + public void close() { + inUse.set(false); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/util/DataBuffer.java b/src/main/java/com/github/manolo8/darkbot/core/api/util/DataBuffer.java new file mode 100644 index 000000000..e80d84983 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/api/util/DataBuffer.java @@ -0,0 +1,63 @@ +package com.github.manolo8.darkbot.core.api.util; + +public interface DataBuffer extends AutoCloseable { + + int MAX_CHUNK_SIZE = 2048; + + int getPosition(); + void setPosition(int pos); + + int getLimit(); + int getAvailable(); + + byte getByte(); + byte getByte(int idx); + + boolean getBoolean(); + boolean getBoolean(int idx); + + short getShort(); + short getShort(int idx); + + int getInt(); + int getInt(int idx); + + long getLong(); + long getLong(int idx); + + /** + * @return readLong() & ATOM_MASK + */ + long getPointer(); + long getPointer(int idx); + + double getDouble(); + double getDouble(int idx); + + String getString(); + String getString(int idx); + + /** + * This method allocates a new array and copies the bytes from this buffer into it. + * Using the method is not advised as it generates new allocations on each call. + * Prefer using {@link #getArray(byte[], int, int)} instead. + * + * @return a new array containing the bytes from this buffer. + */ + byte[] toArray(); + + /** + * This method transfers bytes from this buffer into the given destination array. + * + * @param dst The array into which bytes are to be written. + * @param offset The offset within the array of the first byte to be written; + * must be non-negative and no larger than {@code dst.length} + * @param length The maximum number of bytes to be written to the given array; must be non-negative + * and no larger than {@code dst.length - offset} + * @return The dst param, useful for chaining + */ + byte[] getArray(byte[] dst, int offset, int length); + + @Override + void close(); +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/util/DataReader.java b/src/main/java/com/github/manolo8/darkbot/core/api/util/DataReader.java new file mode 100644 index 000000000..19c9fa0a9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/api/util/DataReader.java @@ -0,0 +1,23 @@ +package com.github.manolo8.darkbot.core.api.util; + +public interface DataReader extends DataBuffer { + + enum Result { + OK, ERROR, BUSY + } + + /** + * Read data from in-game to the data buffer. + * Once read is called, this buffer becomes {@link Result#BUSY}, + * and any further attempts to this function will return {@link Result#BUSY}, + * until {@link #close()} is called. + * + * @param address The address to read from. + * @param length How many bytes to read. + * @return The result of the operation. + */ + Result read(long address, int length); + + void reset(int limit); + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/util/DefaultByteBufferReader.java b/src/main/java/com/github/manolo8/darkbot/core/api/util/DefaultByteBufferReader.java new file mode 100644 index 000000000..fd28c5fae --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/api/util/DefaultByteBufferReader.java @@ -0,0 +1,32 @@ +package com.github.manolo8.darkbot.core.api.util; + +import com.github.manolo8.darkbot.core.api.GameAPI; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class DefaultByteBufferReader extends ByteBufferReader implements DataReader { + + private final GameAPI.Memory memory; + private final byte[] buffer; + + private DefaultByteBufferReader(byte[] buffer, GameAPI.Memory memory, GameAPI.ExtraMemoryReader reader) { + super(ByteBuffer.wrap(buffer).order(ByteOrder.nativeOrder()), reader); + this.buffer = buffer; + this.memory = memory; + } + + public static DefaultByteBufferReader of(GameAPI.Memory memory, GameAPI.ExtraMemoryReader reader) { + return new DefaultByteBufferReader(new byte[DataBuffer.MAX_CHUNK_SIZE], memory, reader); + } + + @Override + public DataReader.Result read(long address, int length) { + if (!inUse.compareAndSet(false, true)) return DataReader.Result.BUSY; + + memory.readBytes(address, buffer, length); + reset(length); + + return Result.OK; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/util/DefaultDataReader.java b/src/main/java/com/github/manolo8/darkbot/core/api/util/DefaultDataReader.java new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/facades/ChatProxy.java b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/ChatProxy.java index 2e7838c2d..316ba7cd2 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/facades/ChatProxy.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/ChatProxy.java @@ -108,15 +108,20 @@ public String getUserId() { } } + @SuppressWarnings("resource") @EventHandler public void onChatMessage(MessageSentEvent event) { if (!ConfigEntity.INSTANCE.getConfig().MISCELLANEOUS.LOG_CHAT) return; - try (OutputStream os = fileWriters.computeIfAbsent(event.getRoom(), LogUtils::createLogFile)) { - if (os != null) os.write(event.getMessage().formatted().getBytes(StandardCharsets.UTF_8)); - } catch (IOException e) { - e.printStackTrace(); + OutputStream os = fileWriters.computeIfAbsent(event.getRoom(), LogUtils::createLogFile); + if (os != null) { + try { + os.write(event.getMessage().formatted().getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + e.printStackTrace(); + } } + } } \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/Item.java b/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/Item.java index 6859cdbd5..d21788c26 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/Item.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/Item.java @@ -1,5 +1,6 @@ package com.github.manolo8.darkbot.core.objects.slotbars; +import com.github.manolo8.darkbot.core.api.util.DataBuffer; import com.github.manolo8.darkbot.core.itf.Updatable; import com.github.manolo8.darkbot.core.objects.facades.SlotBarsProxy; import com.github.manolo8.darkbot.core.utils.ByteUtils; @@ -16,8 +17,7 @@ import static com.github.manolo8.darkbot.Main.API; public class Item extends Updatable.Auto implements eu.darkbot.api.game.items.Item { - private static final int START = 36, END = 128 + 8; - private static final byte[] BUFFER = new byte[END - START]; + private static final int START = 36, END = 136 - START; public final ItemTimer itemTimer = new ItemTimer(); private final Map> associatedSlots = new EnumMap<>(SlotBarsProxy.Type.class); @@ -57,16 +57,22 @@ public void update() { // Doing 5 boolean-read calls is way more expensive than a single mem-read to the buffer // This IS very ugly, but improves performance. // We also avoid updating timer if no other flags change for the extra 3 long-read calls - API.readMemory(address + START, BUFFER); - buyable = BUFFER[0] == 1; - activatable = BUFFER[4] == 1; - selected = BUFFER[8] == 1; - available = BUFFER[12] == 1; - visible = BUFFER[16] == 1; - quantity = ByteUtils.getDouble(BUFFER, 92); + long timerAdr; + try (DataBuffer reader = API.readData(address + START, END)){ + if (reader.getAvailable() == 0) return; //failed to read data - long timerAdr = API.readMemoryLong(ByteUtils.getLong(BUFFER, 52), 40); + buyable = reader.getBoolean(0); + activatable = reader.getBoolean(4); + selected = reader.getBoolean(8); + available = reader.getBoolean(12); + visible = reader.getBoolean(16); + + quantity = reader.getDouble(92); + timerAdr = reader.getLong(52); + } + + timerAdr = API.readMemoryLong(timerAdr, 40); if (itemTimer.address != timerAdr) this.itemTimer.update(timerAdr); this.itemTimer.update(); } diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/AtomKind.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/AtomKind.java new file mode 100644 index 000000000..f444d9946 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/AtomKind.java @@ -0,0 +1,70 @@ +package com.github.manolo8.darkbot.core.objects.swf; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.utils.ByteUtils; + +public enum AtomKind { + UNUSED(null), + OBJECT(Long.class), + STRING(String.class) { + @Override + public String read(long atom) { + return Main.API.readString((Long) super.read(atom)); + } + }, + NAMESPACE(null), // ? + SPECIAL(Float.class),// prob not supported + BOOLEAN(Boolean.class) { + @Override + public Boolean read(long atom) { + return atom == 0x0D; // 0x0D == atom true, 0x05 == atom false; + } + }, + INTEGER(Integer.class) { + @Override + public Integer read(long atom) { + return (int) ((atom & ByteUtils.ATOM_MASK) >> 3); + } + }, + DOUBLE(Double.class) { + @Override + public Double read(long atom) { + return Double.longBitsToDouble(atom & ByteUtils.ATOM_MASK); + } + }; + + private final Class javaType; + + AtomKind(Class javaType) { + this.javaType = javaType; + } + + public static AtomKind of(long atom) { + int kind = (int) (atom & ByteUtils.ATOM_KIND); + if (kind >= values().length) return UNUSED; + + return values()[kind]; + } + + public static AtomKind of(Class type) { + //if (Updatable.class.isAssignableFrom(type)) return OBJECT; + for (AtomKind kind : values()) { + if (kind.javaType == type) + return kind; + } + return null; + } + + public static boolean isNullAtom(long atom) { + return atom < 4; + } + + public static boolean isString(long atom) { + return AtomKind.of(atom) == STRING; + } + + public Object read(long atom) { + return atom & ByteUtils.ATOM_MASK; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/FlashMap.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/FlashMap.java new file mode 100644 index 000000000..1de68be5e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/FlashMap.java @@ -0,0 +1,173 @@ +package com.github.manolo8.darkbot.core.objects.swf; + +import com.github.manolo8.darkbot.core.api.util.DataBuffer; +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.utils.ByteUtils; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Map; + +import static com.github.manolo8.darkbot.Main.API; + +// tableOffset = API.readInt(address, 0x10, 0x28, 236); +// isDictionary = (API.readInt(address, 0x10, 0x28, 248) & (1 << 4)) != 0; // need to read heap hashtable +@Deprecated // TODO unfished, dont use it +public class FlashMap extends SwfPtrCollection { + + private final AtomKind keyKind; + private final AtomKind valueKind; + + private final boolean keyUpdatable, valueUpdatable; + private final Class keyClazz; + private final Class valueClazz; + + private int size; + private boolean autoUpdatable; + + @SuppressWarnings("unchecked") + private Entry[] entries = (Entry[]) Array.newInstance(Entry.class, 0); + + private FlashMap(Class key, Class value) { + this.keyClazz = key; + this.valueClazz = value; + + this.keyUpdatable = Updatable.class.isAssignableFrom(keyClazz); + this.valueUpdatable = Updatable.class.isAssignableFrom(valueClazz); + + this.keyKind = AtomKind.of(keyClazz); + this.valueKind = AtomKind.of(valueClazz); + + if (keyKind == null || keyKind == AtomKind.UNUSED || valueKind == null || valueKind == AtomKind.UNUSED) + throw new IllegalArgumentException("Provided types are not supported"); + } + + public static FlashMap of(Class key, Class value) { + return new FlashMap(key, value); + } + + public FlashMap setAutoUpdatable(boolean updatable) { + this.autoUpdatable = updatable; + return this; + } + + @Override + public void update() { + if (address == 0) return; + + // heap hashtable + boolean isDictionary = (API.readInt(address, 0x10, 0x28, 248) & (1 << 4)) != 0; + int tableOffset = API.readInt(address, 0x10, 0x28, 236); + + long table = address + tableOffset; + if (isDictionary) table = API.readLong(table) + 8; + + if (table == 0) return; + + long atoms = API.readLong(table); + long sizeAndExp = API.readLong(table + 8); + + //boolean hasIterIndex = (atoms & 0x04) != 0; //unused here + + int size = (int) sizeAndExp; + int exp = (int) (sizeAndExp >> 32); + + int capacity = (1 << (exp - 1)) * Long.BYTES; + + if (size <= 0 || size > 1024 || capacity <= 0 || capacity > 1024 * 2 * 8) return; + if (entries.length < size) entries = Arrays.copyOf(entries, size); + + this.size = size; + + atoms = (atoms & ByteUtils.ATOM_MASK) + 8; //remove tags & skip c++ vtable + + for (int offset = 0, idx = 0; offset < capacity && idx < size; offset += DataBuffer.MAX_CHUNK_SIZE) { + try (DataBuffer r = API.readData(atoms + offset, + Math.min(DataBuffer.MAX_CHUNK_SIZE, capacity - offset))) { + + while (r.getAvailable() >= 16 && idx < size) { + long key = r.getLong(); + if (AtomKind.isNullAtom(key)) continue; // key cannot be null + + AtomKind keyKind = AtomKind.of(key); + if (keyKind != this.keyKind) continue; + + Entry entry = entries[idx]; + if (entry == null) entries[idx] = entry = new Entry(); + + entry.set(key, r.getLong()); + idx++; + } + } + } + } + + @Override + public void update(long address) { + super.update(address); + if (autoUpdatable) update(); + } + + @Override + public int getSize() { + return Math.min(size, entries.length); + } + + @Override + public long getPtr(int i) { + if (i < getSize()) { + Entry entry = entries[i]; + if (entry != null && entry.value instanceof Long) + return (Long) entry.value; + } + return 0; + } + + + public class Entry implements Map.Entry { + private K key; + private V value; + + private Entry() { + try { + if (keyUpdatable) + key = keyClazz.newInstance(); + if (valueUpdatable) + value = valueClazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + return null; + } + + @SuppressWarnings("unchecked") + private void set(long keyAtom, long valueAtom) { + if (keyUpdatable) { + ((Updatable) key).update(keyAtom & ByteUtils.ATOM_MASK); + if (autoUpdatable) ((Updatable) key).update(); + } else key = (K) keyKind.read(keyAtom); + + if (valueUpdatable) { + ((Updatable) value).update(valueAtom & ByteUtils.ATOM_MASK); + if (autoUpdatable) ((Updatable) value).update(); + } else value = (V) valueKind.read(valueAtom); + } + + @Override + public String toString() { + return "Entry{key=" + key + ", value=" + value + '}'; + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/ObjArray.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/ObjArray.java index 5deca975c..c62a35851 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/ObjArray.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/ObjArray.java @@ -1,6 +1,6 @@ package com.github.manolo8.darkbot.core.objects.swf; -import com.github.manolo8.darkbot.core.utils.ByteUtils; +import com.github.manolo8.darkbot.core.api.util.DataBuffer; import static com.github.manolo8.darkbot.Main.API; @@ -90,11 +90,15 @@ public void update() { if (elements.length < size) elements = new long[Math.min((int) (size * 1.25), 8192)]; long table = API.readMemoryLong(address + tableOffset) + bytesOffset; - byte[] bytes = API.readMemory(table, size * 8); + int tableSize = size * 8; - for (int i = 0, offset = 0; offset < bytes.length && i < size; offset += 8) { - long value = ByteUtils.getLong(bytes, offset); - elements[i++] = value & ByteUtils.ATOM_MASK; //not sure if we should skip 0 values + for (int i = 0, idx = 0; i < tableSize && idx < size; i += DataBuffer.MAX_CHUNK_SIZE) { + try (DataBuffer reader = API.readData(table + i, Math.min(DataBuffer.MAX_CHUNK_SIZE, tableSize - i))) { + + while (reader.getAvailable() >= 8 && idx < size) { + elements[idx++] = reader.getPointer(); + } + } } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/PairArray.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/PairArray.java index 3b0f61891..13f18a4d1 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/PairArray.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/PairArray.java @@ -20,7 +20,7 @@ public abstract class PairArray extends SwfPtrCollection { public int size; private Pair[] pairs = new Pair[0]; - private Map> lazy = new HashMap<>(); + private final Map> lazy = new HashMap<>(); protected PairArray() {} @@ -28,14 +28,14 @@ protected PairArray() {} * Reads pairs of {@code Array} type */ public static PairArray ofArray() { - return new PairArray.Pairs(); + return new Pairs(); } /** * Reads pairs of {@code Dictionary} type */ public static PairArray ofDictionary() { - return new PairArray.Dictionary(); + return new Dictionary(); } public PairArray setAutoUpdatable(boolean updatable) { @@ -105,8 +105,9 @@ public boolean isInvalid(long addr) { } public String getKey(long addr) { - if (isInvalid(addr)) return null; - String key = API.readMemoryStringFallback(addr, null); + if (AtomKind.isNullAtom(addr) || !AtomKind.isString(addr)) return null; //keys shouldn't be null + + String key = API.readMemoryStringFallback(addr & ByteUtils.ATOM_MASK, null); return key == null || key.isEmpty() ? null : key; } @@ -138,7 +139,7 @@ public void update() { String key = null; offset = 8; for (int i = 0; offset < 8192 && i < size; offset += 8) { - if (key == null && (key = getKey(readLong(table, offset, expected) & ByteUtils.ATOM_MASK)) == null) continue; + if (key == null && (key = getKey(readLong(table, offset, expected))) == null) continue; long value = readLong(table, offset += 8, expected); if (isInvalid(value)) continue; @@ -178,15 +179,15 @@ public void update() { int current = 0, remove = 0; for (int offset = 0; offset < length && current < size; offset += 16) { - long keyAddr = ByteUtils.getLong(bytes, offset) - 2, valAddr = ByteUtils.getLong(bytes, offset + 8) - 1; + long keyAddr = ByteUtils.getLong(bytes, offset), valAddr = ByteUtils.getLong(bytes, offset + 8) - 1; - if (keyAddr == -2 || (valAddr >= -2 && valAddr <= 9)) continue; + if (keyAddr == 0 || (valAddr >= -2 && valAddr <= 9)) continue; Pair pair = super.pairs[current]; - if (pair == null) super.pairs[current] = new Pair(API.readMemoryString(keyAddr), valAddr); + if (pair == null) super.pairs[current] = new Pair(readString(keyAddr), valAddr); else if (pair.value != valAddr) { removed[remove++] = pair.key; - super.pairs[current].set(API.readMemoryString(keyAddr), valAddr); + super.pairs[current].set(readString(keyAddr), valAddr); } current++; } @@ -204,6 +205,10 @@ else if (pair.value != valAddr) { } } + private String readString(long atom) { + return AtomKind.isString(atom) ? API.readString(atom & ByteUtils.ATOM_MASK) : ""; + } + private long align8(long value) { long aligned = value + 8 - (value & 0b1111); return aligned <= value ? aligned + 8 : aligned; diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/ByteUtils.java b/src/main/java/com/github/manolo8/darkbot/core/utils/ByteUtils.java index b753053de..185d09ea6 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/ByteUtils.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/ByteUtils.java @@ -10,6 +10,8 @@ public class ByteUtils { + public static final int ATOM_KIND = 0b111; + /** * The AtomConstants namespace defines constants for * manipulating atoms. @@ -47,9 +49,7 @@ public class ByteUtils { * * A mask that will remove atom constant bits: */ - public static final long ATOM_MASK = ~0b111L; - @Deprecated // Use ATOM_MASK instead. - public static final long FIX = ~0b111L; + public static final int ATOM_MASK = ~ATOM_KIND; /** * Constant value which means that reference to the object, diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ExtraButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ExtraButton.java index 30765bb29..5c6770c23 100644 --- a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ExtraButton.java +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ExtraButton.java @@ -153,7 +153,7 @@ public Collection getExtraMenuItems(PluginAPI api) { })); ConfigSetting root = config.getConfigRoot(); if (root.getValue().BOT_SETTINGS.OTHER.DEV_STUFF) { - list.add(createSeparator("Dev stuff")); + list.add(createSeparator("Dev stuff")); // somehow check if api can do async reads. list.add(create("Save SWF", e -> main.addTask(SWFUtils::dumpMainSWF))); } diff --git a/src/main/java/com/github/manolo8/darkbot/utils/debug/SWFUtils.java b/src/main/java/com/github/manolo8/darkbot/utils/debug/SWFUtils.java index 4e76dc7af..891fff871 100644 --- a/src/main/java/com/github/manolo8/darkbot/utils/debug/SWFUtils.java +++ b/src/main/java/com/github/manolo8/darkbot/utils/debug/SWFUtils.java @@ -1,5 +1,8 @@ package com.github.manolo8.darkbot.utils.debug; +import com.github.manolo8.darkbot.core.api.util.ByteBufferReader; +import com.github.manolo8.darkbot.core.api.util.DataBuffer; + import java.io.FileOutputStream; import java.io.IOException; @@ -16,7 +19,13 @@ public static void dumpMainSWF() { if (size < 11_500_000 || size > 13_000_000) continue; try (FileOutputStream writer = new FileOutputStream("main.swf")) { - writer.write(API.readMemory(addr, size)); + for (int i = 0; i < size; i += DataBuffer.MAX_CHUNK_SIZE) { + try (DataBuffer data = API.readData(addr + i, Math.min(DataBuffer.MAX_CHUNK_SIZE, size - i))) { + if (!(data instanceof ByteBufferReader)) + throw new UnsupportedOperationException("Cannot dump main SWF with this data reader"); + writer.getChannel().write(((ByteBufferReader) data).getByteBuffer(), i); + } + } } catch (IOException e) { e.printStackTrace(); } @@ -24,5 +33,4 @@ public static void dumpMainSWF() { } System.out.println("SWF not found, are you running the flash client?"); } - } diff --git a/src/main/java/eu/darkbot/api/DarkBoat.java b/src/main/java/eu/darkbot/api/DarkBoat.java index a42fd6240..da3c760ed 100644 --- a/src/main/java/eu/darkbot/api/DarkBoat.java +++ b/src/main/java/eu/darkbot/api/DarkBoat.java @@ -1,8 +1,13 @@ package eu.darkbot.api; import com.github.manolo8.darkbot.core.api.GameAPI; +import com.github.manolo8.darkbot.core.api.util.DataBuffer; import com.github.manolo8.darkbot.utils.LibUtils; +import java.lang.annotation.Native; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + public class DarkBoat implements GameAPI.Window, GameAPI.Handler, GameAPI.Memory, GameAPI.Interaction, API.Singleton { static { @@ -50,4 +55,21 @@ public class DarkBoat implements GameAPI.Window, GameAPI.Handler, GameAPI.Memory public native long[] queryInt (int value , int maxSize); public native long[] queryLong (long value , int maxSize); public native long[] queryBytes (byte[] pattern, int maxSize); + + // DarkBoat v9+ + public final @Native ByteBuffer[] buffers = new ByteBuffer[10]; + public ByteBuffer getBuffer(int idx) { + ByteBuffer buffer = buffers[idx]; + if (buffer != null) return buffer; + return buffers[idx] = ByteBuffer.allocateDirect(DataBuffer.MAX_CHUNK_SIZE).order(ByteOrder.nativeOrder()); + } + + // writes data at given address to direct buffer + public native boolean readToBuffer(int bufferIdx, long address, int length); + + // writes string data to direct buffer + public native int readStringBuffer(int bufferIdx, long address); + + // same as above but reads the pointer before reading the string + public native int readStringBufferPtr(int bufferIdx, long address); } \ No newline at end of file