Skip to content

Commit

Permalink
Merge pull request #20 from Pokechu22/tileentity-topping
Browse files Browse the repository at this point in the history
Create tile entity topping
  • Loading branch information
TkTech authored Apr 20, 2017
2 parents 2ae6266 + d061e85 commit 871c88d
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 9 deletions.
56 changes: 47 additions & 9 deletions burger/toppings/identify.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
except ImportError:
from StringIO import StringIO


def identify(class_file):
"""
The first pass across the JAR will identify all possible classes it
Expand All @@ -44,7 +45,7 @@ def identify(class_file):
# We can identify almost every class we need just by
# looking for consistent strings.
matches = (
('Accessed Biomes before Bootstrap!', 'biome.list'), #1.9 only
('Accessed Biomes before Bootstrap!', 'biome.list'), # 1.9 only
('Ice Plains', 'biome.superclass'),
('Accessed Blocks before Bootstrap!', 'block.list'),
('lightgem', 'block.superclass'),
Expand All @@ -57,10 +58,19 @@ def identify(class_file):
('Outdated server!', 'nethandler.server'),
('Corrupt NBT tag', 'nbtcompound'),
(' is already assigned to protocol ', 'packet.connectionstate'),
('The received encoded string buffer length is less than zero! Weird string!', 'packet.packetbuffer'),
(
'The received encoded string buffer length is ' \
'less than zero! Weird string!',
'packet.packetbuffer'
),
('Data value id is too big', 'metadata'),
('X#X', 'recipe.superclass'),
('Accessed Sounds before Bootstrap!', 'sounds.list'),
('Skipping BlockEntity with id ', 'tileentity.superclass'),
(
'Unable to resolve BlockEntity for ItemInstance:',
'tileentity.blockentitytag'
)
)
for c in class_file.constants.find(ConstantString):
value = c.string.value
Expand All @@ -80,10 +90,27 @@ def identify(class_file):
# We _may_ have found the SoundEvent class, but there are several
# other classes with this string constant. So we need to check
# for registration methods.
register_method = class_file.methods.find_one(args='', returns='V', f=lambda m: m.access_flags.acc_public and m.access_flags.acc_static)
private_register_method = class_file.methods.find_one(args='', returns='V', f=lambda m: m.access_flags.acc_public and m.access_flags.acc_static)

if register_method and private_register_method:
def is_public_static(m):
return m.access_flags.acc_public and m.access_flags.acc_static

def is_private_static(m):
return m.access_flags.acc_public and m.access_flags.acc_static

pub_args = {
"args": "",
"returns": "",
"f": is_public_static
}
priv_args = {
"args": "",
"returns": "",
"f": is_private_static
}

public_register_method = class_file.methods.find_one(**pub_args)
private_register_method = class_file.methods.find_one(**priv_args)

if public_register_method and private_register_method:
return 'sounds.event', class_file.this.name.value


Expand All @@ -109,7 +136,9 @@ class IdentifyTopping(Topping):
"identify.packet.packetbuffer",
"identify.recipe.superclass",
"identify.sounds.event",
"identify.sounds.list"
"identify.sounds.list",
"identify.tileentity.superclass",
"identify.tileentity.blockentitytag"
]

DEPENDS = []
Expand All @@ -125,8 +154,17 @@ def act(aggregate, jar, verbose=False):
result = identify(cf)
if result:
if result[0] in classes:
raise Exception("Already registered " + result[0] + " to " + classes[result[0]] + "! Can't overwrite it with " + result[1])
raise Exception(
"Already registered %(value)s to %(old_class)s! "
"Can't overwrite it with %(new_class)s" % {
"value": result[0],
"old_class": classes[result[0]],
"new_class": result[1]
})
classes[result[0]] = result[1]
if len(classes) == len(IdentifyTopping.PROVIDES):
# If everything has been found, we don't need to keep
# searching, so stop early for performance
break
print "identify classes:",classes
if verbose:
print("identify classes:", classes)
134 changes: 134 additions & 0 deletions burger/toppings/tileentities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env python
# -*- coding: utf8 -*-

from .topping import Topping

from jawa.constants import ConstantClass, ConstantString
from jawa.cf import ClassFile

try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO


class TileEntityTopping(Topping):
"""Gets tile entity (block entity) types."""

PROVIDES = [
"tileentities.list",
"tileentities.tags",
"tileentities.networkids"
]

DEPENDS = [
"identify.tileentity.superclass",
"identify.tileentity.blockentitytag",
"packets.classes"
]

@staticmethod
def act(aggregate, jar, verbose=False):
te = aggregate.setdefault("tileentity", {})

if "tileentity.superclass" not in aggregate["classes"]:
if verbose:
print "Missing tileentity.superclass"
return

superclass = aggregate["classes"]["tileentity.superclass"]
cf = ClassFile(StringIO(jar.read(superclass + ".class")))
method = cf.methods.find_one("<clinit>")

tileentities = te.setdefault("tileentities", {})
te_classes = te.setdefault("classes", {})
tmp = {}
for ins in method.code.disassemble():
if ins.mnemonic in ("ldc", "ldc_w"):
const = cf.constants.get(ins.operands[0].value)
if isinstance(const, ConstantClass):
tmp["class"] = const.name.value
elif isinstance(const, ConstantString):
tmp["name"] = const.string.value
elif ins.mnemonic == "invokestatic":
if "class" in tmp and "name" in tmp:
tmp["blocks"] = []
tileentities[tmp["name"]] = tmp
te_classes[tmp["class"]] = tmp["name"]
tmp = {}

if "tileentity.blockentitytag" in aggregate["classes"]:
# Block entity tag matches block names to tile entities.
tag = aggregate["classes"]["tileentity.blockentitytag"] + ".class"
tag_cf = ClassFile(StringIO(jar.read(tag)))
method = tag_cf.methods.find_one("<clinit>")

stack = []
for ins in method.code.disassemble():
if ins.mnemonic in ("ldc", "ldc_w"):
const = tag_cf.constants.get(ins.operands[0].value)
if isinstance(const, ConstantString):
stack.append(const.string.value)
elif ins.mnemonic == "invokeinterface":
if len(stack) == 2:
if not stack[1] in tileentities:
if verbose:
# This does currently happen in 1.9
print ("Trying to mark %s as a block with "
"tile entity %s but that tile entity "
"does not exist!"
% (stack[0], stack[1]))
else:
tileentities[stack[1]]["blocks"].append(stack[0])
stack = []
elif verbose:
print "No block entity tag info; skipping that"

nbt_tag_type = "L" + aggregate["classes"]["nbtcompound"] + ";"
if "nethandler.client" in aggregate["classes"]:
updatepacket = None
for packet in aggregate["packets"]["packet"].itervalues():
if (packet["direction"] != "CLIENTBOUND" or
packet["state"] != "PLAY"):
continue

packet_cf = ClassFile(StringIO(jar.read(packet["class"])))
# Check if the packet has the expected fields in the class file
# for the update tile entity packet
if (len(packet_cf.fields) >= 3 and
# Tile entity type int, at least (maybe also position)
len(list(packet_cf.fields.find(type_="I"))) >= 1 and
# New NBT tag
len(list(packet_cf.fields.find(type_=nbt_tag_type)))):
# There are other fields, but they vary by version.
updatepacket = packet
break

if not updatepacket:
print "Failed to identify update tile entity packet"
return

te["update_packet"] = updatepacket
nethandler = aggregate["classes"]["nethandler.client"] + ".class"
nethandler_cf = ClassFile(StringIO(jar.read(nethandler)))

updatepacket_name = updatepacket["class"].replace(".class", "")

method = nethandler_cf.methods.find_one(
args="L" + updatepacket_name + ";")

value = None
for ins in method.code.disassemble():
if ins.mnemonic.startswith("iconst_"):
value = ins.mnemonic[-1]
elif ins.mnemonic == "bipush":
value = ins.operands[0].value
elif ins.mnemonic == "instanceof":
if value is None:
# Ensure the command block callback is not counted
continue

const = nethandler_cf.constants.get(ins.operands[0].value)
te_name = te_classes[const.name.value]
tileentities[te_name]["network_id"] = value
value = None

0 comments on commit 871c88d

Please sign in to comment.