Files
legacy-version/legacyver/block_translator.go
2025-08-13 21:37:46 +07:00

345 lines
11 KiB
Go

package legacyver
import (
"bytes"
_ "embed"
"github.com/akmalfairuz/legacy-version/internal"
"github.com/akmalfairuz/legacy-version/internal/chunk"
"github.com/akmalfairuz/legacy-version/mapping"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/nbt"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
BlockVersionLatest int32 = (1 << 24) | (21 << 16) | (50 << 8) | 29
)
var (
// LatestNetworkPersistentEncoding is the Encoding used for sending a Chunk over network. It uses NBT, unlike NetworkEncoding.
LatestNetworkPersistentEncoding = chunk.NewNetworkPersistentEncoding(blockMappingLatest, BlockVersionLatest)
// LatestBlockPaletteEncoding is the paletteEncoding used for encoding a palette of block states encoded as NBT.
LatestBlockPaletteEncoding = chunk.NewBlockPaletteEncoding(blockMappingLatest, BlockVersionLatest)
)
type BlockTranslator interface {
// DowngradeBlockPackets downgrades the input block packets to legacy block packets.
DowngradeBlockPackets([]packet.Packet, *minecraft.Conn) (result []packet.Packet)
// UpgradeBlockPackets upgrades the input block packets to the latest block packets.
UpgradeBlockPackets([]packet.Packet, *minecraft.Conn) (result []packet.Packet)
}
type DefaultBlockTranslator struct {
mapping mapping.Block
latest mapping.Block
pse chunk.Encoding
pe chunk.PaletteEncoding
oldFormat bool
}
func NewBlockTranslator(mapping mapping.Block, latestMapping mapping.Block, pse chunk.Encoding, pe chunk.PaletteEncoding, oldFormat bool) *DefaultBlockTranslator {
return &DefaultBlockTranslator{mapping: mapping, latest: latestMapping, pse: pse, pe: pe, oldFormat: oldFormat}
}
func (t *DefaultBlockTranslator) downgradeLevelChunkPacket(pk *packet.LevelChunk) error {
count := int(pk.SubChunkCount)
if count == protocol.SubChunkRequestModeLimitless || count == protocol.SubChunkRequestModeLimited {
return nil
}
buf := bytes.NewBuffer(pk.RawPayload)
writeBuf := internal.BufferPool.Get().(*bytes.Buffer)
defer func() {
writeBuf.Reset()
internal.BufferPool.Put(writeBuf)
}()
if !pk.CacheEnabled {
c, err := chunk.NetworkDecode(t.latest.Air(), buf, count, false, world.Overworld.Range(), LatestNetworkPersistentEncoding, LatestBlockPaletteEncoding)
if err != nil {
return err
}
c = t.DowngradeChunk(c)
payload, err := chunk.NetworkEncode(t.mapping.Air(), c, t.oldFormat, t.pe)
if err != nil {
return err
}
writeBuf.Write(payload)
pk.SubChunkCount = uint32(len(c.Sub()))
}
safeBytes := buf.Bytes()
countBorder, err := buf.ReadByte()
if err != nil {
pk.RawPayload = append(writeBuf.Bytes(), safeBytes...)
return nil
}
borderBytes := make([]byte, countBorder)
if _, err = buf.Read(borderBytes); err != nil {
pk.RawPayload = append(writeBuf.Bytes(), safeBytes...)
return nil
}
writeBuf.WriteByte(countBorder)
writeBuf.Write(borderBytes)
enc := nbt.NewEncoderWithEncoding(writeBuf, nbt.NetworkLittleEndian)
dec := nbt.NewDecoderWithEncoding(buf, nbt.NetworkLittleEndian)
for {
var decNbt map[string]any
if err = dec.Decode(&decNbt); err != nil {
break
}
t.mapping.DowngradeBlockActorData(decNbt)
if err = enc.Encode(decNbt); err != nil {
break
}
}
pk.RawPayload = append(writeBuf.Bytes(), buf.Bytes()...)
return nil
}
func (t *DefaultBlockTranslator) DowngradeBlockPackets(pks []packet.Packet, conn *minecraft.Conn) (result []packet.Packet) {
for _, pk := range pks {
switch pk := pk.(type) {
case *packet.LevelChunk:
_ = t.downgradeLevelChunkPacket(pk)
case *packet.SubChunk:
r := world.Overworld.Range()
if t.oldFormat {
r = cube.Range{0, 255}
}
for i, entry := range pk.SubChunkEntries {
if entry.Result == protocol.SubChunkResultSuccess {
buf := bytes.NewBuffer(entry.RawPayload)
writeBuf := bytes.NewBuffer(nil)
if !pk.CacheEnabled && !conn.ClientCacheEnabled() {
ind := byte(i)
subChunk, err := chunk.DecodeSubChunk(t.latest.Air(), r, buf, &ind, chunk.NetworkEncoding, LatestNetworkPersistentEncoding, LatestBlockPaletteEncoding)
if err != nil {
//fmt.Println(err)
continue
}
t.DowngradeSubChunk(subChunk)
writeBuf.Write(chunk.EncodeSubChunk(subChunk, chunk.NetworkEncoding, t.pe, chunk.SubChunkVersion9, r, int(ind)))
}
enc := nbt.NewEncoderWithEncoding(writeBuf, nbt.NetworkLittleEndian)
dec := nbt.NewDecoderWithEncoding(buf, nbt.NetworkLittleEndian)
for {
var decNbt map[string]any
if err := dec.Decode(&decNbt); err != nil {
break
}
t.mapping.DowngradeBlockActorData(decNbt)
if err := enc.Encode(decNbt); err != nil {
break
}
}
entry.RawPayload = append(writeBuf.Bytes(), buf.Bytes()...)
pk.SubChunkEntries[i] = entry
}
}
case *packet.ClientCacheMissResponse:
r := world.Overworld.Range()
if t.oldFormat {
r = cube.Range{0, 255}
}
for i, blob := range pk.Blobs {
buf := bytes.NewBuffer(blob.Payload)
ind := byte(0)
subChunk, err := chunk.DecodeSubChunk(t.latest.Air(), r, buf, &ind, chunk.NetworkEncoding, LatestNetworkPersistentEncoding, LatestBlockPaletteEncoding)
if err != nil {
// Has a possibility to be a biome, ignore then
continue
}
t.DowngradeSubChunk(subChunk)
blob.Payload = append(chunk.EncodeSubChunk(subChunk, chunk.NetworkEncoding, t.pe, chunk.SubChunkVersion9, r, int(ind)), buf.Bytes()...)
pk.Blobs[i] = blob
}
case *packet.UpdateSubChunkBlocks:
for i, block := range pk.Blocks {
block.BlockRuntimeID = t.DowngradeBlockRuntimeID(block.BlockRuntimeID)
pk.Blocks[i] = block
}
for i, block := range pk.Extra {
block.BlockRuntimeID = t.DowngradeBlockRuntimeID(block.BlockRuntimeID)
pk.Extra[i] = block
}
case *packet.UpdateBlock:
pk.NewBlockRuntimeID = t.DowngradeBlockRuntimeID(pk.NewBlockRuntimeID)
case *packet.UpdateBlockSynced:
pk.NewBlockRuntimeID = t.DowngradeBlockRuntimeID(pk.NewBlockRuntimeID)
case *packet.InventoryTransaction:
if transactionData, ok := pk.TransactionData.(*protocol.UseItemTransactionData); ok {
transactionData.BlockRuntimeID = t.DowngradeBlockRuntimeID(transactionData.BlockRuntimeID)
pk.TransactionData = transactionData
}
case *packet.LevelEvent:
switch pk.EventType {
case packet.LevelEventParticleLegacyEvent | 20: // terrain
fallthrough
case packet.LevelEventParticlesDestroyBlock:
fallthrough
case packet.LevelEventParticlesDestroyBlockNoSound:
pk.EventData = int32(t.DowngradeBlockRuntimeID(uint32(pk.EventData)))
case packet.LevelEventParticlesCrackBlock:
face := pk.EventData >> 24
rid := t.DowngradeBlockRuntimeID(uint32(pk.EventData & 0xffff))
pk.EventData = int32(rid) | (face << 24)
}
case *packet.LevelSoundEvent:
switch pk.SoundType {
case packet.SoundEventBreak:
fallthrough
case packet.SoundEventPlace:
fallthrough
case packet.SoundEventHit:
fallthrough
case packet.SoundEventLand:
fallthrough
case packet.SoundEventItemUseOn:
pk.ExtraData = int32(t.DowngradeBlockRuntimeID(uint32(pk.ExtraData)))
}
case *packet.AddActor:
if pk.EntityType == "minecraft:falling_block" {
pk.EntityMetadata = t.downgradeEntityMetadata(pk.EntityMetadata)
}
case *packet.SetActorData:
//pk.EntityMetadata = t.downgradeEntityMetadata(pk.EntityMetadata)
case *packet.StartGame:
t.latest.Adjust(pk.Blocks)
t.mapping.Adjust(pk.Blocks)
case *packet.ResourcePackStack:
var packs []protocol.StackResourcePack
for _, pack := range pk.TexturePacks {
if pack.UUID == "0fba4063-dba1-4281-9b89-ff9390653530" {
continue
}
packs = append(packs, pack)
}
pk.TexturePacks = packs
}
result = append(result, pk)
}
return result
}
func (t *DefaultBlockTranslator) UpgradeBlockPackets(pks []packet.Packet, conn *minecraft.Conn) (result []packet.Packet) {
for _, pk := range pks {
switch pk := pk.(type) {
case *packet.InventoryTransaction:
if transactionData, ok := pk.TransactionData.(*protocol.UseItemTransactionData); ok {
transactionData.BlockRuntimeID = t.UpgradeBlockRuntimeID(transactionData.BlockRuntimeID)
pk.TransactionData = transactionData
}
case *packet.SetActorData:
//pk.EntityMetadata = t.upgradeEntityMetadata(pk.EntityMetadata)
}
result = append(result, pk)
}
return result
}
func (t *DefaultBlockTranslator) DowngradeBlockRuntimeID(input uint32) uint32 {
if t.latest == t.mapping {
return input
}
state, ok := t.latest.RuntimeIDToState(input)
if !ok {
return t.mapping.InfoUpdate()
}
runtimeID, ok := t.mapping.StateToRuntimeID(state)
if !ok {
return t.mapping.InfoUpdate()
}
return runtimeID
}
func (t *DefaultBlockTranslator) DowngradeChunk(input *chunk.Chunk) *chunk.Chunk {
if t.latest == t.mapping {
return input
}
start := 0
r := world.Overworld.Range()
if t.oldFormat {
start = 4
r = cube.Range{0, 255}
}
downgraded := chunk.New(t.mapping.Air(), r)
i := 0
// First downgrade the blocks.
for _, sub := range input.Sub()[start : len(input.Sub())-start] {
t.DowngradeSubChunk(sub)
downgraded.Sub()[i] = sub
i += 1
}
i = 0
// Then downgrade the biome ids.
for _, sub := range input.BiomeSub()[start : len(input.BiomeSub())-start] {
// todo
sub.Palette().Replace(func(v uint32) uint32 {
return 0 // at least the client doesn't crash now
})
downgraded.BiomeSub()[i] = sub
i += 1
}
return downgraded
}
func (t *DefaultBlockTranslator) DowngradeSubChunk(input *chunk.SubChunk) {
if t.latest == t.mapping {
return
}
for _, storage := range input.Layers() {
storage.Palette().Replace(t.DowngradeBlockRuntimeID)
}
}
func (t *DefaultBlockTranslator) downgradeEntityMetadata(metadata map[uint32]any) map[uint32]any {
if t.latest == t.mapping {
return metadata
}
if latestRID, ok := metadata[protocol.EntityDataKeyVariant]; ok {
metadata[protocol.EntityDataKeyVariant] = int32(t.DowngradeBlockRuntimeID(uint32(latestRID.(int32))))
}
return metadata
}
func (t *DefaultBlockTranslator) UpgradeBlockRuntimeID(input uint32) uint32 {
if t.latest == t.mapping {
return input
}
state, ok := t.mapping.RuntimeIDToState(input)
if !ok {
return t.latest.InfoUpdate()
}
runtimeID, ok := t.latest.StateToRuntimeID(state)
if !ok {
return t.latest.InfoUpdate()
}
return runtimeID
}
func (t *DefaultBlockTranslator) upgradeEntityMetadata(metadata map[uint32]any) map[uint32]any {
if t.latest == t.mapping {
return metadata
}
if latestRID, ok := metadata[protocol.EntityDataKeyVariant]; ok {
metadata[protocol.EntityDataKeyVariant] = int32(t.UpgradeBlockRuntimeID(uint32(latestRID.(int32))))
}
return metadata
}