iris-decompiler 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,174 @@
1
+ export declare enum Op {
2
+ NOP = 0,
3
+ BREAK = 1,
4
+ LOADNIL = 2,
5
+ LOADB = 3,
6
+ LOADN = 4,
7
+ LOADK = 5,
8
+ MOVE = 6,
9
+ GETGLOBAL = 7,
10
+ SETGLOBAL = 8,
11
+ GETUPVAL = 9,
12
+ SETUPVAL = 10,
13
+ CLOSEUPVALS = 11,
14
+ GETIMPORT = 12,
15
+ GETTABLE = 13,
16
+ SETTABLE = 14,
17
+ GETTABLEKS = 15,
18
+ SETTABLEKS = 16,
19
+ GETTABLEN = 17,
20
+ SETTABLEN = 18,
21
+ NEWCLOSURE = 19,
22
+ NAMECALL = 20,
23
+ CALL = 21,
24
+ RETURN = 22,
25
+ JUMP = 23,
26
+ JUMPBACK = 24,
27
+ JUMPIF = 25,
28
+ JUMPIFNOT = 26,
29
+ JUMPIFEQ = 27,
30
+ JUMPIFLE = 28,
31
+ JUMPIFLT = 29,
32
+ JUMPIFNOTEQ = 30,
33
+ JUMPIFNOTLE = 31,
34
+ JUMPIFNOTLT = 32,
35
+ ADD = 33,
36
+ SUB = 34,
37
+ MUL = 35,
38
+ DIV = 36,
39
+ MOD = 37,
40
+ POW = 38,
41
+ ADDK = 39,
42
+ SUBK = 40,
43
+ MULK = 41,
44
+ DIVK = 42,
45
+ MODK = 43,
46
+ POWK = 44,
47
+ AND = 45,
48
+ OR = 46,
49
+ ANDK = 47,
50
+ ORK = 48,
51
+ CONCAT = 49,
52
+ NOT = 50,
53
+ MINUS = 51,
54
+ LENGTH = 52,
55
+ NEWTABLE = 53,
56
+ DUPTABLE = 54,
57
+ SETLIST = 55,
58
+ FORNPREP = 56,
59
+ FORNLOOP = 57,
60
+ FORGLOOP = 58,
61
+ FORGPREP_INEXT = 59,
62
+ FASTCALL3 = 60,
63
+ FORGPREP_NEXT = 61,
64
+ NATIVECALL = 62,
65
+ GETVARARGS = 63,
66
+ DUPCLOSURE = 64,
67
+ PREPVARARGS = 65,
68
+ LOADKX = 66,
69
+ JUMPX = 67,
70
+ FASTCALL = 68,
71
+ COVERAGE = 69,
72
+ CAPTURE = 70,
73
+ SUBRK = 71,
74
+ DIVRK = 72,
75
+ FASTCALL1 = 73,
76
+ FASTCALL2 = 74,
77
+ FASTCALL2K = 75,
78
+ FORGPREP = 76,
79
+ JUMPXEQKNIL = 77,
80
+ JUMPXEQKB = 78,
81
+ JUMPXEQKN = 79,
82
+ JUMPXEQKS = 80,
83
+ IDIV = 81,
84
+ IDIVK = 82,
85
+ GETUDATAKS = 83,
86
+ SETUDATAKS = 84,
87
+ NAMECALLUDATA = 85,
88
+ NEWCLASSMEMBER = 86,
89
+ CALLFB = 87,
90
+ CMPPROTO = 88,
91
+ NEWCLASS = 89
92
+ }
93
+ export declare enum ConstKind {
94
+ Nil = 0,
95
+ Boolean = 1,
96
+ Number = 2,
97
+ String = 3,
98
+ Import = 4,
99
+ Table = 5,
100
+ Closure = 6,
101
+ Vector = 7,
102
+ TableWithConstants = 8,
103
+ Sentinel = 9,
104
+ Integer = 10,
105
+ ClassShape = 11,
106
+ VectorD = 12
107
+ }
108
+ export interface TableEntry {
109
+ key: number;
110
+ value: number;
111
+ }
112
+ export interface Constant {
113
+ kind: ConstKind;
114
+ boolean: boolean;
115
+ number: number;
116
+ importId: number;
117
+ str: string;
118
+ closureProto: number;
119
+ vec: number[];
120
+ table: TableEntry[];
121
+ integer: number;
122
+ }
123
+ export declare function makeConstant(): Constant;
124
+ export interface LocVar {
125
+ name: string;
126
+ startpc: number;
127
+ endpc: number;
128
+ reg: number;
129
+ }
130
+ export interface Proto {
131
+ id: number;
132
+ maxstack: number;
133
+ numparams: number;
134
+ nups: number;
135
+ is_vararg: number;
136
+ flags: number;
137
+ code: number[];
138
+ k: Constant[];
139
+ children: number[];
140
+ lineinfo: number[];
141
+ linedefined: number;
142
+ debugname: string;
143
+ source: string;
144
+ locvars: LocVar[];
145
+ upnames: string[];
146
+ }
147
+ export declare function makeProto(): Proto;
148
+ export interface Program {
149
+ strings: string[];
150
+ protos: Proto[];
151
+ main: number;
152
+ encodingKey: number;
153
+ }
154
+ export declare function makeProgram(): Program;
155
+ export declare function iA(insn: number): number;
156
+ export declare function iB(insn: number): number;
157
+ export declare function iC(insn: number): number;
158
+ export declare function iD(insn: number): number;
159
+ export declare function iE(insn: number): number;
160
+ export declare function iOp(insn: number): Op;
161
+ export declare function auxA(aux: number): number;
162
+ export declare function auxB(aux: number): number;
163
+ export declare function auxKV(aux: number): number;
164
+ export declare function auxKB(aux: number): number;
165
+ export declare function auxNOT(aux: number): boolean;
166
+ export declare const OP_NAMES: string[];
167
+ export declare function opName(op: Op): string;
168
+ export declare function opHasAux(op: Op): boolean;
169
+ export declare function opLength(op: Op): number;
170
+ export declare function opIsJumpD(op: Op): boolean;
171
+ export declare function opIsLoopJump(op: Op): boolean;
172
+ export declare function opIsFallthrough(op: Op): boolean;
173
+ export declare function opIsFastCall(op: Op): boolean;
174
+ export declare function jumpTarget(code: number[], pc: number): number;
package/dist/types.js ADDED
@@ -0,0 +1,299 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OP_NAMES = exports.ConstKind = exports.Op = void 0;
4
+ exports.makeConstant = makeConstant;
5
+ exports.makeProto = makeProto;
6
+ exports.makeProgram = makeProgram;
7
+ exports.iA = iA;
8
+ exports.iB = iB;
9
+ exports.iC = iC;
10
+ exports.iD = iD;
11
+ exports.iE = iE;
12
+ exports.iOp = iOp;
13
+ exports.auxA = auxA;
14
+ exports.auxB = auxB;
15
+ exports.auxKV = auxKV;
16
+ exports.auxKB = auxKB;
17
+ exports.auxNOT = auxNOT;
18
+ exports.opName = opName;
19
+ exports.opHasAux = opHasAux;
20
+ exports.opLength = opLength;
21
+ exports.opIsJumpD = opIsJumpD;
22
+ exports.opIsLoopJump = opIsLoopJump;
23
+ exports.opIsFallthrough = opIsFallthrough;
24
+ exports.opIsFastCall = opIsFastCall;
25
+ exports.jumpTarget = jumpTarget;
26
+ var Op;
27
+ (function (Op) {
28
+ Op[Op["NOP"] = 0] = "NOP";
29
+ Op[Op["BREAK"] = 1] = "BREAK";
30
+ Op[Op["LOADNIL"] = 2] = "LOADNIL";
31
+ Op[Op["LOADB"] = 3] = "LOADB";
32
+ Op[Op["LOADN"] = 4] = "LOADN";
33
+ Op[Op["LOADK"] = 5] = "LOADK";
34
+ Op[Op["MOVE"] = 6] = "MOVE";
35
+ Op[Op["GETGLOBAL"] = 7] = "GETGLOBAL";
36
+ Op[Op["SETGLOBAL"] = 8] = "SETGLOBAL";
37
+ Op[Op["GETUPVAL"] = 9] = "GETUPVAL";
38
+ Op[Op["SETUPVAL"] = 10] = "SETUPVAL";
39
+ Op[Op["CLOSEUPVALS"] = 11] = "CLOSEUPVALS";
40
+ Op[Op["GETIMPORT"] = 12] = "GETIMPORT";
41
+ Op[Op["GETTABLE"] = 13] = "GETTABLE";
42
+ Op[Op["SETTABLE"] = 14] = "SETTABLE";
43
+ Op[Op["GETTABLEKS"] = 15] = "GETTABLEKS";
44
+ Op[Op["SETTABLEKS"] = 16] = "SETTABLEKS";
45
+ Op[Op["GETTABLEN"] = 17] = "GETTABLEN";
46
+ Op[Op["SETTABLEN"] = 18] = "SETTABLEN";
47
+ Op[Op["NEWCLOSURE"] = 19] = "NEWCLOSURE";
48
+ Op[Op["NAMECALL"] = 20] = "NAMECALL";
49
+ Op[Op["CALL"] = 21] = "CALL";
50
+ Op[Op["RETURN"] = 22] = "RETURN";
51
+ Op[Op["JUMP"] = 23] = "JUMP";
52
+ Op[Op["JUMPBACK"] = 24] = "JUMPBACK";
53
+ Op[Op["JUMPIF"] = 25] = "JUMPIF";
54
+ Op[Op["JUMPIFNOT"] = 26] = "JUMPIFNOT";
55
+ Op[Op["JUMPIFEQ"] = 27] = "JUMPIFEQ";
56
+ Op[Op["JUMPIFLE"] = 28] = "JUMPIFLE";
57
+ Op[Op["JUMPIFLT"] = 29] = "JUMPIFLT";
58
+ Op[Op["JUMPIFNOTEQ"] = 30] = "JUMPIFNOTEQ";
59
+ Op[Op["JUMPIFNOTLE"] = 31] = "JUMPIFNOTLE";
60
+ Op[Op["JUMPIFNOTLT"] = 32] = "JUMPIFNOTLT";
61
+ Op[Op["ADD"] = 33] = "ADD";
62
+ Op[Op["SUB"] = 34] = "SUB";
63
+ Op[Op["MUL"] = 35] = "MUL";
64
+ Op[Op["DIV"] = 36] = "DIV";
65
+ Op[Op["MOD"] = 37] = "MOD";
66
+ Op[Op["POW"] = 38] = "POW";
67
+ Op[Op["ADDK"] = 39] = "ADDK";
68
+ Op[Op["SUBK"] = 40] = "SUBK";
69
+ Op[Op["MULK"] = 41] = "MULK";
70
+ Op[Op["DIVK"] = 42] = "DIVK";
71
+ Op[Op["MODK"] = 43] = "MODK";
72
+ Op[Op["POWK"] = 44] = "POWK";
73
+ Op[Op["AND"] = 45] = "AND";
74
+ Op[Op["OR"] = 46] = "OR";
75
+ Op[Op["ANDK"] = 47] = "ANDK";
76
+ Op[Op["ORK"] = 48] = "ORK";
77
+ Op[Op["CONCAT"] = 49] = "CONCAT";
78
+ Op[Op["NOT"] = 50] = "NOT";
79
+ Op[Op["MINUS"] = 51] = "MINUS";
80
+ Op[Op["LENGTH"] = 52] = "LENGTH";
81
+ Op[Op["NEWTABLE"] = 53] = "NEWTABLE";
82
+ Op[Op["DUPTABLE"] = 54] = "DUPTABLE";
83
+ Op[Op["SETLIST"] = 55] = "SETLIST";
84
+ Op[Op["FORNPREP"] = 56] = "FORNPREP";
85
+ Op[Op["FORNLOOP"] = 57] = "FORNLOOP";
86
+ Op[Op["FORGLOOP"] = 58] = "FORGLOOP";
87
+ Op[Op["FORGPREP_INEXT"] = 59] = "FORGPREP_INEXT";
88
+ Op[Op["FASTCALL3"] = 60] = "FASTCALL3";
89
+ Op[Op["FORGPREP_NEXT"] = 61] = "FORGPREP_NEXT";
90
+ Op[Op["NATIVECALL"] = 62] = "NATIVECALL";
91
+ Op[Op["GETVARARGS"] = 63] = "GETVARARGS";
92
+ Op[Op["DUPCLOSURE"] = 64] = "DUPCLOSURE";
93
+ Op[Op["PREPVARARGS"] = 65] = "PREPVARARGS";
94
+ Op[Op["LOADKX"] = 66] = "LOADKX";
95
+ Op[Op["JUMPX"] = 67] = "JUMPX";
96
+ Op[Op["FASTCALL"] = 68] = "FASTCALL";
97
+ Op[Op["COVERAGE"] = 69] = "COVERAGE";
98
+ Op[Op["CAPTURE"] = 70] = "CAPTURE";
99
+ Op[Op["SUBRK"] = 71] = "SUBRK";
100
+ Op[Op["DIVRK"] = 72] = "DIVRK";
101
+ Op[Op["FASTCALL1"] = 73] = "FASTCALL1";
102
+ Op[Op["FASTCALL2"] = 74] = "FASTCALL2";
103
+ Op[Op["FASTCALL2K"] = 75] = "FASTCALL2K";
104
+ Op[Op["FORGPREP"] = 76] = "FORGPREP";
105
+ Op[Op["JUMPXEQKNIL"] = 77] = "JUMPXEQKNIL";
106
+ Op[Op["JUMPXEQKB"] = 78] = "JUMPXEQKB";
107
+ Op[Op["JUMPXEQKN"] = 79] = "JUMPXEQKN";
108
+ Op[Op["JUMPXEQKS"] = 80] = "JUMPXEQKS";
109
+ Op[Op["IDIV"] = 81] = "IDIV";
110
+ Op[Op["IDIVK"] = 82] = "IDIVK";
111
+ Op[Op["GETUDATAKS"] = 83] = "GETUDATAKS";
112
+ Op[Op["SETUDATAKS"] = 84] = "SETUDATAKS";
113
+ Op[Op["NAMECALLUDATA"] = 85] = "NAMECALLUDATA";
114
+ Op[Op["NEWCLASSMEMBER"] = 86] = "NEWCLASSMEMBER";
115
+ Op[Op["CALLFB"] = 87] = "CALLFB";
116
+ Op[Op["CMPPROTO"] = 88] = "CMPPROTO";
117
+ Op[Op["NEWCLASS"] = 89] = "NEWCLASS";
118
+ })(Op || (exports.Op = Op = {}));
119
+ var ConstKind;
120
+ (function (ConstKind) {
121
+ ConstKind[ConstKind["Nil"] = 0] = "Nil";
122
+ ConstKind[ConstKind["Boolean"] = 1] = "Boolean";
123
+ ConstKind[ConstKind["Number"] = 2] = "Number";
124
+ ConstKind[ConstKind["String"] = 3] = "String";
125
+ ConstKind[ConstKind["Import"] = 4] = "Import";
126
+ ConstKind[ConstKind["Table"] = 5] = "Table";
127
+ ConstKind[ConstKind["Closure"] = 6] = "Closure";
128
+ ConstKind[ConstKind["Vector"] = 7] = "Vector";
129
+ ConstKind[ConstKind["TableWithConstants"] = 8] = "TableWithConstants";
130
+ ConstKind[ConstKind["Sentinel"] = 9] = "Sentinel";
131
+ ConstKind[ConstKind["Integer"] = 10] = "Integer";
132
+ ConstKind[ConstKind["ClassShape"] = 11] = "ClassShape";
133
+ ConstKind[ConstKind["VectorD"] = 12] = "VectorD";
134
+ })(ConstKind || (exports.ConstKind = ConstKind = {}));
135
+ function makeConstant() {
136
+ return {
137
+ kind: ConstKind.Nil,
138
+ boolean: false,
139
+ number: 0,
140
+ importId: 0,
141
+ str: "",
142
+ closureProto: -1,
143
+ vec: [],
144
+ table: [],
145
+ integer: 0,
146
+ };
147
+ }
148
+ function makeProto() {
149
+ return {
150
+ id: 0,
151
+ maxstack: 0,
152
+ numparams: 0,
153
+ nups: 0,
154
+ is_vararg: 0,
155
+ flags: 0,
156
+ code: [],
157
+ k: [],
158
+ children: [],
159
+ lineinfo: [],
160
+ linedefined: 0,
161
+ debugname: "",
162
+ source: "",
163
+ locvars: [],
164
+ upnames: [],
165
+ };
166
+ }
167
+ function makeProgram() {
168
+ return { strings: [], protos: [], main: 0, encodingKey: 1 };
169
+ }
170
+ // ---- Instruction field accessors ----
171
+ function iA(insn) { return (insn >> 8) & 0xff; }
172
+ function iB(insn) { return (insn >> 16) & 0xff; }
173
+ function iC(insn) { return (insn >> 24) & 0xff; }
174
+ function iD(insn) { return (insn | 0) >> 16; } // int32 sign-extend of bits 16..31 (Luau D field)
175
+ function iE(insn) { return (insn | 0) >> 8; } // int32 sign-extend of bits 8..31 (Luau E field)
176
+ function iOp(insn) { return (insn & 0xff); }
177
+ function auxA(aux) { return aux & 0xff; }
178
+ function auxB(aux) { return (aux >> 8) & 0xff; }
179
+ function auxKV(aux) { return aux & 0xffffff; }
180
+ function auxKB(aux) { return aux & 0x1; }
181
+ function auxNOT(aux) { return (aux >>> 31) !== 0; }
182
+ // ---- Opcode metadata ----
183
+ exports.OP_NAMES = [
184
+ "NOP", "BREAK", "LOADNIL", "LOADB", "LOADN", "LOADK", "MOVE", "GETGLOBAL", "SETGLOBAL",
185
+ "GETUPVAL", "SETUPVAL", "CLOSEUPVALS", "GETIMPORT", "GETTABLE", "SETTABLE", "GETTABLEKS",
186
+ "SETTABLEKS", "GETTABLEN", "SETTABLEN", "NEWCLOSURE", "NAMECALL", "CALL", "RETURN", "JUMP",
187
+ "JUMPBACK", "JUMPIF", "JUMPIFNOT", "JUMPIFEQ", "JUMPIFLE", "JUMPIFLT", "JUMPIFNOTEQ",
188
+ "JUMPIFNOTLE", "JUMPIFNOTLT", "ADD", "SUB", "MUL", "DIV", "MOD", "POW", "ADDK", "SUBK",
189
+ "MULK", "DIVK", "MODK", "POWK", "AND", "OR", "ANDK", "ORK", "CONCAT", "NOT", "MINUS",
190
+ "LENGTH", "NEWTABLE", "DUPTABLE", "SETLIST", "FORNPREP", "FORNLOOP", "FORGLOOP",
191
+ "FORGPREP_INEXT", "FASTCALL3", "FORGPREP_NEXT", "NATIVECALL", "GETVARARGS", "DUPCLOSURE",
192
+ "PREPVARARGS", "LOADKX", "JUMPX", "FASTCALL", "COVERAGE", "CAPTURE", "SUBRK", "DIVRK",
193
+ "FASTCALL1", "FASTCALL2", "FASTCALL2K", "FORGPREP", "JUMPXEQKNIL", "JUMPXEQKB", "JUMPXEQKN",
194
+ "JUMPXEQKS", "IDIV", "IDIVK", "GETUDATAKS", "SETUDATAKS", "NAMECALLUDATA", "NEWCLASSMEMBER",
195
+ "CALLFB", "CMPPROTO", "NEWCLASS",
196
+ ];
197
+ function opName(op) {
198
+ const i = Number(op);
199
+ return i >= 0 && i < exports.OP_NAMES.length ? exports.OP_NAMES[i] : "???";
200
+ }
201
+ function opHasAux(op) {
202
+ switch (op) {
203
+ case Op.GETGLOBAL:
204
+ case Op.SETGLOBAL:
205
+ case Op.GETIMPORT:
206
+ case Op.GETTABLEKS:
207
+ case Op.SETTABLEKS:
208
+ case Op.NAMECALL:
209
+ case Op.JUMPIFEQ:
210
+ case Op.JUMPIFLE:
211
+ case Op.JUMPIFLT:
212
+ case Op.JUMPIFNOTEQ:
213
+ case Op.JUMPIFNOTLE:
214
+ case Op.JUMPIFNOTLT:
215
+ case Op.NEWTABLE:
216
+ case Op.SETLIST:
217
+ case Op.FORGLOOP:
218
+ case Op.LOADKX:
219
+ case Op.FASTCALL2:
220
+ case Op.FASTCALL2K:
221
+ case Op.FASTCALL3:
222
+ case Op.JUMPXEQKNIL:
223
+ case Op.JUMPXEQKB:
224
+ case Op.JUMPXEQKN:
225
+ case Op.JUMPXEQKS:
226
+ case Op.GETUDATAKS:
227
+ case Op.SETUDATAKS:
228
+ case Op.NAMECALLUDATA:
229
+ case Op.NEWCLASSMEMBER:
230
+ case Op.CALLFB:
231
+ case Op.CMPPROTO:
232
+ case Op.NEWCLASS:
233
+ return true;
234
+ default:
235
+ return false;
236
+ }
237
+ }
238
+ function opLength(op) {
239
+ return opHasAux(op) ? 2 : 1;
240
+ }
241
+ function opIsJumpD(op) {
242
+ switch (op) {
243
+ case Op.JUMP:
244
+ case Op.JUMPIF:
245
+ case Op.JUMPIFNOT:
246
+ case Op.JUMPIFEQ:
247
+ case Op.JUMPIFLE:
248
+ case Op.JUMPIFLT:
249
+ case Op.JUMPIFNOTEQ:
250
+ case Op.JUMPIFNOTLE:
251
+ case Op.JUMPIFNOTLT:
252
+ case Op.FORNPREP:
253
+ case Op.FORNLOOP:
254
+ case Op.FORGPREP:
255
+ case Op.FORGLOOP:
256
+ case Op.FORGPREP_INEXT:
257
+ case Op.FORGPREP_NEXT:
258
+ case Op.JUMPBACK:
259
+ case Op.JUMPXEQKNIL:
260
+ case Op.JUMPXEQKB:
261
+ case Op.JUMPXEQKN:
262
+ case Op.JUMPXEQKS:
263
+ case Op.CMPPROTO:
264
+ return true;
265
+ default:
266
+ return false;
267
+ }
268
+ }
269
+ function opIsLoopJump(op) {
270
+ return op === Op.JUMPBACK || op === Op.FORGLOOP || op === Op.FORNLOOP;
271
+ }
272
+ function opIsFallthrough(op) {
273
+ switch (op) {
274
+ case Op.RETURN:
275
+ case Op.JUMP:
276
+ case Op.JUMPBACK:
277
+ case Op.JUMPX:
278
+ return false;
279
+ default:
280
+ return true;
281
+ }
282
+ }
283
+ function opIsFastCall(op) {
284
+ return op === Op.FASTCALL || op === Op.FASTCALL1 || op === Op.FASTCALL2 ||
285
+ op === Op.FASTCALL2K || op === Op.FASTCALL3;
286
+ }
287
+ function jumpTarget(code, pc) {
288
+ const insn = code[pc];
289
+ const op = iOp(insn);
290
+ if (opIsJumpD(op))
291
+ return pc + iD(insn) + 1;
292
+ if (opIsFastCall(op))
293
+ return pc + iC(insn) + 2;
294
+ if (op === Op.JUMPX)
295
+ return pc + iE(insn) + 1;
296
+ if (op === Op.LOADB && iC(insn))
297
+ return pc + iC(insn) + 1;
298
+ return -1;
299
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "iris-decompiler",
3
+ "version": "1.0.0",
4
+ "description": "decompiles bytecode into readable luau",
5
+ "license": "MIT",
6
+ "type": "commonjs",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "bin": {
10
+ "iris-decompiler": "bin/cli.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "bin"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.json",
18
+ "typecheck": "tsc -p tsconfig.json --noEmit",
19
+ "cli": "node bin/cli.js"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.11.0",
23
+ "typescript": "^5.4.0"
24
+ }
25
+ }