improvements to interactive input, more reliable skins

This commit is contained in:
olebeck 2022-10-21 03:58:30 +02:00
parent a771b5f1d8
commit 8b24744f85
10 changed files with 221 additions and 140 deletions

View File

@ -7,7 +7,7 @@ import (
"fmt"
"os"
"os/signal"
"regexp"
"runtime/debug"
"syscall"
"github.com/bedrock-tool/bedrocktool/utils"
@ -29,6 +29,7 @@ func main() {
logrus.Infof("Version: %s", utils.Version)
logrus.Infof("Cmdline: %s", os.Args)
logrus.Errorf("Error: %s", err)
fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
println("--END COPY HERE--")
println("")
println("if you want to report this error, please open an issue at")
@ -81,12 +82,11 @@ func main() {
}
fmt.Printf("Use '%s <command>' to run a command\n", os.Args[0])
fmt.Printf("Input Command: ")
reader := bufio.NewReader(os.Stdin)
target, _ := reader.ReadString('\n')
r, _ := regexp.Compile(`[\n\r]`)
target = string(r.ReplaceAll([]byte(target), []byte("")))
os.Args = append(os.Args, target)
cmd, cancelled := utils.User_input(ctx, "Input Command: ")
if cancelled {
return
}
os.Args = append(os.Args, cmd)
utils.G_interactive = true
}
}
@ -103,6 +103,7 @@ func main() {
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
println("cancelling")
cancel()
}()

View File

@ -54,7 +54,7 @@ func (c *CaptureCMD) Usage() string {
}
func (c *CaptureCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
address, hostname, err := utils.ServerInput(c.server_address)
address, hostname, err := utils.ServerInput(ctx, c.server_address)
if err != nil {
logrus.Fatal(err)
return 1

View File

@ -32,7 +32,7 @@ func (c *ChatLogCMD) Usage() string {
}
func (c *ChatLogCMD) Execute(ctx context.Context, flags *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
address, hostname, err := utils.ServerInput(c.Address)
address, hostname, err := utils.ServerInput(ctx, c.Address)
if err != nil {
logrus.Error(err)
return 1

View File

@ -29,7 +29,7 @@ func (c *DebugProxyCMD) Usage() string {
}
func (c *DebugProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
address, _, err := utils.ServerInput(c.Address)
address, _, err := utils.ServerInput(ctx, c.Address)
if err != nil {
logrus.Error(err)
return 1

View File

@ -14,8 +14,9 @@ import (
)
type SkinProxyCMD struct {
server_address string
filter string
server_address string
filter string
only_with_geometry bool
}
func (*SkinProxyCMD) Name() string { return "skins-proxy" }
@ -24,6 +25,7 @@ func (*SkinProxyCMD) Synopsis() string { return "download skins from players on
func (c *SkinProxyCMD) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.server_address, "address", "", "remote server address")
f.StringVar(&c.filter, "filter", "", "player name filter prefix")
f.BoolVar(&c.only_with_geometry, "only-geom", false, "only save skins with geometry")
}
func (c *SkinProxyCMD) Usage() string {
@ -31,7 +33,7 @@ func (c *SkinProxyCMD) Usage() string {
}
func (c *SkinProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
address, hostname, err := utils.ServerInput(c.server_address)
address, hostname, err := utils.ServerInput(ctx, c.server_address)
if err != nil {
logrus.Error(err)
return 1
@ -42,7 +44,7 @@ func (c *SkinProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interf
proxy := utils.NewProxy(logrus.StandardLogger())
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool) (packet.Packet, error) {
if !toServer {
process_packet_skins(proxy.Client, out_path, pk, c.filter)
process_packet_skins(proxy.Client, out_path, pk, c.filter, c.only_with_geometry)
}
return pk, nil
}

View File

@ -27,6 +27,18 @@ type Skin struct {
protocol.Skin
}
type SkinMeta struct {
SkinID string
PlayFabID string
PremiumSkin bool
PersonaSkin bool
CapeID string
SkinColour string
ArmSize string
Trusted bool
PersonaPieces []protocol.PersonaPiece
}
// WriteGeometry writes the geometry json for the skin to output_path
func (skin *Skin) WriteGeometry(output_path string) error {
f, err := os.Create(output_path)
@ -96,17 +108,7 @@ func (skin *Skin) WriteMeta(output_path string) error {
return fmt.Errorf("failed to write Tint %s: %s", output_path, err)
}
defer f.Close()
d, err := json.MarshalIndent(struct {
SkinID string
PlayFabID string
PremiumSkin bool
PersonaSkin bool
CapeID string
SkinColour string
ArmSize string
Trusted bool
PersonaPieces []protocol.PersonaPiece
}{
d, err := json.MarshalIndent(SkinMeta{
skin.SkinID,
skin.PlayFabID,
skin.PremiumSkin,
@ -124,13 +126,17 @@ func (skin *Skin) WriteMeta(output_path string) error {
return nil
}
func (skin *Skin) Complex() bool {
have_geometry, have_cape, have_animations, have_tint := len(skin.SkinGeometry) > 0, len(skin.CapeData) > 0, len(skin.Animations) > 0, len(skin.PieceTintColours) > 0
return have_geometry || have_cape || have_animations || have_tint
}
// Write writes all data for this skin to a folder
func (skin *Skin) Write(output_path, name string) error {
name, _ = filenamify.FilenamifyV2(name)
skin_dir := path.Join(output_path, name)
have_geometry, have_cape, have_animations, have_tint := len(skin.SkinGeometry) > 0, len(skin.CapeData) > 0, len(skin.Animations) > 0, len(skin.PieceTintColours) > 0
os.MkdirAll(skin_dir, 0o755)
if have_geometry {
if err := skin.WriteGeometry(path.Join(skin_dir, "geometry.json")); err != nil {
@ -157,17 +163,14 @@ func (skin *Skin) Write(output_path, name string) error {
return err
}
return skin.WriteTexture(path.Join(skin_dir, "skin.png"))
return skin.WriteTexture(skin_dir + "/skin.png")
}
// puts the skin at output_path if the filter matches it
// internally converts the struct so it can use the extra methods
func write_skin(output_path, name string, skin protocol.Skin, filter string) {
if !strings.HasPrefix(name, filter) {
return
}
func write_skin(output_path, name string, skin *protocol.Skin) {
logrus.Infof("Writing skin for %s\n", name)
_skin := &Skin{skin}
_skin := &Skin{*skin}
if err := _skin.Write(output_path, name); err != nil {
fmt.Fprintf(os.Stderr, "Error writing skin: %s\n", err)
}
@ -176,37 +179,73 @@ func write_skin(output_path, name string, skin protocol.Skin, filter string) {
var (
skin_players = make(map[string]string)
skin_player_counts = make(map[string]int)
processed_skins = make(map[string]bool)
)
func process_packet_skins(conn *minecraft.Conn, out_path string, pk packet.Packet, filter string) {
func popup_skin_saved(conn *minecraft.Conn, name string) {
if conn != nil {
(&utils.ProxyContext{Client: conn}).SendPopup(fmt.Sprintf("%s Skin was Saved", name))
}
}
func skin_meta_get_skinid(path string) string {
cont, err := os.ReadFile(fmt.Sprintf("%s/metadata.json", path))
if err != nil {
return ""
}
var meta SkinMeta
if err := json.Unmarshal(cont, &meta); err != nil {
return ""
}
return meta.SkinID
}
func save_player_skin(conn *minecraft.Conn, out_path, player_name string, skin *protocol.Skin) {
count := skin_player_counts[player_name]
if count > 0 {
meta_id := skin_meta_get_skinid(fmt.Sprintf("%s/%s_%d", out_path, player_name, count-1))
if meta_id == skin.SkinID {
return // skin same as before
}
}
skin_player_counts[player_name]++
count++
write_skin(out_path, fmt.Sprintf("%s_%d", player_name, count), skin)
popup_skin_saved(conn, player_name)
}
func process_packet_skins(conn *minecraft.Conn, out_path string, pk packet.Packet, filter string, only_if_geom bool) {
switch _pk := pk.(type) {
case *packet.PlayerSkin:
name := skin_players[_pk.UUID.String()]
if name == "" {
name = _pk.UUID.String()
player_name := skin_players[_pk.UUID.String()]
if player_name == "" {
player_name = _pk.UUID.String()
}
skin_player_counts[name]++
name = fmt.Sprintf("%s_%d", name, skin_player_counts[name])
write_skin(out_path, name, _pk.Skin, filter)
if !strings.HasPrefix(player_name, filter) {
return
}
if only_if_geom && len(_pk.Skin.SkinGeometry) == 0 {
return
}
save_player_skin(conn, out_path, player_name, &_pk.Skin)
case *packet.PlayerList:
if _pk.ActionType == 1 { // remove
return
}
for _, player := range _pk.Entries {
name := utils.CleanupName(player.Username)
if name == "" {
name = player.UUID.String()
player_name := utils.CleanupName(player.Username)
if player_name == "" {
player_name = player.UUID.String()
}
if _, ok := processed_skins[name]; ok {
continue
if !strings.HasPrefix(player_name, filter) {
return
}
write_skin(out_path, name, player.Skin, filter)
skin_players[player.UUID.String()] = name
processed_skins[name] = true
if conn != nil {
(&utils.ProxyContext{Client: conn}).SendPopup(fmt.Sprintf("%s Skin was Saved", name))
if only_if_geom && len(player.Skin.SkinGeometry) == 0 {
return
}
skin_players[player.UUID.String()] = player_name
save_player_skin(conn, out_path, player_name, &player.Skin)
}
}
}
@ -229,7 +268,7 @@ func (c *SkinCMD) Usage() string {
}
func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
address, hostname, err := utils.ServerInput(c.server_address)
address, hostname, err := utils.ServerInput(ctx, c.server_address)
if err != nil {
fmt.Fprint(os.Stderr, err)
return 1
@ -259,7 +298,7 @@ func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}
if err != nil {
return 1
}
process_packet_skins(nil, out_path, pk, c.filter)
process_packet_skins(nil, out_path, pk, c.filter, false)
}
}

View File

@ -142,7 +142,7 @@ func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{
}()
*/
server_address, hostname, err := utils.ServerInput(c.Address)
server_address, hostname, err := utils.ServerInput(ctx, c.Address)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1

View File

@ -12,6 +12,25 @@ import (
"github.com/sirupsen/logrus"
)
func User_input(ctx context.Context, q string) (string, bool) {
c := make(chan string)
go func() {
fmt.Print(q)
reader := bufio.NewReader(os.Stdin)
answer, _ := reader.ReadString('\n')
r, _ := regexp.Compile(`[\n\r]`)
answer = string(r.ReplaceAll([]byte(answer), []byte("")))
c <- answer
}()
select {
case <-ctx.Done():
return "", true
case a := <-c:
return a, false
}
}
func server_url_to_name(server string) string {
host, _, err := net.SplitHostPort(server)
if err != nil {
@ -20,13 +39,13 @@ func server_url_to_name(server string) string {
return host
}
func ServerInput(server string) (address, name string, err error) {
func ServerInput(ctx context.Context, server string) (address, name string, err error) {
if server == "" { // no arg provided, interactive input
fmt.Printf("Enter Server: ")
reader := bufio.NewReader(os.Stdin)
server, _ = reader.ReadString('\n')
r, _ := regexp.Compile(`[\n\r]`)
server = string(r.ReplaceAll([]byte(server), []byte("")))
var cancelled bool
server, cancelled = User_input(ctx, "Enter Server: ")
if cancelled {
return "", "", context.Canceled
}
}
if strings.HasPrefix(server, "realm:") { // for realms use api to get ip address

View File

@ -156,94 +156,109 @@ func NewProxy(log *logrus.Logger) *ProxyContext {
var Client_addr net.Addr
func (p *ProxyContext) Run(ctx context.Context, server_address string) (err error) {
c := make(chan struct{})
if strings.HasSuffix(server_address, ".pcap") {
return fmt.Errorf("not supported anymore!")
return fmt.Errorf("not supported anymore")
}
if strings.HasSuffix(server_address, ".pcap2") {
return create_replay_connection(ctx, p.log, server_address, p.ConnectCB, p.PacketCB)
}
GetTokenSource() // ask for login before listening
var packs []*resource.Pack
if G_preload_packs {
p.log.Info("Preloading resourcepacks")
serverConn, err := ConnectServer(ctx, server_address, nil, true, nil)
if err != nil {
return fmt.Errorf("failed to connect to %s: %s", server_address, err)
go func() {
defer func() { c <- struct{}{} }()
GetTokenSource() // ask for login before listening
var packs []*resource.Pack
if G_preload_packs {
p.log.Info("Preloading resourcepacks")
var serverConn *minecraft.Conn
serverConn, err = ConnectServer(ctx, server_address, nil, true, nil)
if err != nil {
err = fmt.Errorf("failed to connect to %s: %s", server_address, err)
return
}
serverConn.Close()
packs = serverConn.ResourcePacks()
p.log.Infof("%d packs loaded", len(packs))
}
serverConn.Close()
packs = serverConn.ResourcePacks()
p.log.Infof("%d packs loaded", len(packs))
}
_status := minecraft.NewStatusProvider("Server")
p.Listener, err = minecraft.ListenConfig{
StatusProvider: _status,
ResourcePacks: packs,
AcceptedProtocols: []minecraft.Protocol{
dummyProto{id: 544, ver: "1.19.20"},
},
}.Listen("raknet", ":19132")
if err != nil {
_status := minecraft.NewStatusProvider("Server")
p.Listener, err = minecraft.ListenConfig{
StatusProvider: _status,
ResourcePacks: packs,
AcceptedProtocols: []minecraft.Protocol{
dummyProto{id: 544, ver: "1.19.20"},
},
}.Listen("raknet", ":19132")
if err != nil {
return
}
defer p.Listener.Close()
p.log.Infof("Listening on %s", p.Listener.Addr())
var c net.Conn
c, err = p.Listener.Accept()
if err != nil {
p.log.Fatal(err)
}
p.Client = c.(*minecraft.Conn)
cd := p.Client.ClientData()
p.Server, err = ConnectServer(ctx, server_address, &cd, false, p.PacketFunc)
if err != nil {
err = fmt.Errorf("failed to connect to %s: %s", server_address, err)
return
}
// spawn and start the game
if err = spawn_conn(ctx, p.Client, p.Server); err != nil {
err = fmt.Errorf("failed to spawn: %s", err)
return
}
defer p.Server.Close()
defer p.Listener.Disconnect(p.Client, G_disconnect_reason)
if p.ConnectCB != nil {
p.ConnectCB(p)
}
wg := sync.WaitGroup{}
cbs := []PacketCallback{
p.CommandHandlerPacketCB,
}
if p.PacketCB != nil {
cbs = append(cbs, p.PacketCB)
}
// server to client
wg.Add(1)
go func() {
defer wg.Done()
if err := proxyLoop(ctx, p, false, cbs); err != nil {
p.log.Error(err)
return
}
}()
// client to server
wg.Add(1)
go func() {
defer wg.Done()
if err := proxyLoop(ctx, p, true, cbs); err != nil {
p.log.Error(err)
return
}
}()
wg.Wait()
}()
select {
case <-ctx.Done():
println("Cancelled")
return context.Canceled
case <-c:
return err
}
defer p.Listener.Close()
p.log.Infof("Listening on %s", p.Listener.Addr())
c, err := p.Listener.Accept()
if err != nil {
p.log.Fatal(err)
}
p.Client = c.(*minecraft.Conn)
cd := p.Client.ClientData()
p.Server, err = ConnectServer(ctx, server_address, &cd, false, p.PacketFunc)
if err != nil {
return fmt.Errorf("failed to connect to %s: %s", server_address, err)
}
// spawn and start the game
if err := spawn_conn(ctx, p.Client, p.Server); err != nil {
return fmt.Errorf("failed to spawn: %s", err)
}
defer p.Server.Close()
defer p.Listener.Disconnect(p.Client, G_disconnect_reason)
if p.ConnectCB != nil {
p.ConnectCB(p)
}
wg := sync.WaitGroup{}
cbs := []PacketCallback{
p.CommandHandlerPacketCB,
}
if p.PacketCB != nil {
cbs = append(cbs, p.PacketCB)
}
// server to client
wg.Add(1)
go func() {
defer wg.Done()
if err := proxyLoop(ctx, p, false, cbs); err != nil {
p.log.Error(err)
return
}
}()
// client to server
wg.Add(1)
go func() {
defer wg.Done()
if err := proxyLoop(ctx, p, true, cbs); err != nil {
p.log.Error(err)
return
}
}()
wg.Wait()
return nil
}

View File

@ -5,6 +5,7 @@ import (
"encoding/binary"
"io"
"os"
"reflect"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
@ -31,7 +32,9 @@ func create_replay_connection(ctx context.Context, log *logrus.Logger, filename
proxy.Server = minecraft.NewConn()
game_started := false
i := 0
for {
i += 1
var magic uint32 = 0
var packet_length uint32 = 0
var toServer bool = false
@ -55,7 +58,7 @@ func create_replay_connection(ctx context.Context, log *logrus.Logger, filename
return nil
}
if n != int(packet_length) {
log.Errorf("Truncated")
log.Errorf("Truncated %d", i)
return nil
}
@ -76,6 +79,8 @@ func create_replay_connection(ctx context.Context, log *logrus.Logger, filename
}
for _, pk := range pks {
logrus.Printf("%s", reflect.TypeOf(pk).String()[1:])
if game_started {
if packetCB != nil {
packetCB(pk, proxy, toServer)