Skip to content
This repository has been archived by the owner on Jul 21, 2022. It is now read-only.

Commit

Permalink
AutoMuteUs Support (with auethermuteproxy) (#1)
Browse files Browse the repository at this point in the history
* Add Socket.IO Official Client (it might not work in Windows)

* Use patched version of StarScream

daltoniam/Starscream#844

* Use forked version of Starscream

* update dependencies

* update dependencies

* remove socket.io client code

* WIP: support lobby, state, player event

* CI(Linux): Install libcurl-dev package

* CI(Linux): Use libcurl4-openssl-dev insteadof libcurl-dev

* Support gameend event & remove players after gameend/disconnect

* README: ✅ Linked with AutoMuteUs

* Add WindowsCompatibility.swift (sleep)
  • Loading branch information
rinsuki committed Mar 6, 2021
1 parent 5b42855 commit da76cad
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
# --- Linux
- name: Install Dependencies (Linux)
if: startsWith(matrix.os, 'ubuntu-')
run: sudo apt install libpcap-dev
run: sudo apt install libpcap-dev libcurl4-openssl-dev
# --- Windows
- uses: seanmiddleditch/gha-setup-vsdevenv@8c6bbf80998779f2bba87b1452832e561b65fd57
if: startsWith(matrix.os, 'windows-')
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ Among Us のパケットをキャプチャし、

GitHub Actions でコミット毎に自動ビルドを回しています。ビルド成果は各コミットのArtifactsからダウンロードできます (GitHub にログインしていないと表示されません)。

## Tasks (0/4)
## Tasks (1/4)

- Linked with AutoMuteUs https://automute.us/
- GUI: Create GUI Front-end for macOS
- GUI: Create GUI Front-end for Windows
- GUI: Create GUI Front-end for Linux (GTK?)
- [x] Linked with AutoMuteUs https://automute.us/ (requires [auethermuteproxy](https://github.com/rinsuki/auethermuteproxy) for interact with AutoMuteUs server)
- [ ] GUI: Create GUI Front-end for macOS
- [ ] GUI: Create GUI Front-end for Windows
- [ ] GUI: Create GUI Front-end for Linux (GTK?)
19 changes: 17 additions & 2 deletions Sources/AUEtherCapture/CaptureState+handleRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,31 @@ extension CaptureState {
for _ in 0..<impostorsCount {
gameState.impostors.append(reader.uint8())
}
updateAutoMuteUsScene(scene: .tasks)
case .setName:
guard let playerID = sender.playerID, var player = gameState.players[playerID] else {
break
}
player.name = reader.str()
gameState.add(player: player)
updateAutoMuteUsPlayer(player: player, action: .forceUpdated)
case .setColor:
guard let playerID = sender.playerID, var player = gameState.players[playerID] else {
return
}
player.color = Player.Color(rawValue: reader.uint8())!
gameState.add(player: player)
updateAutoMuteUsPlayer(player: player, action: .changedColor)
case .murderPlayer:
let victim = gameState.components[reader.packedUInt32()]!.obj.playerID!
let impostor = sender.playerID!
gameState.add(event: .kill(.init(imposter: impostor, victim: victim, timestamp: timestamp)))
gameState.modify(playerID: victim) { player in
player.deadAt = timestamp
}
if let victim = gameState.players[victim] {
updateAutoMuteUsPlayer(player: victim, action: .died)
}
case .sendChat:
guard let player = sender.playerID else {
break
Expand All @@ -53,6 +59,7 @@ extension CaptureState {
break
}
gameState.add(event: .startMeeting(.init(player: player, deadBody: gameState.players[victim]?.id, timestamp: timestamp)))
updateAutoMuteUsScene(scene: .discussion)
case .sendChatNote:
let player = reader.uint8()
gameState.add(event: .voted(.init(player: player, timestamp: timestamp)))
Expand Down Expand Up @@ -95,10 +102,17 @@ extension CaptureState {
}
let exiled = reader.uint8()
let tie = reader.bool()
gameState.add(event: .voteFinish(.init(states: states, exiled: gameState.players[exiled]?.id, isTie: tie, timestamp: timestamp)))
let exiledPlayer = gameState.players[exiled]
gameState.add(event: .voteFinish(.init(states: states, exiled: exiledPlayer?.id, isTie: tie, timestamp: timestamp)))
updateAutoMuteUsScene(scene: .tasks)
if let exiledPlayer = exiledPlayer {
updateAutoMuteUsPlayer(player: exiledPlayer, action: .exiled)
}
case .updateGameData:
while reader.hasMoreData {
gameState.add(player: .init(from: &reader, update: true))
let player = Player(from: &reader, update: true)
gameState.add(player: player)
updateAutoMuteUsPlayer(player: player, action: .forceUpdated)
}
case .playAnimation, .completeTask: // ignore
break
Expand All @@ -117,6 +131,7 @@ extension CaptureState {
default:
fatalError("Unknown GameSettings Version: \(ver)")
}
updateAutoMuteUsLobby(code: gameState.id.string, region: 0, map: gameState.settings!.v1.map)
default:
print("RPC", rpcType, senderID)
}
Expand Down
83 changes: 83 additions & 0 deletions Sources/AUEtherCapture/CaptureState+muteStateBroker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// File.swift
//
//
// Created by user on 2021/03/06.
//

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

extension CaptureState {
enum GameScene: UInt8, Encodable {
case lobby = 0
case tasks
case discussion
case menu
case ended
case unknown
}

func updateAutoMuteUsScene(scene: GameScene) {
guard let url = muteProxyURL else {
return
}
var req = URLRequest(url: url.appendingPathComponent("state/\(scene.rawValue)"))
req.httpMethod = "POST"
URLSession.shared.dataTask(with: req).resume()
}

func updateAutoMuteUsLobby(code: String, region: UInt8, map: UInt8) {
guard let url = muteProxyURL else {
return
}
var req = URLRequest(url: url.appendingPathComponent("lobby/\(code.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!)/\(region)/\(map)"))
req.httpMethod = "POST"
URLSession.shared.dataTask(with: req).resume()
}

enum PlayerUpdateAction: UInt8, Encodable {
case joined = 0
case left
case died
case changedColor
case forceUpdated
case disconnected
case exiled
}
func updateAutoMuteUsPlayer(player: Player, action: PlayerUpdateAction) {
guard let url = muteProxyURL else {
return
}
var req = URLRequest(url: url.appendingPathComponent([
"player",
// we should add space for empty name (that is invalid, but we should handle invalid name)
// last character is always trimmed by auethermuteproxy
player.name.addingPercentEncoding(withAllowedCharacters: .alphanumerics)! + " ",
action.rawValue.description,
player.deadAt != nil ? "1" : "0",
player.disconnectedAt != nil ? "1" : "0",
player.color.rawValue.description,
].joined(separator: "/")))
req.httpMethod = "POST"
URLSession.shared.dataTask(with: req).resume()
}

func updateAutoMuteUsGameOver(reason: EndReason, players: [Player]) {
guard let url = muteProxyURL else {
return
}
var req = URLRequest(url: url.appendingPathComponent([
"gameover",
reason.rawValue.description,
players.map { [
$0.name.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!,
gameState.impostors.contains($0.id) ? "1" : "0",
].joined(separator: "_") }.joined(separator: ","),
].joined(separator: "/")))
req.httpMethod = "POST"
URLSession.shared.dataTask(with: req).resume()
}
}
30 changes: 24 additions & 6 deletions Sources/AUEtherCapture/CaptureState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ struct CaptureState {
var timestamp: Double = 0
var gameState = GameState()
var outDir: URL?

var muteProxyURL: URL?

mutating func handleACK(ack: Ack) -> Bool {
if ackStore.contains(ack) {
return false
Expand Down Expand Up @@ -55,10 +56,10 @@ struct CaptureState {
break
}
print(packet)
// case .disconnect(forced: let forced, reason: let reason, description: let description):
// <#code#>
// case .disconnectSimple:
// <#code#>
case .disconnect(forced: let forced, reason: let reason, description: let description):
fallthrough
case .disconnectSimple:
updateAutoMuteUsScene(scene: .menu)
case .ack(let ack):
for ack in ack {
let ack = Ack(pair: pair.reversed(), no: ack)
Expand Down Expand Up @@ -88,13 +89,18 @@ struct CaptureState {
handleGameDataArray(&reader)
case .joinedGame:
print("Reset State")
for player in gameState.players.values {
updateAutoMuteUsPlayer(player: player, action: .left)
}
gameState = .init()
updateAutoMuteUsScene(scene: .lobby)
case .endGame: // EndGame
_ = reader.int32()
let reason = EndReason(rawValue: reader.uint8())
gameState.endReason = reason
gameState.duration = timestamp - gameState.startedAt
gameFinish()
updateAutoMuteUsScene(scene: .ended)
default:
print("Hazel", packet)
}
Expand All @@ -104,6 +110,12 @@ struct CaptureState {
if let outDir = outDir {
output(to: outDir)
}
updateAutoMuteUsGameOver(reason: gameState.endReason!, players: Array(gameState.players.values))
updateAutoMuteUsScene(scene: .ended)
sleep(1)
for player in gameState.players.values {
updateAutoMuteUsPlayer(player: player, action: .left)
}
gameState = .init()
}

Expand Down Expand Up @@ -189,7 +201,9 @@ struct CaptureState {
var reader = BinaryReader(data: data)
let playersLength = reader.packedUInt32()
for _ in 0..<playersLength {
gameState.add(player: Player(from: &reader, update: false))
let player = Player(from: &reader, update: false)
gameState.add(player: player)
updateAutoMuteUsPlayer(player: player, action: .joined)
}
print(gameState.players)
print(obj.components)
Expand All @@ -201,6 +215,9 @@ struct CaptureState {
guard let obj = gameState.components[netid]?.obj else {
break
}
if let playerID = obj.playerID, gameState.startedAt == 0, let player = gameState.players[playerID] {
updateAutoMuteUsPlayer(player: player, action: .left)
}
guard let playerID = obj.playerID, gameState.startedAt != 0 else {
gameState.remove(object: obj)
break
Expand All @@ -214,6 +231,7 @@ struct CaptureState {
player.disconnectedAt = timestamp
}
gameState.add(event: .disconnect(.init(player: playerID, timestamp: timestamp)))
updateAutoMuteUsPlayer(player: player, action: .disconnected)
default:
print("GameData", packet)
}
Expand Down
15 changes: 15 additions & 0 deletions Sources/AUEtherCapture/GameState/GameSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,18 @@ enum GameSettings: Encodable {
}
}
}

extension GameSettings {
var v1: V1 {
switch self {
case .v1(let v1):
fallthrough
case .v2(let v1, _):
fallthrough
case .v3(let v1, _, _):
fallthrough
case .v4(let v1, _, _, _):
return v1
}
}
}
14 changes: 14 additions & 0 deletions Sources/AUEtherCapture/WindowsCompatibility.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// File.swift
//
//
// Created by user on 2021/03/06.
//

#if os(Windows)
import WinSDK
func sleep(_ seconds: UInt32) {
// Win32 Sleep is milliseconds, *NIX sleep is seconds
Sleep(seconds * 1000)
}
#endif
11 changes: 11 additions & 0 deletions Sources/AUEtherCapture/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ struct CLI: ParsableCommand {
@Option(name: .long, help: "Output replay data to specific path.")
var outDir: String

@Option(name: .long, help: "auethermuteproxy URL e.g. http://localhost:4494")
var muteProxyURL: URL?

func run() throws {
if listNetworkInterface {
print("Available (and compatible) Network Interfaces: ")
Expand Down Expand Up @@ -75,7 +78,9 @@ struct CLI: ParsableCommand {
print("session started", session)

var state = CaptureState()
state.muteProxyURL = muteProxyURL
state.outDir = URL(fileURLWithPath: outDir)
state.updateAutoMuteUsScene(scene: .menu)
while let (ts, packet) = session.next() {
let ethernet = Ethernet(from: packet)
if case .ipv4(let ipv4) = ethernet.content, case .udp(let udp) = ipv4.content {
Expand All @@ -100,4 +105,10 @@ extension IPv4.Address: ExpressibleByArgument {
}
}

extension URL: ExpressibleByArgument {
public init?(argument: String) {
self.init(string: argument)
}
}

CLI.main()

0 comments on commit da76cad

Please sign in to comment.