/
DisplaySlots.py
215 lines (184 loc) · 8.53 KB
/
DisplaySlots.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
"""
Makes Slot.slotIndex public and then appends this code to the end of GuiContainer.drawSlot(Slot s):
Minecraft.getMinecraft().fontRendererObj.drawString(Integer.toString(s.slotIndex), s.xDisplayPosition + 4, s.yDisplayPosition + 3, 0);
This makes slots render their IDs.
"""
from jawa.cf import ClassFile
from jawa.constants import *
from jawa.util.bytecode import Instruction
from jawa.assemble import assemble
from zipfile import ZipFile
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
import tempfile
import zipfile
import shutil
import os
jar_name = raw_input("Enter path and file name of JAR: ")
brand_class = None
container_class = None
guicontainer_class = None
fontrenderer_class = None
minecraft_class = None
slot_class = None
remove_names = []
with ZipFile(jar_name, "r") as jar:
print "Searching for classes..."
for path in jar.namelist():
if path.startswith("META-INF"):
remove_names.append(path)
if not path.endswith(".class"):
continue
cf = ClassFile(StringIO(jar.read(path)))
if cf.this.name.value == "net/minecraft/client/ClientBrandRetriever":
brand_class = cf
remove_names.append(path)
for c in cf.constants.find(ConstantString):
if c.string.value == "Listener already listening" or c.string.value == "Unable to construct this menu by type":
print "Container = %s" % cf.this.name.value
container_class = cf
# Continue searching classes, stop searching constants
break
elif c.string.value == "textures/gui/container/inventory.png":
print "GuiContainer = %s" % cf.this.name.value
guicontainer_class = cf
remove_names.append(path)
break
elif c.string.value == "textures/font/unicode_page_%02x.png" or cf.this.name.value == 'cwa': # HACK -- assume class for 1.14
print "FontRenderer = %s" % cf.this.name.value
fontrenderer_class = cf
break
elif c.string.value == "textures/font/ascii.png" or c.string.value == "Setting user: {}":
print "Minecraft = %s" % cf.this.name.value
minecraft_class = cf
break
slot_class = None
for method in container_class.methods:
if len(method.args) == 1 and method.access_flags.acc_protected:
path = method.args[0].name + ".class"
slot_class = ClassFile(StringIO(jar.read(path)))
print "Slot = %s" % slot_class.this.name.value
break
fontrendererobj = minecraft_class.fields.find_one(type_="L" + fontrenderer_class.this.name.value + ";")
print "Minecraft.fontRendererObj = %s" % (fontrendererobj.descriptor.value + " " + fontrendererobj.name.value)
draw_string = fontrenderer_class.methods.find_one(args="Ljava/lang/String;FFI")
print "FontRenderer.drawStringWithShadow = %s" % (draw_string.descriptor.value + " " + draw_string.name.value)
slot_fields = list(slot_class.fields)
slot_number = slot_fields[2]
slot_x = slot_fields[3]
slot_y = slot_fields[4]
print "Slot.slotNumber = %s, Slot.x = %s, Slot.y = %s" % (slot_number.descriptor.value + " " + slot_number.name.value, slot_x.descriptor.value + " " + slot_x.name.value, slot_y.descriptor.value + " " + slot_y.name.value)
draw_slot = guicontainer_class.methods.find_one(args="L" + slot_class.this.name.value + ";", f=lambda m: m.access_flags.acc_private)
print "GuiContainer.drawSlot = %s" % (draw_slot.descriptor.value + " " + draw_slot.name.value)
get_minecraft = minecraft_class.methods.find_one(returns="L" + minecraft_class.this.name.value + ";")
print "Minecraft.getMinecraft = %s" % (get_minecraft.descriptor.value + " " + get_minecraft.name.value)
print "Generating new code..."
def new_code():
old_instructions = list(draw_slot.code.disassemble()) # All instructions but the return
"""Generates new code for the draw slot method"""
new_instructions = assemble([
# Get the font renderer
('invokestatic', # Invoke Minecraft.getMinecraft()
guicontainer_class.constants.create_method_ref(
minecraft_class.this.name.value, # Class
get_minecraft.name.value, # getMinecraft() name
get_minecraft.descriptor.value # getMinecraft() descriptor
)
),
('getfield', # Get the font renderer obj field
guicontainer_class.constants.create_field_ref(
minecraft_class.this.name.value, # Class
fontrendererobj.name.value, # fontrendererobj field name
fontrendererobj.descriptor.value # fontrendererobj field descriptor
)
),
# Prepare the arguments
('aload_1',), # Load the slot parameter
('getfield', # Get the slot type field
guicontainer_class.constants.create_field_ref(
slot_class.this.name.value, # Slot class
slot_number.name.value, # Slot number field name
slot_number.descriptor.value # Slot number field type
)
),
('invokestatic', # Invoke Integer.toString(int value)
guicontainer_class.constants.create_method_ref(
'java/lang/Integer', # Class
'toString', # Method named toString
'(I)Ljava/lang/String;' # Takes an int and returns a String
)
),
('aload_1',), # Load the slot parameter
('getfield', # Get the slot type field
guicontainer_class.constants.create_field_ref(
slot_class.this.name.value, # Slot class
slot_x.name.value, # Slot x field name
slot_x.descriptor.value # Slot x field type
)
),
('iconst_3',), # Put 3 on the stack
('iadd',), # Add 3 to x
('i2f',), # Make it into a float
('aload_1',), # Load the slot parameter
('getfield', # Get the slot type field
guicontainer_class.constants.create_field_ref(
slot_class.this.name.value, # Slot class
slot_y.name.value, # Slot y field name
slot_y.descriptor.value # Slot y field type
)
),
('iconst_4',), # Put 4 on the stack
('iadd',), # Add 4 to y
('i2f',), # Make it into a float
('ldc_w', guicontainer_class.constants.create_integer(0xFFFFFF)), # Put the color 0xFFFFFF onto the stack (so, gray / &f)
# drawString does make this solid, IE 0xFFFFFFFF.
# Call drawString
('invokevirtual',
guicontainer_class.constants.create_method_ref(
fontrenderer_class.this.name.value, # Slot class
draw_string.name.value, # Draw string field name
draw_string.descriptor.value # Draw string field type
)
),
('pop',) # Remove the return value from drawString
])
for ins in old_instructions[:-1]: # Old code (except for last return)
yield ins
for ins in new_instructions: # New code
yield ins
yield old_instructions[-1] # Final return
for ins in new_code():
print ins
# Change the code for the draw slot method
draw_slot.code.assemble(new_code())
# Tweak the brand
brand_class.constants.find_one(ConstantString).value = "vanilla + SlotIDDisplay"
# OK, now we've made the changes - save them!
# Remove the old world class (zip files, being strange, allow multiple with the same name)
# http://stackoverflow.com/a/4653863/3991344 - nosklo
def remove_from_zip(zipfname, *filenames):
tempdir = tempfile.mkdtemp()
try:
tempname = os.path.join(tempdir, 'new.zip')
with zipfile.ZipFile(zipfname, 'r') as zipread:
with zipfile.ZipFile(tempname, 'w') as zipwrite:
for item in zipread.infolist():
if item.filename not in filenames:
data = zipread.read(item.filename)
zipwrite.writestr(item, data)
shutil.move(tempname, zipfname)
finally:
shutil.rmtree(tempdir)
# End stackoverflow copypaste
print "Removing old classes and META-INF from zipfile..."
remove_from_zip(jar_name, *remove_names)
print "Writing new classes..."
with ZipFile(jar_name, "a") as jar:
for c in (guicontainer_class, slot_class, brand_class):
print "Writing " + c.this.name.value
out = StringIO()
c.save(out)
jar.writestr(c.this.name.value + ".class", out.getvalue())
print "Saved! Your jar should now be updated."