0.1.1 release
This commit is contained in:
parent
65dcf4bd86
commit
57bc85c0dc
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
|
package-lock.json
|
||||||
node_modules/
|
node_modules/
|
||||||
*.ini
|
*.ini
|
||||||
|
113
README.md
113
README.md
@ -1,26 +1,99 @@
|
|||||||
CHUNICHUNIMatch
|
ShadowTenpo
|
||||||
===============
|
===========
|
||||||
|
|
||||||
Remote LAN multiplayer matching by virtualizing members into the local LAN.
|
Remote SEGA cab-to-cab multiplayer by virtualizing members into the local LAN.
|
||||||
|
|
||||||
*No VPN required*
|
Not a VPN. No need for a VPN.*
|
||||||
|
|
||||||
Instructions
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
***Project is currently in experimentation***
|
You need a server to connect and forward traffic between clients. You can deploy
|
||||||
|
this on any machine that has a public IP address and can accept HTTP/Websockets
|
||||||
|
connection on a port of your choice.
|
||||||
|
|
||||||
* 配置IP别名。提权cmd运行以下指令在主网卡上新增192.168.139.11-14(如果必要,替换Ethernet为网卡名)
|
On the client:
|
||||||
```
|
|
||||||
netsh interface ipv4 set interface interface="Ethernet" dhcpstaticipcoexistence=enabled
|
1. Install NodeJS (and NPM) if not installed already.
|
||||||
netsh interface ipv4 add address "Ethernet" 192.168.139.11 255.255.255.0 store=active
|
2. Open `install.bat`, or run command `npm i` to install node dependencies
|
||||||
netsh interface ipv4 add address "Ethernet" 192.168.139.12 255.255.255.0 store=active
|
3. Edit `config.js` and change the server address and port to your server.
|
||||||
netsh interface ipv4 add address "Ethernet" 192.168.139.13 255.255.255.0 store=active
|
4. If necessary, change other settings in `config.js`. The default values are
|
||||||
netsh interface ipv4 add address "Ethernet" 192.168.139.14 255.255.255.0 store=active
|
for SDBT. Other SEGA games may use different ports, or may not work.
|
||||||
```
|
|
||||||
* 确保使用的segatools更新到至少forcebind
|
To deploy a server:
|
||||||
* 修改segatools.ini添加下面的新section以使用CCM在连接时自动生成的INI
|
|
||||||
```
|
1. Clone this same repo and have a NodeJS installation ready.
|
||||||
[override]
|
2. `npm i` for dependencies
|
||||||
netenv="D:\path\to\ccm\segatools-override.ini"
|
3. Edit `config.js` and change `server_port` to any port of your choice for
|
||||||
```
|
incoming HTTP/Websocket connections.
|
||||||
|
4. Run `node server` to start the server. Using a screen/tmux is recommended as
|
||||||
|
the server will not fork into background by itself.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
*Note: this project may be in active development. Usage instructions may change
|
||||||
|
frequently. If you pull from git or manually update, make sure to revisit the
|
||||||
|
usage every time.*
|
||||||
|
|
||||||
|
1. Open `netif.bat` with administrative privileges to setup IP aliases.
|
||||||
|
* IP aliases are valid until reboot by default.
|
||||||
|
* `NETIF` in the script may be set to `loopback` if the default fails.
|
||||||
|
* Using your physical network interface is discouraged and may cause loss of
|
||||||
|
network. See https://superuser.com/a/1337075 if you really need to.
|
||||||
|
* Using a virtual interface (like VPN adaptor) may cause undefined behavior
|
||||||
|
with the game.
|
||||||
|
2. Open `start.bat` or run `node .` to start the client. Verify that it can
|
||||||
|
connect to the server and obtain party IP addresses.
|
||||||
|
3. Configure `segatools.ini` of the game to use the subnet and address. The
|
||||||
|
client will create a `segatools-override.ini` in the same directory. Use
|
||||||
|
that as a reference.
|
||||||
|
4. Start the game. If IP ADDRESS BAD, start the game as administrator.
|
||||||
|
|
||||||
|
Note that the client cannot be started if the game is running. If the client
|
||||||
|
is closed or crashed, the game must be stopped before you can restart the
|
||||||
|
client.
|
||||||
|
|
||||||
|
Game Settings
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Members must be on the exact same version. This includes game, ICF, options
|
||||||
|
and data.conf.
|
||||||
|
- By default, standard and netdelivery ports are not forwarded. Every member
|
||||||
|
should be their own standard and netdelivery server.
|
||||||
|
- Members must set to the same cab-to-cab group.
|
||||||
|
- Members may use different title servers or even no aime service at all.
|
||||||
|
- No additional executable patching is required.
|
||||||
|
|
||||||
|
Security Warning
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This software has the ability to send arbitrary network traffic to your local
|
||||||
|
network. Your firewall may be bypassed if an adversary took control over parts
|
||||||
|
of the system, including but not limited to, a malicious server, a malicious
|
||||||
|
client on the same server, a middleman attack on any connection to the server.
|
||||||
|
By running the software you acknowledge such security risks on your system, and
|
||||||
|
agree to hold the developers innocent against any potential loss or damages,
|
||||||
|
even if caused by a flaw in the software.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
The authors informally permits acceptable modifications and redistributions
|
||||||
|
within closed communities for the purpose of enhancement and further
|
||||||
|
development. The authors reserves the full authority to determine if any
|
||||||
|
particular usage is acceptable. Specifically, reproduction in any form to any
|
||||||
|
publicly accessible location, including but not limited to GitHub.com, is
|
||||||
|
prohibited.
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
61
cccc.js
Normal file
61
cccc.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
var CHUNICHUNICHUNIC = {};
|
||||||
|
|
||||||
|
const cccc_key = Buffer.from('CHUNICHUNICHUNIC', 'ascii');
|
||||||
|
|
||||||
|
CHUNICHUNICHUNIC.encrypt = function (data) {
|
||||||
|
var length = data.length;
|
||||||
|
if (length % 16 != 0) {
|
||||||
|
var padlen = 16-(length%16);
|
||||||
|
data = Buffer.concat([data, Buffer.alloc(padlen)]);
|
||||||
|
console.warn("Trying to encrypt non-padded data!");
|
||||||
|
length += padlen;
|
||||||
|
}
|
||||||
|
var cipher = crypto.createCipheriv('aes-128-ecb', cccc_key, null);
|
||||||
|
cipher.setAutoPadding(false);
|
||||||
|
var msg0 = Buffer.alloc(4);
|
||||||
|
msg0.writeUInt32LE(length+4, 0);
|
||||||
|
var msg1 = cipher.update(data);
|
||||||
|
var msg2 = cipher.final();
|
||||||
|
return Buffer.concat([msg0, msg1, msg2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHUNICHUNICHUNIC.decrypt = function(msg) {
|
||||||
|
if (msg.length % 16 != 0 ) {
|
||||||
|
console.warn("Bad decrypt length: " + msg.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var decipher = crypto.createDecipheriv('aes-128-ecb', cccc_key, null);
|
||||||
|
decipher.setAutoPadding(false);
|
||||||
|
var data1 = decipher.update(msg);
|
||||||
|
var data2 = decipher.final();
|
||||||
|
return Buffer.concat([data1, data2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHUNICHUNICHUNIC.deframe = function(buf, ctx) {
|
||||||
|
var data, pending, msg;
|
||||||
|
if (!ctx.length) {
|
||||||
|
ctx.length = buf.readUInt32LE(0) - 4;
|
||||||
|
ctx.current = buf.slice(4);
|
||||||
|
if (ctx.length % 16 != 0) {
|
||||||
|
console.warn("Received mispadded packet: ", buf.toString('hex'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.current = Buffer.concat([ctx.current, buf]);
|
||||||
|
}
|
||||||
|
if (ctx.current.length > ctx.length) {
|
||||||
|
pending = ctx.current.slice(ctx.length);
|
||||||
|
msg = ctx.current.slice(0, ctx.length);
|
||||||
|
} else if (ctx.current.length == ctx.length) {
|
||||||
|
msg = ctx.current;
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
ctx.length = 0;
|
||||||
|
data = CHUNICHUNICHUNIC.decrypt(msg);
|
||||||
|
ctx.current = null;
|
||||||
|
}
|
||||||
|
return {data: data, pending: pending};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CHUNICHUNICHUNIC;
|
41
ccencap.js
41
ccencap.js
@ -1,7 +1,10 @@
|
|||||||
var CCEncap = {};
|
var CCEncap = {};
|
||||||
|
|
||||||
const CCM_NET_SENDTCP = 0x01;
|
const CCM_NET_TCPSEND = 0x01;
|
||||||
const CCM_NET_SENDUDP = 0x02;
|
const CCM_NET_UDPSEND = 0x02;
|
||||||
|
const CCM_NET_TCPFIN = 0x03;
|
||||||
|
const CCM_NET_TCPCLOSE = 0x04;
|
||||||
|
const CCM_SYS_MESSAGE = 0x20;
|
||||||
const CCM_PARTY_CREATE = 0x30;
|
const CCM_PARTY_CREATE = 0x30;
|
||||||
const CCM_PARTY_HELLO = 0x31;
|
const CCM_PARTY_HELLO = 0x31;
|
||||||
const CCM_PARTY_UPDATE = 0x32;
|
const CCM_PARTY_UPDATE = 0x32;
|
||||||
@ -26,9 +29,15 @@ CCEncap.encodePartyUpdate = function(req) {
|
|||||||
|
|
||||||
CCEncap.encodePacket = function(src, dst, sport, dport, type, data) {
|
CCEncap.encodePacket = function(src, dst, sport, dport, type, data) {
|
||||||
if (type == 'tcp') {
|
if (type == 'tcp') {
|
||||||
var header = CCM_NET_SENDTCP;
|
var header = CCM_NET_TCPSEND;
|
||||||
} else if (type == 'udp') {
|
} else if (type == 'udp') {
|
||||||
var header = CCM_NET_SENDUDP;
|
var header = CCM_NET_UDPSEND;
|
||||||
|
} else if (type == 'tcpfin') {
|
||||||
|
var header = CCM_NET_TCPFIN;
|
||||||
|
data = Buffer.alloc(0);
|
||||||
|
} else if (type == 'tcpclose') {
|
||||||
|
var header = CCM_NET_TCPCLOSE;
|
||||||
|
data = Buffer.alloc(0);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid network protocol: ' + type);
|
throw new Error('Invalid network protocol: ' + type);
|
||||||
}
|
}
|
||||||
@ -39,10 +48,13 @@ CCEncap.encodePacket = function(src, dst, sport, dport, type, data) {
|
|||||||
msg.writeUInt16LE(dport, 4);
|
msg.writeUInt16LE(dport, 4);
|
||||||
msg[6] = src;
|
msg[6] = src;
|
||||||
data.copy(msg, 7);
|
data.copy(msg, 7);
|
||||||
console.log(type + "_send "+src+':'+sport+'->'+dst+':'+dport);
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CCEncap.encodeTextMessage = function(text) {
|
||||||
|
return Buffer.from(' ' + text); // space is 0x20
|
||||||
|
}
|
||||||
|
|
||||||
CCEncap.decode = function(msg) {
|
CCEncap.decode = function(msg) {
|
||||||
var opcode = msg[0], data = msg;
|
var opcode = msg[0], data = msg;
|
||||||
var ret = {op: '?'};
|
var ret = {op: '?'};
|
||||||
@ -50,8 +62,11 @@ CCEncap.decode = function(msg) {
|
|||||||
case CCM_PARTY_CREATE: ret.op = 'party_create'; break;
|
case CCM_PARTY_CREATE: ret.op = 'party_create'; break;
|
||||||
case CCM_PARTY_HELLO: ret.op = 'party_hello'; break;
|
case CCM_PARTY_HELLO: ret.op = 'party_hello'; break;
|
||||||
case CCM_PARTY_UPDATE: ret.op = 'party_update'; break;
|
case CCM_PARTY_UPDATE: ret.op = 'party_update'; break;
|
||||||
case CCM_NET_SENDTCP: ret.op = 'net_tcp'; break;
|
case CCM_NET_TCPSEND: ret.op = 'net_tcp'; break;
|
||||||
case CCM_NET_SENDUDP: ret.op = 'net_udp'; break;
|
case CCM_NET_UDPSEND: ret.op = 'net_udp'; break;
|
||||||
|
case CCM_NET_TCPFIN: ret.op = 'net_tcpfin'; break;
|
||||||
|
case CCM_NET_TCPCLOSE: ret.op = 'net_tcpclose'; break;
|
||||||
|
case CCM_SYS_MESSAGE: ret.op = 'sys_msg'; break;
|
||||||
}
|
}
|
||||||
switch(opcode) {
|
switch(opcode) {
|
||||||
case CCM_PARTY_CREATE:
|
case CCM_PARTY_CREATE:
|
||||||
@ -60,14 +75,18 @@ CCEncap.decode = function(msg) {
|
|||||||
msg[0] = 0x7b; // '{'
|
msg[0] = 0x7b; // '{'
|
||||||
Object.assign(ret, JSON.parse(msg));
|
Object.assign(ret, JSON.parse(msg));
|
||||||
break;
|
break;
|
||||||
case CCM_NET_SENDTCP:
|
case CCM_NET_TCPSEND:
|
||||||
case CCM_NET_SENDUDP:
|
case CCM_NET_UDPSEND:
|
||||||
|
case CCM_NET_TCPFIN:
|
||||||
|
case CCM_NET_TCPCLOSE:
|
||||||
ret.dst = msg[1];
|
ret.dst = msg[1];
|
||||||
ret.src = msg[6];
|
ret.src = msg[6];
|
||||||
ret.sport = msg.readUInt16LE(2);
|
ret.sport = msg.readUInt16LE(2);
|
||||||
ret.dport = msg.readUInt16LE(4);
|
ret.dport = msg.readUInt16LE(4);
|
||||||
// ret.msg = msg.slice(7);
|
// console.log(ret.op+' '+ret.src+':'+ret.sport+'->'+ret.dst+':'+ret.dport);
|
||||||
console.log(ret.op+" "+ret.src+':'+ret.sport+'->'+ret.dst+':'+ret.dport);
|
break;
|
||||||
|
case CCM_SYS_MESSAGE:
|
||||||
|
ret.msg = msg.subarray(1).toString('utf-8');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn('Received unknown opcode: ' + opcode.toString(16));
|
console.warn('Received unknown opcode: ' + opcode.toString(16));
|
||||||
|
7
cchub.js
7
cchub.js
@ -25,10 +25,15 @@ function CCHub(url) {
|
|||||||
break;
|
break;
|
||||||
case 'net_tcp':
|
case 'net_tcp':
|
||||||
case 'net_udp':
|
case 'net_udp':
|
||||||
|
case 'net_tcpfin':
|
||||||
|
case 'net_tcpclose':
|
||||||
var type = msg.op.slice(4);
|
var type = msg.op.slice(4);
|
||||||
var payload = data.slice(7);
|
var payload = data.subarray(7);
|
||||||
this.onNetForward(msg.src, msg.dst, msg.sport, msg.dport, type, payload);
|
this.onNetForward(msg.src, msg.dst, msg.sport, msg.dport, type, payload);
|
||||||
break;
|
break;
|
||||||
|
case 'sys_msg':
|
||||||
|
console.log('Hub message: ' + msg.msg);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn('Unhandled packed: ', msg)
|
console.warn('Unhandled packed: ', msg)
|
||||||
}
|
}
|
||||||
|
61
ccrelay.js
61
ccrelay.js
@ -4,7 +4,7 @@ const dgram = require('dgram');
|
|||||||
// Note: src and dst addresses in this file are full IP strings only.
|
// Note: src and dst addresses in this file are full IP strings only.
|
||||||
|
|
||||||
function CCRelay(host) {
|
function CCRelay(host) {
|
||||||
console.log("Relay started for " + host);
|
console.info("Relay started for " + host);
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.sockets = {};
|
this.sockets = {};
|
||||||
this.servers = {};
|
this.servers = {};
|
||||||
@ -51,18 +51,29 @@ CCRelay.prototype.sendUDP = function(dst, sport, dport, msg) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CCRelay.prototype._setupTCPSocket = function(socket, sport, dport) {
|
CCRelay.prototype._setupTCPSocket = function(socket, gameport, relayport) {
|
||||||
socket.on('data', (msg) => {
|
socket.on('data', (msg) => {
|
||||||
this.onReceive(sport, dport, 'tcp', msg);
|
this.onReceive(gameport, relayport, 'tcp', msg);
|
||||||
});
|
});
|
||||||
socket.on('end', () => {
|
socket.on('end', () => {
|
||||||
this.sockets['tcp'+sport+'-'+dport] = null;
|
if (this.sockets['tcp'+gameport+'-'+relayport]) {
|
||||||
|
console.info('tcp'+gameport+'-'+relayport + ' fin\'ed');
|
||||||
|
this.onReceive(gameport, relayport, 'tcpfin');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
socket.on('close', () => {
|
socket.on('close', () => {
|
||||||
this.sockets['tcp'+sport+'-'+dport] = null;
|
if (this.sockets['tcp'+gameport+'-'+relayport]) {
|
||||||
|
console.info('tcp'+gameport+'-'+relayport + ' closed');
|
||||||
|
this.sockets['tcp'+gameport+'-'+relayport] = null;
|
||||||
|
this.onReceive(gameport, relayport, 'tcpclose');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
socket.on('error', e => {
|
socket.on('error', e => {
|
||||||
this.sockets['tcp'+sport+'-'+dport] = null;
|
if (this.sockets['tcp'+gameport+'-'+relayport]) {
|
||||||
|
console.info('tcp'+gameport+'-'+relayport + 'error', e);
|
||||||
|
this.sockets['tcp'+gameport+'-'+relayport] = null;
|
||||||
|
this.onReceive(gameport, relayport, 'tcpclose');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,8 +90,8 @@ CCRelay.prototype.listenTCP = function(port) {
|
|||||||
this.listenTCP(port);
|
this.listenTCP(port);
|
||||||
});
|
});
|
||||||
server.on('connection', (socket) => {
|
server.on('connection', (socket) => {
|
||||||
|
console.info('tcp'+socket.remotePort+'-'+port + ' created by accept');
|
||||||
this._setupTCPSocket(socket, socket.remotePort, port);
|
this._setupTCPSocket(socket, socket.remotePort, port);
|
||||||
console.log("Adding passive TCP socket ", 'tcp'+socket.remotePort+'-'+port)
|
|
||||||
this.sockets['tcp'+socket.remotePort+'-'+port] = socket;
|
this.sockets['tcp'+socket.remotePort+'-'+port] = socket;
|
||||||
});
|
});
|
||||||
var listenpromise = new Promise((resolve) => {
|
var listenpromise = new Promise((resolve) => {
|
||||||
@ -94,18 +105,21 @@ CCRelay.prototype.listenTCP = function(port) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
CCRelay.prototype.sendTCP = function(dst, sport, dport, msg) {
|
CCRelay.prototype.sendTCP = function(dst, sport, dport, msg) {
|
||||||
var socket = this.sockets['tcp'+sport+'-'+dport];
|
var socket = this.sockets['tcp'+dport+'-'+sport];
|
||||||
if (!socket) {
|
if (!socket) {
|
||||||
socket = new Promise((resolve) => {
|
socket = new Promise((resolve) => {
|
||||||
console.log("Adding active TCP socket", 'tcp'+sport+'-'+dport);
|
console.info('tcp'+dport+'-'+sport + 'created by connect');
|
||||||
var newsocket = new net.Socket();
|
var newsocket = new net.Socket();
|
||||||
this._setupTCPSocket(newsocket, sport, dport);
|
this._setupTCPSocket(newsocket, dport, sport);
|
||||||
newsocket.connect(dport, dst, () => {
|
newsocket.connect({
|
||||||
this.sockets['tcp'+sport+'-'+dport] = newsocket;
|
port: dport, host: dst,
|
||||||
|
localPort: sport, localAddress: this.host
|
||||||
|
}, () => {
|
||||||
|
this.sockets['tcp'+dport+'-'+sport] = newsocket;
|
||||||
resolve(newsocket);
|
resolve(newsocket);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.sockets['tcp'+sport+'-'+dport] = socket;
|
this.sockets['tcp'+dport+'-'+sport] = socket;
|
||||||
}
|
}
|
||||||
if (socket instanceof Promise) {
|
if (socket instanceof Promise) {
|
||||||
return socket.then(() => {
|
return socket.then(() => {
|
||||||
@ -117,4 +131,25 @@ CCRelay.prototype.sendTCP = function(dst, sport, dport, msg) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CCRelay.prototype.endTCP = function(sport, dport) {
|
||||||
|
var socket = this.sockets['tcp'+dport+'-'+sport];
|
||||||
|
if (!socket) {
|
||||||
|
console.warn('Requested to end nonexistent socket ' + 'tcp'+dport+'-'+sport);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.info('FIN on' + 'tcp'+dport+'-'+sport);
|
||||||
|
socket.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
CCRelay.prototype.closeTCP = function(sport, dport) {
|
||||||
|
var socket = this.sockets['tcp'+dport+'-'+sport];
|
||||||
|
if (!socket) {
|
||||||
|
console.warn('Requested to close nonexistent socket ' + 'tcp'+dport+'-'+sport);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.sockets['tcp'+dport+'-'+sport] = null;
|
||||||
|
console.info('Close on' + 'tcp'+dport+'-'+sport);
|
||||||
|
socket.destroy();
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = CCRelay;
|
module.exports = CCRelay;
|
||||||
|
15
config.js
15
config.js
@ -1,6 +1,13 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
"server": "gcp.naominet.com",
|
"version": "0.1.1",
|
||||||
"server_port": 61820,
|
"server": "example.com",
|
||||||
"login": {},
|
"server_port": 37703,
|
||||||
"write_override_ini": "segatools-override.ini"
|
"login": {
|
||||||
|
pref_ip: 11
|
||||||
|
},
|
||||||
|
"write_override_ini": "segatools-override.ini",
|
||||||
|
"tcp_ports": [50000],
|
||||||
|
"udp_ports": [50000],
|
||||||
|
"verbosity": {"log": 1, "warn": 1, "error": 1, "info": 1, "debug": 0},
|
||||||
|
"debug_tcpdump": 0,
|
||||||
};
|
};
|
||||||
|
85
index.js
85
index.js
@ -4,7 +4,10 @@ const fs = require('fs');
|
|||||||
|
|
||||||
const CCRelay = require('./ccrelay');
|
const CCRelay = require('./ccrelay');
|
||||||
const CCHub = require('./cchub');
|
const CCHub = require('./cchub');
|
||||||
|
const WebMgr = require('./webmgr');
|
||||||
|
const cccc = require('./cccc');
|
||||||
|
|
||||||
|
console.log('ShadowTenpo ver. ' + cfg.version);
|
||||||
|
|
||||||
var hub = new CCHub('ws://' + cfg.server + ':' + cfg.server_port);
|
var hub = new CCHub('ws://' + cfg.server + ':' + cfg.server_port);
|
||||||
var relays = {};
|
var relays = {};
|
||||||
@ -12,20 +15,20 @@ var suffix = 0;
|
|||||||
var subnet = "";
|
var subnet = "";
|
||||||
|
|
||||||
hub.ready.then(() => {
|
hub.ready.then(() => {
|
||||||
console.log('Connecting to CCM Hub ' + cfg.server + '...');
|
console.log('Connecting to ShadowTenpo Route ' + cfg.server + '...');
|
||||||
hub.hello(cfg.login || {});
|
var login = cfg.login || {};
|
||||||
|
login.version = cfg.version;
|
||||||
|
hub.hello(login);
|
||||||
});
|
});
|
||||||
|
|
||||||
hub.onPartyJoin = function(mysubnet, mysuffix) {
|
hub.onPartyJoin = function(mysubnet, mysuffix) {
|
||||||
suffix = mysuffix;
|
suffix = mysuffix;
|
||||||
subnet = mysubnet;
|
subnet = mysubnet;
|
||||||
console.log('Join successful. Game IP should be ' + subnet+suffix);
|
console.log('Join successful. Game IP should be ' + subnet+suffix);
|
||||||
console.log('Please wait until all match members have joined before starting game');
|
|
||||||
if (cfg.write_override_ini) {
|
if (cfg.write_override_ini) {
|
||||||
var override =
|
var override =
|
||||||
'[netenv]\n' +
|
'[netenv]\n' +
|
||||||
'enable=1\n' +
|
'enable=1\n' +
|
||||||
'forcebind=1\n' +
|
|
||||||
'addrSuffix=' + suffix + '\n' +
|
'addrSuffix=' + suffix + '\n' +
|
||||||
'[keychip]\n' +
|
'[keychip]\n' +
|
||||||
'subnet=' + subnet + '0\n';
|
'subnet=' + subnet + '0\n';
|
||||||
@ -35,11 +38,19 @@ hub.onPartyJoin = function(mysubnet, mysuffix) {
|
|||||||
|
|
||||||
hub.onPartyUpdate = function(members) {
|
hub.onPartyUpdate = function(members) {
|
||||||
members.forEach(dst => {
|
members.forEach(dst => {
|
||||||
console.log(subnet+dst + ' joined virtual party.');
|
console.log('Virtual cab: ' + subnet+dst);
|
||||||
var relay = new CCRelay(subnet+dst);
|
var relay = new CCRelay(subnet+dst);
|
||||||
relay.bindUDP(50200);
|
cfg.udp_ports.forEach(port => relay.bindUDP(port));
|
||||||
relay.listenTCP(50200);
|
cfg.tcp_ports.forEach(port => relay.listenTCP(port));
|
||||||
|
relay.txctx = {};
|
||||||
|
relay.rxctx = {}
|
||||||
relay.onReceive = function(sport, dport, type, msg) {
|
relay.onReceive = function(sport, dport, type, msg) {
|
||||||
|
var desc = type+' TX: ' + suffix+':'+sport + '->' + dst+':'+dport;
|
||||||
|
if (cfg.debug_tcpdump) {
|
||||||
|
decodeStream(msg, relay.rxctx, desc);
|
||||||
|
} else {
|
||||||
|
console.debug(desc);
|
||||||
|
}
|
||||||
hub.sendNetForward(suffix, dst, sport, dport, type, msg);
|
hub.sendNetForward(suffix, dst, sport, dport, type, msg);
|
||||||
}
|
}
|
||||||
relays[dst] = relay;
|
relays[dst] = relay;
|
||||||
@ -53,8 +64,66 @@ hub.onNetForward = function(src, dst, sport, dport, type, msg) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type == 'tcp') {
|
if (type == 'tcp') {
|
||||||
relay.sendTCP(subnet+dst, sport, dport, msg)
|
relay.sendTCP(subnet+dst, sport, dport, msg);
|
||||||
|
var desc = 'tcp RX: ' + src+':'+sport + '->' + dst+':'+dport;
|
||||||
|
if (cfg.debug_tcpdump) {
|
||||||
|
decodeStream(msg, relay.txctx, desc);
|
||||||
|
} else {
|
||||||
|
console.debug(desc);
|
||||||
|
}
|
||||||
} else if (type == 'udp') {
|
} else if (type == 'udp') {
|
||||||
|
var desc = 'udp RX: ' + src+':'+sport + '->' + dst+':'+dport;
|
||||||
|
console.debug(desc);
|
||||||
relay.sendUDP(subnet+dst, sport, dport, msg);
|
relay.sendUDP(subnet+dst, sport, dport, msg);
|
||||||
|
} else if (type == 'tcpfin') {
|
||||||
|
relay.closeTCP(sport, dport);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function formatbinary(data) {
|
||||||
|
var ret = '';
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
var c = data[i];
|
||||||
|
if (c==0)
|
||||||
|
ret += ' .';
|
||||||
|
else if (c < 16)
|
||||||
|
ret += '0'+c.toString(16);
|
||||||
|
else
|
||||||
|
ret += c.toString(16);
|
||||||
|
|
||||||
|
ret += ' ';
|
||||||
|
if (i % 4 == 3) ret += ' ';
|
||||||
|
if (i % 16 == 15) ret += '\n';
|
||||||
|
}
|
||||||
|
if (data.length % 16 != 0) ret += '\n';
|
||||||
|
return ret;b
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeStream(data, ctx, desc) {
|
||||||
|
do {
|
||||||
|
var frame = cccc.deframe(data, ctx);
|
||||||
|
var msg = frame.data;
|
||||||
|
if (msg) {
|
||||||
|
switch(msg[8]) {
|
||||||
|
case 2: console.debug(desc + ' ping');break;
|
||||||
|
case 3: console.debug(desc + ' pong');break;
|
||||||
|
default:
|
||||||
|
console.debug(desc);
|
||||||
|
console.debug(formatbinary(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (ctx.pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web interface
|
||||||
|
var webmgr = new WebMgr(8000);
|
||||||
|
function logfork(log) {
|
||||||
|
return function() {
|
||||||
|
webmgr.log(Array.from(arguments).join(' '));
|
||||||
|
log.apply(this, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
["log", "warn", "error", "info", "debug"].forEach(stream => {
|
||||||
|
console[stream] = cfg.verbosity[stream] ? logfork(console[stream]) : () => {};
|
||||||
|
});
|
||||||
|
3
install.bat
Normal file
3
install.bat
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
npm i
|
||||||
|
pause
|
80
mime.js
Normal file
80
mime.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
module.exports = {
|
||||||
|
".aac": "audio/aac",
|
||||||
|
".abw": "application/x-abiword",
|
||||||
|
".arc": "application/x-freearc",
|
||||||
|
".avif": "image/avif",
|
||||||
|
".avi": "video/x-msvideo",
|
||||||
|
".azw": "application/vnd.amazon.ebook",
|
||||||
|
".bin": "application/octet-stream",
|
||||||
|
".bmp": "image/bmp",
|
||||||
|
".bz": "application/x-bzip",
|
||||||
|
".bz2": "application/x-bzip2",
|
||||||
|
".cda": "application/x-cdf",
|
||||||
|
".csh": "application/x-csh",
|
||||||
|
".css": "text/css",
|
||||||
|
".csv": "text/csv",
|
||||||
|
".doc": "application/msword",
|
||||||
|
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
".eot": "application/vnd.ms-fontobject",
|
||||||
|
".epub": "application/epub+zip",
|
||||||
|
".gz": "application/gzip",
|
||||||
|
".gif": "image/gif",
|
||||||
|
".htm": "text/html",
|
||||||
|
".html": "text/html",
|
||||||
|
".ico": "image/vnd.microsoft.icon",
|
||||||
|
".ics": "text/calendar",
|
||||||
|
".jar": "application/java-archive",
|
||||||
|
".jpg": "image/jpeg",
|
||||||
|
".jpeg": "image/jpeg",
|
||||||
|
".js": "text/javascript",
|
||||||
|
".json": "application/json",
|
||||||
|
".jsonld": "application/ld+json",
|
||||||
|
".mid": "audio/midi",
|
||||||
|
".midi": "audio/midi",
|
||||||
|
".mjs": "text/javascript",
|
||||||
|
".mp3": "audio/mpeg",
|
||||||
|
".mp4": "video/mp4",
|
||||||
|
".mpg": "video/mpeg",
|
||||||
|
".mpeg": "video/mpeg",
|
||||||
|
".mpkg": "application/vnd.apple.installer+xml",
|
||||||
|
".odp": "application/vnd.oasis.opendocument.presentation",
|
||||||
|
".ods": "application/vnd.oasis.opendocument.spreadsheet",
|
||||||
|
".odt": "application/vnd.oasis.opendocument.text",
|
||||||
|
".oga": "audio/ogg",
|
||||||
|
".ogv": "video/ogg",
|
||||||
|
".ogx": "application/ogg",
|
||||||
|
".opus": "audio/opus",
|
||||||
|
".otf": "font/otf",
|
||||||
|
".png": "image/png",
|
||||||
|
".pdf": "application/pdf",
|
||||||
|
".php": "application/x-httpd-php",
|
||||||
|
".ppt": "application/vnd.ms-powerpoint",
|
||||||
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
|
".rar": "application/vnd.rar",
|
||||||
|
".rtf": "application/rtf",
|
||||||
|
".sh": "application/x-sh",
|
||||||
|
".svg": "image/svg+xml",
|
||||||
|
".swf": "application/x-shockwave-flash",
|
||||||
|
".tar": "application/x-tar",
|
||||||
|
".tif": "image/tiff",
|
||||||
|
".tiff": "image/tiff",
|
||||||
|
".ts": "video/mp2t",
|
||||||
|
".ttf": "font/ttf",
|
||||||
|
".txt": "text/plain",
|
||||||
|
".vsd": "application/vnd.visio",
|
||||||
|
".wav": "audio/wav",
|
||||||
|
".weba": "audio/webm",
|
||||||
|
".webm": "video/webm",
|
||||||
|
".webp": "image/webp",
|
||||||
|
".woff": "font/woff",
|
||||||
|
".woff2": "font/woff2",
|
||||||
|
".xhtml": "application/xhtml+xml",
|
||||||
|
".xls": "application/vnd.ms-excel",
|
||||||
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
".xml": "application/xml",
|
||||||
|
".xul": "application/vnd.mozilla.xul+xml",
|
||||||
|
".zip": "application/zip",
|
||||||
|
".3gp": "video/3gpp;",
|
||||||
|
".3g2": "video/3gpp2;",
|
||||||
|
".7z": "application/x-7z-compressed",
|
||||||
|
};
|
@ -1,5 +1,4 @@
|
|||||||
set NETIF="Ethernet"
|
set NETIF="Loopback Pseudo-Interface 1"
|
||||||
netsh interface ipv4 set interface interface=%NETIF% dhcpstaticipcoexistence=enabled
|
|
||||||
netsh interface ipv4 add address %NETIF% 192.168.139.11 255.255.255.0 store=active
|
netsh interface ipv4 add address %NETIF% 192.168.139.11 255.255.255.0 store=active
|
||||||
netsh interface ipv4 add address %NETIF% 192.168.139.12 255.255.255.0 store=active
|
netsh interface ipv4 add address %NETIF% 192.168.139.12 255.255.255.0 store=active
|
||||||
netsh interface ipv4 add address %NETIF% 192.168.139.13 255.255.255.0 store=active
|
netsh interface ipv4 add address %NETIF% 192.168.139.13 255.255.255.0 store=active
|
||||||
|
40
package-lock.json
generated
40
package-lock.json
generated
@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "chunichunimatch",
|
|
||||||
"lockfileVersion": 2,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"dependencies": {
|
|
||||||
"ws": "^8.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ws": {
|
|
||||||
"version": "8.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
|
|
||||||
"integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": "^5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ws": {
|
|
||||||
"version": "8.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
|
|
||||||
"integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
|
|
||||||
"requires": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
76
server.js
76
server.js
@ -2,6 +2,8 @@ const cfg = require('./config');
|
|||||||
const ws = require('ws');
|
const ws = require('ws');
|
||||||
const enc = require('./ccencap');
|
const enc = require('./ccencap');
|
||||||
|
|
||||||
|
console.log('ShadowTenpo Route server ver. ' + cfg.version);
|
||||||
|
|
||||||
var server = new ws.WebSocketServer({
|
var server = new ws.WebSocketServer({
|
||||||
port: cfg.server_port,
|
port: cfg.server_port,
|
||||||
perMessageDeflate: false
|
perMessageDeflate: false
|
||||||
@ -12,8 +14,16 @@ var occupancy = [0, 0, 0, 0];
|
|||||||
var suffixes = [11, 12, 13, 14];
|
var suffixes = [11, 12, 13, 14];
|
||||||
var clients = {};
|
var clients = {};
|
||||||
|
|
||||||
function allocateClient() {
|
function allocateClient(req) {
|
||||||
var slot = -1;
|
var slot;
|
||||||
|
if (req.pref_ip) {
|
||||||
|
slot = suffixes.indexOf(req.pref_ip);
|
||||||
|
if (slot >= 0 && !occupancy[slot]) {
|
||||||
|
occupancy[slot] = 1;
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slot = -1;
|
||||||
for (var i = 0; i < occupancy.length; i++) {
|
for (var i = 0; i < occupancy.length; i++) {
|
||||||
if (!occupancy[i]) {
|
if (!occupancy[i]) {
|
||||||
slot = i;
|
slot = i;
|
||||||
@ -25,46 +35,68 @@ function allocateClient() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server.on('connection', function(conn, req) {
|
server.on('connection', function(conn, req) {
|
||||||
var slot = allocateClient();
|
var login = null;
|
||||||
var realip = req.socket.remoteAddress;
|
|
||||||
if (slot < 0) {
|
|
||||||
console.log('Dropping ' + realip + ' due to no party vacancy');
|
|
||||||
conn.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var suffix = suffixes[slot];
|
|
||||||
clients[suffix] = conn;
|
|
||||||
console.log("[Client #" + slot + "] Allocated. Welcome " + realip);
|
|
||||||
conn.on('message', function(data) {
|
conn.on('message', function(data) {
|
||||||
var msg = enc.decode(data);
|
var msg = enc.decode(data);
|
||||||
switch(msg.op) {
|
switch(msg.op) {
|
||||||
case 'party_hello':
|
case 'party_hello':
|
||||||
sendMemberChange();
|
login = registerUser(conn, req, msg);
|
||||||
break;
|
break;
|
||||||
case 'net_tcp':
|
case 'net_tcp':
|
||||||
case 'net_udp':
|
case 'net_udp':
|
||||||
|
case 'net_tcpfin':
|
||||||
|
case 'net_tcpclose':
|
||||||
var dstconn = clients[msg.dst];
|
var dstconn = clients[msg.dst];
|
||||||
if (!dstconn) {
|
if (dstconn) {
|
||||||
console.warn('Invalid dst: ' + msg.src + '->' + msg.dst);
|
|
||||||
} else {
|
|
||||||
dstconn.send(data);
|
dstconn.send(data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
conn.on('close', function() {
|
conn.on('close', function() {
|
||||||
console.log("[Client #" + slot + "] Dropped.");
|
if (login) {
|
||||||
delete clients[suffix];
|
console.log("[Client #" + login.slot + "] Bye.");
|
||||||
occupancy[slot] = 0;
|
delete clients[login.suffix];
|
||||||
|
occupancy[login.slot] = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
conn.on('error', function(e) {
|
conn.on('error', function(e) {
|
||||||
console.log("[Client #" + slot + "] Error ", e);
|
|
||||||
conn.close();
|
conn.close();
|
||||||
delete clients[suffix];
|
if (login) {
|
||||||
occupancy[slot] = 0;
|
console.log("[Client #" + login.slot + "] Error. Dropped.");
|
||||||
|
delete clients[login.suffix];
|
||||||
|
occupancy[login.slot] = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function registerUser(conn, req, msg) {
|
||||||
|
// Validate login
|
||||||
|
if (msg.version != cfg.version) {
|
||||||
|
// DROP!
|
||||||
|
conn.send(enc.encodeTextMessage("内鬼禁止. Server is at " + cfg.version), () => {
|
||||||
|
conn.close();
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate User
|
||||||
|
var slot = allocateClient(req);
|
||||||
|
var realip = req.socket.remoteAddress;
|
||||||
|
if (slot < 0) {
|
||||||
|
console.log('Dropping ' + realip + ' due to no party vacancy');
|
||||||
|
conn.send(enc.encodeTextMessage("Sorry, party is full."), () => {
|
||||||
|
conn.close();
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var suffix = suffixes[slot];
|
||||||
|
clients[suffix] = conn;
|
||||||
|
console.log("[Client #" + slot + "] Allocated. Welcome " + realip);
|
||||||
|
sendMemberChange();
|
||||||
|
return {suffix, slot};
|
||||||
|
}
|
||||||
|
|
||||||
function sendMemberChange() {
|
function sendMemberChange() {
|
||||||
// var members = suffixes.filter((suffix, slot) => occupancy[slot]);
|
// var members = suffixes.filter((suffix, slot) => occupancy[slot]);
|
||||||
// Pre-allocate all members on the client to prevent late joiner crash
|
// Pre-allocate all members on the client to prevent late joiner crash
|
||||||
|
43
test/client.js
Normal file
43
test/client.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
var net = require('net');
|
||||||
|
var dgram = require('dgram');
|
||||||
|
|
||||||
|
var myip = "192.168.139.12";
|
||||||
|
|
||||||
|
var udp50200 = dgram.createSocket('udp4');
|
||||||
|
udp50200.on('message', function(msg, rinfo) {
|
||||||
|
var baseip = msg.toString('ascii');
|
||||||
|
if (!tcpclient) {
|
||||||
|
console.log("Sending TCP Hello to " + baseip);
|
||||||
|
tcphello(baseip);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
udp50200.on('error', function(err) {
|
||||||
|
console.log("Err: ", err);
|
||||||
|
});
|
||||||
|
udp50200.on('listening', function() {
|
||||||
|
console.log('udp50200 listening');
|
||||||
|
})
|
||||||
|
udp50200.bind({port:50200,address:myip});
|
||||||
|
|
||||||
|
var tcpclient = null;
|
||||||
|
function tcphello(host) {
|
||||||
|
if (tcpclient) {
|
||||||
|
console.log("TCP client in use.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tcpclient = new net.Socket();
|
||||||
|
tcpclient.on('data', function(msg) {
|
||||||
|
console.log("tcpclient RX: ", msg.toString('ascii'));
|
||||||
|
});
|
||||||
|
tcpclient.on('end', function() {
|
||||||
|
console.log('tcpclient end');
|
||||||
|
});
|
||||||
|
tcpclient.on('close', function() {
|
||||||
|
console.log('tcpclient close');
|
||||||
|
tcpclient = null;
|
||||||
|
});
|
||||||
|
tcpclient.connect(50200, host, function() {
|
||||||
|
console.log("tcpclient connected");
|
||||||
|
tcpclient.write("FNNC");
|
||||||
|
});
|
||||||
|
}
|
43
test/server.js
Normal file
43
test/server.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Set base configuration here
|
||||||
|
var net = require('net');
|
||||||
|
var dgram = require('dgram');
|
||||||
|
|
||||||
|
var myip = "192.168.139.11";
|
||||||
|
|
||||||
|
// UDP50200 Announce
|
||||||
|
var udpbc = dgram.createSocket('udp4');
|
||||||
|
udpbc.bind(function() {
|
||||||
|
udpbc.setBroadcast(true);
|
||||||
|
console.log('Start udp50200 broadcast');
|
||||||
|
setInterval(function() {
|
||||||
|
// 50200 UDP Broadcast
|
||||||
|
udpbc.send(myip, 50200, '192.168.139.255');
|
||||||
|
// console.log('announce');
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TCP50200 Base Server
|
||||||
|
var tcp50200 = net.createServer();
|
||||||
|
tcp50200.on('connection', function(conn) {
|
||||||
|
console.log('tcp50200 accept ' + conn.remoteAddress);
|
||||||
|
conn.on('data', function(buf) {
|
||||||
|
console.log("tcp50200("+conn.remoteAddress+"): " + buf.toString('ascii'));
|
||||||
|
for (var i = 0; i < buf.length; i++)
|
||||||
|
buf[i]++;
|
||||||
|
conn.write(buf);
|
||||||
|
});
|
||||||
|
conn.on('close', function() {
|
||||||
|
console.log("tcp50200("+conn.remoteAddress+") bye");
|
||||||
|
if (ping_interval) {
|
||||||
|
clearInterval(ping_interval);
|
||||||
|
ping_interval = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
tcp50200.listen({host: myip, port: 50200}, function(e) {
|
||||||
|
if (e) {
|
||||||
|
console.log('tcp50200 listen:', e);
|
||||||
|
}
|
||||||
|
console.log('tcp50200 listen');
|
||||||
|
});
|
94
webmgr.js
Normal file
94
webmgr.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
const http = require('http');
|
||||||
|
const url = require('url');
|
||||||
|
const fs = require('fs');
|
||||||
|
const ws = require('ws');
|
||||||
|
|
||||||
|
const mimetypes = require('./mime');
|
||||||
|
|
||||||
|
function WebManager(port) {
|
||||||
|
const server = http.createServer();
|
||||||
|
const wss = new ws.WebSocketServer({ noServer: true });
|
||||||
|
|
||||||
|
this.clients = [];
|
||||||
|
this.clientcnt = 0;
|
||||||
|
|
||||||
|
wss.on('connection', (ws, request) => {
|
||||||
|
var clientid = this.clientcnt;
|
||||||
|
this.clientcnt++;
|
||||||
|
this.clients.push(ws);
|
||||||
|
ws.on('message', msg => {
|
||||||
|
console.log(msg.toString('utf-8'));
|
||||||
|
});
|
||||||
|
ws.on('close', () => {
|
||||||
|
this.clients[clientid] = false;
|
||||||
|
});
|
||||||
|
ws.on('error', () => {
|
||||||
|
this.clients[clientid] = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('request', (req, rsp) => {
|
||||||
|
const uri = url.parse(req.url).pathname;
|
||||||
|
if (uri == '/') {
|
||||||
|
filename = 'www/index.html';
|
||||||
|
} else {
|
||||||
|
filename = 'www' + uri;
|
||||||
|
}
|
||||||
|
var ext = filename.match(/\.\w+$/);
|
||||||
|
var mime = null;
|
||||||
|
if (ext) {
|
||||||
|
mime = mimetypes[ext[0]];
|
||||||
|
}
|
||||||
|
if (!mime) {
|
||||||
|
mime = 'application/octet-stream';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(filename)) {
|
||||||
|
rsp.writeHead(404);
|
||||||
|
rsp.end('Not Found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var stat = fs.statSync(filename);
|
||||||
|
if (!stat) {
|
||||||
|
rsp.writeHead(403);
|
||||||
|
rsp.end('Forbidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rsp.writeHead(200, {
|
||||||
|
'Content-Length': stat.size,
|
||||||
|
'Content-Type': mime,
|
||||||
|
});
|
||||||
|
rsp.end(fs.readFileSync(filename));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
rsp.writeHead(500);
|
||||||
|
rsp.end(e.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('upgrade', (request, socket, head) => {
|
||||||
|
const uri = url.parse(request.url).pathname;
|
||||||
|
|
||||||
|
if (uri == '/api') {
|
||||||
|
wss.handleUpgrade(request, socket, head, ws => {
|
||||||
|
wss.emit('connection', ws, request);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebManager.prototype.log = function(msg) {
|
||||||
|
this.clients.forEach(ws => {
|
||||||
|
if (!ws) return;
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
t: 'log',
|
||||||
|
m: msg
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = WebManager;
|
43
www/index.html
Normal file
43
www/index.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>CHUNICHUNIMatch</title>
|
||||||
|
<style type="text/css">
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var socket = new WebSocket("ws://"+location.host+"/api");
|
||||||
|
socket.addEventListener("message", function (e) {
|
||||||
|
var msg = JSON.parse(e.data);
|
||||||
|
switch(msg.t) {
|
||||||
|
case "log":
|
||||||
|
addlog(msg.m);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var autoscrollen = 1;
|
||||||
|
function addlog(msg) {
|
||||||
|
var line = document.createElement("pre");
|
||||||
|
line.innerText = msg;
|
||||||
|
document.getElementById("logcat").appendChild(line);
|
||||||
|
if (autoscrollen) {
|
||||||
|
line.scrollIntoView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function autoscroll(value) {
|
||||||
|
autoscrollen = value;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>Logcat</h3>
|
||||||
|
<button onclick="autoscroll(1)">Auto scroll</button>
|
||||||
|
<div id="logcat"></div>
|
||||||
|
<button onclick="autoscroll(0)">No scroll</button>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user