diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 5e6d704..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: 2 - -jobs: - build: - parallelism: 1 - docker: - - image: circleci/node:10 - steps: - - checkout - - restore_cache: - key: dependency-cache-{{ checksum "package.json" }} - - run: sudo apt-get install libpcap-dev - - run: npm i - - save_cache: - key: dependency-cache-{{ checksum "package.json" }} - paths: - - ./node_modules - - run: npm test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bbeb50a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [14.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + run: npm test \ No newline at end of file diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..9d178bf --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,25 @@ +name: npm-publish +on: + push: + branches: + - master # Change this to your default branch +jobs: + npm-publish: + name: npm-publish + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@master + - name: Set up Node.js + uses: actions/setup-node@master + with: + node-version: 10.0.0 + - name: Publish if version has been updated + uses: pascalgn/npm-publish-action@4f4bf159e299f65d21cd1cbd96fc5d53228036df + with: # All of theses inputs are optional + tag_name: "%s" + tag_message: "%s" + commit_pattern: "^Release (\\S+)" + env: # More info about the environment variables in the README + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this as is, it's automatically generated + NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} # You need to set this in your repo settings \ No newline at end of file diff --git a/README.md b/README.md index 213601c..1c91de3 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,78 @@ -# diablo2-protocol -[![NPM version](https://img.shields.io/npm/v/diablo2-protocol.svg)](http://npmjs.com/package/diablo2-protocol) -[![Build Status](https://img.shields.io/circleci/project/MephisTools/diablo2-protocol/master.svg)](https://circleci.com/gh/MephisTools/diablo2-protocol) -[![Discord Chat](https://img.shields.io/badge/discord-here-blue.svg)](https://discord.gg/9RqtApv) -[![Try it on gitpod](https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg)](https://gitpod.io/#https://github.com/MephisTools/diablo2-protocol) - - -Network protocol for diablo 2 : create client and servers for diablo 1.13 and 1.14. -[Bot example](https://www.youtube.com/watch?v=KYPTijLiwMI&feature=youtu.be) - -[Sniffer example](https://www.youtube.com/watch?v=R5yfRTR3-mY) +# diablo2-protocol + + +[![Contributors][contributors-shield]][contributors-url] +[![Forks][forks-shield]][forks-url] +[![Stargazers][stars-shield]][stars-url] +[![Issues][issues-shield]][issues-url] +[![MIT License][license-shield]][license-url] +[![NPM version](https://img.shields.io/npm/v/diablo2-protocol.svg)](npm-url) +[![Build Status](https://github.com/Mephistools/diablo2-protocol/workflows/CI/badge.svg)](build-url) +[![Discord](https://img.shields.io/badge/chat-on%20discord-brightgreen.svg)](discord-url) +[![Try it on gitpod](https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg)](gitpod-url) + + +
+

+ + Logo + + +

diablo2-protocol

+ +

+ Network protocol for diablo 2 : create client and servers for diablo 1.13 and 1.14. +
+ Explore the docs » +
+
+ View Demo + · + Report Bug + · + Request Feature +

+

+ + +## Table of Contents + +* [About the Project](#about-the-project) + * [Built With](#built-with) +* [Getting Started](#getting-started) + * [Prerequisites](#prerequisites) + * [Installation](#installation) +* [Usage](#usage) +* [Projects using diablo2-protocol](#projects-using-diablo2-protocol) +* [Documentation](#documentation) +* [Roadmap](#roadmap) +* [Contributing](#contributing) +* [License](#license) +* [Acknowledgements](#acknowledgements) + +## Built With + +* +* + + +## Getting Started + +### Prerequisites + +```sh +npm install npm@latest -g +``` -## Installation +### Installation -``` +```js npm install diablo2-protocol ``` + ## Usage Follow bot in a few lines @@ -50,52 +105,64 @@ start() ``` -See docs/API.md +## Projects using diablo2-protocol -Follow bot example +* [mephistools-sniffer](https://github.com/MephisTools/mephistools-sniffer) diablo 2 packet sniffing. +* [diablo2-live-viewer](https://github.com/MephisTools/diablo2-live-viewer) web map hack, packets table and inventory. +* [AutoTathamet](https://github.com/MephisTools/AutoTathamet) high level API to build bots for example -``` -node examples/simpleBot.js [-h] [-v] -au USERNAME -ap PASSWORD -c CHARACTER -gn \ - GAMENAME -gp GAMEPASSWORD -gs GAMESERVER -s SIDSERVER \ - [-dv DIABLOVERSION] -k1 KEYCLASSIC -k2 KEYEXTENSION -``` - -Sniffer (Linux / MacOS only) - -``` -cd example/sniffer -npm install -sudo node sniffer.js -``` - -## Projects using diablo-protocol - -* [diablo2-live-viewer](https://github.com/MephisTools/diablo2-live-viewer) displaying a live diablo map and live packets table -* [AutoTathamet](https://github.com/MephisTools/AutoTathamet) Create Diablo2 bots with a powerful, stable, and high level JavaScript API. +## Documentation +* + ## Roadmap -- [ ] Test all packets -- [x] Sniffer -- [x] more documentation -- [ ] Proxy ? -- [ ] More examples -- [ ] Web / mobile interface - -## Docs - -### Diablo - -* doc of diablo2 implementations https://github.com/MephisTools/diablo2-protocol/wiki/Diablo-2-implementations-and-docs -* packets general description http://www.blizzhackers.cc/viewtopic.php?f=182&t=444264 -* dump of a normal connection sequence http://www.blizzhackers.cc/viewtopic.php?t=406445 -* index of packets https://bnetdocs.org/packet/index -* example of packet doc https://bnetdocs.org/packet/146 -* basic example of packet parsing in python of a diablo2 packet https://gist.github.com/rom1504/8d2824d9d89dbd8b991b102696a1321e - -### Libs - -* node basic client implementation https://nodejs.org/api/net.html#net_net_createconnection_options_connectlistener -* protodef js implementation doc https://github.com/ProtoDef-io/node-protodef https://github.com/ProtoDef-io/node-protodef/blob/master/doc/api.md and https://github.com/ProtoDef-io/node-protodef/blob/master/example.js -* protodef types https://github.com/ProtoDef-io/ProtoDef/blob/master/doc/datatypes.md -* nodepcap for sniffing https://github.com/node-pcap/node_pcap \ No newline at end of file + +See the [open issues](https://github.com/Mephistools/diablo2-protocol/issues) for a list of proposed features (and known issues). + + +## Contributing + +Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + + +## License + +Distributed under the MIT License. See `LICENSE` for more information. + + + +## Acknowledgements + +* [GitHub Emoji Cheat Sheet](https://www.webpagefx.com/tools/emoji-cheat-sheet) +* [Img Shields](https://shields.io) +* [Choose an Open Source License](https://choosealicense.com) +* [GitHub Pages](https://pages.github.com) + + + +[contributors-shield]: https://img.shields.io/github/contributors/Mephistools/diablo2-protocol.svg?style=flat-square +[contributors-url]: https://github.com/Mephistools/diablo2-protocol/graphs/contributors +[forks-shield]: https://img.shields.io/github/forks/Mephistools/diablo2-protocol.svg?style=flat-square +[forks-url]: https://github.com/Mephistools/diablo2-protocol/network/members +[stars-shield]: https://img.shields.io/github/stars/Mephistools/diablo2-protocol.svg?style=flat-square +[stars-url]: https://github.com/Mephistools/diablo2-protocol/stargazers +[issues-shield]: https://img.shields.io/github/issues/Mephistools/diablo2-protocol.svg?style=flat-square +[issues-url]: https://github.com/Mephistools/diablo2-protocol/issues +[license-shield]: https://img.shields.io/github/license/Mephistools/diablo2-protocol.svg?style=flat-square +[license-url]: https://github.com/Mephistools/diablo2-protocol/blob/master/LICENSE.txt +[product-screenshot]: images/screenshot.png +[npm-shield]: https://img.shields.io/npm/v/diablo2-protocol.svg +[npm-url]: http://npmjs.com/package/diablo2-protocol +[build-shield]: https://github.com/Mephistools/diablo2-protocol/workflows/CI/badge.svg +[build-url]: https://github.com/Mephistools/diablo2-protocol/actions?query=workflow%3A%22CI%22 +[discord-shield]: https://img.shields.io/badge/chat-on%20discord-brightgreen.svg +[discord-url]: https://discord.gg/9RqtApv +[gitpod-shield]: https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg +[gitpod-url]: https://gitpod.io/#https://github.com/MephisTools/diablo2-protocol diff --git a/data/1.13/d2gs.json b/data/1.13/d2gs.json index ee3db24..8829c61 100644 --- a/data/1.13/d2gs.json +++ b/data/1.13/d2gs.json @@ -878,6 +878,7 @@ } ]], "D2GS_GAMEEXIT": ["container", []], + "D2GS_UNKNOWN3": ["container", []], "packet": [ "container", [ @@ -930,6 +931,7 @@ "0x28": "D2GS_INSERTSOCKETITEM", "0x29": "D2GS_SCROLLTOTOME", "0x2A": "D2GS_ITEMTOCUBE", + "0x2B": "D2GS_UNKNOWN3", "0x2D": "D2GS_UNSELECTOBJ", "0x2E": "D2GS_CHATUNK1", "0x2F": "D2GS_NPCINIT", @@ -1078,7 +1080,8 @@ "D2GS_SWITCHEQUIP": "D2GS_SWITCHEQUIP", "D2GS_RESURRECTMERC": "D2GS_RESURRECTMERC", "D2GS_INVENTORYTOBELT": "D2GS_INVENTORYTOBELT", - "D2GS_GAMEEXIT": "D2GS_GAMEEXIT" + "D2GS_GAMEEXIT": "D2GS_GAMEEXIT", + "D2GS_UNKNOWN3": "D2GS_UNKNOWN3" } } ] @@ -1298,6 +1301,10 @@ { "name": "ladder", "type": "lu8" + }, + { + "name": "unknown", + "type": "lu8" } ]], "D2GS_LOADSUCCESSFUL": ["container", []], @@ -2783,6 +2790,13 @@ } ]], "D2GS_UNKNOWN38": ["container", []], + "D2GS_UNKNOWN39": ["container", [ + {"name":"unknown","type" : ["array", {"count": 356, "type": "lu8" } ] } + ]], + "D2GS_UNKNOWN40": ["container", [ + {"name":"unknown","type" : ["array", {"count": 12, "type": "lu8" } ] } + ]], + "D2GS_UNKOWNFF": ["container", []], "packet": [ "container", [ @@ -2974,7 +2988,10 @@ "0xB2": "D2GS_UNKNOWN37", "0xB3": "D2GS_IPBAN", "0xB4": "D2GS_UNKNOWN38", - "0xB5": "D2GS_OVERHEAD" + "0xB5": "D2GS_OVERHEAD", + "0xD5": "D2GS_UNKNOWN39", + "0xF5": "D2GS_UNKNOWN40", + "0xFF": "D2GS_UNKOWNFF" } } ] @@ -3167,7 +3184,10 @@ "D2GS_UNKNOWN36": "D2GS_UNKNOWN36", "D2GS_UNKNOWN37": "D2GS_UNKNOWN37", "D2GS_IPBAN": "D2GS_IPBAN", - "D2GS_UNKNOWN38": "D2GS_UNKNOWN38" + "D2GS_UNKNOWN38": "D2GS_UNKNOWN38", + "D2GS_UNKNOWN39": "D2GS_UNKNOWN39", + "D2GS_UNKNOWN40": "D2GS_UNKNOWN40", + "D2GS_UNKOWNFF": "D2GS_UNKOWNFF" } } ] diff --git a/data/1.14/d2gs.json b/data/1.14/d2gs.json index 63bd19f..d536ef0 100644 --- a/data/1.14/d2gs.json +++ b/data/1.14/d2gs.json @@ -1298,6 +1298,10 @@ { "name": "ladder", "type": "lu8" + }, + { + "name": "unknown", + "type": "lu8" } ]], "D2GS_LOADSUCCESSFUL": ["container", []], @@ -2783,6 +2787,7 @@ } ]], "D2GS_UNKNOWN38": ["container", []], + "D2GS_UNKNOWN39": ["container", []], "D2GS_UNKOWNFF": ["container", []], "packet": [ "container", @@ -2976,6 +2981,7 @@ "0xB3": "D2GS_IPBAN", "0xB4": "D2GS_UNKNOWN38", "0xB5": "D2GS_OVERHEAD", + "0xD5": "D2GS_UNKNOWN39", "0xFF": "D2GS_UNKOWNFF" } } @@ -3170,6 +3176,7 @@ "D2GS_UNKNOWN37": "D2GS_UNKNOWN37", "D2GS_IPBAN": "D2GS_IPBAN", "D2GS_UNKNOWN38": "D2GS_UNKNOWN38", + "D2GS_UNKNOWN39": "D2GS_UNKNOWN39", "D2GS_UNKOWNFF": "D2GS_UNKOWNFF" } } diff --git a/docs/images/logo.png b/docs/images/logo.png new file mode 100644 index 0000000..8fd9100 Binary files /dev/null and b/docs/images/logo.png differ diff --git a/examples/simpleBot.js b/examples/simpleBot.js index ce1ca42..0d4c24e 100644 --- a/examples/simpleBot.js +++ b/examples/simpleBot.js @@ -1,8 +1,8 @@ const { createClientDiablo } = require('..') const { defaultVersion } = require('..') -var ArgumentParser = require('argparse').ArgumentParser -var parser = new ArgumentParser({ +const ArgumentParser = require('argparse').ArgumentParser +const parser = new ArgumentParser({ version: '1.4.1', addHelp: true, description: 'Simple bot' @@ -31,13 +31,11 @@ async function start () { keyExtension, delayPackets }) - clientDiablo.on('D2GS_PLAYERMOVE', ({ targetX, targetY }) => { - clientDiablo.write('D2GS_RUNTOLOCATION', { - x: targetX, - y: targetY - }) + clientDiablo.on('packet', packet => console.log(`packet: ${JSON.stringify(packet)}`)) + clientDiablo.on('log', log => console.log(`log: ${log}`)) + clientDiablo.on('error', err => { + console.log(`Error: message: ${JSON.stringify(err.message)}, raw: ${JSON.stringify(err.raw.toString('hex'))}`) }) - await clientDiablo.connect() await clientDiablo.selectCharacter(character) await clientDiablo.createGame(gameName, gamePassword, gameServer, 0) diff --git a/examples/sniffer/package.json b/examples/sniffer/package.json deleted file mode 100644 index d8f751f..0000000 --- a/examples/sniffer/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "sniffer", - "version": "0.0.0", - "private": true, - "dependencies": { - "express": "^4.16.4", - "pcap": "^2.1.0", - "pug": "^2.0.3", - "ws": "^6.1.2" - }, - "description": "A sniffer example" -} diff --git a/examples/sniffer/sniffer.js b/examples/sniffer/sniffer.js deleted file mode 100644 index 3fcb624..0000000 --- a/examples/sniffer/sniffer.js +++ /dev/null @@ -1,386 +0,0 @@ -const { supportedVersions, defaultVersion } = require('../..') - -if (process.argv.length !== 4) { - console.log('Usage : node sniffer.js ') - process.exit(1) -} - -// If the version correspond to a supported version else use default -const version = supportedVersions.find(v => v === process.argv[3]) ? process.argv[3] : defaultVersion - -/* -in a new chrome tab press f12 then do this : -const ws = new WebSocket('ws://localhost:8080') - -ws.onmessage = (message) => console.log(message.data) - */ - -const Parser = require('protodef').Parser -const networkInterface = process.argv[2] - -const pcap = require('pcap') - -const tcpTracker = new pcap.TCPTracker() - -const WebSocket = require('ws') - -const wss = new WebSocket.Server({ port: 8080 }) - -// Broadcast to all. -wss.broadcast = function broadcast (data) { - wss.clients.forEach(function each (client) { - if (client.readyState === WebSocket.OPEN) { - client.send(data) - } - }) -} - -const pcapSession = pcap.createSession(networkInterface, 'ip proto \\tcp') - -pcapSession.on('packet', function (rawPacket) { - const packet = pcap.decode.packet(rawPacket) - tcpTracker.track_packet(packet) -}) - -const FullPacketParser = require('protodef').Parser -const ProtoDef = require('protodef').ProtoDef - -const express = require('express') -const app = express() -// app.use(express.static(`${__dirname}/public`)) - -const { - protocol, - createSplitter, - decompress, - d2gsReader, - itemParser, - bitfieldLE -} = require('../..') - -const mcpToServer = new ProtoDef(false) -mcpToServer.addProtocol(protocol[version].mcp, ['toServer']) - -const mcpToClient = new ProtoDef(false) -mcpToClient.addProtocol(protocol[version].mcp, ['toClient']) - -const sidToServer = new ProtoDef(false) -sidToServer.addProtocol(protocol[version].sid, ['toServer']) - -const sidToClient = new ProtoDef(false) -sidToClient.addProtocol(protocol[version].sid, ['toClient']) - -const bnftpToServer = new ProtoDef(false) -bnftpToServer.addProtocol(protocol[version].bnftp, ['toServer']) - -const bnftpToClient = new ProtoDef(false) -bnftpToClient.addProtocol(protocol['1.13'].bnftp, ['toClient']) - -const d2gsToClient = new ProtoDef(false) -d2gsToClient.addTypes(d2gsReader) -d2gsToClient.addTypes(bitfieldLE) -d2gsToClient.addProtocol(protocol['1.13'].d2gs, ['toClient']) - -const d2gsToServer = new ProtoDef(false) -d2gsToServer.addProtocol(protocol[version].d2gs, ['toServer']) - -const toClientParser = new FullPacketParser(d2gsToClient, 'packet') -const splitter = createSplitter() -splitter.sloppyMode = true -let messagesToClient = [] -let messagesToServer = [] - -splitter.on('data', data => { - const uncompressedData = decompress(data) - - toClientParser.write(uncompressedData) -}) - -toClientParser.on('data', ({ data, buffer }) => { - try { - let { name, params } = data - - if (name === 'D2GS_ITEMACTIONWORLD' || name === 'D2GS_ITEMACTIONOWNED') { - params = itemParser(buffer) - } - wss.broadcast(JSON.stringify({ protocol: 'd2gsToClient', name, params })) - console.info('d2gsToClient : ', name, JSON.stringify(params)) - // console.log('raw', 'd2gsToClient', name, buffer) - messagesToClient.push(`d2gsToClient : ${name} ${JSON.stringify(params)}`) - } catch (err) { - console.log(err) - console.log('raw', 'd2gsToClient', buffer) - } -}) - -let clientPortSid = null -let clientPortBnFtp = null -let compression = false - -// server ports -const sidPort = '6112' -const d2gsPort = '4000' -let mcpPort = '6113' -let mcpIp = null - -const trackedPorts = new Set([sidPort, d2gsPort, mcpPort]) - -function displayD2gsToClient (data) { - try { - if (!compression) { - if (data[0] !== 0xaf) { data = data.slice(1) } - - const parsed = d2gsToClient.parsePacketBuffer('packet', data).data - - const { name, params } = parsed - wss.broadcast(JSON.stringify({ protocol: 'd2gsToClient', name, params })) - console.info('d2gsToClient (uncompressed): ', name, JSON.stringify(params)) - if (name === 'D2GS_NEGOTIATECOMPRESSION' && params.compressionMode !== 0) { - console.log('enable compression') - compression = true - } - } else { - splitter.write(data) - } - } catch (error) { - console.log('d2gsToClient : ', error.message) - console.log('raw', 'd2gsToClient', data) - } -} - -function displayParsed (proto, protoName, data, raw = false) { - try { - const { name, params } = proto.parsePacketBuffer('packet', data).data - console.log(protoName, ':', name, JSON.stringify(params)) - wss.broadcast(JSON.stringify({ protocol: protoName, name, params })) - if (raw) console.log('raw', protoName, name, data.toString('hex')) - messagesToServer.push(`${protoName}:${name} ${JSON.stringify(params)}`) - return { name, params } - } catch (error) { - if (raw) console.log('raw', protoName, data.toString('hex')) - console.log(protoName, ':', error.message) - } -} - -function displayD2gsToServer (data) { - displayParsed(d2gsToServer, 'd2gsToServer', data) -} - -function displayMcpToServer (data) { - displayParsed(mcpToServer, 'mcpToServer', data) -} - -function displayMcpToClient (data) { - displayParsed(mcpToClient, 'mcpToClient', data) -} - -function displaySidToServer (data) { - displayParsed(sidToServer, 'sidToServer', data) -} - -function displaySidToClient (data) { - const parsed = displayParsed(sidToClient, 'sidToClient', data) - if (parsed.name === 'SID_LOGONREALMEX') { - const IP = parsed.params.IP - mcpIp = IP[0] + '.' + IP[1] + '.' + IP[2] + '.' + IP[3] - mcpPort = parsed.params.port + '' - console.log(`received SID_LOGONREALMEX ${JSON.stringify({ mcpIp, mcpPort })}`) - } -} - -const challengeParserClient = new Parser(bnftpToClient, 'CHALLENGE') -challengeParserClient.on('error', err => console.log('bnftpToClient challenge error : ', err.message)) -challengeParserClient.on('data', (parsed) => { - console.info('bnftpToClient challenge : ', JSON.stringify(parsed)) -}) - -const protocolParserClient = new Parser(bnftpToClient, 'FILE_TRANSFER_PROTOCOL') -protocolParserClient.on('error', err => console.log('bnftpToClient protocol error : ', err.message)) -protocolParserClient.on('data', (parsed) => { - console.info('bnftpToClient protocol : ', JSON.stringify(parsed)) -}) - -function displayBnftpToClient (data) { - try { - protocolParserClient.write('FILE_TRANSFER_PROTOCOL') - // console.log('bnftpToClient protocol: ', JSON.stringify(bnftpToClient.parsePacketBuffer('FILE_TRANSFER_PROTOCOL', data).data)) - } catch (error) { - console.log('bnftpToClient error: ', error) - console.log('bnftpToClient protocol: ', data) - // challengeParserClient.write(data) - } -} - -const challengeParserServer = new Parser(bnftpToServer, 'CHALLENGE') -challengeParserServer.on('error', err => console.log('bnftpToServer bnftp error : ', err.message)) -challengeParserServer.on('data', (parsed) => { - console.info('bnftpToServer challenge : ', JSON.stringify(parsed)) -}) -const protocolParserServer = new Parser(bnftpToServer, 'FILE_TRANSFER_PROTOCOL') -protocolParserServer.on('error', err => console.log('bnftpToServer bnftp error : ', err.message)) -protocolParserServer.on('data', (parsed) => { - console.info('bnftpToServer protocol : ', JSON.stringify(parsed)) -}) -function displayBnftpToServer (data) { - try { - protocolParserServer.write('FILE_TRANSFER_PROTOCOL') - // console.log('bnftpToServer protocol: ', JSON.stringify(bnftpToServer.parsePacketBuffer('FILE_TRANSFER_PROTOCOL', data).data)) - // console.log('bnftpToServer protocol: ', data) - } catch (error) { - console.log('bnftpToServer error: ', error) - console.log('bnftpToServer write challenge', data) - // challengeParserServer.write(data) - } -} - -// tracker emits sessions, and sessions emit data -tcpTracker.on('session', function (session) { - const srcPort = session.src_name.split(':')[1] - const dstPort = session.dst_name.split(':')[1] - const srcIp = session.src_name.split(':')[0] - const dstIp = session.dst_name.split(':')[0] - if (!trackedPorts.has(srcPort) && !trackedPorts.has(dstPort)) { - return - } - - /* - if (dstPort === sidPort) { - if (clientPortSid === null) { - console.log('zalloooo') - clientPortSid = srcPort - } else { - clientPortBnFtp = srcPort - } - } - if (srcPort === sidPort) { - if (clientPortSid === null) { - clientPortSid = dstPort - } else { - clientPortBnFtp = dstPort - } - } */ - - session.on('start', function () { - if (srcPort === d2gsPort || dstPort === d2gsPort) { - console.log('Start of d2gs session') - } - if ((srcPort === mcpPort || dstPort === mcpPort) && (dstIp === mcpIp || srcIp === mcpIp || mcpIp === null)) { - console.log('Start of mcp session') - } - if ((srcPort === sidPort || dstPort === sidPort) && ((dstIp !== mcpIp && srcIp !== mcpIp) || mcpIp === null)) { - console.log('Start of sid session') - } - }) - session.on('data send', function (session, data) { - if (srcPort === d2gsPort) { - displayD2gsToClient(data) - } - - if (dstPort === d2gsPort) { - displayD2gsToServer(data) - } - - if (srcPort === mcpPort && (srcIp === mcpIp || mcpIp === null)) { - displayMcpToClient(data) - } - - if (dstPort === mcpPort && (dstIp === mcpIp || mcpIp === null)) { - displayMcpToServer(data) - } - - if (dstPort === sidPort && data.length === 1 && data[0] === 1) { - console.log(`sid on port ${srcPort} : ${data}`) - clientPortSid = srcPort - return - } - - if (dstPort === sidPort && data.length === 1 && data[0] === 2) { - console.log(`bnftp on port ${srcPort} : ${data}`) - clientPortBnFtp = srcPort - return - } - - if (srcPort === sidPort && dstPort === clientPortSid && (srcIp !== mcpIp || mcpIp === null)) { - displaySidToClient(data) - } - - if (dstPort === sidPort && srcPort === clientPortSid && (dstIp !== mcpIp || mcpIp === null)) { - displaySidToServer(data) - } - - if (srcPort === sidPort && dstPort === clientPortBnFtp) { - displayBnftpToClient(data) - } - - if (dstPort === sidPort && srcPort === clientPortBnFtp) { - displayBnftpToServer(data) - } - }) - session.on('data recv', function (session_, data) { - if (srcPort === d2gsPort) { - displayD2gsToServer(data) - } - - if (dstPort === d2gsPort) { - displayD2gsToClient(data) - } - - if (srcPort === mcpPort && (srcIp === mcpIp || mcpIp === null)) { - displayMcpToServer(data) - } - - if (dstPort === mcpPort && (dstIp === mcpIp || mcpIp === null)) { - displayMcpToClient(data) - } - - if (srcPort === sidPort && data.length === 1 && data[0] === 1) { - console.log(`sid on port ${dstPort} : ${data}`) - clientPortSid = dstPort - return - } - - if (srcPort === sidPort && data.length === 1 && data[0] === 2) { - console.log(`bnftp on port ${dstPort} : ${data}`) - clientPortBnFtp = dstPort - return - } - - if (srcPort === sidPort && dstPort === clientPortSid && (srcIp !== mcpIp || mcpIp === null)) { - displaySidToServer(data) - } - - if (dstPort === sidPort && srcPort === clientPortSid && (dstIp !== mcpIp || mcpIp === null)) { - displaySidToClient(data) - } - - if (srcPort === sidPort && dstPort === clientPortBnFtp) { - displayBnftpToServer(data) - } - - if (dstPort === sidPort && srcPort === clientPortBnFtp) { - displayBnftpToClient(data) - } - }) - - session.on('end', function () { - if (srcPort === d2gsPort || dstPort === d2gsPort) { - console.log('End of d2gs session') - } - if ((srcPort === mcpPort || dstPort === mcpPort) && (dstIp === mcpIp || srcIp === mcpIp || mcpIp === null)) { - console.log('End of mcp session') - } - if ((srcPort === sidPort || dstPort === sidPort) && (dstIp === mcpIp || srcIp === mcpIp || mcpIp === null)) { - console.log('End of sid session') - } - }) -}) - -console.log('loaded') - -// Set view engine to use, in this case 'pug' -app.set('view engine', 'pug') - -app.get('/', (req, res) => { - res.render('index', { title: 'Sniffer', messagesToClient: messagesToClient, messagesToServer: messagesToServer }) -}) -app.listen(process.env.PORT || 3001) diff --git a/index.js b/index.js index fb1d5e2..97ccc47 100644 --- a/index.js +++ b/index.js @@ -4,10 +4,15 @@ const { decompress, compress, getPacketSize } = require('./lib/utils/compression const d2gsReader = require('./lib/utils/d2gsSpecialReader') const getHash = require('./lib/utils/getHash') const createServerDiablo = require('./lib/server/createServerDiablo') +const ClientSid = require('./lib/client/clientSid') +const ClientMcp = require('./lib/client/clientMcp') +const ClientD2gs = require('./lib/client/clientD2gs') +const ClientDiablo = require('./lib/client/clientDiablo') const ServerDiablo = require('./lib/server/serverDiablo') +const ServerD2gs = require('./lib/server/serverD2gs') + const createServerSid = require('./lib/server/createServerSid') const createServerMcp = require('./lib/server/createServerMcp') -const ServerD2gs = require('./lib/server/serverD2gs') const itemParser = require('./lib/utils/itemParser') const bitfieldLE = require('./lib/utils/bitfieldLE') const { defaultVersion, supportedVersions } = require('./version') @@ -37,6 +42,10 @@ module.exports = { createServerDiablo, createServerSid, createServerMcp, + ClientSid, + ClientMcp, + ClientD2gs, + ClientDiablo, ServerD2gs, ServerDiablo, itemParser, diff --git a/lib/client/baseClient.js b/lib/client/baseClient.js new file mode 100644 index 0000000..3889e5e --- /dev/null +++ b/lib/client/baseClient.js @@ -0,0 +1,68 @@ +const EventEmitter = require('events').EventEmitter +const net = require('net') + + +/** + * Abstract Class Client. + * + * @class BaseClient + */ +class BaseClient extends EventEmitter { + + constructor(version, isServer = false) { + super() + if (this.constructor === BaseClient) { + throw new Error("Abstract classes can't be instantiated."); + } + this.isServer = isServer + this.protoToServer = null + this.protoToClient = null + } + + /** + * Parse a packet, should emit the parsed packet if no error + * @param data raw data + * @param toServer to server or to client + */ + parse(data, toServer) { + if (data.length === 1 && data[0] === 0x01) { + this.emit('init_connection') // TODO: maybe could emit some useful data (ip ..) + return + } + const proto = this.isServer ? this.protoToServer : this.protoToClient + try { + const parsed = proto.parsePacketBuffer('packet', data).data + const {name, params} = parsed + this.emit(name, params) + this.emit('packet', {name, params, toServer}) + } catch (err) { + err.raw = data + err.toServer = toServer + this.emit('error', err) + } + } + + connect (host, port) { + this.host = host + this.port = port + this.setSocket(net.createConnection({ port: this.port, host: this.host }, () => { + this.emit('connect') + })) + } + + setSocket (socket) { + this.socket = socket + this.socket.on('data', (data) => { + try { + this.parse(data, !this.isServer) + } catch (err) { + this.emit('error', err) + } + }) + this.socket.on('end', () => { + this.emit('end') + }) + } +} + +module.exports = BaseClient diff --git a/lib/client/clientD2gs.js b/lib/client/clientD2gs.js index 104e7af..abe638a 100644 --- a/lib/client/clientD2gs.js +++ b/lib/client/clientD2gs.js @@ -1,20 +1,21 @@ -const net = require('net') - const ProtoDef = require('protodef').ProtoDef const FullPacketParser = require('protodef').Parser -const EventEmitter = require('events').EventEmitter -const { decompress } = require('../utils/compression') +const {decompress} = require('../utils/compression') const d2gsReader = require('../utils/d2gsSpecialReader') const bitfieldLE = require('../utils/bitfieldLE') -const itemParser = require('../utils/itemParser') +const BaseClient = require('./baseClient') + -const { createSplitter, createFramer } = require('../utils/splitter') +const {createSplitter, createFramer} = require('../utils/splitter') +// const IGNORED_PACKETS = [0x01, 0xF1, 0x02] // Non debugged packets that just pollute +const LOGIN_PACKETS = [0xAF] -class Client extends EventEmitter { - constructor (version, isServer = false) { - super() +class Client extends BaseClient { + constructor(version, isServer = false, includeRaw = false) { + super(version, isServer) this.compression = false - this.isServer = isServer + this.version = version + this.includeRaw = includeRaw const protocol = require(`../../data/${version}/d2gs`) this.protoToServer = new ProtoDef(false) this.protoToServer.addTypes(d2gsReader) @@ -26,101 +27,89 @@ class Client extends EventEmitter { this.protoToClient.addTypes(bitfieldLE) this.protoToClient.addProtocol(protocol, ['toClient']) this.toClientParser = new FullPacketParser(this.protoToClient, 'packet') - } - - connect (host, port) { - this.host = host - this.port = port - this.setSocket(net.createConnection({ port: this.port, host: this.host }, () => { - this.emit('connect') - })) - } - - setSocket (socket) { - this.socket = socket - this.splitter = createSplitter() + // this.splitter.sloppyMode = true this.framer = createFramer() - this.framer.on('data', data => { - // console.log('sending buffer d2gs ' + data.toString('hex')) - this.socket.write(data) - }) this.splitter.on('data', data => { - // console.log('compressed d2gs received hex : ' + data.toString('hex')) - const uncompressedData = decompress(data) - - // console.log('after decompression d2gs received hex : ' + uncompressedData.toString('hex')) - this.toClientParser.write(uncompressedData) - }) - - this.toClientParser.on('data', ({ data, buffer }) => { - let { name, params } = data - - if (this.isServer === false && (name === 'D2GS_ITEMACTIONWORLD' || name === 'D2GS_ITEMACTIONOWNED')) { - params = itemParser(buffer) + try { + const uncompressedData = decompress(data) + this.toClientParser.write(uncompressedData) + } catch (err) { + err.raw = data + err.toServer = false + this.emit('error', err) } - - // console.info('received compressed packet', name, JSON.stringify(params)) - console.info('d2gsToClient : ', name, JSON.stringify(params)) - + }) + this.toClientParser.on('data', ({data, buffer}) => { + let {name, params} = data this.emit(name, params) - this.emit('packet', { name, params }) + const toServer = false + if (this.includeRaw) { + const raw = buffer + this.emit('packet', {name, params, toServer, raw}) + } else this.emit('packet', {name, params, toServer}) }) - this.toClientParser.on('error', err => console.log('d2gsToClient error : ', err.message)) - - const proto = this.isServer ? this.protoToServer : this.protoToClient - this.socket.on('data', (data) => { - // console.log('received that d2gs hex', data.toString('hex')) + this.toClientParser.on('error', err => this.emit('error', err)) + } - if (!this.compression) { - try { - if (data[0] === 0x2b && this.isServer === true) { - console.log('d2gsToClient anti-cheat') - return + /** + * Parse a packet, should emit the parsed packet if no error + * @param data raw data + * @param toServer to server or to client + */ + parse(data, toServer) { + try { + let parsed + // Message to client + if (!toServer) { + // Uncompressed + if (!this.compression) { + // Not sure optimal conditions, anyway required in some servers + if (this.version === 1.13 && + !LOGIN_PACKETS.includes(data[0]) && + this.isServer === false) { + data = data.slice(1) } - if (data[0] !== 0xaf && this.isServer === false) { data = data.slice(1) } - - const parsed = proto.parsePacketBuffer('packet', data).data - - const { name, params } = parsed - // console.info('received uncompressed packet', name, JSON.stringify(params)) - console.info('d2gsToClient (uncompressed): ', name, JSON.stringify(params)) - - this.emit(name, params) - this.emit('packet', { name, params }) - } catch (err) { - console.log(err.message) + parsed = this.protoToClient.parsePacketBuffer('packet', data).data + } else { // Compressed, need to be split-ed and uncompressed + this.splitter.write(data) + return } - } else { - this.splitter.write(data) + } else { // Message to server + parsed = this.protoToServer.parsePacketBuffer('packet', data).data } - }) + const {name, params} = parsed + this.emit(name, params) + if (this.includeRaw) { + const raw = data + this.emit('packet', {name, params, toServer, raw}) + } else this.emit('packet', {name, params, toServer}) + } catch (err) { + err.raw = data + err.toServer = toServer + this.emit('error', err) + } + } - this.socket.on('end', () => { - console.log('disconnected from server d2gs') + setSocket(socket) { + super.setSocket(socket) + this.framer.on('data', data => { + this.socket.write(data) }) } - enableCompression () { - this.compression = true + enableCompression(value) { + this.compression = value } - write (packetName, params) { + write(packetName, params) { const proto = this.isServer ? this.protoToClient : this.protoToServer - try { - const buffer = proto.createPacketBuffer('packet', { - name: packetName, - params - }) - - // console.info('sending uncompressed packet', packetName, params) - console.info('d2gsToServer : ', packetName, params) - const buffer2 = this.isServer && packetName !== 'D2GS_NEGOTIATECOMPRESSION' ? Buffer.concat([Buffer.from([buffer.length + 1]), buffer]) : buffer - this.socket.write(buffer2) - // console.log('sending that hex', buffer2) - } catch (err) { - console.log(err) - } + const buffer = proto.createPacketBuffer('packet', { + name: packetName, + params + }) + const buffer2 = this.isServer && packetName !== 'D2GS_NEGOTIATECOMPRESSION' ? Buffer.concat([Buffer.from([buffer.length + 1]), buffer]) : buffer + this.socket.write(buffer2) } } diff --git a/lib/client/clientDiablo.js b/lib/client/clientDiablo.js index e1db12f..1e2f9ef 100644 --- a/lib/client/clientDiablo.js +++ b/lib/client/clientDiablo.js @@ -8,49 +8,55 @@ class ClientDiablo extends EventEmitter { setClientSid (clientSid) { this.clientSid = clientSid - this.clientSid.on('packet', ({ name, params }) => { + this.clientSid.on('packet', ({ name, params, toServer }) => { this.emit(name, params) - this.emit('packet', { name, params, protocol: this.clientSid.isServer ? 'sidToServer' : 'sidToClient' }) + this.emit('packet', { name, params, toServer }) }) + this.clientSid.on('error', err => this.emit('error', err)) } setClientMcp (clientMcp) { this.clientMcp = clientMcp - this.clientMcp.on('packet', ({ name, params }) => { + this.clientMcp.on('packet', ({ name, params, toServer }) => { this.emit(name, params) - this.emit('packet', { name, params, protocol: this.clientMcp.isServer ? 'mcpToServer' : 'mcpToClient' }) + this.emit('packet', { name, params, toServer }) }) + this.clientMcp.on('error', err => this.emit('error', err)) + } setClientBnftp (clientBnftp) { this.clientBnftp = clientBnftp - this.clientBnftp.on('packet', ({ name, params }) => { + this.clientBnftp.on('packet', ({ name, params, toServer }) => { this.emit(name, params) - this.emit('packet', { name, params, protocol: this.clientBnftp.isServer ? 'bnftpToServer' : 'bnftpToClient' }) + this.emit('packet', { name, params, toServer }) }) + this.clientBnftp.on('error', err => this.emit('error', err)) + } setClientD2gs (clientD2gs) { this.clientD2gs = clientD2gs - this.clientD2gs.on('packet', ({ name, params }) => { + this.clientD2gs.on('packet', ({ name, params, toServer, raw }) => { this.emit(name, params) - this.emit('packet', { name, params, protocol: this.clientD2gs.isServer ? 'd2gsToServer' : 'd2gsToClient' }) + this.emit('packet', { name, params, toServer, raw }) }) + this.clientD2gs.on('error', err => this.emit('error', err)) } write (name, params) { setTimeout(() => { if (name.startsWith('SID')) { - this.emit('sentPacket', { name, params, protocol: this.clientSid.isServer ? 'sidToClient' : 'sidToServer' }) + this.emit('sentPacket', { name, params, toServer: !this.clientSid.isServer }) this.clientSid.write(name, params) } else if (name.startsWith('MCP')) { - this.emit('sentPacket', { name, params, protocol: this.clientMcp.isServer ? 'mcpToClient' : 'mcpToServer' }) + this.emit('sentPacket', { name, params, toServer: !this.clientMcp.isServer }) this.clientMcp.write(name, params) } else if (name.startsWith('FILE_TRANSFER_PROTOCOL')) { - this.emit('sentPacket', { name, params, protocol: this.clientBnftp.isServer ? 'bnftpToClient' : 'bnftpToServer' }) + this.emit('sentPacket', { name, params, toServer: !this.clientBnftp.isServer }) this.clientBnftp.write(name, params) } else if (name.startsWith('D2GS')) { - this.emit('sentPacket', { name, params, protocol: this.clientD2gs.isServer ? 'd2gsToClient' : 'd2gsToServer' }) + this.emit('sentPacket', { name, params, toServer: !this.clientD2gs.isServer }) this.clientD2gs.write(name, params) } }, this.delayPackets) // Delay between packets, TODO: issue with async ? ... diff --git a/lib/client/clientMcp.js b/lib/client/clientMcp.js index 11b8ead..643b75b 100644 --- a/lib/client/clientMcp.js +++ b/lib/client/clientMcp.js @@ -1,70 +1,25 @@ -const net = require('net') - const ProtoDef = require('protodef').ProtoDef -const EventEmitter = require('events').EventEmitter +const BaseClient = require('./baseClient') -class Client extends EventEmitter { +class Client extends BaseClient { constructor (version, isServer = false) { - super() - this.isServer = isServer + super(version, isServer) const mcp = require(`../../data/${version}/mcp`) - this.mcpToServer = new ProtoDef(false) - this.mcpToServer.addProtocol(mcp, ['toServer']) - - this.mcpToClient = new ProtoDef(false) - this.mcpToClient.addProtocol(mcp, ['toClient']) - } + this.protoToServer = new ProtoDef(false) + this.protoToServer.addProtocol(mcp, ['toServer']) - connect (host, port) { - this.host = host - this.port = port - const socket = net.createConnection({ port: this.port, host: this.host }, () => { - this.emit('connect') - }) - this.setSocket(socket) - } - - setSocket (socket) { - this.socket = socket - - this.socket.on('data', (data) => { - try { - // console.log('received that hex MCP', data.toString('hex')) - if (data.length === 1 && data[0] === 0x01) { - this.emit('init_connection') - return - } - const proto = this.isServer ? this.mcpToServer : this.mcpToClient - const parsed = proto.parsePacketBuffer('packet', data).data - - const { name, params } = parsed - console.info('mcpToClient : ', name, JSON.stringify(parsed)) - - this.emit(name, params) - this.emit('packet', { name, params }) - } catch (err) { - console.log(err.message) - } - }) - - this.socket.on('end', () => { - console.log('disconnected from mcp server') - }) + this.protoToClient = new ProtoDef(false) + this.protoToClient.addProtocol(mcp, ['toClient']) } write (name, params) { - const proto = this.isServer ? this.mcpToClient : this.mcpToServer + const proto = this.isServer ? this.protoToClient : this.protoToServer const buffer = proto.createPacketBuffer('packet', { size: 0, name: name, params }) - - console.info('mcpToServer : ', name, params) buffer.writeInt16LE(buffer.length, 0) - - // console.log('sending that hex MCP ', buffer) - this.socket.write(buffer) } } diff --git a/lib/client/clientSid.js b/lib/client/clientSid.js index 241bb3b..094c85b 100644 --- a/lib/client/clientSid.js +++ b/lib/client/clientSid.js @@ -1,13 +1,9 @@ -const net = require('net') - const ProtoDef = require('protodef').ProtoDef +const BaseClient = require('./baseClient') -const EventEmitter = require('events').EventEmitter - -class Client extends EventEmitter { +class Client extends BaseClient { constructor (version, isServer = false) { - super() - this.isServer = isServer + super(version, isServer) const sid = require(`../../data/${version}/sid`) this.protoToServer = new ProtoDef(false) this.protoToServer.addProtocol(sid, ['toServer']) @@ -16,54 +12,15 @@ class Client extends EventEmitter { this.protoToClient.addProtocol(sid, ['toClient']) } - connect (host, port) { - this.host = host - this.port = port - this.setSocket(net.createConnection({ port: this.port, host: this.host }, () => { - this.emit('connect') - })) - } - - setSocket (socket) { - this.socket = socket - this.socket.on('data', (data) => { - try { - // console.log('received that hex sid', data.toString('hex')) - if (data.length === 1 && data[0] === 0x01) { - this.emit('init_connection') - return - } - const proto = this.isServer ? this.protoToServer : this.protoToClient - const parsed = proto.parsePacketBuffer('packet', data).data - - const { name, params } = parsed - console.info('sidToClient : ', name, JSON.stringify(parsed)) - - this.emit(name, params) - this.emit('packet', { name, params }) - } catch (err) { - console.log(err.message) - } - }) - this.socket.on('end', () => { - console.log('disconnected from server sid') - }) - } - - write (packetName, params) { + write (name, params) { const proto = this.isServer ? this.protoToClient : this.protoToServer const buffer = proto.createPacketBuffer('packet', { - name: packetName, + name: name, size: 0, ff: 255, params }) - - console.info('sidToServer : ', packetName, JSON.stringify(params)) buffer.writeInt16LE(buffer.length, 2) - // console.info(buffer.toString('hex')) - - // console.log('sending that hex SID ', buffer) this.socket.write(buffer) } } diff --git a/lib/client/createClientD2gs.js b/lib/client/createClientD2gs.js index 6f77a82..93c83da 100644 --- a/lib/client/createClientD2gs.js +++ b/lib/client/createClientD2gs.js @@ -12,7 +12,7 @@ async function createClientD2gs (clientDiablo, version) { clientDiablo.emit('clientD2gsReady', clientD2gs) clientD2gs.on('connect', () => { - console.log('connected to clientD2gs') + clientDiablo.emit('log', 'connected to clientD2gs') }) clientDiablo.on('D2GS_NEGOTIATECOMPRESSION', (data) => { @@ -34,26 +34,31 @@ async function createClientD2gs (clientDiablo, version) { }) setInterval(() => { + clientDiablo.emit('log', `Latency ${clientDiablo.lastPing - clientDiablo.lastPong}`) + if (clientDiablo.lastPing - clientDiablo.lastPong > 10000) { + clientDiablo.emit('error', new Error('Server stopped answering, probably crashed')) + process.exit(1) + } clientDiablo.write('D2GS_PING', { tickCount: Date.now() - clientDiablo.initialTime, delay: clientDiablo.latency, wardenResponse: 0 }) - clientDiablo.timeAtLastPing = Date.now() + clientDiablo.lastPing = Date.now() }, 5000) clientDiablo.on('D2GS_PONG', () => { - clientDiablo.latency = Date.now() - clientDiablo.timeAtLastPing - // console.log('latency is ' + clientDiablo.latency + 'ms') + clientDiablo.latency = Date.now() - clientDiablo.lastPing + clientDiablo.lastPong = Date.now() }) if (version === '1.13') await once(clientDiablo, 'D2GS_LOGONRESPONSE') - else { - await once(clientDiablo, 'D2GS_GAMELOADING') - } + else await once(clientDiablo, 'D2GS_GAMELOADING') clientDiablo.write('D2GS_ENTERGAMEENVIRONMENT', {}) - if (version === '1.13') clientD2gs.socket.write(Buffer.from('2b0155332211', 'hex'))// 2b0155332211 // PoD anticheat packet XD - clientD2gs.enableCompression() + if (version === '1.13') { + clientD2gs.socket.write(Buffer.from('2b0155332211', 'hex'))// 2b0155332211 // PoD anticheat + clientD2gs.enableCompression(true) + } clientDiablo.emit('gameJoined') } diff --git a/lib/client/createClientDiablo.js b/lib/client/createClientDiablo.js index ca1ca95..68d4ff6 100644 --- a/lib/client/createClientDiablo.js +++ b/lib/client/createClientDiablo.js @@ -1,14 +1,10 @@ const ClientDiablo = require('./clientDiablo') - const createClientSid = require('./createClientSid') - const createClientMcp = require('./createClientMcp') - const createClientD2gs = require('./createClientD2gs') - const { once } = require('once-promise') - const { toNumVersion } = require('../../version') +const diabloData = require('diablo2-data')('pod_1.13d') function toCamelCase (s) { if (s === '') { return '' } @@ -26,7 +22,8 @@ function createClientDiablo ({ username, password, host, version, keyClassic, ke clientDiablo.connect = async () => { const { hostMcp, portMcp, MCPCookie, MCPStatus, MCPChunk1, MCPChunk2, battleNetUniqueName } = await createClientSid(clientDiablo, host, version) - await createClientMcp(clientDiablo, hostMcp, portMcp, MCPCookie, MCPStatus, MCPChunk1, MCPChunk2, battleNetUniqueName, version) + const result = await createClientMcp(clientDiablo, hostMcp, portMcp, MCPCookie, MCPStatus, MCPChunk1, MCPChunk2, battleNetUniqueName, version) + console.log(`Got characters ${result.characters}`) } clientDiablo.selectCharacter = async (character) => { @@ -56,7 +53,6 @@ function createClientDiablo ({ username, password, host, version, keyClassic, ke // Create game = join game clientDiablo.createGame = async (gameName, gamePassword, gameServer, difficulty = 0, joinIfAlreadyExists = true) => { - console.log('joinIfAlreadyExists', joinIfAlreadyExists) gameName = toCamelCase(gameName) gamePassword = toCamelCase(gamePassword) clientDiablo.write('MCP_CREATEGAME', { @@ -101,21 +97,7 @@ function createClientDiablo ({ username, password, host, version, keyClassic, ke const { gameToken, IPOfD2GSServer: IP2, gameHash, result } = await once(clientDiablo, 'MCP_JOINGAME') if (result !== 0) { - const errors = { - 0x00: 'Game joining succeeded. In this case, Diablo 2 terminates the connection with the MCP and initiates the connection with the D2GS.', - 0x29: 'Password incorrect.', - 0x2A: 'Game does not exist.', - 0x2B: 'Game is full.', - 0x2C: 'You do not meet the level requirements for this game.', - 0x6E: 'A dead hardcore character cannot join a game.', - 0x71: 'A non-hardcore character cannot join a game created by a Hardcore character.', - 0x73: 'Unable to join a Nightmare game.', - 0x74: 'Unable to join a Hell game.', - 0x78: 'A non-expansion character cannot join a game created by an Expansion character.', - 0x79: 'A Expansion character cannot join a game created by a non-expansion character.', - 0x7D: 'A non-ladder character cannot join a game created by a Ladder character.' - } - throw new Error(`Cannot connect error ${result} : ${errors[result]}`) + clientDiablo.emit('error', Error(`Cannot connect error ${result} : ${diabloData.responses.joinGame[result]}`)) } clientDiablo.IP2 = IP2 @@ -136,7 +118,7 @@ function createClientDiablo ({ username, password, host, version, keyClassic, ke const { status } = await once(clientDiablo, 'SID_STARTADVEX3') if (status === 1) { - throw new Error('join failed') + clientDiablo.emit('error', new Error('join failed')) } } diff --git a/lib/client/createClientMcp.js b/lib/client/createClientMcp.js index 7430830..4400c0a 100644 --- a/lib/client/createClientMcp.js +++ b/lib/client/createClientMcp.js @@ -1,17 +1,14 @@ const ClientMcp = require('./clientMcp') - const { once } = require('once-promise') +const diabloData = require('diablo2-data')('pod_1.13d') // Connect to account async function createClientMcp (clientDiablo, hostMcp, portMcp, MCPCookie, MCPStatus, MCPChunk1, MCPChunk2, battleNetUniqueName, version) { const clientMcp = new ClientMcp(version) clientDiablo.setClientMcp(clientMcp) clientMcp.on('connect', () => { - // 'connect' listener - console.log('connected to MCP server!') - + clientDiablo.emit('log', 'connected to MCP server') clientMcp.socket.write(Buffer.from('01', 'hex')) // This Initialise conversation - clientDiablo.write('MCP_STARTUP', { MCPCookie: MCPCookie, MCPStatus: MCPStatus, @@ -22,22 +19,17 @@ async function createClientMcp (clientDiablo, hostMcp, portMcp, MCPCookie, MCPSt }) clientDiablo.on('MCP_STARTUP', ({ result }) => { - if (result === 0x02 || (result >= 0x0A && result <= 0x0D)) { - console.log('Realm Unavailable: No Battle.net connection detected.') - } else if (result === 0x7E) { - console.log('CDKey banned from realm play.') - } else if (result === 0x7F) { - console.log('Temporary IP ban "Your connection has been temporarily restricted from this realm. Please try to log in at another time."') - } else { - console.log('Success!') + if (result === 0) { clientDiablo.write('MCP_CHARLIST2', { numberOfCharacterToList: 8 }) + clientDiablo.emit('log', diabloData.responses.startup[result]) + } else { + clientDiablo.emit('error', new Error(diabloData.responses.startup[result])) } }) const p = once(clientDiablo, 'MCP_CHARLIST2') - clientMcp.connect(hostMcp, portMcp) const { characters } = await p diff --git a/lib/client/createClientSid.js b/lib/client/createClientSid.js index a3184e9..e9ed2e9 100644 --- a/lib/client/createClientSid.js +++ b/lib/client/createClientSid.js @@ -1,23 +1,18 @@ const ClientSid = require('./clientSid') - const getHash = require('../utils/getHash') - const { once } = require('once-promise') - -// const cdkey16 = require('../utils/cdkey') -const cdkey = require('../utils/cdkey26') +const cdKey = require('../utils/cdkey26') const checkRevision = require('../utils/checkRevision') +const diabloData = require('diablo2-data')('pod_1.13d') + -// Connect to battlenet async function createClientSid (clientDiablo, host, version) { - // const getMpq = require(`../utils/getMpq1.13`) const portSid = 6112 const clientSid = new ClientSid(version) clientDiablo.setClientSid(clientSid) clientDiablo.clientToken = 2045114178 clientSid.on('connect', () => { - // 'connect' listener - console.log('connected to server!') + clientDiablo.emit('log', 'connected to SID server!') clientSid.socket.write(Buffer.from('01', 'hex')) // Initialises a normal logon conversation @@ -64,8 +59,8 @@ async function createClientSid (clientDiablo, host, version) { } }, () => {})) */ - const key1 = cdkey(clientDiablo.keyClassic, clientDiablo.clientToken, clientDiablo.serverToken) - const key2 = cdkey(clientDiablo.keyExtension, clientDiablo.clientToken, clientDiablo.serverToken) + const key1 = cdKey(clientDiablo.keyClassic, clientDiablo.clientToken, clientDiablo.serverToken) + const key2 = cdKey(clientDiablo.keyExtension, clientDiablo.clientToken, clientDiablo.serverToken) const { checksum, info } = checkRevision(valuestring) clientDiablo.write('SID_AUTH_CHECK', { @@ -94,14 +89,14 @@ async function createClientSid (clientDiablo, host, version) { clientDiablo.on('SID_AUTH_CHECK', ({ result, additionalInformation }) => { if (result === 0) { - console.log('Correct keys') + clientDiablo.emit('log', 'Correct keys') clientDiablo.write('SID_GETFILETIME', { requestId: 2147483652, unknown: 0, filename: 'bnserver-D2DV.ini' }) } else { - console.log(`result is ${result}, wrong key`) + clientDiablo.emit('error', new Error(`result is ${result}, wrong key`)) } }) @@ -115,9 +110,11 @@ async function createClientSid (clientDiablo, host, version) { }) clientDiablo.on('SID_LOGONRESPONSE2', ({ status }) => { - console.log(status === 0 ? 'Success' : status === 1 ? "Account doesn't exist" : status === 2 ? 'Invalid password' : 'Account closed') + clientDiablo.emit('log', diabloData.responses.logon[status]) if (status === 0) { clientDiablo.write('SID_QUERYREALMS2', {}) + } else { + this.emit('error', new Error(diabloData.responses.logon[status])) } }) diff --git a/package.json b/package.json index f96ed88..ac98fec 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,9 @@ "bit-buffer": "^0.2.4", "bridge.net": "^2.0.0", "csvtojson": "^2.0.8", - "diablo2-data": "^1.3.0", "once-promise": "^2.0.0", - "protodef": "^1.6.9" + "protodef": "^1.6.9", + "diablo2-data": "file:../node-diablo2-data" }, "standard": { "ignore": [