bmweb-cli 0.1.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.
- package/LICENSE +674 -0
- package/README.md +351 -0
- package/dist/bmweb.js +2790 -0
- package/package.json +53 -0
- package/runtime/core/bestvm/codec.js +285 -0
- package/runtime/core/bestvm/environment.js +116 -0
- package/runtime/core/bestvm/executor.js +1483 -0
- package/runtime/core/bestvm/index.js +52 -0
- package/runtime/core/bestvm/machine.js +491 -0
- package/runtime/core/bestvm/operands.js +356 -0
- package/runtime/core/bestvm/registers.js +152 -0
- package/runtime/core/bestvm/write-guard.js +111 -0
- package/runtime/core/ipofile/compile.js +364 -0
- package/runtime/core/ipofile/decls.js +187 -0
- package/runtime/core/ipofile/emit.js +708 -0
- package/runtime/core/ipofile/exec.js +164 -0
- package/runtime/core/ipofile/lex.js +243 -0
- package/runtime/core/ipofile/parse.js +550 -0
- package/runtime/core/ipofile/pool.js +404 -0
- package/runtime/core/ipofile/walk.js +431 -0
- package/runtime/core/ipovm/builtin-helpers.js +182 -0
- package/runtime/core/ipovm/builtins-api.js +610 -0
- package/runtime/core/ipovm/builtins-screen.js +493 -0
- package/runtime/core/ipovm/builtins-table.js +166 -0
- package/runtime/core/ipovm/builtins-text.js +166 -0
- package/runtime/core/ipovm/emissions.js +138 -0
- package/runtime/core/ipovm/hosts.js +191 -0
- package/runtime/core/ipovm/operators.js +229 -0
- package/runtime/core/ipovm/structures.js +250 -0
- package/runtime/core/ipovm/suspensions.js +241 -0
- package/runtime/core/ipovm/tape.js +206 -0
- package/runtime/core/ipovm/values.js +241 -0
- package/runtime/core/ipovm/vm.js +1166 -0
- package/runtime/core/translate.js +526 -0
- package/runtime/core/webshim/api-router.js +592 -0
- package/runtime/core/webshim/bus.js +95 -0
- package/runtime/core/webshim/coding.js +82 -0
- package/runtime/core/webshim/data-fetch.js +66 -0
- package/runtime/core/webshim/exchange.js +288 -0
- package/runtime/core/webshim/framing.js +331 -0
- package/runtime/core/webshim/install.js +30 -0
- package/runtime/core/webshim/job-runner.js +319 -0
- package/runtime/core/webshim/native-bus.js +108 -0
- package/runtime/core/webshim/timers.js +82 -0
- package/runtime/core/webshim/trace.js +205 -0
- package/runtime/core/webshim/transport-base.js +128 -0
- package/runtime/core/webshim/variant-resolver.js +249 -0
- package/runtime/core/webshim/web-serial-bus.js +734 -0
- package/runtime/home/bmweb-home.ips +76 -0
- package/runtime/home/bmweb.h +26 -0
- package/runtime/screens/activations.js +258 -0
- package/runtime/screens/garage/diff.js +331 -0
- package/runtime/screens/garage/share.js +276 -0
- package/runtime/screens/garage/store.js +547 -0
- package/runtime/screens/ipo-runtime/cells.js +176 -0
- package/runtime/screens/ipo-runtime/dialogs.js +254 -0
- package/runtime/screens/ipo-runtime/home.js +358 -0
- package/runtime/screens/ipo-runtime/open.js +393 -0
- package/runtime/screens/ipo-runtime/paint-grid.js +106 -0
- package/runtime/screens/ipo-runtime/paint-modern.js +424 -0
- package/runtime/screens/ipo-runtime/print.js +281 -0
- package/runtime/screens/ipo-runtime/program.js +1337 -0
- package/runtime/screens/ipo-runtime/protocol.js +464 -0
- package/runtime/screens/ipo-runtime/script-scan.js +225 -0
- package/runtime/screens/ipo-runtime/translate-sets.js +130 -0
- package/runtime/screens/ipo-runtime/ui.js +249 -0
- package/runtime/screens/ipo-runtime/wire-policy.js +113 -0
- package/runtime/screens/ir.js +324 -0
- package/runtime/screens/search/data.js +153 -0
- package/runtime/screens/search/match.js +285 -0
- package/runtime/screens/search/open.js +66 -0
- package/runtime/vendor/fflate.min.js +1 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The .IPO token walker, in JavaScript.
|
|
3
|
+
*
|
|
4
|
+
* Port of tools/decompile/ipo_disasm.py's walk() plus its BUILTINS and BINOPS
|
|
5
|
+
* tables. Every token is 4 bytes except the inline LINE/ITEM headers and the
|
|
6
|
+
* `%STATE` label, and every field this emits (op, n, sc, ref, kind, name, to,
|
|
7
|
+
* at, t, v, label, nr, dwords, keys, type, index, bytes) matches the Python
|
|
8
|
+
* name for name -- the shipped exec dumps are that walker's output, and the
|
|
9
|
+
* runtime that consumes them cannot tell the two producers apart.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Builtin call numbers to names, as the disassembler names them. */
|
|
13
|
+
const IPOF_BUILTINS = {
|
|
14
|
+
0x00: 'setmenutitle',
|
|
15
|
+
0x01: 'setmenu',
|
|
16
|
+
0x02: 'setitem',
|
|
17
|
+
0x03: 'settitle',
|
|
18
|
+
0x04: 'setscreen',
|
|
19
|
+
0x0c: 'exit',
|
|
20
|
+
0x05: 'setstate',
|
|
21
|
+
0x06: 'setstatemachine',
|
|
22
|
+
0x0f: 'scriptchange',
|
|
23
|
+
0x10: 'select',
|
|
24
|
+
0x11: 'deselect',
|
|
25
|
+
0x13: 'start',
|
|
26
|
+
0x17: 'printscreen',
|
|
27
|
+
0x18: 'printfile',
|
|
28
|
+
0x1c: 'getdate',
|
|
29
|
+
0x1d: 'gettime',
|
|
30
|
+
0x1e: 'realtostring',
|
|
31
|
+
0x1f: 'stringtoreal',
|
|
32
|
+
0x20: 'inttostring',
|
|
33
|
+
0x24: 'strlen',
|
|
34
|
+
0x25: 'midstr',
|
|
35
|
+
0x27: 'formatnum',
|
|
36
|
+
0x28: 'bytetoint',
|
|
37
|
+
0x29: 'inttolong',
|
|
38
|
+
0x2a: 'longtoreal',
|
|
39
|
+
0x3e: 'getinputstate',
|
|
40
|
+
0x41: 'inputhex',
|
|
41
|
+
0x42: 'inputdigital',
|
|
42
|
+
0x43: 'input2text',
|
|
43
|
+
0x44: 'input2hexnum',
|
|
44
|
+
0x45: 'input2hex',
|
|
45
|
+
0x46: 'inputint',
|
|
46
|
+
0x48: 'text',
|
|
47
|
+
0x49: 'textout',
|
|
48
|
+
0x4a: 'ftextout',
|
|
49
|
+
0x4b: 'digitalout',
|
|
50
|
+
0x4c: 'analogout',
|
|
51
|
+
0x4d: 'multianalogout',
|
|
52
|
+
0x4e: 'hexdump',
|
|
53
|
+
0x52: 'messagebox',
|
|
54
|
+
0x54: 'userboxopen',
|
|
55
|
+
0x55: 'userboxclose',
|
|
56
|
+
0x56: 'userboxftextout',
|
|
57
|
+
0x5b: 'callwin',
|
|
58
|
+
0x5c: 'viewopen',
|
|
59
|
+
0x5d: 'viewclose',
|
|
60
|
+
0x79: 'fileopen',
|
|
61
|
+
0x7a: 'fileclose',
|
|
62
|
+
0x7b: 'filewrite',
|
|
63
|
+
0x7c: 'fileread',
|
|
64
|
+
0x60: 'INPAapiInit',
|
|
65
|
+
0x61: 'INPAapiEnd',
|
|
66
|
+
0x62: 'INPAapiJob',
|
|
67
|
+
0x63: 'INPAapiResultText',
|
|
68
|
+
0x64: 'INPAapiResultInt',
|
|
69
|
+
0x65: 'INPAapiResultSets',
|
|
70
|
+
0x66: 'INPAapiResultDigital',
|
|
71
|
+
0x67: 'INPAapiResultAnalog',
|
|
72
|
+
0x68: 'INPAapiResultBinary',
|
|
73
|
+
0x69: 'INPAapiCheckJobStatus',
|
|
74
|
+
0x6b: 'INPAapiFsLesen',
|
|
75
|
+
0x6c: 'INPAapiFsMode',
|
|
76
|
+
0x6f: 'INP1apiJob',
|
|
77
|
+
0x71: 'INP1apiResultText',
|
|
78
|
+
0x72: 'INP1apiResultInt',
|
|
79
|
+
0x73: 'INP1apiResultSets',
|
|
80
|
+
0x75: 'INP1apiResultBinary',
|
|
81
|
+
0x76: 'INP1apiErrorCode',
|
|
82
|
+
0x77: 'INP1apiErrorText',
|
|
83
|
+
0x78: 'GetBinaryDataString',
|
|
84
|
+
0x8c: 'StrArrayCreate',
|
|
85
|
+
0x8d: 'StrArrayDestroy',
|
|
86
|
+
0x8e: 'StrArrayWrite',
|
|
87
|
+
0x8f: 'StrArrayRead',
|
|
88
|
+
0x91: 'StrArrayDelete',
|
|
89
|
+
0x9a: 'CreateStructure',
|
|
90
|
+
0x9b: 'SetStructureMode',
|
|
91
|
+
0x9c: 'StructureByte',
|
|
92
|
+
0x9d: 'StructureInt',
|
|
93
|
+
0x9e: 'StructureLong',
|
|
94
|
+
0x9f: 'StructureString',
|
|
95
|
+
// BMWeb's own builtins (app/renderer/home/bmweb.h): a script of the
|
|
96
|
+
// app's own can ask the host for a pick and a status line. Numbered past
|
|
97
|
+
// every number the INPA corpus uses, so a BMW script never collides.
|
|
98
|
+
0xe0: 'bmweb_pick',
|
|
99
|
+
0xe1: 'bmweb_status',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Binary operator opcodes to names. */
|
|
103
|
+
const IPOF_BINOPS = {
|
|
104
|
+
0x60: 'add',
|
|
105
|
+
0x61: 'sub',
|
|
106
|
+
0x62: 'mul',
|
|
107
|
+
0x63: 'div',
|
|
108
|
+
0x64: 'lt',
|
|
109
|
+
0x65: 'gt',
|
|
110
|
+
0x66: 'le',
|
|
111
|
+
0x67: 'ge',
|
|
112
|
+
0x68: 'eq',
|
|
113
|
+
0x69: 'ne',
|
|
114
|
+
0x6a: 'and',
|
|
115
|
+
0x6b: 'or',
|
|
116
|
+
0x6c: 'xor',
|
|
117
|
+
0x6d: 'neg',
|
|
118
|
+
0x6e: 'not',
|
|
119
|
+
0x6f: 'band',
|
|
120
|
+
0x70: 'bor',
|
|
121
|
+
0x71: 'bxor',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Local declaration type bytes (opcode 08). */
|
|
125
|
+
const IPOF_DECL_LOCALS = {
|
|
126
|
+
0x50: 'bool',
|
|
127
|
+
0x51: 'int',
|
|
128
|
+
0x52: 'byte',
|
|
129
|
+
0x53: 'long',
|
|
130
|
+
0x54: 'real',
|
|
131
|
+
0x55: 'string',
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Call names of the coding-dispatcher dialect (the `A_<cabd>` files).
|
|
136
|
+
*
|
|
137
|
+
* Same bytecode, a different host table: these files' calls index the coding
|
|
138
|
+
* host, so decoding one with the screen table would name every call wrongly.
|
|
139
|
+
* Carried so the reader reports a dropped coding file honestly rather than as
|
|
140
|
+
* a screen script full of unknown builtins.
|
|
141
|
+
* @type {Object<number, string>}
|
|
142
|
+
*/
|
|
143
|
+
const IPOF_CDH_NAMES = {
|
|
144
|
+
0: 'settimer',
|
|
145
|
+
1: 'testtimer',
|
|
146
|
+
2: 'exit',
|
|
147
|
+
3: 'realtostring',
|
|
148
|
+
4: 'inttostring',
|
|
149
|
+
5: 'hexconvert',
|
|
150
|
+
6: 'strcat',
|
|
151
|
+
7: 'strlen',
|
|
152
|
+
8: 'midstr',
|
|
153
|
+
11: 'CDHapiInit',
|
|
154
|
+
12: 'CDHapiEnd',
|
|
155
|
+
13: 'CDHapiJob',
|
|
156
|
+
14: 'CDHapiJobData',
|
|
157
|
+
15: 'CDHapiResultText',
|
|
158
|
+
16: 'CDHapiResultInt',
|
|
159
|
+
17: 'CDHapiResultSets',
|
|
160
|
+
18: 'CDHapiResultDigital',
|
|
161
|
+
19: 'CDHapiResultAnalog',
|
|
162
|
+
20: 'CDHapiResultBinary',
|
|
163
|
+
21: 'CDHapiCheckJobStatus',
|
|
164
|
+
22: 'apiInit',
|
|
165
|
+
23: 'apiEnd',
|
|
166
|
+
24: 'apiJob',
|
|
167
|
+
25: 'apiState',
|
|
168
|
+
26: 'apiResultText',
|
|
169
|
+
27: 'apiResultInt',
|
|
170
|
+
28: 'apiResultSets',
|
|
171
|
+
29: 'apiResultReal',
|
|
172
|
+
30: 'apiErrorCode',
|
|
173
|
+
31: 'apiErrorText',
|
|
174
|
+
32: 'GetBinaryDataString',
|
|
175
|
+
42: 'CDHGetFswPswFromZcs',
|
|
176
|
+
43: 'CDHSetReturnVal',
|
|
177
|
+
44: 'CDHSetSystemData',
|
|
178
|
+
45: 'CDHGetSystemData',
|
|
179
|
+
46: 'CDHSetCabdPar',
|
|
180
|
+
47: 'CDHGetCabdPar',
|
|
181
|
+
48: 'CDHGetFswPswFromCvt',
|
|
182
|
+
49: 'CDHReadSget',
|
|
183
|
+
50: 'CDHSetSgName',
|
|
184
|
+
51: 'CDHGetSgbdName',
|
|
185
|
+
52: 'CDHGetBaureiheFromZcs',
|
|
186
|
+
53: 'CDHActivateFsw',
|
|
187
|
+
54: 'CDHInactivateFsw',
|
|
188
|
+
55: 'CDHActivateGrp',
|
|
189
|
+
56: 'CDHInactivateGrp',
|
|
190
|
+
57: 'CDHActivateAllFsw',
|
|
191
|
+
58: 'CDHInactivateAllFsw',
|
|
192
|
+
59: 'CDHChangePsw',
|
|
193
|
+
60: 'CDHSaveFswPswList',
|
|
194
|
+
61: 'CDHRestoreFswPswList',
|
|
195
|
+
62: 'CDHSetCbdName',
|
|
196
|
+
63: 'CDHGetInfo',
|
|
197
|
+
64: 'CDHCheckIdent',
|
|
198
|
+
65: 'CDHGetFswDataFromCbd',
|
|
199
|
+
66: 'CDHGetFswPswDataFromCbd',
|
|
200
|
+
67: 'CDHGetGrpDataFromCbd',
|
|
201
|
+
68: 'CDHGetNettoDataFromCbd',
|
|
202
|
+
69: 'CDHGetNettoMaskFromCbd',
|
|
203
|
+
70: 'CDHGetFswPswFromNettoData',
|
|
204
|
+
71: 'CDHResetApiJobData',
|
|
205
|
+
72: 'CDHGetApiJobData',
|
|
206
|
+
73: 'CDHCheckDataUsed',
|
|
207
|
+
74: 'CDHBinBufToNettoData',
|
|
208
|
+
75: 'CDHBinBufCreate',
|
|
209
|
+
76: 'CDHBinBufDelete',
|
|
210
|
+
77: 'CDHBinBufWriteByte',
|
|
211
|
+
78: 'CDHBinBufWriteWord',
|
|
212
|
+
79: 'CDHBinBufReadByte',
|
|
213
|
+
80: 'CDHBinBufReadWord',
|
|
214
|
+
81: 'CDHBinBufToStr',
|
|
215
|
+
82: 'CDHResetError',
|
|
216
|
+
83: 'CDHSetError',
|
|
217
|
+
84: 'CDHTestError',
|
|
218
|
+
85: 'CDHGetApiJobByteData',
|
|
219
|
+
86: 'CDHSetCabdWordPar',
|
|
220
|
+
87: 'CDHGetCabdWordPar',
|
|
221
|
+
88: 'CDHGetReferenzProgramm',
|
|
222
|
+
89: 'CDHGetReferenzDaten',
|
|
223
|
+
91: 'CDHSetDataOrg',
|
|
224
|
+
92: 'CDHIdReady',
|
|
225
|
+
93: 'CDHCallAuthenticate',
|
|
226
|
+
94: 'CDHGetFaVersion',
|
|
227
|
+
95: 'CDHGetAnzahlFaElemente',
|
|
228
|
+
96: 'CDHGetFaElement',
|
|
229
|
+
97: 'CDHCheckIdent2',
|
|
230
|
+
98: 'CDHAuthGetRandom',
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/** LINE (21/22) and ITEM (24) headers carry their label inline. */
|
|
234
|
+
const IPOF_INLINE_OPS = { 0x21: 1, 0x22: 1, 0x24: 1 };
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Format a builtin's fallback name the way the Python's f-string does.
|
|
238
|
+
*
|
|
239
|
+
* @param {number} n The builtin number.
|
|
240
|
+
* @returns {string} `builtin_xx`, two lowercase hex digits.
|
|
241
|
+
*/
|
|
242
|
+
function ipofBuiltinName(n) {
|
|
243
|
+
const hex = n.toString(16);
|
|
244
|
+
return `builtin_${hex.length < 2 ? `0${hex}` : hex}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Try to read a `%STATE` label token at `i`.
|
|
249
|
+
*
|
|
250
|
+
* The token is `%NAME \n <u32 index> 0a`, and it sits where a 4-byte token
|
|
251
|
+
* would. The '%' is part of the token: keying on the shape alone matches
|
|
252
|
+
* pool markers inside ordinary format strings and swallows the instructions
|
|
253
|
+
* after each.
|
|
254
|
+
*
|
|
255
|
+
* @param {Uint8Array} data The file bytes.
|
|
256
|
+
* @param {number} i Offset to read at.
|
|
257
|
+
* @param {number} hi End of the proc body.
|
|
258
|
+
* @returns {{tok: Object, next: number}|null} The token and the offset after
|
|
259
|
+
* it, or null when these bytes are not a label.
|
|
260
|
+
*/
|
|
261
|
+
function ipofReadState(data, i, hi) {
|
|
262
|
+
if (data[i] !== 0x25) return null;
|
|
263
|
+
const j = ipofFindNl(data, i + 1, Math.min(hi, i + 72));
|
|
264
|
+
if (j < 0 || j === i) return null;
|
|
265
|
+
for (let k = i; k < j; k += 1) {
|
|
266
|
+
if (data[k] < 0x20 || data[k] >= 0x7f) return null;
|
|
267
|
+
}
|
|
268
|
+
if (!(j + 5 < hi && data[j + 5] === 0x0a)) return null;
|
|
269
|
+
return {
|
|
270
|
+
tok: {
|
|
271
|
+
op: 'state',
|
|
272
|
+
name: ipofLatin1(data, i, j),
|
|
273
|
+
index: ipofUint(data, j + 1, 4),
|
|
274
|
+
at: i,
|
|
275
|
+
},
|
|
276
|
+
next: j + 6,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Try to read an inline LINE / ITEM header at `i`.
|
|
282
|
+
*
|
|
283
|
+
* Layout: op + u16 0x000a + pad + u16 nr + caption \n + key list \n + pad +
|
|
284
|
+
* u16 end-dword. LINE's second string is its result-key list, usually empty;
|
|
285
|
+
* when empty the tail bytes look like an ordinary token, which is what makes
|
|
286
|
+
* reading the second newline mandatory rather than optional.
|
|
287
|
+
*
|
|
288
|
+
* @param {Uint8Array} data The file bytes.
|
|
289
|
+
* @param {number} i Offset to read at.
|
|
290
|
+
* @param {number} hi End of the proc body.
|
|
291
|
+
* @returns {{tok: Object, next: number}|null} The token and the offset after
|
|
292
|
+
* it, or null.
|
|
293
|
+
*/
|
|
294
|
+
function ipofReadInline(data, i, hi) {
|
|
295
|
+
const op = data[i];
|
|
296
|
+
if (!IPOF_INLINE_OPS[op] || i + 6 > hi) return null;
|
|
297
|
+
const nr = ipofUint(data, i + 4, 2);
|
|
298
|
+
const j = ipofFindNl(data, i + 6, Math.min(hi, i + 220));
|
|
299
|
+
if (j < 0) return null;
|
|
300
|
+
const label = ipofLatin1(data, i + 6, j);
|
|
301
|
+
const j2 = ipofFindNl(data, j + 1, Math.min(hi, j + 260));
|
|
302
|
+
const second = j2 >= 0 ? ipofLatin1(data, j + 1, j2) : '';
|
|
303
|
+
// +4, not +3: the end-dword u16 occupies data[j2+2:j2+4]
|
|
304
|
+
if (!(j2 >= 0 && j2 + 4 <= hi)) return null;
|
|
305
|
+
const tok = {
|
|
306
|
+
op: op === 0x24 ? 'ITEM' : 'LINE',
|
|
307
|
+
nr,
|
|
308
|
+
label,
|
|
309
|
+
dwords: ipofUint(data, j2 + 2, 2),
|
|
310
|
+
at: i,
|
|
311
|
+
};
|
|
312
|
+
if (second) tok.keys = second;
|
|
313
|
+
return { tok, next: j2 + 4 };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The token stream of one proc body.
|
|
318
|
+
*
|
|
319
|
+
* Jump tokens carry `to`, the target resolved to a byte offset: targets are
|
|
320
|
+
* dword indices relative to the ENCLOSING BLOCK (the proc body after its
|
|
321
|
+
* header, or the bytes after the nearest ITEM/LINE header), which `base`
|
|
322
|
+
* tracks.
|
|
323
|
+
*
|
|
324
|
+
* @param {Uint8Array} data The file bytes.
|
|
325
|
+
* @param {number} lo First byte of the body.
|
|
326
|
+
* @param {number} hi End of the body, exclusive.
|
|
327
|
+
* @param {Array<Array>} pool The constant pool entries.
|
|
328
|
+
* @param {Object<number, string>} [names] Call-name table override, for the
|
|
329
|
+
* dialects that index a different host table.
|
|
330
|
+
* @returns {{toks: Object[], unknown: number, len: number}} The tokens, the
|
|
331
|
+
* count of undecoded bytes, and the body length.
|
|
332
|
+
*/
|
|
333
|
+
function ipofWalk(data, lo, hi, pool, names) {
|
|
334
|
+
const callNames = names || IPOF_BUILTINS;
|
|
335
|
+
const toks = [];
|
|
336
|
+
let unknown = 0;
|
|
337
|
+
let i = lo;
|
|
338
|
+
let base = lo + 4; // dword 0 of the proc's own block
|
|
339
|
+
while (i < hi) {
|
|
340
|
+
const start = i;
|
|
341
|
+
const mark = toks.length;
|
|
342
|
+
const st = ipofReadState(data, i, hi);
|
|
343
|
+
if (st) {
|
|
344
|
+
toks.push(st.tok);
|
|
345
|
+
i = st.next;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const inl = ipofReadInline(data, i, hi);
|
|
349
|
+
if (inl) {
|
|
350
|
+
toks.push(inl.tok);
|
|
351
|
+
i = inl.next;
|
|
352
|
+
base = i; // jumps inside this body count from here
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (i + 4 > hi) {
|
|
356
|
+
unknown += hi - i;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
const b0 = data[i];
|
|
360
|
+
const b1 = data[i + 1];
|
|
361
|
+
const u16 = ipofUint(data, i + 2, 2);
|
|
362
|
+
const mid = ipofUint(data, i + 1, 2);
|
|
363
|
+
if (b0 === 0x01 && b1 === 0x01) {
|
|
364
|
+
const v = u16 < pool.length ? pool[u16] : ['?', u16];
|
|
365
|
+
toks.push({ op: 'const', n: u16, t: v[0], v: v[1] });
|
|
366
|
+
} else if (b0 === 0x01 && (b1 === 0x00 || b1 === 0x02 || b1 === 0x03)) {
|
|
367
|
+
toks.push({ op: 'var', n: u16, sc: b1 });
|
|
368
|
+
} else if (b0 === 0x03 && (b1 === 0x00 || b1 === 0x02 || b1 === 0x03)) {
|
|
369
|
+
// push THROUGH a reference param: reading an out/inout param's value
|
|
370
|
+
toks.push({ op: 'var', n: u16, sc: b1, ref: true });
|
|
371
|
+
} else if (b0 === 0x06 && b1 >= 0x00 && b1 <= 0x03) {
|
|
372
|
+
toks.push({ op: 'store', n: u16, sc: b1 });
|
|
373
|
+
} else if (b0 === 0x07 && (b1 === 0x00 || b1 === 0x02 || b1 === 0x03)) {
|
|
374
|
+
toks.push({ op: 'store', n: u16, sc: b1, ref: true });
|
|
375
|
+
} else if (b0 === 0x08 && IPOF_DECL_LOCALS[b1] && u16 === 0) {
|
|
376
|
+
toks.push({ op: 'decl', type: IPOF_DECL_LOCALS[b1] });
|
|
377
|
+
} else if (b0 === 0x02) {
|
|
378
|
+
toks.push({ op: 'procref', kind: b1, n: u16 });
|
|
379
|
+
} else if (b0 === 0x09) {
|
|
380
|
+
// an unnamed operator keeps an explicit null: the Python's dict lookup
|
|
381
|
+
// yields None and the field is present in every shipped dump, so a
|
|
382
|
+
// JS `undefined` would drop the key and diverge
|
|
383
|
+
toks.push({ op: 'binop', n: mid, name: IPOF_BINOPS[mid] || null });
|
|
384
|
+
} else if (b0 === 0x0a && b1 === 0x00) {
|
|
385
|
+
if (!toks.length) toks.push({ op: 'block', dwords: u16 });
|
|
386
|
+
else toks.push({ op: 'jump', to: base + 4 * u16 });
|
|
387
|
+
} else if (b0 === 0x0b && b1 === 0x00) {
|
|
388
|
+
toks.push({ op: 'jfalse', to: base + 4 * u16 });
|
|
389
|
+
} else if (b0 === 0x0e && b1 === 0x00 && u16 === 0) {
|
|
390
|
+
toks.push({ op: 'ret' });
|
|
391
|
+
} else if (b0 === 0x0d && b1 === 0x00 && u16 === 0) {
|
|
392
|
+
toks.push({ op: 'endproc' });
|
|
393
|
+
} else if (b0 === 0x0d && b1 === 0x01) {
|
|
394
|
+
toks.push({ op: 'dllcall', n: u16 });
|
|
395
|
+
} else if (b0 === 0x05 && b1 === 0x00) {
|
|
396
|
+
toks.push({ op: 'stmt', n: u16 });
|
|
397
|
+
} else if (b0 === 0x0c && b1 === 0x80) {
|
|
398
|
+
toks.push({ op: 'calluser', n: u16 });
|
|
399
|
+
} else if (b0 === 0x0c && b1 === 0x81) {
|
|
400
|
+
toks.push({
|
|
401
|
+
op: 'call',
|
|
402
|
+
n: u16,
|
|
403
|
+
name: callNames[u16] || ipofBuiltinName(u16),
|
|
404
|
+
});
|
|
405
|
+
} else if (b0 === 0x0f) {
|
|
406
|
+
toks.push({ op: 'frame' });
|
|
407
|
+
} else {
|
|
408
|
+
let hex = '';
|
|
409
|
+
for (let k = 0; k < 4; k += 1)
|
|
410
|
+
hex += data[i + k].toString(16).padStart(2, '0');
|
|
411
|
+
toks.push({ op: 'unk', bytes: hex });
|
|
412
|
+
unknown += 4;
|
|
413
|
+
}
|
|
414
|
+
for (let k = mark; k < toks.length; k += 1) {
|
|
415
|
+
if (toks[k].at === undefined) toks[k].at = start;
|
|
416
|
+
}
|
|
417
|
+
i += 4;
|
|
418
|
+
}
|
|
419
|
+
return { toks, unknown, len: hi - lo };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
423
|
+
module.exports = {
|
|
424
|
+
ipofWalk,
|
|
425
|
+
IPOF_BUILTINS,
|
|
426
|
+
IPOF_BINOPS,
|
|
427
|
+
IPOF_DECL_LOCALS,
|
|
428
|
+
ipofBuiltinName,
|
|
429
|
+
IPOF_CDH_NAMES,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Argument helpers every builtin shares: picking a builtin's arguments
|
|
3
|
+
* off the call stack by type, and writing its answer through the
|
|
4
|
+
* out-parameter (a procref) the script pushed for it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A builtin implementation. `item` is the menu ITEM being executed (offline
|
|
9
|
+
* lifts attribute jobs and dialogs to it), null when none.
|
|
10
|
+
* @typedef {(vm: IpoVm, stack: IpoValue[], item: (IpoItem|null)) => void} IpoBuiltin
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The name a `call` token dispatches on: its decoded name, else
|
|
15
|
+
* `builtin_<hex>` from its number (the unnamed builtins the corpus proved
|
|
16
|
+
* the shape of are mapped by that form).
|
|
17
|
+
* @param {IpoToken} t - the call token
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Builtins the disassembler leaves numbered, named by their Inpa.h
|
|
22
|
+
* prototype: each number's call shape across the corpus (argument count,
|
|
23
|
+
* out-references, literal types, the procs that call it) fits exactly one
|
|
24
|
+
* prototype. delay is the wait every activation screen paces itself with;
|
|
25
|
+
* clearrect and ftextclear blank screen areas; callstatemachine and
|
|
26
|
+
* returnstatemachine run a sub-machine; inputtext and inputnum are user
|
|
27
|
+
* prompts; the rest are the factory line's interfaces (PLC, order files).
|
|
28
|
+
* @type {Object<string, string>}
|
|
29
|
+
*/
|
|
30
|
+
const IPO_BUILTIN_CANON = {
|
|
31
|
+
builtin_07: 'callstatemachine',
|
|
32
|
+
builtin_08: 'returnstatemachine',
|
|
33
|
+
builtin_0b: 'setjobstatus',
|
|
34
|
+
builtin_1b: 'delay',
|
|
35
|
+
builtin_39: 'inputnum',
|
|
36
|
+
builtin_3a: 'inputtext',
|
|
37
|
+
builtin_4f: 'ftextclear',
|
|
38
|
+
builtin_50: 'clearrect',
|
|
39
|
+
builtin_92: 'SPSInit',
|
|
40
|
+
builtin_94: 'SPSLeseVonSPS',
|
|
41
|
+
builtin_95: 'SPSSendeAnSPS',
|
|
42
|
+
builtin_97: 'ApiJobFsLesenFAB',
|
|
43
|
+
builtin_98: 'ApiResultFsLesenFAB',
|
|
44
|
+
builtin_99: 'ELDIOpenStartDialog',
|
|
45
|
+
builtin_a1: 'setitemrepeat',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function ipoBuiltinName(t) {
|
|
49
|
+
const raw = t.name || `builtin_${t.n.toString(16).padStart(2, '0')}`;
|
|
50
|
+
return IPO_BUILTIN_CANON[raw] || raw;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The result key of the first argument that carries one.
|
|
55
|
+
* @param {IpoValue[]} stack - the call's arguments
|
|
56
|
+
* @returns {string|null}
|
|
57
|
+
*/
|
|
58
|
+
function keyed(stack) {
|
|
59
|
+
for (const x of stack) if (isBound(x) && x.key) return x.key;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The destination a builtin writes through: the first procref, or null.
|
|
65
|
+
* @param {IpoValue[]} stack - the call's arguments
|
|
66
|
+
* @returns {IpoRef|null}
|
|
67
|
+
*/
|
|
68
|
+
function outRef(stack) {
|
|
69
|
+
for (const x of stack) if (isRef(x)) return x;
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Write a builtin's answer through its out-parameter. With a key the value is
|
|
75
|
+
* boxed as bound text and the destination slot is bound to that key. An
|
|
76
|
+
* offline non-answer ('' or null) must not overwrite a preset presence flag
|
|
77
|
+
* (a global holding `true`).
|
|
78
|
+
* @param {IpoVm} vm - the running VM
|
|
79
|
+
* @param {IpoValue[]} stack - the call's arguments (the first ref is the destination)
|
|
80
|
+
* @param {IpoValue} val - the answer
|
|
81
|
+
* @param {string|null} [key] - the result key the answer came from
|
|
82
|
+
* @returns {number|null} the destination slot, or null when there was none
|
|
83
|
+
*/
|
|
84
|
+
function storeOut(vm, stack, val, key) {
|
|
85
|
+
const dest = outRef(stack);
|
|
86
|
+
if (dest == null) return null;
|
|
87
|
+
const { dsc: sc, n, map } = vm._refTarget(dest, vm.frame);
|
|
88
|
+
if (key != null) {
|
|
89
|
+
const amap = isBound(val) ? val.amap : null;
|
|
90
|
+
val = mkBound(mkSlot(sc, n), val == null ? '' : asStr(val), key);
|
|
91
|
+
val.amap = amap;
|
|
92
|
+
vm.setBind(sc, n, key);
|
|
93
|
+
}
|
|
94
|
+
if (sc === GLOBAL) {
|
|
95
|
+
const empty = val === '' || val == null || (isBound(val) && val.s === '');
|
|
96
|
+
if (!empty || vm.globals.get(n) !== true) vm.globals.set(n, val);
|
|
97
|
+
} else {
|
|
98
|
+
map.set(n, val);
|
|
99
|
+
}
|
|
100
|
+
return n;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The first plain string argument, or null.
|
|
105
|
+
* @param {IpoValue[]} stack - the call's arguments
|
|
106
|
+
* @returns {string|null}
|
|
107
|
+
*/
|
|
108
|
+
function firstStr(stack) {
|
|
109
|
+
for (const x of stack) if (isPlainStr(x)) return x;
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Every plain string argument, in order.
|
|
115
|
+
* @param {IpoValue[]} stack - the call's arguments
|
|
116
|
+
* @returns {string[]}
|
|
117
|
+
*/
|
|
118
|
+
function allStrs(stack) {
|
|
119
|
+
return stack.filter(isPlainStr);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Every plain integer argument, in order (booleans excluded).
|
|
124
|
+
* @param {IpoValue[]} stack - the call's arguments
|
|
125
|
+
* @returns {number[]}
|
|
126
|
+
*/
|
|
127
|
+
function allInts(stack) {
|
|
128
|
+
return stack.filter((x) => isPlainInt(x) && typeof x !== 'boolean');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The first string-like argument (a plain string or a bound value), or
|
|
133
|
+
* undefined. Python's builtins test isinstance(x, str), which _Bound (a str
|
|
134
|
+
* subclass) passes, so a job result written through INPAapiResult* counts.
|
|
135
|
+
* @param {IpoValue[]} stack - the call's arguments
|
|
136
|
+
* @returns {string|IpoBound|undefined}
|
|
137
|
+
*/
|
|
138
|
+
function firstText(stack) {
|
|
139
|
+
return stack.find((x) => isPlainStr(x) || isBound(x));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The first number-like argument as a number: an unboxed number or a boxed
|
|
144
|
+
* float, else the first string-like argument parsed (0 when unparseable).
|
|
145
|
+
* @param {IpoValue[]} stack - the call's arguments
|
|
146
|
+
* @returns {number}
|
|
147
|
+
*/
|
|
148
|
+
function firstNumberArg(stack) {
|
|
149
|
+
const n = stack.find((x) => typeof x === 'number' || isFloat(x));
|
|
150
|
+
if (n != null) return num(n);
|
|
151
|
+
const parsed = parseFloat(asStr(firstText(stack)));
|
|
152
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* A result key without its display suffix: STAT_X_WERT, STAT_X_EINH and
|
|
157
|
+
* STAT_X_TEXT all belong to STAT_X.
|
|
158
|
+
* @param {string} key - result key
|
|
159
|
+
* @returns {string}
|
|
160
|
+
*/
|
|
161
|
+
function baseKey(key) {
|
|
162
|
+
const u = String(key).toUpperCase();
|
|
163
|
+
for (const suf of ['_WERT', '_EINH', '_TEXT', '_EINHEIT']) {
|
|
164
|
+
if (u.endsWith(suf)) return key.slice(0, -suf.length);
|
|
165
|
+
}
|
|
166
|
+
return key;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
170
|
+
module.exports = {
|
|
171
|
+
ipoBuiltinName,
|
|
172
|
+
keyed,
|
|
173
|
+
outRef,
|
|
174
|
+
storeOut,
|
|
175
|
+
firstStr,
|
|
176
|
+
allStrs,
|
|
177
|
+
allInts,
|
|
178
|
+
firstText,
|
|
179
|
+
firstNumberArg,
|
|
180
|
+
baseKey,
|
|
181
|
+
};
|
|
182
|
+
}
|