Added rudimentary support for BNCS (chat service)

This commit is contained in:
Collin Smith 2019-12-02 03:12:44 -08:00
parent c009982e13
commit b64900c52c
10 changed files with 474 additions and 1 deletions

View File

@ -234,6 +234,24 @@ project(":server:bnls") {
}
}
project(":server:bncs") {
apply plugin: "java"
dependencies {
compile project(":core")
compile "com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion"
compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
}
dependencies {
compile group: 'commons-cli', name: 'commons-cli', version: cliVersion
}
dependencies {
testCompile 'junit:junit:4.12'
}
}
project(":server:mcp") {
apply plugin: "java"

View File

@ -0,0 +1,40 @@
// automatically generated by the FlatBuffers compiler, do not modify
package com.riiablo.net.packet.bncs;
import com.google.flatbuffers.FlatBufferBuilder;
import com.google.flatbuffers.Table;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@SuppressWarnings("unused")
public final class BNCS extends Table {
public static BNCS getRootAsBNCS(ByteBuffer _bb) { return getRootAsBNCS(_bb, new BNCS()); }
public static BNCS getRootAsBNCS(ByteBuffer _bb, BNCS obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
public BNCS __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
public byte dataType() { int o = __offset(4); return o != 0 ? bb.get(o + bb_pos) : 0; }
public Table data(Table obj) { int o = __offset(6); return o != 0 ? __union(obj, o) : null; }
public static int createBNCS(FlatBufferBuilder builder,
byte data_type,
int dataOffset) {
builder.startObject(2);
BNCS.addData(builder, dataOffset);
BNCS.addDataType(builder, data_type);
return BNCS.endBNCS(builder);
}
public static void startBNCS(FlatBufferBuilder builder) { builder.startObject(2); }
public static void addDataType(FlatBufferBuilder builder, byte dataType) { builder.addByte(0, dataType, 0); }
public static void addData(FlatBufferBuilder builder, int dataOffset) { builder.addOffset(1, dataOffset, 0); }
public static int endBNCS(FlatBufferBuilder builder) {
int o = builder.endObject();
return o;
}
public static void finishBNCSBuffer(FlatBufferBuilder builder, int offset) { builder.finish(offset); }
public static void finishSizePrefixedBNCSBuffer(FlatBufferBuilder builder, int offset) { builder.finishSizePrefixed(offset); }
}

View File

@ -0,0 +1,14 @@
// automatically generated by the FlatBuffers compiler, do not modify
package com.riiablo.net.packet.bncs;
public final class BNCSData {
private BNCSData() { }
public static final byte NONE = 0;
public static final byte ChatEvent = 1;
public static final String[] names = { "NONE", "ChatEvent", };
public static String name(int e) { return names[e]; }
}

View File

@ -0,0 +1,46 @@
// automatically generated by the FlatBuffers compiler, do not modify
package com.riiablo.net.packet.bncs;
import com.google.flatbuffers.FlatBufferBuilder;
import com.google.flatbuffers.Table;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@SuppressWarnings("unused")
public final class ChatEvent extends Table {
public static ChatEvent getRootAsChatEvent(ByteBuffer _bb) { return getRootAsChatEvent(_bb, new ChatEvent()); }
public static ChatEvent getRootAsChatEvent(ByteBuffer _bb, ChatEvent obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
public ChatEvent __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
public byte eid() { int o = __offset(4); return o != 0 ? bb.get(o + bb_pos) : 19; }
public String name() { int o = __offset(6); return o != 0 ? __string(o + bb_pos) : null; }
public ByteBuffer nameAsByteBuffer() { return __vector_as_bytebuffer(6, 1); }
public ByteBuffer nameInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 6, 1); }
public String text() { int o = __offset(8); return o != 0 ? __string(o + bb_pos) : null; }
public ByteBuffer textAsByteBuffer() { return __vector_as_bytebuffer(8, 1); }
public ByteBuffer textInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 8, 1); }
public static int createChatEvent(FlatBufferBuilder builder,
byte eid,
int nameOffset,
int textOffset) {
builder.startObject(3);
ChatEvent.addText(builder, textOffset);
ChatEvent.addName(builder, nameOffset);
ChatEvent.addEid(builder, eid);
return ChatEvent.endChatEvent(builder);
}
public static void startChatEvent(FlatBufferBuilder builder) { builder.startObject(3); }
public static void addEid(FlatBufferBuilder builder, byte eid) { builder.addByte(0, eid, 19); }
public static void addName(FlatBufferBuilder builder, int nameOffset) { builder.addOffset(1, nameOffset, 0); }
public static void addText(FlatBufferBuilder builder, int textOffset) { builder.addOffset(2, textOffset, 0); }
public static int endChatEvent(FlatBufferBuilder builder) {
int o = builder.endObject();
return o;
}
}

View File

@ -0,0 +1,29 @@
// automatically generated by the FlatBuffers compiler, do not modify
package com.riiablo.net.packet.bncs;
public final class EID {
private EID() { }
public static final byte EID_SHOWUSER = 1;
public static final byte EID_JOIN = 2;
public static final byte EID_LEAVE = 3;
public static final byte EID_WHISPER = 4;
public static final byte EID_TALK = 5;
public static final byte EID_BROADCAST = 6;
public static final byte EID_CHANNEL = 7;
public static final byte EID_USERFLAGS = 9;
public static final byte EID_WHISPERSENT = 10;
public static final byte EID_CHANNELFULL = 13;
public static final byte EID_CHANNELDOESNOTEXIST = 14;
public static final byte EID_CHANNELRESTRICTED = 15;
public static final byte EID_INFO = 18;
public static final byte EID_ERROR = 19;
public static final byte EID_IGNORE = 21;
public static final byte EID_ACCEPT = 22;
public static final byte EID_EMOTE = 23;
public static final String[] names = { "EID_SHOWUSER", "EID_JOIN", "EID_LEAVE", "EID_WHISPER", "EID_TALK", "EID_BROADCAST", "EID_CHANNEL", "", "EID_USERFLAGS", "EID_WHISPERSENT", "", "", "EID_CHANNELFULL", "EID_CHANNELDOESNOTEXIST", "EID_CHANNELRESTRICTED", "", "", "EID_INFO", "EID_ERROR", "", "EID_IGNORE", "EID_ACCEPT", "EID_EMOTE", };
public static String name(int e) { return names[e - EID_SHOWUSER]; }
}

View File

@ -0,0 +1,13 @@
include "ChatEvent.fbs";
namespace com.riiablo.net.packet.bncs;
union BNCSData {
ChatEvent,
}
table BNCS {
data:BNCSData;
}
root_type BNCS;

View File

@ -0,0 +1,27 @@
namespace com.riiablo.net.packet.bncs;
enum EID : byte {
EID_SHOWUSER = 0x01, // User in channel
EID_JOIN = 0x02, // User joined channel
EID_LEAVE = 0x03, // User left channel
EID_WHISPER = 0x04, // Recieved whisper
EID_TALK = 0x05, // Chat text
EID_BROADCAST = 0x06, // Server broadcast
EID_CHANNEL = 0x07, // Channel information
EID_USERFLAGS = 0x09, // Flags update
EID_WHISPERSENT = 0x0A, // Sent whisper
EID_CHANNELFULL = 0x0D, // Channel full
EID_CHANNELDOESNOTEXIST = 0x0E, // Channel doesn't exist
EID_CHANNELRESTRICTED = 0x0F, // Channel is restricted
EID_INFO = 0x12, // Information
EID_ERROR = 0x13, // Error message
EID_IGNORE = 0x15, // Notifies that a user has been ignored (DEFUNCT)
EID_ACCEPT = 0x16, // Notifies that a user has been unignored (DEFUNCT)
EID_EMOTE = 0x17, // Emote
}
table ChatEvent {
eid:EID = EID_ERROR;
name:string;
text:string;
}

29
server/bncs/build.gradle Normal file
View File

@ -0,0 +1,29 @@
apply plugin: "java"
sourceCompatibility = 1.7
sourceSets.main.java.srcDirs = [ "src/" ]
sourceSets.test.java.srcDirs = [ "test/" ]
project.ext.mainClassName = "com.riiablo.server.bncs.BNCS"
project.ext.assetsDir = new File("../android/assets");
task run(dependsOn: classes, type: JavaExec) {
main = project.mainClassName
classpath = sourceSets.main.runtimeClasspath
standardInput = System.in
workingDir = project.assetsDir
ignoreExitValue = true
}
task dist(type: Jar) {
from files(sourceSets.main.output.classesDir)
from files(sourceSets.main.output.resourcesDir)
from {configurations.compile.collect {zipTree(it)}}
from files(project.assetsDir);
manifest {
attributes 'Server-Class': project.mainClassName
}
}
dist.dependsOn classes

View File

@ -0,0 +1,257 @@
package com.riiablo.server.bncs;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.backends.headless.HeadlessApplication;
import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.net.ServerSocket;
import com.badlogic.gdx.net.Socket;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.BufferUtils;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicBoolean;
public class Main extends ApplicationAdapter {
private static final String TAG = "D2CS";
private static final int PORT = 6113;
private static final int MAX_CLIENTS = 32;
ServerSocket server;
ByteBuffer buffer;
Thread main;
AtomicBoolean kill;
ThreadGroup clientThreads;
final Array<Client> clients = new Array<>(MAX_CLIENTS);
final Array<Packet> packets = new Array<>(32);
final Array<Packet> cache = new Array<>(32);
public static void main(String[] args) {
HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
new HeadlessApplication(new Main(), config);
}
@Override
public void create() {
Gdx.app.setLogLevel(Application.LOG_DEBUG);
final Calendar calendar = Calendar.getInstance();
DateFormat format = DateFormat.getDateTimeInstance();
Gdx.app.log(TAG, format.format(calendar.getTime()));
try {
InetAddress address = InetAddress.getLocalHost();
Gdx.app.log(TAG, "IP Address: " + address.getHostAddress() + ":" + PORT);
Gdx.app.log(TAG, "Host Name: " + address.getHostName());
} catch (UnknownHostException e) {
Gdx.app.error(TAG, e.getMessage(), e);
}
clientThreads = new ThreadGroup("D2CSClients");
Gdx.app.log(TAG, "Starting server...");
server = Gdx.net.newServerSocket(Net.Protocol.TCP, PORT, null);
buffer = BufferUtils.newByteBuffer(4096);
kill = new AtomicBoolean(false);
main = new Thread(new Runnable() {
@Override
public void run() {
while (!kill.get()) {
Gdx.app.log(TAG, "waiting...");
Socket socket = server.accept(null);
Gdx.app.log(TAG, "connection from " + socket.getRemoteAddress());
synchronized (clients) {
if (clients.size >= MAX_CLIENTS) {
try {
ConnectionDenied(socket, "Server is Full");
} catch (Throwable ignored) {
} finally {
socket.dispose();
}
} else {
try {
ConnectionAccepted(socket);
int id = clients.size;
Client client = new Client(id, socket);
clients.add(client);
client.start();
} catch (Throwable ignored) {
socket.dispose();
}
}
}
}
Gdx.app.log(TAG, "killing child threads...");
synchronized (clients) {
for (Client client : clients) {
if (client != null) {
client.kill.set(true);
}
}
clients.clear();
}
Gdx.app.log(TAG, "killing thread...");
}
});
main.setName("D2CS");
main.start();
}
@Override
public void dispose() {
Gdx.app.log(TAG, "Shutting down...");
kill.set(true);
server.dispose();
try {
main.join();
} catch (Throwable ignored) {}
}
@Override
public void render() {
synchronized (packets) {
cache.clear();
cache.addAll(packets);
packets.clear();
}
for (Packet packet : cache) {
process(packet);
}
}
private void process(Packet packet) {
synchronized (clients) {
for (Client client : clients) {
try {
client.socket.getOutputStream().write(packet.data);
} catch (Throwable t) {
Gdx.app.error(TAG, t.getMessage(), t);
client.kill.set(true);
}
}
}
// BNCS data = packet.data;
// switch (data.dataType()) {
// case BNCSData.ChatEvent:
// synchronized (clients) {
// for (Client client : clients) {
// }
// }
// break;
// default:
// Gdx.app.error(TAG, "Unknown packet type: " + data.dataType());
// }
}
private boolean ConnectionDenied(Socket socket, String reason) throws IOException {
// FlatBufferBuilder builder = new FlatBufferBuilder();
// int reasonOffset = builder.createString(reason);
// int connectionDeniedId = ConnectionClosed.createConnectionClosed(builder, reasonOffset);
// int id = com.riiablo.net.packet.d2gs.D2GS.createD2GS(builder, D2GSData.ConnectionClosed, connectionDeniedId);
// builder.finish(id);
//
// ByteBuffer data = builder.dataBuffer();
// OutputStream out = socket.getOutputStream();
// WritableByteChannel channel = Channels.newChannel(out);
// channel.write(data);
return true;
}
private boolean ConnectionAccepted(Socket socket) throws IOException {
// Gdx.app.debug(TAG, "Connection accepted!");
// FlatBufferBuilder builder = new FlatBufferBuilder();
// ConnectionAccepted.startConnectionAccepted(builder);
// int connectionAcceptedId = ConnectionAccepted.endConnectionAccepted(builder);
// int id = com.riiablo.net.packet.d2gs.D2GS.createD2GS(builder, D2GSData.ConnectionAccepted, connectionAcceptedId);
// builder.finish(id);
//
// ByteBuffer data = builder.dataBuffer();
// OutputStream out = socket.getOutputStream();
// WritableByteChannel channel = Channels.newChannel(out);
// channel.write(data);
return false;
}
private void process(int id, byte[] packet) {
synchronized (packets) {
packets.add(Packet.of(id, packet));
}
}
static String generateClientName() {
return String.format("Client-%08X", MathUtils.random(1, Integer.MAX_VALUE - 1));
}
private class Client extends Thread {
int id;
Socket socket;
AtomicBoolean kill = new AtomicBoolean(false);
Client(int id, Socket socket) {
super(clientThreads, generateClientName());
this.id = id;
this.socket = socket;
}
@Override
public void run() {
while (!kill.get()) {
try {
buffer.clear();
buffer.mark();
ReadableByteChannel in = Channels.newChannel(socket.getInputStream());
if (in.read(buffer) == -1) {
kill.set(true);
break;
}
buffer.limit(buffer.position());
buffer.reset();
byte[] data = com.riiablo.util.BufferUtils.readRemaining(buffer);
/*
BNCS packet = BNCS.getRootAsBNCS(buffer);
Gdx.app.log(TAG, "packet type " + D2GSData.name(packet.dataType()));
*/
process(id, data);
} catch (Throwable t) {
Gdx.app.log(TAG, t.getMessage(), t);
kill.set(true);
}
}
Gdx.app.log(TAG, "closing socket...");
if (socket != null) socket.dispose();
synchronized (clients) {
clients.removeValue(this, true);
}
}
}
private static class Packet {
int id;
byte[] data;
static Packet of(int id, byte[] data) {
Packet packet = new Packet();
packet.id = id;
packet.data = data;
return packet;
}
}
}

View File

@ -3,4 +3,4 @@ include 'desktop'
include 'android'
include 'tools', 'ds1viewer', 'mpqviewer'
include 'tester', 'mpqlib'
include 'server:bnls', 'server:mcp', 'server:d2gs'
include 'server:bnls', 'server:bncs', 'server:mcp', 'server:d2gs'