-
Notifications
You must be signed in to change notification settings - Fork 0
/
_tab_out.ahk
481 lines (450 loc) · 19 KB
/
_tab_out.ahk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
#Requires AutoHotkey v2.0+
#include "_get_c_function_header.ahk"
class tabOut extends GuiBase {
ctl_log := 0
ctl_out := 0
ctl_cpy := 0
getName() {
return "Output"
}
preInit() {
}
start() {
this.guiAdd("Text", "section h0 w0")
this.startGroupBox("Command output")
this.ctl_log := this.guiAdd("Edit", "-E0x200 section xs r11 w" . (this.columnWidthAll - 24) . " +multi +readonly")
this.endGroupBox()
this.startGroupBox("mcode")
this.ctl_out := this.guiAdd("Edit", "-E0x200 section xs r8 w" . (this.columnWidthAll - 24) . " +multi +readonly")
this.pushRightMark()
this.guiAdd("Button", "section xs", "&Decoder...").OnEvent("Click", eventDecoder)
this.ctl_cpy := this.guiAdd("Button", "ys +disabled", "C&opy to clipboard")
this.ctl_cpy.OnEvent("Click", eventCopy)
this.pushRightFinish(true)
this.endGroupBox()
this.finalizeFinalColumn()
return
eventDecoder(*) {
if "Yes" = MsgBox("Copy a sample decoder to the clipboard?",
"Decoder", "YesNo Icon?") {
sample := FileRead(A_ScriptDir . "\sample.ahk")
A_Clipboard := sample
MsgBox("Done.", "Decoder")
}
}
eventCopy(*) {
A_Clipboard := this.ctl_out.Value
}
}
out(txt) {
this.ctl_out.Value := txt
this.ctl_cpy.Opt(txt ? "-disabled" : "+disabled")
}
compile() {
GuiBase.status("Working...")
if (!PROGRAM_SOURCE_FILE) || (!FileExist(PROGRAM_SOURCE_FILE)) {
MsgBox("Please specify a valid source file to compile.")
GuiBase.status()
return
}
if !CL_LOC || !CL_IDENT
{
MsgBox("Cannot find the compiler. Please make sure the file exists.")
GuiBase.status()
return
}
if !AS_LOC || ! AS_IDENT
{
MsgBox("Cannot find the assembler. Please make sure the file exists.")
GuiBase.status()
return
}
; clear output
this.ctl_log.Value := ""
this.out("")
; set working dir to the source's directory so any #includes
; it does will work
workingdir := ""
source_name := PROGRAM_SOURCE_FILE
filesepidx := InStr(PROGRAM_SOURCE_FILE, "\",, -1)
if filesepidx {
workingdir := SubStr(PROGRAM_SOURCE_FILE, 1, filesepidx - 1)
source_name := SubStr(PROGRAM_SOURCE_FILE, filesepidx+1)
}
; cobble up cl flags
clopts := CL_FLG . " " . CL_OPT
clopts := clopts . ((CL_USED & CL_CLANG) ? " -fno-integrated-as" : "")
; source
srcfile := source_name
; linker output
objfile := source_name . ".o"
; c function names
auxfile := source_name . ".aux.txt"
; the listing that we're after
lstfile := source_name . ".lst.txt"
; stderr gets redirected here
errfile := source_name . ".err.txt"
; the intermediate assembly file
asmfile := source_name . ".s"
; remove any previously genrated files if they're still around
removeWorkFiles()
; final command line
fmts := CL_USED & CL_WSL ? (
(CL_USED & CL_GCC) ?
'{} /c "wsl.exe "{}" "{}" {} "{}" -aux-info "{}" 2> "{}""'
:
'{} /c "wsl.exe "{}" "{}" {} "{}" 2> "{}""'
) : (
(CL_USED & CL_GCC) ?
'{} /c ""{}" "{}" {} "{}" -aux-info "{}" 2> "{}""'
:
'{} /c ""{}" "{}" {} "{}" 2> "{}""'
)
cl := CL_USED & CL_GCC ?
Format(fmts, A_ComSpec, CL_LOC, srcfile, clopts . " -S -c -o", asmfile, auxfile, errfile) :
Format(fmts, A_ComSpec, CL_LOC, srcfile, clopts . " -S -c -o", asmfile, errfile)
; log all this stuff
if _OPTS_SHOW_COMMANDS {
log("Working Directory: ")
log(workingdir)
log()
log("Executing: ")
log(cl)
log()
}
; fix up path in case gcc needs additional stuff
ec := run_cl(CL_LOC, cl)
log("Compiler exit code: " . ec)
; read and display stderr
err := ""
if FileExist(workingdir . "\" . errfile)
err := FileRead(workingdir . "\" . errfile)
if (err)
log(err)
; ask about warnings
if ec = 0 && StrLen(err) {
answer := askAboutWarnings()
if answer = "No" {
GuiBase.status()
return
}
}
; bail if error
if ec {
GuiBase.status("Compiler reported failure. Exit code " . ec . ".")
return
}
if _OPTS_MERGE_RDATA | _OPTS_NOPEMPTYCALLS {
; patch the .s file for .rdata or call __chkstk_ms
lst := ""
if FileExist(workingdir . "\" . asmfile) {
lst := FileRead(workingdir . "\" . asmfile)
} else {
log("ERROR: listing file not found while no error reported by assembler. Exiting.")
GuiBase.status()
return
}
out := "", line := 1, removed := 0, ignored := 0
Loop Parse lst, "`n", "`r" {
remove := false, ignore := false
if RegExMatch(A_LoopField, "Ami)\s+call(l|q){0,1}\s+(___chkstk_ms|__stack_chk_fail)\b") {
remove := _OPTS_NOPEMPTYCALLS
ignore := !_OPTS_NOPEMPTYCALLS
} else if RegExMatch(A_LoopField, "Ami)\s+\.section\s+\.(rdata|rodata|note)\b") ||
RegExMatch(A_LoopField, "Ami)\s+\.data\b") {
remove := _OPTS_MERGE_RDATA
ignore := !_OPTS_MERGE_RDATA
}
if (remove) {
log("INFO(" . PROGRAM_TITLE . "): " . asmfile
. "`r`n line " . line . ", removed: " . Trim(A_LoopField))
removed++
} else if (ignore) {
log("WARNING(" . PROGRAM_TITLE . "): " . asmfile
. "`r`n line " . line . ", not handled: " . Trim(A_LoopField))
ignored++
} else {
out := out . A_LoopField . "`n"
}
line++
}
encoding := FileEncoding("UTF-8-RAW")
file := FileOpen(workingdir . "\" . asmfile, "w")
file.Write(out)
file.Close()
FileEncoding(encoding)
if removed | ignored {
log("INFO(" . PROGRAM_TITLE . "): " . asmfile
. "`r`n ignored " . ignored . " and removed " . removed . " line(s)")
}
}
; assembler command line
asopts := AS_FLG
fmts := (AS_USED & AS_WSL) ?
'{} /c "wsl.exe "{}" -aln="{}" {} -o "{}" "{}" 2> "{}""' :
'{} /c ""{}" -aln="{}" {} -o "{}" "{}" 2> "{}""'
; the 16 width makes the listing not very human-readable but it allows for space for a full .align 64
gas := Format(fmts, A_ComSpec, AS_LOC, lstfile, asopts . " --listing-lhs-width=16", objfile, asmfile, errfile)
if _OPTS_SHOW_COMMANDS {
log("Executing: ")
log(gas)
log()
}
ec := run_cl(AS_LOC, gas)
log("Assembler exit code: " . ec)
; read and display stderr
err := ""
if FileExist(workingdir . "\" . errfile)
err := FileRead(workingdir . "\" . errfile)
if (err)
log(err)
; ask about warnings
if ec = 0 && StrLen(err) {
answer := askAboutWarnings()
if answer = "No" {
GuiBase.status()
return
}
}
; bail if error
if ec {
GuiBase.status("Assembler reported failure. Exit code " . ec . ".")
return
}
; load the output
lst := ""
if FileExist(workingdir . "\" . lstfile) {
lst := FileRead(workingdir . "\" . lstfile)
} else {
log("ERROR: listing file not found while no error reported by assembler. Exiting.")
GuiBase.status()
return
}
if !_OPTS_KEEPJUNK
removeWorkFiles()
blob := Buffer(StrLen(lst)), blobptr := blob.ptr
current_offset := 0, exported_labels := []
Loop Parse lst, "`n", "`r" {
found := RegExMatch(A_LoopField, "Ami)"
. "\s*(?<lineno>\d+)\s+" ; " 132 " (line number)
. "((?<offset>[\da-f]+)(?=\s)){0,1}\s*(?=\s)" ; " 002f " (offset, 0 or 1 time)
. "(?<hex>((\s)\K[\da-f]+(?=\s))+)*\s*" ; any hex string plus whitespace, 0 or more times
. "(\.section\s+(?<section>[^\s]+)){0,1}\s*" ; ".section" followed by anything 0 or 1 time
. '(?<label>[a-z_][a-z_\d]*:\s*(#|$)){0,1}' ; " _label2123:" followed by optional whitespace then a comment or the end of the line
. "(?<rest>.*)", &match) ; whatever's left from the line
if found {
if strlen(match["hex"]) {
mstr := StrReplace(StrReplace(match["hex"], " ", ""), "`t", "")
offset := ("0x" . match["offset"]) + 0
if offset != current_offset {
log("INTERNAL ERROR(" . PROGRAM_TITLE . "): " . lstfile
. "`r`n line " . match["lineno"] . ": offset mismatch")
GuiBase.status("Error.")
return
}
if (mstr = "E800000000") {
log("WARNING(" . PROGRAM_TITLE . "): " . lstfile
. "`r`n line " . match["lineno"] . " offs " . match["offset"] . ": " . mstr . " " . match["rest"])
}
blobptr := appendBufferHex(blobptr, mstr)
current_offset := current_offset + strlen(mstr) / 2
}
if match["section"] {
current_offset := 0
log("WARNING(" . PROGRAM_TITLE . "): " . lstfile
. "`r`n line " . match["lineno"] . ": .section " . match["section"])
}
if match["label"] {
; remove the ":" from the end of the label (it's guaranteed to be there)
label := SubStr(match["label"], 1, InStr(match["label"], ":") - 1)
xoffset := format("0x{:06x}", current_offset)
exported_labels.push({offset: xoffset, label: label})
log("INFO(" . PROGRAM_TITLE . "): " . lstfile
. "`r`n exported label at " . xoffset . ": " . label)
}
}
}
bloblen := blobptr - blob.ptr
b64b := b64encode(blob.ptr, bloblen)
formatted := ""
padding := " "
marker := "----------------------------------------------------------------------------"
meta_lines := []
if (_OPTS_MCODE_META) {
metaline(" . `"`" `; " . source_name)
metaline(" . `"`" `; " . bloblen . ' bytes')
metaline(" . `"`" `; " . CL_IDENT)
metaline(" . `"`" `; flags: " . clopts)
metaline(" . `"`" `; " . AS_IDENT)
metaline(" . `"`" `; flags: " . asopts)
}
while StrLen(b64b) > 0 {
formatted := formatted . ' . "' . SubStr(b64b, 1, _OPTS_BASE64_LINELEN) . '"`n'
b64b := SubStr(b64b, _OPTS_BASE64_LINELEN + 1)
}
if (_OPTS_MCODE_META) {
longest_label := 0
for label in exported_labels
longest_label := max(longest_label, strlen(label.label))
for label in exported_labels {
line := " mcode_" . label.label . substr(padding, 1, longest_label - strlen(label.label))
line := line . " := " . label.offset
if (_OPTS_FN_HDRS) {
header := get_c_function_header(label.label, workingdir . "\" . srcfile, workingdir . "\" . auxfile)
if (RegExMatch(header, "\s*^static\s+"))
continue
line := line . " `; " . header
}
formatted := formatted . line . '`n'
}
msg := "end of ahkmcodegen auto-generated section"
lenmsg := strlen(msg)
lenmarker_l := (strlen(marker) - lenmsg) // 2
lenmarker_r := (strlen(marker) - lenmsg) - lenmarker_l
metaline(" `;" . substr(marker, 1, lenmarker_l) . " " . msg . " " . substr(marker, 1, lenmarker_r))
metaline(s) {
meta_lines.push(Trim(s))
formatted := formatted . s . '`n'
}
}
this.out(formatted)
insertcnt := 0
if (_OPTS_MCODE_META && AHK_SOURCE_FILE && FileExist(AHK_SOURCE_FILE) && meta_lines.length = 7) {
ahk := FileRead(AHK_SOURCE_FILE)
ahk_new := ""
ahk_alt := ""
meta_matching := 1
Loop Parse ahk, "`n", "`r" {
s1 := Trim(A_LoopField)
s2 := meta_lines[meta_matching]
m := s1 = s2
if (meta_matching = 1) {
; check for the first line matching, e.g. ' . "" ; program.c'
if m {
ahk_alt := A_LoopField . '`n'
meta_matching++
} else {
ahk_new := ahk_new . A_LoopField . '`n'
}
} else if meta_matching = 2 {
; line 2 is is the one we're not checking for a match, it's the
; size of the binary blob and can change from build to build
meta_matching++
; save whatever's here in case the block is not a match
ahk_alt := ahk_alt . A_LoopField . '`n'
} else if meta_matching = 7 {
if m {
; line 7 is the end of the block so if it's a match we're good:
; add the formatted blob to the output.
ahk_new := ahk_new . formatted
; reset the search (there might be multiple copies of the blob???)
meta_matching := 1
insertcnt++
} else {
; otherwise we're skipping base64-encoded stuff so just advance,
; but accumulate whatever we're skipping in case we never find a type 7 match
ahk_alt := ahk_alt . A_LoopField . '`n'
}
} else {
; we're on lines 3-6, these need to match, otherwise we reset the search
if m {
ahk_alt := ahk_alt . A_LoopField . '`n'
meta_matching++
} else {
ahk_new := ahk_new . ahk_alt . A_LoopField . '`n'
ahk_alt := ""
meta_matching := 1
}
}
}
if (meta_matching != 1) {
; we didn't find a full match, so we're appending the alternate stream
; to the end of the file
ahk_new := ahk_new . ahk_alt
}
; write out the results
fileobj := FileOpen(AHK_SOURCE_FILE, "w")
fileobj.Write(ahk_new)
fileobj.Close()
}
status := "Done. Code bytes: " . bloblen . "."
if insertcnt
status := status . " Inserted into .ahk source " . insertcnt . " time(s)."
GuiBase.status(status)
theGui.setCurrentTab(theGui.tabs, 1)
return
appendBufferHex(ptr, txt) {
txt := StrReplace(txt, " ", "")
while (strlen(txt)) {
byte := ("0x" . SubStr(txt, 1, 2)) + 0
txt := SubStr(txt, 3)
NumPut("uchar", byte, ptr)
ptr++
}
return ptr
}
b64encode(ptr, len) {
str := ""
;just double the binary size, which is more
;than enough to hold the b64-encoded output
;which has a 33% overhead (4 bytes to store 3)
;(also adding 3 in case we're encoding a single
;byte which needs to be padded to 4)
str_len := len * 2 + 3
VarSetStrCapacity(&str, str_len)
DllCall("Crypt32.dll\CryptBinaryToString",
"ptr", ptr, "uint", len, "uint", 0x40000001,
"str", str, "uintp", str_len, "cdecl uint")
VarSetStrCapacity(&str, -1)
return str
}
run_cl(path_cl, cl) {
path_os := EnvGet("PATH")
path_cl := SubStr(path_cl, 1, InStr(path_cl, "\",, -1))
EnvSet("PATH", path_os . ";" . path_cl)
; run and grab output
ec := RunWait(cl, workingdir, "Hide")
EnvSet("PATH", path_os)
return ec
}
askAboutWarnings() {
return _OPTS_IGNOREWARNINGS ? "Yes" : MsgBox("There have been compiler warnings. "
. "Please check the output. Continue generating code?"
. "`r`n`r`n"
. "(This message can be turned off on the Options tab.)",
"Rats.", "Owner" . GuiBase.mainGui.Hwnd . " YesNo Icon!")
}
removeWorkFiles() {
if FileExist( workingdir . "\" . objfile)
FileDelete(workingdir . "\" . objfile)
if FileExist( workingdir . "\" . lstfile)
FileDelete(workingdir . "\" . lstfile)
if FileExist( workingdir . "\" . errfile)
FileDelete(workingdir . "\" . errfile)
if FileExist( workingdir . "\" . asmfile)
FileDelete(workingdir . "\" . asmfile)
if FileExist( workingdir . "\" . auxfile)
FileDelete(workingdir . "\" . auxfile)
}
log(txt := "") {
return this.log(txt)
}
}
log(txt := "") {
txt := txt . "`r`n"
EM_SETSEL := 0x0001
EM_REPLACESEL := 0x00C2
try {
SendMessage(EM_SETSEL, 0, -1, this.ctl_log)
} catch as e {
}
try {
SendMessage(EM_SETSEL, -1, -1, this.ctl_log)
} catch as e {
}
try {
SendMessage(EM_REPLACESEL, -1, StrPtr(txt), this.ctl_log)
} catch as e {
}
}
}