Skip to content

Commit 61dee69

Browse files
slblsLassebq
authored andcommitted
Add additional context with comments
1 parent dc2e1ec commit 61dee69

File tree

1 file changed

+70
-43
lines changed

1 file changed

+70
-43
lines changed

src/main/java/org/mcphackers/launchwrapper/tweak/DelCharTweaker.java

Lines changed: 70 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,22 @@
3535
* visible character in text fields.
3636
*
3737
* Normally, AWT handles platform-specific key mappings, but with AWT removed no
38-
* such handling exists. DEL insertion occurs specifically from Minecraft
39-
* version 1.1 through 1.2.3 because character validation logic was changed in
40-
* 1.1 in order to support 56 languages with extended ASCII characters. The
41-
* validation logic accepts a character if it's either in an allowed list or has
42-
* an ASCII value greater than 32. Because DEL is greater than 32, it's
43-
* considered a valid input and rendered as a replacement for the previous
44-
* character.
38+
* such handling exists. DEL insertion occurs when using deAWT with versions
39+
* 1.1 through 1.2.3 because character validation logic was changed in 1.1 to
40+
* support 56 languages with extended ASCII characters. The validation accepts a
41+
* character if it's either in an allowed list or has an ASCII value greater
42+
* than 32. Because DEL (127) is greater than 32, it's considered valid input
43+
* and rendered as a replacement character.
4544
*
46-
* In Minecraft version 1.1, the validation logic appears in both GuiTextField
47-
* and GuiChat. In versions 1.2.0 through 1.2.3, the logic was moved to
48-
* ChatAllowedCharacters. In version 1.2.4, GuiTextField and GuiChat were
49-
* rewritten, fixing the bug.
45+
* In version 1.1, the validation logic appears in both GuiTextField and
46+
* GuiChat. In versions 1.2.0-1.2.3, it was moved to ChatAllowedCharacters.
47+
* In version 1.2.4, GuiTextField and GuiChat were rewritten, fixing the bug.
5048
*
51-
* This tweaker targets Minecraft version 1.1 specifically. It modifies the
52-
* bytecode to reject ASCII values less than or equal to 127 instead of 32. This
53-
* excludes DEL while preserving intended support for extended ASCII (128-255).
54-
* Regular ASCII characters (Space, letters, numbers, etc.) remain unaffected as
55-
* they're included in the game's allowed list and thus pass the first part of
56-
* the validation condition.
49+
* This tweaker targets Minecraft 1.1 specifically. It modifies the bytecode to
50+
* reject ASCII values less than or equal to 127 instead of 32, excluding DEL
51+
* while preserving support for extended ASCII (128-255). Regular ASCII
52+
* characters (space, letters, numbers) remain unaffected as they're included
53+
* in the allowed list and pass the first validation condition.
5754
*
5855
* Additional links:
5956
*
@@ -70,12 +67,39 @@ public class DelCharTweaker implements Tweaker {
7067
private String guiScreenName;
7168

7269
public DelCharTweaker(LaunchConfig config, LegacyTweakContext context) {
70+
if (config == null || context == null) {
71+
throw new IllegalArgumentException(
72+
"Launch config and legacy tweak context must be non-null to construct DelCharTweaker.");
73+
}
74+
7375
this.config = config;
7476
this.context = context;
7577
}
7678

79+
/*
80+
* Identifies the GuiScreen class name starting from the known Minecraft class.
81+
*
82+
* public void displayGuiScreen(GuiScreen screen) {
83+
* // ...
84+
* screen.setWorldAndResolution(this, int, int);
85+
* // ...
86+
* }
87+
*
88+
* First, finds methods matching the displayGuiScreen signature:
89+
* public void (Object)
90+
*
91+
* Then examines each candidate for this bytecode sequence representing an
92+
* invocation on the method parameter:
93+
* ALOAD 1, ALOAD 0, ILOAD, ILOAD, INVOKEVIRTUAL
94+
*
95+
* Finally, verifies the invocation's owner type matches the method
96+
* parameter and its signature matches setWorldAndResolution:
97+
* public void (Minecraft, int, int)
98+
*
99+
* When all constraints are satisfied, the method parameter type is
100+
* confirmed as GuiScreen and its name is returned.
101+
*/
77102
private String getGuiScreenName(ClassNode node) {
78-
// public void displayGuiScreen(GuiScreen)
79103
for (MethodNode method : node.methods) {
80104
if ((method.access & ACC_PUBLIC) == 0) {
81105
continue;
@@ -91,8 +115,6 @@ private String getGuiScreenName(ClassNode node) {
91115
continue;
92116
}
93117

94-
// ((GuiScreen) _).setWorldAndResolution(Minecraft, int, int)
95-
// ALOAD 1, ALOAD 0, ILOAD, ILOAD, INVOKEVIRTUAL
96118
for (int index = 0; index <= method.instructions.size() - 5; index++) {
97119
AbstractInsnNode[] insns = fill(method.instructions.get(index), 5);
98120

@@ -128,6 +150,17 @@ private String getGuiScreenName(ClassNode node) {
128150
return null;
129151
}
130152

153+
/*
154+
* Identifies GuiChat by its structure and inheritance.
155+
*
156+
* GuiChat extends GuiScreen and contains exactly three fields:
157+
* - protected String message
158+
* - private int updateCounter
159+
* - private static final String allowedCharacters
160+
*
161+
* Further validated by checking for the keyTyped method:
162+
* protected void keyTyped(char, int)
163+
*/
131164
private boolean isGuiChat(ClassNode node) {
132165
if (!node.superName.equals(guiScreenName)) {
133166
return false;
@@ -144,21 +177,16 @@ private boolean isGuiChat(ClassNode node) {
144177
for (FieldNode field : node.fields) {
145178
Type fieldType = Type.getType(field.desc);
146179

147-
// protected String message
148180
if ((field.access & ACC_PROTECTED) != 0 &&
149181
(field.access & ACC_STATIC) == 0 &&
150182
fieldType.getSort() == Type.OBJECT &&
151183
fieldType.getClassName().equals("java.lang.String")) {
152184
hasProtectedString = true;
153-
}
154-
// private int updateCounter
155-
else if ((field.access & ACC_PRIVATE) != 0 &&
185+
} else if ((field.access & ACC_PRIVATE) != 0 &&
156186
(field.access & ACC_STATIC) == 0 &&
157187
fieldType.equals(Type.INT_TYPE)) {
158188
hasPrivateInt = true;
159-
}
160-
// private static final String allowedCharacters
161-
else if ((field.access & ACC_PRIVATE) != 0 &&
189+
} else if ((field.access & ACC_PRIVATE) != 0 &&
162190
(field.access & ACC_STATIC) != 0 &&
163191
(field.access & ACC_FINAL) != 0 &&
164192
fieldType.getSort() == Type.OBJECT &&
@@ -171,7 +199,6 @@ else if ((field.access & ACC_PRIVATE) != 0 &&
171199
return false;
172200
}
173201

174-
// protected keyTyped (char, int) -> void
175202
for (MethodNode method : node.methods) {
176203
if ((method.access & ACC_PROTECTED) == 0) {
177204
continue;
@@ -192,12 +219,17 @@ else if ((field.access & ACC_PRIVATE) != 0 &&
192219
return false;
193220
}
194221

222+
/*
223+
* Identifies GuiTextField by its constructor signature and inheritance.
224+
*
225+
* GuiTextField extends Gui and has a specific seven-parameter constructor:
226+
* public GuiTextField(GuiScreen, FontRenderer, int, int, int, int, String)
227+
*/
195228
private boolean isGuiTextField(ClassNode node) {
196229
if (!node.superName.equals(guiName)) {
197230
return false;
198231
}
199232

200-
// public GuiTextField(GuiScreen, FontRenderer, int, int, int, int, String)
201233
for (MethodNode method : node.methods) {
202234
if (!method.name.equals("<init>") || (method.access & ACC_PUBLIC) == 0) {
203235
continue;
@@ -223,25 +255,20 @@ private boolean isGuiTextField(ClassNode node) {
223255
}
224256

225257
/*
226-
* Identifies the bytecode pattern and returns the BIPUSH instruction
227-
* that needs to be modified when the pattern matches.
258+
* Identifies and returns the BIPUSH instruction to modify.
228259
*
229260
* The validation logic in decompiled form:
230261
* if (ChatAllowedCharacters.allowedCharacters.indexOf(var1) >= 0 || var1 > 32)
231262
*
232-
* The bytecode uses IF_ICMPLE (if less than or equal) with inverted
233-
* logic. It jumps to reject the character if it's <= 32, allowing
234-
* it through if > 32. By changing the constant from 32 to 127,
235-
* characters <= 127 are rejected, which excludes DEL.
236-
*
237-
* The bytecode pattern:
238-
* IFGE [2 offset bytes], ILOAD_1, BIPUSH 32, IF_ICMPLE [2 offset bytes]
263+
* The bytecode uses IF_ICMPLE with inverted logic, jumping to reject
264+
* characters <= 32 and allowing those > 32. Changing the constant from 32
265+
* to 127 rejects characters <= 127, excluding DEL.
239266
*
240-
* Pattern sequence:
241-
* - IFGE: Checks if the indexOf result is >= 0 (character is in allowed list)
242-
* - ILOAD_1: Loads the character parameter onto the stack
243-
* - BIPUSH 32: Pushes the ASCII value for Space onto the stack
244-
* - IF_ICMPLE: Compares and jumps if the character is <= 32
267+
* Bytecode pattern:
268+
* - IFGE: Checks if indexOf result >= 0 (character in allowed list)
269+
* - ILOAD_1: Loads the character parameter
270+
* - BIPUSH 32: Pushes 32 (Space)
271+
* - IF_ICMPLE: Compares and jumps if character <= 32
245272
*/
246273
private IntInsnNode getTargetInsn(AbstractInsnNode insn) {
247274
AbstractInsnNode[] insns = fill(insn, 4);

0 commit comments

Comments
 (0)