├── .golangci.yml ├── LICENSE ├── README.md ├── cache.go ├── cmd ├── dextk-tools │ └── main.go └── example │ └── main.go ├── code.go ├── flags.go ├── go.mod ├── go.sum ├── internal ├── decompiler │ ├── decompiler.go │ └── flags.go ├── gen │ ├── main.go │ └── ops.go └── java │ ├── class.go │ ├── field.go │ ├── flags.go │ ├── method.go │ ├── type.go │ └── writer.go ├── iter.go ├── op.gen.go ├── op.go ├── op_fmt.go ├── op_pseudo.go ├── op_reader.go ├── opts.go ├── reader.go ├── types.go ├── utils.go └── utils_test.go /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable-all: true 3 | disable: 4 | - nosnakecase 5 | - interfacer 6 | - deadcode 7 | - exhaustivestruct 8 | - ifshort 9 | - structcheck 10 | - golint 11 | - varcheck 12 | - maligned 13 | - scopelint 14 | - structcheck 15 | - gomnd 16 | - varnamelen 17 | - exhaustruct 18 | - gocognit 19 | - wrapcheck 20 | - nestif 21 | - cyclop 22 | - ireturn 23 | - bodyclose 24 | - godox 25 | - depguard 26 | linters-settings: 27 | gocritic: 28 | disabled-checks: 29 | - ifElseChain 30 | funlen: 31 | lines: 160 32 | statements: 120 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Chandler Newman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dextk 2 | Android dex file parser in Go 3 | 4 | ### Features 5 | 6 | - String, classes and code parsing 7 | - Low memory usage 8 | - Mmap support 9 | - Designed to support stream processing (No requirement to load entire dex file into memory) 10 | 11 | ### Example 12 | 13 | ```bash 14 | go run ./cmd/example 15 | ``` 16 | 17 | ```go 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "log" 23 | 24 | "github.com/csnewman/dextk" 25 | "golang.org/x/exp/mmap" 26 | ) 27 | 28 | func main() { 29 | f, err := mmap.Open("classes.dex") 30 | if err != nil { 31 | log.Panicln(err) 32 | } 33 | 34 | defer f.Close() 35 | 36 | r, err := dextk.Read(f) 37 | if err != nil { 38 | log.Panicln(err) 39 | } 40 | 41 | ci := r.ClassIter() 42 | for ci.HasNext() { 43 | node, err := ci.Next() 44 | if err != nil { 45 | log.Panicln(err) 46 | } 47 | 48 | for _, method := range node.DirectMethods { 49 | fmt.Println(node.Name, ": ", method.Name) 50 | processMeth(r, method) 51 | } 52 | 53 | for _, method := range node.VirtualMethods { 54 | fmt.Println(node.Name, ": ", method.Name) 55 | processMeth(r, method) 56 | } 57 | } 58 | } 59 | 60 | func processMeth(r *dextk.Reader, m dextk.MethodNode) { 61 | if m.CodeOff == 0 { 62 | return 63 | } 64 | 65 | c, err := r.ReadCodeAndParse(m.CodeOff) 66 | if err != nil { 67 | log.Panic(err) 68 | } 69 | 70 | for _, o := range c.Ops { 71 | fmt.Println(" ", o) 72 | } 73 | } 74 | ``` 75 | 76 | Output: 77 | 78 | ``` 79 | android/support/v4/app/INotificationSideChannel$_Parcel : 80 | invoke-direct method=java/lang/Object::([]):V args=[0] 81 | return-void value=-1 82 | [...] 83 | android/support/v4/app/INotificationSideChannel$_Parcel : readTypedObject 84 | invoke-virtual method=android/os/Parcel:readInt:([]):I args=[1] 85 | move-result dst=0 86 | if-eqz a=0 b=-1 tgt=6 87 | invoke-interface method=android/os/Parcelable$Creator:createFromParcel:([Landroid/os/Parcel;]):Ljava/lang/Object; args=[2 1] 88 | move-result-object dst=0 89 | return-object value=0 90 | const/4 dst=0 value='0' 91 | return-object value=0 92 | [...] 93 | ``` -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | const ( 8 | cacheShift = 12 9 | cacheSize = 1 << cacheShift 10 | cacheMask = cacheSize - 1 11 | ) 12 | 13 | type cachedReader struct { 14 | slots int 15 | store []byte 16 | mapping []int64 17 | lengths []int 18 | r io.ReaderAt 19 | } 20 | 21 | func newCachedReader(r io.ReaderAt, slots int) *cachedReader { 22 | store := make([]byte, cacheSize*slots) 23 | mappings := make([]int64, slots) 24 | lengths := make([]int, slots) 25 | 26 | for i := 0; i < slots; i++ { 27 | mappings[i] = -1 28 | } 29 | 30 | return &cachedReader{ 31 | slots, 32 | store, 33 | mappings, 34 | lengths, 35 | r, 36 | } 37 | } 38 | 39 | func (r *cachedReader) ReadAt(tgt []byte, pos int64) (int, error) { 40 | remaining := len(tgt) 41 | cur := 0 42 | 43 | for remaining > 0 { 44 | chunk := pos >> cacheShift 45 | inner := int(pos & cacheMask) 46 | 47 | data, err := r.getChunk(chunk) 48 | if err != nil { 49 | return len(tgt) - remaining, err 50 | } 51 | 52 | rsize := len(data) - inner 53 | if rsize > remaining { 54 | rsize = remaining 55 | } 56 | 57 | copy(tgt[cur:cur+rsize], data[inner:inner+rsize]) 58 | 59 | remaining -= rsize 60 | cur += rsize 61 | pos += int64(rsize) 62 | 63 | if remaining > 0 && rsize != cacheSize-inner { 64 | return len(tgt) - remaining, io.EOF 65 | } 66 | } 67 | 68 | if remaining > 0 { 69 | panic("remaining != 0") 70 | } 71 | 72 | return len(tgt), nil 73 | } 74 | 75 | func (r *cachedReader) getChunk(chunk int64) ([]byte, error) { 76 | slot := chunk % int64(r.slots) 77 | 78 | slotStart := int(slot << cacheShift) 79 | slotEnd := slotStart + cacheSize 80 | 81 | if r.mapping[slot] == chunk { 82 | l := r.lengths[slot] 83 | 84 | return r.store[slotStart : slotStart+l], nil 85 | } 86 | 87 | data := r.store[slotStart:slotEnd] 88 | 89 | size, err := r.r.ReadAt(data, chunk<> 4 << 4 570 | } 571 | 572 | conv := int32(val) 573 | 574 | parsed = ConstOpNode{ 575 | Raw: ot, 576 | Dst: data.A, 577 | Value: conv, 578 | } 579 | case OpConst16: 580 | data := (ot.Fmt()).(Fmt21s) 581 | 582 | val := uint32(data.B) 583 | if (val & (1 << 15)) != 0 { 584 | val |= math.MaxUint32 >> 16 << 16 585 | } 586 | 587 | conv := int32(val) 588 | 589 | parsed = ConstOpNode{ 590 | Raw: ot, 591 | Dst: data.A, 592 | Value: conv, 593 | } 594 | case OpConst: 595 | data := (ot.Fmt()).(Fmt31i) 596 | conv := int32(data.B) 597 | 598 | parsed = ConstOpNode{ 599 | Raw: ot, 600 | Dst: data.A, 601 | Value: conv, 602 | } 603 | case OpConstHigh16: 604 | data := (ot.Fmt()).(Fmt21h) 605 | conv := int32(uint32(data.B) << 16) 606 | 607 | parsed = ConstOpNode{ 608 | Raw: ot, 609 | Dst: data.A, 610 | Value: conv, 611 | } 612 | case OpConstWide16: 613 | data := (ot.Fmt()).(Fmt21s) 614 | 615 | val := uint64(data.B) 616 | if (val & (1 << 15)) != 0 { 617 | val |= math.MaxUint64 >> 16 << 16 618 | } 619 | 620 | conv := int64(val) 621 | 622 | parsed = ConstOpNode{ 623 | Raw: ot, 624 | Dst: data.A, 625 | Value: conv, 626 | } 627 | case OpConstWide32: 628 | data := (ot.Fmt()).(Fmt31i) 629 | 630 | val := uint64(data.B) 631 | if (val & (1 << 31)) != 0 { 632 | val |= math.MaxUint64 >> 32 << 32 633 | } 634 | 635 | conv := int64(val) 636 | 637 | parsed = ConstOpNode{ 638 | Raw: ot, 639 | Dst: data.A, 640 | Value: conv, 641 | } 642 | case OpConstWide: 643 | data := (ot.Fmt()).(Fmt51l) 644 | conv := int64(data.B) 645 | 646 | parsed = ConstOpNode{ 647 | Raw: ot, 648 | Dst: data.A, 649 | Value: conv, 650 | } 651 | case OpConstWideHigh16: 652 | data := (ot.Fmt()).(Fmt21h) 653 | conv := int64(uint64(data.B) << 48) 654 | 655 | parsed = ConstOpNode{ 656 | Raw: ot, 657 | Dst: data.A, 658 | Value: conv, 659 | } 660 | case OpConstString: 661 | data := (ot.Fmt()).(Fmt21c) 662 | val, err := r.ReadString(uint32(data.B)) 663 | if err != nil { 664 | return res, err 665 | } 666 | 667 | parsed = ConstOpNode{ 668 | Raw: ot, 669 | Dst: data.A, 670 | Value: val, 671 | } 672 | case OpConstStringJumbo: 673 | data := (ot.Fmt()).(Fmt31c) 674 | val, err := r.ReadString(data.B) 675 | if err != nil { 676 | return res, err 677 | } 678 | 679 | parsed = ConstOpNode{ 680 | Raw: ot, 681 | Dst: data.A, 682 | Value: val, 683 | } 684 | case OpConstClass: 685 | data := (ot.Fmt()).(Fmt21c) 686 | val, err := r.ReadTypeAndParse(uint32(data.B)) 687 | if err != nil { 688 | return res, err 689 | } 690 | 691 | parsed = ConstOpNode{ 692 | Raw: ot, 693 | Dst: data.A, 694 | Value: val, 695 | } 696 | case OpMonitorEnter: 697 | data := (ot.Fmt()).(Fmt11x) 698 | parsed = MonitorOpNode{ 699 | Raw: ot, 700 | Ref: data.A, 701 | Enter: true, 702 | } 703 | case OpMonitorExit: 704 | data := (ot.Fmt()).(Fmt11x) 705 | parsed = MonitorOpNode{ 706 | Raw: ot, 707 | Ref: data.A, 708 | Enter: false, 709 | } 710 | case OpCheckCast: 711 | data := (ot.Fmt()).(Fmt21c) 712 | val, err := r.ReadTypeAndParse(uint32(data.B)) 713 | if err != nil { 714 | return res, err 715 | } 716 | 717 | parsed = CheckCastOpNode{ 718 | Raw: ot, 719 | Ref: data.A, 720 | Type: val, 721 | } 722 | case OpInstanceOf: 723 | data := (ot.Fmt()).(Fmt22c) 724 | val, err := r.ReadTypeAndParse(uint32(data.C)) 725 | if err != nil { 726 | return res, err 727 | } 728 | 729 | parsed = InstanceOfOpNode{ 730 | Raw: ot, 731 | Dst: data.A, 732 | Src: data.B, 733 | Type: val, 734 | } 735 | case OpArrayLength: 736 | data := (ot.Fmt()).(Fmt12x) 737 | 738 | parsed = ArrayLenOpNode{ 739 | Raw: ot, 740 | Dst: data.A, 741 | Ref: data.B, 742 | } 743 | case OpNewInstance: 744 | data := (ot.Fmt()).(Fmt21c) 745 | 746 | parsedDesc, err := r.ReadTypeAndParse(uint32(data.B)) 747 | if err != nil { 748 | return res, fmt.Errorf("bad class type: %w", err) 749 | } 750 | 751 | if !parsedDesc.IsClass() || parsedDesc.IsArray() { 752 | return res, fmt.Errorf("%w: invalid descriptor", ErrBadOp) 753 | } 754 | 755 | parsed = NewInstanceOpNode{ 756 | Raw: ot, 757 | Dst: data.A, 758 | Type: parsedDesc.ClassName, 759 | } 760 | case OpNewArray: 761 | data := (ot.Fmt()).(Fmt22c) 762 | 763 | parsedDesc, err := r.ReadTypeAndParse(uint32(data.C)) 764 | if err != nil { 765 | return res, fmt.Errorf("bad class type: %w", err) 766 | } 767 | 768 | if !parsedDesc.IsArray() { 769 | return res, fmt.Errorf("%w: %v: invalid descriptor", ErrBadOp, ot) 770 | } 771 | 772 | parsed = NewArrayOpNode{ 773 | Raw: ot, 774 | Dst: data.A, 775 | SizeReg: data.B, 776 | Type: parsedDesc, 777 | } 778 | case OpFilledNewArray: 779 | data := (ot.Fmt()).(Fmt35c) 780 | 781 | parsedDesc, err := r.ReadTypeAndParse(uint32(data.B)) 782 | if err != nil { 783 | return res, fmt.Errorf("bad class type: %w", err) 784 | } 785 | 786 | if !parsedDesc.IsArray() { 787 | return res, fmt.Errorf("%w: %v: invalid descriptor", ErrBadOp, ot) 788 | } 789 | 790 | values := make([]uint16, data.A) 791 | 792 | if data.A >= 1 { 793 | values[0] = uint16(data.C) 794 | } 795 | 796 | if data.A >= 2 { 797 | values[1] = uint16(data.D) 798 | } 799 | 800 | if data.A >= 3 { 801 | values[2] = uint16(data.E) 802 | } 803 | 804 | if data.A >= 4 { 805 | values[3] = uint16(data.F) 806 | } 807 | 808 | if data.A >= 5 { 809 | values[4] = uint16(data.G) 810 | } 811 | 812 | parsed = FilledNewArrayOpNode{ 813 | Raw: ot, 814 | Type: parsedDesc, 815 | ValueRegs: values, 816 | } 817 | case OpFilledNewArrayRange: 818 | data := (ot.Fmt()).(Fmt3rc) 819 | 820 | parsedDesc, err := r.ReadTypeAndParse(uint32(data.B)) 821 | if err != nil { 822 | return res, fmt.Errorf("bad class type: %w", err) 823 | } 824 | 825 | if !parsedDesc.IsArray() { 826 | return res, fmt.Errorf("%w: %v: invalid descriptor", ErrBadOp, ot) 827 | } 828 | 829 | values := make([]uint16, data.A) 830 | 831 | for i := uint8(0); i < data.A; i++ { 832 | values[i] = data.C + uint16(i) 833 | } 834 | 835 | parsed = FilledNewArrayOpNode{ 836 | Raw: ot, 837 | Type: parsedDesc, 838 | ValueRegs: values, 839 | } 840 | case OpFillArrayData: 841 | data := (ot.Fmt()).(Fmt31t) 842 | 843 | tgt := pos + int(int32(data.B)) 844 | if tgt < 0 || tgt >= len(idMap) { 845 | return res, fmt.Errorf("%w: invalid data offset %v", ErrBadOp, tgt) 846 | } 847 | 848 | oldPos := or.Pos() 849 | or.Seek(tgt) 850 | 851 | rtop, err := or.Read() 852 | if err != nil { 853 | return res, fmt.Errorf("%w: invalid data %w", ErrBadOp, err) 854 | } 855 | 856 | tableOp, ok := (rtop).(OpPseudoFillArrayDataPayload) 857 | if !ok { 858 | return res, fmt.Errorf("%w: invalid data %v", ErrBadOp, tableOp) 859 | } 860 | 861 | or.Seek(oldPos) 862 | 863 | parsed = FillArrayDataOpNode{ 864 | Raw: ot, 865 | Ref: data.A, 866 | FmtFillArrayDataPayload: tableOp.FmtFillArrayDataPayload, 867 | } 868 | case OpThrow: 869 | data := (ot.Fmt()).(Fmt11x) 870 | 871 | parsed = ThrowOpNode{ 872 | Raw: ot, 873 | Reg: data.A, 874 | } 875 | case OpGoto: 876 | data := (ot.Fmt()).(Fmt10t) 877 | 878 | tgt := pos + int(int8(data.A)) 879 | if tgt < 0 || tgt >= len(idMap) { 880 | return res, fmt.Errorf("%w: invalid goto jump offset %v", ErrBadOp, tgt) 881 | } 882 | 883 | id := idMap[tgt] 884 | if id == 0 && tgt != 0 { 885 | return res, fmt.Errorf("%w: invalid goto jump offset %v", ErrBadOp, tgt) 886 | } 887 | 888 | parsed = GotoOpNode{ 889 | Raw: ot, 890 | Tgt: int(id), 891 | } 892 | case OpGoto16: 893 | data := (ot.Fmt()).(Fmt20t) 894 | 895 | tgt := pos + int(int16(data.A)) 896 | if tgt < 0 || tgt >= len(idMap) { 897 | return res, fmt.Errorf("%w: invalid goto16 jump offset %v", ErrBadOp, tgt) 898 | } 899 | 900 | id := idMap[tgt] 901 | if id == 0 && tgt != 0 { 902 | return res, fmt.Errorf("%w: invalid goto16 jump offset %v", ErrBadOp, tgt) 903 | } 904 | 905 | parsed = GotoOpNode{ 906 | Raw: ot, 907 | Tgt: int(id), 908 | } 909 | case OpGoto32: 910 | data := (ot.Fmt()).(Fmt30t) 911 | 912 | tgt := pos + int(int32(data.A)) 913 | if tgt < 0 || tgt >= len(idMap) { 914 | return res, fmt.Errorf("%w: invalid goto32 jump offset %v", ErrBadOp, tgt) 915 | } 916 | 917 | id := idMap[tgt] 918 | if id == 0 && tgt != 0 { 919 | return res, fmt.Errorf("%w: invalid goto32 jump offset %v", ErrBadOp, tgt) 920 | } 921 | 922 | parsed = GotoOpNode{ 923 | Raw: ot, 924 | Tgt: int(id), 925 | } 926 | case OpPackedSwitch: 927 | data := (ot.Fmt()).(Fmt31t) 928 | 929 | tgt := pos + int(int32(data.B)) 930 | if tgt < 0 || tgt >= len(idMap) { 931 | return res, fmt.Errorf("%w: invalid table offset %v", ErrBadOp, tgt) 932 | } 933 | 934 | oldPos := or.Pos() 935 | or.Seek(tgt) 936 | 937 | rtop, err := or.Read() 938 | if err != nil { 939 | return res, fmt.Errorf("%w: invalid table %w", ErrBadOp, err) 940 | } 941 | 942 | tableOp, ok := (rtop).(OpPseudoPackedSwitchPayload) 943 | if !ok { 944 | return res, fmt.Errorf("%w: invalid table %v", ErrBadOp, tableOp) 945 | } 946 | 947 | or.Seek(oldPos) 948 | 949 | tgts := make([]int, len(tableOp.Targets)) 950 | 951 | for i, target := range tableOp.Targets { 952 | tgts[i] = pos + int(target) 953 | } 954 | 955 | parsed = PackedSwitchOpNode{ 956 | Raw: ot, 957 | Reg: data.A, 958 | FirstKey: tableOp.FirstKey, 959 | Targets: tgts, 960 | } 961 | case OpSparseSwitch: 962 | data := (ot.Fmt()).(Fmt31t) 963 | 964 | tgt := pos + int(int32(data.B)) 965 | if tgt < 0 || tgt >= len(idMap) { 966 | return res, fmt.Errorf("%w: invalid table offset %v", ErrBadOp, tgt) 967 | } 968 | 969 | oldPos := or.Pos() 970 | or.Seek(tgt) 971 | 972 | rtop, err := or.Read() 973 | if err != nil { 974 | return res, fmt.Errorf("%w: invalid table %w", ErrBadOp, err) 975 | } 976 | 977 | tableOp, ok := (rtop).(OpPseudoSparseSwitchPayload) 978 | if !ok { 979 | return res, fmt.Errorf("%w: invalid table %v", ErrBadOp, tableOp) 980 | } 981 | 982 | or.Seek(oldPos) 983 | 984 | tgts := make([]int, len(tableOp.Targets)) 985 | 986 | for i, target := range tableOp.Targets { 987 | tgts[i] = pos + int(target) 988 | } 989 | 990 | parsed = SparseSwitchOpNode{ 991 | Raw: ot, 992 | Reg: data.A, 993 | Keys: tableOp.Keys, 994 | Targets: tgts, 995 | } 996 | case OpCmplFloat, OpCmpgFloat, OpCmplDouble, OpCmpgDouble, OpCmpLong: 997 | data := (ot.Fmt()).(Fmt23x) 998 | 999 | parsed = CmpOpNode{ 1000 | Raw: ot, 1001 | SrcA: data.B, 1002 | SrcB: data.C, 1003 | Dst: data.A, 1004 | } 1005 | case OpIfEq, OpIfNe, OpIfLt, OpIfGe, OpIfGt, OpIfLe: 1006 | data := (ot.Fmt()).(Fmt22t) 1007 | 1008 | tgt := pos + int(int16(data.C)) 1009 | if tgt < 0 || tgt >= len(idMap) { 1010 | return res, fmt.Errorf("%w: invalid if jump offset %v", ErrBadOp, tgt) 1011 | } 1012 | 1013 | id := idMap[tgt] 1014 | if id == 0 && tgt != 0 { 1015 | return res, fmt.Errorf("%w: invalid if jump offset %v", ErrBadOp, tgt) 1016 | } 1017 | 1018 | parsed = IfTestOpNode{ 1019 | Raw: ot, 1020 | A: data.A, 1021 | B: int16(data.B), 1022 | Tgt: int(id), 1023 | } 1024 | case OpIfEqz, OpIfNez, OpIfLtz, OpIfGez, OpIfGtz, OpIfLez: 1025 | data := (ot.Fmt()).(Fmt21t) 1026 | 1027 | tgt := pos + int(int16(data.B)) 1028 | if tgt < 0 || tgt >= len(idMap) { 1029 | return res, fmt.Errorf("%w: invalid ifz jump offset %v", ErrBadOp, tgt) 1030 | } 1031 | 1032 | id := idMap[tgt] 1033 | if id == 0 && tgt != 0 { 1034 | return res, fmt.Errorf("%w: invalid ifz jump offset %v", ErrBadOp, tgt) 1035 | } 1036 | 1037 | parsed = IfTestOpNode{ 1038 | Raw: ot, 1039 | A: data.A, 1040 | B: -1, 1041 | Tgt: int(id), 1042 | } 1043 | case OpAget, OpAgetWide, OpAgetObject, OpAgetBoolean, OpAgetByte, OpAgetChar, OpAgetShort: 1044 | data := (ot.Fmt()).(Fmt23x) 1045 | 1046 | parsed = AGetOpNode{ 1047 | Raw: ot, 1048 | Dst: data.A, 1049 | Array: data.B, 1050 | Index: data.C, 1051 | } 1052 | case OpAput, OpAputWide, OpAputObject, OpAputBoolean, OpAputByte, OpAputChar, OpAputShort: 1053 | data := (ot.Fmt()).(Fmt23x) 1054 | 1055 | parsed = APutOpNode{ 1056 | Raw: ot, 1057 | Src: data.A, 1058 | Array: data.B, 1059 | Index: data.C, 1060 | } 1061 | case OpIget, OpIgetWide, OpIgetObject, OpIgetBoolean, OpIgetByte, OpIgetChar, OpIgetShort: 1062 | data := (ot.Fmt()).(Fmt22c) 1063 | 1064 | f, err := r.ReadFieldAndParse(uint32(data.C)) 1065 | if err != nil { 1066 | return res, err 1067 | } 1068 | 1069 | parsed = IGetOpNode{ 1070 | Raw: ot, 1071 | Dst: data.A, 1072 | Obj: data.B, 1073 | Field: f, 1074 | } 1075 | case OpIput, OpIputWide, OpIputObject, OpIputBoolean, OpIputByte, OpIputChar, OpIputShort: 1076 | data := (ot.Fmt()).(Fmt22c) 1077 | 1078 | f, err := r.ReadFieldAndParse(uint32(data.C)) 1079 | if err != nil { 1080 | return res, err 1081 | } 1082 | 1083 | parsed = IPutOpNode{ 1084 | Raw: ot, 1085 | Src: data.A, 1086 | Obj: data.B, 1087 | Field: f, 1088 | } 1089 | case OpSget, OpSgetWide, OpSgetObject, OpSgetBoolean, OpSgetByte, OpSgetChar, OpSgetShort: 1090 | data := (ot.Fmt()).(Fmt21c) 1091 | 1092 | f, err := r.ReadFieldAndParse(uint32(data.B)) 1093 | if err != nil { 1094 | return res, err 1095 | } 1096 | 1097 | parsed = SGetOpNode{ 1098 | Raw: ot, 1099 | Dst: data.A, 1100 | Field: f, 1101 | } 1102 | case OpSput, OpSputWide, OpSputObject, OpSputBoolean, OpSputByte, OpSputChar, OpSputShort: 1103 | data := (ot.Fmt()).(Fmt21c) 1104 | 1105 | f, err := r.ReadFieldAndParse(uint32(data.B)) 1106 | if err != nil { 1107 | return res, err 1108 | } 1109 | 1110 | parsed = SPutOpNode{ 1111 | Raw: ot, 1112 | Src: data.A, 1113 | Field: f, 1114 | } 1115 | case OpInvokeVirtual, OpInvokeSuper, OpInvokeDirect, OpInvokeStatic, OpInvokeInterface: 1116 | data := (ot.Fmt()).(Fmt35c) 1117 | 1118 | m, err := r.ReadMethodAndParse(uint32(data.B)) 1119 | if err != nil { 1120 | return res, err 1121 | } 1122 | 1123 | args := make([]uint16, data.A) 1124 | 1125 | if data.A >= 1 { 1126 | args[0] = uint16(data.C) 1127 | } 1128 | 1129 | if data.A >= 2 { 1130 | args[1] = uint16(data.D) 1131 | } 1132 | 1133 | if data.A >= 3 { 1134 | args[2] = uint16(data.E) 1135 | } 1136 | 1137 | if data.A >= 4 { 1138 | args[3] = uint16(data.F) 1139 | } 1140 | 1141 | if data.A >= 5 { 1142 | args[4] = uint16(data.G) 1143 | } 1144 | 1145 | parsed = InvokeOpNode{ 1146 | Raw: ot, 1147 | Method: m, 1148 | Args: args, 1149 | } 1150 | case OpInvokeVirtualRange, OpInvokeSuperRange, OpInvokeDirectRange, OpInvokeStaticRange, 1151 | OpInvokeInterfaceRange: 1152 | data := (ot.Fmt()).(Fmt3rc) 1153 | 1154 | m, err := r.ReadMethodAndParse(uint32(data.B)) 1155 | if err != nil { 1156 | return res, err 1157 | } 1158 | 1159 | args := make([]uint16, data.A) 1160 | 1161 | for i := uint8(0); i < data.A; i++ { 1162 | args[i] = data.C + uint16(i) 1163 | } 1164 | 1165 | parsed = InvokeOpNode{ 1166 | Raw: ot, 1167 | Method: m, 1168 | Args: args, 1169 | } 1170 | case OpNegInt, OpNotInt, OpNegLong, OpNotLong, OpNegFloat, OpNegDouble, OpIntToLong, OpIntToFloat, 1171 | OpIntToDouble, OpLongToInt, OpLongToFloat, OpLongToDouble, OpFloatToInt, OpFloatToLong, 1172 | OpFloatToDouble, OpDoubleToInt, OpDoubleToLong, OpDoubleToFloat, OpIntToByte, OpIntToChar, 1173 | OpIntToShort: 1174 | data := (ot.Fmt()).(Fmt12x) 1175 | 1176 | parsed = UnaryOpNode{ 1177 | Raw: ot, 1178 | Dst: data.A, 1179 | Src: data.B, 1180 | } 1181 | case OpAddInt, OpSubInt, OpMulInt, OpDivInt, OpRemInt, OpAndInt, OpOrInt, OpXorInt, OpShlInt, 1182 | OpShrInt, OpUshrInt, OpAddLong, OpSubLong, OpMulLong, OpDivLong, OpRemLong, OpAndLong, OpOrLong, 1183 | OpXorLong, OpShlLong, OpShrLong, OpUshrLong, OpAddFloat, OpSubFloat, OpMulFloat, OpDivFloat, 1184 | OpRemFloat, OpAddDouble, OpSubDouble, OpMulDouble, OpDivDouble, OpRemDouble: 1185 | data := (ot.Fmt()).(Fmt23x) 1186 | 1187 | parsed = BinaryOpNode{ 1188 | Raw: ot, 1189 | Dst: data.A, 1190 | SrcA: data.B, 1191 | SrcB: data.C, 1192 | } 1193 | case OpAddInt2Addr, OpSubInt2Addr, OpMulInt2Addr, OpDivInt2Addr, OpRemInt2Addr, OpAndInt2Addr, 1194 | OpOrInt2Addr, OpXorInt2Addr, OpShlInt2Addr, OpShrInt2Addr, OpUshrInt2Addr, OpAddLong2Addr, 1195 | OpSubLong2Addr, OpMulLong2Addr, OpDivLong2Addr, OpRemLong2Addr, OpAndLong2Addr, OpOrLong2Addr, 1196 | OpXorLong2Addr, OpShlLong2Addr, OpShrLong2Addr, OpUshrLong2Addr, OpAddFloat2Addr, OpSubFloat2Addr, 1197 | OpMulFloat2Addr, OpDivFloat2Addr, OpRemFloat2Addr, OpAddDouble2Addr, OpSubDouble2Addr, 1198 | OpMulDouble2Addr, OpDivDouble2Addr, OpRemDouble2Addr: 1199 | data := (ot.Fmt()).(Fmt12x) 1200 | 1201 | parsed = BinaryOpNode{ 1202 | Raw: ot, 1203 | Dst: data.A, 1204 | SrcA: data.A, 1205 | SrcB: data.B, 1206 | } 1207 | case OpAddIntLit16, OpRsubIntLit16, OpMulIntLit16, OpDivIntLit16, OpRemIntLit16, OpAndIntLit16, 1208 | OpOrIntLit16, OpXorIntLit16: 1209 | data := (ot.Fmt()).(Fmt22s) 1210 | 1211 | parsed = BinaryLiteralOpNode{ 1212 | Raw: ot, 1213 | Dst: data.A, 1214 | SrcA: data.B, 1215 | ValueB: int16(data.C), 1216 | } 1217 | case OpAddIntLit8, OpRsubIntLit8, OpMulIntLit8, OpDivIntLit8, OpRemIntLit8, OpAndIntLit8, OpOrIntLit8, 1218 | OpXorIntLit8, OpShlIntLit8, OpShrIntLit8, OpUshrIntLit8: 1219 | data := (ot.Fmt()).(Fmt22b) 1220 | 1221 | parsed = BinaryLiteralOpNode{ 1222 | Raw: ot, 1223 | Dst: data.A, 1224 | SrcA: data.B, 1225 | ValueB: int16(int8(data.C)), 1226 | } 1227 | // TODO: invoke-polymorphic, invoke-polymorphic/range, invoke-custom, invoke-custom/range, const-method-handle, 1228 | // const-method-type 1229 | default: 1230 | parsed = UnknownOpNode{ 1231 | Raw: ot, 1232 | } 1233 | } 1234 | 1235 | res.Ops[idPos] = parsed 1236 | idPos++ 1237 | } 1238 | 1239 | return res, nil 1240 | } 1241 | -------------------------------------------------------------------------------- /flags.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | const ( 4 | AccPublic uint32 = 0x0001 5 | AccPrivate uint32 = 0x0002 6 | AccProtected uint32 = 0x0004 7 | AccStatic uint32 = 0x0008 8 | AccFinal uint32 = 0x0010 9 | AccSynchronized uint32 = 0x0020 10 | AccSuper uint32 = 0x0020 11 | AccVolatile uint32 = 0x0040 12 | AccBridge uint32 = 0x0040 13 | AccTransient uint32 = 0x0080 14 | AccVarargs uint32 = 0x0080 15 | AccNative uint32 = 0x0100 16 | AccInterface uint32 = 0x0200 17 | AccAbstract uint32 = 0x400 18 | AccStrict uint32 = 0x800 19 | AccSynthetic uint32 = 0x1000 20 | AccAnnotation uint32 = 0x2000 21 | AccEnum uint32 = 0x4000 22 | AccConstructor uint32 = 0x10000 23 | AccDeclaredSynchronized uint32 = 0x20000 24 | 25 | ClassFlags = AccPublic | AccFinal | AccSuper | AccInterface | AccAbstract | AccSynthetic | 26 | AccAnnotation | AccEnum 27 | InnerClassFlags = AccPublic | AccPrivate | AccProtected | AccStatic | AccFinal | AccInterface | 28 | AccAbstract | AccSynthetic | AccAnnotation | AccEnum 29 | FieldFlags = AccProtected | AccProtected | AccProtected | AccStatic | AccFinal | AccVolatile | AccTransient | 30 | AccSynthetic | AccEnum 31 | MethodFlags = AccPublic | AccPrivate | AccProtected | AccStatic | AccFinal | AccSynchronized | AccBridge | 32 | AccVarargs | AccNative | AccAbstract | AccStrict | AccSynthetic | AccConstructor | AccDeclaredSynchronized 33 | ) 34 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/csnewman/dextk 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/iancoleman/strcase v0.3.0 7 | github.com/stretchr/testify v1.9.0 8 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/pmezard/go-difflib v1.0.0 // indirect 14 | gopkg.in/yaml.v3 v3.0.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= 4 | github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 8 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= 10 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 14 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 15 | -------------------------------------------------------------------------------- /internal/decompiler/decompiler.go: -------------------------------------------------------------------------------- 1 | package decompiler 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "github.com/csnewman/dextk" 8 | "github.com/csnewman/dextk/internal/java" 9 | ) 10 | 11 | func ProcessClass(reader *dextk.Reader, node dextk.ClassNode) string { 12 | d := &Decompiler{ 13 | reader: reader, 14 | in: node, 15 | } 16 | 17 | d.process() 18 | 19 | w := java.NewWriter() 20 | 21 | d.out.Write(w) 22 | 23 | out := w.String() 24 | 25 | return out 26 | } 27 | 28 | type Decompiler struct { 29 | reader *dextk.Reader 30 | in dextk.ClassNode 31 | out *java.Class 32 | } 33 | 34 | func (d *Decompiler) process() { 35 | name, pkg := SplitType(d.in.Name) 36 | 37 | d.out = &java.Class{ 38 | Name: name, 39 | Package: pkg, 40 | } 41 | 42 | flags := NewFlags(d.in.AccessFlags) 43 | 44 | if flags.Take(dextk.AccPublic) { 45 | d.out.Access = java.AccessPublic 46 | } 47 | 48 | if flags.Take(dextk.AccFinal) { 49 | d.out.Final = true 50 | } 51 | 52 | if flags.Take(dextk.AccSuper) { 53 | panic("todo super") 54 | } 55 | 56 | if flags.Take(dextk.AccAbstract) { 57 | panic("todo abstract") 58 | } 59 | 60 | if flags.Take(dextk.AccSynthetic) { 61 | d.out.Synthetic = true 62 | } 63 | 64 | if flags.Take(dextk.AccInterface) { 65 | panic("todo interface") 66 | } 67 | 68 | if flags.Take(dextk.AccAnnotation) { 69 | panic("todo annotation") 70 | } 71 | 72 | if flags.Take(dextk.AccEnum) { 73 | panic("todo enum") 74 | } 75 | 76 | if flags.Remaining() != 0 { 77 | panic("Flags unparsed: " + strconv.FormatInt(int64(flags.Remaining()), 2)) 78 | } 79 | 80 | for _, field := range d.in.StaticFields { 81 | d.processField(field, true) 82 | } 83 | 84 | for _, field := range d.in.InstanceFields { 85 | d.processField(field, false) 86 | } 87 | } 88 | 89 | func (d *Decompiler) processField(src dextk.FieldNode, static bool) { 90 | out := &java.Field{ 91 | Name: src.Name, 92 | Static: static, 93 | Type: ConvertType(src.Type), 94 | } 95 | 96 | flags := NewFlags(src.AccessFlags) 97 | 98 | if flags.Take(dextk.AccPrivate) { 99 | out.Access = java.AccessPrivate 100 | } 101 | 102 | if flags.Take(dextk.AccProtected) { 103 | out.Access = java.AccessProtected 104 | } 105 | 106 | if flags.Take(dextk.AccPublic) { 107 | out.Access = java.AccessPublic 108 | } 109 | 110 | if flags.Take(dextk.AccFinal) { 111 | out.Final = true 112 | } 113 | 114 | if flags.Take(dextk.AccStatic) { 115 | panic("todo static") 116 | } 117 | 118 | if flags.Take(dextk.AccVolatile) { 119 | panic("todo volatile") 120 | } 121 | 122 | if flags.Take(dextk.AccTransient) { 123 | panic("todo transient") 124 | } 125 | 126 | if flags.Take(dextk.AccSynthetic) { 127 | out.Synthetic = true 128 | } 129 | 130 | if flags.Take(dextk.AccEnum) { 131 | panic("todo enum") 132 | } 133 | 134 | if flags.Remaining() != 0 { 135 | panic("Flags unparsed: " + strconv.FormatInt(int64(flags.Remaining()), 2)) 136 | } 137 | 138 | d.out.AddField(out) 139 | } 140 | 141 | func ConvertType(td dextk.TypeDescriptor) *java.Type { 142 | switch td.Type { 143 | case 'L': 144 | name, pkg := SplitType(td.ClassName) 145 | 146 | return &java.Type{ 147 | Name: name, 148 | Package: pkg, 149 | ArrayLength: td.ArrayLength, 150 | } 151 | 152 | default: 153 | panic("Unknown type: " + strconv.Itoa(int(td.Type)) + " " + string(rune(td.Type))) 154 | } 155 | } 156 | 157 | func SplitType(full string) (string, string) { 158 | parts := strings.Split(full, "/") 159 | 160 | name := parts[len(parts)-1] 161 | pkg := strings.Join(parts[:len(parts)-1], ".") 162 | 163 | return name, pkg 164 | } 165 | -------------------------------------------------------------------------------- /internal/decompiler/flags.go: -------------------------------------------------------------------------------- 1 | package decompiler 2 | 3 | type Flags struct { 4 | value uint32 5 | } 6 | 7 | func NewFlags(value uint32) *Flags { 8 | return &Flags{value} 9 | } 10 | 11 | func (f *Flags) Take(bits uint32) bool { 12 | if f.value&bits == bits { 13 | f.value &= ^bits 14 | 15 | return true 16 | } 17 | 18 | return false 19 | } 20 | 21 | func (f *Flags) Remaining() uint32 { 22 | return f.value 23 | } 24 | -------------------------------------------------------------------------------- /internal/gen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "text/template" 8 | 9 | "github.com/iancoleman/strcase" 10 | ) 11 | 12 | func main() { 13 | t, err := template.New("tmpl").Parse(opsTemplateSrc) 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | f, err := os.Create("op.gen.go") 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | defer f.Close() 24 | 25 | d := &Data{} 26 | 27 | for _, op := range Ops { 28 | d.Ops = append(d.Ops, GenOp{ 29 | DisplayName: op.Name, 30 | PascalName: strcase.ToCamel(strings.ReplaceAll(op.Name, "/", "_")), 31 | CodeConst: fmt.Sprintf("%x", op.Code), 32 | Fmt: op.Fmt, 33 | }) 34 | } 35 | 36 | if err := t.Execute(f, d); err != nil { 37 | panic(err) 38 | } 39 | } 40 | 41 | type Data struct { 42 | Ops []GenOp 43 | } 44 | 45 | type GenOp struct { 46 | DisplayName string 47 | PascalName string 48 | CodeConst string 49 | Fmt string 50 | } 51 | 52 | var opsTemplateSrc = `{{$top := . -}} 53 | package dextk 54 | 55 | import "fmt" 56 | 57 | const ( 58 | {{- range $o := $top.Ops}} 59 | OpCode{{$o.PascalName}} OpCode = 0x{{$o.CodeConst}} 60 | {{- end}} 61 | ) 62 | 63 | var opConfigsExtra = map[OpCode]opConfig{} 64 | 65 | var opConfigs = [256]opConfig{} 66 | 67 | func init() { 68 | {{- range $o := $top.Ops}} 69 | opConfigs[OpCode{{$o.PascalName}}] = opConfig{ 70 | Name: "{{$o.DisplayName}}", 71 | Size: fmt{{$o.Fmt}}Size, 72 | Reader: func(r *OpReader) (Op, error) { 73 | pos := r.pos 74 | 75 | f, err := r.readFmt{{$o.Fmt}}() 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | return Op{{$o.PascalName}}{ 81 | opBase{pos: pos}, 82 | f, 83 | }, nil 84 | }, 85 | } 86 | {{- end}} 87 | } 88 | {{range $o := $top.Ops}} 89 | type Op{{$o.PascalName}} struct { 90 | opBase 91 | Fmt{{$o.Fmt}} 92 | } 93 | 94 | func (o Op{{$o.PascalName}}) Code() OpCode { 95 | return OpCode{{$o.PascalName}} 96 | } 97 | 98 | func (o Op{{$o.PascalName}}) Fmt() Fmt { 99 | return o.Fmt{{$o.Fmt}} 100 | } 101 | 102 | func (o Op{{$o.PascalName}}) String() string { 103 | f := o.Fmt{{$o.Fmt}}.String() 104 | if f != "" { 105 | return fmt.Sprintf("0x%x: {{$o.DisplayName}} %v", o.pos, f) 106 | } 107 | 108 | return fmt.Sprintf("0x%x: {{$o.DisplayName}}", o.pos) 109 | } 110 | {{end}}` 111 | -------------------------------------------------------------------------------- /internal/gen/ops.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Op struct { 4 | Code int 5 | Name string 6 | Fmt string 7 | } 8 | 9 | var Ops = []Op{ 10 | { 11 | Code: 0x00, 12 | Name: "nop", 13 | Fmt: "10x", 14 | }, 15 | { 16 | Code: 0x01, 17 | Name: "move", 18 | Fmt: "12x", 19 | }, 20 | { 21 | Code: 0x02, 22 | Name: "move/from16", 23 | Fmt: "22x", 24 | }, 25 | { 26 | Code: 0x03, 27 | Name: "move/16", 28 | Fmt: "32x", 29 | }, 30 | { 31 | Code: 0x04, 32 | Name: "move-wide", 33 | Fmt: "12x", 34 | }, 35 | { 36 | Code: 0x05, 37 | Name: "move-wide/from16", 38 | Fmt: "22x", 39 | }, 40 | { 41 | Code: 0x06, 42 | Name: "move-wide/16", 43 | Fmt: "32x", 44 | }, 45 | { 46 | Code: 0x07, 47 | Name: "move-object", 48 | Fmt: "12x", 49 | }, 50 | { 51 | Code: 0x08, 52 | Name: "move-object/from16", 53 | Fmt: "22x", 54 | }, 55 | { 56 | Code: 0x09, 57 | Name: "move-object/16", 58 | Fmt: "32x", 59 | }, 60 | { 61 | Code: 0x0a, 62 | Name: "move-result", 63 | Fmt: "11x", 64 | }, 65 | { 66 | Code: 0x0b, 67 | Name: "move-result-wide", 68 | Fmt: "11x", 69 | }, 70 | { 71 | Code: 0x0c, 72 | Name: "move-result-object", 73 | Fmt: "11x", 74 | }, 75 | { 76 | Code: 0x0d, 77 | Name: "move-exception", 78 | Fmt: "11x", 79 | }, 80 | { 81 | Code: 0x0e, 82 | Name: "return-void", 83 | Fmt: "10x", 84 | }, 85 | { 86 | Code: 0x0f, 87 | Name: "return", 88 | Fmt: "11x", 89 | }, 90 | { 91 | Code: 0x10, 92 | Name: "return-wide", 93 | Fmt: "11x", 94 | }, 95 | { 96 | Code: 0x11, 97 | Name: "return-object", 98 | Fmt: "11x", 99 | }, 100 | { 101 | Code: 0x12, 102 | Name: "const/4", 103 | Fmt: "11n", 104 | }, 105 | { 106 | Code: 0x13, 107 | Name: "const/16", 108 | Fmt: "21s", 109 | }, 110 | { 111 | Code: 0x14, 112 | Name: "const", 113 | Fmt: "31i", 114 | }, 115 | { 116 | Code: 0x15, 117 | Name: "const/high16", 118 | Fmt: "21h", 119 | }, 120 | { 121 | Code: 0x16, 122 | Name: "const-wide/16", 123 | Fmt: "21s", 124 | }, 125 | { 126 | Code: 0x17, 127 | Name: "const-wide/32", 128 | Fmt: "31i", 129 | }, 130 | { 131 | Code: 0x18, 132 | Name: "const-wide", 133 | Fmt: "51l", 134 | }, 135 | { 136 | Code: 0x19, 137 | Name: "const-wide/high16", 138 | Fmt: "21h", 139 | }, 140 | { 141 | Code: 0x1a, 142 | Name: "const-string", 143 | Fmt: "21c", 144 | }, 145 | { 146 | Code: 0x1b, 147 | Name: "const-string/jumbo", 148 | Fmt: "31c", 149 | }, 150 | { 151 | Code: 0x1c, 152 | Name: "const-class", 153 | Fmt: "21c", 154 | }, 155 | { 156 | Code: 0x1d, 157 | Name: "monitor-enter", 158 | Fmt: "11x", 159 | }, 160 | { 161 | Code: 0x1e, 162 | Name: "monitor-exit", 163 | Fmt: "11x", 164 | }, 165 | { 166 | Code: 0x1f, 167 | Name: "check-cast", 168 | Fmt: "21c", 169 | }, 170 | { 171 | Code: 0x20, 172 | Name: "instance-of", 173 | Fmt: "22c", 174 | }, 175 | { 176 | Code: 0x21, 177 | Name: "array-length", 178 | Fmt: "12x", 179 | }, 180 | { 181 | Code: 0x22, 182 | Name: "new-instance", 183 | Fmt: "21c", 184 | }, 185 | { 186 | Code: 0x23, 187 | Name: "new-array", 188 | Fmt: "22c", 189 | }, 190 | { 191 | Code: 0x24, 192 | Name: "filled-new-array", 193 | Fmt: "35c", 194 | }, 195 | { 196 | Code: 0x25, 197 | Name: "filled-new-array/range", 198 | Fmt: "3rc", 199 | }, 200 | { 201 | Code: 0x26, 202 | Name: "fill-array-data", 203 | Fmt: "31t", 204 | }, 205 | { 206 | Code: 0x27, 207 | Name: "throw", 208 | Fmt: "11x", 209 | }, 210 | { 211 | Code: 0x28, 212 | Name: "goto", 213 | Fmt: "10t", 214 | }, 215 | { 216 | Code: 0x29, 217 | Name: "goto/16", 218 | Fmt: "20t", 219 | }, 220 | { 221 | Code: 0x2a, 222 | Name: "goto/32", 223 | Fmt: "30t", 224 | }, 225 | { 226 | Code: 0x2b, 227 | Name: "packed-switch", 228 | Fmt: "31t", 229 | }, 230 | { 231 | Code: 0x2c, 232 | Name: "sparse-switch", 233 | Fmt: "31t", 234 | }, 235 | { 236 | Code: 0x2d, 237 | Name: "cmpl-float", 238 | Fmt: "23x", 239 | }, 240 | { 241 | Code: 0x2e, 242 | Name: "cmpg-float", 243 | Fmt: "23x", 244 | }, 245 | { 246 | Code: 0x2f, 247 | Name: "cmpl-double", 248 | Fmt: "23x", 249 | }, 250 | { 251 | Code: 0x30, 252 | Name: "cmpg-double", 253 | Fmt: "23x", 254 | }, 255 | { 256 | Code: 0x31, 257 | Name: "cmp-long", 258 | Fmt: "23x", 259 | }, 260 | { 261 | Code: 0x32, 262 | Name: "if-eq", 263 | Fmt: "22t", 264 | }, 265 | { 266 | Code: 0x33, 267 | Name: "if-ne", 268 | Fmt: "22t", 269 | }, 270 | { 271 | Code: 0x34, 272 | Name: "if-lt", 273 | Fmt: "22t", 274 | }, 275 | { 276 | Code: 0x35, 277 | Name: "if-ge", 278 | Fmt: "22t", 279 | }, 280 | { 281 | Code: 0x36, 282 | Name: "if-gt", 283 | Fmt: "22t", 284 | }, 285 | { 286 | Code: 0x37, 287 | Name: "if-le", 288 | Fmt: "22t", 289 | }, 290 | { 291 | Code: 0x38, 292 | Name: "if-eqz", 293 | Fmt: "21t", 294 | }, 295 | { 296 | Code: 0x39, 297 | Name: "if-nez", 298 | Fmt: "21t", 299 | }, 300 | { 301 | Code: 0x3a, 302 | Name: "if-ltz", 303 | Fmt: "21t", 304 | }, 305 | { 306 | Code: 0x3b, 307 | Name: "if-gez", 308 | Fmt: "21t", 309 | }, 310 | { 311 | Code: 0x3c, 312 | Name: "if-gtz", 313 | Fmt: "21t", 314 | }, 315 | { 316 | Code: 0x3d, 317 | Name: "if-lez", 318 | Fmt: "21t", 319 | }, 320 | // 0x3e-0x43 unused 321 | { 322 | Code: 0x44, 323 | Name: "aget", 324 | Fmt: "23x", 325 | }, 326 | { 327 | Code: 0x45, 328 | Name: "aget-wide", 329 | Fmt: "23x", 330 | }, 331 | { 332 | Code: 0x46, 333 | Name: "aget-object", 334 | Fmt: "23x", 335 | }, 336 | { 337 | Code: 0x47, 338 | Name: "aget-boolean", 339 | Fmt: "23x", 340 | }, 341 | { 342 | Code: 0x48, 343 | Name: "aget-byte", 344 | Fmt: "23x", 345 | }, 346 | { 347 | Code: 0x49, 348 | Name: "aget-char", 349 | Fmt: "23x", 350 | }, 351 | { 352 | Code: 0x4a, 353 | Name: "aget-short", 354 | Fmt: "23x", 355 | }, 356 | { 357 | Code: 0x4b, 358 | Name: "aput", 359 | Fmt: "23x", 360 | }, 361 | { 362 | Code: 0x4c, 363 | Name: "aput-wide", 364 | Fmt: "23x", 365 | }, 366 | { 367 | Code: 0x4d, 368 | Name: "aput-object", 369 | Fmt: "23x", 370 | }, 371 | { 372 | Code: 0x4e, 373 | Name: "aput-boolean", 374 | Fmt: "23x", 375 | }, 376 | { 377 | Code: 0x4f, 378 | Name: "aput-byte", 379 | Fmt: "23x", 380 | }, 381 | { 382 | Code: 0x50, 383 | Name: "aput-char", 384 | Fmt: "23x", 385 | }, 386 | { 387 | Code: 0x51, 388 | Name: "aput-short", 389 | Fmt: "23x", 390 | }, 391 | { 392 | Code: 0x52, 393 | Name: "iget", 394 | Fmt: "22c", 395 | }, 396 | { 397 | Code: 0x53, 398 | Name: "iget-wide", 399 | Fmt: "22c", 400 | }, 401 | { 402 | Code: 0x54, 403 | Name: "iget-object", 404 | Fmt: "22c", 405 | }, 406 | { 407 | Code: 0x55, 408 | Name: "iget-boolean", 409 | Fmt: "22c", 410 | }, 411 | { 412 | Code: 0x56, 413 | Name: "iget-byte", 414 | Fmt: "22c", 415 | }, 416 | { 417 | Code: 0x57, 418 | Name: "iget-char", 419 | Fmt: "22c", 420 | }, 421 | { 422 | Code: 0x58, 423 | Name: "iget-short", 424 | Fmt: "22c", 425 | }, 426 | { 427 | Code: 0x59, 428 | Name: "iput", 429 | Fmt: "22c", 430 | }, 431 | { 432 | Code: 0x5a, 433 | Name: "iput-wide", 434 | Fmt: "22c", 435 | }, 436 | { 437 | Code: 0x5b, 438 | Name: "iput-object", 439 | Fmt: "22c", 440 | }, 441 | { 442 | Code: 0x5c, 443 | Name: "iput-boolean", 444 | Fmt: "22c", 445 | }, 446 | { 447 | Code: 0x5d, 448 | Name: "iput-byte", 449 | Fmt: "22c", 450 | }, 451 | { 452 | Code: 0x5e, 453 | Name: "iput-char", 454 | Fmt: "22c", 455 | }, 456 | { 457 | Code: 0x5f, 458 | Name: "iput-short", 459 | Fmt: "22c", 460 | }, 461 | { 462 | Code: 0x60, 463 | Name: "sget", 464 | Fmt: "21c", 465 | }, 466 | { 467 | Code: 0x61, 468 | Name: "sget-wide", 469 | Fmt: "21c", 470 | }, 471 | { 472 | Code: 0x62, 473 | Name: "sget-object", 474 | Fmt: "21c", 475 | }, 476 | { 477 | Code: 0x63, 478 | Name: "sget-boolean", 479 | Fmt: "21c", 480 | }, 481 | { 482 | Code: 0x64, 483 | Name: "sget-byte", 484 | Fmt: "21c", 485 | }, 486 | { 487 | Code: 0x65, 488 | Name: "sget-char", 489 | Fmt: "21c", 490 | }, 491 | { 492 | Code: 0x66, 493 | Name: "sget-short", 494 | Fmt: "21c", 495 | }, 496 | { 497 | Code: 0x67, 498 | Name: "sput", 499 | Fmt: "21c", 500 | }, 501 | { 502 | Code: 0x68, 503 | Name: "sput-wide", 504 | Fmt: "21c", 505 | }, 506 | { 507 | Code: 0x69, 508 | Name: "sput-object", 509 | Fmt: "21c", 510 | }, 511 | { 512 | Code: 0x6a, 513 | Name: "sput-boolean", 514 | Fmt: "21c", 515 | }, 516 | { 517 | Code: 0x6b, 518 | Name: "sput-byte", 519 | Fmt: "21c", 520 | }, 521 | { 522 | Code: 0x6c, 523 | Name: "sput-char", 524 | Fmt: "21c", 525 | }, 526 | { 527 | Code: 0x6d, 528 | Name: "sput-short", 529 | Fmt: "21c", 530 | }, 531 | { 532 | Code: 0x6e, 533 | Name: "invoke-virtual", 534 | Fmt: "35c", 535 | }, 536 | { 537 | Code: 0x6f, 538 | Name: "invoke-super", 539 | Fmt: "35c", 540 | }, 541 | { 542 | Code: 0x70, 543 | Name: "invoke-direct", 544 | Fmt: "35c", 545 | }, 546 | { 547 | Code: 0x71, 548 | Name: "invoke-static", 549 | Fmt: "35c", 550 | }, 551 | { 552 | Code: 0x72, 553 | Name: "invoke-interface", 554 | Fmt: "35c", 555 | }, 556 | // 0x73 unused 557 | { 558 | Code: 0x74, 559 | Name: "invoke-virtual/range", 560 | Fmt: "3rc", 561 | }, 562 | { 563 | Code: 0x75, 564 | Name: "invoke-super/range", 565 | Fmt: "3rc", 566 | }, 567 | { 568 | Code: 0x76, 569 | Name: "invoke-direct/range", 570 | Fmt: "3rc", 571 | }, 572 | { 573 | Code: 0x77, 574 | Name: "invoke-static/range", 575 | Fmt: "3rc", 576 | }, 577 | { 578 | Code: 0x78, 579 | Name: "invoke-interface/range", 580 | Fmt: "3rc", 581 | }, 582 | // 0x79-0x7a unused 583 | { 584 | Code: 0x7b, 585 | Name: "neg-int", 586 | Fmt: "12x", 587 | }, 588 | { 589 | Code: 0x7c, 590 | Name: "not-int", 591 | Fmt: "12x", 592 | }, 593 | { 594 | Code: 0x7d, 595 | Name: "neg-long", 596 | Fmt: "12x", 597 | }, 598 | { 599 | Code: 0x7e, 600 | Name: "not-long", 601 | Fmt: "12x", 602 | }, 603 | { 604 | Code: 0x7f, 605 | Name: "neg-float", 606 | Fmt: "12x", 607 | }, 608 | { 609 | Code: 0x80, 610 | Name: "neg-double", 611 | Fmt: "12x", 612 | }, 613 | { 614 | Code: 0x81, 615 | Name: "int-to-long", 616 | Fmt: "12x", 617 | }, 618 | { 619 | Code: 0x82, 620 | Name: "int-to-float", 621 | Fmt: "12x", 622 | }, 623 | { 624 | Code: 0x83, 625 | Name: "int-to-double", 626 | Fmt: "12x", 627 | }, 628 | { 629 | Code: 0x84, 630 | Name: "long-to-int", 631 | Fmt: "12x", 632 | }, 633 | { 634 | Code: 0x85, 635 | Name: "long-to-float", 636 | Fmt: "12x", 637 | }, 638 | { 639 | Code: 0x86, 640 | Name: "long-to-double", 641 | Fmt: "12x", 642 | }, 643 | { 644 | Code: 0x87, 645 | Name: "float-to-int", 646 | Fmt: "12x", 647 | }, 648 | { 649 | Code: 0x88, 650 | Name: "float-to-long", 651 | Fmt: "12x", 652 | }, 653 | { 654 | Code: 0x89, 655 | Name: "float-to-double", 656 | Fmt: "12x", 657 | }, 658 | { 659 | Code: 0x8a, 660 | Name: "double-to-int", 661 | Fmt: "12x", 662 | }, 663 | { 664 | Code: 0x8b, 665 | Name: "double-to-long", 666 | Fmt: "12x", 667 | }, 668 | { 669 | Code: 0x8c, 670 | Name: "double-to-float", 671 | Fmt: "12x", 672 | }, 673 | { 674 | Code: 0x8d, 675 | Name: "int-to-byte", 676 | Fmt: "12x", 677 | }, 678 | { 679 | Code: 0x8e, 680 | Name: "int-to-char", 681 | Fmt: "12x", 682 | }, 683 | { 684 | Code: 0x8f, 685 | Name: "int-to-short", 686 | Fmt: "12x", 687 | }, 688 | { 689 | Code: 0x90, 690 | Name: "add-int", 691 | Fmt: "23x", 692 | }, 693 | { 694 | Code: 0x91, 695 | Name: "sub-int", 696 | Fmt: "23x", 697 | }, 698 | { 699 | Code: 0x92, 700 | Name: "mul-int", 701 | Fmt: "23x", 702 | }, 703 | { 704 | Code: 0x93, 705 | Name: "div-int", 706 | Fmt: "23x", 707 | }, 708 | { 709 | Code: 0x94, 710 | Name: "rem-int", 711 | Fmt: "23x", 712 | }, 713 | { 714 | Code: 0x95, 715 | Name: "and-int", 716 | Fmt: "23x", 717 | }, 718 | { 719 | Code: 0x96, 720 | Name: "or-int", 721 | Fmt: "23x", 722 | }, 723 | { 724 | Code: 0x97, 725 | Name: "xor-int", 726 | Fmt: "23x", 727 | }, 728 | { 729 | Code: 0x98, 730 | Name: "shl-int", 731 | Fmt: "23x", 732 | }, 733 | { 734 | Code: 0x99, 735 | Name: "shr-int", 736 | Fmt: "23x", 737 | }, 738 | { 739 | Code: 0x9a, 740 | Name: "ushr-int", 741 | Fmt: "23x", 742 | }, 743 | { 744 | Code: 0x9b, 745 | Name: "add-long", 746 | Fmt: "23x", 747 | }, 748 | { 749 | Code: 0x9c, 750 | Name: "sub-long", 751 | Fmt: "23x", 752 | }, 753 | { 754 | Code: 0x9d, 755 | Name: "mul-long", 756 | Fmt: "23x", 757 | }, 758 | { 759 | Code: 0x9e, 760 | Name: "div-long", 761 | Fmt: "23x", 762 | }, 763 | { 764 | Code: 0x9f, 765 | Name: "rem-long", 766 | Fmt: "23x", 767 | }, 768 | { 769 | Code: 0xa0, 770 | Name: "and-long", 771 | Fmt: "23x", 772 | }, 773 | { 774 | Code: 0xa1, 775 | Name: "or-long", 776 | Fmt: "23x", 777 | }, 778 | { 779 | Code: 0xa2, 780 | Name: "xor-long", 781 | Fmt: "23x", 782 | }, 783 | { 784 | Code: 0xa3, 785 | Name: "shl-long", 786 | Fmt: "23x", 787 | }, 788 | { 789 | Code: 0xa4, 790 | Name: "shr-long", 791 | Fmt: "23x", 792 | }, 793 | { 794 | Code: 0xa5, 795 | Name: "ushr-long", 796 | Fmt: "23x", 797 | }, 798 | { 799 | Code: 0xa6, 800 | Name: "add-float", 801 | Fmt: "23x", 802 | }, 803 | { 804 | Code: 0xa7, 805 | Name: "sub-float", 806 | Fmt: "23x", 807 | }, 808 | { 809 | Code: 0xa8, 810 | Name: "mul-float", 811 | Fmt: "23x", 812 | }, 813 | { 814 | Code: 0xa9, 815 | Name: "div-float", 816 | Fmt: "23x", 817 | }, 818 | { 819 | Code: 0xaa, 820 | Name: "rem-float", 821 | Fmt: "23x", 822 | }, 823 | { 824 | Code: 0xab, 825 | Name: "add-double", 826 | Fmt: "23x", 827 | }, 828 | { 829 | Code: 0xac, 830 | Name: "sub-double", 831 | Fmt: "23x", 832 | }, 833 | { 834 | Code: 0xad, 835 | Name: "mul-double", 836 | Fmt: "23x", 837 | }, 838 | { 839 | Code: 0xae, 840 | Name: "div-double", 841 | Fmt: "23x", 842 | }, 843 | { 844 | Code: 0xaf, 845 | Name: "rem-double", 846 | Fmt: "23x", 847 | }, 848 | { 849 | Code: 0xb0, 850 | Name: "add-int/2addr", 851 | Fmt: "12x", 852 | }, 853 | { 854 | Code: 0xb1, 855 | Name: "sub-int/2addr", 856 | Fmt: "12x", 857 | }, 858 | { 859 | Code: 0xb2, 860 | Name: "mul-int/2addr", 861 | Fmt: "12x", 862 | }, 863 | { 864 | Code: 0xb3, 865 | Name: "div-int/2addr", 866 | Fmt: "12x", 867 | }, 868 | { 869 | Code: 0xb4, 870 | Name: "rem-int/2addr", 871 | Fmt: "12x", 872 | }, 873 | { 874 | Code: 0xb5, 875 | Name: "and-int/2addr", 876 | Fmt: "12x", 877 | }, 878 | { 879 | Code: 0xb6, 880 | Name: "or-int/2addr", 881 | Fmt: "12x", 882 | }, 883 | { 884 | Code: 0xb7, 885 | Name: "xor-int/2addr", 886 | Fmt: "12x", 887 | }, 888 | { 889 | Code: 0xb8, 890 | Name: "shl-int/2addr", 891 | Fmt: "12x", 892 | }, 893 | { 894 | Code: 0xb9, 895 | Name: "shr-int/2addr", 896 | Fmt: "12x", 897 | }, 898 | { 899 | Code: 0xba, 900 | Name: "ushr-int/2addr", 901 | Fmt: "12x", 902 | }, 903 | { 904 | Code: 0xbb, 905 | Name: "add-long/2addr", 906 | Fmt: "12x", 907 | }, 908 | { 909 | Code: 0xbc, 910 | Name: "sub-long/2addr", 911 | Fmt: "12x", 912 | }, 913 | { 914 | Code: 0xbd, 915 | Name: "mul-long/2addr", 916 | Fmt: "12x", 917 | }, 918 | { 919 | Code: 0xbe, 920 | Name: "div-long/2addr", 921 | Fmt: "12x", 922 | }, 923 | { 924 | Code: 0xbf, 925 | Name: "rem-long/2addr", 926 | Fmt: "12x", 927 | }, 928 | { 929 | Code: 0xc0, 930 | Name: "and-long/2addr", 931 | Fmt: "12x", 932 | }, 933 | { 934 | Code: 0xc1, 935 | Name: "or-long/2addr", 936 | Fmt: "12x", 937 | }, 938 | { 939 | Code: 0xc2, 940 | Name: "xor-long/2addr", 941 | Fmt: "12x", 942 | }, 943 | { 944 | Code: 0xc3, 945 | Name: "shl-long/2addr", 946 | Fmt: "12x", 947 | }, 948 | { 949 | Code: 0xc4, 950 | Name: "shr-long/2addr", 951 | Fmt: "12x", 952 | }, 953 | { 954 | Code: 0xc5, 955 | Name: "ushr-long/2addr", 956 | Fmt: "12x", 957 | }, 958 | { 959 | Code: 0xc6, 960 | Name: "add-float/2addr", 961 | Fmt: "12x", 962 | }, 963 | { 964 | Code: 0xc7, 965 | Name: "sub-float/2addr", 966 | Fmt: "12x", 967 | }, 968 | { 969 | Code: 0xc8, 970 | Name: "mul-float/2addr", 971 | Fmt: "12x", 972 | }, 973 | { 974 | Code: 0xc9, 975 | Name: "div-float/2addr", 976 | Fmt: "12x", 977 | }, 978 | { 979 | Code: 0xca, 980 | Name: "rem-float/2addr", 981 | Fmt: "12x", 982 | }, 983 | { 984 | Code: 0xcb, 985 | Name: "add-double/2addr", 986 | Fmt: "12x", 987 | }, 988 | { 989 | Code: 0xcc, 990 | Name: "sub-double/2addr", 991 | Fmt: "12x", 992 | }, 993 | { 994 | Code: 0xcd, 995 | Name: "mul-double/2addr", 996 | Fmt: "12x", 997 | }, 998 | { 999 | Code: 0xce, 1000 | Name: "div-double/2addr", 1001 | Fmt: "12x", 1002 | }, 1003 | { 1004 | Code: 0xcf, 1005 | Name: "rem-double/2addr", 1006 | Fmt: "12x", 1007 | }, 1008 | { 1009 | Code: 0xd0, 1010 | Name: "add-int/lit16", 1011 | Fmt: "22s", 1012 | }, 1013 | { 1014 | Code: 0xd1, 1015 | Name: "rsub-int/lit16", 1016 | Fmt: "22s", 1017 | }, 1018 | { 1019 | Code: 0xd2, 1020 | Name: "mul-int/lit16", 1021 | Fmt: "22s", 1022 | }, 1023 | { 1024 | Code: 0xd3, 1025 | Name: "div-int/lit16", 1026 | Fmt: "22s", 1027 | }, 1028 | { 1029 | Code: 0xd4, 1030 | Name: "rem-int/lit16", 1031 | Fmt: "22s", 1032 | }, 1033 | { 1034 | Code: 0xd5, 1035 | Name: "and-int/lit16", 1036 | Fmt: "22s", 1037 | }, 1038 | { 1039 | Code: 0xd6, 1040 | Name: "or-int/lit16", 1041 | Fmt: "22s", 1042 | }, 1043 | { 1044 | Code: 0xd7, 1045 | Name: "xor-int/lit16", 1046 | Fmt: "22s", 1047 | }, 1048 | { 1049 | Code: 0xd8, 1050 | Name: "add-int/lit8", 1051 | Fmt: "22b", 1052 | }, 1053 | { 1054 | Code: 0xd9, 1055 | Name: "rsub-int/lit8", 1056 | Fmt: "22b", 1057 | }, 1058 | { 1059 | Code: 0xda, 1060 | Name: "mul-int/lit8", 1061 | Fmt: "22b", 1062 | }, 1063 | { 1064 | Code: 0xdb, 1065 | Name: "div-int/lit8", 1066 | Fmt: "22b", 1067 | }, 1068 | { 1069 | Code: 0xdc, 1070 | Name: "rem-int/lit8", 1071 | Fmt: "22b", 1072 | }, 1073 | { 1074 | Code: 0xdd, 1075 | Name: "and-int/lit8", 1076 | Fmt: "22b", 1077 | }, 1078 | { 1079 | Code: 0xde, 1080 | Name: "or-int/lit8", 1081 | Fmt: "22b", 1082 | }, 1083 | { 1084 | Code: 0xdf, 1085 | Name: "xor-int/lit8", 1086 | Fmt: "22b", 1087 | }, 1088 | { 1089 | Code: 0xe0, 1090 | Name: "shl-int/lit8", 1091 | Fmt: "22b", 1092 | }, 1093 | { 1094 | Code: 0xe1, 1095 | Name: "shr-int/lit8", 1096 | Fmt: "22b", 1097 | }, 1098 | { 1099 | Code: 0xe2, 1100 | Name: "ushr-int/lit8", 1101 | Fmt: "22b", 1102 | }, 1103 | // 0xe3-0xf9 unused 1104 | { 1105 | Code: 0xfa, 1106 | Name: "invoke-polymorphic", 1107 | Fmt: "45cc", 1108 | }, 1109 | { 1110 | Code: 0xfb, 1111 | Name: "invoke-polymorphic/range", 1112 | Fmt: "4rcc", 1113 | }, 1114 | { 1115 | Code: 0xfc, 1116 | Name: "invoke-custom", 1117 | Fmt: "35c", 1118 | }, 1119 | { 1120 | Code: 0xfd, 1121 | Name: "invoke-custom/range", 1122 | Fmt: "3rc", 1123 | }, 1124 | { 1125 | Code: 0xfe, 1126 | Name: "const-method-handle", 1127 | Fmt: "21c", 1128 | }, 1129 | { 1130 | Code: 0xff, 1131 | Name: "const-method-type", 1132 | Fmt: "21c", 1133 | }, 1134 | } 1135 | -------------------------------------------------------------------------------- /internal/java/class.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import "strings" 4 | 5 | type Class struct { 6 | Name string 7 | Package string 8 | Access Access 9 | Final bool 10 | Synthetic bool 11 | Fields []*Field 12 | Methods []*Method 13 | } 14 | 15 | func (c *Class) AddField(field *Field) { 16 | c.Fields = append(c.Fields, field) 17 | } 18 | 19 | func (c *Class) Write(w *Writer) { 20 | if c.Package != "" { 21 | w.WriteLinef("package %v;", c.Package) 22 | w.EmptyLine() 23 | } 24 | 25 | acc := c.Access 26 | if acc != "" { 27 | acc += " " 28 | } 29 | 30 | if c.Final { 31 | acc += "final " 32 | } 33 | 34 | if c.Synthetic { 35 | w.WriteLine("/* synthetic */") 36 | } 37 | 38 | w.WriteLinef("%vclass %v {", acc, c.Name) 39 | w.Indent(1) 40 | 41 | for _, field := range c.Fields { 42 | field.Write(w, c) 43 | } 44 | 45 | if len(c.Fields) > 0 && len(c.Methods) > 0 { 46 | w.EmptyLine() 47 | } 48 | 49 | w.Indent(-1) 50 | w.WriteLine("}") 51 | } 52 | 53 | func (c *Class) GenType(t *Type) string { 54 | sb := &strings.Builder{} 55 | 56 | if t.Package != "" { 57 | sb.WriteString(t.Package) 58 | sb.WriteString(".") 59 | } 60 | 61 | sb.WriteString(t.Name) 62 | 63 | for i := 0; i < t.ArrayLength; i++ { 64 | sb.WriteString("[]") 65 | } 66 | 67 | return sb.String() 68 | } 69 | -------------------------------------------------------------------------------- /internal/java/field.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | type Field struct { 4 | Name string 5 | Access Access 6 | Final bool 7 | Static bool 8 | Synthetic bool 9 | Type *Type 10 | } 11 | 12 | func (f *Field) Write(w *Writer, c *Class) { 13 | acc := f.Access 14 | if acc != "" { 15 | acc += " " 16 | } 17 | 18 | if f.Static { 19 | acc += "static " 20 | } 21 | 22 | if f.Final { 23 | acc += "final " 24 | } 25 | 26 | ty := c.GenType(f.Type) 27 | 28 | var cmt string 29 | 30 | if f.Synthetic { 31 | cmt = "/* synthetic */" 32 | } 33 | 34 | if cmt != "" { 35 | cmt = " " 36 | } 37 | 38 | w.WriteLinef("%v%v %v;%v", acc, ty, f.Name, cmt) 39 | } 40 | -------------------------------------------------------------------------------- /internal/java/flags.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | type Access string 4 | 5 | const ( 6 | AccessPublic Access = "public" 7 | AccessPrivate Access = "private" 8 | AccessProtected Access = "protected" 9 | ) 10 | -------------------------------------------------------------------------------- /internal/java/method.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | type Method struct { 4 | } 5 | -------------------------------------------------------------------------------- /internal/java/type.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | type Type struct { 4 | Name string 5 | Package string 6 | ArrayLength int 7 | } 8 | -------------------------------------------------------------------------------- /internal/java/writer.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Writer struct { 9 | buffer *strings.Builder 10 | indent int 11 | } 12 | 13 | func NewWriter() *Writer { 14 | return &Writer{ 15 | buffer: &strings.Builder{}, 16 | } 17 | } 18 | 19 | func (w *Writer) Indent(d int) { 20 | w.indent += d 21 | } 22 | 23 | func (w *Writer) WriteLinef(format string, args ...any) { 24 | w.WriteLine(fmt.Sprintf(format, args...)) 25 | } 26 | 27 | func (w *Writer) WriteLine(text string) { 28 | for i := 0; i < w.indent; i++ { 29 | w.buffer.WriteString(" ") 30 | } 31 | 32 | w.buffer.WriteString(text) 33 | w.buffer.WriteRune('\n') 34 | } 35 | 36 | func (w *Writer) EmptyLine() { 37 | w.buffer.WriteRune('\n') 38 | } 39 | 40 | func (w *Writer) String() string { 41 | return w.buffer.String() 42 | } 43 | 44 | func (w *Writer) Reset() { 45 | w.buffer = &strings.Builder{} 46 | } 47 | -------------------------------------------------------------------------------- /iter.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | ErrIterEnd = errors.New("iter end reached") 10 | ErrBadClass = errors.New("malformed class def") 11 | ) 12 | 13 | type StringIter struct { 14 | r *Reader 15 | pos uint32 16 | } 17 | 18 | type StringNode struct { 19 | Id uint32 20 | Value String 21 | } 22 | 23 | func (r *Reader) StringIter() *StringIter { 24 | return &StringIter{ 25 | r: r, 26 | pos: 0, 27 | } 28 | } 29 | 30 | func (i *StringIter) Seek(pos uint32) { 31 | i.pos = pos 32 | } 33 | 34 | func (i *StringIter) HasNext() bool { 35 | return i.pos < i.r.StringIDCount 36 | } 37 | 38 | func (i *StringIter) Next() (StringNode, error) { 39 | var res StringNode 40 | 41 | res.Id = i.pos 42 | 43 | if i.pos >= i.r.StringIDCount { 44 | return res, ErrIterEnd 45 | } 46 | 47 | value, err := i.r.ReadString(i.pos) 48 | if err != nil { 49 | return res, err 50 | } 51 | 52 | res.Value = value 53 | i.pos++ 54 | 55 | return res, nil 56 | } 57 | 58 | type ClassIter struct { 59 | r *Reader 60 | pos uint32 61 | } 62 | 63 | type ClassNode struct { 64 | Id uint32 65 | Name String 66 | AccessFlags uint32 67 | SuperClass String 68 | Interfaces []String 69 | SourceFile String 70 | 71 | StaticFields []FieldNode 72 | InstanceFields []FieldNode 73 | DirectMethods []MethodNode 74 | VirtualMethods []MethodNode 75 | } 76 | 77 | type FieldNode struct { 78 | Id uint32 79 | AccessFlags uint32 80 | Type TypeDescriptor 81 | Name String 82 | } 83 | 84 | type MethodNode struct { 85 | Id uint32 86 | AccessFlags uint32 87 | Name String 88 | Shorty String 89 | ReturnType TypeDescriptor 90 | Params []TypeDescriptor 91 | CodeOff uint32 92 | } 93 | 94 | func (r *Reader) ClassIter() *ClassIter { 95 | return &ClassIter{ 96 | r: r, 97 | pos: 0, 98 | } 99 | } 100 | 101 | func (i *ClassIter) Seek(pos uint32) { 102 | i.pos = pos 103 | } 104 | 105 | func (i *ClassIter) HasNext() bool { 106 | return i.pos < i.r.ClassDefCount 107 | } 108 | 109 | func (i *ClassIter) Next() (ClassNode, error) { 110 | if i.pos >= i.r.ClassDefCount { 111 | return ClassNode{}, ErrIterEnd 112 | } 113 | 114 | res, err := i.r.ReadClassAndParse(i.pos) 115 | if err != nil { 116 | return res, err 117 | } 118 | 119 | i.pos++ 120 | 121 | return res, nil 122 | } 123 | 124 | func (r *Reader) ReadClassAndParse(id uint32) (ClassNode, error) { 125 | var res ClassNode 126 | 127 | if id >= r.ClassDefCount { 128 | return res, ErrInvalidClassDefID 129 | } 130 | 131 | res.Id = id 132 | 133 | def, err := r.ReadClassDef(id) 134 | if err != nil { 135 | return res, fmt.Errorf("%w: %w", ErrBadClass, err) 136 | } 137 | 138 | res.AccessFlags = def.AccessFlags 139 | 140 | // Read class name 141 | { 142 | parsedDesc, err := r.ReadTypeAndParse(def.ClassTypeID) 143 | if err != nil { 144 | return res, fmt.Errorf("%w: bad class type: %w", ErrBadClass, err) 145 | } 146 | 147 | if !parsedDesc.IsClass() || parsedDesc.IsArray() { 148 | return res, fmt.Errorf("%w: invalid descriptor", ErrBadClass) 149 | } 150 | 151 | res.Name = parsedDesc.ClassName 152 | } 153 | 154 | // Parse superclass 155 | if def.SuperclassTypeID != NoIndex { 156 | parsedDesc, err := r.ReadTypeAndParse(def.SuperclassTypeID) 157 | if err != nil { 158 | return res, fmt.Errorf("%w: bad superclass type: %w", ErrBadClass, err) 159 | } 160 | 161 | if !parsedDesc.IsClass() || parsedDesc.IsArray() { 162 | return res, fmt.Errorf("%w: invalid superclass descriptor", ErrBadClass) 163 | } 164 | 165 | res.SuperClass = parsedDesc.ClassName 166 | } 167 | 168 | // Parse interfaces 169 | if def.InterfacesTypeListOff != 0 { 170 | list, err := r.ReadTypeList(def.InterfacesTypeListOff) 171 | if err != nil { 172 | return res, fmt.Errorf("%w: bad interface list: %w", ErrBadClass, err) 173 | } 174 | 175 | res.Interfaces = make([]String, len(list.TypeIds)) 176 | 177 | for p, id := range list.TypeIds { 178 | parsedDesc, err := r.ReadTypeAndParse(uint32(id)) 179 | if err != nil { 180 | return res, fmt.Errorf("%w: bad iface type: %w", ErrBadClass, err) 181 | } 182 | 183 | if !parsedDesc.IsClass() || parsedDesc.IsArray() { 184 | return res, fmt.Errorf("%w: invalid iface descriptor", ErrBadClass) 185 | } 186 | 187 | res.Interfaces[p] = parsedDesc.ClassName 188 | } 189 | } 190 | 191 | // Parse source file 192 | if def.SourceFileStringID != NoIndex { 193 | res.SourceFile, err = r.ReadString(def.SourceFileStringID) 194 | if err != nil { 195 | return res, fmt.Errorf("%w: bad src file: %w", ErrBadClass, err) 196 | } 197 | } 198 | 199 | // Parse annotations 200 | if def.AnnotationsDirectoryOff != 0 { 201 | // TODO: Parse annotations 202 | } 203 | 204 | // Parse data 205 | if def.ClassDataOff != 0 { 206 | data, err := r.ReadClassData(def.ClassDataOff) 207 | if err != nil { 208 | return res, fmt.Errorf("%w: bad data: %w", ErrBadClass, err) 209 | } 210 | 211 | res.StaticFields, err = r.parseFields(def.ClassTypeID, data.StaticFields) 212 | if err != nil { 213 | return res, err 214 | } 215 | 216 | res.InstanceFields, err = r.parseFields(def.ClassTypeID, data.InstanceFields) 217 | if err != nil { 218 | return res, err 219 | } 220 | 221 | res.DirectMethods, err = r.parseMethods(def.ClassTypeID, data.DirectMethods) 222 | if err != nil { 223 | return res, err 224 | } 225 | 226 | res.VirtualMethods, err = r.parseMethods(def.ClassTypeID, data.VirtualMethods) 227 | if err != nil { 228 | return res, err 229 | } 230 | } 231 | 232 | return res, nil 233 | } 234 | 235 | func (r *Reader) parseFields(classTypeId uint32, input []EncodedField) ([]FieldNode, error) { 236 | res := make([]FieldNode, len(input)) 237 | 238 | for i, f := range input { 239 | fp, err := r.ReadField(f.FieldID) 240 | if err != nil { 241 | return res, fmt.Errorf("%w: bad field: %w", ErrBadClass, err) 242 | } 243 | 244 | if classTypeId != uint32(fp.ClassTypeID) { 245 | return res, fmt.Errorf("%w: bad field: mismatch class id", ErrBadClass) 246 | } 247 | 248 | name, err := r.ReadString(fp.NameStringID) 249 | if err != nil { 250 | return res, fmt.Errorf("%w: bad field name: %w", ErrBadClass, err) 251 | } 252 | 253 | def, err := r.ReadTypeAndParse(uint32(fp.TypeID)) 254 | if err != nil { 255 | return res, fmt.Errorf("%w: bad field type: %w", ErrBadClass, err) 256 | } 257 | 258 | res[i] = FieldNode{ 259 | Id: f.FieldID, 260 | AccessFlags: f.AccessFlags, 261 | Type: def, 262 | Name: name, 263 | } 264 | } 265 | 266 | return res, nil 267 | } 268 | 269 | func (r *Reader) parseMethods(classTypeId uint32, input []EncodedMethod) ([]MethodNode, error) { 270 | res := make([]MethodNode, len(input)) 271 | 272 | for i, m := range input { 273 | mp, err := r.ReadMethod(m.MethodID) 274 | if err != nil { 275 | return res, fmt.Errorf("%w: bad method: %w", ErrBadClass, err) 276 | } 277 | 278 | if classTypeId != uint32(mp.ClassTypeID) { 279 | return res, fmt.Errorf("%w: bad method: mismatch class id", ErrBadClass) 280 | } 281 | 282 | name, err := r.ReadString(mp.NameStringID) 283 | if err != nil { 284 | return res, fmt.Errorf("%w: bad method name: %w", ErrBadClass, err) 285 | } 286 | 287 | proto, err := r.ReadProto(uint32(mp.ProtoID)) 288 | if err != nil { 289 | return res, fmt.Errorf("%w: bad method proto: %w", ErrBadClass, err) 290 | } 291 | 292 | shorty, err := r.ReadString(proto.ShortyStringID) 293 | if err != nil { 294 | return res, fmt.Errorf("%w: bad method shorty: %w", ErrBadClass, err) 295 | } 296 | 297 | retDef, err := r.ReadTypeAndParse(proto.ReturnTypeID) 298 | if err != nil { 299 | return res, fmt.Errorf("%w: bad method type: %w", ErrBadClass, err) 300 | } 301 | 302 | res[i] = MethodNode{ 303 | Id: m.MethodID, 304 | AccessFlags: m.AccessFlags, 305 | Name: name, 306 | Shorty: shorty, 307 | ReturnType: retDef, 308 | CodeOff: m.CodeOff, 309 | } 310 | 311 | if proto.ParametersTypeListOff != 0 { 312 | plist, err := r.ReadTypeList(proto.ParametersTypeListOff) 313 | if err != nil { 314 | return res, fmt.Errorf("%w: bad method params: %w", ErrBadClass, err) 315 | } 316 | 317 | res[i].Params = make([]TypeDescriptor, len(plist.TypeIds)) 318 | 319 | for j, tid := range plist.TypeIds { 320 | res[i].Params[j], err = r.ReadTypeAndParse(uint32(tid)) 321 | if err != nil { 322 | return res, fmt.Errorf("%w: bad method paramm: %w", ErrBadClass, err) 323 | } 324 | } 325 | } 326 | } 327 | 328 | return res, nil 329 | } 330 | -------------------------------------------------------------------------------- /op.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | import "fmt" 4 | 5 | type OpCode int16 6 | 7 | type opConfig struct { 8 | Name string 9 | Size func(r *OpReader) (int, error) 10 | Reader func(r *OpReader) (Op, error) 11 | } 12 | 13 | const ( 14 | OpCodeInvalid OpCode = -1 15 | OpCodePseudoPackedSwitchPayload OpCode = -10 16 | OpCodePseudoSparseSwitchPayload OpCode = -11 17 | OpCodePseudoFillArrayDataPayload OpCode = -12 18 | ) 19 | 20 | func (c OpCode) String() string { 21 | cfg, found := opConfigsExtra[c] 22 | if found { 23 | return cfg.Name 24 | } 25 | 26 | if c >= 0 && c <= 255 { 27 | cfg := opConfigs[c] 28 | 29 | if cfg.Name != "" { 30 | return cfg.Name 31 | } 32 | } 33 | 34 | return fmt.Sprintf("unknown(0x%x)", int16(c)) 35 | } 36 | 37 | func (c OpCode) Size(r *OpReader) (int, error) { 38 | cfg, found := opConfigsExtra[c] 39 | if found { 40 | return cfg.Size(r) 41 | } 42 | 43 | if c >= 0 && c <= 255 { 44 | cfg := opConfigs[c] 45 | 46 | if cfg.Name != "" { 47 | return cfg.Size(r) 48 | } 49 | } 50 | 51 | return 0, fmt.Errorf("%w: %x", ErrUnsupportedOp, int16(c)) 52 | } 53 | 54 | func (c OpCode) Read(r *OpReader) (Op, error) { 55 | cfg, found := opConfigsExtra[c] 56 | if found { 57 | return cfg.Reader(r) 58 | } 59 | 60 | if c >= 0 && c <= 255 { 61 | cfg := opConfigs[c] 62 | 63 | if cfg.Name != "" { 64 | return cfg.Reader(r) 65 | } 66 | } 67 | 68 | return nil, fmt.Errorf("%w: %x", ErrUnsupportedOp, int16(c)) 69 | } 70 | 71 | type Op interface { 72 | Code() OpCode 73 | 74 | Pos() int 75 | 76 | String() string 77 | 78 | Fmt() Fmt 79 | } 80 | 81 | type opBase struct { 82 | pos int 83 | } 84 | 85 | func (o opBase) Pos() int { 86 | return o.pos 87 | } 88 | -------------------------------------------------------------------------------- /op_fmt.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | type Fmt interface { 9 | internalFmt() 10 | 11 | Size() int 12 | 13 | String() string 14 | } 15 | 16 | type Fmt10x struct { 17 | } 18 | 19 | func (f Fmt10x) internalFmt() {} 20 | 21 | func (f Fmt10x) Size() int { 22 | return 1 23 | } 24 | 25 | func (f Fmt10x) String() string { 26 | return "" 27 | } 28 | 29 | func (r *OpReader) readFmt10x() (Fmt10x, error) { 30 | var res Fmt10x 31 | 32 | if r.pos+1 > len(r.ops) { 33 | return res, io.EOF 34 | } 35 | 36 | r.pos += 1 37 | 38 | return res, nil 39 | } 40 | 41 | func fmt10xSize(_ *OpReader) (int, error) { 42 | return 1, nil 43 | } 44 | 45 | type Fmt11 struct { 46 | A uint8 47 | } 48 | 49 | func (f Fmt11) internalFmt() {} 50 | 51 | func (f Fmt11) Size() int { 52 | return 1 53 | } 54 | 55 | func (f Fmt11) String() string { 56 | return fmt.Sprintf("a=%v", f.A) 57 | } 58 | 59 | type Fmt11x struct{ Fmt11 } 60 | type Fmt10t struct{ Fmt11 } 61 | 62 | func (r *OpReader) readFmt11() (Fmt11, error) { 63 | var res Fmt11 64 | 65 | if r.pos+1 > len(r.ops) { 66 | return res, io.EOF 67 | } 68 | 69 | res.A = uint8((r.ops[r.pos] >> 8) & 0xFF) 70 | 71 | r.pos += 1 72 | 73 | return res, nil 74 | } 75 | 76 | func (r *OpReader) readFmt11x() (Fmt11x, error) { 77 | f, e := r.readFmt11() 78 | return Fmt11x{f}, e 79 | } 80 | 81 | func fmt11xSize(_ *OpReader) (int, error) { 82 | return 1, nil 83 | } 84 | 85 | func (r *OpReader) readFmt10t() (Fmt10t, error) { 86 | f, e := r.readFmt11() 87 | return Fmt10t{f}, e 88 | } 89 | 90 | func fmt10tSize(_ *OpReader) (int, error) { 91 | return 1, nil 92 | } 93 | 94 | type Fmt12 struct { 95 | A uint8 96 | B uint8 97 | } 98 | 99 | type Fmt12x struct{ Fmt12 } 100 | type Fmt11n struct{ Fmt12 } 101 | 102 | func (f Fmt12) internalFmt() {} 103 | 104 | func (f Fmt12) Size() int { 105 | return 1 106 | } 107 | 108 | func (f Fmt12) String() string { 109 | return fmt.Sprintf("a=%v, b=%v", f.A, f.B) 110 | } 111 | 112 | func (r *OpReader) readFmt12() (Fmt12, error) { 113 | var res Fmt12 114 | 115 | if r.pos+1 > len(r.ops) { 116 | return res, io.EOF 117 | } 118 | 119 | lower := r.ops[r.pos] 120 | res.A = uint8((lower >> 8) & 0xF) 121 | res.B = uint8((lower >> 12) & 0xF) 122 | 123 | r.pos += 1 124 | 125 | return res, nil 126 | } 127 | 128 | func (r *OpReader) readFmt12x() (Fmt12x, error) { 129 | f, e := r.readFmt12() 130 | return Fmt12x{f}, e 131 | } 132 | 133 | func fmt12xSize(_ *OpReader) (int, error) { 134 | return 1, nil 135 | } 136 | 137 | func (r *OpReader) readFmt11n() (Fmt11n, error) { 138 | f, e := r.readFmt12() 139 | return Fmt11n{f}, e 140 | } 141 | 142 | func fmt11nSize(_ *OpReader) (int, error) { 143 | return 1, nil 144 | } 145 | 146 | type Fmt20t struct { 147 | A uint16 148 | } 149 | 150 | func (f Fmt20t) internalFmt() {} 151 | 152 | func (f Fmt20t) Size() int { 153 | return 2 154 | } 155 | 156 | func (f Fmt20t) String() string { 157 | return fmt.Sprintf("a=%v", f.A) 158 | } 159 | 160 | func (r *OpReader) readFmt20t() (Fmt20t, error) { 161 | var res Fmt20t 162 | 163 | if r.pos+2 > len(r.ops) { 164 | return res, io.EOF 165 | } 166 | 167 | res.A = r.ops[r.pos+1] 168 | 169 | r.pos += 2 170 | 171 | return res, nil 172 | } 173 | 174 | func fmt20tSize(_ *OpReader) (int, error) { 175 | return 2, nil 176 | } 177 | 178 | type Fmt22 struct { 179 | A uint8 180 | B uint16 181 | } 182 | 183 | type Fmt22x struct{ Fmt22 } 184 | type Fmt21t struct{ Fmt22 } 185 | type Fmt21h struct{ Fmt22 } 186 | type Fmt21c struct{ Fmt22 } 187 | type Fmt21s struct{ Fmt22 } 188 | 189 | func (f Fmt22) internalFmt() {} 190 | 191 | func (f Fmt22) Size() int { 192 | return 2 193 | } 194 | 195 | func (f Fmt22) String() string { 196 | return fmt.Sprintf("a=%v, b=%v", f.A, f.B) 197 | } 198 | 199 | func (r *OpReader) readFmt22() (Fmt22, error) { 200 | var res Fmt22 201 | 202 | if r.pos+2 > len(r.ops) { 203 | return res, io.EOF 204 | } 205 | 206 | res.A = uint8((r.ops[r.pos] >> 8) & 0xFF) 207 | res.B = r.ops[r.pos+1] 208 | 209 | r.pos += 2 210 | 211 | return res, nil 212 | } 213 | 214 | func (r *OpReader) readFmt22x() (Fmt22x, error) { 215 | f, e := r.readFmt22() 216 | return Fmt22x{f}, e 217 | } 218 | 219 | func fmt22xSize(_ *OpReader) (int, error) { 220 | return 2, nil 221 | } 222 | 223 | func (r *OpReader) readFmt21t() (Fmt21t, error) { 224 | f, e := r.readFmt22() 225 | return Fmt21t{f}, e 226 | } 227 | 228 | func fmt21tSize(_ *OpReader) (int, error) { 229 | return 2, nil 230 | } 231 | 232 | func (r *OpReader) readFmt21h() (Fmt21h, error) { 233 | f, e := r.readFmt22() 234 | return Fmt21h{f}, e 235 | } 236 | 237 | func fmt21hSize(_ *OpReader) (int, error) { 238 | return 2, nil 239 | } 240 | 241 | func (r *OpReader) readFmt21c() (Fmt21c, error) { 242 | f, e := r.readFmt22() 243 | return Fmt21c{f}, e 244 | } 245 | 246 | func fmt21cSize(_ *OpReader) (int, error) { 247 | return 2, nil 248 | } 249 | 250 | func (r *OpReader) readFmt21s() (Fmt21s, error) { 251 | f, e := r.readFmt22() 252 | return Fmt21s{f}, e 253 | } 254 | 255 | func fmt21sSize(_ *OpReader) (int, error) { 256 | return 2, nil 257 | } 258 | 259 | type Fmt231 struct { 260 | A uint8 261 | B uint8 262 | C uint8 263 | } 264 | 265 | type Fmt23x struct{ Fmt231 } 266 | type Fmt22b struct{ Fmt231 } 267 | 268 | func (f Fmt231) internalFmt() {} 269 | 270 | func (f Fmt231) Size() int { 271 | return 2 272 | } 273 | 274 | func (f Fmt231) String() string { 275 | return fmt.Sprintf("a=%v, b=%v, c=%v", f.A, f.B, f.C) 276 | } 277 | 278 | func (r *OpReader) readFmt231() (Fmt231, error) { 279 | var res Fmt231 280 | 281 | if r.pos+2 > len(r.ops) { 282 | return res, io.EOF 283 | } 284 | 285 | res.A = uint8((r.ops[r.pos] >> 8) & 0xFF) 286 | 287 | upper := r.ops[r.pos+1] 288 | res.B = uint8(upper & 0xFF) 289 | res.C = uint8((upper >> 8) & 0xFF) 290 | 291 | r.pos += 2 292 | 293 | return res, nil 294 | } 295 | 296 | func (r *OpReader) readFmt23x() (Fmt23x, error) { 297 | f, e := r.readFmt231() 298 | return Fmt23x{f}, e 299 | } 300 | 301 | func fmt23xSize(_ *OpReader) (int, error) { 302 | return 2, nil 303 | } 304 | 305 | func (r *OpReader) readFmt22b() (Fmt22b, error) { 306 | f, e := r.readFmt231() 307 | return Fmt22b{f}, e 308 | } 309 | 310 | func fmt22bSize(_ *OpReader) (int, error) { 311 | return 2, nil 312 | } 313 | 314 | type Fmt232 struct { 315 | A uint8 316 | B uint8 317 | C uint16 318 | } 319 | 320 | type Fmt22t struct{ Fmt232 } 321 | type Fmt22s struct{ Fmt232 } 322 | type Fmt22c struct{ Fmt232 } 323 | type Fmt22cs struct{ Fmt232 } 324 | 325 | func (f Fmt232) internalFmt() {} 326 | 327 | func (f Fmt232) Size() int { 328 | return 2 329 | } 330 | 331 | func (f Fmt232) String() string { 332 | return fmt.Sprintf("a=%v, b=%v, c=%v", f.A, f.B, f.C) 333 | } 334 | 335 | func (r *OpReader) readFmt232() (Fmt232, error) { 336 | var res Fmt232 337 | 338 | if r.pos+2 > len(r.ops) { 339 | return res, io.EOF 340 | } 341 | 342 | lower := r.ops[r.pos] 343 | res.A = uint8((lower >> 8) & 0xF) 344 | res.B = uint8((lower >> 12) & 0xF) 345 | res.C = r.ops[r.pos+1] 346 | 347 | r.pos += 2 348 | 349 | return res, nil 350 | } 351 | 352 | func (r *OpReader) readFmt22t() (Fmt22t, error) { 353 | f, e := r.readFmt232() 354 | return Fmt22t{f}, e 355 | } 356 | 357 | func fmt22tSize(_ *OpReader) (int, error) { 358 | return 2, nil 359 | } 360 | 361 | func (r *OpReader) readFmt22s() (Fmt22s, error) { 362 | f, e := r.readFmt232() 363 | return Fmt22s{f}, e 364 | } 365 | 366 | func fmt22sSize(_ *OpReader) (int, error) { 367 | return 2, nil 368 | } 369 | 370 | func (r *OpReader) readFmt22c() (Fmt22c, error) { 371 | f, e := r.readFmt232() 372 | return Fmt22c{f}, e 373 | } 374 | 375 | func fmt22cSize(_ *OpReader) (int, error) { 376 | return 2, nil 377 | } 378 | 379 | func (r *OpReader) readFmt22cs() (Fmt22cs, error) { 380 | f, e := r.readFmt232() 381 | return Fmt22cs{f}, e 382 | } 383 | 384 | func fmt22csSize(_ *OpReader) (int, error) { 385 | return 2, nil 386 | } 387 | 388 | type Fmt30t struct { 389 | A uint32 390 | } 391 | 392 | func (f Fmt30t) internalFmt() {} 393 | 394 | func (f Fmt30t) Size() int { 395 | return 2 396 | } 397 | 398 | func (f Fmt30t) String() string { 399 | return fmt.Sprintf("a=%v", f.A) 400 | } 401 | 402 | func (r *OpReader) readFmt30t() (Fmt30t, error) { 403 | var res Fmt30t 404 | 405 | if r.pos+3 > len(r.ops) { 406 | return res, io.EOF 407 | } 408 | 409 | res.A = (uint32(r.ops[r.pos+2]) << 16) | uint32(r.ops[r.pos+1]) 410 | 411 | r.pos += 3 412 | 413 | return res, nil 414 | } 415 | 416 | func fmt30tSize(_ *OpReader) (int, error) { 417 | return 3, nil 418 | } 419 | 420 | type Fmt32x struct { 421 | A uint16 422 | B uint16 423 | } 424 | 425 | func (f Fmt32x) internalFmt() {} 426 | 427 | func (f Fmt32x) Size() int { 428 | return 3 429 | } 430 | 431 | func (f Fmt32x) String() string { 432 | return fmt.Sprintf("a=%v, b=%v", f.A, f.B) 433 | } 434 | 435 | func (r *OpReader) readFmt32x() (Fmt32x, error) { 436 | var res Fmt32x 437 | 438 | if r.pos+3 > len(r.ops) { 439 | return res, io.EOF 440 | } 441 | 442 | res.A = r.ops[r.pos+1] 443 | res.B = r.ops[r.pos+2] 444 | 445 | r.pos += 3 446 | 447 | return res, nil 448 | } 449 | 450 | func fmt32xSize(_ *OpReader) (int, error) { 451 | return 3, nil 452 | } 453 | 454 | type Fmt35c struct { 455 | A uint8 456 | B uint16 457 | C uint8 458 | D uint8 459 | E uint8 460 | F uint8 461 | G uint8 462 | } 463 | 464 | func (f Fmt35c) internalFmt() {} 465 | 466 | func (f Fmt35c) Size() int { 467 | return 3 468 | } 469 | 470 | func (f Fmt35c) String() string { 471 | return fmt.Sprintf("a=%v, b=%v, c=%v, d=%v, e=%v, f=%v, g=%v", f.A, f.B, f.C, f.D, f.E, f.F, f.G) 472 | } 473 | 474 | func (r *OpReader) readFmt35c() (Fmt35c, error) { 475 | var res Fmt35c 476 | 477 | if r.pos+3 > len(r.ops) { 478 | return res, io.EOF 479 | } 480 | 481 | lower := r.ops[r.pos] 482 | res.A = uint8((lower >> 12) & 0xF) 483 | res.G = uint8((lower >> 8) & 0xF) 484 | 485 | res.B = r.ops[r.pos+1] 486 | 487 | upper := r.ops[r.pos+2] 488 | res.C = uint8(upper & 0xF) 489 | res.D = uint8((upper >> 4) & 0xF) 490 | res.E = uint8((upper >> 8) & 0xF) 491 | res.F = uint8((upper >> 12) & 0xF) 492 | 493 | r.pos += 3 494 | 495 | return res, nil 496 | } 497 | 498 | func fmt35cSize(_ *OpReader) (int, error) { 499 | return 3, nil 500 | } 501 | 502 | type Fmt31 struct { 503 | A uint8 504 | B uint32 505 | } 506 | 507 | func (f Fmt31) internalFmt() {} 508 | 509 | func (f Fmt31) Size() int { 510 | return 3 511 | } 512 | 513 | func (f Fmt31) String() string { 514 | return fmt.Sprintf("a=%v, b=%v", f.A, f.B) 515 | } 516 | 517 | type Fmt31i struct{ Fmt31 } 518 | type Fmt31t struct{ Fmt31 } 519 | type Fmt31c struct{ Fmt31 } 520 | 521 | func (r *OpReader) readFmt31() (Fmt31, error) { 522 | var res Fmt31 523 | 524 | if r.pos+3 > len(r.ops) { 525 | return res, io.EOF 526 | } 527 | 528 | lower := r.ops[r.pos] 529 | res.A = uint8((lower >> 8) & 0xFF) 530 | res.B = (uint32(r.ops[r.pos+2]) << 16) | uint32(r.ops[r.pos+1]) 531 | 532 | r.pos += 3 533 | 534 | return res, nil 535 | } 536 | 537 | func (r *OpReader) readFmt31i() (Fmt31i, error) { 538 | f, e := r.readFmt31() 539 | return Fmt31i{f}, e 540 | } 541 | 542 | func fmt31iSize(_ *OpReader) (int, error) { 543 | return 3, nil 544 | } 545 | 546 | func (r *OpReader) readFmt31t() (Fmt31t, error) { 547 | f, e := r.readFmt31() 548 | return Fmt31t{f}, e 549 | } 550 | 551 | func fmt31tSize(_ *OpReader) (int, error) { 552 | return 3, nil 553 | } 554 | 555 | func (r *OpReader) readFmt31c() (Fmt31c, error) { 556 | f, e := r.readFmt31() 557 | return Fmt31c{f}, e 558 | } 559 | 560 | func fmt31cSize(_ *OpReader) (int, error) { 561 | return 3, nil 562 | } 563 | 564 | type Fmt3r struct { 565 | A uint8 566 | B uint16 567 | C uint16 568 | } 569 | 570 | func (f Fmt3r) internalFmt() {} 571 | 572 | func (f Fmt3r) Size() int { 573 | return 3 574 | } 575 | 576 | func (f Fmt3r) String() string { 577 | return fmt.Sprintf("a=%v, b=%v, c=%v", f.A, f.B, f.C) 578 | } 579 | 580 | type Fmt3rc struct{ Fmt3r } 581 | type Fmt3rms struct{ Fmt3r } 582 | type Fmt3rmi struct{ Fmt3r } 583 | 584 | func (r *OpReader) readFmt3r() (Fmt3r, error) { 585 | var res Fmt3r 586 | 587 | if r.pos+3 > len(r.ops) { 588 | return res, io.EOF 589 | } 590 | 591 | res.A = uint8((r.ops[r.pos] >> 8) & 0xFF) 592 | res.B = r.ops[r.pos+1] 593 | res.C = r.ops[r.pos+2] 594 | 595 | r.pos += 3 596 | 597 | return res, nil 598 | } 599 | 600 | func (r *OpReader) readFmt3rc() (Fmt3rc, error) { 601 | f, e := r.readFmt3r() 602 | return Fmt3rc{f}, e 603 | } 604 | 605 | func fmt3rcSize(_ *OpReader) (int, error) { 606 | return 3, nil 607 | } 608 | 609 | func (r *OpReader) readFmt3rms() (Fmt3rms, error) { 610 | f, e := r.readFmt3r() 611 | return Fmt3rms{f}, e 612 | } 613 | 614 | func fmt3rmsSize(_ *OpReader) (int, error) { 615 | return 3, nil 616 | } 617 | 618 | func (r *OpReader) readFmt3rmi() (Fmt3rmi, error) { 619 | f, e := r.readFmt3r() 620 | return Fmt3rmi{f}, e 621 | } 622 | 623 | func fmt3rmiSize(_ *OpReader) (int, error) { 624 | return 3, nil 625 | } 626 | 627 | type Fmt45cc struct { 628 | A uint8 629 | B uint16 630 | C uint8 631 | D uint8 632 | E uint8 633 | F uint8 634 | G uint8 635 | H uint16 636 | } 637 | 638 | func (f Fmt45cc) internalFmt() {} 639 | 640 | func (f Fmt45cc) Size() int { 641 | return 4 642 | } 643 | 644 | func (f Fmt45cc) String() string { 645 | return fmt.Sprintf("a=%v, b=%v, c=%v, d=%v, e=%v, f=%v, g=%v, h=%v", f.A, f.B, f.C, f.D, f.E, f.F, f.G, f.H) 646 | } 647 | 648 | func (r *OpReader) readFmt45cc() (Fmt45cc, error) { 649 | var res Fmt45cc 650 | 651 | if r.pos+4 > len(r.ops) { 652 | return res, io.EOF 653 | } 654 | 655 | lower := r.ops[r.pos] 656 | res.A = uint8((lower >> 12) & 0xF) 657 | res.G = uint8((lower >> 8) & 0xF) 658 | 659 | res.B = r.ops[r.pos+1] 660 | 661 | upper := r.ops[r.pos+2] 662 | res.C = uint8(upper & 0xF) 663 | res.D = uint8((upper >> 4) & 0xF) 664 | res.E = uint8((upper >> 8) & 0xF) 665 | res.F = uint8((upper >> 12) & 0xF) 666 | 667 | res.H = r.ops[r.pos+3] 668 | 669 | r.pos += 4 670 | 671 | return res, nil 672 | } 673 | 674 | func fmt45ccSize(_ *OpReader) (int, error) { 675 | return 4, nil 676 | } 677 | 678 | type Fmt4rcc struct { 679 | A uint8 680 | B uint16 681 | C uint16 682 | H uint16 683 | } 684 | 685 | func (f Fmt4rcc) internalFmt() {} 686 | 687 | func (f Fmt4rcc) Size() int { 688 | return 4 689 | } 690 | 691 | func (f Fmt4rcc) String() string { 692 | return fmt.Sprintf("a=%v, b=%v, c=%v, h=%v", f.A, f.B, f.C, f.H) 693 | } 694 | 695 | func (r *OpReader) readFmt4rcc() (Fmt4rcc, error) { 696 | var res Fmt4rcc 697 | 698 | if r.pos+4 > len(r.ops) { 699 | return res, io.EOF 700 | } 701 | 702 | lower := r.ops[r.pos] 703 | res.A = uint8((lower >> 8) & 0xFF) 704 | 705 | res.B = r.ops[r.pos+1] 706 | res.C = r.ops[r.pos+1] 707 | res.H = r.ops[r.pos+3] 708 | 709 | r.pos += 4 710 | 711 | return res, nil 712 | } 713 | 714 | func fmt4rccSize(_ *OpReader) (int, error) { 715 | return 4, nil 716 | } 717 | 718 | type Fmt51l struct { 719 | A uint8 720 | B uint64 721 | } 722 | 723 | func (f Fmt51l) internalFmt() {} 724 | 725 | func (f Fmt51l) Size() int { 726 | return 5 727 | } 728 | 729 | func (f Fmt51l) String() string { 730 | return fmt.Sprintf("a=%v, b=%v", f.A, f.B) 731 | } 732 | 733 | func (r *OpReader) readFmt51l() (Fmt51l, error) { 734 | var res Fmt51l 735 | 736 | if r.pos+5 > len(r.ops) { 737 | return res, io.EOF 738 | } 739 | 740 | lower := r.ops[r.pos] 741 | res.A = uint8((lower >> 8) & 0xFF) 742 | res.B = (uint64(r.ops[r.pos+4]) << 48) | (uint64(r.ops[r.pos+3]) << 32) | 743 | (uint64(r.ops[r.pos+2]) << 16) | uint64(r.ops[r.pos+1]) 744 | 745 | r.pos += 5 746 | 747 | return res, nil 748 | } 749 | 750 | func fmt51lSize(_ *OpReader) (int, error) { 751 | return 5, nil 752 | } 753 | -------------------------------------------------------------------------------- /op_pseudo.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | type OpPseudoPackedSwitchPayload struct { 9 | opBase 10 | FmtPackedSwitchPayload 11 | } 12 | 13 | func (o OpPseudoPackedSwitchPayload) Code() OpCode { 14 | return OpCodePseudoPackedSwitchPayload 15 | } 16 | 17 | func (o OpPseudoPackedSwitchPayload) Fmt() Fmt { 18 | return o.FmtPackedSwitchPayload 19 | } 20 | 21 | func (o OpPseudoPackedSwitchPayload) String() string { 22 | return fmt.Sprintf("0x%x: pseudo-packed-switch-payload %v", o.pos, o.FmtPackedSwitchPayload.String()) 23 | } 24 | 25 | type FmtPackedSwitchPayload struct { 26 | FirstKey int32 27 | Targets []int32 28 | } 29 | 30 | func (f FmtPackedSwitchPayload) internalFmt() {} 31 | 32 | func (f FmtPackedSwitchPayload) Size() int { 33 | return (len(f.Targets) * 2) + 4 34 | } 35 | 36 | func (f FmtPackedSwitchPayload) String() string { 37 | return fmt.Sprintf("FirstKey=%v, Targets=%v", f.FirstKey, f.Targets) 38 | } 39 | 40 | func (r *OpReader) readFmtPackedSwitchPayload() (FmtPackedSwitchPayload, error) { 41 | var res FmtPackedSwitchPayload 42 | 43 | if r.pos+4 > len(r.ops) { 44 | return res, io.EOF 45 | } 46 | 47 | if r.ops[r.pos] != 0x0100 { 48 | return res, fmt.Errorf("%w: unexpected op 0x%x", ErrMalformedOp, r.ops[r.pos]) 49 | } 50 | 51 | size := int(r.ops[r.pos+1]) 52 | res.Targets = make([]int32, size) 53 | res.FirstKey = int32((uint32(r.ops[r.pos+3]) << 16) | uint32(r.ops[r.pos+2])) 54 | r.pos += 4 55 | 56 | if r.pos+(size*2) > len(r.ops) { 57 | return res, io.EOF 58 | } 59 | 60 | for i := 0; i < size; i++ { 61 | res.Targets[i] = int32((uint32(r.ops[r.pos+1]) << 16) | uint32(r.ops[r.pos])) 62 | r.pos += 2 63 | } 64 | 65 | return res, nil 66 | } 67 | 68 | func fmtPackedSwitchPayloadSize(r *OpReader) (int, error) { 69 | if r.pos+2 > len(r.ops) { 70 | return 0, io.EOF 71 | } 72 | 73 | if r.ops[r.pos] != 0x0100 { 74 | return 0, fmt.Errorf("%w: unexpected op 0x%x", ErrMalformedOp, r.ops[r.pos]) 75 | } 76 | 77 | size := int(r.ops[r.pos+1]) 78 | 79 | return (size * 2) + 4, nil 80 | } 81 | 82 | type OpPseudoSparseSwitchPayload struct { 83 | opBase 84 | FmtSparseSwitchPayload 85 | } 86 | 87 | func (o OpPseudoSparseSwitchPayload) Code() OpCode { 88 | return OpCodePseudoSparseSwitchPayload 89 | } 90 | 91 | func (o OpPseudoSparseSwitchPayload) Fmt() Fmt { 92 | return o.FmtSparseSwitchPayload 93 | } 94 | 95 | func (o OpPseudoSparseSwitchPayload) String() string { 96 | return fmt.Sprintf("0x%x: pseudo-sparse-switch-payload %v", o.pos, o.FmtSparseSwitchPayload.String()) 97 | } 98 | 99 | type FmtSparseSwitchPayload struct { 100 | Keys []int32 101 | Targets []int32 102 | } 103 | 104 | func (f FmtSparseSwitchPayload) internalFmt() {} 105 | 106 | func (f FmtSparseSwitchPayload) Size() int { 107 | return (len(f.Targets) * 4) + 2 108 | } 109 | 110 | func (f FmtSparseSwitchPayload) String() string { 111 | return fmt.Sprintf("Keys=%v, Targets=%v", f.Keys, f.Targets) 112 | } 113 | 114 | func (r *OpReader) readFmtSparseSwitchPayload() (FmtSparseSwitchPayload, error) { 115 | var res FmtSparseSwitchPayload 116 | 117 | if r.pos+2 > len(r.ops) { 118 | return res, io.EOF 119 | } 120 | 121 | if r.ops[r.pos] != 0x0200 { 122 | return res, fmt.Errorf("%w: unexpected op 0x%x", ErrMalformedOp, r.ops[r.pos]) 123 | } 124 | 125 | size := int(r.ops[r.pos+1]) 126 | res.Keys = make([]int32, size) 127 | res.Targets = make([]int32, size) 128 | r.pos += 2 129 | 130 | if r.pos+(size*4) > len(r.ops) { 131 | return res, io.EOF 132 | } 133 | 134 | for i := 0; i < size; i++ { 135 | res.Keys[i] = int32((uint32(r.ops[r.pos+1]) << 16) | uint32(r.ops[r.pos])) 136 | r.pos += 2 137 | } 138 | 139 | for i := 0; i < size; i++ { 140 | res.Targets[i] = int32((uint32(r.ops[r.pos+1]) << 16) | uint32(r.ops[r.pos])) 141 | r.pos += 2 142 | } 143 | 144 | return res, nil 145 | } 146 | 147 | func fmtSparseSwitchPayloadSize(r *OpReader) (int, error) { 148 | if r.pos+2 > len(r.ops) { 149 | return 0, io.EOF 150 | } 151 | 152 | if r.ops[r.pos] != 0x0200 { 153 | return 0, fmt.Errorf("%w: unexpected op 0x%x", ErrMalformedOp, r.ops[r.pos]) 154 | } 155 | 156 | size := int(r.ops[r.pos+1]) 157 | 158 | return (size * 4) + 2, nil 159 | } 160 | 161 | type OpPseudoFillArrayDataPayload struct { 162 | opBase 163 | FmtFillArrayDataPayload 164 | } 165 | 166 | func (o OpPseudoFillArrayDataPayload) Code() OpCode { 167 | return OpCodePseudoFillArrayDataPayload 168 | } 169 | 170 | func (o OpPseudoFillArrayDataPayload) Fmt() Fmt { 171 | return o.FmtFillArrayDataPayload 172 | } 173 | 174 | func (o OpPseudoFillArrayDataPayload) String() string { 175 | return fmt.Sprintf("0x%x: pseudo-fill-array-data-payload %v", o.pos, o.FmtFillArrayDataPayload.String()) 176 | } 177 | 178 | type FmtFillArrayDataPayload struct { 179 | ElementWidth uint16 180 | ArraySize uint32 181 | Data []uint8 182 | } 183 | 184 | func (f FmtFillArrayDataPayload) internalFmt() {} 185 | 186 | func (f FmtFillArrayDataPayload) Size() int { 187 | return int((uint64(f.ArraySize)*uint64(f.ElementWidth)+1)/2 + 4) 188 | } 189 | 190 | func (f FmtFillArrayDataPayload) String() string { 191 | return fmt.Sprintf("ElementWidth=%v, ArraySize=%v, Data=%v", f.ElementWidth, f.ArraySize, f.Data) 192 | } 193 | 194 | func (r *OpReader) readFmtFillArrayDataPayload() (FmtFillArrayDataPayload, error) { 195 | var res FmtFillArrayDataPayload 196 | 197 | if r.pos+4 > len(r.ops) { 198 | return res, io.EOF 199 | } 200 | 201 | if r.ops[r.pos] != 0x0300 { 202 | return res, fmt.Errorf("%w: unexpected op 0x%x", ErrMalformedOp, r.ops[r.pos]) 203 | } 204 | 205 | res.ElementWidth = r.ops[r.pos+1] 206 | res.ArraySize = (uint32(r.ops[r.pos+3]) << 16) | uint32(r.ops[r.pos+2]) 207 | r.pos += 4 208 | 209 | readSize := int((uint64(res.ArraySize)*uint64(res.ElementWidth) + 1) / 2) 210 | 211 | if r.pos+readSize > len(r.ops) { 212 | return res, io.EOF 213 | } 214 | 215 | res.Data = make([]uint8, readSize*2) 216 | 217 | for i := 0; i < readSize; i++ { 218 | read := r.ops[r.pos] 219 | res.Data[i*2] = uint8(read & 0xFF) 220 | res.Data[i*2+1] = uint8((read >> 8) & 0xFF) 221 | r.pos += 1 222 | } 223 | 224 | return res, nil 225 | } 226 | 227 | func fmtFillArrayDataPayloadSize(r *OpReader) (int, error) { 228 | if r.pos+4 > len(r.ops) { 229 | return 0, io.EOF 230 | } 231 | 232 | if r.ops[r.pos] != 0x0300 { 233 | return 0, fmt.Errorf("%w: unexpected op 0x%x", ErrMalformedOp, r.ops[r.pos]) 234 | } 235 | 236 | width := uint64(r.ops[r.pos+1]) 237 | size := uint64((uint32(r.ops[r.pos+3]) << 16) | uint32(r.ops[r.pos+2])) 238 | 239 | return int((width*size+1)/2 + 4), nil 240 | } 241 | -------------------------------------------------------------------------------- /op_reader.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | var ( 10 | ErrUnsupportedOp = errors.New("unsupported op") 11 | ErrMalformedOp = errors.New("malformed op") 12 | ) 13 | 14 | type OpReader struct { 15 | ops []uint16 16 | pos int 17 | } 18 | 19 | func NewOpReader(ops []uint16) *OpReader { 20 | return &OpReader{ 21 | ops: ops, 22 | pos: 0, 23 | } 24 | } 25 | 26 | func (r *OpReader) HasMore() bool { 27 | return r.pos < len(r.ops) 28 | } 29 | 30 | func (r *OpReader) Pos() int { 31 | return r.pos 32 | } 33 | 34 | func (r *OpReader) Seek(pos int) { 35 | r.pos = pos 36 | } 37 | 38 | func (r *OpReader) PeekCode() (OpCode, error) { 39 | if r.pos >= len(r.ops) { 40 | return OpCodeInvalid, io.EOF 41 | } 42 | 43 | value := r.ops[r.pos] 44 | code := OpCode(value & 0xFF) 45 | 46 | if code == OpCodeNop { 47 | pseudo := (value >> 8) & 0xFF 48 | 49 | switch pseudo { 50 | case 0: 51 | // Standard noop 52 | case 1: 53 | return OpCodePseudoPackedSwitchPayload, nil 54 | case 2: 55 | return OpCodePseudoSparseSwitchPayload, nil 56 | case 3: 57 | return OpCodePseudoFillArrayDataPayload, nil 58 | default: 59 | return OpCodeInvalid, fmt.Errorf("%w: unknown pseudo %x", ErrUnsupportedOp, pseudo) 60 | } 61 | 62 | } 63 | 64 | return code, nil 65 | } 66 | 67 | func (r *OpReader) Skip() error { 68 | op, err := r.PeekCode() 69 | if err != nil { 70 | return err 71 | } 72 | 73 | size, err := op.Size(r) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | r.pos += size 79 | 80 | return nil 81 | } 82 | 83 | func (r *OpReader) Read() (Op, error) { 84 | op, err := r.PeekCode() 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | return op.Read(r) 90 | } 91 | 92 | func init() { 93 | opConfigsExtra[OpCodePseudoPackedSwitchPayload] = opConfig{ 94 | Name: "pseudo-packed-switch-payload", 95 | Size: fmtPackedSwitchPayloadSize, 96 | Reader: func(r *OpReader) (Op, error) { 97 | pos := r.pos 98 | 99 | f, err := r.readFmtPackedSwitchPayload() 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | return OpPseudoPackedSwitchPayload{ 105 | opBase{pos: pos}, 106 | f, 107 | }, nil 108 | }, 109 | } 110 | opConfigsExtra[OpCodePseudoSparseSwitchPayload] = opConfig{ 111 | Name: "pseudo-sparse-switch-payload", 112 | Size: fmtSparseSwitchPayloadSize, 113 | Reader: func(r *OpReader) (Op, error) { 114 | pos := r.pos 115 | 116 | f, err := r.readFmtSparseSwitchPayload() 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | return OpPseudoSparseSwitchPayload{ 122 | opBase{pos: pos}, 123 | f, 124 | }, nil 125 | }, 126 | } 127 | opConfigsExtra[OpCodePseudoFillArrayDataPayload] = opConfig{ 128 | Name: "pseudo-fill-array-data-payload", 129 | Size: fmtFillArrayDataPayloadSize, 130 | Reader: func(r *OpReader) (Op, error) { 131 | pos := r.pos 132 | 133 | f, err := r.readFmtFillArrayDataPayload() 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | return OpPseudoFillArrayDataPayload{ 139 | opBase{pos: pos}, 140 | f, 141 | }, nil 142 | }, 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /opts.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | type config struct { 4 | readSlots int 5 | } 6 | 7 | type Opt func(*config) 8 | 9 | func WithReadCache(slots int) Opt { 10 | return func(c *config) { 11 | c.readSlots = slots 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /reader.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "unicode/utf16" 9 | ) 10 | 11 | var ( 12 | ErrInvalidHeader = errors.New("invalid header") 13 | ErrInvalidStringID = errors.New("invalid string id") 14 | ErrNoStringEnd = errors.New("failed to find terminating byte") 15 | ErrInvalidTypeID = errors.New("invalid type id") 16 | ErrInvalidProtoID = errors.New("invalid proto id") 17 | ErrInvalidFieldID = errors.New("invalid field id") 18 | ErrInvalidMethodID = errors.New("invalid method id") 19 | ErrInvalidClassDefID = errors.New("invalid class def id") 20 | ErrInvalidTryHandlerOffset = errors.New("invalid try handler offset") 21 | ErrUnexpectedType = errors.New("unexpected type") 22 | ) 23 | 24 | const ( 25 | EndianConst = uint32(0x12345678) 26 | ReverseEndianConst = uint32(0x78563412) 27 | NoIndex = uint32(0xFFFFFFFF) 28 | ) 29 | 30 | type Reader struct { 31 | file io.ReaderAt 32 | Version string 33 | ReverseEndian bool 34 | 35 | mapOff uint32 36 | StringIDCount uint32 37 | stringIDOff uint32 38 | TypeIDCount uint32 39 | typeIDOff uint32 40 | ProtoIDCount uint32 41 | protoIDOff uint32 42 | FieldIDCount uint32 43 | fieldIDOff uint32 44 | MethodIDCount uint32 45 | methodIDOff uint32 46 | ClassDefCount uint32 47 | classDefOff uint32 48 | } 49 | 50 | func Read(file io.ReaderAt, opts ...Opt) (*Reader, error) { 51 | cfg := &config{} 52 | 53 | for _, opt := range opts { 54 | opt(cfg) 55 | } 56 | 57 | if cfg.readSlots > 0 { 58 | file = newCachedReader(file, cfg.readSlots) 59 | } 60 | 61 | header := make([]byte, 112) 62 | 63 | _, err := file.ReadAt(header[0:40], 0) 64 | if err != nil { 65 | return nil, fmt.Errorf("%w: %w", ErrInvalidHeader, err) 66 | } 67 | 68 | // Validate file magic 69 | if header[0] != 'd' || header[1] != 'e' || header[2] != 'x' || header[3] != '\n' || header[7] != 0 { 70 | return nil, fmt.Errorf("%w: invalid magic", ErrInvalidHeader) 71 | } 72 | 73 | ver := string(header[4:7]) 74 | 75 | // TODO: checksum, signature 76 | 77 | headerSize := binary.LittleEndian.Uint32(header[36:40]) 78 | if headerSize < 112 { 79 | return nil, fmt.Errorf("%w: header too small %d", ErrInvalidHeader, headerSize) 80 | } 81 | 82 | _, err = file.ReadAt(header[40:112], 40) 83 | if err != nil { 84 | return nil, fmt.Errorf("%w: %w", ErrInvalidHeader, err) 85 | } 86 | 87 | endian := binary.LittleEndian.Uint32(header[40:44]) 88 | reverseEndian := endian == ReverseEndianConst 89 | 90 | r := &Reader{ 91 | file: file, 92 | Version: ver, 93 | ReverseEndian: reverseEndian, 94 | } 95 | 96 | r.mapOff = r.toUint(header[52:56]) 97 | r.StringIDCount = r.toUint(header[56:60]) 98 | r.stringIDOff = r.toUint(header[60:64]) 99 | r.TypeIDCount = r.toUint(header[64:68]) 100 | r.typeIDOff = r.toUint(header[68:72]) 101 | r.ProtoIDCount = r.toUint(header[72:76]) 102 | r.protoIDOff = r.toUint(header[76:80]) 103 | r.FieldIDCount = r.toUint(header[80:84]) 104 | r.fieldIDOff = r.toUint(header[84:88]) 105 | r.MethodIDCount = r.toUint(header[88:92]) 106 | r.methodIDOff = r.toUint(header[92:96]) 107 | r.ClassDefCount = r.toUint(header[96:100]) 108 | r.classDefOff = r.toUint(header[100:104]) 109 | 110 | return r, nil 111 | } 112 | 113 | type String struct { 114 | Raw []uint16 115 | Parsed string 116 | } 117 | 118 | func StringFromUTF16(points []uint16) String { 119 | return String{ 120 | Raw: points, 121 | Parsed: string(utf16.Decode(points)), 122 | } 123 | } 124 | 125 | func (s String) String() string { 126 | return s.Parsed 127 | } 128 | 129 | func (r *Reader) ReadString(id uint32) (String, error) { 130 | if id >= r.StringIDCount { 131 | return String{}, ErrInvalidStringID 132 | } 133 | 134 | idPos := r.stringIDOff + (id * 4) 135 | 136 | strPos, err := r.readUint(idPos) 137 | if err != nil { 138 | return String{}, err 139 | } 140 | 141 | strSize, n, err := r.readUleb128(strPos) 142 | if err != nil { 143 | return String{}, err 144 | } 145 | 146 | if strSize == 0 { 147 | return String{}, nil 148 | } 149 | 150 | // mutf-8 encodes upto 3 bytes per char 151 | data := make([]byte, strSize*3+1) 152 | 153 | rsize, err := r.file.ReadAt(data, int64(strPos+n)) 154 | if err != nil && err != io.EOF { 155 | return String{}, err 156 | } 157 | 158 | // Find the null terminating byte 159 | pos := 0 160 | 161 | for pos < rsize { 162 | if data[pos] == 0 { 163 | break 164 | } 165 | 166 | pos++ 167 | } 168 | 169 | if pos == rsize { 170 | return String{}, ErrNoStringEnd 171 | } 172 | 173 | str, err := MUTF8Decode(data[:pos], int(strSize)) 174 | if err != nil { 175 | return String{}, fmt.Errorf("mutf8-8 decode failed: %w", err) 176 | } 177 | 178 | return str, nil 179 | } 180 | 181 | type Type struct { 182 | DescriptorStringID uint32 183 | } 184 | 185 | func (r *Reader) ReadType(id uint32) (Type, error) { 186 | var ( 187 | res Type 188 | err error 189 | ) 190 | 191 | if id >= r.TypeIDCount { 192 | return res, ErrInvalidTypeID 193 | } 194 | 195 | entryPos := r.typeIDOff + (id * 4) 196 | 197 | res.DescriptorStringID, err = r.readUint(entryPos) 198 | if err != nil { 199 | return res, err 200 | } 201 | 202 | return res, nil 203 | } 204 | 205 | type Proto struct { 206 | ShortyStringID uint32 207 | ReturnTypeID uint32 208 | ParametersTypeListOff uint32 209 | } 210 | 211 | func (r *Reader) ReadProto(id uint32) (Proto, error) { 212 | var ( 213 | res Proto 214 | err error 215 | ) 216 | 217 | if id >= r.ProtoIDCount { 218 | return res, ErrInvalidProtoID 219 | } 220 | 221 | entryPos := r.protoIDOff + (id * 12) 222 | 223 | res.ShortyStringID, err = r.readUint(entryPos) 224 | if err != nil { 225 | return res, err 226 | } 227 | 228 | res.ReturnTypeID, err = r.readUint(entryPos + 4) 229 | if err != nil { 230 | return res, err 231 | } 232 | 233 | res.ParametersTypeListOff, err = r.readUint(entryPos + 8) 234 | if err != nil { 235 | return res, err 236 | } 237 | 238 | return res, nil 239 | } 240 | 241 | type Field struct { 242 | ClassTypeID uint16 243 | TypeID uint16 244 | NameStringID uint32 245 | } 246 | 247 | type FieldRef struct { 248 | Class String 249 | Type TypeDescriptor 250 | Name String 251 | } 252 | 253 | func (r FieldRef) String() string { 254 | return fmt.Sprintf("%v:%v:%v", r.Class, r.Name, r.Type) 255 | } 256 | 257 | func (r *Reader) ReadField(id uint32) (Field, error) { 258 | var ( 259 | res Field 260 | err error 261 | ) 262 | 263 | if id >= r.FieldIDCount { 264 | return res, ErrInvalidFieldID 265 | } 266 | 267 | entryPos := r.fieldIDOff + (id * 8) 268 | 269 | res.ClassTypeID, err = r.readUshort(entryPos) 270 | if err != nil { 271 | return res, err 272 | } 273 | 274 | res.TypeID, err = r.readUshort(entryPos + 2) 275 | if err != nil { 276 | return res, err 277 | } 278 | 279 | res.NameStringID, err = r.readUint(entryPos + 4) 280 | if err != nil { 281 | return res, err 282 | } 283 | 284 | return res, nil 285 | } 286 | 287 | func (r *Reader) ReadFieldAndParse(id uint32) (FieldRef, error) { 288 | var res FieldRef 289 | 290 | f, err := r.ReadField(id) 291 | if err != nil { 292 | return res, err 293 | } 294 | 295 | res.Name, err = r.ReadString(f.NameStringID) 296 | if err != nil { 297 | return res, fmt.Errorf("bad field name: %w", err) 298 | } 299 | 300 | res.Type, err = r.ReadTypeAndParse(uint32(f.TypeID)) 301 | if err != nil { 302 | return res, fmt.Errorf("bad field type: %w", err) 303 | } 304 | 305 | parsedDesc, err := r.ReadTypeAndParse(uint32(f.ClassTypeID)) 306 | if err != nil { 307 | return res, fmt.Errorf("bad class type: %w", err) 308 | } 309 | 310 | if !parsedDesc.IsClass() || parsedDesc.IsArray() { 311 | return res, fmt.Errorf("bad class type: %w", ErrUnexpectedType) 312 | } 313 | 314 | res.Class = parsedDesc.ClassName 315 | 316 | return res, nil 317 | } 318 | 319 | type Method struct { 320 | ClassTypeID uint16 321 | ProtoID uint16 322 | NameStringID uint32 323 | } 324 | 325 | type MethodRef struct { 326 | Class String 327 | Name String 328 | Shorty String 329 | ReturnType TypeDescriptor 330 | Params []TypeDescriptor 331 | } 332 | 333 | func (r MethodRef) String() string { 334 | return fmt.Sprintf("%v:%v:(%v):%v", r.Class, r.Name, r.Params, r.ReturnType) 335 | } 336 | 337 | func (r *Reader) ReadMethod(id uint32) (Method, error) { 338 | var ( 339 | res Method 340 | err error 341 | ) 342 | 343 | if id >= r.MethodIDCount { 344 | return res, ErrInvalidMethodID 345 | } 346 | 347 | entryPos := r.methodIDOff + (id * 8) 348 | 349 | res.ClassTypeID, err = r.readUshort(entryPos) 350 | if err != nil { 351 | return res, err 352 | } 353 | 354 | res.ProtoID, err = r.readUshort(entryPos + 2) 355 | if err != nil { 356 | return res, err 357 | } 358 | 359 | res.NameStringID, err = r.readUint(entryPos + 4) 360 | if err != nil { 361 | return res, err 362 | } 363 | 364 | return res, nil 365 | } 366 | 367 | func (r *Reader) ReadMethodAndParse(id uint32) (MethodRef, error) { 368 | var res MethodRef 369 | 370 | m, err := r.ReadMethod(id) 371 | if err != nil { 372 | return res, err 373 | } 374 | 375 | res.Name, err = r.ReadString(m.NameStringID) 376 | if err != nil { 377 | return res, fmt.Errorf("bad method name: %w", err) 378 | } 379 | 380 | parsedDesc, err := r.ReadTypeAndParse(uint32(m.ClassTypeID)) 381 | if err != nil { 382 | return res, fmt.Errorf("bad class type: %w", err) 383 | } 384 | 385 | if !parsedDesc.IsClass() && !parsedDesc.IsArray() { 386 | return res, fmt.Errorf("bad class type: %w: %v", ErrUnexpectedType, parsedDesc) 387 | } 388 | 389 | res.Class = parsedDesc.ClassName 390 | 391 | proto, err := r.ReadProto(uint32(m.ProtoID)) 392 | if err != nil { 393 | return res, fmt.Errorf("%w: bad method proto: %w", ErrBadClass, err) 394 | } 395 | 396 | res.Shorty, err = r.ReadString(proto.ShortyStringID) 397 | if err != nil { 398 | return res, fmt.Errorf("bad method shorty: %w", err) 399 | } 400 | 401 | res.ReturnType, err = r.ReadTypeAndParse(proto.ReturnTypeID) 402 | if err != nil { 403 | return res, fmt.Errorf("bad method type: %w", err) 404 | } 405 | 406 | if proto.ParametersTypeListOff != 0 { 407 | plist, err := r.ReadTypeList(proto.ParametersTypeListOff) 408 | if err != nil { 409 | return res, fmt.Errorf("bad method params: %w", err) 410 | } 411 | 412 | res.Params = make([]TypeDescriptor, len(plist.TypeIds)) 413 | 414 | for i, tid := range plist.TypeIds { 415 | res.Params[i], err = r.ReadTypeAndParse(uint32(tid)) 416 | if err != nil { 417 | return res, fmt.Errorf("bad method paramm: %w", err) 418 | } 419 | } 420 | } 421 | 422 | return res, nil 423 | } 424 | 425 | type ClassDef struct { 426 | ClassTypeID uint32 427 | AccessFlags uint32 428 | SuperclassTypeID uint32 429 | InterfacesTypeListOff uint32 430 | SourceFileStringID uint32 431 | AnnotationsDirectoryOff uint32 432 | ClassDataOff uint32 433 | StaticValuesOff uint32 434 | } 435 | 436 | func (r *Reader) ReadClassDef(id uint32) (ClassDef, error) { 437 | var ( 438 | res ClassDef 439 | err error 440 | ) 441 | 442 | if id >= r.ClassDefCount { 443 | return res, ErrInvalidClassDefID 444 | } 445 | 446 | entryPos := r.classDefOff + (id * 32) 447 | 448 | res.ClassTypeID, err = r.readUint(entryPos) 449 | if err != nil { 450 | return res, err 451 | } 452 | 453 | res.AccessFlags, err = r.readUint(entryPos + 4) 454 | if err != nil { 455 | return res, err 456 | } 457 | 458 | res.SuperclassTypeID, err = r.readUint(entryPos + 8) 459 | if err != nil { 460 | return res, err 461 | } 462 | 463 | res.InterfacesTypeListOff, err = r.readUint(entryPos + 12) 464 | if err != nil { 465 | return res, err 466 | } 467 | 468 | res.SourceFileStringID, err = r.readUint(entryPos + 16) 469 | if err != nil { 470 | return res, err 471 | } 472 | 473 | res.AnnotationsDirectoryOff, err = r.readUint(entryPos + 20) 474 | if err != nil { 475 | return res, err 476 | } 477 | 478 | res.ClassDataOff, err = r.readUint(entryPos + 24) 479 | if err != nil { 480 | return res, err 481 | } 482 | 483 | res.StaticValuesOff, err = r.readUint(entryPos + 28) 484 | if err != nil { 485 | return res, err 486 | } 487 | 488 | return res, nil 489 | } 490 | 491 | type EncodedField struct { 492 | FieldID uint32 493 | AccessFlags uint32 494 | } 495 | 496 | type EncodedMethod struct { 497 | MethodID uint32 498 | AccessFlags uint32 499 | CodeOff uint32 500 | } 501 | 502 | type ClassData struct { 503 | StaticFields []EncodedField 504 | InstanceFields []EncodedField 505 | DirectMethods []EncodedMethod 506 | VirtualMethods []EncodedMethod 507 | } 508 | 509 | func (r *Reader) ReadClassData(off uint32) (ClassData, error) { 510 | if off == 0 { 511 | panic("invalid class data offset") 512 | } 513 | 514 | var ( 515 | res ClassData 516 | err error 517 | ) 518 | 519 | staticCount, n, err := r.readUleb128(off) 520 | if err != nil { 521 | return res, err 522 | } 523 | 524 | res.StaticFields = make([]EncodedField, staticCount) 525 | off += n 526 | 527 | instanceCount, n, err := r.readUleb128(off) 528 | if err != nil { 529 | return res, err 530 | } 531 | 532 | res.InstanceFields = make([]EncodedField, instanceCount) 533 | off += n 534 | 535 | directCount, n, err := r.readUleb128(off) 536 | if err != nil { 537 | return res, err 538 | } 539 | 540 | res.DirectMethods = make([]EncodedMethod, directCount) 541 | off += n 542 | 543 | virtualCount, n, err := r.readUleb128(off) 544 | if err != nil { 545 | return res, err 546 | } 547 | 548 | res.VirtualMethods = make([]EncodedMethod, virtualCount) 549 | off += n 550 | 551 | lastFieldId := uint32(0) 552 | 553 | for i := uint32(0); i < staticCount; i++ { 554 | idOff, n, err := r.readUleb128(off) 555 | if err != nil { 556 | return res, err 557 | } 558 | 559 | lastFieldId += idOff 560 | off += n 561 | 562 | flags, n, err := r.readUleb128(off) 563 | if err != nil { 564 | return res, err 565 | } 566 | 567 | off += n 568 | 569 | res.StaticFields[i].FieldID = lastFieldId 570 | res.StaticFields[i].AccessFlags = flags 571 | } 572 | 573 | lastFieldId = uint32(0) 574 | 575 | for i := uint32(0); i < instanceCount; i++ { 576 | idOff, n, err := r.readUleb128(off) 577 | if err != nil { 578 | return res, err 579 | } 580 | 581 | lastFieldId += idOff 582 | off += n 583 | 584 | flags, n, err := r.readUleb128(off) 585 | if err != nil { 586 | return res, err 587 | } 588 | 589 | off += n 590 | 591 | res.InstanceFields[i].FieldID = lastFieldId 592 | res.InstanceFields[i].AccessFlags = flags 593 | } 594 | 595 | lastMethodId := uint32(0) 596 | 597 | for i := uint32(0); i < directCount; i++ { 598 | idOff, n, err := r.readUleb128(off) 599 | if err != nil { 600 | return res, err 601 | } 602 | 603 | lastMethodId += idOff 604 | off += n 605 | 606 | flags, n, err := r.readUleb128(off) 607 | if err != nil { 608 | return res, err 609 | } 610 | 611 | off += n 612 | 613 | code, n, err := r.readUleb128(off) 614 | if err != nil { 615 | return res, err 616 | } 617 | 618 | off += n 619 | 620 | res.DirectMethods[i].MethodID = lastMethodId 621 | res.DirectMethods[i].AccessFlags = flags 622 | res.DirectMethods[i].CodeOff = code 623 | } 624 | 625 | lastMethodId = uint32(0) 626 | 627 | for i := uint32(0); i < virtualCount; i++ { 628 | idOff, n, err := r.readUleb128(off) 629 | if err != nil { 630 | return res, err 631 | } 632 | 633 | lastMethodId += idOff 634 | off += n 635 | 636 | flags, n, err := r.readUleb128(off) 637 | if err != nil { 638 | return res, err 639 | } 640 | 641 | off += n 642 | 643 | code, n, err := r.readUleb128(off) 644 | if err != nil { 645 | return res, err 646 | } 647 | 648 | off += n 649 | 650 | res.VirtualMethods[i].MethodID = lastMethodId 651 | res.VirtualMethods[i].AccessFlags = flags 652 | res.VirtualMethods[i].CodeOff = code 653 | } 654 | 655 | return res, nil 656 | } 657 | 658 | type TypeList struct { 659 | TypeIds []uint16 660 | } 661 | 662 | func (r *Reader) ReadTypeList(off uint32) (TypeList, error) { 663 | if off == 0 { 664 | panic("invalid type list offset") 665 | } 666 | 667 | var ( 668 | res TypeList 669 | err error 670 | ) 671 | 672 | size, err := r.readUint(off) 673 | if err != nil { 674 | return res, err 675 | } 676 | 677 | off += 4 678 | res.TypeIds = make([]uint16, size) 679 | 680 | for i := uint32(0); i < size; i++ { 681 | id, err := r.readUshort(off) 682 | if err != nil { 683 | return res, err 684 | } 685 | 686 | off += 2 687 | 688 | res.TypeIds[i] = id 689 | } 690 | 691 | return res, nil 692 | } 693 | 694 | type Code struct { 695 | RegisterCount uint16 696 | IncomingCount uint16 697 | OutgoingCount uint16 698 | DebugInfoOff uint32 699 | Insns []uint16 700 | Tries []Try 701 | Handlers []CatchHandler 702 | } 703 | 704 | type Try struct { 705 | StartAddr uint32 706 | InsnCount uint16 707 | HandlerId uint16 708 | } 709 | 710 | type CatchHandler struct { 711 | Handlers []HandlerPair 712 | HasCatchAll bool 713 | CatchAllAddr uint32 714 | } 715 | 716 | type HandlerPair struct { 717 | TypeID uint32 718 | Addr uint32 719 | } 720 | 721 | func (r *Reader) ReadCode(off uint32) (Code, error) { 722 | if off == 0 { 723 | panic("invalid code offset") 724 | } 725 | 726 | var ( 727 | res Code 728 | err error 729 | ) 730 | 731 | res.RegisterCount, err = r.readUshort(off) 732 | if err != nil { 733 | return res, err 734 | } 735 | 736 | res.IncomingCount, err = r.readUshort(off + 2) 737 | if err != nil { 738 | return res, err 739 | } 740 | 741 | res.OutgoingCount, err = r.readUshort(off + 4) 742 | if err != nil { 743 | return res, err 744 | } 745 | 746 | triesCount, err := r.readUshort(off + 6) 747 | if err != nil { 748 | return res, err 749 | } 750 | 751 | res.DebugInfoOff, err = r.readUint(off + 8) 752 | if err != nil { 753 | return res, err 754 | } 755 | 756 | insCount, err := r.readUint(off + 12) 757 | if err != nil { 758 | return res, err 759 | } 760 | 761 | off += 16 762 | 763 | res.Insns = make([]uint16, insCount) 764 | 765 | for i := uint32(0); i < insCount; i++ { 766 | res.Insns[i], err = r.readUshort(off) 767 | if err != nil { 768 | return res, err 769 | } 770 | 771 | off += 2 772 | } 773 | 774 | if triesCount == 0 { 775 | return res, err 776 | } 777 | 778 | // Padding 779 | if triesCount != 0 && insCount%2 == 1 { 780 | off += 2 781 | } 782 | 783 | // Skip ahead and read handlers 784 | handlerMap := make(map[uint16]uint16) 785 | handOff := off + (uint32(triesCount) * 8) 786 | handOffStart := handOff 787 | 788 | handlerCount, n, err := r.readUleb128(handOff) 789 | if err != nil { 790 | return res, err 791 | } 792 | 793 | handOff += n 794 | res.Handlers = make([]CatchHandler, handlerCount) 795 | 796 | for i := uint16(0); i < uint16(handlerCount); i++ { 797 | handlerMap[uint16(handOff-handOffStart)] = i 798 | 799 | size, n, err := r.readSleb128(handOff) 800 | if err != nil { 801 | return res, err 802 | } 803 | 804 | handOff += n 805 | 806 | if size <= 0 { 807 | res.Handlers[i].HasCatchAll = true 808 | size = -size 809 | } 810 | 811 | res.Handlers[i].Handlers = make([]HandlerPair, size) 812 | 813 | for j := uint16(0); j < uint16(size); j++ { 814 | typeIdx, n, err := r.readUleb128(handOff) 815 | if err != nil { 816 | return res, err 817 | } 818 | handOff += n 819 | 820 | addr, n, err := r.readUleb128(handOff) 821 | if err != nil { 822 | return res, err 823 | } 824 | handOff += n 825 | 826 | res.Handlers[i].Handlers[j] = HandlerPair{ 827 | TypeID: typeIdx, 828 | Addr: addr, 829 | } 830 | } 831 | 832 | if res.Handlers[i].HasCatchAll { 833 | res.Handlers[i].CatchAllAddr, n, err = r.readUleb128(handOff) 834 | if err != nil { 835 | return res, err 836 | } 837 | handOff += n 838 | } 839 | } 840 | 841 | // Jump back and read try blocks 842 | res.Tries = make([]Try, triesCount) 843 | 844 | for i := uint16(0); i < triesCount; i++ { 845 | res.Tries[i].StartAddr, err = r.readUint(off) 846 | if err != nil { 847 | return res, err 848 | } 849 | 850 | res.Tries[i].InsnCount, err = r.readUshort(off + 4) 851 | if err != nil { 852 | return res, err 853 | } 854 | 855 | handlerOff, err := r.readUshort(off + 6) 856 | if err != nil { 857 | return res, err 858 | } 859 | 860 | handlerId, found := handlerMap[handlerOff] 861 | 862 | if !found { 863 | return res, ErrInvalidTryHandlerOffset 864 | } 865 | 866 | res.Tries[i].HandlerId = handlerId 867 | 868 | off += 8 869 | } 870 | 871 | return res, nil 872 | } 873 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | ErrEmptyTypeDesc = errors.New("empty type desc") 11 | ErrBadTypeDesc = errors.New("malformed type desc") 12 | ) 13 | 14 | func (r *Reader) ReadTypeAndParse(id uint32) (TypeDescriptor, error) { 15 | var res TypeDescriptor 16 | 17 | typeDef, err := r.ReadType(id) 18 | if err != nil { 19 | return res, err 20 | } 21 | 22 | typeDesc, err := r.ReadString(typeDef.DescriptorStringID) 23 | if err != nil { 24 | return res, err 25 | } 26 | 27 | return ParseTypeDescriptor(typeDesc) 28 | } 29 | 30 | type TypeDescriptor struct { 31 | Type uint8 32 | ArrayLength int 33 | ClassName String 34 | } 35 | 36 | func ParseTypeDescriptor(value String) (TypeDescriptor, error) { 37 | var res TypeDescriptor 38 | 39 | l := len(value.Raw) 40 | 41 | if l == 0 { 42 | return res, ErrEmptyTypeDesc 43 | } 44 | 45 | for value.Raw[res.ArrayLength] == '[' { 46 | res.ArrayLength++ 47 | 48 | // Ensure there is a next character 49 | if res.ArrayLength == l { 50 | return res, fmt.Errorf("%w: %v", ErrBadTypeDesc, value) 51 | } 52 | } 53 | 54 | res.Type = uint8(value.Raw[res.ArrayLength]) 55 | 56 | // Check if a string 57 | if res.Type != 'L' { 58 | // Only a single character should appear if not a string 59 | if res.ArrayLength+1 != l { 60 | return res, fmt.Errorf("%w: %v", ErrBadTypeDesc, value) 61 | } 62 | 63 | return res, nil 64 | } 65 | 66 | if value.Raw[l-1] != ';' { 67 | return res, fmt.Errorf("%w: %v", ErrBadTypeDesc, value) 68 | } 69 | 70 | if l-2-res.ArrayLength <= 0 { 71 | return res, fmt.Errorf("%w: %v", ErrBadTypeDesc, value) 72 | } 73 | 74 | res.ClassName = StringFromUTF16(value.Raw[1+res.ArrayLength : l-1]) 75 | 76 | return res, nil 77 | } 78 | 79 | func (d TypeDescriptor) String() string { 80 | ap := strings.Repeat("[", d.ArrayLength) 81 | 82 | if d.Type == 'L' { 83 | return fmt.Sprintf("%vL%s;", ap, d.ClassName) 84 | } 85 | 86 | return fmt.Sprintf("%v%c", ap, d.Type) 87 | } 88 | 89 | func (d TypeDescriptor) Base() TypeDescriptor { 90 | return TypeDescriptor{ 91 | Type: d.Type, 92 | ArrayLength: 0, 93 | ClassName: d.ClassName, 94 | } 95 | } 96 | 97 | func (d TypeDescriptor) IsArray() bool { 98 | return d.ArrayLength != 0 99 | } 100 | 101 | func (d TypeDescriptor) IsClass() bool { 102 | return d.Type == 'L' 103 | } 104 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "math" 9 | ) 10 | 11 | const reNotImpl = "reverse endian not implemented" 12 | 13 | func (r *Reader) toUint(data []byte) uint32 { 14 | if r.ReverseEndian { 15 | panic(reNotImpl) 16 | } 17 | 18 | return binary.LittleEndian.Uint32(data) 19 | } 20 | 21 | func (r *Reader) toUshort(data []byte) uint16 { 22 | if r.ReverseEndian { 23 | panic(reNotImpl) 24 | } 25 | 26 | return binary.LittleEndian.Uint16(data) 27 | } 28 | 29 | func (r *Reader) readUint(pos uint32) (uint32, error) { 30 | raw := make([]byte, 4) 31 | 32 | _, err := r.file.ReadAt(raw, int64(pos)) 33 | if err != nil { 34 | return 0, err 35 | } 36 | 37 | return r.toUint(raw), nil 38 | } 39 | 40 | func (r *Reader) readUshort(pos uint32) (uint16, error) { 41 | raw := make([]byte, 2) 42 | 43 | _, err := r.file.ReadAt(raw, int64(pos)) 44 | if err != nil { 45 | return 0, err 46 | } 47 | 48 | return r.toUshort(raw), nil 49 | } 50 | 51 | func (r *Reader) readUleb128(pos uint32) (uint32, uint32, error) { 52 | if r.ReverseEndian { 53 | panic(reNotImpl) 54 | } 55 | 56 | raw := make([]byte, 5) 57 | 58 | n, err := r.file.ReadAt(raw, int64(pos)) 59 | if err != nil && err != io.EOF { 60 | return 0, 0, err 61 | } 62 | 63 | value := uint32(0) 64 | shift := 0 65 | 66 | i := 0 67 | for i < 5 { 68 | if n <= i { 69 | return 0, 0, io.EOF 70 | } 71 | 72 | b := raw[i] 73 | 74 | more := (b & 0b10000000) != 0 75 | data := uint32(b & 0b01111111) 76 | 77 | value |= data << shift 78 | shift += 7 79 | i++ 80 | 81 | if !more { 82 | break 83 | } 84 | } 85 | 86 | return value, uint32(i), nil 87 | } 88 | 89 | var ErrMUTF8 = errors.New("invalid encoding") 90 | 91 | func MUTF8Decode(d []byte, expectedSize int) (String, error) { 92 | inLen := len(d) 93 | buf := make([]uint16, 0, expectedSize) 94 | 95 | for i := 0; i < inLen; { 96 | if d[i] == 0 { 97 | return String{}, fmt.Errorf("%w: null unexpected", ErrMUTF8) 98 | } else if d[i] < 0x80 { 99 | buf = append(buf, uint16(d[i])) 100 | i++ 101 | } else if d[i]&0xE0 == 0xC0 { 102 | if i+1 >= inLen { 103 | return String{}, fmt.Errorf("%w: bytes missing", ErrMUTF8) 104 | } 105 | 106 | buf = append(buf, ((uint16(d[i])&0x1F)<<6)|(uint16(d[i+1])&0x3F)) 107 | i += 2 108 | } else if d[i]&0xF0 == 0xE0 { 109 | if i+2 >= inLen { 110 | return String{}, fmt.Errorf("%w: bytes missing", ErrMUTF8) 111 | } 112 | 113 | buf = append(buf, ((uint16(d[i])&0x0F)<<12)|((uint16(d[i+1])&0x3F)<<6)|(uint16(d[i+2])&0x3F)) 114 | i += 3 115 | } else { 116 | return String{}, fmt.Errorf("%w: unexpected byte", ErrMUTF8) 117 | } 118 | } 119 | 120 | return StringFromUTF16(buf), nil 121 | } 122 | 123 | func (r *Reader) readSleb128(pos uint32) (int32, uint32, error) { 124 | if r.ReverseEndian { 125 | panic(reNotImpl) 126 | } 127 | 128 | raw := make([]byte, 5) 129 | 130 | n, err := r.file.ReadAt(raw, int64(pos)) 131 | if err != nil && err != io.EOF { 132 | return 0, 0, err 133 | } 134 | 135 | value := uint32(0) 136 | shift := 0 137 | lastBit := false 138 | 139 | i := 0 140 | for i < 5 { 141 | if n <= i { 142 | return 0, 0, io.EOF 143 | } 144 | 145 | b := raw[i] 146 | 147 | more := (b & 0b10000000) != 0 148 | data := uint32(b & 0b01111111) 149 | 150 | lastBit = (b & 0b1000000) != 0 151 | 152 | value |= data << shift 153 | shift += 7 154 | i++ 155 | 156 | if !more { 157 | break 158 | } 159 | } 160 | 161 | if lastBit { 162 | // Set top bits if negative 163 | value |= (math.MaxUint32 >> shift) << shift 164 | } 165 | 166 | return int32(value), uint32(i), nil 167 | } 168 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package dextk 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | type ulebCase struct { 11 | bytes []byte 12 | size uint32 13 | value uint32 14 | } 15 | 16 | var ulebCases = []ulebCase{ 17 | { 18 | bytes: []byte{0x0}, 19 | size: 1, 20 | value: 0, 21 | }, 22 | { 23 | bytes: []byte{0x1}, 24 | size: 1, 25 | value: 1, 26 | }, 27 | { 28 | bytes: []byte{0x7f}, 29 | size: 1, 30 | value: 127, 31 | }, 32 | { 33 | bytes: []byte{0x80, 0x7f}, 34 | size: 2, 35 | value: 16256, 36 | }, 37 | } 38 | 39 | func TestReadUleb128(t *testing.T) { 40 | for _, c := range ulebCases { 41 | bytes := bytes.NewReader(c.bytes) 42 | reader := &Reader{ 43 | file: bytes, 44 | } 45 | 46 | gotVal, gotSize, gotErr := reader.readUleb128(0) 47 | 48 | require.NoError(t, gotErr, "read error") 49 | require.Equal(t, c.size, gotSize, "read size") 50 | require.Equal(t, c.value, gotVal, "read value") 51 | } 52 | } 53 | 54 | type slebCase struct { 55 | bytes []byte 56 | size uint32 57 | value int32 58 | } 59 | 60 | var slebCases = []slebCase{ 61 | { 62 | bytes: []byte{0x0}, 63 | size: 1, 64 | value: 0, 65 | }, 66 | { 67 | bytes: []byte{0x1}, 68 | size: 1, 69 | value: 1, 70 | }, 71 | { 72 | bytes: []byte{0x7f}, 73 | size: 1, 74 | value: -1, 75 | }, 76 | { 77 | bytes: []byte{0x80, 0x7f}, 78 | size: 2, 79 | value: -128, 80 | }, 81 | } 82 | 83 | func TestReadSleb128(t *testing.T) { 84 | for _, c := range slebCases { 85 | bytes := bytes.NewReader(c.bytes) 86 | reader := &Reader{ 87 | file: bytes, 88 | } 89 | 90 | gotVal, gotSize, gotErr := reader.readSleb128(0) 91 | 92 | require.NoError(t, gotErr, "read error") 93 | require.Equal(t, c.size, gotSize, "read size") 94 | require.Equal(t, c.value, gotVal, "read value") 95 | } 96 | } 97 | --------------------------------------------------------------------------------