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