forked from BeepIsla/CSGO-Overwatch-Bot
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f754e5f
Showing
30 changed files
with
7,650 additions
and
0 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,5 @@ | ||
node_modules | ||
.vscode | ||
config.json | ||
GC2ClientHelloResponse*.json | ||
demofile.dem |
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,33 @@ | ||
const Protos = require("./protos.js"); | ||
const Events = require("events"); | ||
const Steam = require("steam"); | ||
|
||
module.exports = class GameCoordinator extends Events { | ||
constructor(steamUser) { | ||
super(); | ||
|
||
this._SteamUser = steamUser; | ||
this._GC = new Steam.SteamGameCoordinator(steamUser.client, 730); | ||
this.Protos = Protos; | ||
|
||
this._GC.on("message", (header, buffer, callback) => { | ||
this.emit("debug", { header: header, buffer: buffer }); | ||
|
||
if (header.msg == Protos.EGCBaseClientMsg.k_EMsgGCClientWelcome) { | ||
// Hello GC! 👋 | ||
// Stop ClientHello interval | ||
if (this._GCHelloInterval) clearInterval(this._GCHelloInterval); | ||
} | ||
}); | ||
}; | ||
|
||
start() { | ||
this._GCHelloInterval = setInterval(() => { | ||
// Client Hello | ||
this._GC.send({ | ||
msg: Protos.EGCBaseClientMsg.k_EMsgGCClientHello, | ||
proto: {} | ||
}, new Protos.CMsgClientHello({}).toBuffer()); | ||
}, (2 * 1000)); | ||
}; | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
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,83 @@ | ||
# CSGO Overwatch Bot | ||
|
||
Automatically solves Overwatch cases. Although **the detection algorythm for Aimbotting is really bad and there is no Wallhack/Griefing/etc detection**. | ||
|
||
When running this you will get some unhandled cases which we simply don't care about. | ||
|
||
# Aimbot detection | ||
|
||
Log the past X ticks, when the suspect gets a kill check all angles within the past X ticks. If the difference is above the threshold add an infraction. | ||
|
||
**This does NOT account for changes from -179 to 179 or similar and will count as an infraction** | ||
|
||
# Config | ||
|
||
- `account` | ||
- - `username`: The account name you use to log into that account | ||
- - `password`: The password for the account | ||
- `parsing` | ||
- - `maxTicks`: The maximum amount of ticks to check when the suspect gets a kill | ||
- - `threshold`: The maximum threshold between angles before adding an infraction | ||
- `verdict` | ||
- - `maxAimbot`: The maximum amount of infractions allowed before setting user as aimbotter | ||
|
||
# GameCoordinator | ||
|
||
How Overwatch data is exchanged between GC and Client. The data shown below has been collected from only a single Overwatch case, results may vary depending on the case we get. | ||
|
||
1. Send `CMsgGCCStrike15_v2_PlayerOverwatchCaseUpdate` | ||
- `reason` (Ex: `1`) | ||
- - I assume this tells the GC we are waiting for a new case | ||
2. Wait for `CMsgGCCStrike15_v2_PlayerOverwatchCaseAssignment` | ||
- `caseid` (Ex: `331349...`) | ||
- - Case identifier | ||
- `caseurl` (Ex: `http://replay192.valve.net...`) | ||
- - The download URL for the entire demo, not just the specific Overwatch | ||
- `verdict` (Ex: `0`) | ||
- - Unknown | ||
- `throttleseconds` (Ex: `4800`) | ||
- - I assume this is the time between cases, although 1.5 hours makes little sense | ||
- `suspectid` (Ex: `909243640`) | ||
- - Account ID of the suspect. (`[U:1:<SUSPECTID>]` for SteamID3) | ||
- `fractionid` (Ex: `11`) | ||
- - Unknown | ||
- `numrounds` (Ex: `19`) | ||
- - The round the demo starts at | ||
- `fractionrounds` (Ex: `8`) | ||
- - How many rounds we watch | ||
- `streakconvictions` (Ex: `1`) | ||
- - I assume this is how many correct convictions we got in a row. Although why would it be shown to us? | ||
3. *Download the demo* | ||
4. Send `CMsgGCCStrike15_v2_PlayerOverwatchCaseStatus` | ||
- `caseid` (Ex: `331349...`) | ||
- - Our case identifier | ||
- `statusid` (Ex: `1`) | ||
- - I assume this tells the GC we are currently assigned to a case and have successfully downloaded it | ||
5. *Analyse the demo file* | ||
6. Send `CMsgGCCStrike15_v2_PlayerOverwatchCaseUpdate` | ||
- `caseid` (Ex: `331349...`) | ||
- - Our case identifier | ||
- `suspectid` (Ex: `909243640`) | ||
- - Our suspect | ||
- `fractionid` (Ex: `11`) | ||
- - Unknown | ||
- `rpt_aimbot` (Ex: `1`) | ||
- - Did this user have aimbot? | ||
- `rpt_wallhack` (Ex: `1`) | ||
- - Did this user have wallhack? | ||
- `rpt_speedhack` (Ex: `1`) | ||
- - Did this user have any other external assistance? | ||
- `rpt_teamharm` (Ex: `0`) | ||
- - Did this user grief? | ||
- `reason` (Ex: `3`) | ||
- - I assume this tells the GC we have finished the demo and made our verdict | ||
7. Wait for `CMsgGCCStrike15_v2_PlayerOverwatchCaseAssignment` | ||
- `caseid` (Ex: `331349...`) | ||
- - Our case identifier | ||
- `verdict` (Ex: `2`) | ||
- - I assume this tells our client to remove the Overwatch button from the menu and that we are done with the current case | ||
- `throttleseconds` (Ex: `7`) | ||
- - I assume this tells our client to wait this many seconds before we are allowed a new case | ||
8. *Repeat* | ||
|
||
If we start the game with a Overwatch case already being assigned to us the game sends `CMsgGCCStrike15_v2_PlayerOverwatchCaseUpdate` with `reason` being `0`. The GC wil respond with the same assigned case we got earlier. It might also respond with a new case incase our old case has expired, this is untested though. |
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,14 @@ | ||
{ | ||
"account": { | ||
"username": "ExampleUsername", | ||
"password": "ExamplePassword", | ||
"sharedSecret": "" | ||
}, | ||
"parsing": { | ||
"maxTicks": 10, | ||
"threshold": 1.0 | ||
}, | ||
"verdict": { | ||
"maxAimbot": 20 | ||
} | ||
} |
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,188 @@ | ||
const SteamUser = require("steam-user"); | ||
const SteamTotp = require("steam-totp"); | ||
const fs = require("fs"); | ||
const request = require("request"); | ||
const demofile = require("demofile"); | ||
const bz2 = require("unbzip2-stream"); | ||
const SteamID = require("steamid"); | ||
const almostEqual = require("almost-equal"); | ||
const GameCoordinator = require("./GameCoordinator.js"); | ||
const config = require("./config.json"); | ||
|
||
const steamUser = new SteamUser(); | ||
const csgoUser = new GameCoordinator(steamUser); | ||
|
||
var logonSettings = { | ||
accountName: config.account.username, | ||
password: config.account.password | ||
}; | ||
|
||
if (config.account.sharedSecret && config.account.sharedSecret.length > 5) { | ||
logonSettings.twoFactorCode = SteamTotp.getAuthCode(config.account.sharedSecret); | ||
} | ||
|
||
steamUser.logOn(logonSettings); | ||
|
||
steamUser.once("loggedOn", () => { | ||
console.log("Logged in"); | ||
steamUser.setPersona(SteamUser.Steam.EPersonaState.Online); | ||
steamUser.gamesPlayed([ 730 ]); | ||
csgoUser.start(); | ||
|
||
setTimeout(() => { | ||
console.log("-----------------\nRequested Overwatch case"); | ||
csgoUser._GC.send({ | ||
msg: csgoUser.Protos.ECsgoGCMsg.k_EMsgGCCStrike15_v2_PlayerOverwatchCaseUpdate, | ||
proto: {} | ||
}, new csgoUser.Protos.CMsgGCCStrike15_v2_PlayerOverwatchCaseUpdate({ | ||
reason: 1 | ||
}).toBuffer()); | ||
}, (4 * 1000)); | ||
}); | ||
|
||
steamUser.on("error", (err) => { | ||
if (csgoUser._GCHelloInterval) clearInterval(csgoUser._GCHelloInterval); | ||
|
||
console.error(err); | ||
}); | ||
|
||
csgoUser.on("debug", (event) => { | ||
if (event.header.msg === csgoUser.Protos.ECsgoGCMsg.k_EMsgGCCStrike15_v2_MatchmakingGC2ClientHello) { | ||
var msg = csgoUser.Protos.CMsgGCCStrike15_v2_MatchmakingGC2ClientHello.decode(event.buffer); | ||
console.log(msg); | ||
return; | ||
} | ||
|
||
if (event.header.msg === csgoUser.Protos.EGCBaseClientMsg.k_EMsgGCClientWelcome) { | ||
var msg = csgoUser.Protos.CMsgClientWelcome.decode(event.buffer); | ||
console.log(msg); | ||
return; | ||
} | ||
|
||
if (event.header.msg === csgoUser.Protos.ECsgoGCMsg.k_EMsgGCCStrike15_v2_PlayerOverwatchCaseAssignment) { | ||
var msg = csgoUser.Protos.CMsgGCCStrike15_v2_PlayerOverwatchCaseAssignment.decode(event.buffer); | ||
console.log(msg); | ||
|
||
if (msg.caseurl) { | ||
// Download demo | ||
if (fs.existsSync("./demofile.dem")) fs.unlinkSync("./demofile.dem"); | ||
console.log("Downloading case " + msg.caseid + " from url " + msg.caseurl); | ||
|
||
var sid = new SteamID("[U:1:" + msg.suspectid + "]"); | ||
if (!sid.isValid()) { | ||
console.log("Got invalid suspect ID " + msg.suspectid); | ||
return; | ||
} | ||
|
||
var r = request(msg.caseurl); | ||
r.on("response", (res) => { | ||
res.pipe(fs.createWriteStream("./demofile.bz2")).on("close", () => { | ||
// Successfully downloaded, tell the GC about it! | ||
console.log("Finished downloading " + msg.caseid + ". Unpacking..."); | ||
|
||
csgoUser._GC.send({ | ||
msg: csgoUser.Protos.ECsgoGCMsg.k_EMsgGCCStrike15_v2_PlayerOverwatchCaseStatus, | ||
proto: {} | ||
}, new csgoUser.Protos.CMsgGCCStrike15_v2_PlayerOverwatchCaseStatus({ | ||
caseid: msg.caseid, | ||
statusid: 1 | ||
}).toBuffer()); | ||
|
||
// Parse the demo | ||
fs.createReadStream("./demofile.bz2").pipe(bz2()).pipe(fs.createWriteStream("./demofile.dem")).on("close", () => { | ||
fs.unlinkSync("./demofile.bz2"); | ||
|
||
console.log("Finished unpacking " + msg.caseid + "Parsing as suspect " + sid.getSteamID64() + "..."); | ||
|
||
// WARNING: Really shitty aimbot detection ahead! | ||
fs.readFile("./demofile.dem", (err, buffer) => { | ||
if (err) return console.error(err); | ||
|
||
var aimbot_infractions = []; | ||
|
||
const demoFile = new demofile.DemoFile(); | ||
|
||
// Detect Aimbot | ||
var lastFewAngles = []; | ||
demoFile.on("tickend", (curTick) => { | ||
var ourPlayer = demoFile.players.filter(p => p.steam64Id === sid.getSteamID64()); | ||
if (ourPlayer.length <= 0) { // User left | ||
lastFewAngles = []; | ||
return; | ||
} | ||
lastFewAngles.push(ourPlayer[0].eyeAngles); | ||
|
||
if (lastFewAngles.length >= config.parsing.maxTicks) { | ||
lastFewAngles.shift(); | ||
} | ||
}); | ||
|
||
demoFile.gameEvents.on("player_death", (event) => { | ||
var attacker = demoFile.entities.getByUserId(event.attacker); | ||
if (!attacker) return; // Attacker no longer available | ||
|
||
for (let i = 0; i < lastFewAngles.length; i++) { | ||
// Check pitch | ||
if (typeof lastFewAngles[i] !== "undefined" && typeof lastFewAngles[i + 1] !== "undefined") { | ||
if (!almostEqual(lastFewAngles[i].pitch, lastFewAngles[i + 1].pitch, config.parsing.threshold)) { | ||
aimbot_infractions.push({ prevAngle: lastFewAngles[i], nextAngle: lastFewAngles[i + 1], tick: demoFile.currentTick }); | ||
} | ||
} | ||
|
||
// Check yaw | ||
if (typeof lastFewAngles[i] !== "undefined" && typeof lastFewAngles[i + 1] !== "undefined") { | ||
if (!almostEqual(lastFewAngles[i].yaw, lastFewAngles[i + 1].yaw, config.parsing.threshold)) { | ||
aimbot_infractions.push({ prevAngle: lastFewAngles[i], nextAngle: lastFewAngles[i + 1], tick: demoFile.currentTick }); | ||
} | ||
} | ||
} | ||
}); | ||
|
||
demoFile.parse(buffer); | ||
|
||
demoFile.on("end", async (err) => { | ||
if (err.error) console.error(err); | ||
|
||
console.log("Done parsing case " + msg.caseid); | ||
console.log(sid.getSteamID64() + " has " + aimbot_infractions.length + " infraction" + (aimbot_infractions.length === 1 ? "" : "s") + " for aimbotting"); | ||
|
||
// Once we finished analysing the demo send the results | ||
csgoUser._GC.send({ | ||
msg: csgoUser.Protos.ECsgoGCMsg.k_EMsgGCCStrike15_v2_PlayerOverwatchCaseUpdate, | ||
proto: {} | ||
}, new csgoUser.Protos.CMsgGCCStrike15_v2_PlayerOverwatchCaseUpdate({ | ||
caseid: msg.caseid, | ||
suspectid: msg.suspectid, | ||
fractionid: msg.fractionid, | ||
rpt_aimbot: (aimbot_infractions.length > config.verdict.maxAimbot) ? 1 : 0, | ||
rpt_wallhack: 0, // TODO: Add detection for wallhacking | ||
rpt_speedhack: 0, // TODO: Add detection for other cheats (Ex BunnyHopping) | ||
rpt_teamharm: 0, // TODO: Add detection of griefing (Ex Afking, Damaging teammates) | ||
reason: 3 | ||
}).toBuffer()); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} else { | ||
if (msg.verdict === 2) { // We are done here! | ||
console.log("Successfully submitted verdict for case " + msg.caseid + " throttled for " + msg.throttleseconds + " seconds"); | ||
|
||
// Request a overwatch case after the time has run out | ||
setTimeout(() => { | ||
console.log("-----------------\nRequested Overwatch case"); | ||
csgoUser._GC.send({ | ||
msg: csgoUser.Protos.ECsgoGCMsg.k_EMsgGCCStrike15_v2_PlayerOverwatchCaseUpdate, | ||
proto: {} | ||
}, new csgoUser.Protos.CMsgGCCStrike15_v2_PlayerOverwatchCaseUpdate({ | ||
reason: 1 | ||
}).toBuffer()); | ||
}, ((msg.throttleseconds + 1) * 1000)); | ||
} | ||
} | ||
return; | ||
} | ||
|
||
console.log(event); // Unhandled event | ||
}); |
Oops, something went wrong.