findMakeClientMessage(Function fn) {
168 | Function makeClientMessageFn = null;
169 | Instruction makeClientMessageInsn = null;
170 | outer:
171 | for (Instruction insn : getFunctionInstructions(fn)) {
172 | if (!insn.getMnemonicString().equals("CALL"))
173 | continue;
174 | if (insn.getNumOperands() == 0 || insn.getAddress(0) == null)
175 | continue;
176 |
177 | Function called = getFunctionAt(insn.getAddress(0));
178 | if (called == null)
179 | continue;
180 |
181 | for (Instruction inner : getFunctionInstructions(called)) {
182 | if (!inner.getMnemonicString().equals("CALL"))
183 | continue;
184 | if (inner.getNumOperands() == 0 || inner.getAddress(0) == null)
185 | continue;
186 |
187 | Function called2 = getFunctionAt(inner.getAddress(0));
188 | if (called2 == null)
189 | continue;
190 |
191 | if (called2.getName().equals("_Throw_C_error")) {
192 | if (makeClientMessageFn != null && makeClientMessageFn != called) {
193 | throw new IllegalStateException("more than one option for MakeClientMessage found, disable ClientProt finding: " + makeClientMessageFn.getName() + ", " + called.getName());
194 | }
195 |
196 | makeClientMessageFn = called;
197 | makeClientMessageInsn = insn;
198 | continue outer;
199 | }
200 | }
201 | }
202 | return Pair.of(makeClientMessageFn, makeClientMessageInsn);
203 | }
204 |
205 | private Function findNextFunction(Instruction startingAt) {
206 | for (int i = 0; i < 10; i++) {
207 | if (startingAt == null) return null;
208 |
209 | if (!startingAt.getMnemonicString().equals("CALL") && !startingAt.getMnemonicString().equals("JMP")) {
210 | startingAt = startingAt.getNext();
211 | continue;
212 | }
213 |
214 | if (startingAt.getAddress(0) == null) {
215 | continue;
216 | }
217 |
218 | return getFunctionAt(startingAt.getAddress(0));
219 | }
220 |
221 | return null;
222 | }
223 |
224 | private void clientProtPrototype() throws Exception {
225 | // reverse lookup...
226 | SymbolIterator it = currentProgram.getSymbolTable().getSymbols("SendPing");
227 | if (!it.hasNext()) throw new IllegalStateException("SendPing not found (Obtained from ServerProt decoding)");
228 | Symbol sendPingSymbol = it.next();
229 | if (it.hasNext()) throw new IllegalStateException("more than one SendPing found?");
230 |
231 | Function f = findNextFunction(getInstructionAt(sendPingSymbol.getAddress()));
232 | if (f == null) {
233 | throw new IllegalStateException("Failed to find inner function of SendPing (At " + sendPingSymbol.getAddress() +")");
234 | }
235 |
236 | // TODO: If ClientProt shit breaks, this is likely it.
237 | Function makeClientMessageFn = null;
238 | Instruction makeClientMessageInsn = null;
239 | for (Instruction insn : getFunctionInstructions(f)) {
240 | if (!insn.getMnemonicString().equals("CALL")) {
241 | continue;
242 | }
243 |
244 | makeClientMessageFn = getFunctionAt(insn.getAddress(0));
245 | makeClientMessageInsn = insn;
246 | break;
247 | }
248 |
249 | Address someClientProt = null;
250 | renameFunction(makeClientMessageFn, "jag::ServerConnection::MakeClientMessage");
251 | int i = 10;
252 | while (i-- > 0) {
253 | makeClientMessageInsn = makeClientMessageInsn.getPrevious();
254 | if (makeClientMessageInsn.getMnemonicString().equals("LEA") && makeClientMessageInsn.getRegister(0).getBaseRegister().getName().equals("R8")) {
255 | someClientProt = makeClientMessageInsn.getAddress(1);
256 | break;
257 | }
258 | }
259 | if (someClientProt == null) {
260 | throw new IllegalStateException("Couldn't find a ClientProt struct, please disable ClientProt finding");
261 | }
262 | printf("Found a ClientProt struct at %s%n", someClientProt);
263 |
264 | Address registerClientProt = null;
265 | for (Reference reference : getReferencesTo(someClientProt.subtract(4))) {
266 | Instruction insn = getInstructionAt(reference.getFromAddress());
267 | int j = 3;
268 | while( insn.getPrevious() != null && j >= 0) {
269 | insn = insn.getPrevious();
270 | j--;
271 | }
272 | i = 8;
273 | while (i-- > 0) {
274 | insn = insn.getNext();
275 | if (insn.getMnemonicString().equals("JMP")) {
276 | if (registerClientProt != null) {
277 | throw new IllegalStateException("more than one register client prot qualifier found, disable ClientProt finding");
278 | }
279 |
280 | int numReferences = getReferencesTo(insn.getAddress(0)).length;
281 | if (numReferences > Settings.MIN_CLIENT_PROTS && numReferences < Settings.MAX_CLIENT_PROTS)
282 | registerClientProt = insn.getAddress(0);
283 | break;
284 | }
285 | }
286 | }
287 | if (registerClientProt == null) {
288 | throw new IllegalStateException("no client prot register function found, disable ClientProt finding");
289 | }
290 |
291 | // rcx -> TcpConnectionBase
292 | // rdx -> ?
293 | // r8 -> message / ClientProt
294 | // r9 -> ?
295 | Reference[] references = getReferencesTo(registerClientProt);
296 | clientProt = new ClientProt[references.length];
297 | printf("Found %d references to REGISTER_CLIENT_MESSAGE at %s%n", references.length, registerClientProt.toString());
298 |
299 | for (Reference reference : references) {
300 | if (!reference.getReferenceType().isCall()) continue;
301 |
302 | Instruction insn = getInstructionAt(reference.getFromAddress());
303 |
304 | outer:
305 | while (true) {
306 | Reference[] referencesTo = getReferencesTo(insn.getAddress());
307 |
308 | if (referencesTo.length != 0) {
309 | for (Reference ref : referencesTo) {
310 | if (ref.getReferenceType() == RefType.DATA) {
311 | break outer;
312 | }
313 | }
314 | }
315 |
316 | insn = insn.getPrevious(); // roll back to begin of function
317 | }
318 |
319 | Address clientProt = null;
320 | int opcode = -100;
321 | int size = -100;
322 |
323 | i = 10;
324 | boolean rcx = false, rdx = false, r8 = false;
325 | while (i-- > 0 && (!rcx || !rdx || !r8)) {
326 | if (insn.getRegister(0) == null) {
327 | insn = insn.getNext();
328 | continue;
329 | }
330 |
331 | String register = insn.getRegister(0).getBaseRegister().getName();
332 | switch (register) {
333 | case "RCX":
334 | if (!insn.getMnemonicString().equals("LEA"))
335 | throw new IllegalStateException("expected LEA for loading ClientProt struct");
336 | clientProt = insn.getAddress(1);
337 | rcx = true;
338 | break;
339 | case "RDX":
340 | if (insn.getMnemonicString().equals("MOV") && insn.getRegister(1) != null && insn.getRegister(1).getBaseRegister().getName().equals("R8")) {
341 | if (size == -100)
342 | throw new IllegalStateException("attempted to set opcode from size, but size is not set");
343 | opcode = size;
344 | } else if (insn.getMnemonicString().equals("MOV")) {
345 | opcode = (int) insn.getScalar(1).getSignedValue();//insn.getInt(1);
346 | } else if (insn.getMnemonicString().equals("XOR") && insn.getRegister(0).getBaseRegister().getName().equals("RDX") && insn.getRegister(1).getBaseRegister().getName().equals("RDX")) {
347 | opcode = 0;
348 | } else if (insn.getMnemonicString().equals("LEA") && size != -100 && insn.getOpObjects(1).length == 2) {
349 | if (!((Register) insn.getOpObjects(1)[0]).getBaseRegister().getName().equals("R8"))
350 | throw new IllegalStateException("unexpected register, expected r8");
351 |
352 | opcode = (int) (size + ((Scalar) insn.getOpObjects(1)[1]).getValue());
353 | } else {
354 | throw new RuntimeException(" unsure how to get opcode from " + insn + " [size = " + size + "] at " + insn.getAddress());
355 | }
356 |
357 | rdx = true;
358 | break;
359 | case "R8":
360 | if (insn.getMnemonicString().equals("MOV") && insn.getRegister(1) != null && insn.getRegister(1).getBaseRegister().getName().equals("RDX")) {
361 | if (opcode == -100)
362 | throw new IllegalStateException("attempted to set size from opcode, but opcode is not set");
363 | size = opcode;
364 | } else if (insn.getMnemonicString().equals("MOV")) {
365 | // printf("???? %d%n", insn.getScalar(1).getSignedValue());
366 | size = (int) insn.getScalar(1).getSignedValue();
367 | // printf("Size of %d is %d%n", opcode, size);
368 | } else if (insn.getMnemonicString().equals("XOR") && insn.getRegister(0).getBaseRegister().getName().equals("R8") && insn.getRegister(1).getBaseRegister().getName().equals("R8")) {
369 | size = 0;
370 | } else if (insn.getMnemonicString().equals("LEA") && opcode != -100 && insn.getOpObjects(1).length == 2) {
371 | if (!((Register) insn.getOpObjects(1)[0]).getBaseRegister().getName().equals("RDX"))
372 | throw new IllegalStateException("unexpected register, expected rdx");
373 |
374 | size = (int) (opcode + ((Scalar) insn.getOpObjects(1)[1]).getValue());
375 | // if (size != -1 && size != -2)
376 | // size &= 0xff;
377 | // printf("Size of %d is %d%n", opcode, size);
378 | } else {
379 | throw new RuntimeException(" unsure how to get size from " + insn + " [opcode = " + opcode + "] at " + insn.getAddress());
380 | }
381 | r8 = true;
382 | break;
383 | }
384 |
385 | insn = insn.getNext();
386 | }
387 |
388 | if (clientProt == null || opcode == -100 || size == -100) {
389 | throw new IllegalStateException("failed to read clientprot from address " + reference.getFromAddress());
390 | }
391 |
392 | this.clientProt[opcode] = new ClientProt(clientProt, opcode, size);
393 |
394 | setDataType(clientProt, Types.S_CLIENT_PROT, 8);
395 | setLabel(clientProt, "jag::ClientProt::ClientProtOP_" + opcode);
396 | }
397 | }
398 |
399 | private void setDataType(Address address, DataType type, int length) throws Exception {
400 | Listing listing = currentProgram.getListing();
401 | Data sourceData = listing.getDataAt(address);
402 | if (sourceData == null) return;
403 |
404 | listing.clearCodeUnits(address, address.add(length), false);
405 | listing.createData(address, type, length);
406 | }
407 |
408 | private Data setDataType(Program program, Address address, DataType dataType, int length) {
409 |
410 | int txID = program.startTransaction("Change Data Type");
411 | boolean commit = false;
412 | try {
413 | Listing listing = program.getListing();
414 | Data sourceData = listing.getDataAt(address);
415 | if (sourceData == null) {
416 | return null;
417 | }
418 | listing.clearCodeUnits(address, sourceData.getMaxAddress(), false);
419 | Data data;
420 | if (length > 0) {
421 | data = listing.createData(address, dataType, length);
422 | } else {
423 | data = listing.createData(address, dataType);
424 | }
425 | commit = true;
426 | return data;
427 | } catch (Exception e) {
428 | // Commit is false by default so nothing else to do.
429 | return null;
430 | } finally {
431 | program.endTransaction(txID, commit);
432 | }
433 | }
434 |
435 | private void createDataStructures() throws DuplicateNameException, InvalidInputException {
436 | printf(" - jag::Isaac%n");
437 | Types.C_ISAAC = getOrCreateClass("jag::Isaac");
438 | Types.S_ISAAC = getStructureForClass(Types.C_ISAAC);
439 | Types.S_ISAAC.deleteAll();
440 | resizeStructure(Types.S_ISAAC, 2064);
441 | Types.S_ISAAC.replaceAtOffset(0, Types.UINT, 4, "values_left", "The amount of values left before having to generate new ones");
442 | Types.S_ISAAC.replaceAtOffset(4, arr(Types.UINT, 256, 4), 1024, "rand_results", "The generated random results");
443 | Types.S_ISAAC.replaceAtOffset(1028, arr(Types.UINT, 256, 4), 1024, "mm", TODO_DESC);
444 | Types.S_ISAAC.replaceAtOffset(2052, Types.INT, 4, "aa", TODO_DESC);
445 | Types.S_ISAAC.replaceAtOffset(2056, Types.INT, 4, "bb", TODO_DESC);
446 | Types.S_ISAAC.replaceAtOffset(2060, Types.INT, 4, "cc", TODO_DESC);
447 |
448 | printf(" - jag::HeapInterface");
449 | Types.C_HEAP_INTERFACE = getOrCreateClass("jag::HeapInterface");
450 | Types.S_HEAP_INTERFACE = getStructureForClass(Types.C_HEAP_INTERFACE);
451 |
452 | printf(" - jag::Client%n");
453 | Types.C_CLIENT = getOrCreateClass("jag::Client");
454 | Types.S_CLIENT = getStructureForClass(Types.C_CLIENT); // We will initialize this later on.
455 |
456 | printf(" - jag::ConnectionManager%n");
457 | Types.C_CONNECTION_MANAGER = getOrCreateClass("jag::ConnectionManager");
458 | Types.S_CONNECTION_MANAGER = getStructureForClass(Types.C_CONNECTION_MANAGER);
459 | if (Types.S_CONNECTION_MANAGER.getLength() < 0x10)
460 | resizeStructure(Types.S_CONNECTION_MANAGER, 0x10); // We will resize this later on
461 | Types.S_CONNECTION_MANAGER.replaceAtOffset(0x8, ptr(Types.S_CLIENT), 8, "client", TODO_DESC);
462 |
463 | printf("- jag::Packet%n");
464 | Types.C_PACKET = getOrCreateClass("jag::Packet");
465 | Types.S_PACKET = getStructureForClass(Types.C_PACKET);
466 | resizeStructure(Types.S_PACKET, 0x20);
467 | Types.S_PACKET.replaceAtOffset(0x0, Types.LONGLONG, 8, "field_0x0", TODO_DESC);
468 | Types.S_PACKET.replaceAtOffset(0x8, Types.LONGLONG, 8, "capacity", "The capacity of the buffer (todo: confirm)");
469 | Types.S_PACKET.replaceAtOffset(0x10, ptr(Types.BYTE), 8, "buffer", "The backing buffer");
470 | Types.S_PACKET.replaceAtOffset(0x18, Types.LONGLONG, 8, "offset", "The offset (writer AND reader offset) in the buffer");
471 |
472 | printf("- jag::ClientProt%n");
473 | Types.C_CLIENT_PROT = getOrCreateClass("jag::ClientProt");
474 | Types.S_CLIENT_PROT = getStructureForClass(Types.C_CLIENT_PROT);
475 | Types.S_CLIENT_PROT.deleteAll();
476 | resizeStructure(Types.S_CLIENT_PROT, 8);
477 | Types.S_CLIENT_PROT.replaceAtOffset(0, Types.UINT, 4, "opcode", "Opcode of this ClientProt");
478 | Types.S_CLIENT_PROT.replaceAtOffset(4, Types.INT, 4, "size", "Size of this ClientProt");
479 | Types.S_CLIENT_PROT.setDescription("ClientProt is also used by login packets, opcodes may overlap with in-game protocol definitions.");
480 | }
481 |
482 | /**
483 | * Attempts to find the KERNEL32.DLL:SetErrorMode method. This is called once in the main app method.
484 | */
485 | private Function findAppEntryFunction() {
486 | Symbol symbol = null;
487 |
488 | for (Symbol s : currentProgram.getSymbolTable().getSymbols("SetErrorMode")) {
489 | symbol = s;
490 | }
491 |
492 | if (symbol == null)
493 | throw new NullPointerException("Could not find SetErrorMode");
494 |
495 | int count = 0;
496 | Function f = null;
497 | for (Reference reference : getReferencesTo(symbol.getAddress())) {
498 | Function l = getCurrentProgram().getFunctionManager().getFunctionContaining(reference.getFromAddress());
499 | if (l != null) {
500 | f = l;
501 | count++;
502 | }
503 | }
504 |
505 | if (count > 1) {
506 | throw new IllegalStateException("Multiple possibilities of SetErrorMode xrefs");
507 | }
508 |
509 | if (f != null) {
510 | return f;
511 | }
512 |
513 | throw new NullPointerException("Could not find app entry function");
514 | }
515 |
516 | private boolean isValidFunctionCall(Instruction insn) {
517 | if (!insn.getMnemonicString().equals("CALL"))
518 | return false;
519 |
520 | // Some call functions don't actually have addresses (eg. when using a vtable)
521 | if (insn.getNumOperands() == 0 || insn.getAddress(0) == null)
522 | return false;
523 |
524 | // And some don't have a function at all
525 | Function called = getFunctionAt(insn.getAddress(0));
526 | if (called == null)
527 | return false;
528 |
529 | return true;
530 | }
531 |
532 | /**
533 | * Handles the app's main entry. This performs the following operations:
534 | *
535 | * - Finds function jag::HeapInterface::Alloc
536 | * This is the first method with a LOT of calls in the entrypoint. Found by checking amount of xrefs
537 | *
538 | * - Finds address jag::HeapInterface::g_pHeapInterface
539 | * This is the first argument to jag::HeapInterface::Alloc
540 | *
541 | * - Finds the size of structure jag::Client
542 | * This is the second argument to the first call of jag::HeapInterface::Alloc
543 | *
544 | * - Finds the constructor jag::Client::Client
545 | * This is the first function after the jag::HeapInterface::Alloc class
546 | *
547 | * @param fn The app main entry
548 | */
549 | private void refactorAppEntry(Function fn) throws Exception {
550 | println("App entry at " + fn.getEntryPoint());
551 | int XREF_THRESHOLD = 1500; // Min. number of references to jag::HeapInterface::Alloc
552 | boolean foundAlloc = false;
553 |
554 | RegisterTracker tracker = new RegisterTracker();
555 | for (Instruction insn : getFunctionInstructions(fn)) {
556 | tracker.update(insn);
557 |
558 | if (!isValidFunctionCall(insn)) {
559 | continue;
560 | }
561 |
562 | Function called = getFunctionAt(insn.getAddress(0));
563 | if (!foundAlloc && getReferencesTo(called.getEntryPoint()).length > XREF_THRESHOLD) {
564 | // Jag introduced a function that gets called in place of directly calling Alloc on HeapInterface
565 | Instruction rcx = tracker.getRegisterValue("RCX"); // num_bytes
566 | Instruction rdx = tracker.getRegisterValue("RDX"); // alignment
567 |
568 | renameFunction(called, "jag::HeapInterface::CheckedAlloc");
569 | called.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, false, SourceType.USER_DEFINED,
570 | new ParameterImpl("num_bytes", Types.LONGLONG, currentProgram),
571 | new ParameterImpl("alignment", Types.LONGLONG, currentProgram));
572 |
573 | resizeStructure(Types.S_CLIENT, rcx.getInt(1));
574 |
575 | RegisterTracker tracker2 = new RegisterTracker();
576 | for (Instruction insn2 : getFunctionInstructions(called)) {
577 | tracker2.update(insn2);
578 |
579 | if (!isValidFunctionCall(insn2)) {
580 | continue;
581 | }
582 |
583 | Function called2 = getFunctionAt(insn2.getAddress(0));
584 | Instruction rcx2 = tracker2.getRegisterValue("RCX"); // num_bytes
585 | Instruction rdx2 = tracker2.getRegisterValue("RDX"); // alignment
586 |
587 | setLabel(rcx2.getAddress(1), "jag::HeapInterface::g_pHeapInterface");
588 |
589 | renameFunction(called2, "jag::HeapInterface::Alloc");
590 | called2.setCallingConvention("__thiscall");
591 | called2.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, false, SourceType.USER_DEFINED,
592 | new ParameterImpl("num_bytes", Types.LONGLONG, currentProgram),
593 | new ParameterImpl("alignment", Types.LONGLONG, currentProgram));
594 | break;
595 | }
596 |
597 | foundAlloc = true;
598 | continue;
599 | }
600 |
601 | if (foundAlloc) {
602 | renameFunction(called, "jag::Client::Client");
603 | return;
604 | }
605 | }
606 | }
607 |
608 | /**
609 | * Handles a few ISAAC functions. This performs the following operations:
610 | *
611 | * - Finds function jag::Isaac::Init
612 | * This is the only method in the client with references to a constant and certain bit shifting.
613 | *
614 | * - Finds function jag::Isaac::Generate
615 | * This is the only method that's called from function jag::Isaac::Init
616 | */
617 | private void refactorIsaac() throws Exception {
618 | List initQualifiers = new ArrayList<>();
619 |
620 | fn_loop:
621 | for (Function fn : currentProgram.getFunctionManager().getFunctions(true)) {
622 | for (Instruction insn : getFunctionInstructions(fn)) {
623 | if (!insn.getMnemonicString().equals("MOV"))
624 | continue;
625 |
626 | if (insn.getInt(0) == 0x9e3779b9 || insn.getInt(1) == 0x9e3779b9 || insn.getInt(2) == 0x9e3779b9) {
627 | boolean shl8 = false;
628 | boolean shla = false;
629 | boolean shr10 = false;
630 |
631 | for (Instruction inner : getFunctionInstructions(fn)) {
632 | if (inner.getMnemonicString().equals("SHL")) {
633 | if (inner.getByte(2) == 0x8)
634 | shl8 = true;
635 | else if (inner.getByte(2) == 0xa)
636 | shla = true;
637 | } else if (inner.getMnemonicString().equals("SHR")) {
638 | if (inner.getByte(2) == 0x10)
639 | shr10 = true;
640 | }
641 | }
642 |
643 | if (shl8 && shla && shr10) {
644 | initQualifiers.add(fn);
645 | }
646 |
647 | continue fn_loop;
648 | }
649 | }
650 | }
651 |
652 | if (initQualifiers.size() != 1) {
653 | throw new IllegalStateException("couldn't find jag::Isaac::Init qualifiers! (found " + initQualifiers.size() + ")");
654 | }
655 |
656 | Function init = initQualifiers.get(0);
657 | renameFunction(init, "jag::Isaac::Init");
658 | init.setCallingConvention("__thiscall");
659 | init.setReturnType(Types.VOID, SourceType.USER_DEFINED);
660 | init.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, false, SourceType.USER_DEFINED,
661 | new ParameterImpl("seeds", ptr(Types.UINT), currentProgram));
662 |
663 | // Find generate
664 | Function generate = null;
665 | for (Instruction insn : getFunctionInstructions(init)) {
666 | if (insn.getMnemonicString().equals("CALL")) {
667 | if (generate != null) {
668 | throw new IllegalStateException("More than 1 CALL in jag::Isaac::Init");
669 | }
670 |
671 | generate = getFunctionAt(insn.getAddress(0));
672 | }
673 | }
674 |
675 | if (generate == null) {
676 | throw new IllegalStateException("Failed to find jag::Isaac::Generate in jag::Isaac::Init");
677 | }
678 |
679 | renameFunction(generate, "jag::Isaac::Generate");
680 | generate.setCallingConvention("__thiscall");
681 | generate.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, false, SourceType.USER_DEFINED);
682 | generate.setReturnType(Types.VOID, SourceType.USER_DEFINED);
683 | }
684 |
685 | private Function connectionManagerCtor;
686 |
687 | /**
688 | * Handles the connection manager ctor. This performs the following operations:
689 | *
690 | * - Finds function jag::ConnectionManager::ConnectionManager
691 | * There are only a few methods that have the int constant 20_000, which is always at the end of the function. So we
692 | * scan instructions backwards to filter out the other few remaining functions.
693 | */
694 | private void refactorConnectionManagerCtor() throws Exception {
695 | Map qualifiers = new HashMap<>();
696 |
697 | for (Function f : currentProgram.getFunctionManager().getFunctions(true)) {
698 | for (Instruction insn : getFunctionInstructions(f)) {
699 | if (insn.getMnemonicString().equals("ADD")) {
700 | if (insn.getInt(0) == 20_000 || insn.getInt(1) == 20_000 || insn.getInt(2) == 20_000) {
701 | qualifiers.put(insn, f);
702 | }
703 | }
704 | }
705 | }
706 |
707 | Function ctor = null;
708 | Instruction needle = null;
709 | Iterator> it = qualifiers.entrySet().iterator();
710 | while (it.hasNext()) {
711 | Map.Entry entry = it.next();
712 | Instruction insn = entry.getKey();
713 |
714 | int dist = 0;
715 | while (!insn.getMnemonicString().equals("RET")) {
716 | insn = insn.getNext();
717 | dist++;
718 | }
719 |
720 | if (dist > 15) {
721 | it.remove();
722 | continue;
723 | }
724 |
725 | needle = entry.getKey();
726 | ctor = entry.getValue();
727 | }
728 |
729 | if (qualifiers.size() > 1) {
730 | qualifiers.forEach((insn, f) -> printerr("at: " + f.getEntryPoint() + " (" + f.getName() + ") @ " + insn.getAddress()));
731 | throw new IllegalStateException("Found more than one qualifier for jag::ConnectionManager::ConnectionManager");
732 | }
733 |
734 | if (ctor == null)
735 | throw new NullPointerException("Found no qualifiers for jag::ConnectionManager::ConnectionManager");
736 |
737 | renameFunction(ctor, "jag::ConnectionManager::ConnectionManager");
738 | ctor.setCallingConvention("__thiscall");
739 | ctor.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, false, SourceType.USER_DEFINED, new ParameterImpl("client", ptr(Types.S_CLIENT), currentProgram));
740 | ctor.setReturnType(ptr(Types.S_CONNECTION_MANAGER), SourceType.USER_DEFINED);
741 |
742 | RegisterTracker tracker = new RegisterTracker();
743 | for (Instruction insn : getFunctionInstructions(ctor)) {
744 | if (insn == needle) {
745 | String register = insn.getRegister(0).getName();
746 | Instruction setter = tracker.getRegisterValue(register);
747 |
748 | Address m_currentTimeMS = null;
749 | for (int k = 0; k < 5; k++) {
750 | Address a = setter.getAddress(k);
751 | if (a != null) {
752 | m_currentTimeMS = a;
753 | }
754 | }
755 |
756 | if (m_currentTimeMS == null) {
757 | throw new IllegalStateException("Couldn't find jag::FrameTime::m_currentTimeMS");
758 | }
759 |
760 | setLabel(m_currentTimeMS, "jag::FrameTime::m_currentTimeMS");
761 |
762 | break;
763 | }
764 |
765 | tracker.update(insn);
766 | }
767 |
768 | Reference[] xrefs = getReferencesTo(ctor.getEntryPoint());
769 | if (xrefs.length != 1) {
770 | Address to = xrefs[0].getToAddress();
771 | for (Reference xref : xrefs) {
772 | if (!xref.getToAddress().equals(to))
773 | throw new IllegalStateException("0 or more than 1 xref to jag::ConnectionManager::ConnectionManager");
774 | }
775 |
776 | for (Reference xref : xrefs) {
777 | if (xref.getReferenceType() != RefType.DATA)
778 | xrefs[0] = xref;
779 | }
780 | }
781 |
782 | Instruction insn = getInstructionAt(xrefs[0].getFromAddress()).getPrevious();
783 | while (!insn.getMnemonicString().equals("CALL")) insn = insn.getPrevious();
784 |
785 | while ((insn = insn.getPrevious()) != null) {
786 | if (!insn.getMnemonicString().equals("MOV")) {
787 | continue;
788 | }
789 |
790 | if (insn.getRegister(0) == null || !insn.getRegister(0).getName().equals("RDX")) {
791 | continue;
792 | }
793 |
794 | int size = insn.getInt(1);
795 |
796 | resizeStructure(Types.S_CONNECTION_MANAGER, size);
797 |
798 | break;
799 | }
800 |
801 | connectionManagerCtor = ctor;
802 | }
803 |
804 | private Function serverProtReg1;
805 |
806 | /**
807 | * Black magic.
808 | */
809 | private void refactorPackets() throws Exception {
810 | RegisterTracker tracker = new RegisterTracker();
811 | HashSet visited = new HashSet<>();
812 |
813 | if (connectionManagerCtor == null) throw new NullPointerException("?");
814 |
815 | try {
816 | int i = 0;
817 | for (Instruction insn : getFunctionInstructions(connectionManagerCtor)) {
818 | tracker.update(insn);
819 |
820 | checkAndNameServerProt(insn);
821 |
822 | if (!insn.getMnemonicString().equals("CALL"))
823 | continue;
824 |
825 | i++;
826 | if (i <= 2)
827 | continue;
828 |
829 | Address addr = insn.getAddress(0);
830 | if (addr != null)
831 | refactorPacketsRecursive(insn, getFunctionAt(addr), visited, tracker.waistClone(), tracker.getRegisterValue("RCX"), tracker.getRegisterValue("RDX"));
832 | }
833 | } catch (Exception e) {
834 | if (e.getMessage().equals("yayeeeet")) {
835 | refactorPackets();
836 | return;
837 | } else {
838 | throw e;
839 | }
840 | }
841 |
842 | for (int i = 0; i < packets.length; i++) {
843 | ServerProtInfo info = packets[i];
844 | if (info.name == null) continue;
845 |
846 | StringBuilder nameBuilder = new StringBuilder();
847 | for (String s : info.name.split("_")) {
848 | if (s.length() < 2) continue;
849 | nameBuilder.append(s.substring(0, 1).toUpperCase(Locale.ROOT));
850 | nameBuilder.append(s.substring(1).toLowerCase(Locale.ROOT));
851 | }
852 |
853 | Data data = getDataAt(info.vtable.add(16));
854 | if (data == null || data.getValue() == null) {
855 | printerr("Addr == null @ " + info.vtable.toString());
856 | }
857 |
858 | Address fnAddr = (Address) getDataAt(info.vtable.add(16)).getValue();
859 | Function fn;
860 | try {
861 | fn = getFunctionAt(fnAddr);
862 | renameFunction(fn, "jag::PacketHandlers::" + nameBuilder);
863 | } catch (Exception e) {
864 | createFunction(fnAddr, nameBuilder.toString());
865 | fn = getFunctionAt(fnAddr);
866 | renameFunction(fn, "jag::PacketHandlers::" + nameBuilder);
867 | }
868 | fn.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, false, SourceType.USER_DEFINED,
869 | new ParameterImpl("param1", Types.LONGLONG, currentProgram),
870 | new ParameterImpl("packet", ptr(Types.S_PACKET), currentProgram),
871 | new ParameterImpl("param3", Types.LONGLONG, currentProgram),
872 | new ParameterImpl("isaac", ptr(Types.S_ISAAC), currentProgram)
873 | );
874 | fn.setComment("\n << AUTO REFACTORED BY RS3 NXT REFACTORER >>\nOpcode: " + info.opcode + "\nSize: " + info.size + "\nName: " + info.name);
875 | println("Found ServerProt " + info);
876 | }
877 | }
878 |
879 | HashSet addresses = new HashSet<>();
880 |
881 | private ServerProtInfo serverProtFromAddress(Address a) {
882 | if (addresses.isEmpty()) {
883 | for (ServerProtInfo packet : packets) {
884 | addresses.add(packet.addr);
885 | }
886 | }
887 | for (int i = 0; i < packets.length; i++) {
888 | if (a.equals(packets[i].addr))
889 | return packets[i];
890 | }
891 | throw new IllegalStateException("???");
892 | }
893 |
894 | private void refactorPacketsRecursive(Instruction callInsn, Function fn, HashSet visited, RegisterTracker tracker, Instruction rcx, Instruction rdx) throws Exception {
895 | // if fn we called is null...
896 | if (fn == null)
897 | return;
898 |
899 | // did we find server prot register
900 | if (serverProtReg1 == null) {
901 | if (getReferencesTo(fn.getEntryPoint()).length > 200) {
902 | visited.remove(fn.getEntryPoint());
903 | serverProtReg1 = fn;
904 |
905 | printerr("serverProtReg1 = @ " + serverProtReg1.getName() + " " + serverProtReg1.getEntryPoint());
906 |
907 | while (callInsn.getRegister(0) == null || !callInsn.getRegister(0).getName().equals("RDX")) {
908 | callInsn = callInsn.getPrevious();
909 | }
910 |
911 | Address referringTo = callInsn.getAddress(1);
912 |
913 | if (referringTo == null) {
914 | if (rdx.getMnemonicString().equals("LEA") && rdx.getAddress(1) != null) {
915 | referringTo = rdx.getAddress(1);
916 | } else if (rcx.getMnemonicString().equals("LEA") && rcx.getAddress(1) != null) {
917 | referringTo = rcx.getAddress(1);
918 | } else {
919 | printerr("hmm0 " + callInsn + ", " + callInsn.getAddress());
920 | throw new IllegalStateException("wat");
921 | }
922 | }
923 |
924 | Reference[] references = getReferencesTo(referringTo.subtract(0x8));
925 | if (references.length != 1) {
926 | throw new IllegalStateException("What @ " + referringTo);
927 | }
928 |
929 | Function fn2 = getFunctionContaining(references[0].getFromAddress());
930 | Instruction callTo = null;
931 | for (Instruction insn : getFunctionInstructions(fn2)) {
932 | if (!insn.getMnemonicString().equals("CALL"))
933 | continue;
934 |
935 | if (callTo != null)
936 | throw new IllegalStateException("wot");
937 |
938 | callTo = insn;
939 | }
940 |
941 | Function fn3 = getFunctionAt(callTo.getAddress(0));
942 | Reference[] refs = getReferencesTo(fn3.getEntryPoint());
943 | if (((int) Stream.of(refs).filter(ref -> ref.getReferenceType().isCall()).count()) != NUM_PACKETS) {
944 | // if (getReferencesTo(fn3.getEntryPoint()).length != NUM_PACKETS) {
945 | printerr("invalid packet count " + callInsn + ", " + callInsn.getAddress() + " @ " + fn3.getEntryPoint());
946 | printerr("expected " + NUM_PACKETS + "packets, got " + (((int) Stream.of(refs).filter(ref -> ref.getReferenceType().isCall()).count())));
947 | return;
948 | }
949 |
950 | for (Reference ref : refs) {
951 | if (!ref.getReferenceType().isCall()) continue;
952 |
953 | Function regF = getFunctionContaining(ref.getFromAddress());
954 | RegisterTracker t = new RegisterTracker();
955 | Instruction b = getInstructionAt(regF.getEntryPoint());
956 | Instruction s = getInstructionAt(ref.getFromAddress());
957 | while (!b.equals(s)) {
958 | t.update(b);
959 | b = b.getNext();
960 | }
961 |
962 | List opcodeInsns = t.getRegisterValues("RDX");
963 | int opcode = -500;
964 | if (opcodeInsns.size() == 0) { // probably no need to do this check, but whatever it's 4am i am tired
965 | boolean xored = false;
966 |
967 | b = getInstructionAt(regF.getEntryPoint());
968 | s = getInstructionAt(ref.getFromAddress());
969 | while (!b.equals(s)) {
970 | b = b.getNext();
971 | if (b.getMnemonicString().equals("XOR") && b.getRegister(0).getBaseRegister().getName().equals("RDX") && b.getRegister(1).getBaseRegister().getName().equals("RDX")) {
972 | xored = true;
973 | }
974 | }
975 |
976 | if (!xored) throw new IllegalStateException("the fuck");
977 | opcode = 0;
978 | } else if (opcodeInsns.size() == 1 && opcodeInsns.get(0).getMnemonicString().equals("MOV")) {
979 | opcode = opcodeInsns.get(0).getInt(1);
980 | } else if (opcodeInsns.size() == 1 && opcodeInsns.get(0).getMnemonicString().equals("LEA") && opcodeInsns.get(0).getOpObjects(1).length == 2) {
981 | if (!opcodeInsns.get(0).getRegister(0).getBaseRegister().getName().equals("RDX"))
982 | throw new IllegalStateException("WHAT");
983 | if (!((Register) opcodeInsns.get(0).getOpObjects(1)[0]).getBaseRegister().getName().equals("R8"))
984 | throw new IllegalStateException("WHAT 2");
985 |
986 | List sizeInsns = t.getRegisterValues("R8");
987 | boolean xoredd = false;
988 |
989 | Instruction bs = getInstructionAt(regF.getEntryPoint());
990 | Instruction ss = getInstructionAt(ref.getFromAddress());
991 | while (!bs.equals(ss)) {
992 | bs = bs.getNext();
993 | if (bs.getMnemonicString().equals("XOR") && bs.getRegister(0).getBaseRegister().getName().equals("R8") && bs.getRegister(1).getBaseRegister().getName().equals("R8")) {
994 | xoredd = true;
995 | }
996 | }
997 |
998 | if (!xoredd) throw new IllegalStateException("the fuck " + sizeInsns);
999 | opcode = (int) (((Scalar) opcodeInsns.get(0).getOpObjects(1)[1]).getValue());
1000 | }
1001 |
1002 | List sizeInsns = t.getRegisterValues("R8");
1003 | int size = -500;
1004 | if (sizeInsns.size() == 0) { // probably no need to do this check, but whatever it's 4am i am tired
1005 | boolean xored = false;
1006 |
1007 | b = getInstructionAt(regF.getEntryPoint());
1008 | s = getInstructionAt(ref.getFromAddress());
1009 | while (!b.equals(s)) {
1010 | b = b.getNext();
1011 | if (b.getMnemonicString().equals("XOR") && b.getRegister(0).getBaseRegister().getName().equals("R8") && b.getRegister(1).getBaseRegister().getName().equals("R8")) {
1012 | xored = true;
1013 | }
1014 | }
1015 |
1016 | if (!xored) throw new IllegalStateException("the fuck " + sizeInsns);
1017 | size = 0;
1018 | } else if (sizeInsns.size() == 1 && sizeInsns.get(0).getMnemonicString().equals("MOV")) {
1019 | if (sizeInsns.get(0).getRegister(0).getBaseRegister().getName().equals("RDX")) {
1020 | size = opcode;
1021 | } else {
1022 | size = sizeInsns.get(0).getInt(2);
1023 | }
1024 | } else if (sizeInsns.size() == 1 && sizeInsns.get(0).getMnemonicString().equals("LEA") && sizeInsns.get(0).getOpObjects(1).length == 2) {
1025 | if (!sizeInsns.get(0).getRegister(0).getBaseRegister().getName().equals("R8"))
1026 | throw new IllegalStateException("WHAT");
1027 | if (!((Register) sizeInsns.get(0).getOpObjects(1)[0]).getBaseRegister().getName().equals("RDX"))
1028 | throw new IllegalStateException("WHAT 2");
1029 | size = (int) (opcode + ((Scalar) sizeInsns.get(0).getOpObjects(1)[1]).getValue());
1030 | }
1031 |
1032 | ServerProtInfo info = new ServerProtInfo();
1033 | info.opcode = opcode;
1034 | info.size = size;
1035 | info.addr = t.getRegisterValue("RCX").getAddress(1).add(8);
1036 | packets[opcode] = info;
1037 | }
1038 |
1039 | for (int i = 0; i < NUM_PACKETS; i++) {
1040 | if (packets[i] == null)
1041 | throw new IllegalStateException("i thought i had em all :( at " + i);
1042 | }
1043 |
1044 | HashSet a = new HashSet<>();
1045 | for (ServerProtInfo packet : packets) {
1046 | if (a.contains(packet.addr))
1047 | throw new IllegalStateException("REWRWE");
1048 | a.add(packet.addr);
1049 | }
1050 |
1051 | throw new Exception("yayeeeet");
1052 | }
1053 | }
1054 |
1055 | // okay welp time to scan insns
1056 | for (Instruction insn : getFunctionInstructions(fn)) {
1057 | tracker.update(insn);
1058 |
1059 | checkAndNameServerProt(insn);
1060 |
1061 | if (!insn.getMnemonicString().equals("CALL"))
1062 | continue;
1063 |
1064 | Address addr = insn.getAddress(0);
1065 |
1066 | boolean isRegister = (addr != null && serverProtReg1 != null && addr.equals(serverProtReg1.getEntryPoint()));
1067 | if (addr != null && (!visited.contains(addr) || isRegister)) {
1068 | if (!isRegister)
1069 | visited.add(addr);
1070 | refactorPacketsRecursive(insn, getFunctionAt(addr), visited, tracker.waistClone(), isRegister ? rdx : tracker.getRegisterValue("RCX"), tracker.getRegisterValue("RDX"));
1071 | }
1072 | }
1073 | }
1074 |
1075 | private void checkAndNameServerProt(Instruction insn) {
1076 | if (serverProtReg1 != null && insn.getAddress(1) != null) {
1077 | try {
1078 | ServerProtInfo info = serverProtFromAddress(insn.getAddress(1));
1079 | addresses.remove(insn.getAddress(1));
1080 | if (!info.done) {
1081 | info.done = true;
1082 | // println("before " + info);
1083 | info.name = PACKET_NAMES[packetNamesOffset++];
1084 |
1085 | if (insn.getRegister(0).getBaseRegister().getName().equals("RDX")) {
1086 | Instruction t = insn.getPrevious();
1087 | while (t.getAddress(1) == null || t.getAddress(1).getAddressSpace().isStackSpace()) {
1088 | t = t.getPrevious();
1089 | }
1090 | info.vtable = t.getAddress(1);
1091 | } else if (insn.getRegister(0).getBaseRegister().getName().equals("RCX")) {
1092 | Function f = getFunctionAt(insn.getNext().getAddress(0));
1093 | Instruction t = getInstructionAt(f.getEntryPoint());
1094 | while (t.getAddress(1) == null || t.getAddress(1).getAddressSpace().isStackSpace()) {
1095 | t = t.getNext();
1096 | }
1097 | info.vtable = t.getAddress(1);
1098 | } else {
1099 | throw new IllegalStateException();
1100 | }
1101 |
1102 | // println(" hmm " + insn.toString() + " @ " + insn.getAddress());
1103 |
1104 | // i know yayeet should be used here but whatever it's 4:05am
1105 | // StringBuilder handlerName = new StringBuilder();
1106 | // for (String s : info.name.toLowerCase(Locale.ROOT).split("_")) {
1107 | // handlerName.append(s.substring(0, 1).toUpperCase(Locale.ROOT)).append(s.substring(1).toUpperCase(Locale.ROOT));
1108 | // }
1109 |
1110 | // println("after " + info);
1111 | }
1112 | } catch (Exception e) {
1113 | }
1114 | }
1115 | }
1116 |
1117 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1118 | // UTILITIES SECTION //
1119 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1120 |
1121 | /**
1122 | * @param fn The function to rename
1123 | * @param name The new name of the function
1124 | * @throws DuplicateNameException [Document Ghidra exceptions here]
1125 | * @throws InvalidInputException [Document Ghidra exceptions here]
1126 | * @throws CircularDependencyException [Document Ghidra exceptions here]
1127 | */
1128 | private void renameFunction(Function fn, FullyQualifiedName name) throws DuplicateNameException, InvalidInputException, CircularDependencyException {
1129 | fn.setName(name.name, SourceType.USER_DEFINED);
1130 | fn.setParentNamespace(getOrCreateNamespace(name.namespace));
1131 |
1132 | printf("Renamed function at %s to '%s'%n", fn.getEntryPoint().toString(), name.toString());
1133 | }
1134 |
1135 | /**
1136 | * @param fn The function to rename
1137 | * @param name The new name of the function
1138 | * @throws DuplicateNameException [Document Ghidra exceptions here]
1139 | * @throws InvalidInputException [Document Ghidra exceptions here]
1140 | * @throws CircularDependencyException [Document Ghidra exceptions here]
1141 | */
1142 | private void renameFunction(Function fn, String name) throws DuplicateNameException, InvalidInputException, CircularDependencyException {
1143 | renameFunction(fn, new FullyQualifiedName(name));
1144 | }
1145 |
1146 | /**
1147 | * Sets a label at a point in the code
1148 | *
1149 | * @param address The address to apply the label to
1150 | * @param name The name of the label
1151 | * @throws DuplicateNameException [Document Ghidra exceptions here]
1152 | * @throws InvalidInputException [Document Ghidra exceptions here]
1153 | */
1154 | private void setLabel(Address address, FullyQualifiedName name) throws DuplicateNameException, InvalidInputException {
1155 | printf("Set label at %s to '%s'%n", address.toString(), name.toString());
1156 |
1157 | SymbolTable table = currentProgram.getSymbolTable();
1158 |
1159 | for (Symbol symbol : table.getSymbols(address)) {
1160 | if (symbol.getName().equals(name.name))
1161 | return;
1162 | }
1163 |
1164 | table.createLabel(address, name.name, getOrCreateNamespace(name.namespace), SourceType.USER_DEFINED);
1165 | }
1166 |
1167 | /**
1168 | * Sets a label at a point in the code
1169 | *
1170 | * @param address The address to apply the label to
1171 | * @param name The name of the label
1172 | * @throws DuplicateNameException [Document Ghidra exceptions here]
1173 | * @throws InvalidInputException [Document Ghidra exceptions here]
1174 | */
1175 | private void setLabel(Address address, String name) throws DuplicateNameException, InvalidInputException {
1176 | setLabel(address, new FullyQualifiedName(name));
1177 | }
1178 |
1179 | /**
1180 | * Gets a namespace from a string, or create it if it does not exist yet. This supports multi-level namespaces.
1181 | *
1182 | * @param name The name to convert into a namespace
1183 | * @return The namespace. If name is null, the global namespace will be returned.
1184 | * @throws DuplicateNameException [Document Ghidra exceptions here]
1185 | * @throws InvalidInputException [Document Ghidra exceptions here]
1186 | */
1187 | private Namespace getOrCreateNamespace(String name) throws DuplicateNameException, InvalidInputException {
1188 | if (name == null) {
1189 | return currentProgram.getGlobalNamespace();
1190 | }
1191 |
1192 | SymbolTable table = currentProgram.getSymbolTable();
1193 |
1194 | String[] path = name.split("::");
1195 | Namespace parent = currentProgram.getGlobalNamespace();
1196 | for (String s : path) {
1197 | Namespace child = table.getNamespace(s, parent);
1198 | if (child == null) {
1199 | child = table.createNameSpace(parent, s, SourceType.USER_DEFINED);
1200 | }
1201 | parent = child;
1202 | }
1203 |
1204 | return parent;
1205 | }
1206 |
1207 | /**
1208 | * Lists all instructions for the provided function.
1209 | *
1210 | * @param fn The function to list instructions for
1211 | * @return A list containing the instructions in the function. Modifying this list does not reflect on the function.
1212 | */
1213 | private List getFunctionInstructions(Function fn) {
1214 | List insns = new ArrayList<>();
1215 |
1216 | for (CodeUnit codeUnit : currentProgram.getListing().getCodeUnits(fn.getBody(), true)) {
1217 | Instruction insn = getInstructionAt(codeUnit.getAddress());
1218 | if (insn == null) {
1219 | // printf("Error: insn == null%n");
1220 | continue;
1221 | }
1222 |
1223 | insns.add(insn);
1224 | }
1225 |
1226 | return insns;
1227 | }
1228 |
1229 | /**
1230 | * Represents a full name space. There's probably support for this in Ghidra but oh well.
1231 | *
1232 | * +---------------------------------------+
1233 | * | some::long::path::to::a::FunctionName |
1234 | * | Namespace || Name |
1235 | * +------------------------++-------------+
1236 | */
1237 | public static class FullyQualifiedName {
1238 | public final String namespace;
1239 | public final String name;
1240 |
1241 | public FullyQualifiedName(String namespace, String name) {
1242 | this.namespace = namespace;
1243 | this.name = name;
1244 | }
1245 |
1246 | public FullyQualifiedName(String full) {
1247 | String[] split = full.split("::");
1248 |
1249 | if (split.length == 0) {
1250 | this.namespace = null;
1251 | this.name = full;
1252 | } else {
1253 | StringJoiner jnr = new StringJoiner("::");
1254 | for (int i = 0; i < split.length - 1; i++) {
1255 | jnr.add(split[i]);
1256 | }
1257 |
1258 | this.name = split[split.length - 1];
1259 | this.namespace = jnr.toString();
1260 | }
1261 | }
1262 |
1263 | @Override
1264 | public String toString() {
1265 | return namespace + "::" + name;
1266 | }
1267 | }
1268 |
1269 | public static RS3NXTRefactorer instance;
1270 |
1271 |
1272 | private static HashSet E = new HashSet<>();
1273 |
1274 | /**
1275 | * Tracks the instructions that were used to manipulate a register. This can be useful for certain applications
1276 | */
1277 | public static class RegisterTracker {
1278 | private HashMap> registerValues = new HashMap<>();
1279 | private Stack> stack = new Stack<>();
1280 |
1281 | /**
1282 | * Wipes all tracked registers
1283 | */
1284 | public void clear() {
1285 | registerValues.clear();
1286 | }
1287 |
1288 | /**
1289 | * Updates the register using ANY instruction.
1290 | *
1291 | * Function calls are not supported yet (RAX).
1292 | *
1293 | * If an instruction does not modify a register, this will do nothing. No exception will be thrown.
1294 | *
1295 | * @param insn Any instruction
1296 | */
1297 | public void update(Instruction insn) {
1298 | if (insn == null || ((insn.getRegisters().size() == 0 || insn.getRegister(0) == null) && !insn.getMnemonicString().equals("CALL")))
1299 | return;
1300 |
1301 | String registerName = insn.getMnemonicString().equals("CALL") ? "RAX" : insn.getRegister(0).getBaseRegister().getName();
1302 | List prior = registerValues.getOrDefault(registerName, new ArrayList<>());
1303 |
1304 | HashSet blegh = new HashSet() {{
1305 | add("SUB");
1306 | add("ADD");
1307 | add("XOR");
1308 | add("TEST");
1309 | add("CMP");
1310 | add("SETNZ");
1311 | add("AND");
1312 | add("ROR");
1313 | add("MOV");
1314 | add("MOVSXD");
1315 | add("SAR");
1316 | add("CMOVZ");
1317 | add("CMOVA");
1318 | add("DIV");
1319 | add("OR");
1320 | add("SBB");
1321 | add("MOVZX");
1322 | add("NEG");
1323 | add("INC");
1324 | add("IMUL");
1325 | add("DEC");
1326 | add("CMOVBE");
1327 | add("PUNPCKLBW");
1328 | add("JMP");
1329 | add("ROL");
1330 | add("SHR");
1331 | add("MOVQ");
1332 | }};
1333 | if (insn.getMnemonicString().equals("PUSH")) {
1334 | prior = registerValues.remove(registerName);
1335 | if (prior == null) prior = new ArrayList<>();
1336 | stack.push(prior);
1337 | } else if (insn.getMnemonicString().equals("POP")) {
1338 | registerValues.put(registerName, stack.pop());
1339 | } else if (insn.getMnemonicString().equals("LEA")) {
1340 | List list = new ArrayList<>();
1341 | list.add(insn);
1342 | registerValues.put(registerName, list);
1343 | } else if (blegh.contains(insn.getMnemonicString())) {
1344 | List src;
1345 | if (insn.getRegister(1) != null)
1346 | src = registerValues.getOrDefault(insn.getRegister(1).getBaseRegister().getName(), new ArrayList<>());
1347 | else {
1348 | src = new ArrayList<>();
1349 | src.add(insn);
1350 | }
1351 | registerValues.put(registerName, src);
1352 | } else if (insn.getMnemonicString().equals("CALL")) {
1353 | List list = new ArrayList<>();
1354 | list.add(insn);
1355 | registerValues.put("RAX", list);
1356 | } else if (insn.getMnemonicString().equals("JMP")) {
1357 | // ignored
1358 | } else {
1359 | E.add(insn.getMnemonicString());
1360 |
1361 |
1362 | List src;
1363 | if (insn.getRegister(1) != null)
1364 | src = registerValues.getOrDefault(insn.getRegister(1).getBaseRegister().getName(), new ArrayList<>());
1365 | else {
1366 | src = new ArrayList<>();
1367 | src.add(insn);
1368 | }
1369 | registerValues.put(registerName, src);
1370 | // throw new IllegalStateException("? " + registerName + " @ " + insn.getAddress() + ": " + insn);
1371 | }
1372 |
1373 | // registerValues.put(registerName, prior);
1374 | }
1375 |
1376 | /**
1377 | * @param register The register to check
1378 | * @return The last instruction that modified the register. May be null.
1379 | */
1380 | public Instruction getRegisterValue(String register) {
1381 | List insn = registerValues.get(register);
1382 | if (insn == null || insn.isEmpty()) return null;
1383 | return registerValues.get(register).get(0);
1384 | }
1385 |
1386 | public List getRegisterValues(String register) {
1387 | return registerValues.getOrDefault(register, new ArrayList<>());
1388 | }
1389 |
1390 | /**
1391 | * @return A semi-deep clone of this tracker, instructions are not deep-cloned.
1392 | */
1393 | public RegisterTracker waistClone() { // haha shallow is feet, deep is head-under, waist is in-between l0l
1394 | RegisterTracker clone = new RegisterTracker();
1395 | clone.stack.addAll(stack);
1396 | registerValues.forEach((k, v) -> {
1397 | List list = new ArrayList<>(v);
1398 | clone.registerValues.put(k, list);
1399 | });
1400 | return clone;
1401 | }
1402 | }
1403 |
1404 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1405 | // DATA TYPES //
1406 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1407 |
1408 | /**
1409 | * Initializes default/builtin data types that we use
1410 | */
1411 | private void initDefaultDataTypes() {
1412 | Types.LONGLONG = getDataType("/longlong");
1413 | Types.BOOL = getDataType("/bool");
1414 | Types.VOID = getDataType("/void");
1415 | Types.UINT = getDataType("/uint");
1416 | Types.INT = getDataType("/int");
1417 | Types.BYTE = getDataType("/byte");
1418 | }
1419 |
1420 | /**
1421 | * Gets the data type according to Ghidra's path, throwing a NPE if it could not be found.
1422 | *
1423 | * @param path The path of the data type. By default this would be "/path/to/data/type/name"
1424 | * @return The data type
1425 | * @throws NullPointerException If said data type could not be found
1426 | */
1427 | private DataType getDataType(String path) throws NullPointerException {
1428 | DataType type = currentProgram.getDataTypeManager().getDataType(path);
1429 |
1430 | if (type == null) {
1431 | throw new NullPointerException("DataType: " + path);
1432 | }
1433 |
1434 | return type;
1435 | }
1436 |
1437 | /**
1438 | * @return A pointer to the data type
1439 | */
1440 | private DataType ptr(DataType type) {
1441 | return currentProgram.getDataTypeManager().getPointer(type);
1442 | }
1443 |
1444 | /**
1445 | * Creates a new array data type
1446 | *
1447 | * @param type The type of the elements in this array
1448 | * @param arraySize The amount of elements in this array
1449 | * @param elementLength The size of each element. For pointers, this would be 8, for ints, this would be 4, byte 1..
1450 | * @return The newly created array data type.
1451 | */
1452 | private DataType arr(DataType type, int arraySize, int elementLength) {
1453 | return new ArrayDataType(type, arraySize, elementLength, currentProgram.getDataTypeManager());
1454 | }
1455 |
1456 | /**
1457 | * Gets the class, or creates the class
1458 | *
1459 | * @param name The name of the class
1460 | * @return The class
1461 | * @throws DuplicateNameException [Document Ghidra exceptions here]
1462 | * @throws InvalidInputException [Document Ghidra exceptions here]
1463 | * @throws IllegalStateException If the existing namespace is not a class
1464 | */
1465 | private GhidraClass getOrCreateClass(String name) throws DuplicateNameException, InvalidInputException {
1466 | return getOrCreateClass(new FullyQualifiedName(name));
1467 | }
1468 |
1469 | /**
1470 | * Gets the class, or creates the class
1471 | *
1472 | * @param name The name of the class
1473 | * @return The class
1474 | * @throws DuplicateNameException [Document Ghidra exceptions here]
1475 | * @throws InvalidInputException [Document Ghidra exceptions here]
1476 | * @throws IllegalStateException If the existing namespace is not a class
1477 | */
1478 | private GhidraClass getOrCreateClass(FullyQualifiedName name) throws DuplicateNameException, InvalidInputException {
1479 | SymbolTable table = currentProgram.getSymbolTable();
1480 |
1481 | Namespace parent = getOrCreateNamespace(name.namespace);
1482 |
1483 | Namespace existing = table.getNamespace(name.name, parent);
1484 | if (existing == null) {
1485 | return table.createClass(parent, name.name, SourceType.USER_DEFINED);
1486 | }
1487 |
1488 | if (!(existing instanceof GhidraClass)) {
1489 | throw new IllegalStateException("expected class, got namespace for " + name);
1490 | }
1491 |
1492 | return (GhidraClass) existing;
1493 | }
1494 |
1495 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1496 | // STRUCTURES //
1497 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1498 |
1499 | /**
1500 | * Resizes a structure. This will throw an exception if the structure is bigger than the size.
1501 | *
1502 | * @param struct The struct to resize
1503 | * @param size The desired size of the struct
1504 | * @throws NullPointerException If struct is null
1505 | * @throws IllegalStateException If the struct size is bigger than what it should be
1506 | */
1507 | private void resizeStructure(Structure struct, int size) throws NullPointerException {
1508 | String fullPath = (struct.getCategoryPath().toString() + "::" + struct.getName()).replaceAll("/", "::").substring(2);
1509 |
1510 | if (struct.getLength() < size) {
1511 | int growBy = size - struct.getLength();
1512 |
1513 | printf("Growing struct '%s' size from %d to %d (+%d bytes)%n", fullPath, struct.getLength(), size, growBy);
1514 |
1515 | struct.growStructure(growBy);
1516 |
1517 | if (struct.getLength() < size) {
1518 | struct.growStructure(size - struct.getLength());
1519 | }
1520 | } else if (struct.getLength() > size) {
1521 | throw new IllegalStateException("Structure '" + fullPath + "' data structure too big: " + struct.getLength() + ", expected: " + size);
1522 | } else {
1523 | printf("Structure '%s' size already optimal! (=%d bytes)%n", fullPath, struct.getLength());
1524 | }
1525 | }
1526 |
1527 | /**
1528 | * Gets the structure of a class. If the structure does not exist, it will create a new, empty, structure.
1529 | *
1530 | * @param clazz The class to get the structure for
1531 | * @return The structure of the class
1532 | * @throws IllegalStateException If the existing data type is not an instance of {@link Structure} or the data type
1533 | * was not found and could not be created
1534 | */
1535 | private Structure getStructureForClass(GhidraClass clazz) {
1536 | CategoryPath path = new CategoryPath(CategoryPath.ROOT, clazz.getName(true).split("::"));
1537 |
1538 | DataType type = currentProgram.getDataTypeManager().getDataType(path.getParent(), path.getName());
1539 |
1540 | if (type == null) {
1541 | printf("Created new data type/structure: %s%n", path.toString());
1542 | currentProgram.getDataTypeManager().addDataType(new StructureDataType(path.getParent(), path.getName(), 0, currentProgram.getDataTypeManager()), DataTypeConflictHandler.DEFAULT_HANDLER);
1543 |
1544 | type = currentProgram.getDataTypeManager().getDataType(path.getParent(), path.getName());
1545 | if (type == null) {
1546 | throw new IllegalStateException("no DataType found for class " + clazz.getName(true));
1547 | }
1548 | }
1549 |
1550 | if (!(type instanceof Structure)) {
1551 | throw new IllegalStateException("class DataType is not instance of Structure " + clazz.getName(true) + ", but of " + type.getClass().getSimpleName());
1552 | }
1553 |
1554 | return (Structure) type;
1555 | }
1556 |
1557 | private int packetNamesOffset = 0;
1558 | private static final String[] PACKET_NAMES = new String[]{
1559 | /* Animations */
1560 | "LOC_ANIM_SPECIFIC",
1561 | "PROJANIM_SPECIFIC",
1562 | "SPOTANIM_SPECIFIC",
1563 | "NPC_ANIM_SPECIFIC",
1564 | "RESET_ANIMS",
1565 | "SERVER_TICK_END",
1566 |
1567 | /* Audio */
1568 | "SYNTH_SOUND",
1569 | "VORBIS_SOUND",
1570 | "VORBIS_SPEECH_SOUND",
1571 | "VORBIS_SPEECH_STOP",
1572 | "VORBIS_PRELOAD_SOUNDS",
1573 | "VORBIS_SOUND_GROUP",
1574 | "VORBIS_SOUND_GROUP_START",
1575 | "VORBIS_SOUND_GROUP_STOP",
1576 | "VORBIS_PRELOAD_SOUND_GROUP",
1577 | "SOUND_MIXBUSS_ADD",
1578 | "SOUND_MIXBUSS_SETLEVEL",
1579 | "MIDI_SONG",
1580 | "MIDI_SONG_STOP",
1581 | "MIDI_SONG_LOCATION",
1582 | "MIDI_JINGLE",
1583 | "SONG_PRELOAD",
1584 |
1585 | /* Camera */
1586 | "CAMERA_UPDATE",
1587 | "CAM2_ENABLE",
1588 | "CAM_RESET",
1589 | "CAM_FORCEANGLE",
1590 | "CAM_MOVETO",
1591 | "CAM_LOOKAT",
1592 | "CAM_SMOOTHRESET",
1593 | "CAM_SHAKE",
1594 | "CAM_REMOVEROOF",
1595 | "CUTSCENE",
1596 |
1597 | /* Chat */
1598 | "MESSAGE_PUBLIC",
1599 | "MESSAGE_GAME",
1600 | "CHAT_FILTER_SETTINGS",
1601 | "MESSAGE_PRIVATE",
1602 | "MESSAGE_PRIVATE_ECHO",
1603 | "MESSAGE_FRIENDCHANNEL",
1604 | "MESSAGE_CLANCHANNEL",
1605 | "MESSAGE_CLANCHANNEL_SYSTEM",
1606 | "MESSAGE_QUICKCHAT_PRIVATE_ECHO",
1607 | "MESSAGE_QUICKCHAT_PRIVATE",
1608 | "MESSAGE_QUICKCHAT_FRIENDCHAT",
1609 | "MESSAGE_QUICKCHAT_CLANCHANNEL",
1610 | "MESSAGE_PLAYER_GROUP",
1611 | "MESSAGE_QUICKCHAT_PLAYER_GROUP",
1612 |
1613 | /* Clans */
1614 | "CLANSETTINGS_FULL",
1615 | "CLANSETTINGS_DELTA",
1616 | "CLANCHANNEL_FULL",
1617 | "CLANCHANNEL_DELTA",
1618 |
1619 | /* ClientState */
1620 | "LOGOUT",
1621 | "LOGOUT_FULL",
1622 | "LOGOUT_TRANSFER",
1623 | "REBUILD_REGION",
1624 | "REBUILD_NORMAL",
1625 | "SET_MOVEACTION",
1626 | "SET_MAP_FLAG",
1627 | "RUNCLIENTSCRIPT",
1628 | "UPDATE_REBOOT_TIMER",
1629 | "JCOINS_UPDATE",
1630 | "LOYALTY_UPDATE",
1631 |
1632 | /* Debug */
1633 | "DEBUG_SERVER_TRIGGERS",
1634 | "CONSOLE_FEEDBACK",
1635 |
1636 | /* Environment */
1637 | "ENVIRONMENT_OVERRIDE",
1638 | "POINTLIGHT_COLOUR",
1639 | "_UNKNOWN1_",
1640 |
1641 | /* Friend Chat */
1642 | "UPDATE_FRIENDCHAT_CHANNEL_FULL",
1643 | "UPDATE_FRIENDCHAT_CHANNEL_SINGLEUSER",
1644 |
1645 | /* Friends */
1646 | "UPDATE_FRIENDLIST",
1647 | "FRIENDLIST_LOADED",
1648 | "CHAT_FILTER_SETTINGS_PRIVATECHAT",
1649 |
1650 | /* Hint */
1651 | "HINT_ARROW",
1652 | "HINT_TRAIL",
1653 |
1654 | /* Ignores */
1655 | "UPDATE_IGNORELIST",
1656 |
1657 | /* Interfaces */
1658 | "IF_SETPOSITION",
1659 | "IF_SETSCROLLPOS",
1660 | "IF_OPENTOP",
1661 | "IF_OPENSUB",
1662 | "IF_OPENSUB_ACTIVE_PLAYER",
1663 | "IF_OPENSUB_ACTIVE_NPC",
1664 | "IF_OPENSUB_ACTIVE_LOC",
1665 | "IF_OPENSUB_ACTIVE_OBJ",
1666 | "IF_CLOSESUB",
1667 | "IF_MOVESUB",
1668 | "IF_SETEVENTS",
1669 | "IF_SETTARGETPARAM",
1670 | "IF_SETTEXT",
1671 | "IF_SETHIDE",
1672 | "IF_SETGRAPHIC",
1673 | "IF_SET_HTTP_IMAGE",
1674 | "IF_SETPLAYERMODEL_OTHER",
1675 | "IF_SETPLAYERMODEL_SELF",
1676 | "IF_SETPLAYERMODEL_SNAPSHOT",
1677 | "IF_SETMODEL",
1678 | "IF_SETANIM",
1679 | "IF_SETNPCHEAD",
1680 | "IF_SETPLAYERHEAD",
1681 | "IF_SETPLAYERHEAD_OTHER",
1682 | "IF_SETPLAYERHEAD_IGNOREWORN",
1683 | "IF_SETOBJECT",
1684 | "IF_SETTEXTFONT",
1685 | "IF_SETCOLOUR",
1686 | "IF_SETRECOL",
1687 | "IF_SETRETEX",
1688 | "IF_SETCLICKMASK",
1689 | "IF_SETTEXTANTIMACRO",
1690 | "TRIGGER_ONDIALOGABORT",
1691 | "IF_SETANGLE",
1692 | "UNKNOWN_IF_1",
1693 | "UNKONWN_IF_2_930",
1694 | "UNKONWN_IF_3_930",
1695 |
1696 | /* Inventories */
1697 | "UPDATE_INV_PARTIAL",
1698 | "UPDATE_INV_FULL",
1699 | "UPDATE_INV_STOP_TRANSMIT",
1700 | "UPDATE_STOCKMARKET_SLOT",
1701 |
1702 | /* Lobby */
1703 | "NO_TIMEOUT",
1704 | "CREATE_CHECK_EMAIL_REPLY",
1705 | "CREATE_ACCOUNT_REPLY",
1706 | "CREATE_CHECK_NAME_REPLY",
1707 | "CREATE_SUGGEST_NAME_ERROR",
1708 | "CREATE_SUGGEST_NAME_REPLY",
1709 | "LOBBY_APPEARANCE",
1710 | "CHANGE_LOBBY",
1711 |
1712 | /* Misc */
1713 | "SEND_PING",
1714 | "MINIMAP_TOGGLE",
1715 | "SHOW_FACE_HERE",
1716 | "EXECUTE_CLIENT_CHEAT",
1717 | "DO_CHEAT",
1718 | "SETDRAWORDER",
1719 | "JS5_RELOAD",
1720 | "WORLDLIST_FETCH_REPLY",
1721 |
1722 | /* NPC Info */
1723 | "NPC_INFO",
1724 | "NPC_HEADICON_SPECIFIC",
1725 |
1726 | /* Player Groups */
1727 | "PLAYER_GROUP_FULL",
1728 | "PLAYER_GROUP_DELTA",
1729 | "PLAYER_GROUP_VARPS",
1730 |
1731 | /* Player Info */
1732 | "LAST_LOGIN_INFO",
1733 | "PLAYER_INFO",
1734 | "SET_PLAYER_OP",
1735 | "UPDATE_RUNENERGY",
1736 | "UPDATE_RUNWEIGHT",
1737 | "UPDATE_UID192",
1738 | "SET_TARGET",
1739 | "REDUCE_PLAYER_ATTACK_PRIORITY",
1740 | "REDUCE_NPC_ATTACK_PRIORITY",
1741 | "PLAYER_SNAPSHOT",
1742 | "CLEAR_PLAYER_SNAPSHOT",
1743 | "UPDATE_DOB",
1744 |
1745 | /* Server Reply */
1746 | "SERVER_REPLY",
1747 |
1748 | /* Telemetry */
1749 | "TELEMETRY_GRID_FULL",
1750 | "TELEMETRY_GRID_VALUES_DELTA",
1751 | "TELEMETRY_GRID_ADD_GROUP",
1752 | "TELEMETRY_GRID_REMOVE_GROUP",
1753 | "TELEMETRY_GRID_ADD_ROW",
1754 | "TELEMETRY_GRID_REMOVE_ROW",
1755 | "TELEMETRY_GRID_SET_ROW_PINNED",
1756 | "TELEMETRY_GRID_MOVE_ROW",
1757 | "TELEMETRY_GRID_ADD_COLUMN",
1758 | "TELEMETRY_GRID_REMOVE_COLUMN",
1759 | "TELEMETRY_GRID_MOVE_COLUMN",
1760 | "TELEMETRY_CLEAR_GRID_VALUE",
1761 |
1762 | /* Variables */
1763 | "RESET_CLIENT_VARCACHE",
1764 | "VARP_SMALL",
1765 | "VARP_LARGE",
1766 | "VARBIT_SMALL",
1767 | "VARBIT_LARGE",
1768 | "CLIENT_SETVARC_SMALL",
1769 | "CLIENT_SETVARC_LARGE",
1770 | "CLIENT_SETVARCBIT_SMALL",
1771 | "CLIENT_SETVARCBIT_LARGE",
1772 | "CLIENT_SETVARCSTR_SMALL",
1773 | "CLIENT_SETVARCSTR_LARGE",
1774 | "STORE_SERVERPERM_VARCS_ACK",
1775 | "VARCLAN_DISABLE",
1776 | "VARCLAN_ENABLE",
1777 | "VARCLAN",
1778 | "UPDATE_STAT",
1779 | "UNKNOWN_VAR_1_930",
1780 | "UNKNOWN_VAR_2_930",
1781 |
1782 | /* Web Page */
1783 | "UPDATE_SITESETTINGS",
1784 | "URL_OPEN",
1785 | "SOCIAL_NETWORK_LOGOUT",
1786 |
1787 | /* Zone Updates */
1788 | "UPDATE_ZONE_PARTIAL_FOLLOWS",
1789 | "UPDATE_ZONE_FULL_FOLLOWS",
1790 | "UPDATE_ZONE_PARTIAL_ENCLOSED",
1791 | "LOC_ADD_CHANGE",
1792 | "LOC_CUSTOMISE",
1793 | "LOC_DEL",
1794 | "LOC_ANIM",
1795 | "MAP_PROJANIM",
1796 | "MAP_PROJANIM_HALFSQ",
1797 | "MAP_ANIM",
1798 | "OBJ_ADD",
1799 | "OBJ_DEL",
1800 | "OBJ_REVEAL",
1801 | "OBJ_COUNT",
1802 | "SOUND_AREA",
1803 | "____WAT____",
1804 | "LOC_PREFETCH",
1805 | "TEXT_COORD"
1806 | };
1807 |
1808 | public static final int NUM_PACKETS = 200;
1809 |
1810 | private ServerProtInfo[] packets = new ServerProtInfo[NUM_PACKETS];
1811 |
1812 | static class ServerProtInfo {
1813 | boolean done = false;
1814 | public int opcode;
1815 | public int size;
1816 | public String name;
1817 | public Address addr;
1818 | public Address vtable;
1819 |
1820 | @Override
1821 | public String toString() {
1822 | return "ServerProt[opcode=" + opcode + ", size=" + size + ", name=" + name + ", addr= " + addr + " ]";
1823 | }
1824 | }
1825 | }
1826 |
--------------------------------------------------------------------------------