Simplify GUI settings and build flags
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -9,6 +8,9 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -77,6 +79,12 @@ func findLatestVrchatLog() (string, error) {
|
||||
}
|
||||
|
||||
func watchVrchatLog() {
|
||||
log.Printf("vrchat log watcher started")
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("vrchat log watcher panic: %v", r)
|
||||
}
|
||||
}()
|
||||
lastPath := ""
|
||||
lastSize := int64(0)
|
||||
state := &vrcLogState{
|
||||
@@ -85,20 +93,26 @@ func watchVrchatLog() {
|
||||
for {
|
||||
path, err := findLatestVrchatLog()
|
||||
if err != nil {
|
||||
log.Printf("vrchat log not found: %v", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
log.Printf("vrchat log stat failed: %v", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
log.Printf("vrchat log loop path=%s size=%d last=%d", path, info.Size(), lastSize)
|
||||
if path != lastPath {
|
||||
log.Printf("vrchat log watching %s", path)
|
||||
lastPath = path
|
||||
lastSize = 0
|
||||
log.Printf("vrchat log initial scan begin path=%s", path)
|
||||
if err := scanExistingVrchatLog(path, state); err != nil {
|
||||
log.Printf("vrchat log initial scan failed: %v", err)
|
||||
} else {
|
||||
log.Printf("vrchat log initial scan ok path=%s", path)
|
||||
}
|
||||
if info2, err := os.Stat(path); err == nil {
|
||||
lastSize = info2.Size()
|
||||
@@ -113,15 +127,22 @@ func watchVrchatLog() {
|
||||
lastSize = 0
|
||||
}
|
||||
if info.Size() > lastSize {
|
||||
f, err := os.Open(path)
|
||||
log.Printf("vrchat log updated path=%s old=%d new=%d", path, lastSize, info.Size())
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Printf("vrchat log read failed: %v", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
_, _ = f.Seek(lastSize, 0)
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
text := decodeVRChatLog(b)
|
||||
lines := strings.Split(text, "\n")
|
||||
joinHits := 0
|
||||
leaveHits := 0
|
||||
for _, line := range lines {
|
||||
line = strings.TrimRight(line, "\r")
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if err := appendRuntimeLog("VRC LOG", line); err != nil {
|
||||
log.Printf("append runtime log failed: %v", err)
|
||||
}
|
||||
@@ -137,41 +158,47 @@ func watchVrchatLog() {
|
||||
at := extractLineTime(line)
|
||||
if m := joinPattern.FindStringSubmatch(line); len(m) == 2 {
|
||||
name := strings.TrimSpace(m[1])
|
||||
log.Printf("join detected: %s", name)
|
||||
joinHits++
|
||||
state.presentSet[name] = struct{}{}
|
||||
if tracker := GetGuestTracker(); tracker != nil {
|
||||
tracker.MarkJoin(name, at)
|
||||
}
|
||||
out := fmt.Sprintf("[join] %s (%d)", name, len(state.presentSet))
|
||||
log.Print(out)
|
||||
_ = appendJoinLeaveLog(out)
|
||||
if err := appendJoinLeaveLog(out); err != nil {
|
||||
log.Printf("append join log failed: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if m := leftPattern.FindStringSubmatch(line); len(m) == 2 {
|
||||
name := strings.TrimSpace(m[1])
|
||||
log.Printf("leave detected: %s", name)
|
||||
leaveHits++
|
||||
delete(state.presentSet, name)
|
||||
if tracker := GetGuestTracker(); tracker != nil {
|
||||
tracker.MarkLeave(name, at)
|
||||
}
|
||||
out := fmt.Sprintf("[leave] %s (%d)", name, len(state.presentSet))
|
||||
log.Print(out)
|
||||
_ = appendJoinLeaveLog(out)
|
||||
if err := appendJoinLeaveLog(out); err != nil {
|
||||
log.Printf("append leave log failed: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
log.Printf("vrchat log batch done join=%d leave=%d", joinHits, leaveHits)
|
||||
lastSize = info.Size()
|
||||
_ = f.Close()
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func scanExistingVrchatLog(path string, state *vrcLogState) error {
|
||||
f, err := os.Open(path)
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
state.presentSet = map[string]struct{}{}
|
||||
state.location = ""
|
||||
state.worldID = ""
|
||||
@@ -180,9 +207,12 @@ func scanExistingVrchatLog(path string, state *vrcLogState) error {
|
||||
state.pendingWorldName = ""
|
||||
state.initialized = false
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
text := decodeVRChatLog(b)
|
||||
for _, raw := range strings.Split(text, "\n") {
|
||||
line := strings.TrimRight(raw, "\r")
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if changed, _ := updateWorldState(state, line); changed {
|
||||
state.presentSet = map[string]struct{}{}
|
||||
}
|
||||
@@ -203,9 +233,6 @@ func scanExistingVrchatLog(path string, state *vrcLogState) error {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
state.initialized = true
|
||||
if state.worldName != "" {
|
||||
log.Printf("world changed: %s", state.worldName)
|
||||
@@ -213,11 +240,130 @@ func scanExistingVrchatLog(path string, state *vrcLogState) error {
|
||||
if tracker := GetGuestTracker(); tracker != nil {
|
||||
tracker.SetCurrentInstance(state.worldName)
|
||||
}
|
||||
_ = appendRuntimeLog("VRC WORLD", state.worldName)
|
||||
if err := appendRuntimeLog("VRC WORLD", state.worldName); err != nil {
|
||||
log.Printf("append world log failed: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeVRChatLog(b []byte) string {
|
||||
if len(b) == 0 {
|
||||
return ""
|
||||
}
|
||||
if utf8.Valid(b) {
|
||||
return strings.TrimPrefix(string(b), "\ufeff")
|
||||
}
|
||||
if len(b) >= 2 {
|
||||
if b[0] == 0xff && b[1] == 0xfe {
|
||||
u16 := make([]uint16, 0, (len(b)-2)/2)
|
||||
for i := 2; i+1 < len(b); i += 2 {
|
||||
u16 = append(u16, uint16(b[i])|uint16(b[i+1])<<8)
|
||||
}
|
||||
return strings.TrimPrefix(fixMojibake(syscall.UTF16ToString(u16)), "\ufeff")
|
||||
}
|
||||
if b[0] == 0xfe && b[1] == 0xff {
|
||||
u16 := make([]uint16, 0, (len(b)-2)/2)
|
||||
for i := 2; i+1 < len(b); i += 2 {
|
||||
u16 = append(u16, uint16(b[i+1])|uint16(b[i])<<8)
|
||||
}
|
||||
return strings.TrimPrefix(fixMojibake(syscall.UTF16ToString(u16)), "\ufeff")
|
||||
}
|
||||
}
|
||||
for _, cp := range []uint32{932, 1252, 65001} {
|
||||
if decoded := decodeWithCodePage(b, cp); decoded != "" {
|
||||
return strings.TrimPrefix(fixMojibake(decoded), "\ufeff")
|
||||
}
|
||||
}
|
||||
return strings.TrimPrefix(fixMojibake(string(b)), "\ufeff")
|
||||
}
|
||||
|
||||
func DecodeVRChatLog(b []byte) string {
|
||||
return decodeVRChatLog(b)
|
||||
}
|
||||
|
||||
func fixMojibake(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
candidates := []string{s}
|
||||
if repaired := tryRepairShiftJISMojibake(s); repaired != "" {
|
||||
candidates = append(candidates, repaired)
|
||||
}
|
||||
best := s
|
||||
bestScore := scoreReadable(best)
|
||||
for _, cand := range candidates {
|
||||
if score := scoreReadable(cand); score > bestScore {
|
||||
best = cand
|
||||
bestScore = score
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
func tryRepairShiftJISMojibake(s string) string {
|
||||
raw := []byte(s)
|
||||
// Try the common "UTF-8 bytes interpreted as CP932" pattern repair by
|
||||
// round-tripping through CP932 in the reverse direction.
|
||||
if repaired := decodeWithCodePage(raw, 932); repaired != "" && repaired != s {
|
||||
return repaired
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func scoreReadable(s string) int {
|
||||
if s == "" {
|
||||
return -1
|
||||
}
|
||||
score := 0
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case r == '<27>':
|
||||
score -= 8
|
||||
case r >= 0x3040 && r <= 0x30ff:
|
||||
score += 3
|
||||
case r >= 0x4e00 && r <= 0x9fff:
|
||||
score += 3
|
||||
case r >= 0x0020 && r <= 0x007e:
|
||||
score += 1
|
||||
case r == '\n' || r == '\r' || r == '\t':
|
||||
score += 1
|
||||
default:
|
||||
score -= 1
|
||||
}
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
func decodeWithCodePage(b []byte, codePage uint32) string {
|
||||
if len(b) == 0 {
|
||||
return ""
|
||||
}
|
||||
kernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||||
multiByteToWideChar := kernel32.NewProc("MultiByteToWideChar")
|
||||
n, _, _ := multiByteToWideChar.Call(
|
||||
uintptr(codePage),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&b[0])),
|
||||
uintptr(len(b)),
|
||||
0,
|
||||
0,
|
||||
)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
buf := make([]uint16, n)
|
||||
multiByteToWideChar.Call(
|
||||
uintptr(codePage),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&b[0])),
|
||||
uintptr(len(b)),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(len(buf)),
|
||||
)
|
||||
return syscall.UTF16ToString(buf)
|
||||
}
|
||||
|
||||
func updateWorldState(state *vrcLogState, line string) (bool, string) {
|
||||
worldID := ""
|
||||
instanceID := ""
|
||||
|
||||
Reference in New Issue
Block a user