package main import ( "errors" "log" "os" "sync" "time" "encoding/json" "fmt" "github.com/akmalfairuz/legacy-version/legacyver" "github.com/pelletier/go-toml" "github.com/sandertv/gophertunnel/minecraft" "github.com/sandertv/gophertunnel/minecraft/auth" "golang.org/x/oauth2" "os/signal" "syscall" ) // The following program implements a proxy that forwards players from one local address to a remote address. func main() { config := readConfig() src := tokenSource() p, err := minecraft.NewForeignStatusProvider(config.Connection.RemoteAddress) if err != nil { panic(err) } listener, err := minecraft.ListenConfig{ StatusProvider: p, AcceptedProtocols: legacyver.All(false), }.Listen("raknet", config.Connection.LocalAddress) if err != nil { panic(err) } defer listener.Close() for { c, err := listener.Accept() if err != nil { panic(err) } go handleConn(c.(*minecraft.Conn), listener, config, src) } } // handleConn handles a new incoming minecraft.Conn from the minecraft.Listener passed. func handleConn(conn *minecraft.Conn, listener *minecraft.Listener, config config, src oauth2.TokenSource) { serverConn, err := minecraft.Dialer{ TokenSource: src, ClientData: conn.ClientData(), }.DialTimeout("raknet", config.Connection.RemoteAddress, time.Second*100) if err != nil { panic(err) } var g sync.WaitGroup g.Add(2) go func() { if err := conn.StartGame(serverConn.GameData()); err != nil { panic(err) } g.Done() }() go func() { if err := serverConn.DoSpawn(); err != nil { panic(err) } g.Done() }() g.Wait() go func() { defer listener.Disconnect(conn, "connection lost") defer serverConn.Close() for { pk, err := conn.ReadPacket() if err != nil { return } if err := serverConn.WritePacket(pk); err != nil { var disc minecraft.DisconnectError if ok := errors.As(err, &disc); ok { _ = listener.Disconnect(conn, disc.Error()) } return } } }() go func() { defer serverConn.Close() defer listener.Disconnect(conn, "connection lost") for { pk, err := serverConn.ReadPacket() if err != nil { var disc minecraft.DisconnectError if ok := errors.As(err, &disc); ok { _ = listener.Disconnect(conn, disc.Error()) } return } fmt.Println(pk.ID()) time.Sleep(time.Millisecond * 1000) if pk.ID() == 498 { fmt.Println("skipping packet 498") continue } if err := conn.WritePacket(pk); err != nil { return } } }() } type config struct { Connection struct { LocalAddress string RemoteAddress string } } func readConfig() config { c := config{} if _, err := os.Stat("config.toml"); os.IsNotExist(err) { f, err := os.Create("config.toml") if err != nil { log.Fatalf("create config: %v", err) } data, err := toml.Marshal(c) if err != nil { log.Fatalf("encode default config: %v", err) } if _, err := f.Write(data); err != nil { log.Fatalf("write default config: %v", err) } _ = f.Close() } data, err := os.ReadFile("config.toml") if err != nil { log.Fatalf("read config: %v", err) } if err := toml.Unmarshal(data, &c); err != nil { log.Fatalf("decode config: %v", err) } if c.Connection.LocalAddress == "" { c.Connection.LocalAddress = "0.0.0.0:19132" } data, _ = toml.Marshal(c) if err := os.WriteFile("config.toml", data, 0644); err != nil { log.Fatalf("write config: %v", err) } return c } // tokenSource returns a token source for using with a gophertunnel client. It either reads it from the // token.tok file if cached or requests logging in with a device code. func tokenSource() oauth2.TokenSource { check := func(err error) { if err != nil { panic(err) } } token := new(oauth2.Token) tokenData, err := os.ReadFile("token.tok") if err == nil { _ = json.Unmarshal(tokenData, token) } else { token, err = auth.RequestLiveToken() check(err) } src := auth.RefreshTokenSource(token) _, err = src.Token() if err != nil { // The cached refresh token expired and can no longer be used to obtain a new token. We require the // user to log in again and use that token instead. token, err = auth.RequestLiveToken() check(err) src = auth.RefreshTokenSource(token) } go func() { c := make(chan os.Signal, 3) signal.Notify(c, syscall.SIGTERM, syscall.SIGINT) <-c tok, _ := src.Token() b, _ := json.Marshal(tok) _ = os.WriteFile("token.tok", b, 0644) os.Exit(0) }() return src }