35
35
* visible character in text fields.
36
36
*
37
37
* 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.
45
44
*
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.
50
48
*
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.
57
54
*
58
55
* Additional links:
59
56
*
@@ -70,12 +67,39 @@ public class DelCharTweaker implements Tweaker {
70
67
private String guiScreenName ;
71
68
72
69
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
+
73
75
this .config = config ;
74
76
this .context = context ;
75
77
}
76
78
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
+ */
77
102
private String getGuiScreenName (ClassNode node ) {
78
- // public void displayGuiScreen(GuiScreen)
79
103
for (MethodNode method : node .methods ) {
80
104
if ((method .access & ACC_PUBLIC ) == 0 ) {
81
105
continue ;
@@ -91,8 +115,6 @@ private String getGuiScreenName(ClassNode node) {
91
115
continue ;
92
116
}
93
117
94
- // ((GuiScreen) _).setWorldAndResolution(Minecraft, int, int)
95
- // ALOAD 1, ALOAD 0, ILOAD, ILOAD, INVOKEVIRTUAL
96
118
for (int index = 0 ; index <= method .instructions .size () - 5 ; index ++) {
97
119
AbstractInsnNode [] insns = fill (method .instructions .get (index ), 5 );
98
120
@@ -128,6 +150,17 @@ private String getGuiScreenName(ClassNode node) {
128
150
return null ;
129
151
}
130
152
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
+ */
131
164
private boolean isGuiChat (ClassNode node ) {
132
165
if (!node .superName .equals (guiScreenName )) {
133
166
return false ;
@@ -144,21 +177,16 @@ private boolean isGuiChat(ClassNode node) {
144
177
for (FieldNode field : node .fields ) {
145
178
Type fieldType = Type .getType (field .desc );
146
179
147
- // protected String message
148
180
if ((field .access & ACC_PROTECTED ) != 0 &&
149
181
(field .access & ACC_STATIC ) == 0 &&
150
182
fieldType .getSort () == Type .OBJECT &&
151
183
fieldType .getClassName ().equals ("java.lang.String" )) {
152
184
hasProtectedString = true ;
153
- }
154
- // private int updateCounter
155
- else if ((field .access & ACC_PRIVATE ) != 0 &&
185
+ } else if ((field .access & ACC_PRIVATE ) != 0 &&
156
186
(field .access & ACC_STATIC ) == 0 &&
157
187
fieldType .equals (Type .INT_TYPE )) {
158
188
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 &&
162
190
(field .access & ACC_STATIC ) != 0 &&
163
191
(field .access & ACC_FINAL ) != 0 &&
164
192
fieldType .getSort () == Type .OBJECT &&
@@ -171,7 +199,6 @@ else if ((field.access & ACC_PRIVATE) != 0 &&
171
199
return false ;
172
200
}
173
201
174
- // protected keyTyped (char, int) -> void
175
202
for (MethodNode method : node .methods ) {
176
203
if ((method .access & ACC_PROTECTED ) == 0 ) {
177
204
continue ;
@@ -192,12 +219,17 @@ else if ((field.access & ACC_PRIVATE) != 0 &&
192
219
return false ;
193
220
}
194
221
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
+ */
195
228
private boolean isGuiTextField (ClassNode node ) {
196
229
if (!node .superName .equals (guiName )) {
197
230
return false ;
198
231
}
199
232
200
- // public GuiTextField(GuiScreen, FontRenderer, int, int, int, int, String)
201
233
for (MethodNode method : node .methods ) {
202
234
if (!method .name .equals ("<init>" ) || (method .access & ACC_PUBLIC ) == 0 ) {
203
235
continue ;
@@ -223,25 +255,20 @@ private boolean isGuiTextField(ClassNode node) {
223
255
}
224
256
225
257
/*
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.
228
259
*
229
260
* The validation logic in decompiled form:
230
261
* if (ChatAllowedCharacters.allowedCharacters.indexOf(var1) >= 0 || var1 > 32)
231
262
*
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.
239
266
*
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
245
272
*/
246
273
private IntInsnNode getTargetInsn (AbstractInsnNode insn ) {
247
274
AbstractInsnNode [] insns = fill (insn , 4 );
0 commit comments