server stuff

This commit is contained in:
Andrey Kolosov
2019-08-14 19:32:18 +03:00
parent 8c33947d85
commit 916bbd90cb
8 changed files with 2831 additions and 2185 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -4,6 +4,8 @@ import SpawnBinary from './DiabloSpawn.wasm';
import SpawnModule from './DiabloSpawn.jscc';
import axios from 'axios';
import websocket_open from './websocket';
const DiabloSize = 1466809;
const SpawnSize = 1337416;
@@ -16,6 +18,7 @@ let files = null;
let renderBatch = null;
let drawBelt = null;
let is_spawn = false;
let websocket = null;
const ChunkSize = 1 << 20;
class RemoteFile {
@@ -116,8 +119,35 @@ const DApi = {
worker.postMessage({action: "keyboard", rect: null});
},
use_websocket(flag) {
if (flag) {
if (!websocket || websocket.readyState !== 1) {
const sock = websocket = websocket_open('ws://diablo.rivsoft.net/', data => {
if (websocket === sock) {
try_api(() => {
const ptr = wasm._DApi_AllocPacket(data.byteLength);
wasm.HEAPU8.set(new Uint8Array(data), ptr);
});
}
}, code => {
if (typeof code !== "number") {
worker.postMessage({action: "error", error: code.toString(), stack: code.stack});
code = 3;
}
call_api("SNet_WebsocketStatus", code);
});
} else {
call_api("SNet_WebsocketStatus", 0);
}
} else {
if (websocket) {
websocket.close();
}
websocket = null;
}
},
websocket_closed() {
return false;
return websocket ? websocket.readyState !== 1 : false;
},
};
@@ -234,7 +264,9 @@ let maxSoundId = 0, maxBatchId = 0;
let packetBatch = null;
DApi.websocket_send = function(data) {
if (packetBatch) {
if (websocket) {
websocket.send(data);
} else if (packetBatch) {
packetBatch.push(data.slice().buffer);
} else {
worker.postMessage({action: "packet", buffer: data});
@@ -249,9 +281,6 @@ function try_api(func) {
try {
func();
} catch (e) {
if (typeof e === "string") {
worker.postMessage({action: ""})
}
worker.postMessage({action: "error", error: e.toString(), stack: e.stack});
}
}

260
src/api/packet.js Normal file
View File

@@ -0,0 +1,260 @@
export class buffer_reader {
constructor(buffer) {
this.buffer = (buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer));
this.pos = 0;
}
done() {
return this.pos === this.buffer.byteLength;
}
read8() {
if (this.pos >= this.buffer.byteLength) {
throw Error('packet too small');
}
return this.buffer[this.pos++];
}
read16() {
const {pos, buffer} = this;
if (pos + 2 > buffer.byteLength) {
throw Error('packet too small');
}
const result = buffer[pos] | (buffer[pos + 1] << 8);
this.pos += 2;
return result;
}
read32() {
const {pos, buffer} = this;
if (pos + 4 > buffer.byteLength) {
throw Error('packet too small');
}
const result = buffer[pos] | (buffer[pos + 1] << 8) | (buffer[pos + 2] << 16) | (buffer[pos + 3] << 24);
this.pos += 4;
return result;
}
read_str() {
const length = this.read8();
const {pos, buffer} = this;
if (pos + length > buffer.byteLength) {
throw Error('packet too small');
}
const result = String.fromCharCode(...buffer.subarray(pos, pos + length));
this.pos += length;
return result;
}
rest() {
const size = this.read32();
const result = this.buffer.subarray(this.pos, this.pos + size);
this.pos += size;
return result;
}
}
export class buffer_writer {
constructor(length) {
this.buffer = new Uint8Array(length);
this.pos = 0;
}
get result() {
return this.buffer.buffer;
}
write8(value) {
this.buffer[this.pos++] = value;
return this;
}
write16(value) {
const {pos, buffer} = this;
buffer[pos] = value;
buffer[pos + 1] = value >> 8;
this.pos += 2;
return this;
}
write32(value) {
const {pos, buffer} = this;
buffer[pos] = value;
buffer[pos + 1] = value >> 8;
buffer[pos + 2] = value >> 16;
buffer[pos + 3] = value >> 24;
this.pos += 4;
return this;
}
write_str(value) {
const length = value.length;
this.write8(length);
const {pos, buffer} = this;
for (let i = 0; i < length; ++i) {
buffer[pos + i] = value.charCodeAt(i);
}
this.pos += length;
return this;
}
rest(value) {
this.write32(value.byteLength);
this.buffer.set(value, this.pos);
this.pos += value.byteLength;
return this;
}
}
export const RejectionReason = {
JOIN_SUCCESS: 0x00,
JOIN_ALREADY_IN_GAME: 0x01,
JOIN_GAME_NOT_FOUND: 0x02,
JOIN_INCORRECT_PASSWORD: 0x03,
JOIN_VERSION_MISMATCH: 0x04,
JOIN_GAME_FULL: 0x05,
CREATE_GAME_EXISTS: 0x06,
};
export function read_packet(reader, types) {
const code = reader.read8();
const cls = Object.values(types).find(cls => cls.code === code);
if (!cls) {
throw Error('invalid packet code');
}
return {type: cls, packet: cls.read(reader)};
}
export function packet_size(type, packet) {
return (typeof type.size === "function" ? type.size(packet) : type.size) + 1;
}
export function write_packet(type, packet) {
const size = packet_size(type, packet);
return type.write(new buffer_writer(size).write8(type.code), packet).result;
}
export function make_batch(types) {
return {
code: 0x00,
read: reader => {
const count = reader.read16();
const packets = [];
for (let i = 0; i < count; ++i) {
packets.push(read_packet(reader, types()));
}
return packets;
},
size: packets => packets.reduce((sum, {type, packet}) => sum + packet_size(type, packet), 2),
write: (writer, packets) => {
writer.write16(packets.length);
for (let {type, packet} of packets) {
type.write(writer.write8(type.code), packet);
}
return writer;
},
};
}
export const server_packet = {
info: {
code: 0x32,
read: reader => ({version: reader.read32()}),
size: 4,
write: (writer, {version}) => writer.write32(version),
},
game_list: {
code: 0x21,
read: reader => {
const count = reader.read16();
const games = [];
for (let i = 0; i < count; ++i) {
games.push({type: reader.read32(), name: reader.read_str()});
}
return {games};
},
size: ({games}) => games.reduce((sum, {name}) => sum + 5 + name.length, 2),
write: (writer, {games}) => {
writer.write16(games.length);
for (let {type, name} of games) {
writer.write32(type);
writer.write_str(name);
}
return writer;
},
},
join_accept: {
code: 0x12,
read: reader => ({cookie: reader.read32(), index: reader.read8(), seed: reader.read32(), difficulty: reader.read32()}),
size: 13,
write: (writer, {cookie, index, seed, difficulty}) => writer.write32(cookie).write8(index).write32(seed).write32(difficulty),
},
join_reject: {
code: 0x15,
read: reader => ({cookie: reader.read32(), reason: reader.read8()}),
size: 5,
write: (writer, {cookie, reason}) => writer.write32(cookie).write8(reason),
},
connect: {
code: 0x13,
read: reader => ({id: reader.read8()}),
size: 1,
write: (writer, {id}) => writer.write8(id),
},
disconnect: {
code: 0x14,
read: reader => ({id: reader.read8(), reason: reader.read32()}),
size: 5,
write: (writer, {id, reason}) => writer.write8(id).write32(reason),
},
message: {
code: 0x01,
read: reader => ({id: reader.read8(), payload: reader.rest()}),
size: ({payload}) => 5 + payload.byteLength,
write: (writer, {id, payload}) => writer.write8(id).rest(payload),
},
turn: {
code: 0x02,
read: reader => ({id: reader.read8(), turn: reader.read32()}),
size: 5,
write: (writer, {id, turn}) => writer.write8(id).write32(turn),
},
batch: make_batch(() => server_packet),
};
export const client_packet = {
info: {
code: 0x31,
read: reader => ({version: reader.read32()}),
size: 4,
write: (writer, {version}) => writer.write32(version),
},
game_list: {
code: 0x21,
read: () => ({}),
size: 0,
write: writer => writer,
},
create_game: {
code: 0x22,
read: reader => ({cookie: reader.read32(), name: reader.read_str(), password: reader.read_str(), difficulty: reader.read32()}),
size: ({name, password}) => 10 + name.length + password.length,
write: (writer, {cookie, name, password, difficulty}) => writer.write32(cookie).write_str(name).write_str(password).write32(difficulty),
},
join_game: {
code: 0x23,
read: reader => ({cookie: reader.read32(), name: reader.read_str(), password: reader.read_str()}),
size: ({name, password}) => 6 + name.length + password.length,
write: (writer, {cookie, name, password}) => writer.write32(cookie).write_str(name).write_str(password),
},
leave_game: {
code: 0x24,
read: () => ({}),
size: 0,
write: writer => writer,
},
drop_player: {
code: 0x03,
read: reader => ({id: reader.read8(), reason: reader.read32()}),
size: 5,
write: (writer, {id, reason}) => writer.write8(id).write32(reason),
},
message: {
code: 0x01,
read: reader => ({id: reader.read8(), payload: reader.rest()}),
size: ({payload}) => 5 + payload.byteLength,
write: (writer, {id, payload}) => writer.write8(id).rest(payload),
},
turn: {
code: 0x02,
read: reader => ({turn: reader.read32()}),
size: 4,
write: (writer, {turn}) => writer.write32(turn),
},
batch: make_batch(() => server_packet),
};

View File

@@ -1,210 +1,5 @@
import Peer from 'peerjs';
class buffer_reader {
constructor(buffer) {
this.buffer = (buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer));
this.pos = 0;
}
done() {
return this.pos === this.buffer.byteLength;
}
read8() {
if (this.pos >= this.buffer.byteLength) {
throw Error('packet too small');
}
return this.buffer[this.pos++];
}
read16() {
const {pos, buffer} = this;
if (pos + 2 > buffer.byteLength) {
throw Error('packet too small');
}
const result = buffer[pos] | (buffer[pos + 1] << 8);
this.pos += 2;
return result;
}
read32() {
const {pos, buffer} = this;
if (pos + 4 > buffer.byteLength) {
throw Error('packet too small');
}
const result = buffer[pos] | (buffer[pos + 1] << 8) | (buffer[pos + 2] << 16) | (buffer[pos + 3] << 24);
this.pos += 4;
return result;
}
read_str() {
const length = this.read8();
const {pos, buffer} = this;
if (pos + length > buffer.byteLength) {
throw Error('packet too small');
}
const result = String.fromCharCode(...buffer.subarray(pos, pos + length));
this.pos += length;
return result;
}
rest() {
const result = this.buffer.subarray(this.pos);
this.pos = this.buffer.length;
return result;
}
}
class buffer_writer {
constructor(length) {
this.buffer = new Uint8Array(length);
this.pos = 0;
}
get result() {
return this.buffer.buffer;
}
write8(value) {
this.buffer[this.pos++] = value;
return this;
}
write16(value) {
const {pos, buffer} = this;
buffer[pos] = value;
buffer[pos + 1] = value >> 8;
this.pos += 2;
return this;
}
write32(value) {
const {pos, buffer} = this;
buffer[pos] = value;
buffer[pos + 1] = value >> 8;
buffer[pos + 2] = value >> 16;
buffer[pos + 3] = value >> 24;
this.pos += 4;
return this;
}
write_str(value) {
const length = value.length;
this.write8(length);
const {pos, buffer} = this;
for (let i = 0; i < length; ++i) {
buffer[pos + i] = value.charCodeAt(i);
}
this.pos += length;
return this;
}
rest(value) {
this.buffer.set(value, this.pos);
return this;
}
}
const RejectionReason = {
JOIN_SUCCESS: 0x00,
JOIN_ALREADY_IN_GAME: 0x01,
JOIN_GAME_NOT_FOUND: 0x02,
JOIN_INCORRECT_PASSWORD: 0x03,
JOIN_VERSION_MISMATCH: 0x04,
JOIN_GAME_FULL: 0x05,
CREATE_GAME_EXISTS: 0x06,
};
const server_packet = {
info: {
code: 0x32,
read: reader => ({version: reader.read32()}),
write: ({version}) => new buffer_writer(5).write8(server_packet.info.code).write32(version).result,
},
game_list: {
code: 0x21,
read: reader => {
const count = reader.read8();
const games = [];
for (let i = 0; i < count; ++i) {
games.push({type: reader.read32(), name: reader.read_str()});
}
return {games};
},
write: ({games}) => {
const writer = new buffer_writer(games.reduce((sum, {name}) => sum + 5 + name.length, 2));
writer.write8(server_packet.game_list.code);
writer.write8(games.length);
for (let {code, name} of games) {
writer.write32(code);
writer.write_str(name);
}
return writer.result;
},
},
join_accept: {
code: 0x12,
read: reader => ({cookie: reader.read32(), index: reader.read8(), seed: reader.read32(), difficulty: reader.read32()}),
write: ({cookie, index, seed, difficulty}) => new buffer_writer(14).write8(server_packet.join_accept.code).write32(cookie).write8(index).write32(seed).write32(difficulty).result,
},
join_reject: {
code: 0x15,
read: reader => ({cookie: reader.read32(), reason: reader.read8()}),
write: ({cookie, reason}) => new buffer_writer(6).write8(server_packet.join_reject.code).write32(cookie).write8(reason).result,
},
connect: {
code: 0x13,
read: reader => ({id: reader.read8()}),
write: ({id}) => new buffer_writer(2).write8(server_packet.connect.code).write8(id).result,
},
disconnect: {
code: 0x14,
read: reader => ({id: reader.read8(), reason: reader.read32()}),
write: ({id, reason}) => new buffer_writer(6).write8(server_packet.disconnect.code).write8(id).write32(reason).result,
},
message: {
code: 0x01,
read: reader => ({id: reader.read8(), payload: reader.rest()}),
write: ({id, payload}) => new buffer_writer(2 + payload.byteLength).write8(server_packet.message.code).write8(id).rest(payload).result,
},
turn: {
code: 0x02,
read: reader => ({id: reader.read8(), turn: reader.read32()}),
write: ({id, turn}) => new buffer_writer(6).write8(server_packet.turn.code).write8(id).write32(turn).result,
},
};
const client_packet = {
info: {
code: 0x31,
read: reader => ({version: reader.read32()}),
write: ({version}) => new buffer_writer(5).write8(client_packet.info.code).write32(version).result,
},
game_list: {
code: 0x21,
read: () => ({}),
write: () => new buffer_writer(1).write8(client_packet.game_list.code).result,
},
create_game: {
code: 0x22,
read: reader => ({cookie: reader.read32(), name: reader.read_str(), password: reader.read_str(), difficulty: reader.read32()}),
write: ({cookie, name, password, difficulty}) => new buffer_writer(11 + name.length + password.length)
.write8(client_packet.create_game.code).write32(cookie).write_str(name).write_str(password).write32(difficulty).result,
},
join_game: {
code: 0x23,
read: reader => ({cookie: reader.read32(), name: reader.read_str(), password: reader.read_str()}),
write: ({cookie, name, password}) => new buffer_writer(7 + name.length + password.length)
.write8(client_packet.join_game.code).write32(cookie).write_str(name).write_str(password).result,
},
leave_game: {
code: 0x24,
read: () => ({}),
write: () => new buffer_writer(1).write8(client_packet.leave_game.code).result,
},
drop_player: {
code: 0x03,
read: reader => ({id: reader.read8(), reason: reader.read32()}),
write: ({id, reason}) => new buffer_writer(6).write8(client_packet.drop_player.code).write8(id).write32(reason).result,
},
message: {
code: 0x01,
read: reader => ({id: reader.read8(), payload: reader.rest()}),
write: ({id, payload}) => new buffer_writer(2 + payload.byteLength).write8(client_packet.message.code).write8(id).rest(payload).result,
},
turn: {
code: 0x02,
read: reader => ({turn: reader.read32()}),
write: ({turn}) => new buffer_writer(5).write8(client_packet.turn.code).write32(turn).result,
},
};
import { buffer_reader, read_packet, write_packet, client_packet, server_packet, RejectionReason } from './packet';
/*function log_packet(data, type) {
const reader = new buffer_reader(data);
@@ -262,15 +57,12 @@ class webrtc_server {
const peer = {conn};
conn.on('data', packet => {
const reader = new buffer_reader(packet);
const code = reader.read8();
let pkt;
switch (code) {
const {type, packet: pkt} = read_packet(reader, client_packet);
switch (type.code) {
case client_packet.info.code:
pkt = client_packet.info.read(reader);
peer.version = pkt.version;
break;
case client_packet.join_game.code:
pkt = client_packet.join_game.read(reader);
if (peer.version !== this.version) {
conn.send(server_packet.join_reject.write({cookie: pkt.cookie, reason: RejectionReason.JOIN_VERSION_MISMATCH}));
} else if (pkt.name !== this.name) {
@@ -294,7 +86,7 @@ class webrtc_server {
break;
default:
if (peer.id != null) {
this.handle(peer.id, code, reader);
this.handle(peer.id, type.code, pkt);
} else {
return;
}
@@ -343,23 +135,18 @@ class webrtc_server {
}
}
handle(id, code, reader) {
let pkt;
handle(id, code, pkt) {
switch (code) {
case client_packet.leave_game.code:
pkt = client_packet.leave_game.read(reader);
this.drop(id, 3);
break;
case client_packet.drop_player.code:
pkt = client_packet.drop_player.read(reader);
this.drop(pkt.id, pkt.reason);
break;
case client_packet.message.code:
pkt = client_packet.message.read(reader);
this.send(pkt.id === 0xFF ? ~(1 << id) : (1 << pkt.id), server_packet.message.write({id, payload: pkt.payload}));
break;
case client_packet.turn.code:
pkt = client_packet.turn.read(reader);
this.send(~(1 << id), server_packet.turn.write({id, turn: pkt.turn}));
break;
default:
@@ -411,18 +198,15 @@ class webrtc_client {
this.conn.on('data', data => {
unreg();
const reader = new buffer_reader(data);
const code = reader.read8();
let pkt;
switch (code) {
const {type, packet: pkt} = read_packet(reader, server_packet);
switch (type.code) {
case server_packet.join_accept.code:
pkt = server_packet.join_accept.read(reader);
this.myplr = pkt.index;
break;
case server_packet.join_reject.code:
onClose();
break;
case server_packet.disconnect.code:
pkt = server_packet.disconnect.read(reader);
if (pkt.id === 'myplr') {
onClose();
}
@@ -460,15 +244,12 @@ export default function webrtc_open(onMessage) {
send: function(packet) {
//log_packet(packet, client_packet);
const reader = new buffer_reader(packet);
const code = reader.read8();
let pkt;
switch (code) {
const {type, packet: pkt} = read_packet(reader, client_packet);
switch (type.code) {
case client_packet.info.code:
pkt = client_packet.info.read(reader);
version = pkt.version;
break;
case client_packet.create_game.code:
pkt = client_packet.create_game.read(reader);
if (server || client) {
onMessage(server_packet.join_reject.write({cookie: pkt.cookie, reason: RejectionReason.JOIN_ALREADY_IN_GAME}));
} else {
@@ -476,7 +257,6 @@ export default function webrtc_open(onMessage) {
}
break;
case client_packet.join_game.code:
pkt = client_packet.join_game.read(reader);
if (server || client) {
onMessage(server_packet.join_reject.write({cookie: pkt.cookie, reason: RejectionReason.JOIN_ALREADY_IN_GAME}));
} else {
@@ -485,18 +265,18 @@ export default function webrtc_open(onMessage) {
break;
default:
if (server) {
server.handle(0, code, reader);
if (code === client_packet.leave_game.code) {
server.handle(0, type.code, pkt);
if (type.code === client_packet.leave_game.code) {
server = null;
}
} else if (client) {
client.send(packet);
if (code === client_packet.leave_game.code) {
if (type.code === client_packet.leave_game.code) {
client = null;
}
return;
} else if (code !== client_packet.leave_game.code) {
throw Error(`invalid packet ${code}`);
} else if (type.code !== client_packet.leave_game.code) {
throw Error(`invalid packet ${type.code}`);
}
}
if (!reader.done()) {

View File

@@ -1,4 +1,4 @@
export default async function websocket_open(url, handler) {
async function do_websocket_open(url, handler) {
const socket = new WebSocket(url);
socket.binaryType = "arraybuffer";
let versionCbk = null;
@@ -9,7 +9,7 @@ export default async function websocket_open(url, handler) {
handler(data);
});
await new Promise((resolve, reject) => {
const onError = err => reject(err);
const onError = err => reject(1);
socket.addEventListener("error", onError);
socket.addEventListener("open", () => {
socket.removeEventListener("error", onError);
@@ -19,7 +19,7 @@ export default async function websocket_open(url, handler) {
await new Promise((resolve, reject) => {
const to = setTimeout(() => {
versionCbk = null;
reject(Error("connection timed out"));
reject(1);
}, 5000);
versionCbk = data => {
clearTimeout(to);
@@ -30,7 +30,7 @@ export default async function websocket_open(url, handler) {
if (version === 1) {
resolve();
} else {
reject("server version mismatch");
reject(2);
}
}
};
@@ -46,3 +46,40 @@ export default async function websocket_open(url, handler) {
socket.send(clientInfo);
return socket;
}
export default function websocket_open(url, handler, finisher) {
let ws = null, pending = [];
const proxy = {
get readyState() {
return ws ? ws.readyState : 0;
},
send(msg) {
if (ws) {
ws.send(msg);
} else {
pending.push(msg.slice());
}
},
close() {
if (ws) {
ws.close();
} else {
pending = null;
}
},
};
do_websocket_open(url, handler).then(sock => {
ws = sock;
if (pending) {
for (let msg of pending) {
ws.send(msg);
}
} else {
ws.close();
}
finisher(0);
}, err => {
finisher(err);
});
return proxy;
}