-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19 from rustymotors/nps-user-login
refactor: Add NPS data handling and web request handling functions
- Loading branch information
Showing
6 changed files
with
323 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { NPSMessageHeader } from "./NPSMessageHeader.js"; | ||
import { NPSMessagePayload } from "./NPSMessagePayload.js"; | ||
|
||
export class NPSMessage { | ||
constructor() { | ||
this._header = new NPSMessageHeader(); | ||
this.data = new NPSMessagePayload(); | ||
} | ||
/** | ||
* | ||
* @param {Buffer} data | ||
* @returns {NPSMessage} | ||
*/ | ||
static parse(data) { | ||
const self = new NPSMessage(); | ||
if (data.length < 8) { | ||
throw new Error(`Invalid message length: ${data.length}`); | ||
} | ||
|
||
self._header = NPSMessageHeader.parse(data); | ||
|
||
const expectedLength = self._header.messageLength - self._header.dataOffset; | ||
|
||
self.data = NPSMessagePayload.parse( | ||
data.subarray(self._header.dataOffset), | ||
expectedLength, | ||
); | ||
|
||
return self; | ||
} | ||
|
||
/** | ||
* @returns Buffer | ||
*/ | ||
toBuffer() { | ||
return Buffer.concat([this._header.toBuffer(), this.data.toBuffer()]); | ||
} | ||
|
||
/** | ||
* @returns string | ||
*/ | ||
toString() { | ||
return `${this._header.toString()}, Data: ${this.data.toString()}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
export class NPSMessageHeader { | ||
constructor() { | ||
this._dataStart = -1; | ||
this.messageId = -1; | ||
this.messageLength = -1; | ||
this.version = -1; | ||
} | ||
|
||
/** | ||
* | ||
* @param {Buffer} data | ||
* @returns NPSMessageHeader | ||
*/ | ||
static parse(data) { | ||
const self = new NPSMessageHeader(); | ||
if (data.length < 6) { | ||
throw new Error("Invalid header length"); | ||
} | ||
self.messageId = data.readUInt16BE(0); | ||
self.messageLength = data.readUInt16BE(2); | ||
|
||
self.version = data.readUInt16BE(4); | ||
|
||
if (self.version === 257) { | ||
self._dataStart = 12; | ||
} else { | ||
self._dataStart = 6; | ||
} | ||
|
||
return self; | ||
} | ||
|
||
get dataOffset() { | ||
return this._dataStart; | ||
} | ||
|
||
/** | ||
* @private | ||
* @returns Buffer | ||
*/ | ||
_writeExtraData() { | ||
const buffer = Buffer.alloc(6); | ||
buffer.writeUInt16BE(0, 0); | ||
buffer.writeUInt32BE(this.messageLength, 2); | ||
return buffer; | ||
} | ||
|
||
/** | ||
* @returns Buffer | ||
*/ | ||
toBuffer() { | ||
let buffer = Buffer.alloc(6); | ||
buffer.writeUInt16BE(this.messageId, 0); | ||
buffer.writeUInt16BE(this.messageLength, 2); | ||
buffer.writeUInt16BE(this.version, 4); | ||
|
||
if (this.version === 257) { | ||
return Buffer.concat([buffer, this._writeExtraData()]); | ||
} | ||
return buffer; | ||
} | ||
|
||
/** | ||
* @returns string | ||
*/ | ||
toString() { | ||
return `ID: ${this.messageId}, Length: ${this.messageLength}, Version: ${this.version}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/** | ||
* @interface INPSPayload | ||
*/ | ||
/** | ||
* @interface INPSPayload | ||
* @static parse | ||
* @property {Buffer} data | ||
*/ | ||
export class INPSPayload { | ||
constructor() { | ||
this.data = Buffer.alloc(0); | ||
this.toBuffer = function () {}; | ||
this.toString = function () {}; | ||
} | ||
|
||
/** | ||
* @param {Buffer} data | ||
* @returns INPSPayload | ||
*/ | ||
static parse(data) { | ||
const self = new NPSMessagePayload(); | ||
self.data = data; | ||
return self; | ||
} | ||
} | ||
|
||
/** | ||
* To be used as a base class for NPS message payloads. | ||
* | ||
* @implements {INPSPayload} | ||
* @class | ||
* @property {Buffer} data | ||
* | ||
* @example | ||
* class MyPayload extends NPSMessagePayload { | ||
* constructor() { | ||
* super(); | ||
* this.myProperty = 0; | ||
* } | ||
* | ||
* static parse(data) { | ||
* this.myProperty = data.readUInt32LE(0); | ||
* } | ||
* | ||
* toBuffer() { | ||
* const buffer = Buffer.alloc(4); | ||
* buffer.writeUInt32LE(this.myProperty, 0); | ||
* return buffer; | ||
* } | ||
* | ||
* toString() { | ||
* return `MyPayload: ${this.myProperty}`; | ||
* } | ||
* } | ||
*/ | ||
|
||
export class NPSMessagePayload { | ||
constructor() { | ||
this.data = Buffer.alloc(0); | ||
} | ||
|
||
/** | ||
* | ||
* @param {Buffer} data | ||
* @returns NPSMessagePayload | ||
*/ | ||
static parse(data, len = data.length) { | ||
if (data.length !== len) { | ||
throw new Error( | ||
`Invalid payload length: ${data.length}, expected: ${len}`, | ||
); | ||
} | ||
const self = new NPSMessagePayload(); | ||
self.data = data; | ||
return self; | ||
} | ||
|
||
/** | ||
* @returns Buffer | ||
*/ | ||
toBuffer() { | ||
return this.data; | ||
} | ||
|
||
/** | ||
* @returns string | ||
*/ | ||
toString() { | ||
return this.data.toString("hex"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { NPSMessagePayload } from "./NPSMessagePayload.js"; | ||
|
||
/** | ||
* @typedef INPSPayload | ||
* @type {import("./NPSMessagePayload.js").INPSPayload} | ||
*/ | ||
|
||
/** | ||
* @implements {INPSPayload} | ||
* @extends {NPSMessagePayload} | ||
* Payload for the NPSUserLogin message. | ||
*/ | ||
export class NPSUserLoginPayload extends NPSMessagePayload { | ||
constructor() { | ||
super(); | ||
this.data = Buffer.alloc(0); | ||
this.ticket = ""; | ||
this.sessionKey = ""; | ||
this.gameId = ""; | ||
} | ||
|
||
/** | ||
* | ||
* @param {number} len | ||
* @param {Buffer} data | ||
* @returns {NPSUserLoginPayload} | ||
*/ | ||
static parse(data, len = data.length) { | ||
if (data.length !== len) { | ||
throw new Error( | ||
`Invalid payload length: ${data.length}, expected: ${len}`, | ||
); | ||
} | ||
|
||
const self = new NPSUserLoginPayload(); | ||
try { | ||
let offset = 0; | ||
let nextLen = data.readUInt16BE(0); | ||
self.ticket = data.toString("utf8", 2, nextLen + 2); | ||
offset = nextLen + 2; | ||
offset += 2; // Skip one empty word | ||
nextLen = data.readUInt16BE(offset); | ||
self.sessionKey = data.toString("hex", offset + 2, offset + 2 + nextLen); | ||
offset += nextLen + 2; | ||
nextLen = data.readUInt16BE(offset); | ||
self.gameId = data | ||
.subarray(offset + 2, offset + 2 + nextLen) | ||
.toString("utf8"); | ||
} catch (error) { | ||
if (!(error instanceof Error)) { | ||
throw new Error(`Error parsing payload: ${error}`); | ||
} | ||
console.error(`Error parsing payload: ${error.message}`); | ||
throw new Error(`Error parsing payload: ${error.message}`); | ||
} | ||
|
||
return self; | ||
} | ||
|
||
/** | ||
* @returns {Buffer} | ||
*/ | ||
toBuffer() { | ||
throw new Error("Method not implemented."); | ||
} | ||
|
||
/** | ||
* @returns {string} | ||
*/ | ||
toString() { | ||
return `Ticket: ${this.ticket}, SessionKey: ${this.sessionKey}, GameId: ${this.gameId}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,36 @@ | ||
import { NPSMessage } from "./NPSMessage.js"; | ||
import { NPSUserLoginPayload } from "./NPSUserLoginPayload.js"; | ||
|
||
/** | ||
* @typedef INPSPayload | ||
* @type {import("./NPSMessagePayload.js").INPSPayload} | ||
*/ | ||
|
||
/** @type {Map<number, (data: Buffer, len: number) => INPSPayload>} */ | ||
const payloadMap = new Map(); | ||
|
||
payloadMap.set(1281, NPSUserLoginPayload.parse); | ||
|
||
/** | ||
* @param {number} port | ||
* @param {Buffer} data | ||
* @param {(data: Buffer) => void} sendToClient | ||
*/ | ||
export function onNPSData(port, data) { | ||
const hex = data.toString("hex"); | ||
console.log(`Data received: ${hex}`); | ||
export function onNPSData(port, data, sendToClient) { | ||
const message = NPSMessage.parse(data); | ||
console.log(`Received message on port ${port}: ${message.toString()}`); | ||
|
||
const messageType = payloadMap.get(message._header.messageId); | ||
|
||
if (!messageType) { | ||
console.error(`Unknown message type: ${message._header.messageId}`); | ||
return; | ||
} | ||
|
||
const payload = messageType( | ||
message.data.data, | ||
message._header.messageLength - message._header.dataOffset, | ||
); | ||
|
||
console.log(`Parsed payload: ${payload.toString()}`); | ||
} |