Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

Commit

Permalink
add: adb wrapper to get unity logs
Browse files Browse the repository at this point in the history
  • Loading branch information
ggmolly committed Jun 9, 2024
1 parent 7fce4d8 commit 387ba01
Show file tree
Hide file tree
Showing 3 changed files with 292 additions and 21 deletions.
13 changes: 6 additions & 7 deletions connection/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package connection
import (
"bytes"
"fmt"
"log"
"net"
"os"
"reflect"
Expand Down Expand Up @@ -65,21 +64,21 @@ func (server *Server) RemoveClient(client *Client) {
delete(server.Clients, client.FD)
}

func (server *Server) Run() {
func (server *Server) Run() error {
var err error
BelfastInstance = server
if server.SocketFD, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM|syscall.O_NONBLOCK, 0); err != nil {
log.Fatalf("failed to create socket : %v", err)
return fmt.Errorf("failed to create socket : %v", err)
}
defer syscall.Close(server.SocketFD)
logger.LogEvent("Server", "Listen", fmt.Sprintf("Listening on %s:%d", server.BindAddress, server.Port), logger.LOG_LEVEL_AUTO)

if err = syscall.SetsockoptInt(server.SocketFD, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
log.Fatalf("setsockopt error: %v", err)
return fmt.Errorf("setsockopt error: %v", err)
}

if err = syscall.SetNonblock(server.SocketFD, true); err != nil {
log.Fatalf("setnonblock error: %v", err)
return fmt.Errorf("setnonblock error: %v", err)
}

var ip [4]byte
Expand All @@ -90,11 +89,11 @@ func (server *Server) Run() {
}

if err = syscall.Bind(server.SocketFD, &addr); err != nil {
log.Fatalf("bind error: %v", err)
return fmt.Errorf("bind error: %v", err)
}

if err = syscall.Listen(server.SocketFD, syscall.SOMAXCONN); err != nil {
log.Fatalf("listen error: %v", err)
return fmt.Errorf("listen error: %v", err)
}

if server.EpollFD, err = syscall.EpollCreate1(0); err != nil {
Expand Down
243 changes: 243 additions & 0 deletions debug/adb_watcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package debug

import (
"bufio"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"time"

"github.com/ggmolly/belfast/logger"
"github.com/mattn/go-tty"

Check failure on line 13 in debug/adb_watcher.go

View workflow job for this annotation

GitHub Actions / test

no required module provides package github.com/mattn/go-tty; to add it:
)

var handlers = map[string]func(){
"?": help,
"c": clear,
"l": listDevices,
"s": toggleLogcat,
"f": flushLogcat,
"d": dumpLogcat,
"+": increaseSleep,
"-": decreaseSleep,
"=": printDelay,
}

// a list of needles to search for in the process list
// to find Azur Lane's PID -- these should be lowercase
// to make the search case-insensitive
const grepRegex = "'(azurlane|blhx|manjuu|yostar)'"

// Filter to remove Azur Lane's uninteresting logs (FacebookSDK, ...) -- regex for -e parameter
// see https://developer.android.com/studio/command-line/logcat#filteringOutput
const defaultLogcatFilter = "(System|Unity)"

var logcatProcess *exec.Cmd
var azurLanePID int
var psDelay time.Duration = 3 * time.Second

func help() {
fmt.Println("belfast -- adb watcher help")
fmt.Println("?: print this help")
fmt.Println("l: list connected devices")
fmt.Println("c: clear terminal")
fmt.Println("s: start/stop logcat parsing")
fmt.Println("f: flush logcat")
fmt.Println("d: dump logcat buffer to a file")
fmt.Println("+: increase delay between ps commands (default: 3s)")
fmt.Println("-: decrease delay between ps commands (default: 3s)")
fmt.Println("=: print current delay between ps commands")
fmt.Println("x: exit adb watcher")
}

// stupid way to clear the terminal, calls 'clear' on non-windows and 'cls' on windows
func clear() {
if runtime.GOOS == "windows" {
cmd := exec.Command("cmd", "/c", "cls")
cmd.Stdout = os.Stdout
cmd.Run()
} else {
cmd := exec.Command("clear")
cmd.Stdout = os.Stdout
cmd.Run()
}
}

// runs 'adb devices' and prints the output
func listDevices() {
cmd := exec.Command("adb", "devices")
out, err := cmd.Output()
if err != nil {
logger.LogEvent("ADB", "ListDevices", "Failed to list devices", logger.LOG_LEVEL_ERROR)
return
}
fmt.Print(string(out))
}

// runs 'adb logcat -c' to flush logcat's buffer
func flushLogcat() {
cmd := exec.Command("adb", "logcat", "-c")
if err := cmd.Run(); err != nil {
logger.LogEvent("ADB", "Flush", "Failed to flush logcat", logger.LOG_LEVEL_ERROR)
return
}
logger.LogEvent("ADB", "FlushLogcat", "Logcat flushed", logger.LOG_LEVEL_INFO)
}

// wrapper function to print logcat lines
func echoLog(line *string) {
fmt.Println(*line)
}

func stopLogcat() {
if logcatProcess == nil {
return
}
pid := logcatProcess.Process.Pid
logcatProcess.Process.Kill()
logcatProcess = nil
logger.LogEvent("ADB", "Logcat", fmt.Sprintf("Logcat stopped (PID: %d)", pid), logger.LOG_LEVEL_INFO)
}

// starts/stops logcat parsing
func toggleLogcat() {
if logcatProcess != nil {
stopLogcat()
return
}
go func() {
args := []string{"logcat"}
if azurLanePID != 0 {
args = append(args, "--pid", fmt.Sprintf("%d", azurLanePID), "-e", defaultLogcatFilter)
} else {
logger.LogEvent("ADB", "Logcat", fmt.Sprintf("Azur Lane PID not found, waiting %v to retry", psDelay), logger.LOG_LEVEL_INFO)
return
}
logcatProcess = exec.Command("adb", args...)
processStdout, err := logcatProcess.StdoutPipe()
if err != nil {
logger.LogEvent("ADB", "Logcat", fmt.Sprintf("Failed to get logcat stdout: %v", err), logger.LOG_LEVEL_ERROR)
return
}

if err := logcatProcess.Start(); err != nil {
logger.LogEvent("ADB", "Logcat", fmt.Sprintf("Failed to start logcat: %v", err), logger.LOG_LEVEL_ERROR)
return
}
logger.LogEvent("ADB", "Logcat", fmt.Sprintf("Logcat started (PID: %d)", logcatProcess.Process.Pid), logger.LOG_LEVEL_INFO)

// Parse logcat output in background
go func() {
scanner := bufio.NewScanner(processStdout)
for scanner.Scan() {
line := scanner.Text()
echoLog(&line)
}
if err := scanner.Err(); err != nil {
logger.LogEvent("ADB", "Logcat", fmt.Sprintf("Error reading logcat stdout: %v", err), logger.LOG_LEVEL_INFO)
}
}()
logcatProcess.Wait()
exitCode := logcatProcess.ProcessState.ExitCode()
if exitCode != 0 {
logger.LogEvent("ADB", "Logcat", fmt.Sprintf("Logcat process (PID: %d) exited with code %d", logcatProcess.Process.Pid, exitCode), logger.LOG_LEVEL_ERROR)
}
}()
}

// increases by 1s the delay between ps commands
func increaseSleep() {
psDelay += 1 * time.Second
logger.LogEvent("Watcher", "Delay", fmt.Sprintf("Delay increased to %v", psDelay), logger.LOG_LEVEL_INFO)
}

// decreases by 1s the delay between ps commands
func decreaseSleep() {
if psDelay > 1*time.Second {
psDelay -= 1 * time.Second
logger.LogEvent("Watcher", "Delay", fmt.Sprintf("Delay decreased to %v", psDelay), logger.LOG_LEVEL_INFO)
} else {
logger.LogEvent("Watcher", "Delay", "Delay cannot be decreased further, minimum is 1s", logger.LOG_LEVEL_INFO)
}
}

// prints the current delay between ps commands
func printDelay() {
logger.LogEvent("ADB", "PrintDelay", fmt.Sprintf("Current delay: %v", psDelay), logger.LOG_LEVEL_INFO)
}

// dump logcat buffer to a file
func dumpLogcat() {
cmd := exec.Command("adb", "logcat", "-d")
filename := time.Now().Format("2006-01-02_15-04-05") + "_belfast_logcat.log"
file, err := os.Create(filename)
if err != nil {
logger.LogEvent("ADB", "DumpLogcat", "Failed to create file", logger.LOG_LEVEL_ERROR)
return
}
cmd.Stdout = file
cmd.Stderr = file
if err := cmd.Run(); err != nil {
logger.LogEvent("ADB", "DumpLogcat", "Failed to dump logcat", logger.LOG_LEVEL_ERROR)
return
}
defer file.Close()
logger.LogEvent("ADB", "DumpLogcat", fmt.Sprintf("Logcat dumped to %s", filename), logger.LOG_LEVEL_INFO)
}

// main routine for ADB watcher, listens for key presses and executes commands
func ADBRoutine(tty *tty.TTY, flush bool) {
if tty == nil {
return // silently return, main function will handle the error
}
// Checking if adb is installed / in PATH
_, err := exec.LookPath("adb")
if err != nil {
logger.LogEvent("ADB", "Init", "ADB not found in PATH", logger.LOG_LEVEL_ERROR)
return
}
logger.LogEvent("ADB", "Init", "ADB watcher started", logger.LOG_LEVEL_INFO)
if flush {
flushLogcat()
}
help()

// Goroutine to find Azur Lane's PID
go func() {
for {
cmd := exec.Command("adb", "shell", "ps", "-A", "-o", "PID,NAME", "|", "grep", "-iE", grepRegex)
if out, err := cmd.Output(); err == nil {
for _, line := range strings.Split(string(out), "\n") {
newPid := 0
fmt.Sscanf(line, "%d", &newPid)
if newPid != 0 && newPid != azurLanePID {
azurLanePID = newPid
logger.LogEvent("ADB", "Shell", fmt.Sprintf("Azur Lane PID: %d", azurLanePID), logger.LOG_LEVEL_INFO)
stopLogcat() // force stop logcat to restart with new PID
toggleLogcat() // restart logcat with new PID
}
}
}
time.Sleep(psDelay)
}
}()

for {
// Read key from stdin
// If key is '?' print hello world
r, err := tty.ReadRune()
if err != nil {
logger.LogEvent("ADB", "ReadRune", fmt.Sprintf("Failed to read rune: %v", err), logger.LOG_LEVEL_ERROR)
break
}
if r == 'x' {
logger.LogEvent("ADB", "Exit", "ADB watcher exited", logger.LOG_LEVEL_INFO)
break
}
if handler, ok := handlers[string(r)]; ok {
handler()
}
}
}
57 changes: 43 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
"github.com/akamensky/argparse"
"github.com/ggmolly/belfast/answer"
"github.com/ggmolly/belfast/connection"
"github.com/ggmolly/belfast/debug"
"github.com/ggmolly/belfast/logger"
"github.com/ggmolly/belfast/misc"
"github.com/ggmolly/belfast/orm"
"github.com/ggmolly/belfast/packets"
"github.com/ggmolly/belfast/protobuf"
"github.com/ggmolly/belfast/web"
"github.com/joho/godotenv"
"github.com/mattn/go-tty"
"google.golang.org/protobuf/proto"
)

Expand All @@ -34,6 +36,16 @@ func main() {
Help: "Forces the reseed of the database with the latest data",
Default: false,
})
adb := parser.Flag("a", "adb", &argparse.Options{
Required: false,
Help: "Parse ADB logs for debugging purposes (experimental -- tested on Linux only)",
Default: false,
})
flushLogcat := parser.Flag("f", "flush-logcat", &argparse.Options{
Required: false,
Help: "Flush the logcat buffer upon starting the ADB watcher",
Default: false,
})
if err := parser.Parse(os.Args); err != nil {
fmt.Print(parser.Usage(err))
os.Exit(1)
Expand All @@ -43,21 +55,38 @@ func main() {
misc.UpdateAllData(os.Getenv("AL_REGION"))
}
server := connection.NewServer("0.0.0.0", 80, packets.Dispatch)

// Open TTY for adb controls
tty, err := tty.Open()
if err != nil {
log.Println("failed to open tty:", err)
log.Println("adb background routine will be disabled.")
return
}
// wait for SIGINT
sigChannel := make(chan os.Signal, 1)
signal.Notify(sigChannel, os.Interrupt)
go func() {
<-sigChannel
fmt.Printf("\r") // trick to avoid ^C in the terminal, could use low-level RawMode() but why bother
server.Kill()
tty.Close()
os.Exit(0)
}()
// Prepare web server
go func() {
web.StartWeb()
}()

server.Run()
// Prepare adb background task
if *adb {
go debug.ADBRoutine(tty, *flushLogcat)
}
if err := server.Run(); err != nil {
logger.LogEvent("Server", "Run", fmt.Sprintf("%v", err), logger.LOG_LEVEL_ERROR)
tty.Close()
os.Exit(1)
}
}

func init() {
Expand Down Expand Up @@ -88,19 +117,19 @@ func init() {
packets.RegisterPacketHandler(10022, []packets.PacketHandler{answer.JoinServer})
packets.RegisterPacketHandler(10026, []packets.PacketHandler{answer.PlayerExist})
packets.RegisterPacketHandler(11001, []packets.PacketHandler{
answer.LastLogin, // SC_11000
answer.PlayerInfo, // SC_11003
answer.PlayerBuffs, // SC_11015
answer.GetMetaProgress, // SC_63315
answer.LastOnlineInfo, // SC_11752
answer.ResourcesInfo, // SC_22001
answer.EventData, // SC_26120
answer.Meowfficers, // SC_25001
answer.CommanderCollection, // SC_17001
answer.OngoingBuilds, // SC_12024
answer.PlayerDock, // SC_12001
answer.CommanderFleetA, // SC_12010
// answer.UNK_12101, // SC_12101
answer.LastLogin, // SC_11000
answer.PlayerInfo, // SC_11003
answer.PlayerBuffs, // SC_11015
answer.GetMetaProgress, // SC_63315
answer.LastOnlineInfo, // SC_11752
answer.ResourcesInfo, // SC_22001
answer.EventData, // SC_26120
answer.Meowfficers, // SC_25001
answer.CommanderCollection, // SC_17001
answer.OngoingBuilds, // SC_12024
answer.PlayerDock, // SC_12001
answer.CommanderFleetA, // SC_12010
answer.UNK_12101, // SC_12101
answer.CommanderOwnedSkins, // SC_12201
answer.UNK_63000, // SC_63000
answer.ShipyardData, // SC_63100
Expand Down

0 comments on commit 387ba01

Please sign in to comment.