├── tests
├── config.nims
├── tproto.nim
├── tblob.nim
├── tdclass2native.nim
├── tclass.nim
└── tdex.nim
├── config.nims
├── src
├── dali.nim
└── dali
│ ├── utils
│ ├── sortedset.nim
│ ├── macromatch.nim
│ └── blob.nim
│ ├── instrs.nim
│ ├── types.nim
│ ├── macros.nim
│ └── dex.nim
├── dali.nimble
├── czak_manifest.xml
├── README.md
├── czak_hello.nim
├── jclass.nim
├── jni_hello.nim
├── dclass.nim
├── jni_wrapper.nim
└── LICENSE
/tests/config.nims:
--------------------------------------------------------------------------------
1 | switch("path", "$projectDir/../src")
--------------------------------------------------------------------------------
/config.nims:
--------------------------------------------------------------------------------
1 | switch("path", "$projectDir/src")
2 |
3 |
--------------------------------------------------------------------------------
/src/dali.nim:
--------------------------------------------------------------------------------
1 | import dali/[types, dex, instrs, macros]
2 | export types, dex, instrs, macros
3 |
--------------------------------------------------------------------------------
/dali.nimble:
--------------------------------------------------------------------------------
1 | # Package
2 |
3 | version = "0.2.3"
4 | author = "Mateusz Czapliński"
5 | description = "Dalvik Assembler/Linker"
6 | license = "AGPL-3.0"
7 | srcDir = "src"
8 |
9 |
10 | # Dependencies
11 |
12 | requires "nim >= 1.0.0"
13 | requires "patty 0.3.3"
14 |
--------------------------------------------------------------------------------
/czak_manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **dali — an indie assembler/linker for Dalvik VM .dex & .apk files** (Work In Progress)
2 |
3 | This project has no proper usage guide yet.
4 | Please see [hellomello](https://github.com/akavel/hellomello) for a **sample project** using it,
5 | with step-by-step build instructions.
6 | For **hacking** on the project,
7 | see: [src/dali.nim](https://github.com/akavel/dali/blob/master/src/dali.nim),
8 | esp. the [render function](https://github.com/akavel/dali/blob/adc2d2d6c1fb33aaac89d49e80a4f0ff4378fd10/src/dali.nim#L163),
9 | and the **test suite** in: [tests/tdex.nim](https://github.com/akavel/dali/blob/master/tests/tdex.nim).
10 |
--------------------------------------------------------------------------------
/czak_hello.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 | import src / dali
3 |
4 | # Based on: https://github.com/czak/minimal-android-project/blob/9aad6bd2f1f443aa8dfe887d3ed6ea27576aa609/src/main/java/pl/czak/minimal/MainActivity.java
5 |
6 | const
7 | Activity = "Landroid/app/Activity;"
8 | HelloActivity = "Lcom/akavel/hello/HelloActivity;"
9 | Bundle = "Landroid/os/Bundle;"
10 | TextView = "Landroid/widget/TextView;"
11 | Context = "Landroid/content/Context;"
12 | CharSequence = "Ljava/lang/CharSequence;"
13 | View = "Landroid/view/View;"
14 |
15 | var dex = newDex()
16 | dex.classes.add:
17 | dclass com.akavel.hello.HelloActivity {.public.} of Activity:
18 | proc ``() {.public, constructor, regs:1, ins:1, outs:1.} =
19 | invoke_direct(0, jproto Activity.``())
20 | return_void()
21 | proc onCreate(Bundle) {.public, regs:4, ins:2, outs:2.} =
22 | invoke_super(2, 3, jproto Activity.onCreate(Bundle))
23 | new_instance(0, TextView)
24 | invoke_direct(0, 2, jproto TextView.``(Context))
25 | const_string(1, "Hello dali!")
26 | invoke_virtual(0, 1, jproto TextView.setText(CharSequence))
27 | invoke_virtual(2, 0, jproto HelloActivity.setContentView(View))
28 | return_void()
29 |
30 | stdout.write(dex.render)
31 |
32 |
--------------------------------------------------------------------------------
/tests/tproto.nim:
--------------------------------------------------------------------------------
1 | import unittest
2 | import dali
3 |
4 | # dumpTree:
5 | # Method(
6 | # class: HelloActivity,
7 | # name: "",
8 | # prototype: Prototype(ret: "V", params: @[1,2,3]),
9 | # )
10 |
11 | # echo "runtime"
12 | #
13 | let
14 | hello = "Lcom/hello/Hello;"
15 | z = "Lfoo/bar/Z;"
16 | zz = "Lfoo/bar/Zz;"
17 | zzz = "Lfoo/bar/Zzz;"
18 | View = "Landroid/view/View;"
19 | h1 = jproto hello.world()
20 | h2 = jproto hello.world(z, zz, int) -> zzz
21 | # jproto hello.world(z, zz, int): zzz[int, float]
22 | h4 = jproto hello.``()
23 | h5 = jproto hello.world() -> zzz
24 | # jproto hello.()
25 | # jproto hello.world(z: y) -> zzz[int, float]
26 | # jproto hello.world(z: y, one: two) -> zzz[int, float]
27 | # jproto(hello.world(z: y) -> zzz)
28 |
29 | let
30 | HelloActivity = "Lfoo/HelloActivity;"
31 | String = "Ljava/lang/String;"
32 |
33 | test "long prototype syntax":
34 | let p = jproto HelloActivity.setContentView(View, int) -> String
35 | checkpoint p.repr
36 | check p.equals Method(
37 | class: HelloActivity,
38 | name: "setContentView",
39 | prototype: Prototype(ret: String, params: @[View, "I"]))
40 |
41 | test "short prototype syntax":
42 | let p = jproto HelloActivity.foo()
43 | checkpoint p.repr
44 | check p.equals Method(
45 | class: HelloActivity,
46 | name: "foo",
47 | prototype: Prototype(ret: "V", params: @[]))
48 |
49 | test "constructor":
50 | let p = jproto HelloActivity.``()
51 | checkpoint p.repr
52 | check p.equals Method(
53 | class: HelloActivity,
54 | name: "",
55 | prototype: Prototype(ret: "V", params: @[]))
56 |
--------------------------------------------------------------------------------
/jclass.nim:
--------------------------------------------------------------------------------
1 |
2 | ## Code more or less I'd like to be able to eventually run as macro to generate classes.dex:
3 | #
4 | # jtype
5 | # android.app.Activity
6 | # com.akavel.hello2.HelloActivity
7 | # android.os.Bundle
8 | # android.widget.TextView
9 | # java.lang.System
10 | # java.lang.String
11 | #
12 | # jclass HelloActivity {.public, extends: Activity.}:
13 | # ``() {.final, static, constructor.} =
14 | # # System.loadLibrary("hello-mello")
15 | # const_string(v0, "hello-mello")
16 | # invoke_static(v0, System.loadLibrary(String))
17 | # #return_void()
18 | # ``() {.final, public, constructor.} =
19 | # invoke_direct(v0, Activity.``())
20 | # #return_void()
21 | # onCreate(Bundle) {.public.} =
22 | # # super.onCreate(arg0) [this?]
23 | # invoke_super(v2, v3, Activity.onCreate(Bundle))
24 | # # v0 = new TextView(this)
25 | # new_instance(v0, TextView)
26 | # invoke_direct(v0, v2, TextView.``(jtype android.context.Context))
27 | # # v1 = this.stringFromJNI()
28 | # invoke_virtual(v2, HelloActivity.stringFromJNI(): String)
29 | # move_result_object(v1)
30 | # # v0.setText(v1)
31 | # invoke_virtual(v0, v1, TextView.setText(jtype java.lang.CharSequence))
32 | # # this.setContentView(v0)
33 | # invoke_virtual(v2, v0, HelloActivity.setContentView(jtype android.view.View))
34 | # #return_void()
35 | # stringFromJNI(): String {.public, native.}
36 |
37 | # type JType = distinct string
38 |
39 | # func jtype(humanReadable: string): JType =
40 | # case humanReadable
41 | # else:
42 | # return JType("L" & humanReadable.replace('.', '/') & ";")
43 |
44 | #TODO:
45 | # static:
46 | # writeFile(...)
47 |
--------------------------------------------------------------------------------
/src/dali/utils/sortedset.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 |
3 | type SortedSet*[T] = distinct seq[T]
4 |
5 | proc newSortedSet*[T](): SortedSet[T] {.inline.} = SortedSet[T](newSeq[T]())
6 | proc newSortedSet*[T](s: var SortedSet[T]): SortedSet[T] {.inline.} = s = SortedSet[T](newSeq[T]())
7 | proc init*[T](s: var SortedSet[T]) {.inline.} = s = SortedSet[T](newSeq[T]())
8 |
9 | proc incl*[T](s: var SortedSet[T], item: T) =
10 | let i = s.search(item)
11 | if i == s.len:
12 | seq[T](s).add(item)
13 | elif item < s[i]: # NOTE: we assume `<` is defined, but other operators like `==` may not
14 | seq[T](s).insert(item, i)
15 | # # HACK: 3 lines, as the following oneliner seems to fail with an error on Nim 19.4:
16 | # # (HeapQueue[T](s)).push(item)
17 | # var workaround = seq[T](s)
18 | # workaround.push(item)
19 | # s = SortedSet[T](workaround)
20 |
21 | proc `[]`*[T](s: SortedSet[T], i: int): T {.inline.} = seq[T](s)[i]
22 |
23 | proc len*[T](s: SortedSet[T]): int {.inline.} = seq[T](s).len
24 |
25 | proc search*[T](s: SortedSet[T], item: T): int =
26 | # Based on algorithm.binarySearch, but returns position where element would be inserted if not found
27 | var i = s.len
28 | while result < i:
29 | let mid = (result + i) shr 1
30 | # echo result, mid, i
31 | if item < s[mid]:
32 | i = mid
33 | elif s[mid] < item:
34 | result = mid + 1
35 | else:
36 | return mid
37 |
38 | iterator items*[T](s: SortedSet[T]): T =
39 | for item in seq[T](s):
40 | yield item
41 |
42 | # iterator pairs*[T](s: SortedSet[T]): tuple[idx: int, item: T] =
43 | # for idx, item in seq[T](s):
44 | # yield (idx, item)
45 |
46 |
47 | when isMainModule:
48 | var x = newSortedSet[int]()
49 | x.incl(5)
50 | x.incl(3)
51 | x.incl(3)
52 | x.incl(4)
53 | for i in x:
54 | echo i
55 |
--------------------------------------------------------------------------------
/tests/tblob.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 | import unittest
3 | import strutils
4 | import dali/utils/blob
5 |
6 | test "skip":
7 | var s: Blob
8 | s.skip(4)
9 | check s.string.toHex == strip_space"00 00 00 00"
10 |
11 | test "puts":
12 | var s: Blob
13 | s.puts "hello "
14 | s.puts "world"
15 | check s.string == "hello world"
16 |
17 | test "pad32":
18 | proc pad(s: string): string =
19 | var b: Blob
20 | b.puts s
21 | b.pad32()
22 | return b.string
23 | check pad("") == ""
24 | check pad("a") == "a\x00\x00\x00"
25 | check pad("ab") == "ab\x00\x00"
26 | check pad("abc") == "abc\x00"
27 | check pad("abcd") == "abcd"
28 | check pad("abcde") == "abcde\x00\x00\x00"
29 |
30 | test "put32 little endian":
31 | var s: Blob
32 | s.put32 0x1234_5678
33 | check s.string.toHex == strip_space"78 56 34 12"
34 |
35 | test "put32 x2":
36 | var s: Blob
37 | s.put32 0x1234_5678
38 | s.put32 0xdead_beef'u32
39 | check s.string.toHex == strip_space"78 56 34 12 EF BE AD DE"
40 |
41 | test "put16 little endian x2":
42 | var s: Blob
43 | s.put16 0x1234
44 | s.put16 0x5678
45 | check s.string.toHex == strip_space"34 12 78 56"
46 |
47 | test "put_uleb128":
48 | proc uleb128(n: uint32): string =
49 | var s: Blob
50 | s.put_uleb128 n
51 | return s.string
52 | check uleb128(0).toHex == strip_space"00"
53 | check uleb128(1).toHex == strip_space"01"
54 | check uleb128(127).toHex == strip_space"7F"
55 | check uleb128(16256).toHex == strip_space"80 7F"
56 |
57 | test "put4":
58 | var s: Blob
59 | s.put4 0xf, true
60 | s.put4 0xa, false
61 | s.put4 0x1, true
62 | s.put4 0x2, false
63 | check s.string.toHex == strip_space"FA 12"
64 |
65 | test "slot let op":
66 | var s: Blob
67 | s.put32 0x1234_5678
68 | s.put32 >>: slot
69 | s.put32 0x1234_5678
70 | s[slot] = 0xabcd_ef00'u32
71 | check s.string.toHex == strip_space"78 56 34 12 00 EF CD AB 78 56 34 12"
72 |
73 | test "slot assign op":
74 | var s: Blob
75 | var slot: Slot32
76 | s.put32 0x1234_5678
77 | s.put32 >> slot
78 | s.put32 0x1234_5678
79 | s[slot] = 0xabcd_ef00'u32
80 | check s.string.toHex == strip_space"78 56 34 12 00 EF CD AB 78 56 34 12"
81 |
82 | proc strip_space(s: string): string =
83 | return s.multiReplace(("\n", ""), (" ", ""))
84 |
--------------------------------------------------------------------------------
/jni_hello.nim:
--------------------------------------------------------------------------------
1 | # Resources:
2 | # - System.loadLibrary - hello-jni in Android samples, and also:
3 | # https://github.com/skanti/Android-Manual-Build-Command-Line/blob/3fea20b3b52ac04cb0208a691529a89cfb2064c1/hello-jni/src/com/example/hellojni/HelloJNI.java#L25
4 | # - - http://mariokmk.github.io/programming/2015/03/06/learning-android-bytecode.html
5 |
6 | {.experimental: "codeReordering".}
7 | import src / dali
8 |
9 | # Extended czak_hello.nim
10 |
11 | const
12 | Activity = "Landroid/app/Activity;"
13 | HelloActivity = "Lcom/akavel/hello2/HelloActivity;"
14 | Bundle = "Landroid/os/Bundle;"
15 | TextView = "Landroid/widget/TextView;"
16 | System = "Ljava/lang/System;"
17 | String = "Ljava/lang/String;"
18 | Context = "Landroid/content/Context;"
19 | View = "Landroid/view/View;"
20 | CharSequence = "Ljava/lang/CharSequence;"
21 |
22 | var dex = newDex()
23 |
24 | dex.classes.add:
25 | dclass com.akavel.hello2.HelloActivity {.public.} of Activity:
26 | proc ``() {.static, constructor, regs:2, ins:0, outs:1.} =
27 | # System.loadLibrary("hello-mello")
28 | const_string(0, "hello-mello")
29 | invoke_static(0, jproto System.loadLibrary(String))
30 | return_void()
31 | proc ``() {.public, constructor, regs:1, ins:1, outs:1.} =
32 | invoke_direct(0, jproto Activity.``())
33 | return_void()
34 | proc onCreate(Bundle) {.public, regs:4, ins:2, outs:2.} =
35 | # ins: this, arg0
36 | # super.onCreate(arg0)
37 | invoke_super(2, 3, jproto Activity.onCreate(Bundle))
38 | # v0 = new TextView(this)
39 | new_instance(0, TextView)
40 | invoke_direct(0, 2, jproto TextView.``(Context))
41 | # v1 = this.stringFromJNI()
42 | # NOTE: failure to call a Native function should result in
43 | # java.lang.UnsatisfiedLinkError exception
44 | invoke_virtual(2, jproto HelloActivity.stringFromJNI() -> String)
45 | move_result_object(1)
46 | # v0.setText(v1)
47 | invoke_virtual(0, 1, jproto TextView.setText(CharSequence))
48 | # this.setContentView(v0)
49 | invoke_virtual(2, 0, jproto HelloActivity.setContentView(View))
50 | # return
51 | return_void()
52 | proc stringFromJNI(): String {.public, native.}
53 |
54 | stdout.write(dex.render)
55 |
56 |
--------------------------------------------------------------------------------
/tests/tdclass2native.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 | import unittest
3 | import macros
4 | import strutils
5 |
6 | import dali
7 | include dali/macros
8 |
9 | macro dclass2native_string(header, body: untyped): untyped =
10 | newStrLitNode(newStmtList(dclass2native(header, body)).repr)
11 |
12 | let jni_hello_code = dclass2native_string com.akavel.hello2.HelloActivity {.public.} of Activity:
13 | proc ``() {.static, constructor, regs:2, ins:0, outs:1.} =
14 | # System.loadLibrary("hello-mello")
15 | const_string(0, "hello-mello")
16 | invoke_static(0, jproto System.loadLibrary(String))
17 | return_void()
18 | proc ``() {.public, constructor, regs:1, ins:1, outs:1.} =
19 | invoke_direct(0, jproto Activity.``())
20 | return_void()
21 | proc onCreate(_: Bundle) {.public, regs:4, ins:2, outs:2.} =
22 | # ins: this, arg0
23 | # super.onCreate(arg0)
24 | invoke_super(2, 3, jproto Activity.onCreate(Bundle))
25 | # v0 = new TextView(this)
26 | new_instance(0, TextView)
27 | invoke_direct(0, 2, jproto TextView.``(Context))
28 | # v1 = this.stringFromJNI()
29 | # NOTE: failure to call a Native function should result in
30 | # java.lang.UnsatisfiedLinkError exception
31 | invoke_virtual(2, jproto HelloActivity.stringFromJNI() -> String)
32 | move_result_object(1)
33 | # v0.setText(v1)
34 | invoke_virtual(0, 1, jproto TextView.setText(CharSequence))
35 | # this.setContentView(v0)
36 | invoke_virtual(2, 0, jproto HelloActivity.setContentView(View))
37 | # return
38 | return_void()
39 | proc stringFromJNI(): jstring {.public, native.} =
40 | return jenv.NewStringUTF(jenv, "Hello from Nim dclass :D")
41 | proc nopVoid() {.public, native.} =
42 | discard jenv.NewStringUTF(jenv, "nop")
43 |
44 | test "jni_hello.so code as string":
45 | let wantCode = """
46 | proc Java_com_akavel_hello2_HelloActivity_stringFromJNI*(jenv: JNIEnvPtr;
47 | jthis: jobject): jstring {.cdecl, exportc, dynlib.} =
48 | return jenv.NewStringUTF(jenv, "Hello from Nim dclass :D")
49 |
50 | proc Java_com_akavel_hello2_HelloActivity_nopVoid*(jenv: JNIEnvPtr; jthis: jobject): void {.
51 | cdecl, exportc, dynlib.} =
52 | discard jenv.NewStringUTF(jenv, "nop")
53 | """
54 | check trim(wantCode) == trim(jni_hello_code)
55 |
56 | proc trim(s: string): string =
57 | var x = s
58 | x.removePrefix
59 | x.removePrefix "[" # not sure why this shows up in NimNode.repr :/
60 | x.removePrefix
61 | x.removeSuffix {' '}
62 | x.removeSuffix
63 | x.removeSuffix "]" # not sure why this shows up in NimNode.repr :/
64 | x.removeSuffix
65 | return "\n" & x.replace(" ", "·").replace("\t", "¬").replace("\n", "¶\n") & "\n"
66 |
67 |
--------------------------------------------------------------------------------
/src/dali/instrs.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 |
3 | import dali/types
4 |
5 | proc move_result_object*(reg: uint8): Instr =
6 | return newInstr(0x0c, RegXX(reg))
7 |
8 | proc return_void*(): Instr =
9 | return newInstr(0x0e, RawXX(0))
10 | proc return_object*(reg: uint8): Instr =
11 | return newInstr(0x11, RawXX(reg))
12 |
13 | proc const_high16*(reg: uint8, highBits: uint16): Instr =
14 | return newInstr(0x15, RegXX(reg), RawXXXX(highBits))
15 | proc const_wide_16*(regs: uint8, v: int16): Instr =
16 | return newInstr(0x16, RegXX(regs), RawXXXX(v.uint16))
17 | proc const_string*(reg: uint8, s: String): Instr =
18 | return newInstr(0x1a, RegXX(reg), StringXXXX(s))
19 |
20 | proc new_instance*(reg: uint8, t: Type): Instr =
21 | return newInstr(0x22, RegXX(reg), TypeXXXX(t))
22 |
23 | proc iget_wide*(regsA: uint4, regB: uint4, field: Field): Instr =
24 | return newInstr(0x53, RegX(regB), RegX(regsA), FieldXXXX(field))
25 | proc iput_wide*(regsA: uint4, regB: uint4, field: Field): Instr =
26 | return newInstr(0x5a, RegX(regB), RegX(regsA), FieldXXXX(field))
27 |
28 | proc sget_object*(reg: uint8, field: Field): Instr =
29 | return newInstr(0x62, RegXX(reg), FieldXXXX(field))
30 |
31 | proc invoke_virtual*(regC: uint4, m: Method): Instr =
32 | return newInvoke1(0x6e, regC, m)
33 | proc invoke_virtual*(regC: uint4, regD: uint4, m: Method): Instr =
34 | return newInvoke2(0x6e, regC, regD, m)
35 |
36 | proc invoke_super*(regC: uint4, regD: uint4, m: Method): Instr =
37 | return newInvoke2(0x6f, regC, regD, m)
38 |
39 | proc invoke_direct*(regC: uint4, m: Method): Instr =
40 | return newInvoke1(0x70, regC, m)
41 | proc invoke_direct*(regC: uint4, regD: uint4, m: Method): Instr =
42 | return newInvoke2(0x70, regC, regD, m)
43 |
44 | proc invoke_static*(regC: uint4, m: Method): Instr =
45 | return newInvoke1(0x71, regC, m)
46 | proc invoke_static*(regC, regD: uint4, m: Method): Instr =
47 | return newInvoke2(0x71, regC, regD, m)
48 |
49 |
50 | proc newInvoke1*(opcode: uint8, regC: uint4, m: Method): Instr =
51 | return newInstr(opcode, RawX(1), RawX(0), MethodXXXX(m), RawX(0), RegX(regC), RawXX(0))
52 | proc newInvoke2*(opcode: uint8, regC: uint4, regD: uint4, m: Method): Instr =
53 | return newInstr(opcode, RawX(2), RawX(0), MethodXXXX(m), RawX(regD), RegX(regC), RawXX(0))
54 |
55 | proc newInstr*(opcode: uint8, args: varargs[Arg]): Instr =
56 | ## NOTE: We're assuming little endian encoding of the
57 | ## file here; 8-bit args should be ordered in
58 | ## "swapped order" vs. the one listed in official
59 | ## Android bytecode spec (i.e., add lower byte first,
60 | ## higher byte later). On the other hand, 16-bit
61 | ## words should not have contents rotated (just fill
62 | ## them as in the spec).
63 | return Instr(opcode: opcode, args: @args)
64 |
65 |
--------------------------------------------------------------------------------
/src/dali/utils/macromatch.nim:
--------------------------------------------------------------------------------
1 | import macros
2 | import sequtils
3 | import strutils
4 |
5 | # TODO: how do I write tests for functions operating on NimNodes ??? :( https://forum.nim-lang.org/t/5261
6 |
7 | type
8 | MatchKind = enum
9 | mkConcrete, mkOne, mkAny,
10 | mkStrVal
11 | MatchNode = ref object
12 | case level: MatchKind
13 | of mkStrVal:
14 | strVal: string
15 | of mkConcrete:
16 | kind: NimNodeKind
17 | kids: seq[MatchNode]
18 | else:
19 | discard
20 |
21 | proc `$`(m: MatchNode): string =
22 | case m.level
23 | of mkStrVal: return "\"" & m.strVal & "\""
24 | of mkAny: return "_"
25 | of mkOne: return "[]"
26 | of mkConcrete:
27 | var kind = $m.kind
28 | kind.removePrefix "nnk"
29 | return kind & "(" & m.kids.map(`$`).join(", ") & ")"
30 |
31 |
32 | proc toMatchable(nTree: NimNode): NimNode =
33 | case nTree.kind
34 | of nnkCall:
35 | if nTree[0].kind != nnkIdent: error "expected identifier", nTree[0]
36 | let
37 | kind = ident("nnk" & nTree[0].strVal)
38 | kids = newTree(nnkBracket)
39 | for i in 1..= tree.kids.len:
77 | return false
78 | case tree.kids[i].level
79 | of mkAny:
80 | return true
81 | of mkOne:
82 | continue
83 | of mkConcrete:
84 | if not nn.matches(tree.kids[i]):
85 | return false
86 | else:
87 | raise newException(CatchableError, "logic error")
88 | return true
89 |
90 | macro `=~`*(n: NimNode, matchTree: untyped): untyped =
91 | let
92 | matchable = toMatchable(matchTree)
93 | matches = bindSym"matches"
94 | quote do:
95 | `matches`(`n`, `matchable`)
96 |
97 | template `!~`*(n: NimNode, matchTree: untyped): untyped =
98 | not(n =~ matchTree)
99 |
--------------------------------------------------------------------------------
/dclass.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 | # import macros
3 | import dali
4 | # import jni_wrapper
5 |
6 | # let
7 | # HelloActivity = jtype com.akavel.hello2.HelloActivity
8 | # Activity = jtype android.app.Activity
9 | # Bundle = jtype android.os.Bundle
10 | # String = jtype java.lang.String
11 | let
12 | Activity = "Landroid/app/Activity;"
13 | HelloActivity = "Lcom/akavel/hello2/HelloActivity;"
14 | Bundle = "Landroid/os/Bundle;"
15 | TextView = "Landroid/widget/TextView;"
16 | System = "Ljava/lang/System;"
17 | String = "Ljava/lang/String;"
18 | Context = "Landroid/content/Context;"
19 | View = "Landroid/view/View;"
20 | CharSequence = "Ljava/lang/CharSequence;"
21 | Long = "Ljava/lang/Long;"
22 |
23 | # TODO 1:
24 | # 1. this.nimSelf = (long)42
25 | # 2. v2/3 = this.nimSelf
26 | # 3. v1 = ltoa(v2/3)
27 | # 4. return v1
28 | # TODO 2:
29 | # - same as above, but steps 2+3 are implemented in stringFromJNI()
30 |
31 | classes_dex "dclass.dex":
32 | dclass com.akavel.hello2.HelloActivity {.public.} of Activity:
33 | # proc ``() {.static, constructor, regs:2, ins:0, outs:1.} =
34 | # # System.loadLibrary("hello-mello")
35 | # const_string(0, "hello-mello")
36 | # invoke_static(0, jproto System.loadLibrary(String))
37 | # return_void()
38 | proc ``() {.public, constructor, regs:1, ins:1, outs:1.} =
39 | invoke_direct(0, jproto Activity.``())
40 | return_void()
41 | proc onCreate(_: Bundle) {.public, regs:4, ins:2, outs:2.} =
42 | # ins: this, arg0
43 | # super.onCreate(arg0)
44 | invoke_super(2, 3, jproto Activity.onCreate(Bundle))
45 | # v0 = new TextView(this)
46 | new_instance(0, TextView)
47 | invoke_direct(0, 2, jproto TextView.``(Context))
48 | # # v1 = this.stringFromJNI()
49 | # # NOTE: failure to call a Native function should result in
50 | # # java.lang.UnsatisfiedLinkError exception
51 | # invoke_virtual(2, jproto HelloActivity.stringFromJNI() -> String)
52 | # move_result_object(1)
53 | invoke_virtual(2, jproto HelloActivity.stringFromField() -> String)
54 | move_result_object(1)
55 | # v0.setText(v1)
56 | invoke_virtual(0, 1, jproto TextView.setText(CharSequence))
57 | # this.setContentView(v0)
58 | invoke_virtual(2, 0, jproto HelloActivity.setContentView(View))
59 | # return
60 | return_void()
61 | # proc stringFromJNI(): jstring {.public, native.} =
62 | # return jenv.NewStringUTF(jenv, "Hello from Nim dclass :D")
63 |
64 | proc stringFromField(): jstring {.regs:4, ins:1, outs:3.} =
65 | # this.nimSelf = (long)42
66 | const_wide_16(0, 42'i16)
67 | iput_wide(0, 3,
68 | Field(class:HelloActivity, typ:"J", name:"nimSelf"))
69 | # v1..2 = this.nimSelf
70 | iget_wide(1, 3,
71 | Field(class:HelloActivity, typ:"J", name:"nimSelf"))
72 | # v0 = Long.toString(v1..2)
73 | invoke_static(1, 2, jproto Long.toString(jlong))
74 | move_result_object(0)
75 | # return v0
76 | return_object(0)
77 |
78 | # dumpTree:
79 | # var foo, bing: seq[int]
80 | # var
81 | # bar: seq[int] = @[1,2]
82 | # baz = @[3,4]
83 |
84 | # dumpTree:
85 | # if foo =~ Ident([]):
86 | # while bar =~ Biz(_):
87 | # discard
88 |
--------------------------------------------------------------------------------
/src/dali/utils/blob.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 | import bitops
3 | import tables
4 |
5 | type
6 | Blob* = distinct string
7 | uint4* = range[0..15] # "nibble" / hex digit / half-byte
8 | Slot32* = distinct int
9 | # Slot16* = distinct int
10 |
11 | Slots32*[T] = distinct TSlots32[T]
12 | TSlots32[T] = Table[T, seq[Slot32]]
13 |
14 | proc skip*(b: var Blob, n: int) {.inline.} =
15 | let pos = b.string.len
16 | b.string.setLen(pos + n)
17 | for i in pos ..< b.string.len:
18 | b.string[i] = chr(0)
19 |
20 | template `>>:`*(slot32: Slot32, slot: untyped): untyped =
21 | let slot = slot32
22 |
23 | proc `>>`*(slot32: Slot32, slot: var Slot32) =
24 | slot = slot32
25 |
26 | proc slot32*(b: var Blob): Slot32 {.inline.} =
27 | result = b.string.len.Slot32
28 | b.skip(4)
29 |
30 | proc set*(b: var Blob, slot: Slot32, v: uint32) =
31 | let i = slot.int
32 | # Little-endian
33 | b.string[i+0] = chr(v and 0xff)
34 | b.string[i+1] = chr(v shr 8 and 0xff)
35 | b.string[i+2] = chr(v shr 16 and 0xff)
36 | b.string[i+3] = chr(v shr 24 and 0xff)
37 |
38 | proc `[]=`*(b: var Blob, slot: Slot32, v: uint32) =
39 | b.set(slot, v)
40 |
41 | proc pad32*(b: var Blob) {.inline.} =
42 | let n = (4 - (b.string.len mod 4)) mod 4
43 | b.skip(n)
44 |
45 | proc puts*(b: var Blob, v: string) =
46 | if v.len == 0:
47 | return
48 | var s = b.string
49 | let pos = s.len
50 | s.setLen(pos + v.len)
51 | copyMem(addr(s[pos]), cstring(v), v.len)
52 | b = s.Blob
53 |
54 | proc putc*(b: var Blob, v: char) {.inline.} =
55 | b.puts($v)
56 |
57 | proc put32*(b: var Blob, v: uint32) =
58 | b.set(b.slot32, v)
59 |
60 | proc put32*(b: var Blob): Slot32 =
61 | b.slot32()
62 |
63 | proc put16*(b: var Blob, v: uint16) =
64 | # Little-endian
65 | var buf = newString(2)
66 | buf[0] = chr(v and 0xff)
67 | buf[1] = chr(v shr 8 and 0xff)
68 | b.puts(buf)
69 |
70 | proc put_uleb128*(b: var Blob, v: uint32) =
71 | ## Writes an uint32 in ULEB128 format
72 | ## (https://source.android.com/devices/tech/dalvik/dex-format#leb128)
73 | if v == 0:
74 | b.puts("\x00")
75 | return
76 | let
77 | topBit = fastLog2(v) # position of the highest bit set
78 | n = topBit div 7 + 1 # number of bytes required for ULEB128 encoding of 'v'
79 | var
80 | buf = newString(n.Natural)
81 | work = v
82 | i = 0
83 | while work >= 0x80'u32:
84 | buf[i] = chr(0x80.byte or (work and 0x7F).byte)
85 | work = work shr 7
86 | inc i
87 | buf[i] = chr(work.byte)
88 | b.puts(buf)
89 |
90 | proc put4*(b: var Blob, v: uint4, high: bool) =
91 | var s = b.string
92 | let pos = s.len
93 | if high:
94 | s.setLen(pos + 1)
95 | s[pos] = chr(v.uint8 shl 4)
96 | else:
97 | s[pos-1] = chr(s[pos-1].ord.uint8 or v.uint8)
98 | b = s.Blob
99 |
100 | proc pos*(b: var Blob): uint32 {.inline.} =
101 | return b.string.len.uint32
102 |
103 |
104 | proc add*[T](slots: var Slots32[T], key: T, val: Slot32) =
105 | slots.TSlots32[:T].mgetOrPut(key, newSeq[Slot32]()).add(val)
106 |
107 | proc setAll*[T](slots: Slots32[T], key: T, val: uint32, blob: var Blob) =
108 | if not slots.TSlots32[:T].contains(key):
109 | return
110 | for slot in slots.TSlots32[:T][key]:
111 | blob.set(slot, val)
112 |
113 |
--------------------------------------------------------------------------------
/src/dali/types.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 | import hashes
3 |
4 | import patty
5 |
6 |
7 | variantp Arg: # Argument of an instruction of Dalvik bytecode
8 | RawX(raw4: uint4)
9 | RawXX(raw8: uint8)
10 | RawXXXX(raw16: uint16)
11 | RegX(reg4: uint4)
12 | RegXX(reg8: uint8)
13 | FieldXXXX(field16: Field)
14 | StringXXXX(string16: String)
15 | TypeXXXX(type16: Type)
16 | MethodXXXX(method16: Method)
17 |
18 | variantp MaybeType:
19 | SomeType(typ: Type)
20 | NoType
21 |
22 | variantp MaybeCode:
23 | SomeCode(code: Code)
24 | NoCode
25 |
26 |
27 | type
28 | Field* = ref object
29 | class*: Type
30 | typ*: Type
31 | name*: String
32 | Type* = String
33 | String* = string
34 | Method* = ref object
35 | class*: Type
36 | prototype*: Prototype # a.k.a. method signature
37 | name*: String
38 | Prototype* = ref object
39 | ret*: Type
40 | params*: TypeList
41 | TypeList* = seq[Type]
42 |
43 | uint4* = range[0..15] # e.g. register v0..v15
44 |
45 | func equals*(a, b: Method): bool =
46 | a.class == b.class and a.name == b.name and a.prototype.equals(b.prototype)
47 | func equals*(a, b: Prototype): bool =
48 | a.ret == b.ret and a.params == b.params
49 |
50 | type
51 | Instr* = ref object
52 | opcode*: uint8
53 | args*: seq[Arg]
54 | Code* = ref object
55 | registers*: uint16
56 | ins*: uint16
57 | outs*: uint16 # "the number of words of outgoing argument space required by this code for method invocation"
58 | # tries: ?
59 | # debug_info: ?
60 | instrs*: seq[Instr]
61 |
62 | type
63 | ClassDef* = ref object
64 | class*: Type
65 | access*: set[Access]
66 | superclass*: MaybeType
67 | # interfaces: TypeList
68 | # sourcefile: String
69 | # annotations: ?
70 | class_data*: ClassData
71 | # static_values: ?
72 | ClassData* = ref object
73 | # static_fields*: ?
74 | #TODO: add some tests for rendered instance_fields
75 | instance_fields*: seq[EncodedField]
76 | direct_methods*: seq[EncodedMethod]
77 | virtual_methods*: seq[EncodedMethod]
78 | EncodedField* = ref object
79 | f*: Field
80 | access*: set[Access]
81 | EncodedMethod* = ref object
82 | m*: Method
83 | access*: set[Access]
84 | code*: MaybeCode
85 | Access* = enum
86 | Public = 0x1
87 | Private = 0x2
88 | Protected = 0x4
89 | Static = 0x8
90 | Final = 0x10
91 | Synchronized = 0x20
92 | Varargs = 0x80
93 | Native = 0x100
94 | Interface = 0x200
95 | Abstract = 0x400
96 | Annotation = 0x2000
97 | Enum = 0x4000
98 | Constructor = 0x1_0000
99 |
100 | NotImplementedYetError* = object of CatchableError
101 | ConsistencyError* = object of CatchableError
102 |
103 | proc hash*(proto: Prototype): Hash =
104 | var h: Hash = 0
105 | h = h !& hash(proto.ret)
106 | h = h !& hash(proto.params)
107 | result = !$h
108 | func equals[T](a, b: seq[T]): bool =
109 | if a.len != b.len: return false
110 | for i in 0..`() {.public, constructor, regs:1, ins:1, outs:1.} =
16 | invoke_direct 0, jproto Application.``()
17 | return_void
18 |
19 | discard dclass com.android.hello.HelloAndroid {.public.} of Activity:
20 | proc ``() {.public, constructor, regs:1, ins:1, outs:1.} =
21 | invoke_direct(0, jproto Activity.``())
22 | return_void()
23 | proc fooBar(_: Bundle, _: View, _: int)
24 | proc onCreate(_: Bundle) {.public, regs:3, ins:2, outs:2.} =
25 | invoke_super(1, 2, jproto Activity.onCreate(Bundle))
26 | const_high16(0, 0x7f03)
27 | invoke_virtual(1, 0, jproto HelloAndroid.setContentView(int))
28 | return_void()
29 |
30 | test "bugsnag.apk":
31 | let c =
32 | dclass com.bugsnag.dexexample.BugsnagApp {.public.} of Application:
33 | proc ``() {.public, constructor, regs:1, ins:1, outs:1.} =
34 | invoke_direct(0, jproto Application.``())
35 | return_void()
36 | checkpoint c.repr
37 | check c.equals ClassDef(
38 | class: "Lcom/bugsnag/dexexample/BugsnagApp;",
39 | access: {Public}, # TODO
40 | superclass: SomeType("Landroid/app/Application;"),
41 | class_data: ClassData(
42 | direct_methods: @[
43 | EncodedMethod(
44 | m: Method(
45 | class: "Lcom/bugsnag/dexexample/BugsnagApp;",
46 | name: "",
47 | prototype: Prototype(
48 | ret: "V",
49 | params: @[],
50 | ),
51 | ),
52 | access: {Public, Constructor},
53 | code: SomeCode(Code(
54 | registers: 1,
55 | ins: 1,
56 | outs: 1,
57 | instrs: @[
58 | invoke_direct(0, Method(class: "Landroid/app/Application;", name: "",
59 | prototype: Prototype(ret: "V", params: @[]))),
60 | return_void(),
61 | ],
62 | )),
63 | )
64 | ]
65 | )
66 | )
67 |
68 | test "hello_android.apk":
69 | let c =
70 | dclass com.android.hello.HelloAndroid {.public.} of Activity:
71 | proc ``() {.public, constructor, regs:1, ins:1, outs:1.} =
72 | invoke_direct(0, jproto Activity.``())
73 | return_void()
74 | proc onCreate(_: Bundle) {.public, regs:3, ins:2, outs:2.} =
75 | invoke_super(1, 2, jproto Activity.onCreate(Bundle))
76 | const_high16(0, 0x7f03)
77 | invoke_virtual(1, 0, jproto HelloAndroid.setContentView(int))
78 | return_void()
79 | checkpoint c.repr
80 | check c.equals ClassDef(
81 | class: "Lcom/android/hello/HelloAndroid;",
82 | access: {Public},
83 | superclass: SomeType("Landroid/app/Activity;"),
84 | class_data: ClassData(
85 | direct_methods: @[
86 | EncodedMethod(
87 | m: Method(
88 | class: "Lcom/android/hello/HelloAndroid;",
89 | name: "",
90 | prototype: Prototype(ret: "V", params: @[]),
91 | ),
92 | access: {Public, Constructor},
93 | code: SomeCode(Code(
94 | registers: 1,
95 | ins: 1,
96 | outs: 1,
97 | instrs: @[
98 | invoke_direct(0, Method(class: "Landroid/app/Activity;", name: "",
99 | prototype: Prototype(ret: "V", params: @[]))),
100 | return_void(),
101 | ],
102 | )),
103 | ),
104 | ],
105 | virtual_methods: @[
106 | EncodedMethod(
107 | m: Method(
108 | class: "Lcom/android/hello/HelloAndroid;",
109 | name: "onCreate",
110 | prototype: Prototype(
111 | ret: "V",
112 | params: @["Landroid/os/Bundle;"],
113 | ),
114 | ),
115 | access: {Public},
116 | code: SomeCode(Code(
117 | registers: 3,
118 | ins: 2,
119 | outs: 2,
120 | instrs: @[
121 | invoke_super(1, 2, Method(class: "Landroid/app/Activity;", name: "onCreate",
122 | prototype: Prototype(ret: "V", params: @["Landroid/os/Bundle;"]))),
123 | const_high16(0, 0x7f03),
124 | invoke_virtual(1, 0, Method(class: "Lcom/android/hello/HelloAndroid;", name: "setContentView",
125 | prototype: Prototype(ret: "V", params: @["I"]))),
126 | return_void(),
127 | ],
128 | )),
129 | ),
130 | ],
131 | )
132 | )
133 |
134 | test "with NimSelf":
135 | let c =
136 | dclass com.akavel.HasSelf {.public, nimSelf.}:
137 | proc ``() {.public, constructor, regs:1, ins:1, outs:0.} =
138 | return_void()
139 | checkpoint c.repr
140 | check c.equals ClassDef(
141 | class: "Lcom/akavel/HasSelf;",
142 | superclass: NoType(),
143 | access: {Public},
144 | class_data: ClassData(
145 | instance_fields: @[
146 | EncodedField(
147 | f: Field(
148 | class: "Lcom/akavel/HasSelf;",
149 | name: "nimSelf",
150 | typ: "J"),
151 | access: {Private}),
152 | ],
153 | direct_methods: @[
154 | EncodedMethod(
155 | m: Method(
156 | class: "Lcom/akavel/HasSelf;",
157 | name: "",
158 | prototype: Prototype(ret: "V", params: @[]),
159 | ),
160 | access: {Public, Constructor},
161 | code: SomeCode(Code(
162 | registers: 1,
163 | ins: 1,
164 | outs: 0,
165 | instrs: @[
166 | return_void(),
167 | ],
168 | )),
169 | ),
170 | ],
171 | )
172 | )
173 |
174 |
175 |
176 |
--------------------------------------------------------------------------------
/tests/tdex.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 | import unittest
3 | import strutils
4 | include dali/dex
5 | import dali
6 |
7 | let hello_world_apk = strip_space"""
8 | .d .e .x 0A .0 .3 .5 00 6F 53 89 BC 1E 79 B2 4F
9 | 1F 9C 09 66 15 23 2D 3B 56 65 32 C3 B5 81 B4 5A
10 | 70 02 00 00 70 00 00 00 78 56 34 12 00 00 00 00
11 | 00 00 00 00 DC 01 00 00 0C 00 00 00 70 00 00 00
12 | 07 00 00 00 A0 00 00 00 02 00 00 00 BC 00 00 00
13 | 01 00 00 00 D4 00 00 00 02 00 00 00 DC 00 00 00
14 | 01 00 00 00 EC 00 00 00 64 01 00 00 0C 01 00 00
15 | A6 01 00 00 3A 01 00 00 8A 01 00 00 40 01 00 00
16 | B4 01 00 00 76 01 00 00 54 01 00 00 6C 01 00 00
17 | 57 01 00 00 70 01 00 00 A1 01 00 00 C8 01 00 00
18 | 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
19 | 05 00 00 00 06 00 00 00 08 00 00 00 07 00 00 00
20 | 05 00 00 00 34 01 00 00 07 00 00 00 05 00 00 00
21 | 2C 01 00 00 04 00 01 00 0A 00 00 00 00 00 01 00
22 | 09 00 00 00 01 00 00 00 0B 00 00 00 00 00 00 00
23 | 01 00 00 00 02 00 00 00 00 00 00 00 FF FF FF FF
24 | 00 00 00 00 D1 01 00 00 00 00 00 00 02 00 01 00
25 | 02 00 00 00 00 00 00 00 08 00 00 00 62 00 00 00
26 | 1A 01 00 00 6E 20 01 00 10 00 0E 00 01 00 00 00
27 | 06 00 00 00 01 00 00 00 03 00 04 .L .h .w .; 00
28 | 12 .L .j .a .v .a ./ .l .a .n .g ./ .O .b .j .e
29 | .c .t .; 00 01 .V 00 13 .[ .L .j .a .v .a ./ .l
30 | .a .n .g ./ .S .t .r .i .n .g .; 00 02 .V .L 00
31 | 04 .m .a .i .n 00 12 .L .j .a .v .a ./ .l .a .n
32 | .g ./ .S .y .s .t .e .m .; 00 15 .L .j .a .v .a
33 | ./ .i .o ./ .P .r .i .n .t .S .t .r .e .a .m .;
34 | 00 03 .o .u .t 00 0C .H .e .l .l .o 20 .W .o .r
35 | .l .d .! 00 12 .L .j .a .v .a ./ .l .a .n .g ./
36 | .S .t .r .i .n .g .; 00 07 .p .r .i .n .t .l .n
37 | 00 00 00 01 00 00 09 8C 02 00 00 00 0C 00 00 00
38 | 00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00
39 | 0C 00 00 00 70 00 00 00 02 00 00 00 07 00 00 00
40 | A0 00 00 00 03 00 00 00 02 00 00 00 BC 00 00 00
41 | 04 00 00 00 01 00 00 00 D4 00 00 00 05 00 00 00
42 | 02 00 00 00 DC 00 00 00 06 00 00 00 01 00 00 00
43 | EC 00 00 00 01 20 00 00 01 00 00 00 0C 01 00 00
44 | 01 10 00 00 02 00 00 00 2C 01 00 00 02 20 00 00
45 | 0C 00 00 00 3A 01 00 00 00 20 00 00 01 00 00 00
46 | D1 01 00 00 00 10 00 00 01 00 00 00 DC 01 00 00
47 | """.dehexify
48 |
49 |
50 | test "synthesized hello_world.apk":
51 | let dex = newDex()
52 | dex.classes.add(ClassDef(
53 | class: "Lhw;",
54 | access: {Public},
55 | superclass: SomeType("Ljava/lang/Object;"),
56 | class_data: ClassData(
57 | direct_methods: @[
58 | EncodedMethod(
59 | m: Method(
60 | class: "Lhw;",
61 | name: "main",
62 | prototype: Prototype(
63 | ret: "V",
64 | params: @["[Ljava/lang/String;"]),
65 | ),
66 | access: {Public, Static},
67 | code: SomeCode(Code(
68 | registers: 2,
69 | ins: 1,
70 | outs: 2,
71 | instrs: @[
72 | sget_object(0, Field(class: "Ljava/lang/System;", typ: "Ljava/io/PrintStream;", name: "out")),
73 | const_string(1, "Hello World!"),
74 | invoke_virtual(0, 1, Method(class: "Ljava/io/PrintStream;", name: "println",
75 | prototype: Prototype(ret: "V", params: @["Ljava/lang/String;"]))),
76 | return_void(),
77 | ]))
78 | )
79 | ]
80 | )
81 | ))
82 | check dex.render.dumpHex == hello_world_apk.dumpHex
83 |
84 | test "synthesized hello_world.apk prettified with macros":
85 | let
86 | dex = newDex()
87 | PrintStream = "Ljava/io/PrintStream;"
88 | String = "Ljava/lang/String;"
89 | dex.classes.add(ClassDef(
90 | class: "Lhw;",
91 | access: {Public},
92 | superclass: SomeType("Ljava/lang/Object;"),
93 | class_data: ClassData(
94 | direct_methods: @[
95 | EncodedMethod(
96 | m: Method(
97 | class: "Lhw;",
98 | name: "main",
99 | prototype: Prototype(
100 | ret: "V",
101 | params: @["[Ljava/lang/String;"]),
102 | ),
103 | access: {Public, Static},
104 | code: SomeCode(Code(
105 | registers: 2,
106 | ins: 1,
107 | outs: 2,
108 | instrs: @[
109 | sget_object(0, Field(class: "Ljava/lang/System;", typ: "Ljava/io/PrintStream;", name: "out")),
110 | const_string(1, "Hello World!"),
111 | invoke_virtual(0, 1, jproto PrintStream.println(String)),
112 | return_void(),
113 | ]))
114 | )
115 | ]
116 | )
117 | ))
118 | check dex.render.dumpHex == hello_world_apk.dumpHex
119 |
120 | let bugsnag_sample_apk = strip_space"""
121 | 6465780A 30333800 7A44CBBB FB4AE841 0286C06A 8DF19000
122 | 3C5DE024 D07326A2 E0010000 70000000 78563412 00000000
123 | 00000000 64010000 05000000 70000000 03000000 84000000
124 | 01000000 90000000 00000000 00000000 02000000 9C000000
125 | 01000000 AC000000 14010000 CC000000 E4000000 EC000000
126 | 07010000 2C010000 2F010000 01000000 02000000 03000000
127 | 03000000 02000000 00000000 00000000 00000000 01000000
128 | 00000000 01000000 01000000 00000000 00000000 FFFFFFFF
129 | 00000000 57010000 00000000 01000100 01000000 00000000
130 | 04000000 70100000 00000E00 063C696E 69743E00 194C616E
131 | 64726F69 642F6170 702F4170 706C6963 6174696F 6E3B0023
132 | 4C636F6D 2F627567 736E6167 2F646578 6578616D 706C652F
133 | 42756773 6E616741 70703B00 01560026 7E7E4438 7B226D69
134 | 6E2D6170 69223A32 362C2276 65727369 6F6E223A 2276302E
135 | 312E3134 227D0000 00010001 818004CC 01000000 0A000000
136 | 00000000 01000000 00000000 01000000 05000000 70000000
137 | 02000000 03000000 84000000 03000000 01000000 90000000
138 | 05000000 02000000 9C000000 06000000 01000000 AC000000
139 | 01200000 01000000 CC000000 02200000 05000000 E4000000
140 | 00200000 01000000 57010000 00100000 01000000 64010000
141 | """.dehexify
142 |
143 | test "synthesized bugsnag.apk (FIXME: except checksums)":
144 | let dex = newDex()
145 | #-- Prime some strings, to make sure their order matches bugsnag_sample_apk
146 | dex.addStr""
147 | dex.addStr"Landroid/app/Application;"
148 | dex.addStr"Lcom/bugsnag/dexexample/BugsnagApp;"
149 | dex.addStr"V"
150 | dex.addStr"""~~D8{"min-api":26,"version":"v0.1.14"}"""
151 | dex.classes.add(ClassDef(
152 | class: "Lcom/bugsnag/dexexample/BugsnagApp;",
153 | access: {Public}, # TODO
154 | superclass: SomeType("Landroid/app/Application;"),
155 | class_data: ClassData(
156 | direct_methods: @[
157 | EncodedMethod(
158 | m: Method(
159 | class: "Lcom/bugsnag/dexexample/BugsnagApp;",
160 | name: "",
161 | prototype: Prototype(
162 | ret: "V",
163 | params: @[],
164 | ),
165 | ),
166 | access: {Public, Constructor},
167 | code: SomeCode(Code(
168 | registers: 1,
169 | ins: 1,
170 | outs: 1,
171 | instrs: @[
172 | invoke_direct(0, Method(class: "Landroid/app/Application;", name: "",
173 | prototype: Prototype(ret: "V", params: @[]))),
174 | return_void(),
175 | ],
176 | )),
177 | )
178 | ]
179 | )
180 | ))
181 | # FIXME(akavel): don't know why, but the SHA1 sum in bugsnag_sample_apk seems incorrect (!)
182 | check dex.render.substr(0x20).dumpHex == bugsnag_sample_apk.substr(0x20).dumpHex
183 |
184 | test "synthesized bugsnag.apk (FIXME: except checksums) prettified with macros":
185 | let
186 | dex = newDex()
187 | BugsnagApp = "Lcom/bugsnag/dexexample/BugsnagApp;"
188 | Application = "Landroid/app/Application;"
189 |
190 | #-- Prime some strings, to make sure their order matches bugsnag_sample_apk
191 | dex.addStr ""
192 | dex.addStr Application
193 | dex.addStr BugsnagApp
194 | dex.addStr "V"
195 | dex.addStr """~~D8{"min-api":26,"version":"v0.1.14"}"""
196 |
197 | dex.classes.add:
198 | dclass com.bugsnag.dexexample.BugsnagApp {.public.} of Application:
199 | proc ``() {.public, constructor, regs:1, ins:1, outs:1.} =
200 | invoke_direct(0, jproto Application.``())
201 | return_void()
202 |
203 | # FIXME(akavel): don't know why, but the SHA1 sum in bugsnag_sample_apk seems incorrect (!)
204 | check dex.render.substr(0x20).dumpHex == bugsnag_sample_apk.substr(0x20).dumpHex
205 |
206 | let hello_android_apk = strip_space"""
207 | 6465 780a 3033 3500 2f4f 153b 3623 8747
208 | 6d02 4697 5b1e 959d a8b1 2f0f 9c3a a14f
209 | 7802 0000 7000 0000 7856 3412 0000 0000
210 | 0000 0000 f001 0000 0a00 0000 7000 0000
211 | 0500 0000 9800 0000 0300 0000 ac00 0000
212 | 0000 0000 0000 0000 0500 0000 d000 0000
213 | 0100 0000 f800 0000 6001 0000 1801 0000
214 | 6201 0000 6a01 0000 6d01 0000 8501 0000
215 | 9a01 0000 bc01 0000 bf01 0000 c301 0000
216 | c701 0000 d101 0000 0100 0000 0200 0000
217 | 0300 0000 0400 0000 0500 0000 0500 0000
218 | 0400 0000 0000 0000 0600 0000 0400 0000
219 | 5401 0000 0700 0000 0400 0000 5c01 0000
220 | 0100 0000 0000 0000 0100 0200 0800 0000
221 | 0300 0000 0000 0000 0300 0200 0800 0000
222 | 0300 0100 0900 0000 0300 0000 0100 0000
223 | 0100 0000 0000 0000 ffff ffff 0000 0000
224 | e101 0000 0000 0000 0100 0100 0100 0000
225 | 0000 0000 0400 0000 7010 0000 0000 0e00
226 | 0300 0200 0200 0000 0000 0000 0900 0000
227 | 6f20 0100 2100 1500 037f 6e20 0400 0100
228 | 0e00 0000 0100 0000 0000 0000 0100 0000
229 | 0200 063c 696e 6974 3e00 0149 0016 4c61
230 | 6e64 726f 6964 2f61 7070 2f41 6374 6976
231 | 6974 793b 0013 4c61 6e64 726f 6964 2f6f
232 | 732f 4275 6e64 6c65 3b00 204c 636f 6d2f
233 | 616e 6472 6f69 642f 6865 6c6c 6f2f 4865
234 | 6c6c 6f41 6e64 726f 6964 3b00 0156 0002
235 | 5649 0002 564c 0008 6f6e 4372 6561 7465
236 | 000e 7365 7443 6f6e 7465 6e74 5669 6577
237 | 0000 0001 0102 8180 0498 0203 01b0 0200
238 | 0b00 0000 0000 0000 0100 0000 0000 0000
239 | 0100 0000 0a00 0000 7000 0000 0200 0000
240 | 0500 0000 9800 0000 0300 0000 0300 0000
241 | ac00 0000 0500 0000 0500 0000 d000 0000
242 | 0600 0000 0100 0000 f800 0000 0120 0000
243 | 0200 0000 1801 0000 0110 0000 0200 0000
244 | 5401 0000 0220 0000 0a00 0000 6201 0000
245 | 0020 0000 0100 0000 e101 0000 0010 0000
246 | 0100 0000 f001 0000
247 | """.dehexify
248 |
249 | test "synthesized hello_android.apk":
250 | let dex = newDex()
251 | #-- Prime some arrays, to make sure their order matches hello_android_apk
252 | dex.addStr""
253 | dex.addStr"I"
254 | dex.addStr"Landroid/app/Activity;"
255 | dex.addStr"Landroid/os/Bundle;"
256 | dex.addStr"Lcom/android/hello/HelloAndroid;"
257 | dex.addStr"V"
258 | dex.addStr"VI"
259 | dex.addStr"VL"
260 | dex.addStr"onCreate"
261 | dex.addStr"setContentView"
262 | dex.addTypeList(@["I"])
263 |
264 | dex.classes.add(ClassDef(
265 | class: "Lcom/android/hello/HelloAndroid;",
266 | access: {Public},
267 | superclass: SomeType("Landroid/app/Activity;"),
268 | class_data: ClassData(
269 | direct_methods: @[
270 | EncodedMethod(
271 | m: Method(
272 | class: "Lcom/android/hello/HelloAndroid;",
273 | name: "",
274 | prototype: Prototype(ret: "V", params: @[]),
275 | ),
276 | access: {Public, Constructor},
277 | code: SomeCode(Code(
278 | registers: 1,
279 | ins: 1,
280 | outs: 1,
281 | instrs: @[
282 | invoke_direct(0, Method(class: "Landroid/app/Activity;", name: "",
283 | prototype: Prototype(ret: "V", params: @[]))),
284 | return_void(),
285 | ],
286 | )),
287 | ),
288 | ],
289 | virtual_methods: @[
290 | EncodedMethod(
291 | m: Method(
292 | class: "Lcom/android/hello/HelloAndroid;",
293 | name: "onCreate",
294 | prototype: Prototype(
295 | ret: "V",
296 | params: @["Landroid/os/Bundle;"],
297 | ),
298 | ),
299 | access: {Public},
300 | code: SomeCode(Code(
301 | registers: 3,
302 | ins: 2,
303 | outs: 2,
304 | instrs: @[
305 | invoke_super(1, 2, Method(class: "Landroid/app/Activity;", name: "onCreate",
306 | prototype: Prototype(ret: "V", params: @["Landroid/os/Bundle;"]))),
307 | const_high16(0, 0x7f03),
308 | invoke_virtual(1, 0, Method(class: "Lcom/android/hello/HelloAndroid;", name: "setContentView",
309 | prototype: Prototype(ret: "V", params: @["I"]))),
310 | return_void(),
311 | ],
312 | )),
313 | ),
314 | ],
315 | )
316 | ))
317 | check dex.render.dumpHex == hello_android_apk.dumpHex
318 |
319 | test "synthesized hello_android.apk prettified with macros":
320 | let
321 | dex = newDex()
322 | HelloAndroid = "Lcom/android/hello/HelloAndroid;"
323 | Activity = "Landroid/app/Activity;"
324 | Bundle = "Landroid/os/Bundle;"
325 | #-- Prime some arrays, to make sure their order matches hello_android_apk
326 | dex.addStr ""
327 | dex.addStr "I"
328 | dex.addStr Activity
329 | dex.addStr Bundle
330 | dex.addStr HelloAndroid
331 | dex.addStr "V"
332 | dex.addStr "VI"
333 | dex.addStr "VL"
334 | dex.addStr "onCreate"
335 | dex.addStr "setContentView"
336 | dex.addTypeList(@["I"])
337 |
338 | dex.classes.add:
339 | dclass com.android.hello.HelloAndroid {.public.} of Activity:
340 | proc ``() {.public, constructor, regs:1, ins:1, outs:1.} =
341 | invoke_direct 0, jproto Activity.``()
342 | return_void
343 | proc onCreate(_: Bundle) {.public, regs:3, ins:2, outs:2.} =
344 | invoke_super 1, 2, jproto Activity.onCreate(Bundle)
345 | const_high16 0, 0x7f03
346 | invoke_virtual 1, 0, jproto HelloAndroid.setContentView(int)
347 | return_void
348 |
349 | check dex.render.dumpHex == hello_android_apk.dumpHex
350 |
351 | proc strip_space(s: string): string =
352 | return s.multiReplace(("\n", ""), (" ", ""))
353 |
354 | const HexChars = "0123456789ABCDEF"
355 |
356 | func printable(c: char): bool =
357 | let n = ord(c)
358 | return 0x21 <= n and n <= 0x7E
359 |
360 | proc dehexify(s: string): string =
361 | result = newString(s.len div 2)
362 | for i in 0 ..< s.len div 2:
363 | let chunk = s.substr(2 * i, 2 * i + 1)
364 | if chunk[0] == '.':
365 | result[i] = chunk[1]
366 | else:
367 | result[i] = parseHexStr(chunk)[0]
368 |
369 | proc dumpHex(s: string): string =
370 | if s.len == 0: return ""
371 | let nlines = (s.len + 15) div 16
372 | const
373 | left = 3*8 + 2 + 3*8 + 2
374 | right = 16
375 | line = left+right+1
376 | result = ' '.repeat(nlines*line)
377 | for i, ch in s:
378 | let
379 | y = i div 16
380 | xr = i mod 16
381 | xl = if xr < 8: 3*xr else: 3*xr + 1
382 | n = ord(ch)
383 | result[y*line + xl] = HexChars[n shr 4]
384 | result[y*line + xl + 1] = HexChars[n and 0x0F]
385 | result[y*line + left + xr - 1] = if printable(ch): ch else: '.'
386 | if xr == 0:
387 | result[y*line + left + right - 1] = '\n'
388 | result = "\n " & result
389 |
390 |
--------------------------------------------------------------------------------
/src/dali/macros.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 | import std/macros
3 | import strutils
4 | import sequtils
5 | import algorithm
6 | import sets
7 |
8 | import dali/utils/macromatch
9 |
10 | import dali/dex
11 |
12 | macro jproto*(prototype: untyped): untyped =
13 | ## jproto is a macro converting a prototype declaration into a dali Method object.
14 | ## Example:
15 | ##
16 | ## let HelloWorld = "Lcom/hello/HelloWorld;"
17 | ## let String = "Ljava/lang/String;"
18 | ## jproto HelloWorld.hello(String, int) -> String
19 | ## jproto HelloWorld.``()
20 | ## # NOTE: jproto argument must not be parenthesized; the following does not work unfortunately:
21 | ## # jproto(HelloWorld.hello(String, int) -> String)
22 | ##
23 | ## TODO: support Java array types, i.e. int[], int[][], etc.
24 | # echo proto.treeRepr
25 | # echo ret.treeRepr
26 | # result = nnkStmtList.newTree()
27 | # echo "----------------"
28 | var proto = prototype
29 |
30 | # Parse & verify that proto has correct syntax
31 | # ...return type if present
32 | var rett = newLit("V")
33 | if proto =~ Infix(Ident("->"), [], Ident(_)):
34 | rett = proto[2].handleJavaType
35 | proto = proto[1]
36 | # ...class & method name:
37 | if proto !~ Call(_):
38 | error "jproto expects a method declaration as an argument", proto
39 | if proto !~ Call(DotExpr(_), _):
40 | error "jproto expects dot-separated class & method name in the argument", proto
41 | if proto[0] !~ DotExpr(Ident(_), Ident(_)) and
42 | proto[0] !~ DotExpr(Ident(_), AccQuoted(_)):
43 | error "jproto expects exactly 2 dot-separated names in the argument", proto[0]
44 | # ... parameters list:
45 | for i in 1..`
121 | # # LATER LATER: some syntax for constructor calling base class constructor
122 | # var x: int # Nim int
123 | # proc onCreate(_: Bundle) {.dalvik, public, regs: 4, ...} =
124 | # invoke_super(2, 3, jproto Activity.onCreate(Bundle))
125 | # ...
126 | # proc sampleNative(y: jint): String
127 | # self.x = y
128 | # return jstring("x = {}" % self.x)
129 |
130 | macro classes_dex*(filename: string, body: untyped): untyped =
131 | result = nnkStmtList.newTree()
132 | # echo body.treeRepr
133 |
134 | # Expecting a list of "dclass Foo {.public.} of Bar:" definitions
135 | if body !~ StmtList(_):
136 | error "classes_dex expects a list of 'dclass' definitions", body
137 | for c in body:
138 | if c !~ Command(Ident("dclass"), [], []):
139 | error "expected 'dclass' keyword", c
140 |
141 | when defined android:
142 | for c in body:
143 | result.add dclass2native(c[1], c[2])
144 |
145 | when not defined android:
146 | let dex = genSym()
147 | let newDex = bindSym"newDex"
148 | result.add(quote do:
149 | let `dex` = `newDex`())
150 | for c in body:
151 | let cl = dclass2ClassDef(c[1], c[2])
152 | result.add(quote do:
153 | `dex`.classes.add(`cl`))
154 | let renderDex = bindSym"renderDex"
155 | result.add(quote do:
156 | `renderDex`(`dex`, `filename`))
157 |
158 | proc renderDex(dex: Dex, filename: string) =
159 | ## Temporary workaround for https://github.com/nim-lang/Nim/issues/12315
160 | ## (write & writeFile skip null bytes on Windows)
161 | let buf = dex.render
162 | var f: File
163 | if f.open(filename, fmWrite):
164 | try:
165 | f.writeBuffer(buf[0].unsafeAddr, buf.len)
166 | finally:
167 | close(f)
168 | else:
169 | raise newException(IOError, "cannot open: classes.dex")
170 |
171 | proc dclass2native(header, body: NimNode): seq[NimNode] =
172 | var h = parseDClassHeader(header)
173 | for procDef in body:
174 | let p = parseDClassProc(procDef)
175 | if not p.native:
176 | continue
177 | let
178 | # TODO: handle '$' in class names
179 | nameAST = ident("Java_" & h.fullName.join("_") & "_" & p.name.strVal)
180 | bodyAST = p.body
181 | jenv = ident("jenv")
182 | jthis = ident("jthis")
183 | # Note: below procAST is not complete yet at this
184 | # phase, but it's a good starting point.
185 | procAST = quote do:
186 | proc `nameAST`*(`jenv`: JNIEnvPtr, `jthis`: jobject) {.cdecl,exportc,dynlib.} =
187 | `bodyAST`
188 | # Transplant the proc's returned type
189 | procAST.params[0] = p.ret
190 | # Append original proc's parameters to the procAST
191 | for param in p.params:
192 | procAST.params.add newIdentDefs(param.name, param.typ)
193 | result.add procAST
194 |
195 | proc dclass2ClassDef(header, body: NimNode): NimNode =
196 | var h = parseDClassHeader(header)
197 |
198 | # Translate the class name to a string understood by Java bytecode. Do it
199 | # here as it'll be needed below.
200 | let classString = "L" & h.fullName.join("/") & ";"
201 |
202 | # Parse class body - a list of proc definitions
203 | if body !~ StmtList(_):
204 | error "expected a list of proc definitions", body
205 | var
206 | directMethods: seq[NimNode]
207 | virtualMethods: seq[NimNode]
208 | for procDef in body:
209 | let p = parseDClassProc(procDef)
210 |
211 | var instrs: seq[NimNode]
212 | if not p.native:
213 | # check proc body
214 | if p.body =~ Empty():
215 | continue
216 | if p.body !~ StmtList(_):
217 | error "unexpected syntax of proc body", p.body
218 | for stmt in p.body:
219 | case stmt.kind
220 | of nnkCall:
221 | instrs.add stmt
222 | of nnkCommand:
223 | var call = newTree(nnkCall)
224 | stmt.copyChildrenTo(call)
225 | call.copyLineInfo(stmt)
226 | instrs.add call
227 | of nnkIdent:
228 | instrs.add newCall(stmt)
229 | else:
230 | error "expected proc body to contain only Android assembly instructions", stmt
231 |
232 | # Rewrite the procedure as an EncodedMethod object
233 | let
234 | name = p.name.collectProcName
235 | paramsTree = newTree(nnkBracket, p.params[0..^1].mapIt(it.typ.handleJavaType))
236 | ret = p.ret.handleJavaType
237 | procAccessTree = newTree(nnkCurly, p.pragmas)
238 | regsTree = newLit(p.regs)
239 | insTree = newLit(p.ins)
240 | outsTree = newLit(p.outs)
241 | codeTree =
242 | if instrs.len > 0:
243 | let instrsTree = newTree(nnkBracket, instrs)
244 | quote do:
245 | SomeCode(Code(
246 | registers: `regsTree`,
247 | ins: `insTree`,
248 | outs: `outsTree`,
249 | instrs: @`instrsTree`))
250 | else:
251 | quote do:
252 | NoCode()
253 | let enc = quote do:
254 | EncodedMethod(
255 | m: Method(
256 | class: `classString`,
257 | name: `name`,
258 | prototype: Prototype(
259 | ret: `ret`,
260 | params: @ `paramsTree`)),
261 | access: `procAccessTree`,
262 | code: `codeTree`)
263 | # echo enc.repr # this prints Nim code - Thanks @disruptek on Nim chatroom for the hint!
264 | # echo enc.treeRepr
265 | if p.direct:
266 | directMethods.add enc
267 | else:
268 | virtualMethods.add enc
269 |
270 | let classTree = newLit(classString)
271 |
272 | # Add "nimSelf" field to the class if specified in pragmas.
273 | # TODO: add it always when there are any native methods?
274 | let instanceFieldsTree = newTree(nnkBracket)
275 | if h.hasNimSelf:
276 | instanceFieldsTree.add(quote do:
277 | EncodedField(
278 | f: Field(
279 | class: `classTree`,
280 | typ: "J",
281 | name: "nimSelf"),
282 | access: {Private}))
283 |
284 | # Render collected data into a ClassDef object
285 | let
286 | accessTree = newTree(nnkCurly, h.pragmas)
287 | super = h.super
288 | superclassTree =
289 | if super =~ Empty():
290 | quote do:
291 | NoType()
292 | else:
293 | quote do:
294 | SomeType(`super`)
295 | directMethodsTree = newTree(nnkBracket, directMethods)
296 | virtualMethodsTree = newTree(nnkBracket, virtualMethods)
297 | # TODO: also, create a `let` identifer for the class name
298 | result = quote do:
299 | ClassDef(
300 | class: `classTree`,
301 | access: `accessTree`,
302 | superclass: `superclassTree`,
303 | class_data: ClassData(
304 | instance_fields: @`instanceFieldsTree`,
305 | direct_methods: @`directMethodsTree`,
306 | virtual_methods: @`virtualMethodsTree`))
307 |
308 |
309 |
310 | type DClassHeaderInfo = tuple
311 | super: NimNode # nnkEmpty if no superclass declared
312 | pragmas: seq[NimNode] # pragmas, with first letter modified to uppercase
313 | hasNimSelf: bool
314 | fullName: seq[string] # Fully Qualified Class Name
315 |
316 | proc parseDClassHeader(header: NimNode): DClassHeaderInfo =
317 | ## parseDClassHeader parses Java class header (class name & various modifiers)
318 | ## specified in Nim-like syntax.
319 | ## Example:
320 | ##
321 | ## com.akavel.hello2.HelloActivity {.public.} of Activity
322 | ##
323 | ## corresponds to:
324 | ##
325 | ## package com.akavel.hello2;
326 | ## public class HelloActivity extends Activity { ... }
327 |
328 | # [com.foo.Bar {.public.}] of Activity
329 | result.super = newEmptyNode()
330 | var rest = header
331 | if header =~ Infix(Ident("of"), [], []):
332 | result.super = header[2]
333 | rest = header[1]
334 |
335 | # [com.foo.Bar] {.public.}
336 | if rest =~ PragmaExpr(_):
337 | if rest !~ PragmaExpr([], []):
338 | error "encountered unexpected syntax (too many words)", rest
339 | if rest !~ PragmaExpr([], Pragma(_)):
340 | error "expected pragmas list", rest[1]
341 | for p in rest[1]:
342 | if p !~ Ident(_):
343 | error "expected a simple pragma identifer", p
344 | let x = ident(p.strVal.capitalizeAscii)
345 | if x.strVal.eqIdent "NimSelf":
346 | result.hasNimSelf = true
347 | continue
348 | x.copyLineInfo(p)
349 | result.pragmas.add x
350 | rest = rest[0]
351 |
352 | # com.foo.[Bar]
353 | while rest =~ DotExpr(_):
354 | # TODO: allow and handle "$" character in class names
355 | if rest !~ DotExpr([], Ident(_)):
356 | error "expected 2+ dot-separated simple identifiers", rest
357 | result.fullName.add rest[1].strVal
358 | rest = rest[0]
359 |
360 | # [com.foo.]Bar
361 | if rest !~ Ident(_):
362 | error "expected dot-separated simple identifiers", rest
363 | result.fullname.add rest.strVal
364 | # After the processing above, fullName has unnatural, reversed order of segments; fix this
365 | reverse(result.fullname)
366 |
367 | type DClassProcInfo = tuple
368 | name: NimNode
369 | pragmas: seq[NimNode]
370 | direct, native: bool
371 | regs, ins, outs: int
372 | params: seq[tuple[name, typ: NimNode]]
373 | ret: NimNode
374 | body: NimNode
375 |
376 | proc parseDClassProc(procDef: NimNode): DClassProcInfo =
377 | if procDef !~ ProcDef(_):
378 | error "expected a proc definition", procDef
379 | # Parse proc header
380 | const
381 | # important indexes in nnkProcDef children,
382 | # see: https://nim-lang.org/docs/macros.html#statements-procedure-declaration
383 | i_name = 0
384 |
385 | # proc name must be a simple identifier, or backtick-quoted name
386 | if procDef[i_name] !~ Ident(_) and procDef[i_name] !~ AccQuoted(_):
387 | error "expected a proc name to be a simple identifier, or a backtick-quoted name", procDef[0]
388 | result.name = procDef[i_name]
389 |
390 | if procDef[1] !~ Empty():
391 | error "unexpected term rewriting pattern", procDef[1]
392 | if procDef[2] !~ Empty():
393 | error "unexpected generic type param", procDef[2]
394 | # TODO: shouldn't below also contain Final???
395 | const directMethodPragmas = toHashSet(["Static", "Private", "Constructor"])
396 | if procDef.pragma =~ Pragma(_):
397 | for p in procDef.pragma:
398 | if p =~ Ident(_):
399 | let x = ident(p.strVal.capitalizeAscii)
400 | x.copyLineInfo(p)
401 | result.pragmas.add x
402 | if x.strVal in directMethodPragmas:
403 | result.direct = true
404 | elif x.strVal == "Native":
405 | result.native = true
406 | elif p =~ ExprColonExpr(Ident(_), IntLit(_)):
407 | case p[0].strVal
408 | of "regs": result.regs = p[1].intVal.int
409 | of "ins": result.ins = p[1].intVal.int
410 | of "outs": result.outs = p[1].intVal.int
411 | else: error "expected one of: 'regs: N', 'ins: N', 'outs: N' or access pragmas", p[0]
412 | else:
413 | error "unexpected format of pragma", p
414 | # proc return type
415 | result.ret = ident("void") # 'void' by default
416 | if procDef.params[0] !~ Empty():
417 | result.ret = procDef.params[0]
418 | # check & collect proc params
419 | result.params = extractParams(procDef)
420 | # copy proc body
421 | result.body = procDef.body
422 |
423 | proc extractParams(procDef: NimNode): seq[tuple[name, typ: NimNode]] =
424 | for p in procDef.params[1..^1]:
425 | if p[^2] =~ Empty(): error "missing type for param", p
426 | if p[^1] !~ Empty(): error "default param values not supported", p[^1]
427 | for name in p[0..^3]:
428 | result.add (name, p[^2])
429 |
430 |
--------------------------------------------------------------------------------
/src/dali/dex.nim:
--------------------------------------------------------------------------------
1 | {.experimental: "codeReordering".}
2 | import critbits
3 | import strutils
4 | import sequtils
5 | import std/sha1
6 | import tables
7 |
8 | import patty
9 |
10 | import dali/utils/blob
11 | import dali/utils/sortedset
12 |
13 | import dali/types
14 |
15 | # Potentially useful bibliography
16 | #
17 | # DEX:
18 | # - https://github.com/corkami/pics/blob/master/binary/DalvikEXecutable.pdf
19 | # - [dex-format]: https://source.android.com/devices/tech/dalvik/dex-format
20 | # - https://blog.bugsnag.com/dex-and-d8/
21 | # - http://benlynn.blogspot.com/2009/02/minimal-dalvik-executables_06.html
22 | #
23 | # APK:
24 | # - https://fractalwrench.co.uk/posts/playing-apk-golf-how-low-can-an-android-app-go/
25 | # - https://github.com/fractalwrench/ApkGolf
26 | #
27 | # Opcodes:
28 | # - https://github.com/corkami/pics/blob/master/binary/opcodes_tables_compact.pdf
29 | # - https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html
30 | #
31 | # MORE:
32 | # - https://github.com/JesusFreke/smali
33 | # - https://github.com/linkedin/dexmaker
34 | # - https://github.com/iBotPeaches/Apktool
35 |
36 | #########
37 | # NOTE(akavel): this must be early, to make sure it's used, as codeReordering fails to move it
38 | #########
39 | proc `<`(p1, p2: Prototype): bool =
40 | # echo "called <"
41 | if p1.ret != p2.ret:
42 | return p1.ret < p2.ret
43 | for i in 0 ..< min(p1.params.len, p2.params.len):
44 | if p1.params[i] != p2.params[i]:
45 | return p1.params[i] < p2.params[i]
46 | return p1.params.len < p2.params.len
47 | converter toUint32[T: enum](s: set[T]): uint32 =
48 | for v in s:
49 | result = result or v.ord.uint32
50 | #########
51 | #########
52 |
53 | proc newDex*(): Dex =
54 | new(result)
55 |
56 | type
57 | Dex* = ref object
58 | # Note: below fields are generally ordered from simplest to more complex
59 | # (in order of dependency)
60 | strings: CritBitTree[int] # value: order of addition
61 | types: SortedSet[string]
62 | typeLists: seq[seq[Type]]
63 | # NOTE: prototypes must have no duplicates, TODO: and be sorted by:
64 | # (ret's type ID; args' type ID)
65 | prototypes: SortedSet[Prototype]
66 | # NOTE: fields must have no duplicates, TODO: and be sorted by:
67 | # (class type ID, field name's string ID, field's type ID)
68 | fields: SortedSet[tuple[class: Type, name: string, typ: Type]]
69 | # NOTE: methods must have no duplicates, TODO: and be sorted by:
70 | # (class type ID, name's string ID, prototype's proto ID)
71 | methods: SortedSet[tuple[class: Type, name: string, proto: Prototype]]
72 | classes*: seq[ClassDef]
73 |
74 |
75 | proc render*(dex: Dex): string =
76 | # stderr.write(dex.repr)
77 | dex.collect()
78 |
79 | # Storage for offsets where various sections of the file
80 | # start. Will be needed to render map_list.
81 | # NOTE: n is number of elements in the section, not length in bytes.
82 | var sections: seq[tuple[kind: uint16, pos: uint32, n: int]]
83 |
84 | # blob is the buffer where we will render the binary contents of the .dex file
85 | var blob: Blob
86 |
87 | # slots are places in the blob, where we can't put data immediately. That's
88 | # because the value that should be there depends on some data further along
89 | # in the file. We bookmark some space for them in the blob, for filling in
90 | # later, when we will know what to put in the slot.
91 | var slots: tuple[
92 | adlerSum: Slot32,
93 | fileSize: Slot32,
94 | mapOffset: Slot32,
95 | stringIdsOff: Slot32,
96 | typeIdsOff: Slot32,
97 | protoIdsOff: Slot32,
98 | fieldIdsOff: Slot32,
99 | methodIdsOff: Slot32,
100 | classDefsOff: Slot32,
101 | dataSize: Slot32,
102 | dataOff: Slot32,
103 | stringOffsets: seq[Slot32],
104 | ]
105 | slots.stringOffsets.setLen(dex.strings.len)
106 |
107 | # FIXME: ensure correct padding everywhere
108 |
109 | #-- Partially render header
110 | # Most of it can only be calculated after the rest of the segments.
111 | sections.add (0x0000'u16, blob.pos, 1)
112 | # TODO: handle various versions of targetSdkVersion file, not only 035
113 | blob.puts "dex\n035\x00" # Magic prefix
114 | blob.put32 >> slots.adlerSum
115 | blob.skip(20) # SHA1 sum; we will fill it much later
116 | blob.put32 >> slots.fileSize
117 | blob.put32 0x70 # Header size
118 | blob.put32 0x12345678 # Endian constant
119 | blob.put32 0 # link_size
120 | blob.put32 0 # link_off
121 | blob.put32 >> slots.mapOffset
122 | blob.put32 dex.strings.len.uint32
123 | blob.put32 >> slots.stringIdsOff
124 | blob.put32 dex.types.len.uint32
125 | blob.put32 >> slots.typeIdsOff
126 | blob.put32 dex.prototypes.len.uint32
127 | blob.put32 >> slots.protoIdsOff
128 | blob.put32 dex.fields.len.uint32
129 | blob.put32 >> slots.fieldIdsOff
130 | blob.put32 dex.methods.len.uint32
131 | blob.put32 >> slots.methodIdsOff
132 | blob.put32 dex.classes.len.uint32
133 | blob.put32 >> slots.classDefsOff
134 | blob.put32 >> slots.dataSize
135 | blob.put32 >> slots.dataOff
136 |
137 | # stderr.write(blob.string.dumpHex)
138 | # stderr.write("\n")
139 |
140 | # blob.reserve(0x70 - blob.pos.int)
141 |
142 | #-- Partially render string_ids
143 | # We preallocate space for the list of string offsets. We cannot fill it yet, as its contents
144 | # will depend on the size of the other segments.
145 | sections.add (0x0001'u16, blob.pos, dex.strings.len)
146 | blob[slots.stringIdsOff] = blob.pos
147 | for i in 0 ..< dex.strings.len:
148 | blob.put32 >> slots.stringOffsets[i]
149 |
150 | # stderr.write(blob.string.dumpHex)
151 | # stderr.write("\n")
152 |
153 | #-- Render typeIDs.
154 | sections.add (0x0002'u16, blob.pos, dex.types.len)
155 | blob[slots.typeIdsOff] = blob.pos
156 | let stringIds = dex.stringsOrdering
157 | # dex.types are already stored sorted, same as dex.strings, so we don't need
158 | # to sort again by type IDs
159 | for t in dex.types:
160 | blob.put32 stringIds[dex.strings[t]].uint32
161 |
162 | #-- Partially render proto IDs.
163 | # We cannot fill offsets for parameters (type lists), as they'll depend on the size of the
164 | # segments inbetween.
165 | sections.add (0x0003'u16, blob.pos, dex.prototypes.len)
166 | blob[slots.protoIdsOff] = blob.pos
167 | var typeListOffsets: Slots32[seq[Type]]
168 | for p in dex.prototypes:
169 | blob.put32 stringIds[dex.strings[p.descriptor]].uint32
170 | blob.put32 dex.types.search(p.ret).uint32
171 | blob.put32 >>: slot
172 | typeListOffsets.add(p.params, slot)
173 | # echo p.ret, " ", p.params
174 |
175 | #-- Render field IDs
176 | if dex.fields.len > 0:
177 | sections.add (0x0004'u16, blob.pos, dex.fields.len)
178 | blob[slots.fieldIdsOff] = blob.pos
179 | for f in dex.fields:
180 | blob.put16 dex.types.search(f.class).uint16
181 | blob.put16 dex.types.search(f.typ).uint16
182 | blob.put32 stringIds[dex.strings[f.name]].uint32
183 |
184 | #-- Render method IDs
185 | sections.add (0x0005'u16, blob.pos, dex.methods.len)
186 | if dex.methods.len > 0:
187 | blob[slots.methodIdsOff] = blob.pos
188 | for m in dex.methods:
189 | # echo $m
190 | blob.put16 dex.types.search(m.class).uint16
191 | blob.put16 dex.prototypes.search(m.proto).uint16
192 | blob.put32 stringIds[dex.strings[m.name]].uint32
193 |
194 | #-- Partially render class defs.
195 | sections.add (0x0006'u16, blob.pos, dex.classes.len)
196 | blob[slots.classDefsOff] = blob.pos
197 | var classDataOffsets: Slots32[Type]
198 | const NO_INDEX = 0xffff_ffff'u32
199 | for c in dex.classes:
200 | blob.put32 dex.types.search(c.class).uint32
201 | blob.put32 c.access.uint32
202 | match c.superclass:
203 | SomeType(t):
204 | blob.put32 dex.types.search(t).uint32
205 | NoType:
206 | blob.put32 NO_INDEX
207 | blob.put32 0'u32 # TODO: interfaces_off
208 | blob.put32 NO_INDEX # TODO: source_file_idx
209 | blob.put32 0'u32 # TODO: annotations_off
210 | blob.put32 >>: slot
211 | classDataOffsets.add(c.class, slot)
212 | blob.put32 0'u32 # TODO: static_values
213 |
214 | #-- Render code items
215 | let dataStart = blob.pos
216 | blob[slots.dataOff] = dataStart
217 | var
218 | codeItems = 0
219 | codeOffsets: Table[tuple[class: Type, name: string, proto: Prototype], uint32]
220 | for c in dex.classes:
221 | let cd = c.class_data
222 | for dm in cd.direct_methods & cd.virtual_methods:
223 | if dm.code.kind == MaybeCodeKind.SomeCode:
224 | codeItems.inc()
225 | let code = dm.code.code
226 | blob.pad32()
227 | codeOffsets[dm.m.asTuple] = blob.pos
228 | blob.put16 code.registers
229 | blob.put16 code.ins
230 | blob.put16 code.outs
231 | blob.put16 0'u16 # TODO: tries_size
232 | blob.put32 0'u32 # TODO: debug_info_off
233 | blob.put32 >>: slot # This shall be filled with size of instrs, in 16-bit code units
234 | dex.renderInstrs(blob, code.instrs, stringIds)
235 | blob[slot] = (blob.pos - slot.uint32 - 4) div 2
236 | if codeItems > 0:
237 | sections.add (0x2001'u16, dataStart, codeItems)
238 |
239 | #-- Render type lists
240 | blob.pad32()
241 | if dex.typeLists.len > 0:
242 | sections.add (0x1001'u16, blob.pos, dex.typeLists.len)
243 | for l in dex.typeLists:
244 | blob.pad32()
245 | typeListOffsets.setAll(l, blob.pos, blob)
246 | blob.put32 l.len.uint32
247 | for t in l:
248 | blob.put16 dex.types.search(t).uint16
249 |
250 | #-- Render strings data
251 | sections.add (0x2002'u16, blob.pos, dex.strings.len)
252 | for s in dex.stringsAsAdded:
253 | let slot = slots.stringOffsets[stringIds[dex.strings[s]]]
254 | blob[slot] = blob.pos
255 | # FIXME: MUTF-8: encode U+0000 as hex: C0 80
256 | # FIXME: MUTF-8: use CESU-8 to encode code-points from beneath Basic Multilingual Plane (> U+FFFF)
257 | # FIXME: length *in UTF-16 code units*, as ULEB128
258 | blob.put_uleb128 s.len.uint32
259 | blob.puts s & "\x00"
260 |
261 | #-- Render class data
262 | sections.add (0x2000'u16, blob.pos, dex.classes.len)
263 | for c in dex.classes:
264 | classDataOffsets.setAll(c.class, blob.pos, blob)
265 | let d = c.class_data
266 | blob.put_uleb128 0 # TODO: static_fields_size
267 | blob.put_uleb128 d.instance_fields.len.uint32
268 | blob.put_uleb128 d.direct_methods.len.uint32
269 | blob.put_uleb128 d.virtual_methods.len.uint32
270 | # TODO: static_fields
271 | dex.renderEncodedFields(blob, d.instance_fields)
272 | dex.renderEncodedMethods(blob, d.direct_methods, codeOffsets)
273 | dex.renderEncodedMethods(blob, d.virtual_methods, codeOffsets)
274 |
275 | #-- Render map_list
276 | blob.pad32()
277 | sections.add (0x1000'u16, blob.pos, 1)
278 | blob[slots.mapOffset] = blob.pos
279 | blob.put32 sections.len.uint32
280 | for s in sections:
281 | blob.put16 s.kind
282 | blob.skip(2) # unused
283 | blob.put32 s.n.uint32
284 | blob.put32 s.pos
285 |
286 | #-- Fill remaining slots related to file size
287 | blob[slots.dataSize] = blob.pos - dataStart # FIXME: round to 64?
288 | blob[slots.fileSize] = blob.pos
289 | #-- Fill checksums
290 | let sha1 = secureHash(blob.string.substr(0x20)).Sha1Digest
291 | for i in 0 ..< 20:
292 | blob.string[0x0c + i] = sha1[i].char
293 | blob[slots.adlerSum] = adler32(blob.string.substr(0x0c))
294 | # stderr.write(blob.string.dumpHex)
295 | # stderr.write("\n")
296 |
297 | return blob.string
298 |
299 |
300 | proc collect(dex: Dex) =
301 | # Collect strings and all the things from classes.
302 | # (types, prototypes/signatures, fields, methods)
303 | for c in dex.classes:
304 | dex.addType(c.class)
305 | if c.superclass.kind == MaybeTypeKind.SomeType:
306 | dex.addType(c.superclass.typ)
307 | let cd = c.class_data
308 | for f in cd.instance_fields:
309 | dex.addField(f.f)
310 | for dm in cd.direct_methods & cd.virtual_methods:
311 | dex.addMethod(dm.m)
312 | if dm.code.kind == MaybeCodeKind.SomeCode:
313 | for instr in dm.code.code.instrs:
314 | for arg in instr.args:
315 | match arg:
316 | RawX(_): discard
317 | RawXX(_): discard
318 | RawXXXX(_): discard
319 | RegX(_): discard
320 | RegXX(_): discard
321 | FieldXXXX(f):
322 | dex.addField(f)
323 | StringXXXX(s):
324 | dex.addStr(s)
325 | TypeXXXX(t):
326 | dex.addType(t)
327 | MethodXXXX(m):
328 | dex.addMethod(m)
329 |
330 | proc renderEncodedFields(dex: Dex, blob: var Blob, fields: openArray[EncodedField]) =
331 | var prev = 0
332 | for f in fields:
333 | let tupl = f.f.asTuple
334 | let idx = dex.fields.search(tupl)
335 | blob.put_uleb128 uint32(idx - prev)
336 | prev = idx
337 | blob.put_uleb128 f.access.toUint32
338 |
339 | proc renderEncodedMethods(dex: Dex, blob: var Blob, methods: openArray[EncodedMethod], codeOffsets: Table[tuple[class: Type, name: string, proto: Prototype], uint32]) =
340 | var prev = 0
341 | for m in methods:
342 | let tupl = m.m.asTuple
343 | let idx = dex.methods.search(tupl)
344 | blob.put_uleb128 uint32(idx - prev)
345 | prev = idx
346 | blob.put_uleb128 m.access.toUint32
347 | if Native notin m.access and Abstract notin m.access:
348 | blob.put_uleb128 codeOffsets[tupl]
349 | else:
350 | blob.put_uleb128 0
351 |
352 | proc renderInstrs(dex: Dex, blob: var Blob, instrs: openArray[Instr], stringIds: openArray[int]) =
353 | var
354 | high = true
355 | for instr in instrs:
356 | blob.putc instr.opcode.chr
357 | for arg in instr.args:
358 | # FIXME(akavel): padding
359 | match arg:
360 | RawX(v):
361 | blob.put4 v, high
362 | high = not high
363 | RawXX(v):
364 | blob.putc v.chr
365 | RawXXXX(v):
366 | blob.put16 v
367 | RegX(v):
368 | blob.put4 v, high
369 | high = not high
370 | RegXX(v):
371 | blob.putc v.chr
372 | FieldXXXX(v):
373 | blob.put16 dex.fields.search((v.class, v.name, v.typ)).uint16
374 | StringXXXX(v):
375 | blob.put16 stringIds[dex.strings[v]].uint16
376 | TypeXXXX(v):
377 | blob.put16 dex.types.search(v).uint16
378 | MethodXXXX(v):
379 | blob.put16 dex.methods.search((v.class, v.name, v.prototype)).uint16
380 |
381 |
382 | proc addField(dex: Dex, f: Field) =
383 | dex.addType(f.class)
384 | dex.addType(f.typ)
385 | dex.addStr(f.name)
386 | dex.fields.incl((f.class, f.name, f.typ))
387 |
388 | proc addMethod(dex: Dex, m: Method) =
389 | dex.addType(m.class)
390 | dex.addPrototype(m.prototype)
391 | dex.addStr(m.name)
392 | dex.methods.incl((m.class, m.name, m.prototype))
393 |
394 | proc addPrototype(dex: Dex, proto: Prototype) =
395 | dex.addType(proto.ret)
396 | dex.addTypeList(proto.params)
397 | dex.prototypes.incl(proto)
398 | dex.addStr(proto.descriptor)
399 |
400 | proc descriptor(proto: Prototype): string =
401 | proc typeChar(t: Type): string =
402 | if t.len==1 and t[0] in {'V','Z','B','S','C','I','J','F','D'}:
403 | return t
404 | elif t.len>=1 and t[0] in {'[','L'}:
405 | return "L"
406 | else:
407 | raise newException(ConsistencyError, "unexpected type in prototype: " & t)
408 |
409 | return (proto.ret & proto.params).map(typeChar).join
410 |
411 | proc addTypeList(dex: Dex, ts: seq[Type]) =
412 | if ts.len == 0:
413 | return
414 | for t in ts:
415 | dex.addType(t)
416 | if ts notin dex.typeLists:
417 | dex.typeLists.add(ts)
418 |
419 | proc addType(dex: Dex, t: Type) =
420 | dex.addStr(t)
421 | dex.types.incl(t)
422 |
423 | proc addStr(dex: Dex, s: string) =
424 | if s.contains({'\x00', '\x80'..'\xFF'}):
425 | raise newException(NotImplementedYetError, "strings with 0x00 or 0x80..0xFF bytes are not yet supported")
426 | discard dex.strings.containsOrIncl(s, dex.strings.len)
427 | # "This list must be sorted by string contents, using UTF-16 code point
428 | # values (not in a locale-sensitive manner), and it must not contain any
429 | # duplicate entries." [dex-format] <- I think this is guaranteed by UTF-8 + CritBitTree type
430 |
431 | proc stringsOrdering(dex: Dex): seq[int] =
432 | var i = 0
433 | result.setLen dex.strings.len
434 | for s, added in dex.strings:
435 | result[added] = i
436 | inc i
437 |
438 | proc stringsAsAdded(dex: Dex): seq[string] =
439 | result.setLen dex.strings.len
440 | for s, added in dex.strings:
441 | result[added] = s
442 |
443 | func asTuple(f: Field): tuple[class: Type, name: string, typ: Type] =
444 | return (class: f.class, name: f.name, typ: f.typ)
445 | func asTuple(m: Method): tuple[class: Type, name: string, proto: Prototype] =
446 | return (class: m.class, name: m.name, proto: m.prototype)
447 |
448 | proc adler32(s: string): uint32 =
449 | # https://en.wikipedia.org/wiki/Adler-32
450 | var a: uint32 = 1
451 | var b: uint32 = 0
452 | const MOD_ADLER = 65521
453 | for c in s:
454 | a = (a + c.uint32) mod MOD_ADLER
455 | b = (b + a) mod MOD_ADLER
456 | result = (b shl 16) or a
457 |
458 |
--------------------------------------------------------------------------------
/jni_wrapper.nim:
--------------------------------------------------------------------------------
1 | # NOTE(akavel): copied from github.com/yglukhov/jnim, with jvm_finder features disabled
2 |
3 | import os, dynlib, strutils, macros, options
4 |
5 | # from jvm_finder import CT_JVM, findJVM
6 |
7 | when defined macosx:
8 | {.emit: """
9 | #include
10 | """.}
11 | {.passL: "-framework CoreFoundation".}
12 |
13 | {.warning[SmallLshouldNotBeUsed]: off.}
14 |
15 | type
16 | JNIException* = object of Exception
17 |
18 | proc newJNIException*(msg: string): ref JNIException =
19 | newException(JNIException, msg)
20 |
21 | template jniAssert*(call: untyped): untyped =
22 | if not `call`:
23 | raise newJNIException(call.astToStr & " is false")
24 |
25 | template jniAssert*(call: untyped, msg: string): untyped =
26 | if not `call`:
27 | raise newJNIException(msg)
28 |
29 | template jniAssertEx*(call: untyped, msg: string): untyped =
30 | if not `call`:
31 | raise newJNIException(msg & " (" & call.astToStr & " is false)")
32 |
33 | template jniCall*(call: untyped): untyped =
34 | let res = `call`
35 | if res != 0.jint:
36 | raise newJNIException(call.astToStr & " returned " & $res)
37 |
38 | template jniCall*(call: untyped, msg: string): untyped =
39 | let res = `call`
40 | if res != 0.jint:
41 | raise newJNIException(msg & " (result = " & $res & ")")
42 |
43 | template jniCallEx*(call: untyped, msg: string): untyped =
44 | let res = `call`
45 | if res != 0.jint:
46 | raise newJNIException(msg & " (" & call.astToStr & " returned " & $res & ")")
47 |
48 | type
49 | jint* = int32
50 | jsize* = jint
51 | jchar* = uint16
52 | jlong* = int64
53 | jshort* = int16
54 | jbyte* = int8
55 | jfloat* = cfloat
56 | jdouble* = cdouble
57 | jboolean* = uint8
58 |
59 | jobject_base {.inheritable, pure.} = object
60 | jobject* = ptr jobject_base
61 | JClass* = ptr object of jobject
62 | jmethodID* = pointer
63 | jfieldID* = pointer
64 | jstring* = ptr object of jobject
65 | jthrowable* = ptr object of jobject
66 |
67 | jarray* = ptr object of jobject
68 | jtypedArray*[T] = ptr object of jarray
69 |
70 | jobjectArray* = jtypedArray[jobject]
71 | jbooleanArray* = jtypedArray[jboolean]
72 | jbyteArray* = jtypedArray[jbyte]
73 | jcharArray* = jtypedArray[jchar]
74 | jshortArray* = jtypedArray[jshort]
75 | jintArray* = jtypedArray[jint]
76 | jlongArray* = jtypedArray[jlong]
77 | jfloatArray* = jtypedArray[jfloat]
78 | jdoubleArray* = jtypedArray[jdouble]
79 | jweak* = jobject
80 |
81 | jvalue* {.union.} = object
82 | z*: jboolean
83 | b*: jbyte
84 | c*: jchar
85 | s*: jshort
86 | i*: jint
87 | j*: jlong
88 | f*: jfloat
89 | d*: jdouble
90 | l*: jobject
91 |
92 | const JVM_TRUE* = 1.jboolean
93 | const JVM_FALSE* = 0.jboolean
94 |
95 | type
96 | JNIInvokeInterface* = object
97 | # WARNING: The fields should be defined in exact same order as they are
98 | # defined in jni.h to preserve ABI compatibility.
99 | reserved0: pointer
100 | reserved1: pointer
101 | reserved2: pointer
102 | when defined(not_TARGET_RT_MAC_CFM_and_ppc): # No idea what this means.
103 | cfm_vectors: array[4, pointer]
104 |
105 | DestroyJavaVM*: proc(vm: JavaVMPtr): jint {.cdecl.}
106 | AttachCurrentThread*: proc(vm: JavaVMPtr, penv: ptr pointer, args: pointer): jint {.cdecl.}
107 | DetachCurrentThread*: proc(vm: JavaVMPtr): jint {.cdecl.}
108 | GetEnv*: proc(vm: JavaVMPtr, penv: ptr pointer, version: jint): jint {.cdecl.}
109 | AttachCurrentThreadAsDaemon*: proc(vm: JavaVMPtr, penv: ptr pointer, args: pointer): jint {.cdecl.}
110 |
111 | JavaVM* = ptr JNIInvokeInterface
112 | JavaVMPtr* = ptr JavaVM
113 | JavaVMOption* = object
114 | # WARNING: The fields should be defined in exact same order as they are
115 | # defined in jni.h to preserve ABI compatibility.
116 | optionString*: cstring
117 | extraInfo*: pointer
118 |
119 | JavaVMInitArgs* = object
120 | # WARNING: The fields should be defined in exact same order as they are
121 | # defined in jni.h to preserve ABI compatibility.
122 | version*: jint
123 | nOptions*: jint
124 | options*: ptr JavaVMOption
125 | ignoreUnrecognized*: jboolean
126 |
127 | JNINativeInterface* = object
128 | # WARNING: The fields should be defined in exact same order as they are
129 | # defined in jni.h to preserve ABI compatibility.
130 |
131 | reserved0: pointer
132 | reserved1: pointer
133 | reserved2: pointer
134 | reserved3: pointer
135 |
136 | when defined(not_TARGET_RT_MAC_CFM_and_ppc): # No idea what this means.
137 | cfm_vectors: array[225, pointer]
138 |
139 | GetVersion*: proc(env: JNIEnvPtr): jint {.cdecl.}
140 |
141 | DefineClass*: proc(env: JNIEnvPtr, name: cstring, loader: jobject, buf: ptr jbyte, len: jsize): JClass {.cdecl.}
142 | FindClass*: proc(env: JNIEnvPtr, name: cstring): JClass {.cdecl.}
143 |
144 | FromReflectedMethod*: proc(env: JNIEnvPtr, meth: jobject): jmethodID {.cdecl.}
145 | FromReflectedField*: proc(env: JNIEnvPtr, field: jobject): jfieldID {.cdecl.}
146 |
147 | ToReflectedMethod*: proc(env: JNIEnvPtr, cls: JClass, methodID: jmethodID, isStatic: jboolean): jobject {.cdecl.}
148 |
149 | GetSuperclass*: proc(env: JNIEnvPtr, sub: JClass): JClass {.cdecl.}
150 | IsAssignableFrom*: proc(env: JNIEnvPtr, sub, sup: JClass): jboolean {.cdecl.}
151 |
152 | ToReflectedField*: proc(env: JNIEnvPtr, cls: JClass, fieldID: jfieldID, isStatic: jboolean): jobject {.cdecl.}
153 |
154 | Throw*: proc(env: JNIEnvPtr, obj: jthrowable): jint {.cdecl.}
155 | ThrowNew*: proc(env: JNIEnvPtr, clazz: JClass, msg: cstring): jint {.cdecl.}
156 | ExceptionOccurred*: proc(env: JNIEnvPtr): jthrowable {.cdecl.}
157 | ExceptionDescribe*: proc(env: JNIEnvPtr) {.cdecl.}
158 | ExceptionClear*: proc(env: JNIEnvPtr) {.cdecl.}
159 | FatalError*: proc(env: JNIEnvPtr, msg: cstring) {.cdecl.}
160 |
161 | PushLocalFrame*: proc(env: JNIEnvPtr, capacity: jint): jint {.cdecl.}
162 | PopLocalFrame*: proc(env: JNIEnvPtr, res: jobject): jobject {.cdecl.}
163 |
164 | NewGlobalRef*: proc(env: JNIEnvPtr, obj: jobject): jobject {.cdecl.}
165 | DeleteGlobalRef*: proc(env: JNIEnvPtr, obj: jobject) {.cdecl.}
166 | DeleteLocalRef*: proc(env: JNIEnvPtr, obj: jobject) {.cdecl.}
167 | IsSameObject*: proc(env: JNIEnvPtr, obj1, obj2: jobject): jboolean {.cdecl.}
168 | NewLocalRef*: proc(env: JNIEnvPtr, obj: jobject): jobject {.cdecl.}
169 | EnsureLocalCapacity*: proc(env: JNIEnvPtr, capacity: jint): jint {.cdecl.}
170 |
171 | AllocObject*: proc(env: JNIEnvPtr, clazz: JClass): jobject {.cdecl.}
172 | NewObject*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID): jobject {.cdecl, varargs.}
173 | NewObjectV: pointer # This function utilizes va_list which is not needed in Nim
174 | NewObjectA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jobject {.cdecl.}
175 |
176 | GetObjectClass*: proc(env: JNIEnvPtr, obj: jobject): JClass {.cdecl.}
177 | IsInstanceOf*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass): jboolean {.cdecl.}
178 |
179 | GetMethodID*: proc(env: JNIEnvPtr, clazz: JClass, name, sig: cstring): jmethodID {.cdecl.}
180 |
181 | CallObjectMethod*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID): jobject {.cdecl, varargs.}
182 | CallObjectMethodV: pointer # This function utilizes va_list which is not needed in Nim
183 | CallObjectMethodA*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID, args: ptr jvalue): jobject {.cdecl.}
184 |
185 | CallBooleanMethod*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID): jboolean {.cdecl, varargs.}
186 | CallBooleanMethodV: pointer # This function utilizes va_list which is not needed in Nim
187 | CallBooleanMethodA*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID, args: ptr jvalue): jboolean {.cdecl.}
188 |
189 | CallByteMethod*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID): jbyte {.cdecl, varargs.}
190 | CallByteMethodV: pointer # This function utilizes va_list which is not needed in Nim
191 | CallByteMethodA*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID, args: ptr jvalue): jbyte {.cdecl.}
192 |
193 | CallCharMethod*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID): jchar {.cdecl, varargs.}
194 | CallCharMethodV: pointer # This function utilizes va_list which is not needed in Nim
195 | CallCharMethodA*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID, args: ptr jvalue): jchar {.cdecl.}
196 |
197 | CallShortMethod*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID): jshort {.cdecl, varargs.}
198 | CallShortMethodV: pointer # This function utilizes va_list which is not needed in Nim
199 | CallShortMethodA*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID, args: ptr jvalue): jshort {.cdecl.}
200 |
201 | CallIntMethod*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID): jint {.cdecl, varargs.}
202 | CallIntMethodV: pointer # This function utilizes va_list which is not needed in Nim
203 | CallIntMethodA*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID, args: ptr jvalue): jint {.cdecl.}
204 |
205 | CallLongMethod*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID): jlong {.cdecl, varargs.}
206 | CallLongMethodV: pointer # This function utilizes va_list which is not needed in Nim
207 | CallLongMethodA*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID, args: ptr jvalue): jlong {.cdecl.}
208 |
209 | CallFloatMethod*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID): jfloat {.cdecl, varargs.}
210 | CallFloatMethodV: pointer # This function utilizes va_list which is not needed in Nim
211 | CallFloatMethodA*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID, args: ptr jvalue): jfloat {.cdecl.}
212 |
213 | CallDoubleMethod*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID): jdouble {.cdecl, varargs.}
214 | CallDoubleMethodV: pointer # This function utilizes va_list which is not needed in Nim
215 | CallDoubleMethodA*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID, args: ptr jvalue): jdouble {.cdecl.}
216 |
217 | CallVoidMethod*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID) {.cdecl, varargs.}
218 | CallVoidMethodV: pointer # This function utilizes va_list which is not needed in Nim
219 | CallVoidMethodA*: proc(env: JNIEnvPtr, obj: jobject, methodID: jmethodID, args: ptr jvalue) {.cdecl.}
220 |
221 | CallNonvirtualObjectMethod*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID): jobject {.cdecl, varargs.}
222 | CallNonvirtualObjectMethodV: pointer # This function utilizes va_list which is not needed in Nim
223 | CallNonvirtualObjectMethodA*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jobject {.cdecl.}
224 |
225 | CallNonvirtualBooleanMethod*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID): jboolean {.cdecl, varargs.}
226 | CallNonvirtualBooleanMethodV: pointer # This function utilizes va_list which is not needed in Nim
227 | CallNonvirtualBooleanMethodA*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jboolean {.cdecl.}
228 |
229 | CallNonvirtualByteMethod*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID): jbyte {.cdecl, varargs.}
230 | CallNonvirtualByteMethodV: pointer # This function utilizes va_list which is not needed in Nim
231 | CallNonvirtualByteMethodA*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jbyte {.cdecl.}
232 |
233 | CallNonvirtualCharMethod*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID): jchar {.cdecl, varargs.}
234 | CallNonvirtualCharMethodV: pointer # This function utilizes va_list which is not needed in Nim
235 | CallNonvirtualCharMethodA*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jchar {.cdecl.}
236 |
237 | CallNonvirtualShortMethod*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID): jshort {.cdecl, varargs.}
238 | CallNonvirtualShortMethodV: pointer # This function utilizes va_list which is not needed in Nim
239 | CallNonvirtualShortMethodA*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jshort {.cdecl.}
240 |
241 | CallNonvirtualIntMethod*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID): jint {.cdecl, varargs.}
242 | CallNonvirtualIntMethodV: pointer # This function utilizes va_list which is not needed in Nim
243 | CallNonvirtualIntMethodA*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jint {.cdecl.}
244 |
245 | CallNonvirtualLongMethod*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID): jlong {.cdecl, varargs.}
246 | CallNonvirtualLongMethodV: pointer # This function utilizes va_list which is not needed in Nim
247 | CallNonvirtualLongMethodA*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jlong {.cdecl.}
248 |
249 | CallNonvirtualFloatMethod*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID): jfloat {.cdecl, varargs.}
250 | CallNonvirtualFloatMethodV: pointer # This function utilizes va_list which is not needed in Nim
251 | CallNonvirtualFloatMethodA*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jfloat {.cdecl.}
252 |
253 | CallNonvirtualDoubleMethod*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID): jdouble {.cdecl, varargs.}
254 | CallNonvirtualDoubleMethodV: pointer # This function utilizes va_list which is not needed in Nim
255 | CallNonvirtualDoubleMethodA*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jdouble {.cdecl.}
256 |
257 | CallNonvirtualVoidMethod*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID) {.cdecl, varargs.}
258 | CallNonvirtualVoidMethodV: pointer # This function utilizes va_list which is not needed in Nim
259 | CallNonvirtualVoidMethodA*: proc(env: JNIEnvPtr, obj: jobject, clazz: JClass, methodID: jmethodID, args: ptr jvalue) {.cdecl.}
260 |
261 | GetFieldID*: proc(env: JNIEnvPtr, cls: JClass, name, sig: cstring): jfieldID {.cdecl.}
262 |
263 | GetObjectField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID): jobject {.cdecl.}
264 | GetBooleanField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID): jboolean {.cdecl.}
265 | GetByteField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID): jbyte {.cdecl.}
266 | GetCharField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID): jchar {.cdecl.}
267 | GetShortField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID): jshort {.cdecl.}
268 | GetIntField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID): jint {.cdecl.}
269 | GetLongField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID): jlong {.cdecl.}
270 | GetFloatField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID): jfloat {.cdecl.}
271 | GetDoubleField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID): jdouble {.cdecl.}
272 |
273 | SetObjectField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID, val: jobject) {.cdecl.}
274 | SetBooleanField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID, val: jboolean) {.cdecl.}
275 | SetByteField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID, val: jbyte) {.cdecl.}
276 | SetCharField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID, val: jchar) {.cdecl.}
277 | SetShortField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID, val: jshort) {.cdecl.}
278 | SetIntField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID, val: jint) {.cdecl.}
279 | SetLongField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID, val: jlong) {.cdecl.}
280 | SetFloatField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID, val: jfloat) {.cdecl.}
281 | SetDoubleField*: proc(env: JNIEnvPtr, obj: jobject, fieldId: jfieldID, val: jdouble) {.cdecl.}
282 |
283 | GetStaticMethodID*: proc(env: JNIEnvPtr, cls: JClass, name, sig: cstring): jmethodID {.cdecl.}
284 |
285 | CallStaticObjectMethod*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID): jobject {.cdecl, varargs.}
286 | CallStaticObjectMethodV: pointer # This function utilizes va_list which is not needed in Nim
287 | CallStaticObjectMethodA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jobject {.cdecl.}
288 |
289 | CallStaticBooleanMethod*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID): jboolean {.cdecl, varargs.}
290 | CallStaticBooleanMethodV: pointer # This function utilizes va_list which is not needed in Nim
291 | CallStaticBooleanMethodA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jboolean {.cdecl.}
292 |
293 | CallStaticByteMethod*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID): jbyte {.cdecl, varargs.}
294 | CallStaticByteMethodV: pointer # This function utilizes va_list which is not needed in Nim
295 | CallStaticByteMethodA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jbyte {.cdecl.}
296 |
297 | CallStaticCharMethod*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID): jchar {.cdecl, varargs.}
298 | CallStaticCharMethodV: pointer # This function utilizes va_list which is not needed in Nim
299 | CallStaticCharMethodA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jchar {.cdecl.}
300 |
301 | CallStaticShortMethod*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID): jshort {.cdecl, varargs.}
302 | CallStaticShortMethodV: pointer # This function utilizes va_list which is not needed in Nim
303 | CallStaticShortMethodA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jshort {.cdecl.}
304 |
305 | CallStaticIntMethod*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID): jint {.cdecl, varargs.}
306 | CallStaticIntMethodV: pointer # This function utilizes va_list which is not needed in Nim
307 | CallStaticIntMethodA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jint {.cdecl.}
308 |
309 | CallStaticLongMethod*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID): jlong {.cdecl, varargs.}
310 | CallStaticLongMethodV: pointer # This function utilizes va_list which is not needed in Nim
311 | CallStaticLongMethodA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jlong {.cdecl.}
312 |
313 | CallStaticFloatMethod*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID): jfloat {.cdecl, varargs.}
314 | CallStaticFloatMethodV: pointer # This function utilizes va_list which is not needed in Nim
315 | CallStaticFloatMethodA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jfloat {.cdecl.}
316 |
317 | CallStaticDoubleMethod*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID): jdouble {.cdecl, varargs.}
318 | CallStaticDoubleMethodV: pointer # This function utilizes va_list which is not needed in Nim
319 | CallStaticDoubleMethodA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue): jdouble {.cdecl.}
320 |
321 | CallStaticVoidMethod*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID) {.cdecl, varargs.}
322 | CallStaticVoidMethodV: pointer # This function utilizes va_list which is not needed in Nim
323 | CallStaticVoidMethodA*: proc(env: JNIEnvPtr, clazz: JClass, methodID: jmethodID, args: ptr jvalue) {.cdecl.}
324 |
325 | GetStaticFieldID*: proc(env: JNIEnvPtr, cls: JClass, name, sig: cstring): jfieldID {.cdecl.}
326 |
327 | GetStaticObjectField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID): jobject {.cdecl.}
328 | GetStaticBooleanField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID): jboolean {.cdecl.}
329 | GetStaticByteField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID): jbyte {.cdecl.}
330 | GetStaticCharField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID): jchar {.cdecl.}
331 | GetStaticShortField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID): jshort {.cdecl.}
332 | GetStaticIntField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID): jint {.cdecl.}
333 | GetStaticLongField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID): jlong {.cdecl.}
334 | GetStaticFloatField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID): jfloat {.cdecl.}
335 | GetStaticDoubleField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID): jdouble {.cdecl.}
336 |
337 | SetStaticObjectField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID, val: jobject) {.cdecl.}
338 | SetStaticBooleanField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID, val: jboolean) {.cdecl.}
339 | SetStaticByteField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID, val: jbyte) {.cdecl.}
340 | SetStaticCharField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID, val: jchar) {.cdecl.}
341 | SetStaticShortField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID, val: jshort) {.cdecl.}
342 | SetStaticIntField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID, val: jint) {.cdecl.}
343 | SetStaticLongField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID, val: jlong) {.cdecl.}
344 | SetStaticFloatField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID, val: jfloat) {.cdecl.}
345 | SetStaticDoubleField*: proc(env: JNIEnvPtr, obj: JClass, fieldId: jfieldID, val: jdouble) {.cdecl.}
346 |
347 | NewString*: proc(env: JNIEnvPtr, unicode: ptr jchar, len: jsize): jstring {.cdecl.}
348 | GetStringLength*: proc(env: JNIEnvPtr, str: jstring): jsize {.cdecl.}
349 | GetStringChars*: proc(env: JNIEnvPtr, str: jstring, isCopy: ptr jboolean): ptr jchar {.cdecl.}
350 | ReleaseStringChars*: proc(env: JNIEnvPtr, str: jstring, chars: ptr jchar) {.cdecl.}
351 |
352 | NewStringUTF*: proc(env: JNIEnvPtr, s: cstring): jstring {.cdecl.}
353 | GetStringUTFLength*: proc(env: JNIEnvPtr, str: jstring): jsize {.cdecl.}
354 | GetStringUTFChars*: proc(env: JNIEnvPtr, s: jstring, isCopy: ptr jboolean): cstring {.cdecl.}
355 | ReleaseStringUTFChars*: proc(env: JNIEnvPtr, s: jstring, cstr: cstring) {.cdecl.}
356 |
357 | GetArrayLength*: proc(env: JNIEnvPtr, arr: jarray): jsize {.cdecl.}
358 |
359 | NewObjectArray*: proc(env: JNIEnvPtr, size: jsize, clazz: JClass, init: jobject): jobjectArray {.cdecl.}
360 | GetObjectArrayElement*: proc(env: JNIEnvPtr, arr: jobjectArray, index: jsize): jobject {.cdecl.}
361 | SetObjectArrayElement*: proc(env: JNIEnvPtr, arr: jobjectArray, index: jsize, val: jobject) {.cdecl.}
362 |
363 | NewBooleanArray*: proc(env: JNIEnvPtr, len: jsize): jbooleanArray {.cdecl.}
364 | NewByteArray*: proc(env: JNIEnvPtr, len: jsize): jbyteArray {.cdecl.}
365 | NewCharArray*: proc(env: JNIEnvPtr, len: jsize): jcharArray {.cdecl.}
366 | NewShortArray*: proc(env: JNIEnvPtr, len: jsize): jshortArray {.cdecl.}
367 | NewIntArray*: proc(env: JNIEnvPtr, len: jsize): jintArray {.cdecl.}
368 | NewLongArray*: proc(env: JNIEnvPtr, len: jsize): jlongArray {.cdecl.}
369 | NewFloatArray*: proc(env: JNIEnvPtr, len: jsize): jfloatArray {.cdecl.}
370 | NewDoubleArray*: proc(env: JNIEnvPtr, len: jsize): jdoubleArray {.cdecl.}
371 |
372 | GetBooleanArrayElements*: proc(env: JNIEnvPtr, arr: jbooleanArray, isCopy: ptr jboolean): ptr jboolean {.cdecl.}
373 | GetByteArrayElements*: proc(env: JNIEnvPtr, arr: jbyteArray, isCopy: ptr jboolean): ptr jbyte {.cdecl.}
374 | GetCharArrayElements*: proc(env: JNIEnvPtr, arr: jcharArray, isCopy: ptr jboolean): ptr jchar {.cdecl.}
375 | GetShortArrayElements*: proc(env: JNIEnvPtr, arr: jshortArray, isCopy: ptr jboolean): ptr jshort {.cdecl.}
376 | GetIntArrayElements*: proc(env: JNIEnvPtr, arr: jintArray, isCopy: ptr jboolean): ptr jint {.cdecl.}
377 | GetLongArrayElements*: proc(env: JNIEnvPtr, arr: jlongArray, isCopy: ptr jboolean): ptr jlong {.cdecl.}
378 | GetFloatArrayElements*: proc(env: JNIEnvPtr, arr: jfloatArray, isCopy: ptr jboolean): ptr jfloat {.cdecl.}
379 | GetDoubleArrayElements*: proc(env: JNIEnvPtr, arr: jdoubleArray, isCopy: ptr jboolean): ptr jdouble {.cdecl.}
380 |
381 | ReleaseBooleanArrayElements*: proc(env: JNIEnvPtr, arr: jbooleanArray, elems: ptr jboolean, mode: jint) {.cdecl.}
382 | ReleaseByteArrayElements*: proc(env: JNIEnvPtr, arr: jbyteArray, elems: ptr jbyte, mode: jint) {.cdecl.}
383 | ReleaseCharArrayElements*: proc(env: JNIEnvPtr, arr: jcharArray, elems: ptr jchar, mode: jint) {.cdecl.}
384 | ReleaseShortArrayElements*: proc(env: JNIEnvPtr, arr: jshortArray, elems: ptr jshort, mode: jint) {.cdecl.}
385 | ReleaseIntArrayElements*: proc(env: JNIEnvPtr, arr: jintArray, elems: ptr jint, mode: jint) {.cdecl.}
386 | ReleaseLongArrayElements*: proc(env: JNIEnvPtr, arr: jlongArray, elems: ptr jlong, mode: jint) {.cdecl.}
387 | ReleaseFloatArrayElements*: proc(env: JNIEnvPtr, arr: jfloatArray, elems: ptr jfloat, mode: jint) {.cdecl.}
388 | ReleaseDoubleArrayElements*: proc(env: JNIEnvPtr, arr: jdoubleArray, elems: ptr jdouble, mode: jint) {.cdecl.}
389 |
390 | GetBooleanArrayRegion*: proc(env: JNIEnvPtr, arr: jbooleanArray, start, len: jsize, buf: ptr jboolean) {.cdecl.}
391 | GetByteArrayRegion*: proc(env: JNIEnvPtr, arr: jbyteArray, start, len: jsize, buf: ptr jbyte) {.cdecl.}
392 | GetCharArrayRegion*: proc(env: JNIEnvPtr, arr: jcharArray, start, len: jsize, buf: ptr jchar) {.cdecl.}
393 | GetShortArrayRegion*: proc(env: JNIEnvPtr, arr: jshortArray, start, len: jsize, buf: ptr jshort) {.cdecl.}
394 | GetIntArrayRegion*: proc(env: JNIEnvPtr, arr: jintArray, start, len: jsize, buf: ptr jint) {.cdecl.}
395 | GetLongArrayRegion*: proc(env: JNIEnvPtr, arr: jlongArray, start, len: jsize, buf: ptr jlong) {.cdecl.}
396 | GetFloatArrayRegion*: proc(env: JNIEnvPtr, arr: jfloatArray, start, len: jsize, buf: ptr jfloat) {.cdecl.}
397 | GetDoubleArrayRegion*: proc(env: JNIEnvPtr, arr: jdoubleArray, start, len: jsize, buf: ptr jdouble) {.cdecl.}
398 |
399 | SetBooleanArrayRegion*: proc(env: JNIEnvPtr, arr: jbooleanArray, start, len: jsize, buf: ptr jboolean) {.cdecl.}
400 | SetByteArrayRegion*: proc(env: JNIEnvPtr, arr: jbyteArray, start, len: jsize, buf: ptr jbyte) {.cdecl.}
401 | SetCharArrayRegion*: proc(env: JNIEnvPtr, arr: jcharArray, start, len: jsize, buf: ptr jchar) {.cdecl.}
402 | SetShortArrayRegion*: proc(env: JNIEnvPtr, arr: jshortArray, start, len: jsize, buf: ptr jshort) {.cdecl.}
403 | SetIntArrayRegion*: proc(env: JNIEnvPtr, arr: jintArray, start, len: jsize, buf: ptr jint) {.cdecl.}
404 | SetLongArrayRegion*: proc(env: JNIEnvPtr, arr: jlongArray, start, len: jsize, buf: ptr jlong) {.cdecl.}
405 | SetFloatArrayRegion*: proc(env: JNIEnvPtr, arr: jfloatArray, start, len: jsize, buf: ptr jfloat) {.cdecl.}
406 | SetDoubleArrayRegion*: proc(env: JNIEnvPtr, arr: jdoubleArray, start, len: jsize, buf: ptr jdouble) {.cdecl.}
407 |
408 | RegisterNatives*: proc(env: JNIEnvPtr, clazz: JClass, methods: ptr JNINativeMethod, nMethods: jint): jint {.cdecl.}
409 | UnregisterNatives*: proc(env: JNIEnvPtr, clazz: JClass): jint {.cdecl.}
410 |
411 | MonitorEnter*: proc(env: JNIEnvPtr, obj: jobject): jint {.cdecl.}
412 | MonitorExit*: proc(env: JNIEnvPtr, obj: jobject): jint {.cdecl.}
413 |
414 | GetJavaVM*: proc(env: JNIEnvPtr, vm: ptr JavaVMPtr): jint {.cdecl.}
415 |
416 | GetStringRegion*: proc(env: JNIEnvPtr, str: jstring, start, len: jsize, buf: ptr jchar) {.cdecl.}
417 | GetStringUTFRegion*: proc(env: JNIEnvPtr, str: jstring, start, len: jsize, buf: ptr char) {.cdecl.}
418 |
419 | GetPrimitiveArrayCritical*: proc(env: JNIEnvPtr, arr: jarray, isCopy: ptr jboolean): pointer {.cdecl.}
420 | ReleasePrimitiveArrayCritical*: proc(env: JNIEnvPtr, arr: jarray, carray: jarray, mode: jint) {.cdecl.}
421 |
422 | GetStringCritical*: proc(env: JNIEnvPtr, str: jstring, isCopy: ptr jboolean): ptr jchar {.cdecl.}
423 | ReleaseStringCritical*: proc(env: JNIEnvPtr, str: jstring, cstr: ptr jchar) {.cdecl.}
424 |
425 | NewWeakGlobalRef*: proc(env: JNIEnvPtr, obj: jobject): jweak {.cdecl.}
426 | DeleteWeakGlobalRef*: proc(env: JNIEnvPtr, r: jweak) {.cdecl.}
427 |
428 | ExceptionCheck*: proc(env: JNIEnvPtr): jboolean {.cdecl.}
429 |
430 | NewDirectByteBuffer*: proc(env: JNIEnvPtr, address: pointer, capacity: jlong): jobject {.cdecl.}
431 | GetDirectBufferAddress*: proc(env: JNIEnvPtr, buf: jobject): pointer {.cdecl.}
432 | GetDirectBufferCapacity*: proc(env: JNIEnvPtr, buf: jobject): jlong {.cdecl.}
433 |
434 | # New JNI 1.6 Features
435 |
436 | GetObjectRefType*: proc(env: JNIEnvPtr, obj: jobject): jobjectRefType {.cdecl.}
437 |
438 | JNIEnv* = ptr JNINativeInterface
439 | JNIEnvPtr* = ptr JNIEnv
440 |
441 | JNINativeMethod* = object
442 | name*: cstring
443 | signature*: cstring
444 | fnPtr*: pointer
445 |
446 | jobjectRefType* {.size: sizeof(cint).} = enum
447 | JNIInvalidRefType
448 | JNILocalRefType
449 | JNIGlobalRefType
450 | JNIWeakGlobalRefType
451 |
452 | const
453 | JNI_VERSION_1_1* = 0x00010001.jint
454 | JNI_VERSION_1_2* = 0x00010002.jint
455 | JNI_VERSION_1_4* = 0x00010004.jint
456 | JNI_VERSION_1_6* = 0x00010006.jint
457 | JNI_VERSION_1_8* = 0x00010008.jint
458 |
459 | const
460 | JNI_OK* = 0.jint
461 | JNI_ERR* = jint(-1)
462 | JNI_EDETACHED* = jint(-2)
463 | JNI_EVERSION* = jint(-3)
464 | JNI_ENOMEM* = jint(-4)
465 | JNI_EEXIST* = jint(-5)
466 | JNI_EINVAL* = jint(-6)
467 |
468 | var JNI_CreateJavaVM*: proc (pvm: ptr JavaVMPtr, penv: ptr pointer, args: pointer): jint {.cdecl, gcsafe.}
469 | var JNI_GetDefaultJavaVMInitArgs*: proc(vm_args: ptr JavaVMInitArgs): jint {.cdecl, gcsafe.}
470 | var JNI_GetCreatedJavaVMs*: proc(vmBuf: ptr JavaVMPtr, bufLen: jsize, nVMs: ptr jsize): jint {.cdecl, gcsafe.}
471 |
472 | proc isJVMLoaded*: bool {.gcsafe.} =
473 | not JNI_CreateJavaVM.isNil and not JNI_GetDefaultJavaVMInitArgs.isNil and not JNI_GetCreatedJavaVMs.isNil
474 |
475 | # proc linkWithJVMLib* =
476 | # when defined(macosx):
477 | # let libPath {.hint[XDeclaredButNotUsed]: off.} = CT_JVM.root.parentDir.parentDir.cstring
478 | # {.emit: """
479 | # CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)`libPath`, strlen(`libPath`), true);
480 | # if (url)
481 | # {
482 | # CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, url);
483 | # CFRelease(url);
484 |
485 | # if (bundle)
486 | # {
487 | # `JNI_CreateJavaVM` = CFBundleGetFunctionPointerForName(bundle, CFSTR("JNI_CreateJavaVM"));
488 | # `JNI_GetDefaultJavaVMInitArgs` = CFBundleGetFunctionPointerForName(bundle, CFSTR("JNI_GetDefaultJavaVMInitArgs"));
489 | # `JNI_GetCreatedJavaVMs` = CFBundleGetFunctionPointerForName(bundle, CFSTR("JNI_GetCreatedJavaVMs"));
490 | # }
491 | # }
492 | # """.}
493 | # else:
494 | # proc linkWithJVMModule(handle: LibHandle) =
495 | # JNI_CreateJavaVM = cast[type(JNI_CreateJavaVM)](symAddr(handle, "JNI_CreateJavaVM"))
496 | # JNI_GetDefaultJavaVMInitArgs = cast[type(JNI_GetDefaultJavaVMInitArgs)](symAddr(handle, "JNI_GetDefaultJavaVMInitArgs"))
497 | # JNI_GetCreatedJavaVMs = cast[type(JNI_GetCreatedJavaVMs)](symAddr(handle, "JNI_GetCreatedJavaVMs"))
498 |
499 | # # First we try to find the JNI functions in the current process. We may already be linked with those.
500 | # var handle = loadLib()
501 | # if not handle.isNil:
502 | # linkWithJVMModule(handle)
503 |
504 | # # Then try locating JVM dynamically
505 | # if not isJVMLoaded():
506 | # if not handle.isNil:
507 | # unloadLib(handle)
508 | # let foundJVM = findJVM()
509 | # if foundJVM.isSome:
510 | # handle = loadLib(foundJVM.get.lib)
511 | # linkWithJVMModule(handle)
512 |
513 | # # If everything fails - try JVM we compiled with
514 | # if not isJVMLoaded():
515 | # if not handle.isNil:
516 | # unloadLib(handle)
517 | # handle = loadLib(CT_JVM.lib)
518 | # linkWithJVMModule(handle)
519 |
520 | # if not isJVMLoaded():
521 | # raise newException(Exception, "JVM could not be loaded")
522 |
523 | proc fqcn*(cls: string): string =
524 | ## Create fullqualified class name
525 | cls.replace(".", "/")
526 |
527 | proc sigForClass*(cls: string): string =
528 | ## Create method/field signature part for class name
529 | "L" & fqcn(cls) & ";"
530 |
531 | proc toJValue*(v: cfloat): jvalue = result.f = v
532 | proc toJValue*(v: jdouble): jvalue = result.d = v
533 | proc toJValue*(v: jint): jvalue = result.i = v
534 | proc toJValue*(v: jlong): jvalue = result.j = v
535 | proc toJValue*(v: jboolean): jvalue = result.z = v
536 | proc toJValue*(v: bool): jvalue = result.z = if v: JVM_TRUE else: JVM_FALSE
537 | proc toJValue*(v: jbyte): jvalue = result.b = v
538 | proc toJValue*(v: jchar): jvalue = result.c = v
539 | proc toJValue*(v: jshort): jvalue = result.s = v
540 | proc toJValue*(v: jobject): jvalue = result.l = v
541 |
542 | template fromJValue*(T: typedesc, v: jvalue): auto =
543 | when T is jboolean: v.z
544 | elif T is bool: (if v.z == JVM_TRUE: true else: false)
545 | elif T is jbyte: v.b
546 | elif T is jchar: v.c
547 | elif T is jshort: v.s
548 | elif T is jint: v.i
549 | elif T is jlong: v.j
550 | elif T is jfloat: v.f
551 | elif T is jdouble: v.d
552 | elif T is jobject: v.l
553 | else:
554 | {.error: "wrong type".}
555 |
556 | template jniSig*(t: typedesc[jlong]): string = "J"
557 | template jniSig*(t: typedesc[jint]): string = "I"
558 | template jniSig*(t: typedesc[jboolean]): string = "Z"
559 | template jniSig*(t: typedesc[bool]): string = "Z"
560 | template jniSig*(t: typedesc[jbyte]): string = "B"
561 | template jniSig*(t: typedesc[jchar]): string = "C"
562 | template jniSig*(t: typedesc[jshort]): string = "S"
563 | template jniSig*(t: typedesc[jfloat]): string = "F"
564 | template jniSig*(t: typedesc[jdouble]): string = "D"
565 | template jniSig*(t: typedesc[string]): string = sigForClass"java.lang.String"
566 | template jniSig*(t: typedesc[jobject]): string = sigForClass"java.lang.Object"
567 | template jniSig*(t: typedesc[void]): string = "V"
568 | proc elementTypeOfOpenArrayType[OpenArrayType](dummy: OpenArrayType = @[]): auto = dummy[0]
569 | template jniSig*(t: typedesc[openarray]): string = "[" & jniSig(type(elementTypeOfOpenArrayType[t]()))
570 |
571 | type
572 | JVMArrayType* = jobjectArray |
573 | jcharArray |
574 | jbyteArray |
575 | jshortArray |
576 | jintArray |
577 | jlongArray |
578 | jfloatArray |
579 | jdoubleArray |
580 | jbooleanArray
581 | JVMValueType* = jobject |
582 | jchar |
583 | jbyte |
584 | jshort |
585 | jint |
586 | jlong |
587 | jfloat |
588 | jdouble |
589 | jboolean
590 |
591 | # The following templates are redundand because jarray types are generic.
592 | template valueType*(T: typedesc): typedesc {.deprecated.} =
593 | when T is jobjectArray:
594 | jobject
595 | elif T is jcharArray:
596 | jchar
597 | elif T is jbyteArray:
598 | jbyte
599 | elif T is jshortArray:
600 | jshort
601 | elif T is jintArray:
602 | jint
603 | elif T is jlongArray:
604 | jlong
605 | elif T is jfloatArray:
606 | jfloat
607 | elif T is jdoubleArray:
608 | jdouble
609 | elif T is jbooleanArray:
610 | jboolean
611 | else:
612 | {.error: "Can't use type " & astToStr(T) & " with java's arrays".}
613 | discard
614 |
615 | template arrayType*(T: typedesc): typedesc {.deprecated.} =
616 | when T is jobject:
617 | jobjectArray
618 | elif T is jchar:
619 | jcharArray
620 | elif T is jbyte:
621 | jbyteArray
622 | elif T is jshort:
623 | jshortArray
624 | elif T is jint:
625 | jintArray
626 | elif T is jlong:
627 | jlongArray
628 | elif T is jfloat:
629 | jfloatArray
630 | elif T is jdouble:
631 | jdoubleArray
632 | elif T is jboolean:
633 | jbooleanArray
634 | else:
635 | {.error: "Can't use type " & astToStr(T) & " with java's arrays".}
636 | discard
637 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------