Files
legacy-version/example-proxy.go
AkmalFairuz 151816f18d fix...
2025-12-11 08:08:58 +07:00

191 lines
4.4 KiB
Go

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
}