Init
parent
6e0224102b
commit
1a8c5c72d7
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,375 @@
|
|||
const commandLineArgs = require('command-line-args'),
|
||||
commandLineUsage = require('command-line-usage'),
|
||||
https = require('https'),
|
||||
qstr = require('querystring'),
|
||||
url = require('url'),
|
||||
fs = require('fs'),
|
||||
pbjs = require("protobufjs/cli/pbjs"),
|
||||
WebSocketClient = require('websocket').client,
|
||||
MS_HOST = 'mahjongsoul.game.yo-star.com',
|
||||
opts = [{
|
||||
name: 'help',
|
||||
alias: 'h',
|
||||
description: 'Display this usage guide',
|
||||
type: Boolean
|
||||
}, {
|
||||
name: 'servers',
|
||||
alias: 'l',
|
||||
description: 'Get server list',
|
||||
type: Boolean
|
||||
}, {
|
||||
name: 'server',
|
||||
alias: 's',
|
||||
description: 'Use custom server within check',
|
||||
type: String
|
||||
}, {
|
||||
name: 'trueserver',
|
||||
alias: 'S',
|
||||
description: 'Use custom server without check',
|
||||
type: String
|
||||
}, {
|
||||
name: 'update',
|
||||
alias: 'u',
|
||||
description: 'Update protocol',
|
||||
type: Boolean
|
||||
}, {
|
||||
name: 'info',
|
||||
alias: 'i',
|
||||
description: 'Get info',
|
||||
type: Boolean
|
||||
}, {
|
||||
name: 'docs',
|
||||
alias: 'd',
|
||||
description: 'Generate documentation from liqi.json',
|
||||
type: Boolean
|
||||
}],
|
||||
sections = [{
|
||||
header: 'Mahjong Soul RPC client',
|
||||
content: 'RPC client for Mahjong Soul API'
|
||||
}, {
|
||||
header: 'Options',
|
||||
optionList: opts
|
||||
}],
|
||||
args = commandLineArgs(opts);
|
||||
let ProtoBuf = require("protobufjs");
|
||||
|
||||
const getServers = (serv = null, nocheck = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (serv && nocheck) {
|
||||
resolve([serv]);
|
||||
} else {
|
||||
let ms_opts = {
|
||||
hostname: MS_HOST,
|
||||
port: 443,
|
||||
path: '/version.json',
|
||||
method: 'GET'
|
||||
};
|
||||
// Запрос версии
|
||||
const reqVersion = https.request(ms_opts, (resVersion) => {
|
||||
resVersion.on('data', (dataVersion) => {
|
||||
let resVers = JSON.parse(dataVersion);
|
||||
//console.log('Version:', JSON.stringify(resVers, null, 2));
|
||||
ms_opts.path = `/v${resVers.version}/config.json`;
|
||||
// Запрос конфигурации
|
||||
const reqConfig = https.request(ms_opts, (resConfig) => {
|
||||
resConfig.on('data', (dataConfig) => {
|
||||
let resCfg = JSON.parse(dataConfig);
|
||||
//console.log('Config:', JSON.stringify(resCfg, null, 2));
|
||||
const mainland = url.parse(resCfg.ip[0].region_urls[0]);
|
||||
ms_opts.hostname = mainland.hostname;
|
||||
ms_opts.port = mainland.port;
|
||||
ms_opts.data = {
|
||||
service: 'ws-gateway',
|
||||
protocol: 'ws',
|
||||
ssl: 'true'
|
||||
};
|
||||
ms_opts.path = `${mainland.pathname}?${qstr.stringify(ms_opts.data)}`;
|
||||
// Запрос серверов
|
||||
const reqServers = https.request(ms_opts, (resServers) => {
|
||||
resServers.on('data', (dataServers) => {
|
||||
let resServs = JSON.parse(dataServers);
|
||||
//console.log('Servers:', JSON.stringify(resServs, null, 2));
|
||||
if (serv && !nocheck) {
|
||||
if (resServs.servers.indexOf(serv) >= 0) {
|
||||
resolve([serv]);
|
||||
} else {
|
||||
reject(`Wrong server: ${serv}`);
|
||||
}
|
||||
} else {
|
||||
resolve(resServs.servers);
|
||||
}
|
||||
});
|
||||
});
|
||||
reqServers.on('error', (e) => {
|
||||
reject(`Error: ${e.message}`);
|
||||
});
|
||||
reqServers.end();
|
||||
});
|
||||
});
|
||||
reqConfig.on('error', (e) => {
|
||||
reject(`Error: ${e.message}`);
|
||||
});
|
||||
reqConfig.end();
|
||||
});
|
||||
});
|
||||
reqVersion.on('error', (e) => {
|
||||
reject(`Error: ${e.message}`);
|
||||
});
|
||||
reqVersion.end();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// version URL https://mahjongsoul.game.yo-star.com/version.json
|
||||
const getLastVersion = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let ms_opts = {
|
||||
hostname: MS_HOST,
|
||||
port: 443,
|
||||
path: '/version.json',
|
||||
method: 'GET'
|
||||
};
|
||||
const reqVersion = https.request(ms_opts, (resVersion) => {
|
||||
let dataVersion = '';
|
||||
resVersion.on('data', (chunk) => {
|
||||
dataVersion += chunk;
|
||||
});
|
||||
resVersion.on('end', () => {
|
||||
let resVers = JSON.parse(dataVersion);
|
||||
resolve(resVers.version);
|
||||
});
|
||||
});
|
||||
reqVersion.on('error', (e) => {
|
||||
reject(`Error: ${e.message}`);
|
||||
});
|
||||
reqVersion.end();
|
||||
});
|
||||
};
|
||||
|
||||
const sendRequest = (server, package_name, service_name, method_name, params, idx = 1) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proto = ProtoBuf.loadSync('liqi.json'),
|
||||
client = new WebSocketClient(),
|
||||
service = proto.lookup(`${package_name}.${service_name}`),
|
||||
method = service.methods[method_name].resolve(),
|
||||
req = method.resolvedRequestType,
|
||||
res = method.resolvedResponseType,
|
||||
wrapper = proto.lookupType(`${package_name}.Wrapper`);
|
||||
let req_str = wrapper.create({
|
||||
//name: `${req.name}`,
|
||||
name: `${package_name}.${req.name}`,
|
||||
//data: params
|
||||
data: req.encode(params).finish()
|
||||
});
|
||||
let meth_str = wrapper.create({
|
||||
name: `${package_name}.${service_name}.${method_name}`,
|
||||
data: wrapper.encode(params).finish()
|
||||
//data: wrapper.encode(req_str).finish()
|
||||
});
|
||||
let msg = wrapper.encode(meth_str).finish(),
|
||||
pkt_type = [Number(2)],
|
||||
idx_bytes = to_bytes(idx);
|
||||
let header = pkt_type.concat(idx_bytes),
|
||||
pkt = Buffer.concat([Buffer.from(header), Buffer.from(msg)]);
|
||||
console.log(`method: .${package_name}.${service_name}.${method_name}`);
|
||||
console.log(`request: ${JSON.stringify(params, null, 2)}`);
|
||||
client.on('connect', function(connection) {
|
||||
console.log('WebSocket Client Connected');
|
||||
connection.on('error', function(error) {
|
||||
reject(error.toString());
|
||||
});
|
||||
connection.on('close', function() {
|
||||
console.log('Connection Closed');
|
||||
});
|
||||
connection.on('message', function(message) {
|
||||
console.log(message);
|
||||
if (message.type === 'binary') {
|
||||
let r = message.binaryData.slice(3),
|
||||
msg = res.decode(r);
|
||||
resolve(msg);
|
||||
connection.close(connection.CLOSE_REASON_NORMAL, 'Close');
|
||||
}
|
||||
});
|
||||
const sendPacket = (packet) => {
|
||||
if (connection.connected) {
|
||||
console.log('Send data: ', packet.toString('hex'));
|
||||
connection.sendBytes(packet);
|
||||
}
|
||||
};
|
||||
sendPacket(pkt);
|
||||
});
|
||||
client.connect(`wss://${server}`);
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
const random = (arr) => {
|
||||
return arr[Math.floor((Math.random() * arr.length))];
|
||||
};
|
||||
|
||||
const to_bytes = (x) => {
|
||||
return [(x & 255), x >> 8];
|
||||
};
|
||||
|
||||
if (!args || args.help) {
|
||||
console.log(commandLineUsage(sections));
|
||||
} else {
|
||||
if (args.update) {
|
||||
console.log('Protocol update:');
|
||||
getLastVersion().then(
|
||||
resGameVersion => {
|
||||
console.log(` Game version: ${resGameVersion}`);
|
||||
// Resource version URL https://mahjongsoul.game.yo-star.com/resversion(version).json
|
||||
const resvers_opts = {
|
||||
hostname: MS_HOST,
|
||||
port: 443,
|
||||
path: `/resversion${resGameVersion}.json`,
|
||||
method: 'GET'
|
||||
};
|
||||
const reqVersion = https.request(resvers_opts, (resVersion) => {
|
||||
let dataProto = '';
|
||||
resVersion.on('data', (chunk) => {
|
||||
dataProto += chunk;
|
||||
});
|
||||
resVersion.on('end', () => {
|
||||
const resJSON = JSON.parse(dataProto),
|
||||
liqiVersion = resJSON.res['res/proto/liqi.json'].prefix;
|
||||
let res_list = '';
|
||||
Object.keys(resJSON.res).forEach((res_id) => {
|
||||
res_list += `https://${MS_HOST}/${resJSON.res[res_id].prefix}/${res_id}\n`;
|
||||
});
|
||||
fs.writeFileSync(`${__dirname}/res_list.txt`, res_list);
|
||||
console.log(' Save res_list.txt');
|
||||
console.log(` Liqi version: ${liqiVersion}`);
|
||||
// Resource version URL https://mahjongsoul.game.yo-star.com/(versionLiqi)/res/proto/liqi.json
|
||||
const proto_opts = {
|
||||
hostname: MS_HOST,
|
||||
port: 443,
|
||||
path: `/${liqiVersion}/res/proto/liqi.json`,
|
||||
method: 'GET'
|
||||
};
|
||||
const reqProto = https.request(proto_opts, (resProto) => {
|
||||
let dataProto = '';
|
||||
resProto.on('data', (chunk) => {
|
||||
dataProto += chunk;
|
||||
});
|
||||
resProto.on('end', () => {
|
||||
let resJSON = JSON.parse(dataProto);
|
||||
fs.writeFileSync(`${__dirname}/liqi.json`, JSON.stringify(resJSON, null, 2));
|
||||
console.log(' Save liqi.json');
|
||||
// Если нужен proto раскомментить
|
||||
/*
|
||||
pbjs.main(['-t', 'proto3', `${__dirname}/liqi.json`, '-o', `${__dirname}/liqi.proto`], function(err, output) {
|
||||
if (err) throw err;
|
||||
console.log(' Save to proto');
|
||||
});
|
||||
*/
|
||||
});
|
||||
});
|
||||
reqProto.on('error', (e) => {
|
||||
console.log(`Error: ${e.message}`);
|
||||
});
|
||||
reqProto.end();
|
||||
|
||||
});
|
||||
});
|
||||
reqVersion.on('error', (e) => {
|
||||
console.log(`Error: ${e.message}`);
|
||||
});
|
||||
reqVersion.end();
|
||||
},
|
||||
errGameVersion => {
|
||||
console.log(`Error: ${errGameVersion}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
if (args.servers) {
|
||||
getServers().then(
|
||||
resServer => {
|
||||
console.log('Server list:');
|
||||
for (let i in resServer) {
|
||||
console.log(` ${resServer[i]}`);
|
||||
}
|
||||
console.log(`Random server: ${random(resServer)}`);
|
||||
},
|
||||
errServer => {
|
||||
console.error(errServer);
|
||||
}
|
||||
);
|
||||
}
|
||||
if (args.info) {
|
||||
if (!fs.existsSync(`${__dirname}/liqi.json`)) {
|
||||
console.log('Protocol file not found. Run update with option --update');
|
||||
} else {
|
||||
getServers((args.server ? args.server : args.trueserver), args.trueserver).then(
|
||||
resServer => {
|
||||
let server = random(resServer);
|
||||
if (server) {
|
||||
console.log(`Use server: ${server}`);
|
||||
let idx = Math.floor(60007 * Math.random()),
|
||||
params = {
|
||||
//account_id: 117411310
|
||||
code: '526091',
|
||||
operation: 0
|
||||
};
|
||||
//sendRequest(server, 'lq', 'Lobby', 'fetchConnectionInfo', params, idx).then(
|
||||
sendRequest(server, 'lq', 'Lobby', 'verfifyCodeForSecure', params, idx).then(
|
||||
resSend => {
|
||||
console.log(`Result: ${JSON.stringify(resSend, null, 2)}`);
|
||||
},
|
||||
errSend => {
|
||||
console.error(`Error: ${errSend}`);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.error(`Wrong server: ${args.server}`);
|
||||
}
|
||||
},
|
||||
errServer => {
|
||||
console.error(errServer);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
if (args.docs) {
|
||||
if (!fs.existsSync(`${__dirname}/liqi.json`)) {
|
||||
console.log('Protocol file not found. Run update with option --update');
|
||||
} else {
|
||||
const liqi = require(`${__dirname}/liqi.json`),
|
||||
types = new RegExp(/uint32|bool|string|int32/);
|
||||
let doc = "# MahjongSoul protocol documentation\n\n"
|
||||
items = liqi.nested.lq.nested;
|
||||
for (const [key, item] of Object.entries(items)) {
|
||||
doc += `## ${key}\n\n`;
|
||||
if (item.fields) {
|
||||
let fields = [];
|
||||
for (let [name, field] of Object.entries(item.fields)) {
|
||||
fields[field.id] = {
|
||||
name: name,
|
||||
type: field.type
|
||||
};
|
||||
}
|
||||
if (fields.length > 0) {
|
||||
doc += "|#|Field name|Field type|\n|:-:|-|-|\n";
|
||||
for (let i = 1; i <= fields.length; i++) {
|
||||
if (fields[i]) {
|
||||
doc += `|${i}|${fields[i].name}|${(types.test(fields[i].type) ? fields[i].type : '['+fields[i].type+'](#'+fields[i].type+')')}|\n`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
doc += "No fields\n";
|
||||
}
|
||||
doc += "\n";
|
||||
} else if (item.methods) {
|
||||
const methods = item.methods;
|
||||
for (const [key, method] of Object.entries(methods)) {
|
||||
doc += `* ${key}([${method.responseType}](#${method.responseType}) return [${method.requestType}](#${method.requestType})\n`;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(`${__dirname}/liqi.md`, doc);
|
||||
console.log('Save liqi.md');
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "ms",
|
||||
"version": "1.0.0",
|
||||
"description": "Mahjong Soul RPC client",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bytebuffer": "^5.0.1",
|
||||
"command-line-args": "^5.1.1",
|
||||
"command-line-usage": "^6.0.2",
|
||||
"protobufjs": "^6.8.8",
|
||||
"websocket": "^1.0.30"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue