package main import ( "errors" "log" "os" "sync" "time" "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" ) // The following program implements a proxy that forwards players from one local address to a remote address. func main() { config := readConfig() token, err := auth.RequestLiveToken() if err != nil { panic(err) } src := auth.RefreshTokenSource(token) 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 } 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 }