From 39d7f06bd9a49008c4f7a224d02e739406677ab2 Mon Sep 17 00:00:00 2001 From: RattleSN4K3 Date: Sat, 28 Sep 2024 01:39:14 +0200 Subject: [PATCH] feat: add Toxikk support (#641) * Add support for Toxikk, using Valve protocol plus UnrealEngine3 rules parsing * docs: update CHANGELOG for Toxikk * docs: update GAMES_LIST for Toxikk --- CHANGELOG.md | 1 + GAMES_LIST.md | 1 + lib/games.js | 9 ++++ protocols/index.js | 3 +- protocols/toxikk.js | 122 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 protocols/toxikk.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 9340dc92..721af3ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Feat: Replaced `punycode` package usage with `url.domainToASCII` (#630). * Feat: World of Padman (2007) - Added support (#636) * Feat: Update Soldat protocol (#642) +* Feat: TOXIKK (2016) - Added support (#641) ## 5.1.3 * Fix: `Deus Ex` using the wrong protocol (#621) diff --git a/GAMES_LIST.md b/GAMES_LIST.md index 0dfa39f2..ee78fb0c 100644 --- a/GAMES_LIST.md +++ b/GAMES_LIST.md @@ -314,6 +314,7 @@ | toh | Take On Helicopters | | | tonolf | The Operative: No One Lives Forever | | | towerunite | Tower Unite | [Valve Protocol](#valve) | +| toxikk | TOXIKK | | | trackmania2 | Trackmania 2 | [Notes](#nadeo) | | trackmaniaforever | Trackmania Forever | [Notes](#nadeo) | | tremulous | Tremulous | | diff --git a/lib/games.js b/lib/games.js index 1bec7153..ddfe220f 100644 --- a/lib/games.js +++ b/lib/games.js @@ -3063,6 +3063,15 @@ export const games = { protocol: 'valve' } }, + toxikk: { + name: 'TOXIKK', + release_year: 2016, + options: { + port: 7777, + port_query: 27015, + protocol: 'toxikk' + } + }, turok2: { name: 'Turok 2', release_year: 1998, diff --git a/protocols/index.js b/protocols/index.js index b1bf4851..cd54f12b 100644 --- a/protocols/index.js +++ b/protocols/index.js @@ -45,6 +45,7 @@ import starsiege from './starsiege.js' import teamspeak2 from './teamspeak2.js' import teamspeak3 from './teamspeak3.js' import terraria from './terraria.js' +import toxikk from './toxikk.js' import tribes1 from './tribes1.js' import tribes1master from './tribes1master.js' import unreal2 from './unreal2.js' @@ -68,7 +69,7 @@ export { armagetron, ase, asa, assettocorsa, battlefield, buildandshoot, cs2d, discord, doom3, eco, epic, factorio, farmingsimulator, ffow, fivem, gamespy1, gamespy2, gamespy3, geneshift, goldsrc, gtasao, hexen2, jc2mp, kspdmp, mafia2mp, mafia2online, minecraft, minecraftbedrock, minecraftvanilla, minetest, mumble, mumbleping, nadeo, openttd, palworld, quake1, quake2, quake3, rfactor, ragemp, samp, - savage2, starmade, starsiege, teamspeak2, teamspeak3, terraria, tribes1, tribes1master, unreal2, ut3, valve, + savage2, starmade, starsiege, teamspeak2, teamspeak3, terraria, toxikk, tribes1, tribes1master, unreal2, ut3, valve, vcmp, ventrilo, warsow, eldewrito, beammpmaster, beammp, dayz, theisleevrima, xonotic, altvmp, vintagestorymaster, vintagestory, soldat } diff --git a/protocols/toxikk.js b/protocols/toxikk.js new file mode 100644 index 00000000..7a2613f0 --- /dev/null +++ b/protocols/toxikk.js @@ -0,0 +1,122 @@ +import valve from './valve.js' +// import { TranslateMapUT3 } from './ut3.js' + +export const TranslateMapToxikk = Object.freeze({ + // add UT3 values + // Toxikk is using UDK which includes basic implementation of UT3 + // ...TranslateMapUT3, + + // Old UT3/UDK properties + p1073741825: 'map', // UC Source='CRZMapName' + p1073741826: 'game', // UC Source='CustomGameMode' + p268435704: 'frag_limit', // UC Source='TimeLimit' + p268435705: 'time_limit', // UC Source='TimeLimit' + p268435703: 'numbots', + p1073741827: 'servername', // UC Source='ServerDescription' + p268435717: false, // 'stock_mutators' // UC Source='OfficialMutators' // Note: "EpicMutators" are bit-masked and require Full UE3 license (C++) to flag mutators, stock mutators could be always 0, ignore for now + p1073741828: 'custom_mutators', // UC Source='CustomMutators' + + // Toxikk specific localized settings (commented name is based on source code) + s32779: 'GameMode', // 8=Custom, anything else is UT3/UDK + s0: 'bot_skill', // UC Source='BotSkill', // 0=Ridiculous 1=Novice 2=Average 3=Experienced 4=Adept 5=Masterful 6=Inhuman 7=Godlike + s1: false, // UC Source='Map' // Note: set as '0' mostly, generally the index will state the official map, ignore for now + s6: 'pure_server', // UC Source='PureServer', // bool + s7: 'password', // UC Source='LockedServer', // bool + s8: 'vs_bots', // UC Source='VsBots', // 0=Disabled 1="2:1" 2="1:1" 3="2:3" 4="1:2" 5="1:3" 6="1:4" + s9: 'Campaign', // bool + s10: 'force_respawn', // UC Source='ForceRespawn', // bool + s11: 'AllowKeyboard', // bool + s12: 'IsFullServer', // bool + s13: 'IsEmptyServer', // bool + s14: 'IsDedicated', // bool + s15: 'RankedServer', // 0=UnRanked 1=Ranked + s16: 'OnlyFullGamePlayers', // bool + s17: 'IgnoredByMatchmaking', // bool + s18: 'OfficialServer', // 0=Community 1=Official + s19: 'ModdingLevel', // 0=Unmodded 1=Server Modded 2=Client Modded + + // Toxikk properties + p268435706: 'MaxPlayers', + p268435707: 'MinNetPlayers', + p268435708: 'MinSkillClass', + p268435709: 'MaxSkillClass', + p1073741829: 'PLAYERIDS1', + p1073741830: 'PLAYERIDS2', + p1073741831: 'PLAYERIDS3', + p1073741832: 'PLAYERNAMES1', + p1073741833: 'PLAYERNAMES2', + p1073741834: 'PLAYERNAMES3', + p1073741837: 'PLAYERSCS', + p1073741838: 'PLAYERBadgeRanks', + p1073741839: 'GameVersion', + p1073741840: 'GameVoteList' +}) + +/** + * Implements the protocol for Toxikk, an UnrealEngine3 based game, + * using Valve protocol for query with additional UE3 properties/settings parsing + */ +export default class toxikk extends valve { + async run (state) { + if (!this.options.port) this.options.port = 27015 + await this.queryInfo(state) + await this.queryChallenge() + await this.queryPlayers(state) + await this.queryRules(state) + + this.processQueryInfo(state) + await this.cleanup(state) + } + + /** @override */ + async cleanup (state) { + // valve protocol attempts to put "hidden players" into player/bot array + // the bot data is not properly queried, therefore prevent push players into bots-array + const originalNumBots = state.raw.numbots + state.raw.numbots = null + super.cleanup(state) + state.raw.numbots = originalNumBots + } + + async queryRules (state) { + if (!this.options.requestRules) { + return + } + + const rules = {} + this.logger.debug('Requesting rules ...') + + const b = await this.sendPacket(0x56, null, 0x45, true) + if (b === null && !this.options.requestRulesRequired) return // timed out - the server probably has rules disabled + + const reader = this.reader(b) + const num = reader.uint(2) + for (let i = num - 1; i > 0 && !reader.done(); i--) { + const key = reader.string() + const value = reader.string() + if (reader.remaining() <= 0) { + // data might be corrupt in this case, keep existing rules + break + } + + rules[key] = value + } + + state.raw.rules = rules + } + + processQueryInfo (state) { + // move raw rules into root-raw object and attempt to translate properties + Object.assign(state.raw, state.raw.rules) + this.translate(state.raw, TranslateMapToxikk) + + const split = (a) => { + let s = a.split('\x1c') + s = s.filter((e) => { return e }) + return s + } + if ('custom_mutators' in state.raw) state.raw.custom_mutators = split(state.raw.custom_mutators) + if ('stock_mutators' in state.raw) state.raw.stock_mutators = split(state.raw.stock_mutators) + if ('map' in state.raw) state.map ??= state.raw.map + } +}