Init
This commit is contained in:
		
							
								
								
									
										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
											
										
									
								
							
		Reference in New Issue
	
	Block a user