Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing the full cache update server. #131

Merged
merged 3 commits into from
Mar 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ Currently the server is set up for the 435 revision of the game, which was a gam
- NPC spawn loading via YAML configuration.
- Player & NPC pathing validation via collision and tile maps generated from the 377 game cache.
- A basic REST service for polling logged in users.
- Full functional update server.
- A diverse TypeScript plugin system for easily writing new content based off of in-game actions.
- Flexible quest and dialogue systems for easy content development.

## Usage

Expand Down Expand Up @@ -69,4 +71,4 @@ RuneJS supports the 435 RuneScape game client being renamed by [Promises](https:

#### Update Server

RuneJS does not provide a fully working update server for revision 435. While this is planned for a future release, the [refactored-client-435](https://github.com/Promises/refactored-client-435) currently has the update server disabled. Due to this, we can only recommend this client at this time.
RuneJS provides a fully working update server for the 435 client to use. The update server runs alongside the regular game server using the same port, so no additional configuration is required. Simply start the server and then your game client.
31 changes: 28 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@
"license": "GPL-3.0",
"dependencies": {
"@hapi/joi": "^16.1.8",
"@runejs/cache-parser": "0.3.0",
"@runejs/cache-parser": "0.4.5",
"@runejs/logger": "^1.0.0",
"bigi": "^1.4.2",
"body-parser": "^1.19.0",
"crc-32": "^1.2.0",
"express": "^4.17.1",
"js-yaml": "^3.13.1",
"lodash": "^4.17.15",
Expand Down
25 changes: 24 additions & 1 deletion src/game-server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as net from 'net';
import { watch } from 'chokidar';
import * as CRC32 from 'crc-32';

import { RsBuffer } from './net/rs-buffer';
import { World } from './world/world';
Expand All @@ -25,10 +26,12 @@ import { setPlayerInitPlugins } from '@server/world/actor/player/player';
import { setNpcInitPlugins } from '@server/world/actor/npc/npc';
import { setQuestPlugins } from '@server/world/config/quests';


export let serverConfig: ServerConfig;
export let gameCache377: EarlyFormatGameCache;
export let gameCache: NewFormatGameCache;
export let world: World;
export let crcTable: Buffer;

export async function injectPlugins(): Promise<void> {
const actionTypes: { [key: string]: ActionPlugin[] } = {};
Expand Down Expand Up @@ -59,6 +62,20 @@ export async function injectPlugins(): Promise<void> {
setNpcInitPlugins(actionTypes[ActionType.NPC_INIT]);
}

function generateCrcTable(): void {
const index = gameCache.metaChannel;
const indexLength = index.getBuffer().length;
const buffer = RsBuffer.create(4048);
buffer.writeByte(0);
buffer.writeIntBE(indexLength);
for(let file = 0; file < (indexLength / 6); file++) {
const crcValue = CRC32.buf(gameCache.getRawCacheFile(255, file).getBuffer());
buffer.writeIntBE(crcValue);
}

crcTable = buffer.getBuffer();
}

export function runGameServer(): void {
serverConfig = parseServerConfig();

Expand All @@ -69,6 +86,7 @@ export function runGameServer(): void {

gameCache377 = new EarlyFormatGameCache('cache/377', { loadMaps: true, loadDefinitions: false, loadWidgets: false });
gameCache = new NewFormatGameCache('cache/435');
generateCrcTable();
world = new World();
injectPlugins().then(() => {
world.init();
Expand All @@ -87,7 +105,11 @@ export function runGameServer(): void {

net.createServer(socket => {
logger.info('Socket opened');
// socket.setNoDelay(true);

socket.setNoDelay(true);
socket.setKeepAlive(true);
socket.setTimeout(30000);

let clientConnection = new ClientConnection(socket);

socket.on('data', data => {
Expand All @@ -104,6 +126,7 @@ export function runGameServer(): void {
});

socket.on('error', error => {
logger.error(error.message);
socket.destroy();
logger.error('Socket destroyed due to connection error.');
});
Expand Down
10 changes: 5 additions & 5 deletions src/net/client-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { ClientLoginParser } from './data-parser/client-login-parser';
import { ClientPacketDataParser } from './data-parser/client-packet-data-parser';
import { DataParser } from './data-parser/data-parser';
import { VersionHandshakeParser } from '@server/net/data-parser/version-handshake-parser';
import { VersionListParser } from '@server/net/data-parser/version-list-parser';
import { UpdateServerParser } from '@server/net/data-parser/update-server-parser';

enum ConnectionStage {
VERSION_HANDSHAKE = 'VERSION_HANDSHAKE',
VERSION_LIST = 'VERSION_LIST',
UPDATE_SERVER = 'UPDATE_SERVER',
LOGIN_HANDSHAKE = 'LOGIN_HANDSHAKE',
LOGIN = 'LOGIN',
LOGGED_IN = 'LOGGED_IN'
Expand Down Expand Up @@ -54,8 +54,8 @@ export class ClientConnection {
}

if(this.connectionStage === ConnectionStage.VERSION_HANDSHAKE) {
this.connectionStage = ConnectionStage.VERSION_LIST;
this.dataParser = new VersionListParser(this);
this.connectionStage = ConnectionStage.UPDATE_SERVER;
this.dataParser = new UpdateServerParser(this);
} else if(this.connectionStage === ConnectionStage.LOGIN_HANDSHAKE) {
this.connectionStage = ConnectionStage.LOGIN;
this.dataParser = new ClientLoginParser(this);
Expand All @@ -64,9 +64,9 @@ export class ClientConnection {
this.dataParser = new ClientPacketDataParser(this);
}
} catch(err) {
this.socket.destroy();
console.error('Error decoding client data');
console.error(err);
this.socket.destroy();
}
}

Expand Down
2 changes: 0 additions & 2 deletions src/net/data-parser/client-packet-data-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ export class ClientPacketDataParser extends DataParser {
}

if(this.activeBuffer.getReadable() < this.activePacketSize) {
//console.error('Not enough readable data for packet ' + this.activePacketId + ' with size ' + this.activePacketSize + ', but only ' +
// this.activeBuffer.getReadable() + ' data is left of ' + this.activeBuffer.getBuffer().length);
return;
}

Expand Down
84 changes: 84 additions & 0 deletions src/net/data-parser/update-server-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { RsBuffer } from '@server/net/rs-buffer';
import { DataParser } from './data-parser';
import { crcTable, gameCache } from '@server/game-server';

/**
* Handles the cache update server.
*/
export class UpdateServerParser extends DataParser {

private files: { file: number, index: number }[] = [];

public parse(buffer?: RsBuffer): void {
if(!buffer) {
return;
}

while(buffer.getReadable() >= 4) {
const type = buffer.readUnsignedByte();
const index = buffer.readUnsignedByte();
const file = buffer.readUnsignedShortBE();

switch(type) {
case 0: // queue
this.files.push({ index, file });
break;
case 1: // immediate
this.clientConnection.socket.write(this.generateFile(index, file));
break;
case 2:
case 3: // clear queue
this.files = [];
break;
case 4: // error
break;
}

while(this.files.length > 0) {
const info = this.files.shift();
this.clientConnection.socket.write(this.generateFile(info.index, info.file));
}
}
}

private generateFile(index: number, file: number): Buffer {
let cacheFile;

if(index === 255 && file === 255) {
const crcBuffer = Buffer.alloc(crcTable.length);
crcTable.copy(crcBuffer, 0, 0);
cacheFile = new RsBuffer(crcBuffer);
} else {
cacheFile = gameCache.getRawCacheFile(index, file);
}

if(!cacheFile || cacheFile.getBuffer().length === 0) {
throw `Cache file not found; file(${file}) with index(${index})`;
}

const cacheFileBuffer = cacheFile.getBuffer();

const buffer = RsBuffer.create((cacheFileBuffer.length - 2) + ((cacheFileBuffer.length - 2) / 511) + 8);
buffer.writeUnsignedByte(index);
buffer.writeUnsignedShortBE(file);

let length: number = ((cacheFileBuffer.readUInt8(1) << 24) + (cacheFileBuffer.readUInt8(2) << 16) +
(cacheFileBuffer.readUInt8(3) << 8) + cacheFileBuffer.readUInt8(4)) + 9;
if(cacheFileBuffer[0] == 0) {
length -= 4;
}

let c = 3;
for(let i = 0; i < length; i++) {
if(c == 512) {
buffer.writeUnsignedByte(255);
c = 1;
}

buffer.writeByte(cacheFileBuffer.readInt8(i));
c++;
}

return buffer.getData();
}
}
35 changes: 0 additions & 35 deletions src/net/data-parser/version-list-parser.ts

This file was deleted.