Cosmos - Runescape emulator


#1

Repository
Wiki
Issues

Plan

The plan currently is to completely research, document, and implement the foundation for a build 530 server. Cosmos utilizes Scapeemulator as the basis for the framework currently, plans to shift that around are in progress after functionality is implemented. The issues section has plenty of planned features and status updates on what has been done.


#2

Good luck with your new project! Hopefully this one may get to the stage of being host able for the community??


#3

That’d be nice :slight_smile:

Perhaps.


#4

haha are you new here?


#5

I wish you’d just disappear


#6

Lighten up nerd, pretty much everyone on these forums can’t finish a project what makes you think you are unique

Thornefall
Official Moparscape serve
Tekk - Runetek 4 Engine Rewrite
Nyphrex - 530 Emulation Project
RuneScript Compiler
Evelus | Red
Mint
RuneEmu
ScapeEmu
FrontierFS
Evelus
Omega
Evelus | Gamma Multi-Revision Server
Evelus | Zeus - Model/Animation Viewer
Fusion Micro - Quick/Speedy Cache Editor
RuneForge - Cache Writer/Reader

Any of these projects still ongoing?


#7

You forgot Hybridscape. We should bring it back from the grave.


#8

Or just help me so that we can have our own server if we wanted. Considering the amount of research I’ve done, most of this is pretty trivial now.


#9

As tempting as that is it always boils down to the same result, Content is work a lot of it the amount of time is not really worth it when you consider that if its anything decent Jagex is gonna come after it. I would much rather see the mitb community do something like thornefall


#10

I don’t think nearly anyone in the community is as educated in the sector of how game engines work as much as Taharok is to get that done in a decent timeframe. Plus that project is still under development as soon as he finishes a subproject I’ll be unblocked. (Similar to: Protobuf).

I don’t see anything wrong with creating a decent server. Given, yes, it could be shut down. But it’s still an interesting investment. And it’s always nice just to say we did it.


#11

But you aren’t even changing anything. It’s the same dreary design that’s been used for the last five years, as if that’s the only possible way to write a server.
Even if my challenge on Moparscape is ‘optimisation’, at least it’d lead to something fresh.


#12

It’s not but it’s the simplest model to just add in to get the other core pieces first. Which no one really understands a lot of internal client mechanics hence why every issue in the repository is pretty much research+implementation. Pretty much it currently stands as a test platform, and most of the commands that I use to do certain functions are actually client sided.

Did you know you can actually animate individual models of a player/npc. And in models, there are flags which are used in coordination with these animations to act on certain vertices. Did you know there are animation priorities? Did you know there are 3 movement speeds? Run, walk, crawl. Did you know animations can actually block walking, or specify if an animate is playing how the behavior works for that?

All of this is fairly undocumented. Your server doesn’t mean anything if you don’t know this information to try and emulate things in a manner that’s effective.

That coupled with the fact there aren’t many CS2 (Client Script V2) decompilers which the scripts pretty much define how the game is executed and ran in the first place make it kind of hard to just implement content freely.

So trying to say that you need to come up with a new design doesn’t matter when first you need to try and paint a picture of what you’re dealing with. You can engineer to network pipeline and everything else after the fact, and just use what works for now. It doesn’t have to be perfect. It has to fit your specifications.


#13

https://github.com/Hadyn/cosmos/issues/2
https://github.com/Hadyn/cosmos/wiki/Server-to-client-packets

Finished identifying all the packets.


#14

Any progress?

Apparently I need more text.


#15

I have a lot of my client refactored. Pretty much everything BUT the audio classes. I have no knowledge of how the encoding works. Ill commit that soon. Just chugging along on issues

I found out a lot of interesting animation information

There’s actually flags for if the animation should execute while walking, or halt walking (or paths). Also the ‘traverse path’ update block is quite interesting. I’m working on a script decompiler so it’s kinda blocking my other stuff.


#16

I don’t have it up yet because its a WIP. You can print out a stream of tokens from an AST though which is cool.


#17

I’ve done some research on interfaces in Runescape. Might be of interest to some people. I omitted some fields for now, but the jist of everything is here (the modern format at least).

Also in the most recent commit I changed a lot of message encoders/message names to reflect my naming scheme in the wiki. Also started normalizing packet streams.

package net.scapeemulator.cache.ui;

import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import static net.scapeemulator.cache.util.ByteBufferUtils.getString;
import static net.scapeemulator.cache.util.ByteBufferUtils.getTriByte;

/**
 * An interface component.
 *
 * @author hadyn
 */
public class InterfaceComponent {

  public static final int TYPE_CONTAINER = 0;

  public static final int SIZE_FIXED = 0;
  public static final int SIZE_INSET = 1;
  public static final int SIZE_PERCENT = 2;

  /**
   * The identifier.
   */
  private int id;

  /**
   * The type of the component.
   */
  private int type;

  /**
   * The render type. The render types determines special handling for when the component is
   * rendered such as what is rendered on the component (world map, scene, etc).
   */
  private int renderType;

  /**
   * The x position. This value's meaning is determined by the horizontal alignment.
   */
  private int x;

  /**
   * The y position. This value's meaning is determined by the vertical alignment.
   */
  private int y;

  /**
   * The width of the interface. This value's meaning is determined by the width type.
   */
  private int width;

  /**
   * The height of the interface. This value's meaning is determined by the height type.
   */
  private int height;

  /**
   * The width type. Determines the width of the interface relative to the parent.
   */
  private int widthType;

  /**
   * The height type. Determines the height of the interface relative to the parent.
   */
  private int heightType;

  /**
   * The horizontal alignment value. Determines where the interface is horizontally (x axis) aligned
   * on the screen relative to the parent.
   */
  private int horizontalAlign;

  /**
   * The vertical alignment value. Determines where the interface is vertically (y axis) aligned on
   * the screen relative to the parent.
   */
  private int verticalAlign;

  /**
   * The parent component identifier.
   */
  private int parentId;

  /**
   * Flag for if the component is currently hidden from view.
   */
  private boolean hidden;

  /**
   * Flag for if the component utilizes the legacy format.
   */
  private boolean legacy;

  private String text;
  private int spriteId = -1;

  /**
   * This script is called when the interface is opened.
   */
  private ScriptArguments initializeScript;

  private ScriptArguments mouseEnteredScript;
  private ScriptArguments mouseExitedScript;
  private ScriptArguments varpScript;

  /**
   * This script is called when an inventory is updated.
   */
  private ScriptArguments inventoryScript;

  /**
   * This script is called when a skill is updated.
   */
  private ScriptArguments skillScript;

  private ScriptArguments updateScript;
  private ScriptArguments menuScript;
  private ScriptArguments mouseHover;
  private ScriptArguments mousePressedScript;
  private ScriptArguments mouseHeldScript;
  private ScriptArguments mouseReleasedScript;
  private ScriptArguments mouseDraggedScript;
  private ScriptArguments mouseWheelScript;

  /**
   * This script is called when a global script integer is updated.
   */
  private ScriptArguments globalIntegerScript;

  /**
   * This script is called when a global script string is updated.
   */
  private ScriptArguments globalStringScript;

  private Set<Integer> playerVariableIds;
  private Set<Integer> inventoryIds;
  private Set<Integer> skillIds;
  private Set<Integer> globalIntegerIds;
  private Set<Integer> globalStringIds;


  public InterfaceComponent(int id) {
    this.id = id;
  }

  public void parse(ByteBuffer buffer) {
    if (buffer.get(0) == -1) {
      parseModernFormat(buffer);
    } else {
      parseLegacyFormat(buffer);
    }
  }

  private void parseModernFormat(ByteBuffer buffer) {
    buffer.get();

    type = buffer.get() & 0xff;
    if ((type & 0x80) != 0) {
      getString(buffer);
      type = type & 0x7f;
    }

    renderType = buffer.getShort();

    x = buffer.getShort();
    y = buffer.getShort();

    width = buffer.getShort() & 0xffff;
    height = buffer.getShort() & 0xffff;

    widthType = buffer.get();
    heightType = buffer.get();

    horizontalAlign = buffer.get();
    verticalAlign = buffer.get();

    parentId = buffer.getShort();
    if (parentId == 65535) {
      parentId = -1;
    }

    hidden = buffer.get() == 1;

    if (type == 0) {
      buffer.getShort();
      buffer.getShort();
      buffer.get();
    }

    if (type == 3) {
      buffer.getInt();
      buffer.get();
      buffer.get();
    }

    if (type == 4) {
      buffer.getShort();
      text = getString(buffer);
      buffer.get();
      buffer.get();
      buffer.get();
      buffer.get();
      buffer.getInt();
    }

    if (type == 5) {
      spriteId = buffer.getInt();
      buffer.getShort();
      buffer.get();
      buffer.get();
      buffer.get();
      buffer.getInt();
      buffer.get();
      buffer.get();
    }

    if (type == 6) {
      buffer.getShort();
      buffer.getShort();
      buffer.getShort();
      buffer.getShort();
      buffer.getShort();
      buffer.getShort();
      buffer.getShort();
      buffer.getShort();
      buffer.get();
      buffer.getShort();
      buffer.getShort();
      buffer.get();

      if (widthType != 0) {
        buffer.getShort();
      }

      if (heightType != 0) {
        buffer.getShort();
      }
    }

    if (type == 9) {
      buffer.get();
      buffer.getInt();
      buffer.get();
    }

    int flags = getTriByte(buffer);
    int keyFlags = buffer.get();

    if (keyFlags != 0) {
      for (; keyFlags != 0; keyFlags = buffer.get() & 0xff) {
        int i = (keyFlags >> 4) - 1;
        keyFlags = (buffer.get() & 0xff) | keyFlags << 8;
        keyFlags &= 4095;

        buffer.get();
        buffer.get();
      }
    }

    getString(buffer);

    int contextMenuFlags = buffer.get() & 0xff;

    int amountActions = contextMenuFlags & 0xf;
    if (amountActions > 0) {
      for (int i = 0; i < amountActions; ++i) {
        getString(buffer);
      }
    }

    int amountCursors = contextMenuFlags >> 4;
    if (amountCursors > 0) {
      buffer.get();
      buffer.getShort();
    }

    if (amountCursors > 1) {
      buffer.get();
      buffer.getShort();
    }

    buffer.get();
    buffer.get();
    buffer.get();

    getString(buffer);

    if ((0x7f & flags >> 11) != 0) {
      buffer.getShort();
      buffer.getShort();
      buffer.getShort();
    }

    initializeScript = parseScriptArguments(buffer);
    mouseEnteredScript = parseScriptArguments(buffer);
    mouseExitedScript = parseScriptArguments(buffer);
    parseScriptArguments(buffer);
    parseScriptArguments(buffer);
    varpScript = parseScriptArguments(buffer);
    inventoryScript = parseScriptArguments(buffer);
    skillScript = parseScriptArguments(buffer);
    updateScript = parseScriptArguments(buffer);
    menuScript = parseScriptArguments(buffer);
    mouseHover = parseScriptArguments(buffer);
    mousePressedScript = parseScriptArguments(buffer);
    mouseHeldScript = parseScriptArguments(buffer);
    mouseReleasedScript = parseScriptArguments(buffer);
    mouseDraggedScript = parseScriptArguments(buffer);
    parseScriptArguments(buffer);
    parseScriptArguments(buffer);
    mouseWheelScript = parseScriptArguments(buffer);
    globalIntegerScript = parseScriptArguments(buffer);
    globalStringScript = parseScriptArguments(buffer);

    playerVariableIds = parseIdentifierSet(buffer);
    inventoryIds = parseIdentifierSet(buffer);
    skillIds = parseIdentifierSet(buffer);
    globalIntegerIds = parseIdentifierSet(buffer);
    globalStringIds = parseIdentifierSet(buffer);
  }

  private void parseLegacyFormat(ByteBuffer buffer) {
    throw new UnsupportedOperationException("Legacy format is not yet supported.");
  }

  private ScriptArguments parseScriptArguments(ByteBuffer buffer) {
    int amountArguments = buffer.get() & 0xff;
    if (amountArguments == 0) {
      return null;
    }

    Object[] arguments = new Object[amountArguments];
    for (int i = 0; i < amountArguments; i++) {
      int type = buffer.get() & 0xff;
      if (type == 0) {
        arguments[i] = buffer.getInt();
      } else if (type == 1) {
        arguments[i] = getString(buffer);
      }
    }

    int id = (Integer) arguments[0];

    Object[] args = new Object[arguments.length - 1];
    System.arraycopy(args, 0, args, 0, args.length);
    return new ScriptArguments(id, args);
  }

  private Set<Integer> parseIdentifierSet(ByteBuffer buffer) {
    int count = buffer.get() & 0xff;
    if (count == 0) {
      return Collections.emptySet();
    }

    Set<Integer> ids = new HashSet<>();
    for (int i = 0; i < count; ++i) {
      ids.add(buffer.getInt());
    }
    return ids;
  }

  public int getId() {
    return id;
  }

  public int getType() {
    return type;
  }

  public int getRenderType() {
    return renderType;
  }

  public int getWidth() {
    return width;
  }

  public int getHeight() {
    return height;
  }

  public int getWidthType() {
    return widthType;
  }

  public int getHeightType() {
    return heightType;
  }

  public int getParentId() {
    return parentId;
  }

  public boolean hasParent() {
    return parentId != -1;
  }

  public String getText() {
    return text;
  }
}