Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96481da0a4 | ||
|
|
159437c1ca | ||
|
|
3d34ad59e9 | ||
|
|
4878c6cc7b | ||
|
|
719a3bacd8 | ||
|
|
ce0c58e7c5 | ||
|
|
8a4e44760e | ||
|
|
e810a60848 | ||
|
|
0981339028 | ||
|
|
7746dd8ee9 | ||
|
|
378b78312e | ||
|
|
bd3139754d | ||
|
|
841d91b91d | ||
|
|
895ec53698 | ||
|
|
f6001c8b22 | ||
|
|
d87c362270 | ||
|
|
236e414242 | ||
|
|
5222390b65 | ||
|
|
dc04fdc7d0 | ||
|
|
8de34dfda1 | ||
|
|
997594d7c3 | ||
|
|
c83a777eac | ||
|
|
65fea61667 | ||
|
|
c2777f2ca9 | ||
|
|
dcb42f1970 | ||
|
|
7a2081f0a0 | ||
|
|
acc583f694 | ||
|
|
76409d07a3 | ||
|
|
bb89730781 | ||
|
|
9f6b1f6bcf | ||
|
|
1b8a9677c6 | ||
|
|
79ab13ca4b | ||
|
|
3cc414185a | ||
|
|
fe4a17dce2 | ||
|
|
891551f9dd | ||
|
|
8fb48c6ea9 | ||
|
|
ec25c28061 | ||
|
|
45287f47d2 | ||
|
|
4cc4b6af5b | ||
|
|
09e6760d61 | ||
|
|
ca74a3c3d4 | ||
|
|
fbc3693219 | ||
|
|
fb69fd93ec | ||
|
|
a3a3c3714e | ||
|
|
9294631c6c | ||
|
|
293f0b1eed | ||
|
|
272dcb4d36 | ||
|
|
abb0fa4d37 | ||
|
|
e5212624af | ||
|
|
35772aca03 | ||
|
|
819d89b26e | ||
|
|
958102e43c | ||
|
|
f749d3bfc5 | ||
|
|
d207cf52cf | ||
|
|
420ec5611c | ||
|
|
4bfaf9b7c5 | ||
|
|
48beb861e8 | ||
|
|
9fd2f517d9 | ||
|
|
1b5af63b75 | ||
|
|
dfb847eb3e | ||
|
|
322bd77f7f | ||
|
|
aefe850281 | ||
|
|
57f88f2176 | ||
|
|
8dd566c0da | ||
|
|
e386a45d96 | ||
|
|
8f771166a6 | ||
|
|
5d6a1a25ea | ||
|
|
ca33185f20 | ||
|
|
e921e9e9d2 | ||
|
|
7337068d86 | ||
|
|
9983062522 | ||
|
|
4fd5324064 | ||
|
|
df0e5e354b | ||
|
|
23b5a5d9b7 | ||
|
|
46770ed980 | ||
|
|
6179c5f6b6 |
+2
-1
@@ -1,4 +1,5 @@
|
||||
.devres/
|
||||
.idea/
|
||||
.failed_packets/
|
||||
tools/.*/
|
||||
tools/.*/
|
||||
.vscode/
|
||||
@@ -0,0 +1,5 @@
|
||||
# STROM
|
||||
|
||||
# THIS IS WORK IN PROGRESS AND THE API WILL BREAK AS LONG AS THE THE PACKAGE VERSION IS v0.x.x
|
||||
|
||||
feel free to contact me on [discord](https://discord.gg/n2Vjc2vcMf)
|
||||
+64
-37
@@ -9,32 +9,40 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/admin-else/strom/crypto"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Servers struct {
|
||||
ServicesHost string
|
||||
SessionHost string
|
||||
ProfilesHost string
|
||||
}
|
||||
|
||||
var DefaultServers = &Servers{
|
||||
ServicesHost: "https://api.minecraftservices.com",
|
||||
SessionHost: "https://sessionserver.mojang.com",
|
||||
ProfilesHost: "https://api.mojang.com",
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
*Servers
|
||||
Name string
|
||||
Uuid uuid.UUID
|
||||
Ygg string
|
||||
}
|
||||
|
||||
const (
|
||||
BaseUrlApi = "https://api.minecraftservices.com"
|
||||
)
|
||||
|
||||
func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) {
|
||||
var b []byte
|
||||
if method != "GET" {
|
||||
b, err = json.Marshal(from)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r, err := http.NewRequest(method, url, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
@@ -44,21 +52,22 @@ func (a *Account) doMcApiRequest(method string, url string, from any, to any) (e
|
||||
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
|
||||
return fmt.Errorf("bad status code %v", resp.StatusCode)
|
||||
}
|
||||
b, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(b, to)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
type PlayerKeys struct {
|
||||
@@ -73,7 +82,7 @@ type PlayerKeys struct {
|
||||
}
|
||||
|
||||
func (a *Account) FetchKeys() (keys PlayerKeys, err error) {
|
||||
err = a.doMcApiRequest("POST", BaseUrlApi+"/player/certificates", nil, &keys)
|
||||
err = a.doMcApiRequest("POST", a.ServicesHost+"/player/certificates", nil, &keys)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -90,10 +99,12 @@ func (a *Account) JoinServer(serverId string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err := http.Post("https://sessionserver.mojang.com/session/minecraft/join", "application/json", bytes.NewReader(body))
|
||||
resp, err := http.Post(a.SessionHost+"/session/minecraft/join", "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 204 {
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
@@ -112,17 +123,10 @@ type NameChangResp struct {
|
||||
}
|
||||
|
||||
func (a *Account) NameChangeInfo() (r NameChangResp, err error) {
|
||||
err = a.doMcApiRequest("GET", "https://api.minecraftservices.com/minecraft/profile/namechange", nil, &r)
|
||||
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile/namechange", nil, &r)
|
||||
return
|
||||
}
|
||||
|
||||
func NewOfflineAccount(name string) *Account {
|
||||
return &Account{
|
||||
Name: name,
|
||||
Uuid: crypto.FromOfflinePlayer(name),
|
||||
}
|
||||
}
|
||||
|
||||
// PlayerProfile represents the player's profile information
|
||||
type PlayerProfile struct {
|
||||
ID string `json:"id"`
|
||||
@@ -143,7 +147,7 @@ type PlayerProfile struct {
|
||||
|
||||
// QueryProfile queries the player's profile
|
||||
func (a *Account) QueryProfile() (profile PlayerProfile, err error) {
|
||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile", nil, &profile)
|
||||
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile", nil, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -181,7 +185,7 @@ type PlayerAttributes struct {
|
||||
|
||||
// QueryAttributes queries the player's attributes
|
||||
func (a *Account) QueryAttributes() (attrs PlayerAttributes, err error) {
|
||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/player/attributes", nil, &attrs)
|
||||
err = a.doMcApiRequest("GET", a.ServicesHost+"/player/attributes", nil, &attrs)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -194,7 +198,7 @@ type ModifyAttributesRequest struct {
|
||||
|
||||
// ModifyAttributes modifies the player's attributes
|
||||
func (a *Account) ModifyAttributes(req ModifyAttributesRequest) (attrs PlayerAttributes, err error) {
|
||||
err = a.doMcApiRequest("POST", BaseUrlApi+"/player/attributes", req, &attrs)
|
||||
err = a.doMcApiRequest("POST", a.ServicesHost+"/player/attributes", req, &attrs)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -205,24 +209,23 @@ type BlockedUsers struct {
|
||||
|
||||
// GetBlocklist gets the list of blocked users
|
||||
func (a *Account) GetBlocklist() (blocklist BlockedUsers, err error) {
|
||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/privacy/blocklist", nil, &blocklist)
|
||||
err = a.doMcApiRequest("GET", a.ServicesHost+"/privacy/blocklist", nil, &blocklist)
|
||||
return
|
||||
}
|
||||
|
||||
// CheckNameAvailability checks if a name is available
|
||||
type NameAvailability struct {
|
||||
Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED
|
||||
}
|
||||
|
||||
// CheckNameAvailability checks if a name is available
|
||||
func (a *Account) CheckNameAvailability(name string) (avail NameAvailability, err error) {
|
||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile/name/"+name+"/available", nil, &avail)
|
||||
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile/name/"+name+"/available", nil, &avail)
|
||||
return
|
||||
}
|
||||
|
||||
// ChangeName changes the player's name
|
||||
func (a *Account) ChangeName(newName string) (profile PlayerProfile, err error) {
|
||||
err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/name/"+newName, nil, &profile)
|
||||
err = a.doMcApiRequest("PUT", a.ServicesHost+"/minecraft/profile/name/"+newName, nil, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -234,19 +237,19 @@ type ChangeSkinRequest struct {
|
||||
|
||||
// ChangeSkin changes the player's skin
|
||||
func (a *Account) ChangeSkin(req ChangeSkinRequest) (profile PlayerProfile, err error) {
|
||||
err = a.doMcApiRequest("POST", BaseUrlApi+"/minecraft/profile/skins", req, &profile)
|
||||
err = a.doMcApiRequest("POST", a.ServicesHost+"/minecraft/profile/skins", req, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
// ResetSkin resets the player's skin to default
|
||||
func (a *Account) ResetSkin() (profile PlayerProfile, err error) {
|
||||
err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/skins/active", nil, &profile)
|
||||
err = a.doMcApiRequest("DELETE", a.ServicesHost+"/minecraft/profile/skins/active", nil, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
// HideCape hides the player's active cape
|
||||
func (a *Account) HideCape() (profile PlayerProfile, err error) {
|
||||
err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/capes/active", nil, &profile)
|
||||
err = a.doMcApiRequest("DELETE", a.ServicesHost+"/minecraft/profile/capes/active", nil, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -258,29 +261,53 @@ type ShowCapeRequest struct {
|
||||
// ShowCape shows a specific cape
|
||||
func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) {
|
||||
req := ShowCapeRequest{CapeID: capeID}
|
||||
err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/capes/active", req, &profile)
|
||||
err = a.doMcApiRequest("PUT", a.ServicesHost+"/minecraft/profile/capes/active", req, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
// CheckGiftCodeValidity checks if a gift code is valid
|
||||
func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error) {
|
||||
url := BaseUrlApi + "/productvoucher/giftcode"
|
||||
url := a.ServicesHost + "/productvoucher/" + giftCode
|
||||
r, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return
|
||||
}
|
||||
|
||||
r.Header.Set("Authorization", "Bearer "+a.Ygg)
|
||||
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Returns HTTP 200 or 204 if valid, 404 if invalid
|
||||
if resp.StatusCode == 200 || resp.StatusCode == 204 {
|
||||
return true, nil
|
||||
valid = true
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewAccountFromYGG(ygg string) (a *Account, err error) {
|
||||
a = &Account{
|
||||
Ygg: ygg,
|
||||
Servers: DefaultServers,
|
||||
}
|
||||
p, err := a.QueryProfile()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a.Name = p.Name
|
||||
a.Uuid, err = uuid.Parse(strings.ReplaceAll(p.ID, "-", ""))
|
||||
return
|
||||
}
|
||||
|
||||
func NewAccount(name string, uuid uuid.UUID, ygg string) (a *Account) {
|
||||
return &Account{
|
||||
Name: name,
|
||||
Uuid: uuid,
|
||||
Ygg: ygg,
|
||||
Servers: DefaultServers,
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package api
|
||||
|
||||
import "github.com/admin-else/strom/crypto"
|
||||
|
||||
func NewOfflineAccount(name string) *Account {
|
||||
return &Account{
|
||||
Name: name,
|
||||
Uuid: crypto.FromOfflinePlayer(name),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func BlockedServers() (s []string, err error) {
|
||||
resp, err := http.Get("https://sessionserver.mojang.com/blockedservers")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
d, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s = strings.Split(string(d), "\n")
|
||||
return
|
||||
}
|
||||
|
||||
type ProfileProperty struct {
|
||||
Name string `json:"name"`
|
||||
Signature string `json:"signature,omitempty"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type ProfileResp struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Legacy bool `json:"legacy"`
|
||||
Properties []ProfileProperty `json:"properties"`
|
||||
}
|
||||
|
||||
func HasJoined(name, server, ip string) (p ProfileResp, err error) {
|
||||
url := "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + name + "&serverId=" + server
|
||||
if ip != "" {
|
||||
url += "&ip=" + ip
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
err = fmt.Errorf("bad status code %v", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
err = json.NewDecoder(resp.Body).Decode(&p)
|
||||
return
|
||||
}
|
||||
|
||||
type MojangPublicKeysResp struct {
|
||||
ProfilePropertyKeys []string `json:"profilePropertyKeys"`
|
||||
PlayerCertificateKeys []string `json:"playerCertificateKeys"`
|
||||
|
||||
// AuthenticationKeys are undocumented by wiki.vg but they exist as of the time of writing so ill include them
|
||||
AuthenticationKeys []string `json:"authenticationKeys"`
|
||||
}
|
||||
|
||||
func MojangPublicKeys() (keys MojangPublicKeysResp, err error) {
|
||||
resp, err := http.Get("https://sessionserver.mojang.com/session/minecraft/profile/public_key_by_token")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
err = json.NewDecoder(resp.Body).Decode(&keys)
|
||||
return
|
||||
}
|
||||
|
||||
func Profile(id string, unsinged bool) (p ProfileResp, err error) {
|
||||
url := "https://sessionserver.mojang.com/session/minecraft/profile/" + id
|
||||
if !unsinged {
|
||||
url += "?unsigned=false"
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
err = json.NewDecoder(resp.Body).Decode(&p)
|
||||
return
|
||||
}
|
||||
|
||||
type IdName struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func NameToId(name string) (idName IdName, err error) {
|
||||
// may also use these:
|
||||
//https://api.mojang.com/users/profiles/minecraft/<player name>
|
||||
//https://api.minecraftservices.com/minecraft/profile/lookup/name/<player name>
|
||||
//https://api.mojang.com/minecraft/profile/lookup/name/<player name>
|
||||
resp, err := http.Get("https://api.minecraftservices.com/minecraft/profile/lookup/name/" + name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
err = json.NewDecoder(resp.Body).Decode(&idName)
|
||||
return
|
||||
}
|
||||
|
||||
func NameToIdBulk(names []string) (idNames []IdName, err error) {
|
||||
b := bytes.NewBuffer(nil)
|
||||
err = json.NewEncoder(b).Encode(names)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// may also use these:
|
||||
//https://api.mojang.com/profiles/minecraft
|
||||
//https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname
|
||||
//https://api.mojang.com/minecraft/profile/lookup/bulk/byname
|
||||
r, err := http.NewRequest("POST", "https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname", b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
err = json.NewDecoder(resp.Body).Decode(&idNames)
|
||||
return
|
||||
}
|
||||
+8
-7
@@ -1,9 +1,9 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/admin-else/strom/event"
|
||||
"github.com/admin-else/strom/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
)
|
||||
@@ -23,16 +23,17 @@ func Connect(addr string) (ret *proto.Conn, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func ConnectVersionLess(addr string) (ret *proto.Conn, err error) {
|
||||
return ConnectVersionLessCtx(context.Background(), addr)
|
||||
}
|
||||
|
||||
// ConnectVersionLess connects to the given address without setting the version you should set it
|
||||
// when using this function.
|
||||
// You must close the connection when you're done with it
|
||||
func ConnectVersionLess(addr string) (ret *proto.Conn, err error) {
|
||||
ret = &proto.Conn{}
|
||||
ret.SetState(proto_base.Handshaking)
|
||||
ret.SetCompressionThreshold(-1)
|
||||
func ConnectVersionLessCtx(ctx context.Context, addr string) (ret *proto.Conn, err error) {
|
||||
ret = proto.NewConnCtx(ctx)
|
||||
ret.Actor = proto_base.Client
|
||||
ret.Loop = event.NewLoop()
|
||||
ret.Conn, err = net.Dial("tcp", addr)
|
||||
ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package client
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/admin-else/strom/event"
|
||||
"github.com/admin-else/strom/proto"
|
||||
@@ -20,10 +19,7 @@ func (c *ConfigIgnorer) OnStart() (err error) {
|
||||
}
|
||||
|
||||
func (c *ConfigIgnorer) Default(e event.Anything) (err error) {
|
||||
if e.Val == (event.Tick{}) {
|
||||
return
|
||||
}
|
||||
slog.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
|
||||
c.Log.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ func MakeHandshakePacket(c *proto.Conn, nextState proto_base.State) (p *v1_21_8.
|
||||
return MakeHandshakePacketAddr(c, nextState, c.RemoteAddr().String())
|
||||
}
|
||||
|
||||
// MakeHandshakePacketAddr gets a custom addr
|
||||
func MakeHandshakePacketAddr(s *proto.Conn, nextState proto_base.State, addr string) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
|
||||
p = &v1_21_8.HandshakingToServerPacketSetProtocol{NextState: int32(nextState), ProtocolVersion: s.ProtocolVersion}
|
||||
var portStr string
|
||||
|
||||
+24
-14
@@ -6,7 +6,6 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/admin-else/strom/api"
|
||||
"github.com/admin-else/strom/crypto"
|
||||
@@ -42,11 +41,10 @@ func (k KickedDuringLoginErr) Error() string {
|
||||
}
|
||||
|
||||
func (s *LoginClient) OnDefault(event event.Unhandled) (err error) {
|
||||
err = fmt.Errorf("unexpected event: %#v", event)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *LoginClient) OnCycle(_ event.Tick) (err error) {
|
||||
if _, ok := event.Val.(proto_base.EncodeDecodeAble); !ok {
|
||||
return
|
||||
}
|
||||
s.Log.Warn("unexpected packet during login client", "packet", event.Val)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -127,18 +125,17 @@ func (s *LoginClient) OnStart() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func Login(c *proto.Conn, account *api.Account) (err error) {
|
||||
func LoginRaw(c *proto.Conn, account *api.Account, addr string) (err error) {
|
||||
lc := &LoginClient{
|
||||
Conn: c,
|
||||
Account: account,
|
||||
}
|
||||
lc.RegisterCritical(lc.OnDefault)
|
||||
lc.RegisterCritical(lc.OnCycle)
|
||||
lc.RegisterCritical(lc.OnClose)
|
||||
lc.RegisterUntilLatest(lc.OnCompress)
|
||||
lc.RegisterUntilLatest(lc.OnEncrypt)
|
||||
lc.RegisterUntilLatest(lc.OnDisconnect)
|
||||
lc.RegisterUntilLatest(lc.OnSuccess)
|
||||
lc.RegisterCriticalUntilLatest(lc.OnCompress)
|
||||
lc.RegisterCriticalUntilLatest(lc.OnEncrypt)
|
||||
lc.RegisterCriticalUntilLatest(lc.OnDisconnect)
|
||||
lc.RegisterCriticalUntilLatest(lc.OnSuccess)
|
||||
|
||||
p, err := MakeHandshakePacket(lc.Conn, proto_base.Login)
|
||||
if err != nil {
|
||||
@@ -159,11 +156,24 @@ func Login(c *proto.Conn, account *api.Account) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func ConnectAndLogin(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
||||
func LoginNoSrv(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
||||
c, err = Connect(connectTo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = Login(c, account)
|
||||
err = LoginRaw(c, account, connectTo)
|
||||
return
|
||||
}
|
||||
|
||||
func Login(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
||||
connectTo, err = DoDnsSimple(connectTo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c, err = Connect(connectTo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = LoginRaw(c, account, connectTo)
|
||||
return
|
||||
}
|
||||
|
||||
+16
-4
@@ -1,6 +1,7 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
@@ -37,8 +38,9 @@ func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
|
||||
}
|
||||
|
||||
// StatusRaw returns a StatusClient that is not connected to a server.
|
||||
// So ignore the resource leak warning. And maybe attach a warn ignore comment
|
||||
func StatusRaw(addr string) (s *StatusClient, err error) {
|
||||
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
|
||||
// It does not resolve SRV records.
|
||||
func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
|
||||
c, err := ConnectVersionLess(addr)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -69,11 +71,21 @@ func StatusRaw(addr string) (s *StatusClient, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func Status(addr string) (status server.StatusResponse, err error) {
|
||||
s, err := StatusRaw(addr)
|
||||
// StatusNoDns is like Status but does not resolve SRV records.
|
||||
func StatusNoDns(addr string) (status server.StatusResponse, err error) {
|
||||
s, err := StatusRaw(context.Background(), addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal([]byte(s.Status), &status)
|
||||
return
|
||||
}
|
||||
|
||||
// Status resolves the status of the minecraft server at the given address.
|
||||
func Status(addr string) (status server.StatusResponse, err error) {
|
||||
addr, err = DoDnsSimple(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return StatusNoDns(addr)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/admin-else/strom/api"
|
||||
)
|
||||
|
||||
var cmd = flag.NewFlagSet("api", flag.ContinueOnError)
|
||||
var tokenFlag = cmd.String("token", os.Getenv("MC_TOKEN"), "token to use")
|
||||
|
||||
var renameFlag = cmd.String("rename", "", "rename the player")
|
||||
|
||||
var uuidFlag = cmd.String("uuid", "", "get data about player with uuid")
|
||||
var nameFlag = cmd.String("name", "", "get data about player with name")
|
||||
var unsignedFlag = cmd.Bool("unsigned", true, "use unsigned mode for properties signatures")
|
||||
|
||||
var PleaseProvideArgumentErr = errors.New("please provide an argument")
|
||||
|
||||
func Run(args []string) (err error) {
|
||||
err = cmd.Parse(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a := &api.Account{}
|
||||
if *tokenFlag != "" {
|
||||
a, err = api.NewAccountFromYGG(*tokenFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
var data any
|
||||
if *renameFlag != "" {
|
||||
data, err = a.ChangeName(*renameFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if *uuidFlag != "" || *nameFlag != "" {
|
||||
if *nameFlag != "" {
|
||||
var idName api.IdName
|
||||
idName, err = api.NameToId(*nameFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
*uuidFlag = idName.Id
|
||||
}
|
||||
data, err = api.Profile(*uuidFlag, *unsignedFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = PleaseProvideArgumentErr
|
||||
return
|
||||
}
|
||||
err = json.NewEncoder(os.Stdout).Encode(data)
|
||||
return
|
||||
}
|
||||
@@ -8,18 +8,22 @@ import (
|
||||
"os"
|
||||
"slices"
|
||||
|
||||
"github.com/admin-else/strom/tools/cli/offline_uuid"
|
||||
"github.com/admin-else/strom/tools/cli/print_nbt"
|
||||
"github.com/admin-else/strom/tools/cli/status"
|
||||
"github.com/admin-else/strom/cmd/strom/api"
|
||||
"github.com/admin-else/strom/cmd/strom/offline_uuid"
|
||||
"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/status"
|
||||
)
|
||||
|
||||
var ExpectedASubcommandErr = errors.New("expected a subcommand")
|
||||
var UnknownSubcommandErr = errors.New("unknown subcommand")
|
||||
var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands")
|
||||
|
||||
var subcommands = map[string]func(args []string) error{
|
||||
"offline-uuid": offline_uuid.Run,
|
||||
"print-nbt": print_nbt.Run,
|
||||
"status": status.Run,
|
||||
"packet-spy": packet_inspector.Run,
|
||||
"api": api.Run,
|
||||
}
|
||||
|
||||
func Help(args []string) (err error) {
|
||||
@@ -34,8 +38,8 @@ func mainE() (err error) {
|
||||
args := os.Args
|
||||
|
||||
// Debug stuff
|
||||
//slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
//args = []string{"TEST", "status", "-srv", "-addr", "tcpshield.1f2d.net:25565"}
|
||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
//args = []string{"TEST", "api", "-name", "Admin_Else"}
|
||||
|
||||
if len(args) < 2 {
|
||||
err = ExpectedASubcommandErr
|
||||
@@ -0,0 +1,129 @@
|
||||
package packet_inspector
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log/slog"
|
||||
|
||||
"github.com/admin-else/strom/api"
|
||||
"github.com/admin-else/strom/client"
|
||||
"github.com/admin-else/strom/event"
|
||||
"github.com/admin-else/strom/nbt"
|
||||
"github.com/admin-else/strom/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
"github.com/admin-else/strom/proto_generated/v1_21_11"
|
||||
"github.com/admin-else/strom/server"
|
||||
"github.com/admin-else/strom/text"
|
||||
)
|
||||
|
||||
var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError)
|
||||
|
||||
var OfflineNameFlag = Cmd.String("name", "packet_inspector", "offline player name")
|
||||
var Token = Cmd.String("token", "", "token to use")
|
||||
|
||||
var TargetAddr = Cmd.String("addr", "127.0.0.1:25565", "address to connect to")
|
||||
var ListenAddr = Cmd.String("listen", "127.0.0.1:25566", "address to listen on")
|
||||
|
||||
type Proxy struct {
|
||||
Client, Servee *proto.Conn
|
||||
Data nbt.Tag
|
||||
}
|
||||
|
||||
func (p *Proxy) Start() (err error) {
|
||||
p.Client.RegisterCritical(p.OnAnything)
|
||||
p.Servee.RegisterCritical(p.OnAnything)
|
||||
|
||||
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
||||
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
errChan <- p.Client.StartConn()
|
||||
}()
|
||||
go func() {
|
||||
errChan <- p.Servee.StartConn()
|
||||
}()
|
||||
err = <-errChan
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
packetInfo, ok := proto.LookupPacketInfoByType(packet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
switch packetInfo.Direction {
|
||||
case proto_base.ToServer:
|
||||
err = p.Client.Send(packet)
|
||||
case proto_base.ToClient:
|
||||
err = p.Servee.Send(packet)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPacketFinishConfiguration) (err error) {
|
||||
err = p.Client.Send(packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Servee.SetState(proto_base.Play)
|
||||
p.Client.SetState(proto_base.Play)
|
||||
err = event.DontForwardErr
|
||||
return
|
||||
}
|
||||
|
||||
var StatusResponse = server.StatusResponse{
|
||||
Version: server.StatusResponseVersion{
|
||||
Name: text.Pretty("STROM"),
|
||||
Protocol: 772,
|
||||
},
|
||||
Players: server.StatusResponsePlayers{},
|
||||
Description: text.Pretty("Packet spy forwards to " + *TargetAddr),
|
||||
Favicon: "",
|
||||
EnforcesSecureChat: false,
|
||||
}
|
||||
|
||||
func Run(args []string) (err error) {
|
||||
err = Cmd.Parse(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
slog.Info("Starting packet inspector", "listen", *ListenAddr)
|
||||
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
||||
serveeConn.DebugPrintPackets = []string{""}
|
||||
p := &Proxy{Servee: serveeConn, Client: nil}
|
||||
var acc = api.NewOfflineAccount(*OfflineNameFlag)
|
||||
if *Token != "" {
|
||||
acc, err = api.NewAccountFromYGG(*Token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
*TargetAddr, err = client.DoDnsSimple(*TargetAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c, err := client.Connect(*TargetAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.DebugPrintPackets = []string{""}
|
||||
err = client.LoginRaw(c, acc, *TargetAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
p.Client = c
|
||||
err = p.Start()
|
||||
return
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func Run(args []string) (err error) {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
n, err := nbt.ReadFile(f)
|
||||
n, err := nbt.ReadUnstructuredFile(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/admin-else/strom/client"
|
||||
"github.com/admin-else/strom/event"
|
||||
"github.com/admin-else/strom/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
||||
)
|
||||
|
||||
var cmd = flag.NewFlagSet("status", flag.ContinueOnError)
|
||||
var addrFlag = cmd.String("addr", "localhost:25565", "address to connect to")
|
||||
var srvFlag = cmd.Bool("srv", false, "use SRV records to resolve the address")
|
||||
var versionFlag = cmd.String("version", "1.21.8", "version to use")
|
||||
|
||||
type StatusClient struct {
|
||||
*proto.Conn
|
||||
Status string
|
||||
PingSendTime, PingReceiveTime time.Time
|
||||
DoPingRoundTripTime bool
|
||||
}
|
||||
|
||||
func (s *StatusClient) OnStatus(p *v1_21_8.StatusToClientPacketServerInfo) (err error) {
|
||||
s.Status = p.Response
|
||||
s.PingSendTime = time.Now()
|
||||
if s.DoPingRoundTripTime {
|
||||
err = s.Send(&v1_21_8.StatusToServerPacketPing{Time: s.PingSendTime.UnixMilli()})
|
||||
} else {
|
||||
err = event.HandlerDoneErr{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
|
||||
s.PingReceiveTime = time.Now()
|
||||
err = event.HandlerDoneErr{}
|
||||
return
|
||||
}
|
||||
|
||||
// StatusRaw returns a StatusClient that is not connected to a server.
|
||||
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
|
||||
// It does not resolve SRV records.
|
||||
func StatusRaw(addr string) (s *StatusClient, err error) {
|
||||
c, err := client.ConnectVersionLess(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
err = c.SetVersion(*versionFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s = &StatusClient{
|
||||
Conn: c,
|
||||
}
|
||||
s.RegisterCriticalUntilLatest(s.OnStatus)
|
||||
s.RegisterCriticalUntilLatest(s.OnPong)
|
||||
|
||||
p, err := client.MakeHandshakePacketAddr(s.Conn, proto_base.Status, addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = s.Send(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.SetState(proto_base.Status)
|
||||
err = s.Send(&v1_21_8.StatusToServerPacketPingStart{})
|
||||
|
||||
err = s.StartConn()
|
||||
return
|
||||
}
|
||||
|
||||
func Run(args []string) (err error) {
|
||||
err = cmd.Parse(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if *srvFlag {
|
||||
*addrFlag, err = client.DoDnsSimple(*addrFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
slog.Debug("resolved address to ", "addr", *addrFlag)
|
||||
|
||||
status, err := StatusRaw(*addrFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(status.Status)
|
||||
return
|
||||
}
|
||||
+21
-6
@@ -1,5 +1,7 @@
|
||||
package data
|
||||
|
||||
import "errors"
|
||||
|
||||
// [
|
||||
//
|
||||
// {
|
||||
@@ -14,7 +16,7 @@ package data
|
||||
// },
|
||||
|
||||
type Biome struct {
|
||||
Id int
|
||||
Id int32
|
||||
Name string
|
||||
Category string
|
||||
Temperature float64
|
||||
@@ -24,22 +26,35 @@ type Biome struct {
|
||||
Color int
|
||||
}
|
||||
|
||||
var BiomesCache = make(map[string][]Biome)
|
||||
var BiomesCache = make(map[string][]*Biome)
|
||||
|
||||
func BiomesForVersion(v string) (ret []Biome) {
|
||||
func BiomesForVersion(v string) (ret []*Biome) {
|
||||
var ok bool
|
||||
if ret, ok = BiomesCache[v]; ok {
|
||||
return
|
||||
}
|
||||
var b []Biome
|
||||
var b []*Biome
|
||||
must(LoadVersionedJson(v, "biomes", &b))
|
||||
BiomesCache[v] = b
|
||||
return b
|
||||
}
|
||||
|
||||
func LookupBiomeById(version string, id int32) (biome Biome, ok bool) {
|
||||
func LookupBiomeById(version string, id int32) (biome *Biome, ok bool) {
|
||||
for _, b := range BiomesForVersion(version) {
|
||||
if b.Id == int(id) {
|
||||
if b.Id == id {
|
||||
biome = b
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var UnknownBiomeNameErr = errors.New("unknown biome Name")
|
||||
|
||||
func LookupBiomeByName(version string, name string) (biome *Biome, ok bool) {
|
||||
for _, b := range BiomesForVersion(version) {
|
||||
if b.Name == name {
|
||||
biome = b
|
||||
ok = true
|
||||
return
|
||||
|
||||
@@ -81,6 +81,8 @@ func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool)
|
||||
return
|
||||
}
|
||||
|
||||
var UnknownBlockNameErr = errors.New("unknown block Name")
|
||||
|
||||
func LookupBlockByName(version string, name string) (block *Block, ok bool) {
|
||||
for _, b := range BlocksForVersion(version) {
|
||||
if b.Name == name {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
//go:embed minecraft-data/data
|
||||
//go:embed minecraft-data/data/pc minecraft-data/data/dataPaths.json
|
||||
var MinecraftData embed.FS
|
||||
|
||||
func LoadJson(path string, data any) (err error) {
|
||||
|
||||
+3
-1
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/admin-else/strom/nbt"
|
||||
)
|
||||
|
||||
// TODO: this is stupid loginPacket.json exists next version migrate to that
|
||||
|
||||
//go:embed registry/*
|
||||
var Registry embed.FS
|
||||
|
||||
@@ -15,6 +17,6 @@ func LoadRegistry(name string) (n *nbt.Tag, err error) {
|
||||
return
|
||||
}
|
||||
defer r.Close()
|
||||
n, err = nbt.ReadFile(r)
|
||||
n, err = nbt.ReadUnstructuredFile(r)
|
||||
return
|
||||
}
|
||||
|
||||
Binary file not shown.
+101
-68
@@ -7,20 +7,14 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
LoopsPerSecond = 10000 // this is just so the cpu doesn't burn itself in an unlimited infinite loop
|
||||
TimePerLoop = time.Second / LoopsPerSecond
|
||||
)
|
||||
|
||||
type Loop struct {
|
||||
HandlerFunctions map[reflect.Type][]reflect.Value
|
||||
|
||||
Ctx context.Context
|
||||
ErrChan chan error
|
||||
cancel context.CancelFunc
|
||||
Ctx context.Context
|
||||
Cancel context.CancelFunc
|
||||
eventSources []reflect.SelectCase
|
||||
}
|
||||
|
||||
type Unhandled struct {
|
||||
@@ -31,10 +25,6 @@ type Anything struct {
|
||||
Val any
|
||||
}
|
||||
|
||||
type (
|
||||
Tick struct{}
|
||||
)
|
||||
|
||||
// Close is fired when the loop shuts down.
|
||||
type Close struct {
|
||||
Reason error
|
||||
@@ -45,6 +35,14 @@ type HandlerDoneErr struct {
|
||||
Return error
|
||||
}
|
||||
|
||||
// CallFunc is a special value that when fired with Loop.Fire it calls a function on the main loop
|
||||
// Its usefull for event event sources to call functions on the main loop
|
||||
// because event sources dont run on the main loop
|
||||
type CallFunc struct {
|
||||
F reflect.Value
|
||||
Args []reflect.Value
|
||||
}
|
||||
|
||||
func (e HandlerDoneErr) Error() string {
|
||||
return "HandlerDoneErr"
|
||||
}
|
||||
@@ -69,10 +67,8 @@ func (e MainHandlerErr) Unwrap() error {
|
||||
// DontForwardErr will stop the event from being forwarded to other handlers, including anything handlers.
|
||||
var DontForwardErr = errors.New("dont forward")
|
||||
var WhileClosingErr = errors.New("error while closing")
|
||||
|
||||
// CloseNonCriticalErr will close even when the event is non-critical
|
||||
var CloseNonCriticalErr = errors.New("close non-critical")
|
||||
var ContextDoneErr = errors.New("context done")
|
||||
var RecvNotOkErr = errors.New("recv not ok")
|
||||
|
||||
func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) {
|
||||
hv = reflect.ValueOf(h)
|
||||
@@ -89,40 +85,64 @@ func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) {
|
||||
return
|
||||
}
|
||||
|
||||
// RegisterEventSource registers an event source an event source is a channel this channel along with all other event sources will be reflect.Select 'ed until the loop closes
|
||||
// here is an example of an event source function
|
||||
//
|
||||
// func() (chSending <-chan any) {
|
||||
// ch := make(chan any)
|
||||
// go func() {
|
||||
// ctx := conn.Ctx
|
||||
// for {
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// return
|
||||
// case eventt := <-otherSource:
|
||||
// err = c.DoCommand(eventt)
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
// chSending = ch
|
||||
// return
|
||||
// }
|
||||
func (l *Loop) RegisterEventSource(ch <-chan any) {
|
||||
l.eventSources = append(l.eventSources, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)})
|
||||
}
|
||||
|
||||
// Register registers a handler that will not close the loop if it returns an error.
|
||||
// Instead, it will log it to slog.Error
|
||||
// Warning: If a bad handler is passed in the program will panic.
|
||||
func (l *Loop) Register(h any) {
|
||||
l.RegisterCustomType(ValidateHandler(h))
|
||||
eventType, hv := ValidateHandler(h)
|
||||
l.RegisterLow(eventType, hv, false)
|
||||
}
|
||||
|
||||
// RegisterCustomType registers a handler that will not close the loop if it returns an error.
|
||||
// RegisterLow registers a handler that will not close the loop if it returns an error.
|
||||
// Instead, it will log it to slog.Error
|
||||
// This does not hold true for signaling errors like HandlerDoneErr or DontForwardErr
|
||||
// Warning: If a bad handler is passed in the program will panic.
|
||||
func (l *Loop) RegisterCustomType(eventType reflect.Type, hv reflect.Value) {
|
||||
handleFunc := func(packet any) error {
|
||||
v := hv.Call([]reflect.Value{reflect.ValueOf(packet)})[0]
|
||||
if !v.IsNil() {
|
||||
err := v.Interface().(error)
|
||||
if errors.Is(err, CloseNonCriticalErr) {
|
||||
return err
|
||||
func (l *Loop) RegisterLow(eventType reflect.Type, hv reflect.Value, critical bool) {
|
||||
handleFunc := hv
|
||||
if !critical {
|
||||
handleFunc = reflect.ValueOf(func(packet any) error {
|
||||
v := hv.Call([]reflect.Value{reflect.ValueOf(packet)})[0]
|
||||
if !v.IsNil() {
|
||||
err := v.Interface().(error)
|
||||
if errors.As(err, &HandlerDoneErr{}) || errors.Is(err, DontForwardErr) {
|
||||
return err
|
||||
}
|
||||
slog.Error("handler failed", "err", err)
|
||||
}
|
||||
slog.Error("handler failed", "err", err)
|
||||
}
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
l.RegisterDirect(eventType, reflect.ValueOf(handleFunc))
|
||||
l.HandlerFunctions[eventType] = append(l.HandlerFunctions[eventType], handleFunc)
|
||||
}
|
||||
|
||||
// RegisterCritical registers a handler that will close the loop if it returns an error.
|
||||
// Warning: If a bad handler is passed in the program will panic.
|
||||
func (l *Loop) RegisterCritical(h any) {
|
||||
eventType, hv := ValidateHandler(h)
|
||||
l.RegisterDirect(eventType, hv)
|
||||
}
|
||||
|
||||
func (l *Loop) RegisterDirect(k reflect.Type, h reflect.Value) {
|
||||
l.HandlerFunctions[k] = append(l.HandlerFunctions[k], h)
|
||||
l.HandlerFunctions[eventType] = append(l.HandlerFunctions[eventType], hv)
|
||||
}
|
||||
|
||||
// FireFound triggers all handlers registered for the specified event type and returns if handlers were found or an error occurred.
|
||||
@@ -133,7 +153,7 @@ func (l *Loop) FireFound(event any) (found bool, err error) {
|
||||
errV := handler.Call([]reflect.Value{reflect.ValueOf(event)})[0]
|
||||
if !errV.IsNil() {
|
||||
err = errV.Interface().(error)
|
||||
err = errors.Join(fmt.Errorf("event handler failed at: %s(%T)", handler.Type().In(0), event), err)
|
||||
err = errors.Join(fmt.Errorf("event handler failed at: %T", event), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -143,6 +163,21 @@ func (l *Loop) FireFound(event any) (found bool, err error) {
|
||||
|
||||
// Fire dispatches the provided event to all appropriate handlers and invokes fallback handlers if the event is unhandled.
|
||||
func (l *Loop) Fire(event any) (err error) {
|
||||
err, isErr := event.(error)
|
||||
if isErr {
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
|
||||
f, isCallFunc := event.(CallFunc)
|
||||
if isCallFunc {
|
||||
errV := f.F.Call(f.Args)[0].Interface()
|
||||
if errV != nil {
|
||||
err = errV.(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
found, err := l.FireFound(event)
|
||||
if err != nil {
|
||||
if errors.Is(err, DontForwardErr) {
|
||||
@@ -151,64 +186,62 @@ func (l *Loop) Fire(event any) (err error) {
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
_, err = l.FireFound(Unhandled{event})
|
||||
found, err = l.FireFound(Unhandled{event})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = l.FireFound(Anything{event})
|
||||
found, err = l.FireFound(Anything{event})
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Loop) OnTick(_ Tick) (err error) {
|
||||
select {
|
||||
case <-l.Ctx.Done():
|
||||
err = ContextDoneErr
|
||||
default:
|
||||
}
|
||||
return
|
||||
func (l *Loop) ContextDoneListener() (chSending <-chan any) {
|
||||
ch := make(chan any)
|
||||
go func() {
|
||||
<-l.Ctx.Done()
|
||||
ch <- ContextDoneErr
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (l *Loop) startLoop() (err error) {
|
||||
func (l *Loop) IsTypeRegistered(t reflect.Type) bool {
|
||||
_, ok := l.HandlerFunctions[t]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (l *Loop) StartLoop() (err error) {
|
||||
_ = *l // exit early on nil loop
|
||||
l.RegisterEventSource(l.ContextDoneListener())
|
||||
for {
|
||||
tickStartTime := time.Now()
|
||||
err = l.Fire(Tick{})
|
||||
_, v, ok := reflect.Select(l.eventSources)
|
||||
if !ok {
|
||||
err = RecvNotOkErr
|
||||
break
|
||||
}
|
||||
err = l.Fire(v.Interface())
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(TimePerLoop - time.Since(tickStartTime))
|
||||
}
|
||||
if errors.Is(err, HandlerDoneErr{}) {
|
||||
err = errors.Unwrap(err)
|
||||
var contextDoneErr HandlerDoneErr
|
||||
if errors.As(err, &contextDoneErr) {
|
||||
err = contextDoneErr.Return
|
||||
}
|
||||
closeErr := l.Fire(Close{err})
|
||||
if closeErr != nil {
|
||||
err = errors.Join(err, errors.Join(WhileClosingErr, closeErr))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Loop) StartLoop() (err error) {
|
||||
go func() {
|
||||
l.ErrChan <- MainHandlerErr{l.startLoop()}
|
||||
}()
|
||||
err = <-l.ErrChan
|
||||
l.cancel()
|
||||
errMaybe := MainHandlerErr{}
|
||||
if errors.As(err, &errMaybe) {
|
||||
err = errors.Unwrap(err)
|
||||
} else {
|
||||
for !errors.Is(<-l.ErrChan, MainHandlerErr{}) {
|
||||
}
|
||||
}
|
||||
l.Cancel()
|
||||
return
|
||||
}
|
||||
|
||||
func NewLoop() (l *Loop) {
|
||||
return NewLoopCtx(context.Background())
|
||||
}
|
||||
|
||||
func NewLoopCtx(ctx context.Context) (l *Loop) {
|
||||
l = &Loop{}
|
||||
l.ErrChan = make(chan error)
|
||||
l.Ctx, l.cancel = context.WithCancel(context.Background())
|
||||
l.Ctx, l.Cancel = context.WithCancel(ctx)
|
||||
l.HandlerFunctions = make(map[reflect.Type][]reflect.Value)
|
||||
return
|
||||
}
|
||||
|
||||
+21
-32
@@ -2,7 +2,6 @@ package event
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -10,36 +9,26 @@ type Stdin struct {
|
||||
Val string
|
||||
}
|
||||
|
||||
type StdinHandler struct {
|
||||
*Loop
|
||||
stdInChan chan Stdin
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
func (s *StdinHandler) OnTick(_ Tick) (err error) {
|
||||
select {
|
||||
case stdin := <-s.stdInChan:
|
||||
err = s.Fire(Stdin{stdin.Val})
|
||||
default:
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *StdinHandler) ScanJob() {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
select {
|
||||
case <-s.Ctx.Done():
|
||||
return
|
||||
default:
|
||||
s.stdInChan <- Stdin{scanner.Text()}
|
||||
}
|
||||
}
|
||||
}
|
||||
func (s *StdinHandler) OnStart() (err error) {
|
||||
s.RegisterCritical(s.OnTick)
|
||||
s.stdInChan = make(chan Stdin)
|
||||
go s.ScanJob()
|
||||
func StartListingStdin(l *Loop) {
|
||||
l.RegisterEventSource(func() (chSending <-chan any) {
|
||||
ch := make(chan any)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
ctx := l.Ctx
|
||||
for scanner.Scan() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
ch <- Stdin{scanner.Text()}
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
ch <- err
|
||||
}
|
||||
}()
|
||||
chSending = ch
|
||||
return
|
||||
}())
|
||||
return
|
||||
}
|
||||
|
||||
+61
-24
@@ -1,6 +1,8 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
@@ -19,39 +21,54 @@ type OnceTask struct {
|
||||
type Timer struct {
|
||||
Once []OnceTask
|
||||
Interval []IntervalTask
|
||||
Active bool
|
||||
}
|
||||
|
||||
func (t *Timer) Tick() (err error) {
|
||||
if !t.Active {
|
||||
func (t *Timer) Trigger(ch chan any) {
|
||||
triggerAt := time.Time{}
|
||||
found := false
|
||||
iOnce, iInterval := -1, -1
|
||||
|
||||
for i, oTask := range t.Once {
|
||||
if oTask.Trigger.Before(triggerAt) || triggerAt.IsZero() {
|
||||
found = true
|
||||
triggerAt = oTask.Trigger
|
||||
iOnce = i
|
||||
}
|
||||
}
|
||||
|
||||
for i, iTask := range t.Interval {
|
||||
if iTask.LastTrigger.Add(iTask.Interval).Before(triggerAt) || triggerAt.IsZero() {
|
||||
found = true
|
||||
triggerAt = iTask.LastTrigger.Add(iTask.Interval)
|
||||
iOnce = -1
|
||||
iInterval = i
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
for i, task := range t.Interval {
|
||||
if time.Since(task.LastTrigger) >= task.Interval {
|
||||
task.LastTrigger = time.Now()
|
||||
err = task.F()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Interval[i] = task
|
||||
now := time.Now()
|
||||
sleepFor := triggerAt.Sub(now)
|
||||
//slog.Debug("sleep next", "dur", sleepFor)
|
||||
time.Sleep(sleepFor)
|
||||
if iOnce >= 0 {
|
||||
ch <- CallFunc{F: reflect.ValueOf(t.Once[iOnce].F), Args: []reflect.Value{}}
|
||||
t.Once = slices.Delete(t.Once, iOnce, iOnce+1)
|
||||
return
|
||||
}
|
||||
for i, task := range t.Once {
|
||||
if task.Trigger.After(time.Now()) {
|
||||
err = task.F()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.Once = slices.Delete(t.Once, i, i)
|
||||
}
|
||||
if iInterval >= 0 {
|
||||
t.Interval[iInterval].LastTrigger = triggerAt
|
||||
ch <- CallFunc{F: reflect.ValueOf(t.Interval[iInterval].F), Args: []reflect.Value{}}
|
||||
return
|
||||
}
|
||||
return
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (t *Timer) Every(interval time.Duration, f func() error) {
|
||||
t.Interval = append(t.Interval, IntervalTask{
|
||||
F: f,
|
||||
Interval: interval,
|
||||
F: f,
|
||||
Interval: interval,
|
||||
LastTrigger: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,3 +78,23 @@ func (t *Timer) In(interval time.Duration, f func() error) {
|
||||
Trigger: time.Now().Add(interval),
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Timer) MakeEventSource(ctx context.Context) (chSending <-chan any) {
|
||||
ch := make(chan any)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
t.Trigger(ch)
|
||||
}
|
||||
}
|
||||
}()
|
||||
chSending = ch
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Timer) Start(c *Loop) {
|
||||
c.RegisterEventSource(t.MakeEventSource(c.Ctx))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package anvil
|
||||
|
||||
type Chunk struct {
|
||||
DataVersion int32
|
||||
Heightmaps map[string][]int64
|
||||
InhabitedTime int64
|
||||
XPos, YPos, ZPos int32
|
||||
Sections []Section
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
Y int8
|
||||
BlockStates struct {
|
||||
Data []int64
|
||||
Palette []struct {
|
||||
Name string
|
||||
Properties map[string]string `nbt:"properties,omitempty"`
|
||||
}
|
||||
}
|
||||
Biomes []struct {
|
||||
Data []int64
|
||||
Palette []string
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package anvil
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/admin-else/strom/nbt"
|
||||
)
|
||||
|
||||
type Level struct {
|
||||
ClearWeatherTime int32 `nbt:"clearWeatherTime"`
|
||||
DataPacks DataPacks
|
||||
DayTime int64
|
||||
WanderingTraderSpawnChance int32
|
||||
GameType int32
|
||||
WorldGenSettings WorldGenSettings
|
||||
ScheduledEvents []interface{}
|
||||
WasModded bool
|
||||
ThunderTime int32 `nbt:"thunderTime"`
|
||||
Difficulty int8
|
||||
DragonFight DragonFight
|
||||
Initialized bool
|
||||
AllowCommands bool `nbt:"allowCommands"`
|
||||
Thundering bool
|
||||
LastPlayed int64
|
||||
GameRules map[string]interface{}
|
||||
RainTime int32 `nbt:"rainTime"`
|
||||
DataVersion int32
|
||||
Version Version
|
||||
WanderingTraderSpawnDelay int32
|
||||
Hardcore bool
|
||||
Spawn Spawn
|
||||
Raining bool
|
||||
Player Player
|
||||
DifficultyLocked bool
|
||||
LevelName string
|
||||
Time int64
|
||||
ServerBrands []string
|
||||
CustomBossEvents map[string]interface{}
|
||||
}
|
||||
|
||||
type DataPacks struct {
|
||||
Enabled []string
|
||||
Disabled []string
|
||||
}
|
||||
|
||||
type WorldGenSettings struct {
|
||||
BonusChest bool
|
||||
Seed int64
|
||||
GenerateFeatures bool
|
||||
Dimensions map[string]Dimension
|
||||
}
|
||||
|
||||
type Dimensions struct {
|
||||
Overworld Dimension
|
||||
Nether Dimension
|
||||
End Dimension
|
||||
}
|
||||
|
||||
type Dimension struct {
|
||||
Generator Generator
|
||||
Type string
|
||||
}
|
||||
|
||||
type Generator struct {
|
||||
Type string
|
||||
Settings string
|
||||
BiomeSource BiomeSource
|
||||
}
|
||||
|
||||
type BiomeSource struct {
|
||||
Preset string `nbt:"preset,omitempty"`
|
||||
Type string
|
||||
}
|
||||
|
||||
type DragonFight struct {
|
||||
NeedsStateScanning bool
|
||||
Gateways []int32
|
||||
DragonKilled bool
|
||||
PreviouslyKilled bool
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
Snapshot bool
|
||||
Series string
|
||||
Id int32
|
||||
Name string
|
||||
}
|
||||
|
||||
type Spawn struct {
|
||||
Dimension string
|
||||
Yaw float32
|
||||
Pos [3]int32
|
||||
Pitch float32
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
XpLevel int32
|
||||
HurtTime int16
|
||||
DeathTime int16
|
||||
XpTotal int32
|
||||
FoodExhaustionLevel float32
|
||||
RecipeBook RecipeBook
|
||||
Fire int16
|
||||
Brain Brain
|
||||
XpSeed int32
|
||||
XpP float32
|
||||
EnderItems []interface{}
|
||||
Invulnerable bool
|
||||
Pos [3]float64
|
||||
Inventory []InventoryItem
|
||||
SleepTimer int16
|
||||
Abilities Abilities
|
||||
PlayerGameType int32
|
||||
SeenCredits bool
|
||||
FoodSaturationLevel float32
|
||||
FallDistance float64
|
||||
OnGround bool
|
||||
Dimension string
|
||||
PortalCooldown int32
|
||||
Motion [3]float64
|
||||
IgnoreFallDamageFromCurrentExplosion bool
|
||||
Rotation [2]float32
|
||||
CurrentImpulseContextResetGraceTime int32
|
||||
WardenSpawnTracker WardenSpawnTracker
|
||||
PreviousPlayerGameType int32
|
||||
Attributes []Attribute
|
||||
HurtByTimestamp int32
|
||||
UUID [4]int32
|
||||
Score int32
|
||||
DataVersion int32
|
||||
FoodLevel int32
|
||||
SpawnExtraParticlesOnFall bool
|
||||
SelectedItemSlot int32
|
||||
FoodTickTimer int32
|
||||
FallFlying bool
|
||||
AbsorptionAmount float32
|
||||
Health float32
|
||||
Air int16
|
||||
}
|
||||
|
||||
type RecipeBook struct {
|
||||
Recipes []string
|
||||
ToBeDisplayed []string
|
||||
}
|
||||
|
||||
type Brain struct {
|
||||
Memories map[string]interface{}
|
||||
}
|
||||
|
||||
type InventoryItem struct {
|
||||
Count int32
|
||||
Slot int8
|
||||
Id string
|
||||
}
|
||||
|
||||
type Abilities struct {
|
||||
Invulnerable bool
|
||||
Mayfly bool
|
||||
Instabuild bool
|
||||
WalkSpeed float32
|
||||
MayBuild bool
|
||||
Flying bool
|
||||
FlySpeed float32
|
||||
}
|
||||
|
||||
type WardenSpawnTracker struct {
|
||||
WarningLevel int32
|
||||
TicksSinceLastWarning int32
|
||||
CooldownTicks int32
|
||||
}
|
||||
|
||||
type Attribute struct {
|
||||
Id string
|
||||
Base float64
|
||||
}
|
||||
|
||||
func ReadLevelData(r io.Reader) (l Level, err error) {
|
||||
var lDataWrapped struct{ Data Level }
|
||||
err = nbt.ReadFile(r, &lDataWrapped)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
l = lDataWrapped.Data
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package anvil_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/admin-else/strom/level/anvil"
|
||||
"github.com/admin-else/strom/util"
|
||||
)
|
||||
|
||||
func TestLevel(t *testing.T) {
|
||||
f, err := os.Open("./testdata/testworld/level.dat")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
l, err := anvil.ReadLevelData(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(l)
|
||||
}
|
||||
|
||||
func TestRegionFile(t *testing.T) {
|
||||
f, err := os.Open("./testdata/testworld/region/r.0.0.mca")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
ch, err := anvil.ReadChunkHeader(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := ch.ChunkAt(f, 0, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(string(util.MustT(json.MarshalIndent(n, "", " "))))
|
||||
fmt.Println("Chunk loaded successfully")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package anvil
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/admin-else/strom/nbt"
|
||||
)
|
||||
|
||||
type CompressionStrategy uint8
|
||||
|
||||
const (
|
||||
ChunksWidthRegionFile = 32
|
||||
ChunksRegionFile = ChunksWidthRegionFile * ChunksWidthRegionFile
|
||||
)
|
||||
|
||||
const (
|
||||
_ CompressionStrategy = iota
|
||||
CompressionStrategyGZIP
|
||||
CompressionStrategyZLIB
|
||||
CompressionStrategyNONE
|
||||
CompressionStrategyLZ4
|
||||
)
|
||||
|
||||
func (c CompressionStrategy) Wrap(r io.ReadCloser) (rNew io.ReadCloser, shouldClose bool, err error) {
|
||||
switch c {
|
||||
case CompressionStrategyGZIP:
|
||||
shouldClose = true
|
||||
rNew, err = gzip.NewReader(r)
|
||||
return
|
||||
case CompressionStrategyZLIB:
|
||||
shouldClose = true
|
||||
rNew, err = zlib.NewReader(r)
|
||||
return
|
||||
case CompressionStrategyNONE:
|
||||
rNew = r
|
||||
return
|
||||
case CompressionStrategyLZ4:
|
||||
err = fmt.Errorf("lz4 compression not supported")
|
||||
return
|
||||
default:
|
||||
err = fmt.Errorf("unknown compression strategy %d", c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type ChunkHeader struct {
|
||||
Chunk, Time [ChunksRegionFile]int32
|
||||
}
|
||||
|
||||
func ReadChunkHeader(r *os.File) (ch *ChunkHeader, err error) {
|
||||
var h ChunkHeader
|
||||
err = binary.Read(r, binary.BigEndian, &h)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ch = &h
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ChunkHeader) ChunkAt(f *os.File, x, z int32) (n *nbt.Tag, err error) {
|
||||
off, size := chunkLocation(c.Chunk[chunkXZtoIndex(x, z)])
|
||||
_, err = f.Seek(int64(off), 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = size // i dont really know why mc has this maybe to point out how much space is wasted?
|
||||
var realSize int32
|
||||
err = binary.Read(f, binary.BigEndian, &realSize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var compressionStrategy CompressionStrategy
|
||||
err = binary.Read(f, binary.BigEndian, &compressionStrategy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r, shouldClose, err := compressionStrategy.Wrap(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if shouldClose {
|
||||
defer r.Close()
|
||||
}
|
||||
n = &nbt.Tag{}
|
||||
err = n.Decode(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func chunkXZtoIndex(x, z int32) int32 {
|
||||
return (x % ChunksWidthRegionFile) + (z%ChunksWidthRegionFile)*ChunksWidthRegionFile
|
||||
}
|
||||
|
||||
func chunkLocation(v int32) (offset, l int32) {
|
||||
return ((v >> 8) & 0xFFFFFF) * 4096, (v & 0xFF) * 4096
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+215
@@ -0,0 +1,215 @@
|
||||
{
|
||||
"minecraft:recipes/building_blocks/polished_deepslate": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/combat/stone_sword": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_axe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:story/mine_stone": {
|
||||
"criteria": {
|
||||
"get_stone": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/furnace": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/crafting_table": {
|
||||
"criteria": {
|
||||
"unlock_right_away": "2026-01-18 19:40:40 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-01-18 19:40:40 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-01-18 19:40:40 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_pickaxe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/misc/charcoal": {
|
||||
"criteria": {
|
||||
"has_log": "2026-01-18 19:41:43 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/oak_planks": {
|
||||
"criteria": {
|
||||
"has_logs": "2026-01-18 19:41:43 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-01-18 19:40:40 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:adventure/adventuring_time": {
|
||||
"criteria": {
|
||||
"minecraft:forest": "2026-01-18 19:40:40 +0100",
|
||||
"minecraft:deep_dark": "2026-01-18 19:41:56 +0100",
|
||||
"minecraft:stony_shore": "2026-01-18 19:40:46 +0100"
|
||||
},
|
||||
"done": false
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_hoe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_shovel": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"DataVersion": 4556
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
{
|
||||
"minecraft:recipes/building_blocks/polished_deepslate": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/combat/stone_sword": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_axe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:story/mine_stone": {
|
||||
"criteria": {
|
||||
"get_stone": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/furnace": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/crafting_table": {
|
||||
"criteria": {
|
||||
"unlock_right_away": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_pickaxe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/misc/charcoal": {
|
||||
"criteria": {
|
||||
"has_log": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/combat/stone_spear": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/oak_planks": {
|
||||
"criteria": {
|
||||
"has_logs": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:adventure/adventuring_time": {
|
||||
"criteria": {
|
||||
"minecraft:stony_shore": "2026-03-11 19:22:59 +0100",
|
||||
"minecraft:plains": "2026-03-11 19:22:38 +0100"
|
||||
},
|
||||
"done": false
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_hoe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_shovel": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"DataVersion": 4671
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
{
|
||||
"minecraft:recipes/building_blocks/polished_deepslate": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/combat/stone_sword": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_axe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:story/mine_stone": {
|
||||
"criteria": {
|
||||
"get_stone": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/furnace": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/crafting_table": {
|
||||
"criteria": {
|
||||
"unlock_right_away": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_pickaxe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/misc/charcoal": {
|
||||
"criteria": {
|
||||
"has_log": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/combat/stone_spear": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/oak_planks": {
|
||||
"criteria": {
|
||||
"has_logs": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:adventure/adventuring_time": {
|
||||
"criteria": {
|
||||
"minecraft:forest": "2026-02-07 19:19:04 +0100",
|
||||
"minecraft:river": "2026-02-07 19:20:26 +0100",
|
||||
"minecraft:stony_shore": "2026-02-07 19:20:11 +0100",
|
||||
"minecraft:plains": "2026-02-07 19:20:12 +0100"
|
||||
},
|
||||
"done": false
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_hoe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_shovel": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"DataVersion": 4671
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"minecraft:recipes/decorations/crafting_table": {
|
||||
"criteria": {
|
||||
"unlock_right_away": "2025-11-30 09:32:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/transportation/cherry_boat": {
|
||||
"criteria": {
|
||||
"in_water": "2025-11-30 10:05:13 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:adventure/kill_all_mobs": {
|
||||
"criteria": {
|
||||
"minecraft:zombie": "2025-11-30 10:06:33 +0100"
|
||||
},
|
||||
"done": false
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2025-11-30 10:06:57 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/transportation/bamboo_raft": {
|
||||
"criteria": {
|
||||
"in_water": "2025-11-30 10:05:13 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/transportation/spruce_boat": {
|
||||
"criteria": {
|
||||
"in_water": "2025-11-30 10:05:13 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/transportation/birch_boat": {
|
||||
"criteria": {
|
||||
"in_water": "2025-11-30 10:05:13 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/transportation/oak_boat": {
|
||||
"criteria": {
|
||||
"in_water": "2025-11-30 10:05:13 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/transportation/mangrove_boat": {
|
||||
"criteria": {
|
||||
"in_water": "2025-11-30 10:05:13 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2025-11-30 10:06:57 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/transportation/acacia_boat": {
|
||||
"criteria": {
|
||||
"in_water": "2025-11-30 10:05:13 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:adventure/root": {
|
||||
"criteria": {
|
||||
"killed_something": "2025-11-30 10:06:33 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2025-11-30 10:06:57 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:adventure/kill_a_mob": {
|
||||
"criteria": {
|
||||
"minecraft:zombie": "2025-11-30 10:06:33 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/transportation/pale_oak_boat": {
|
||||
"criteria": {
|
||||
"in_water": "2025-11-30 10:05:13 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:adventure/adventuring_time": {
|
||||
"criteria": {
|
||||
"minecraft:old_growth_birch_forest": "2025-11-30 10:03:58 +0100",
|
||||
"minecraft:forest": "2025-11-30 10:04:40 +0100",
|
||||
"minecraft:taiga": "2025-11-30 09:32:29 +0100",
|
||||
"minecraft:deep_dark": "2025-11-30 10:05:12 +0100",
|
||||
"minecraft:river": "2025-11-30 10:09:03 +0100",
|
||||
"minecraft:grove": "2025-11-30 09:32:32 +0100",
|
||||
"minecraft:meadow": "2025-11-30 09:32:44 +0100",
|
||||
"minecraft:birch_forest": "2025-11-30 10:05:33 +0100",
|
||||
"minecraft:frozen_peaks": "2025-11-30 09:32:55 +0100"
|
||||
},
|
||||
"done": false
|
||||
},
|
||||
"minecraft:recipes/transportation/jungle_boat": {
|
||||
"criteria": {
|
||||
"in_water": "2025-11-30 10:05:13 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/transportation/dark_oak_boat": {
|
||||
"criteria": {
|
||||
"in_water": "2025-11-30 10:05:13 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"DataVersion": 4556
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
{
|
||||
"minecraft:recipes/building_blocks/polished_deepslate": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/combat/stone_sword": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_axe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:story/mine_stone": {
|
||||
"criteria": {
|
||||
"get_stone": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/furnace": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/crafting_table": {
|
||||
"criteria": {
|
||||
"unlock_right_away": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_pickaxe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/misc/charcoal": {
|
||||
"criteria": {
|
||||
"has_log": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/combat/stone_spear": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/oak_planks": {
|
||||
"criteria": {
|
||||
"has_logs": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
|
||||
"criteria": {
|
||||
"has_porkchop": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:adventure/adventuring_time": {
|
||||
"criteria": {
|
||||
"minecraft:stony_shore": "2026-03-12 21:17:48 +0100",
|
||||
"minecraft:plains": "2026-03-12 21:17:31 +0100"
|
||||
},
|
||||
"done": false
|
||||
},
|
||||
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
|
||||
"criteria": {
|
||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_hoe": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"minecraft:recipes/tools/stone_shovel": {
|
||||
"criteria": {
|
||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
||||
},
|
||||
"done": true
|
||||
},
|
||||
"DataVersion": 4671
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
https://github.com/cabaletta/baritone
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user