191 lines
4.4 KiB
Go
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
|
|
}
|