move tools to strom cli

This commit is contained in:
adman
2026-06-27 13:06:05 +02:00
parent c0b1501f23
commit b2784574bd
6 changed files with 333 additions and 30 deletions
-1
View File
@@ -1,5 +1,4 @@
.devres/ .devres/
.idea/ .idea/
.failed_packets/ .failed_packets/
tools/.*/
.vscode/ .vscode/
+18 -10
View File
@@ -12,11 +12,15 @@ import (
"github.com/admin-else/strom/cmd/strom/api" "github.com/admin-else/strom/cmd/strom/api"
"github.com/admin-else/strom/cmd/strom/data" "github.com/admin-else/strom/cmd/strom/data"
"github.com/admin-else/strom/cmd/strom/extract_mca" "github.com/admin-else/strom/cmd/strom/extract_mca"
"github.com/admin-else/strom/cmd/strom/mca_to_tmcpr"
"github.com/admin-else/strom/cmd/strom/offline_uuid" "github.com/admin-else/strom/cmd/strom/offline_uuid"
"github.com/admin-else/strom/cmd/strom/packet_info" "github.com/admin-else/strom/cmd/strom/packet_info"
"github.com/admin-else/strom/cmd/strom/packet_inspector" "github.com/admin-else/strom/cmd/strom/packet_inspector"
"github.com/admin-else/strom/cmd/strom/print_nbt" "github.com/admin-else/strom/cmd/strom/print_nbt"
"github.com/admin-else/strom/cmd/strom/print_replay" "github.com/admin-else/strom/cmd/strom/print_replay"
"github.com/admin-else/strom/cmd/strom/registry_data_hunter"
"github.com/admin-else/strom/cmd/strom/serve_tmcpr"
"github.com/admin-else/strom/cmd/strom/serve_world"
"github.com/admin-else/strom/cmd/strom/status" "github.com/admin-else/strom/cmd/strom/status"
) )
@@ -45,16 +49,20 @@ func Version(args []string) error {
} }
var subcommands = map[string]func(args []string) error{ var subcommands = map[string]func(args []string) error{
"offline-uuid": offline_uuid.Run, "offline-uuid": offline_uuid.Run,
"packet-info": packet_info.Run, "packet-info": packet_info.Run,
"print-nbt": print_nbt.Run, "print-nbt": print_nbt.Run,
"status": status.Run, "status": status.Run,
"packet-spy": packet_inspector.Run, "packet-spy": packet_inspector.Run,
"api": api.Run, "api": api.Run,
"version": Version, "version": Version,
"extract-mca": extract_mca.Run, "extract-mca": extract_mca.Run,
"data": data.Run, "data": data.Run,
"print-replay": print_replay.Run, "print-replay": print_replay.Run,
"registry-data-hunter": registry_data_hunter.Run,
"serve-world": serve_world.Run,
"mca-to-tmcpr": mca_to_tmcpr.Run,
"serve-tmcpr": serve_tmcpr.Run,
} }
func Help(args []string) (err error) { func Help(args []string) (err error) {
+211
View File
@@ -0,0 +1,211 @@
package mca_to_tmcpr
import (
"bytes"
"flag"
"fmt"
"log/slog"
"os"
"github.com/admin-else/strom/level"
"github.com/admin-else/strom/level/anvil"
"github.com/admin-else/strom/nbt"
"github.com/admin-else/strom/proto/replay"
"github.com/admin-else/strom/proto_generated/v1_21_11"
"github.com/admin-else/strom/util"
)
var (
cmd = flag.NewFlagSet("mca-to-tmcpr", flag.ContinueOnError)
worldPath = cmd.String("world", "", "Path to world directory")
output = cmd.String("output", "", "Output .tmcpr file path")
radius = cmd.Int("radius", 14, "Chunk radius around spawn")
spawnX = cmd.Float64("spawn-x", 0.5, "Spawn X coordinate")
spawnY = cmd.Float64("spawn-y", 100, "Spawn Y coordinate")
spawnZ = cmd.Float64("spawn-z", 0.5, "Spawn Z coordinate")
)
var FullLight [][]uint8
func init() {
light := make([]uint8, 2048)
for i := range light {
light[i] = 0xFF
}
sectionsPerChunk := 384 / 16
FullLight = make([][]uint8, sectionsPerChunk)
for i := range sectionsPerChunk {
FullLight[i] = light
}
}
func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
xmca := util.FloorDiv(x, anvil.ChunksWidthRegionFile)
zmca := util.FloorDiv(z, anvil.ChunksWidthRegionFile)
x = x % anvil.ChunksWidthRegionFile
if x < 0 {
x += anvil.ChunksWidthRegionFile
}
z = z % anvil.ChunksWidthRegionFile
if z < 0 {
z += anvil.ChunksWidthRegionFile
}
f, err := os.Open(fmt.Sprintf("./level/anvil/testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
if err != nil {
return
}
defer f.Close()
mca, err := anvil.ReadChunkHeader(f)
if err != nil {
return
}
var n *nbt.Tag
n, err = mca.ChunkAt(f, x, z)
if err != nil {
return
}
if n == nil {
return
}
var chunk anvil.Chunk
err = nbt.Format.Decode(n.Value, &chunk)
if err != nil {
return
}
var packetChunk *level.Chunk
packetChunk, err = chunk.ToStorage(version)
if err != nil {
return
}
buffer := bytes.NewBuffer(nil)
err = packetChunk.WriteChunkData(buffer)
if err != nil {
return
}
b = buffer.Bytes()
return
}
func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug)
err := cmd.Parse(args)
if err != nil {
return err
}
if *worldPath == "" {
slog.Error("no world path specified")
return nil
}
if *output == "" {
slog.Error("no output path specified")
return nil
}
outFile, err := os.Create(*output)
if err != nil {
slog.Error("failed to create output file", "error", err)
return err
}
defer outFile.Close()
w := replay.NewWriter(outFile)
err = w.WritePacket(&v1_21_11.PlayToClientPacketLogin{
EntityId: 73,
IsHardcore: false,
WorldNames: []string{"minecraft:overworld", "minecraft:the_end", "minecraft:the_nether"},
MaxPlayers: 100,
ViewDistance: 10,
SimulationDistance: 10,
ReducedDebugInfo: false,
EnableRespawnScreen: true,
DoLimitedCrafting: false,
WorldState: v1_21_11.PlayToClientSpawnInfo{
Dimension: 0,
Name: "minecraft:overworld",
HashedSeed: -8365436745629239539,
Gamemode: "survival",
PreviousGamemode: 0xff,
IsDebug: false,
IsFlat: false,
Death: nil,
PortalCooldown: 0,
SeaLevel: 63,
},
EnforcesSecureChat: false,
}, 0)
if err != nil {
slog.Error("failed to write login packet", "error", err)
return err
}
err = w.WritePacket(&v1_21_11.PlayToClientPacketPosition{
TeleportId: 0,
X: *spawnX,
Y: *spawnY,
Z: *spawnZ,
Dx: 0,
Dy: 0,
Dz: 0,
Yaw: 0,
Pitch: 0,
Flags: v1_21_11.PlayToClientPositionUpdateRelatives{},
}, 0)
if err != nil {
slog.Error("failed to write position packet", "error", err)
return err
}
chunkX := int32(*spawnX) >> 4
chunkZ := int32(*spawnZ) >> 4
err = w.WritePacket(&v1_21_11.PlayToClientPacketUpdateViewPosition{
ChunkX: chunkX,
ChunkZ: chunkZ,
}, 0)
if err != nil {
slog.Error("failed to write view position packet", "error", err)
return err
}
err = w.WritePacket(&v1_21_11.PlayToClientPacketGameStateChange{
Reason: "level_chunks_load_start",
}, 0)
if err != nil {
slog.Error("failed to write game state packet", "error", err)
return err
}
radius32 := int32(*radius)
for x := -radius32; x < radius32; x++ {
for z := -radius32; z < radius32; z++ {
var chunkData []byte
chunkData, err = GetChunkFromMca(x, z, "1.21.11")
if err != nil {
return err
}
if chunkData == nil {
continue
}
OLDpacket := &v1_21_11.PlayToClientPacketMapChunk{}
OLDpacket.X = x
OLDpacket.Z = z
OLDpacket.ChunkData = v1_21_11.ByteArray{Val: chunkData}
OLDpacket.BlockLight = FullLight
OLDpacket.BlockLightMask = []int64{(1 << 24) - 1}
err = w.WritePacket(OLDpacket, 0)
if err != nil {
return err
}
}
}
slog.Info("conversion complete", "output", *output)
return nil
}
@@ -1,4 +1,4 @@
package main package registry_data_hunter
import ( import (
"encoding/json" "encoding/json"
@@ -25,10 +25,7 @@ func wontFail[T any](v T, err error) T {
return v return v
} }
// THIS IS REDUNDANT USE LOGIN PACKET IN MINECRAFT DATA
var ( var (
// Edit these two
Version = "1.21.11" Version = "1.21.11"
SaveAs = "data/registry/" + Version + ".nbt" SaveAs = "data/registry/" + Version + ".nbt"
@@ -113,7 +110,6 @@ func (p *Proxy) OnTags(data *v1_21_8.ConfigurationToClientPacketTags) (err error
func handleClient(c *proto.Conn) (err error) { func handleClient(c *proto.Conn) (err error) {
acc := api.NewOfflineAccount("RegistryHunter") acc := api.NewOfflineAccount("RegistryHunter")
// ignore resource leak warning we own c conn so we should not close it here
_, err = server.ServeLogin(c, server.WithOtherAccount(acc), server.WithCompatibleVersions(CompatibleProtocolVersion), server.WithRawStatus(Status)) _, err = server.ServeLogin(c, server.WithOtherAccount(acc), server.WithCompatibleVersions(CompatibleProtocolVersion), server.WithRawStatus(Status))
if err != nil { if err != nil {
return return
@@ -150,10 +146,7 @@ func handleClient(c *proto.Conn) (err error) {
return return
} }
func main() { func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug) slog.SetLogLoggerLevel(slog.LevelDebug)
err := server.StartServerWithOnConn(":25566", handleClient) return server.StartServerWithOnConn(":25566", handleClient)
if err != nil {
panic(err)
}
} }
+97
View File
@@ -0,0 +1,97 @@
package serve_tmcpr
import (
"errors"
"flag"
"io"
"log/slog"
"math/rand/v2"
"os"
"time"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto/replay"
"github.com/admin-else/strom/proto_generated/v1_21_8"
"github.com/admin-else/strom/server"
)
type Server struct {
*proto.Conn
*event.Timer
}
var (
cmd = flag.NewFlagSet("serve-tmcpr", flag.ContinueOnError)
tmcprFile = cmd.String("file", "/tmp/world.tmcpr", "Path to .tmcpr replay file")
bindAddr = cmd.String("bind", ":25566", "Address to listen on")
)
func Limbo(c *proto.Conn, tmcprPath string) (err error) {
s := &Server{Conn: c}
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
if err != nil {
return
}
err = server.ServeConfig(s.Conn)
if err != nil {
return
}
f, err := os.Open(tmcprPath)
if err != nil {
return
}
defer f.Close()
r := replay.NewReader(f)
r.Version = c.ProtocolVersion
start := time.Now()
for {
var packetData []byte
var timestamp uint32
packetData, timestamp, _, err = r.ReadRaw()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
time.Sleep(time.Duration(timestamp)*time.Millisecond - time.Since(start))
err = s.SendRaw(packetData)
if err != nil {
return err
}
}
s.Timer = &event.Timer{}
s.Timer.Every(time.Second*15, s.SendKeepAlive)
s.Timer.Start(c.Loop)
return s.StartConn()
}
func (s *Server) SendKeepAlive() error {
return s.Send(&v1_21_8.PlayToClientPacketKeepAlive{KeepAliveId: rand.Int64()})
}
func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug)
err := cmd.Parse(args)
if err != nil {
return err
}
if *tmcprFile == "" {
slog.Error("no file specified")
return nil
}
return server.StartServerWithOnConn(*bindAddr, func(c *proto.Conn) error {
return Limbo(c, *tmcprFile)
})
}
@@ -1,4 +1,4 @@
package main package serve_world
import ( import (
"bytes" "bytes"
@@ -28,7 +28,7 @@ type Server struct {
var FullLight [][]uint8 var FullLight [][]uint8
func init() { func init() {
light := make([]uint8, 2048) // 4096 values 2 per uint8 light := make([]uint8, 2048)
for i := range light { for i := range light {
light[i] = 0xFF light[i] = 0xFF
} }
@@ -95,13 +95,11 @@ func Limbo(c *proto.Conn) (err error) {
s := &Server{ s := &Server{
Conn: c, Conn: c,
} }
//s.DebugPrintPackets = []string{""}
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode()) _, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
if err != nil { if err != nil {
return return
} }
//goland:noinspection GoResourceLeak captures the conn object which should NOT be closed
err = server.ServeConfig(s.Conn) err = server.ServeConfig(s.Conn)
if err != nil { if err != nil {
return return
@@ -176,10 +174,7 @@ func (s *Server) SendKeepAlive() (err error) {
return return
} }
func main() { func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug) slog.SetLogLoggerLevel(slog.LevelDebug)
err := server.StartServerWithOnConn(":25566", Limbo) return server.StartServerWithOnConn(":25566", Limbo)
if err != nil {
panic(err)
}
} }