chore: Convert all files to LF endings (#400)

* Convert to LF?

* Modify gitattributes

* Force LF

* Git --renormalize

* Update .gitattributes to enforce eol=lf

* Redo CRLF -> LF on remaining files
This commit is contained in:
CosminPerRam 2023-11-12 13:14:43 +02:00 committed by GitHub
parent a8bc7521f6
commit cee42e7a88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 5697 additions and 5697 deletions

View file

@ -1,74 +1,74 @@
import dns from 'node:dns'
import punycode from 'punycode/punycode.js'
export default class DnsResolver {
/**
* @param {Logger} logger
*/
constructor (logger) {
this.logger = logger
}
isIp (host) {
return !!host.match(/\d+\.\d+\.\d+\.\d+/)
}
/**
* Response port will only be present if srv record was involved.
* @param {string} host
* @param {number} ipFamily
* @param {string=} srvRecordPrefix
* @returns {Promise<{address:string, port:number=}>}
*/
async resolve (host, ipFamily, srvRecordPrefix) {
this.logger.debug('DNS Lookup: ' + host)
if (this.isIp(host)) {
this.logger.debug('Raw IP Address: ' + host)
return { address: host }
}
const asciiForm = punycode.toASCII(host)
if (asciiForm !== host) {
this.logger.debug('Encoded punycode: ' + host + ' -> ' + asciiForm)
host = asciiForm
}
if (srvRecordPrefix) {
this.logger.debug('SRV Resolve: ' + srvRecordPrefix + '.' + host)
let records
try {
records = await dns.promises.resolve(srvRecordPrefix + '.' + host, 'SRV')
if (records.length >= 1) {
this.logger.debug('Found SRV Records: ', records)
const record = records[0]
const srvPort = record.port
const srvHost = record.name
if (srvHost === host) {
throw new Error('Loop in DNS SRV records')
}
return {
port: srvPort,
...await this.resolve(srvHost, ipFamily, srvRecordPrefix)
}
}
this.logger.debug('No SRV Record')
} catch (e) {
this.logger.debug(e)
}
}
this.logger.debug('Standard Resolve: ' + host)
const dnsResult = await dns.promises.lookup(host, ipFamily)
// For some reason, this sometimes returns a string address rather than an object.
// I haven't been able to reproduce, but it's been reported on the issue tracker.
let address
if (typeof dnsResult === 'string') {
address = dnsResult
} else {
address = dnsResult.address
}
this.logger.debug('Found address: ' + address)
return { address }
}
}
import dns from 'node:dns'
import punycode from 'punycode/punycode.js'
export default class DnsResolver {
/**
* @param {Logger} logger
*/
constructor (logger) {
this.logger = logger
}
isIp (host) {
return !!host.match(/\d+\.\d+\.\d+\.\d+/)
}
/**
* Response port will only be present if srv record was involved.
* @param {string} host
* @param {number} ipFamily
* @param {string=} srvRecordPrefix
* @returns {Promise<{address:string, port:number=}>}
*/
async resolve (host, ipFamily, srvRecordPrefix) {
this.logger.debug('DNS Lookup: ' + host)
if (this.isIp(host)) {
this.logger.debug('Raw IP Address: ' + host)
return { address: host }
}
const asciiForm = punycode.toASCII(host)
if (asciiForm !== host) {
this.logger.debug('Encoded punycode: ' + host + ' -> ' + asciiForm)
host = asciiForm
}
if (srvRecordPrefix) {
this.logger.debug('SRV Resolve: ' + srvRecordPrefix + '.' + host)
let records
try {
records = await dns.promises.resolve(srvRecordPrefix + '.' + host, 'SRV')
if (records.length >= 1) {
this.logger.debug('Found SRV Records: ', records)
const record = records[0]
const srvPort = record.port
const srvHost = record.name
if (srvHost === host) {
throw new Error('Loop in DNS SRV records')
}
return {
port: srvPort,
...await this.resolve(srvHost, ipFamily, srvRecordPrefix)
}
}
this.logger.debug('No SRV Record')
} catch (e) {
this.logger.debug(e)
}
}
this.logger.debug('Standard Resolve: ' + host)
const dnsResult = await dns.promises.lookup(host, ipFamily)
// For some reason, this sometimes returns a string address rather than an object.
// I haven't been able to reproduce, but it's been reported on the issue tracker.
let address
if (typeof dnsResult === 'string') {
address = dnsResult
} else {
address = dnsResult.address
}
this.logger.debug('Found address: ' + address)
return { address }
}
}

View file

@ -1,114 +1,114 @@
import * as path from 'node:path'
import { fileURLToPath } from 'node:url'
import * as fs from 'node:fs'
export default class GameResolver {
constructor () {
const loaded = this._readGames()
this.gamesByKey = loaded.gamesByKey
this.games = loaded.games
}
lookup (type) {
if (!type) { throw Error('No game specified') }
if (type.startsWith('protocol-')) {
return {
protocol: type.substring(9)
}
}
const game = this.gamesByKey.get(type)
if (!game) { throw Error('Invalid game: ' + type) }
return game.options
}
printReadme () {
let out = ''
out += '| GameDig Type ID | Name | See Also\n'
out += '|---|---|---\n'
const sorted = this.games
.filter(game => game.pretty)
.sort((a, b) => {
return a.pretty.localeCompare(b.pretty)
})
for (const game of sorted) {
const keysOut = game.keys.map(key => '`' + key + '`').join('<br>')
out += '| ' + keysOut.padEnd(10, ' ') + ' ' +
'| ' + game.pretty
const notes = []
if (game.extra.doc_notes) {
notes.push('[Notes](#' + game.extra.doc_notes + ')')
}
if (game.options.protocol === 'valve') {
notes.push('[Valve Protocol](#valve)')
}
if (notes.length) {
out += ' | ' + notes.join(', ')
}
out += '\n'
}
return out
}
_readGames () {
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const gamesFile = path.normalize(__dirname + '/../games.txt')
const lines = fs.readFileSync(gamesFile, 'utf8').split('\n')
const gamesByKey = new Map()
const games = []
for (let line of lines) {
// strip comments
const comment = line.indexOf('#')
if (comment !== -1) line = line.substring(0, comment)
line = line.trim()
if (!line) continue
const split = line.split('|')
const keys = split[0].trim().split(',')
const name = split[1].trim()
const options = this._parseList(split[3])
options.protocol = split[2].trim()
const extra = this._parseList(split[4])
const game = {
keys,
pretty: name,
options,
extra
}
for (const key of keys) {
gamesByKey.set(key, game)
}
games.push(game)
}
return { gamesByKey, games }
}
_parseList (str) {
if (!str) { return {} }
const out = {}
for (const one of str.split(',')) {
const equals = one.indexOf('=')
const key = equals === -1 ? one : one.substring(0, equals)
/** @type {string|number|boolean} */
let value = equals === -1 ? '' : one.substring(equals + 1)
if (value === 'true' || value === '') { value = true } else if (value === 'false') { value = false } else if (!isNaN(parseInt(value))) { value = parseInt(value) }
out[key] = value
}
return out
}
}
import * as path from 'node:path'
import { fileURLToPath } from 'node:url'
import * as fs from 'node:fs'
export default class GameResolver {
constructor () {
const loaded = this._readGames()
this.gamesByKey = loaded.gamesByKey
this.games = loaded.games
}
lookup (type) {
if (!type) { throw Error('No game specified') }
if (type.startsWith('protocol-')) {
return {
protocol: type.substring(9)
}
}
const game = this.gamesByKey.get(type)
if (!game) { throw Error('Invalid game: ' + type) }
return game.options
}
printReadme () {
let out = ''
out += '| GameDig Type ID | Name | See Also\n'
out += '|---|---|---\n'
const sorted = this.games
.filter(game => game.pretty)
.sort((a, b) => {
return a.pretty.localeCompare(b.pretty)
})
for (const game of sorted) {
const keysOut = game.keys.map(key => '`' + key + '`').join('<br>')
out += '| ' + keysOut.padEnd(10, ' ') + ' ' +
'| ' + game.pretty
const notes = []
if (game.extra.doc_notes) {
notes.push('[Notes](#' + game.extra.doc_notes + ')')
}
if (game.options.protocol === 'valve') {
notes.push('[Valve Protocol](#valve)')
}
if (notes.length) {
out += ' | ' + notes.join(', ')
}
out += '\n'
}
return out
}
_readGames () {
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const gamesFile = path.normalize(__dirname + '/../games.txt')
const lines = fs.readFileSync(gamesFile, 'utf8').split('\n')
const gamesByKey = new Map()
const games = []
for (let line of lines) {
// strip comments
const comment = line.indexOf('#')
if (comment !== -1) line = line.substring(0, comment)
line = line.trim()
if (!line) continue
const split = line.split('|')
const keys = split[0].trim().split(',')
const name = split[1].trim()
const options = this._parseList(split[3])
options.protocol = split[2].trim()
const extra = this._parseList(split[4])
const game = {
keys,
pretty: name,
options,
extra
}
for (const key of keys) {
gamesByKey.set(key, game)
}
games.push(game)
}
return { gamesByKey, games }
}
_parseList (str) {
if (!str) { return {} }
const out = {}
for (const one of str.split(',')) {
const equals = one.indexOf('=')
const key = equals === -1 ? one : one.substring(0, equals)
/** @type {string|number|boolean} */
let value = equals === -1 ? '' : one.substring(equals + 1)
if (value === 'true' || value === '') { value = true } else if (value === 'false') { value = false } else if (!isNaN(parseInt(value))) { value = parseInt(value) }
out[key] = value
}
return out
}
}

View file

@ -1,72 +1,72 @@
import { createSocket } from 'node:dgram'
import { debugDump } from './HexUtil.js'
import { promisify } from 'node:util'
import Logger from './Logger.js'
export default class GlobalUdpSocket {
constructor ({ port }) {
this.socket = null
this.callbacks = new Set()
this.debuggingCallbacks = new Set()
this.logger = new Logger()
this.port = port
}
async _getSocket () {
if (!this.socket) {
const udpSocket = createSocket({
type: 'udp4',
reuseAddr: true
})
// https://github.com/denoland/deno/issues/20138
if (typeof Deno === "undefined") {
udpSocket.unref();
}
udpSocket.on('message', (buffer, rinfo) => {
const fromAddress = rinfo.address
const fromPort = rinfo.port
this.logger.debug(log => {
log(fromAddress + ':' + fromPort + ' <--UDP(' + this.port + ')')
log(debugDump(buffer))
})
for (const callback of this.callbacks) {
callback(fromAddress, fromPort, buffer)
}
})
udpSocket.on('error', e => {
this.logger.debug('UDP ERROR:', e)
})
await promisify(udpSocket.bind).bind(udpSocket)(this.port)
this.port = udpSocket.address().port
this.socket = udpSocket
}
return this.socket
}
async send (buffer, address, port, debug) {
const socket = await this._getSocket()
if (debug) {
this.logger._print(log => {
log(address + ':' + port + ' UDP(' + this.port + ')-->')
log(debugDump(buffer))
})
}
await promisify(socket.send).bind(socket)(buffer, 0, buffer.length, port, address)
}
addCallback (callback, debug) {
this.callbacks.add(callback)
if (debug) {
this.debuggingCallbacks.add(callback)
this.logger.debugEnabled = true
}
}
removeCallback (callback) {
this.callbacks.delete(callback)
this.debuggingCallbacks.delete(callback)
this.logger.debugEnabled = this.debuggingCallbacks.size > 0
}
}
import { createSocket } from 'node:dgram'
import { debugDump } from './HexUtil.js'
import { promisify } from 'node:util'
import Logger from './Logger.js'
export default class GlobalUdpSocket {
constructor ({ port }) {
this.socket = null
this.callbacks = new Set()
this.debuggingCallbacks = new Set()
this.logger = new Logger()
this.port = port
}
async _getSocket () {
if (!this.socket) {
const udpSocket = createSocket({
type: 'udp4',
reuseAddr: true
})
// https://github.com/denoland/deno/issues/20138
if (typeof Deno === "undefined") {
udpSocket.unref();
}
udpSocket.on('message', (buffer, rinfo) => {
const fromAddress = rinfo.address
const fromPort = rinfo.port
this.logger.debug(log => {
log(fromAddress + ':' + fromPort + ' <--UDP(' + this.port + ')')
log(debugDump(buffer))
})
for (const callback of this.callbacks) {
callback(fromAddress, fromPort, buffer)
}
})
udpSocket.on('error', e => {
this.logger.debug('UDP ERROR:', e)
})
await promisify(udpSocket.bind).bind(udpSocket)(this.port)
this.port = udpSocket.address().port
this.socket = udpSocket
}
return this.socket
}
async send (buffer, address, port, debug) {
const socket = await this._getSocket()
if (debug) {
this.logger._print(log => {
log(address + ':' + port + ' UDP(' + this.port + ')-->')
log(debugDump(buffer))
})
}
await promisify(socket.send).bind(socket)(buffer, 0, buffer.length, port, address)
}
addCallback (callback, debug) {
this.callbacks.add(callback)
if (debug) {
this.debuggingCallbacks.add(callback)
this.logger.debugEnabled = true
}
}
removeCallback (callback) {
this.callbacks.delete(callback)
this.debuggingCallbacks.delete(callback)
this.logger.debugEnabled = this.debuggingCallbacks.size > 0
}
}

View file

@ -1,20 +1,20 @@
/** @param {Buffer} buffer */
export const debugDump = (buffer) => {
let hexLine = ''
let chrLine = ''
let out = ''
out += 'Buffer length: ' + buffer.length + ' bytes\n'
for (let i = 0; i < buffer.length; i++) {
const sliced = buffer.slice(i, i + 1)
hexLine += sliced.toString('hex') + ' '
let chr = sliced.toString()
if (chr < ' ' || chr > '~') chr = ' '
chrLine += chr + ' '
if (hexLine.length > 60 || i === buffer.length - 1) {
out += hexLine + '\n'
out += chrLine + '\n'
hexLine = chrLine = ''
}
}
return out
}
/** @param {Buffer} buffer */
export const debugDump = (buffer) => {
let hexLine = ''
let chrLine = ''
let out = ''
out += 'Buffer length: ' + buffer.length + ' bytes\n'
for (let i = 0; i < buffer.length; i++) {
const sliced = buffer.slice(i, i + 1)
hexLine += sliced.toString('hex') + ' '
let chr = sliced.toString()
if (chr < ' ' || chr > '~') chr = ' '
chrLine += chr + ' '
if (hexLine.length > 60 || i === buffer.length - 1) {
out += hexLine + '\n'
out += chrLine + '\n'
hexLine = chrLine = ''
}
}
return out
}

View file

@ -1,45 +1,45 @@
import { debugDump } from './HexUtil.js'
import { Buffer} from 'node:buffer'
export default class Logger {
constructor () {
this.debugEnabled = false
this.prefix = ''
}
debug (...args) {
if (!this.debugEnabled) return
this._print(...args)
}
_print (...args) {
try {
const strings = this._convertArgsToStrings(...args)
if (strings.length) {
if (this.prefix) {
strings.unshift(this.prefix)
}
console.log(...strings)
}
} catch (e) {
console.log('Error while logging: ' + e)
}
}
_convertArgsToStrings (...args) {
const out = []
for (const arg of args) {
if (arg instanceof Error) {
out.push(arg.stack)
} else if (arg instanceof Buffer) {
out.push(debugDump(arg))
} else if (typeof arg === 'function') {
const result = arg.call(undefined, (...args) => this._print(...args))
if (result !== undefined) out.push(...this._convertArgsToStrings(result))
} else {
out.push(arg)
}
}
return out
}
}
import { debugDump } from './HexUtil.js'
import { Buffer} from 'node:buffer'
export default class Logger {
constructor () {
this.debugEnabled = false
this.prefix = ''
}
debug (...args) {
if (!this.debugEnabled) return
this._print(...args)
}
_print (...args) {
try {
const strings = this._convertArgsToStrings(...args)
if (strings.length) {
if (this.prefix) {
strings.unshift(this.prefix)
}
console.log(...strings)
}
} catch (e) {
console.log('Error while logging: ' + e)
}
}
_convertArgsToStrings (...args) {
const out = []
for (const arg of args) {
if (arg instanceof Error) {
out.push(arg.stack)
} else if (arg instanceof Buffer) {
out.push(debugDump(arg))
} else if (typeof arg === 'function') {
const result = arg.call(undefined, (...args) => this._print(...args))
if (result !== undefined) out.push(...this._convertArgsToStrings(result))
} else {
out.push(arg)
}
}
return out
}
}

View file

@ -1,18 +1,18 @@
export default class Promises {
static createTimeout (timeoutMs, timeoutMsg) {
let cancel = null
const wrapped = new Promise((resolve, reject) => {
const timeout = setTimeout(
() => {
reject(new Error(timeoutMsg + ' - Timed out after ' + timeoutMs + 'ms'))
},
timeoutMs
)
cancel = () => {
clearTimeout(timeout)
}
})
wrapped.cancel = cancel
return wrapped
}
}
export default class Promises {
static createTimeout (timeoutMs, timeoutMsg) {
let cancel = null
const wrapped = new Promise((resolve, reject) => {
const timeout = setTimeout(
() => {
reject(new Error(timeoutMsg + ' - Timed out after ' + timeoutMs + 'ms'))
},
timeoutMs
)
cancel = () => {
clearTimeout(timeout)
}
})
wrapped.cancel = cancel
return wrapped
}
}

View file

@ -1,7 +1,7 @@
import * as Protocols from '../protocols/index.js'
export const getProtocol = (protocolId) => {
if (!(protocolId in Protocols)) { throw Error('Protocol definition file missing: ' + protocolId) }
return new Protocols[protocolId]()
}
import * as Protocols from '../protocols/index.js'
export const getProtocol = (protocolId) => {
if (!(protocolId in Protocols)) { throw Error('Protocol definition file missing: ' + protocolId) }
return new Protocols[protocolId]()
}

View file

@ -1,104 +1,104 @@
import GameResolver from './GameResolver.js'
import { getProtocol } from './ProtocolResolver.js'
import GlobalUdpSocket from './GlobalUdpSocket.js'
const defaultOptions = {
socketTimeout: 2000,
attemptTimeout: 10000,
maxAttempts: 1,
ipFamily: 0
}
export default class QueryRunner {
constructor (runnerOpts = {}) {
this.udpSocket = new GlobalUdpSocket({
port: runnerOpts.listenUdpPort
})
this.gameResolver = new GameResolver()
}
async run (userOptions) {
for (const key of Object.keys(userOptions)) {
const value = userOptions[key]
if (['port', 'ipFamily'].includes(key)) {
userOptions[key] = parseInt(value)
}
}
const {
port_query: gameQueryPort,
port_query_offset: gameQueryPortOffset,
...gameOptions
} = this.gameResolver.lookup(userOptions.type)
const attempts = []
const optionsCollection = {
...defaultOptions,
...gameOptions,
...userOptions
}
const addAttemptWithPort = port => {
attempts.push({
...optionsCollection,
port
})
}
if (userOptions.port) {
if (!userOptions.givenPortOnly) {
if (gameQueryPortOffset) { addAttemptWithPort(userOptions.port + gameQueryPortOffset) }
if (userOptions.port === gameOptions.port && gameQueryPort) { addAttemptWithPort(gameQueryPort) }
}
attempts.push(optionsCollection)
} else if (gameQueryPort) {
addAttemptWithPort(gameQueryPort)
} else if (gameOptions.port) {
addAttemptWithPort(gameOptions.port + (gameQueryPortOffset || 0))
} else {
// Hopefully the request doesn't need a port. If it does, it'll fail when making the request.
attempts.push(optionsCollection)
}
const numRetries = userOptions.maxAttempts || gameOptions.maxAttempts || defaultOptions.maxAttempts
let attemptNum = 0
const errors = []
for (const attempt of attempts) {
for (let retry = 0; retry < numRetries; retry++) {
attemptNum++
let result
try {
result = await this._attempt(attempt)
} catch (e) {
e.stack = 'Attempt #' + attemptNum + ' - Port=' + attempt.port + ' Retry=' + (retry) + ':\n' + e.stack
errors.push(e)
} finally {
// Deno doesn't support unref, so we must close the socket after every connection
// https://github.com/denoland/deno/issues/20138
if (typeof Deno !== "undefined") {
this.udpSocket?.socket?.close()
delete this.udpSocket
}
}
if (result) return result
}
}
const err = new Error('Failed all ' + errors.length + ' attempts')
for (const e of errors) {
err.stack += '\n' + e.stack
}
throw err
}
async _attempt (options) {
const core = getProtocol(options.protocol)
core.options = options
core.udpSocket = this.udpSocket
return await core.runOnceSafe()
}
}
import GameResolver from './GameResolver.js'
import { getProtocol } from './ProtocolResolver.js'
import GlobalUdpSocket from './GlobalUdpSocket.js'
const defaultOptions = {
socketTimeout: 2000,
attemptTimeout: 10000,
maxAttempts: 1,
ipFamily: 0
}
export default class QueryRunner {
constructor (runnerOpts = {}) {
this.udpSocket = new GlobalUdpSocket({
port: runnerOpts.listenUdpPort
})
this.gameResolver = new GameResolver()
}
async run (userOptions) {
for (const key of Object.keys(userOptions)) {
const value = userOptions[key]
if (['port', 'ipFamily'].includes(key)) {
userOptions[key] = parseInt(value)
}
}
const {
port_query: gameQueryPort,
port_query_offset: gameQueryPortOffset,
...gameOptions
} = this.gameResolver.lookup(userOptions.type)
const attempts = []
const optionsCollection = {
...defaultOptions,
...gameOptions,
...userOptions
}
const addAttemptWithPort = port => {
attempts.push({
...optionsCollection,
port
})
}
if (userOptions.port) {
if (!userOptions.givenPortOnly) {
if (gameQueryPortOffset) { addAttemptWithPort(userOptions.port + gameQueryPortOffset) }
if (userOptions.port === gameOptions.port && gameQueryPort) { addAttemptWithPort(gameQueryPort) }
}
attempts.push(optionsCollection)
} else if (gameQueryPort) {
addAttemptWithPort(gameQueryPort)
} else if (gameOptions.port) {
addAttemptWithPort(gameOptions.port + (gameQueryPortOffset || 0))
} else {
// Hopefully the request doesn't need a port. If it does, it'll fail when making the request.
attempts.push(optionsCollection)
}
const numRetries = userOptions.maxAttempts || gameOptions.maxAttempts || defaultOptions.maxAttempts
let attemptNum = 0
const errors = []
for (const attempt of attempts) {
for (let retry = 0; retry < numRetries; retry++) {
attemptNum++
let result
try {
result = await this._attempt(attempt)
} catch (e) {
e.stack = 'Attempt #' + attemptNum + ' - Port=' + attempt.port + ' Retry=' + (retry) + ':\n' + e.stack
errors.push(e)
} finally {
// Deno doesn't support unref, so we must close the socket after every connection
// https://github.com/denoland/deno/issues/20138
if (typeof Deno !== "undefined") {
this.udpSocket?.socket?.close()
delete this.udpSocket
}
}
if (result) return result
}
}
const err = new Error('Failed all ' + errors.length + ' attempts')
for (const e of errors) {
err.stack += '\n' + e.stack
}
throw err
}
async _attempt (options) {
const core = getProtocol(options.protocol)
core.options = options
core.udpSocket = this.udpSocket
return await core.runOnceSafe()
}
}

View file

@ -1,32 +1,32 @@
export class Player {
name = ''
raw = {}
constructor (data) {
if (typeof data === 'string') {
this.name = data
} else {
const { name, ...raw } = data
if (name) this.name = name
if (raw) this.raw = raw
}
}
}
export class Players extends Array {
push (data) {
super.push(new Player(data))
}
}
export class Results {
name = ''
map = ''
password = false
raw = {}
maxplayers = 0
players = new Players()
bots = new Players()
}
export class Player {
name = ''
raw = {}
constructor (data) {
if (typeof data === 'string') {
this.name = data
} else {
const { name, ...raw } = data
if (name) this.name = name
if (raw) this.raw = raw
}
}
}
export class Players extends Array {
push (data) {
super.push(new Player(data))
}
}
export class Results {
name = ''
map = ''
password = false
raw = {}
maxplayers = 0
players = new Players()
bots = new Players()
}

View file

@ -1,23 +1,23 @@
import QueryRunner from './QueryRunner.js'
let singleton = null
export default class Gamedig {
constructor (runnerOpts) {
this.queryRunner = new QueryRunner(runnerOpts)
}
async query (userOptions) {
return await this.queryRunner.run(userOptions)
}
static getInstance () {
if (!singleton) { singleton = new Gamedig() }
return singleton
}
static async query (...args) {
return await Gamedig.getInstance().query(...args)
}
}
import QueryRunner from './QueryRunner.js'
let singleton = null
export default class Gamedig {
constructor (runnerOpts) {
this.queryRunner = new QueryRunner(runnerOpts)
}
async query (userOptions) {
return await this.queryRunner.run(userOptions)
}
static getInstance () {
if (!singleton) { singleton = new Gamedig() }
return singleton
}
static async query (...args) {
return await Gamedig.getInstance().query(...args)
}
}

View file

@ -1,172 +1,172 @@
import Iconv from 'iconv-lite'
import Long from 'long'
import { Buffer } from 'node:buffer'
import Varint from 'varint'
function readUInt64BE (buffer, offset) {
const high = buffer.readUInt32BE(offset)
const low = buffer.readUInt32BE(offset + 4)
return new Long(low, high, true)
}
function readUInt64LE (buffer, offset) {
const low = buffer.readUInt32LE(offset)
const high = buffer.readUInt32LE(offset + 4)
return new Long(low, high, true)
}
export default class Reader {
/**
* @param {Core} query
* @param {Buffer} buffer
**/
constructor (query, buffer) {
this.defaultEncoding = query.options.encoding || query.encoding
this.defaultDelimiter = query.delimiter
this.defaultByteOrder = query.byteorder
this.buffer = buffer
this.i = 0
}
setOffset (offset) {
this.i = offset
}
offset () {
return this.i
}
skip (i) {
this.i += i
}
pascalString (bytesForSize, adjustment = 0) {
const length = this.uint(bytesForSize) + adjustment
return this.string(length)
}
string (arg) {
let encoding = this.defaultEncoding
let length = null
let delimiter = this.defaultDelimiter
if (typeof arg === 'string') delimiter = arg
else if (typeof arg === 'number') length = arg
else if (typeof arg === 'object') {
if ('encoding' in arg) encoding = arg.encoding
if ('length' in arg) length = arg.length
if ('delimiter' in arg) delimiter = arg.delimiter
}
if (encoding === 'latin1') encoding = 'win1252'
const start = this.i
let end = start
if (length === null) {
// terminated by the delimiter
let delim = delimiter
if (typeof delim === 'string') delim = delim.charCodeAt(0)
while (true) {
if (end >= this.buffer.length) {
end = this.buffer.length
break
}
if (this.buffer.readUInt8(end) === delim) break
end++
}
this.i = end + 1
} else if (length <= 0) {
return ''
} else {
end = start + length
if (end >= this.buffer.length) {
end = this.buffer.length
}
this.i = end
}
const slice = this.buffer.slice(start, end)
const enc = encoding
if (enc === 'utf8' || enc === 'ucs2' || enc === 'binary') {
return slice.toString(enc)
} else {
return Iconv.decode(slice, enc)
}
}
int (bytes) {
let r = 0
if (this.remaining() >= bytes) {
if (this.defaultByteOrder === 'be') {
if (bytes === 1) r = this.buffer.readInt8(this.i)
else if (bytes === 2) r = this.buffer.readInt16BE(this.i)
else if (bytes === 4) r = this.buffer.readInt32BE(this.i)
} else {
if (bytes === 1) r = this.buffer.readInt8(this.i)
else if (bytes === 2) r = this.buffer.readInt16LE(this.i)
else if (bytes === 4) r = this.buffer.readInt32LE(this.i)
}
}
this.i += bytes
return r
}
/** @returns {number} */
uint (bytes) {
let r = 0
if (this.remaining() >= bytes) {
if (this.defaultByteOrder === 'be') {
if (bytes === 1) r = this.buffer.readUInt8(this.i)
else if (bytes === 2) r = this.buffer.readUInt16BE(this.i)
else if (bytes === 4) r = this.buffer.readUInt32BE(this.i)
else if (bytes === 8) r = readUInt64BE(this.buffer, this.i)
} else {
if (bytes === 1) r = this.buffer.readUInt8(this.i)
else if (bytes === 2) r = this.buffer.readUInt16LE(this.i)
else if (bytes === 4) r = this.buffer.readUInt32LE(this.i)
else if (bytes === 8) r = readUInt64LE(this.buffer, this.i)
}
}
this.i += bytes
return r
}
float () {
let r = 0
if (this.remaining() >= 4) {
if (this.defaultByteOrder === 'be') r = this.buffer.readFloatBE(this.i)
else r = this.buffer.readFloatLE(this.i)
}
this.i += 4
return r
}
varint () {
const out = Varint.decode(this.buffer, this.i)
this.i += Varint.decode.bytes
return out
}
/** @returns Buffer */
part (bytes) {
let r
if (this.remaining() >= bytes) {
r = this.buffer.slice(this.i, this.i + bytes)
} else {
r = Buffer.from([])
}
this.i += bytes
return r
}
remaining () {
return this.buffer.length - this.i
}
rest () {
return this.buffer.slice(this.i)
}
done () {
return this.i >= this.buffer.length
}
}
import Iconv from 'iconv-lite'
import Long from 'long'
import { Buffer } from 'node:buffer'
import Varint from 'varint'
function readUInt64BE (buffer, offset) {
const high = buffer.readUInt32BE(offset)
const low = buffer.readUInt32BE(offset + 4)
return new Long(low, high, true)
}
function readUInt64LE (buffer, offset) {
const low = buffer.readUInt32LE(offset)
const high = buffer.readUInt32LE(offset + 4)
return new Long(low, high, true)
}
export default class Reader {
/**
* @param {Core} query
* @param {Buffer} buffer
**/
constructor (query, buffer) {
this.defaultEncoding = query.options.encoding || query.encoding
this.defaultDelimiter = query.delimiter
this.defaultByteOrder = query.byteorder
this.buffer = buffer
this.i = 0
}
setOffset (offset) {
this.i = offset
}
offset () {
return this.i
}
skip (i) {
this.i += i
}
pascalString (bytesForSize, adjustment = 0) {
const length = this.uint(bytesForSize) + adjustment
return this.string(length)
}
string (arg) {
let encoding = this.defaultEncoding
let length = null
let delimiter = this.defaultDelimiter
if (typeof arg === 'string') delimiter = arg
else if (typeof arg === 'number') length = arg
else if (typeof arg === 'object') {
if ('encoding' in arg) encoding = arg.encoding
if ('length' in arg) length = arg.length
if ('delimiter' in arg) delimiter = arg.delimiter
}
if (encoding === 'latin1') encoding = 'win1252'
const start = this.i
let end = start
if (length === null) {
// terminated by the delimiter
let delim = delimiter
if (typeof delim === 'string') delim = delim.charCodeAt(0)
while (true) {
if (end >= this.buffer.length) {
end = this.buffer.length
break
}
if (this.buffer.readUInt8(end) === delim) break
end++
}
this.i = end + 1
} else if (length <= 0) {
return ''
} else {
end = start + length
if (end >= this.buffer.length) {
end = this.buffer.length
}
this.i = end
}
const slice = this.buffer.slice(start, end)
const enc = encoding
if (enc === 'utf8' || enc === 'ucs2' || enc === 'binary') {
return slice.toString(enc)
} else {
return Iconv.decode(slice, enc)
}
}
int (bytes) {
let r = 0
if (this.remaining() >= bytes) {
if (this.defaultByteOrder === 'be') {
if (bytes === 1) r = this.buffer.readInt8(this.i)
else if (bytes === 2) r = this.buffer.readInt16BE(this.i)
else if (bytes === 4) r = this.buffer.readInt32BE(this.i)
} else {
if (bytes === 1) r = this.buffer.readInt8(this.i)
else if (bytes === 2) r = this.buffer.readInt16LE(this.i)
else if (bytes === 4) r = this.buffer.readInt32LE(this.i)
}
}
this.i += bytes
return r
}
/** @returns {number} */
uint (bytes) {
let r = 0
if (this.remaining() >= bytes) {
if (this.defaultByteOrder === 'be') {
if (bytes === 1) r = this.buffer.readUInt8(this.i)
else if (bytes === 2) r = this.buffer.readUInt16BE(this.i)
else if (bytes === 4) r = this.buffer.readUInt32BE(this.i)
else if (bytes === 8) r = readUInt64BE(this.buffer, this.i)
} else {
if (bytes === 1) r = this.buffer.readUInt8(this.i)
else if (bytes === 2) r = this.buffer.readUInt16LE(this.i)
else if (bytes === 4) r = this.buffer.readUInt32LE(this.i)
else if (bytes === 8) r = readUInt64LE(this.buffer, this.i)
}
}
this.i += bytes
return r
}
float () {
let r = 0
if (this.remaining() >= 4) {
if (this.defaultByteOrder === 'be') r = this.buffer.readFloatBE(this.i)
else r = this.buffer.readFloatLE(this.i)
}
this.i += 4
return r
}
varint () {
const out = Varint.decode(this.buffer, this.i)
this.i += Varint.decode.bytes
return out
}
/** @returns Buffer */
part (bytes) {
let r
if (this.remaining() >= bytes) {
r = this.buffer.slice(this.i, this.i + bytes)
} else {
r = Buffer.from([])
}
this.i += bytes
return r
}
remaining () {
return this.buffer.length - this.i
}
rest () {
return this.buffer.slice(this.i)
}
done () {
return this.i >= this.buffer.length
}
}