Init
This commit is contained in:
parent
6e0224102b
commit
1a8c5c72d7
139421
code.js
Executable file
139421
code.js
Executable file
File diff suppressed because one or more lines are too long
375
index.js
Normal file
375
index.js
Normal file
@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3999
liqi.proto
Normal file
3999
liqi.proto
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
9767
res_list.txt
Normal file
9767
res_list.txt
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user