diff --git a/lib/games.js b/lib/games.js index 0ced694..057167f 100644 --- a/lib/games.js +++ b/lib/games.js @@ -654,7 +654,7 @@ export const games = { options: { port: 2302, port_query_offset: 24714, - protocol: 'valve' + protocol: 'dayz' } }, dayzmod: { diff --git a/protocols/dayz.js b/protocols/dayz.js new file mode 100644 index 0000000..9c32f34 --- /dev/null +++ b/protocols/dayz.js @@ -0,0 +1,182 @@ +import valve from './valve.js' +import { Buffer } from 'node:buffer' + +export default class dayz extends valve { + async run (state) { + if (!this.options.port) this.options.port = 27016 + await super.queryInfo(state) + await super.queryChallenge() + await super.queryPlayers(state) + await this.queryRules(state) + + this.processQueryInfo(state) + await super.cleanup(state) + } + + async queryRules (state) { + if (!this.options.requestRules) { + return + } + + const rules = {} + state.raw.rules = rules + const dayZPayload = [] + + this.logger.debug('Requesting rules ...') + + const b = await this.sendPacket(0x56, null, 0x45, true) + if (b === null) return // timed out - the server probably has rules disabled + + let dayZPayloadEnded = false + + const reader = this.reader(b) + const num = reader.uint(2) + for (let i = 0; i < num; i++) { + if (!dayZPayloadEnded) { + const one = reader.uint(1) + const two = reader.uint(1) + const three = reader.uint(1) + if (one !== 0 && two !== 0 && three === 0) { + while (true) { + const byte = reader.uint(1) + if (byte === 0) break + dayZPayload.push(byte) + } + continue + } else { + reader.skip(-3) + dayZPayloadEnded = true + } + } + + const key = reader.string() + rules[key] = reader.string() + } + + state.raw.dayzMods = this.readDayzMods(Buffer.from(dayZPayload)) + } + + processQueryInfo (state) { + // DayZ embeds some of the server information inside the tags attribute + if (!state.raw.tags) { return } + + state.raw.dlcEnabled = false + state.raw.firstPerson = false + for (const tag of state.raw.tags) { + if (tag.startsWith('lqs')) { + const value = parseInt(tag.replace('lqs', '')) + if (!isNaN(value)) { + state.raw.queue = value + } + } + if (tag.includes('no3rd')) { + state.raw.firstPerson = true + } + if (tag.includes('isDLC')) { + state.raw.dlcEnabled = true + } + if (tag.includes(':')) { + state.raw.time = tag + } + if (tag.startsWith('etm')) { + const value = parseInt(tag.replace('etm', '')) + if (!isNaN(value)) { + state.raw.dayAcceleration = value + } + } + if (tag.startsWith('entm')) { + const value = parseInt(tag.replace('entm', '')) + if (!isNaN(value)) { + state.raw.nightAcceleration = value + } + } + } + } + + readDayzMods (/** Buffer */ buffer) { + if (!buffer.length) { + return {} + } + + this.logger.debug('DAYZ BUFFER') + this.logger.debug(buffer) + + const reader = this.reader(buffer) + const version = this.readDayzByte(reader) + const overflow = this.readDayzByte(reader) + const dlc1 = this.readDayzByte(reader) + const dlc2 = this.readDayzByte(reader) + this.logger.debug('version ' + version) + this.logger.debug('overflow ' + overflow) + this.logger.debug('dlc1 ' + dlc1) + this.logger.debug('dlc2 ' + dlc2) + if (dlc1) { + const unknown = this.readDayzUint(reader, 4) // ? + this.logger.debug('unknown ' + unknown) + } + if (dlc2) { + const unknown = this.readDayzUint(reader, 4) // ? + this.logger.debug('unknown ' + unknown) + } + const mods = [] + mods.push(...this.readDayzModsSection(reader, true)) + mods.push(...this.readDayzModsSection(reader, false)) + this.logger.debug('dayz buffer rest:', reader.rest()) + return mods + } + + readDayzModsSection (/** Reader */ reader, withHeader) { + const out = [] + const count = this.readDayzByte(reader) + this.logger.debug('dayz mod section withHeader:' + withHeader + ' count:' + count) + for (let i = 0; i < count; i++) { + if (reader.done()) break + const mod = {} + if (withHeader) { + mod.unknown = this.readDayzUint(reader, 4) // ? + + // For some reason this is 4 on all of them, but doesn't exist on the last one? but only sometimes? + const offset = reader.offset() + const flag = this.readDayzByte(reader) + if (flag !== 4) reader.setOffset(offset) + + mod.workshopId = this.readDayzUint(reader, 4) + } + mod.title = this.readDayzString(reader) + this.logger.debug(mod) + out.push(mod) + } + return out + } + + readDayzUint (reader, bytes) { + const out = [] + for (let i = 0; i < bytes; i++) { + out.push(this.readDayzByte(reader)) + } + const buf = Buffer.from(out) + const r2 = this.reader(buf) + return r2.uint(bytes) + } + + readDayzByte (reader) { + const byte = reader.uint(1) + if (byte === 1) { + const byte2 = reader.uint(1) + if (byte2 === 1) return 1 + if (byte2 === 2) return 0 + if (byte2 === 3) return 0xff + return 0 // ? + } + return byte + } + + readDayzString (reader) { + const length = this.readDayzByte(reader) + const out = [] + for (let i = 0; i < length; i++) { + out.push(this.readDayzByte(reader)) + } + return Buffer.from(out).toString('utf8') + } +} diff --git a/protocols/index.js b/protocols/index.js index d512804..ab72a60 100644 --- a/protocols/index.js +++ b/protocols/index.js @@ -50,11 +50,12 @@ import ventrilo from './ventrilo.js' import warsow from './warsow.js' import beammpmaster from './beammpmaster.js' import beammp from './beammp.js' +import dayz from './dayz.js' export { armagetron, ase, asa, assettocorsa, battlefield, buildandshoot, cs2d, discord, doom3, eco, epic, ffow, fivem, gamespy1, gamespy2, gamespy3, geneshift, goldsrc, hexen2, jc2mp, kspdmp, mafia2mp, mafia2online, minecraft, minecraftbedrock, minecraftvanilla, mumble, mumbleping, nadeo, openttd, quake1, quake2, quake3, rfactor, samp, savage2, starmade, starsiege, teamspeak2, teamspeak3, terraria, tribes1, tribes1master, unreal2, ut3, valve, - vcmp, ventrilo, warsow, eldewrito, beammpmaster, beammp + vcmp, ventrilo, warsow, eldewrito, beammpmaster, beammp, dayz } diff --git a/protocols/valve.js b/protocols/valve.js index 800091b..acb6d2a 100644 --- a/protocols/valve.js +++ b/protocols/valve.js @@ -6,7 +6,6 @@ const AppId = { Squad: 393380, Bat1944: 489940, Ship: 2400, - DayZ: 221100, Rust: 252490, CSGO: 730, CS_Source: 240, @@ -132,43 +131,6 @@ export default class valve extends Core { this.goldsrcSplits = true } - // DayZ embeds some of the server information inside the tags attribute - if (appId === AppId.DayZ) { - if (state.raw.tags) { - state.raw.dlcEnabled = false - state.raw.firstPerson = false - for (const tag of state.raw.tags) { - if (tag.startsWith('lqs')) { - const value = parseInt(tag.replace('lqs', '')) - if (!isNaN(value)) { - state.raw.queue = value - } - } - if (tag.includes('no3rd')) { - state.raw.firstPerson = true - } - if (tag.includes('isDLC')) { - state.raw.dlcEnabled = true - } - if (tag.includes(':')) { - state.raw.time = tag - } - if (tag.startsWith('etm')) { - const value = parseInt(tag.replace('etm', '')) - if (!isNaN(value)) { - state.raw.dayAcceleration = value - } - } - if (tag.startsWith('entm')) { - const value = parseInt(tag.replace('entm', '')) - if (!isNaN(value)) { - state.raw.nightAcceleration = value - } - } - } - } - } - if (appId === AppId.Rust) { if (state.raw.tags) { for (const tag of state.raw.tags) { @@ -250,7 +212,6 @@ export default class valve extends Core { const rules = {} state.raw.rules = rules - const dayZPayload = [] this.logger.debug('Requesting rules ...') @@ -260,38 +221,17 @@ export default class valve extends Core { const reader = this.reader(b) while (!reader.done()) { const key = reader.string() - const value = reader.string() - rules[key] = value + rules[key] = reader.string() } } else { const b = await this.sendPacket(0x56, null, 0x45, true) if (b === null) return // timed out - the server probably has rules disabled - let dayZPayloadEnded = false - const reader = this.reader(b) const num = reader.uint(2) for (let i = 0; i < num; i++) { - if (appId === AppId.DayZ && !dayZPayloadEnded) { - const one = reader.uint(1) - const two = reader.uint(1) - const three = reader.uint(1) - if (one !== 0 && two !== 0 && three === 0) { - while (true) { - const byte = reader.uint(1) - if (byte === 0) break - dayZPayload.push(byte) - } - continue - } else { - reader.skip(-3) - dayZPayloadEnded = true - } - } - const key = reader.string() - const value = reader.string() - rules[key] = value + rules[key] = reader.string() } } @@ -323,97 +263,6 @@ export default class valve extends Core { state.password = true } } - - if (appId === AppId.DayZ) { - state.raw.dayzMods = this.readDayzMods(Buffer.from(dayZPayload)) - } - } - - readDayzMods (/** Buffer */ buffer) { - if (!buffer.length) { - return {} - } - - this.logger.debug('DAYZ BUFFER') - this.logger.debug(buffer) - - const reader = this.reader(buffer) - const version = this.readDayzByte(reader) - const overflow = this.readDayzByte(reader) - const dlc1 = this.readDayzByte(reader) - const dlc2 = this.readDayzByte(reader) - this.logger.debug('version ' + version) - this.logger.debug('overflow ' + overflow) - this.logger.debug('dlc1 ' + dlc1) - this.logger.debug('dlc2 ' + dlc2) - if (dlc1) { - const unknown = this.readDayzUint(reader, 4) // ? - this.logger.debug('unknown ' + unknown) - } - if (dlc2) { - const unknown = this.readDayzUint(reader, 4) // ? - this.logger.debug('unknown ' + unknown) - } - const mods = [] - mods.push(...this.readDayzModsSection(reader, true)) - mods.push(...this.readDayzModsSection(reader, false)) - this.logger.debug('dayz buffer rest:', reader.rest()) - return mods - } - - readDayzModsSection (/** Reader */ reader, withHeader) { - const out = [] - const count = this.readDayzByte(reader) - this.logger.debug('dayz mod section withHeader:' + withHeader + ' count:' + count) - for (let i = 0; i < count; i++) { - if (reader.done()) break - const mod = {} - if (withHeader) { - mod.unknown = this.readDayzUint(reader, 4) // ? - - // For some reason this is 4 on all of them, but doesn't exist on the last one? but only sometimes? - const offset = reader.offset() - const flag = this.readDayzByte(reader) - if (flag !== 4) reader.setOffset(offset) - - mod.workshopId = this.readDayzUint(reader, 4) - } - mod.title = this.readDayzString(reader) - this.logger.debug(mod) - out.push(mod) - } - return out - } - - readDayzUint (reader, bytes) { - const out = [] - for (let i = 0; i < bytes; i++) { - out.push(this.readDayzByte(reader)) - } - const buf = Buffer.from(out) - const r2 = this.reader(buf) - return r2.uint(bytes) - } - - readDayzByte (reader) { - const byte = reader.uint(1) - if (byte === 1) { - const byte2 = reader.uint(1) - if (byte2 === 1) return 1 - if (byte2 === 2) return 0 - if (byte2 === 3) return 0xff - return 0 // ? - } - return byte - } - - readDayzString (reader) { - const length = this.readDayzByte(reader) - const out = [] - for (let i = 0; i < length; i++) { - out.push(this.readDayzByte(reader)) - } - return Buffer.from(out).toString('utf8') } async cleanup (/** Results */ state) {