agentgui 1.0.273 → 1.0.274
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/build-portable.js +150 -0
- package/package.json +1 -1
- package/portable-entry.js +43 -0
- package/scripts/inject-pe-section.py +185 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { execSync, execFileSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const src = __dirname;
|
|
8
|
+
const out = path.join(src, '..', 'wagentgui');
|
|
9
|
+
const nm = path.join(src, 'node_modules');
|
|
10
|
+
const BUN_WIN_CACHE = path.join(process.env.HOME, '.bun', 'install', 'cache', 'bun-windows-x64-v1.3.9');
|
|
11
|
+
|
|
12
|
+
function log(msg) { console.log('[BUILD] ' + msg); }
|
|
13
|
+
|
|
14
|
+
function rmrf(dir) {
|
|
15
|
+
if (!fs.existsSync(dir)) return;
|
|
16
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
17
|
+
const fp = path.join(dir, entry.name);
|
|
18
|
+
if (entry.isDirectory()) fs.rmSync(fp, { recursive: true, force: true });
|
|
19
|
+
else fs.unlinkSync(fp);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function copyDir(from, to) {
|
|
24
|
+
if (!fs.existsSync(from)) return;
|
|
25
|
+
fs.mkdirSync(to, { recursive: true });
|
|
26
|
+
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
27
|
+
const s = path.join(from, entry.name);
|
|
28
|
+
const d = path.join(to, entry.name);
|
|
29
|
+
if (entry.isDirectory()) copyDir(s, d);
|
|
30
|
+
else fs.copyFileSync(s, d);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function copyFile(from, to) {
|
|
35
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
36
|
+
fs.copyFileSync(from, to);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sizeOf(dir) {
|
|
40
|
+
if (!fs.existsSync(dir)) return 0;
|
|
41
|
+
let sz = 0;
|
|
42
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
43
|
+
const fp = path.join(dir, e.name);
|
|
44
|
+
sz += e.isDirectory() ? sizeOf(fp) : fs.statSync(fp).size;
|
|
45
|
+
}
|
|
46
|
+
return sz;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ensureWinBun() {
|
|
50
|
+
if (fs.existsSync(BUN_WIN_CACHE)) {
|
|
51
|
+
const stat = fs.statSync(BUN_WIN_CACHE);
|
|
52
|
+
if (stat.size > 50 * 1024 * 1024) {
|
|
53
|
+
// Verify it's a PE file
|
|
54
|
+
const buf = Buffer.alloc(2);
|
|
55
|
+
const fd = fs.openSync(BUN_WIN_CACHE, 'r');
|
|
56
|
+
fs.readSync(fd, buf, 0, 2, 0);
|
|
57
|
+
fs.closeSync(fd);
|
|
58
|
+
if (buf[0] === 0x4D && buf[1] === 0x5A) return BUN_WIN_CACHE;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
log('Downloading Windows Bun binary...');
|
|
62
|
+
execSync(
|
|
63
|
+
`~/.bun/bin/bun build --compile --target=bun-windows-x64 --outfile=/tmp/_bun_trigger.exe /dev/null 2>/dev/null || true`,
|
|
64
|
+
{ stdio: 'inherit' }
|
|
65
|
+
);
|
|
66
|
+
if (fs.existsSync(BUN_WIN_CACHE)) return BUN_WIN_CACHE;
|
|
67
|
+
throw new Error(`Windows Bun binary not found at ${BUN_WIN_CACHE}. Run: bun build --compile --target=bun-windows-x64 --outfile=/tmp/x.exe /dev/null`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
log('Output: ' + out);
|
|
71
|
+
rmrf(out);
|
|
72
|
+
fs.mkdirSync(out, { recursive: true });
|
|
73
|
+
|
|
74
|
+
log('Building JS bundle...');
|
|
75
|
+
execSync(
|
|
76
|
+
`~/.bun/bin/bun build --minify --target=bun --outfile=${path.join(out, 'bundle.js')} ${path.join(src, 'portable-entry.js')}`,
|
|
77
|
+
{ stdio: 'inherit', cwd: src }
|
|
78
|
+
);
|
|
79
|
+
const bundleSize = fs.statSync(path.join(out, 'bundle.js')).size;
|
|
80
|
+
log(`Bundle: ${Math.round(bundleSize / 1024)}KB`);
|
|
81
|
+
|
|
82
|
+
log('Locating Windows Bun binary...');
|
|
83
|
+
const bunWinPath = ensureWinBun();
|
|
84
|
+
log(`Windows Bun: ${bunWinPath} (${Math.round(fs.statSync(bunWinPath).size / 1024 / 1024)}MB)`);
|
|
85
|
+
|
|
86
|
+
log('Injecting bundle into Windows Bun exe (.bun PE section)...');
|
|
87
|
+
const injector = path.join(src, 'scripts', 'inject-pe-section.py');
|
|
88
|
+
execFileSync('python3', [
|
|
89
|
+
injector,
|
|
90
|
+
bunWinPath,
|
|
91
|
+
path.join(out, 'bundle.js'),
|
|
92
|
+
path.join(out, 'agentgui.exe')
|
|
93
|
+
], { stdio: 'inherit' });
|
|
94
|
+
fs.unlinkSync(path.join(out, 'bundle.js'));
|
|
95
|
+
|
|
96
|
+
const exeSize = fs.statSync(path.join(out, 'agentgui.exe')).size;
|
|
97
|
+
// Verify PE magic
|
|
98
|
+
const buf = Buffer.alloc(2);
|
|
99
|
+
const fd = fs.openSync(path.join(out, 'agentgui.exe'), 'r');
|
|
100
|
+
fs.readSync(fd, buf, 0, 2, 0);
|
|
101
|
+
fs.closeSync(fd);
|
|
102
|
+
if (buf[0] !== 0x4D || buf[1] !== 0x5A) throw new Error('Output exe is not a valid PE file (bad MZ magic)');
|
|
103
|
+
log(`Exe compiled: ${Math.round(exeSize / 1024 / 1024)}MB (valid PE)`);
|
|
104
|
+
|
|
105
|
+
log('Copying static files...');
|
|
106
|
+
copyDir(path.join(src, 'static'), path.join(out, 'static'));
|
|
107
|
+
|
|
108
|
+
const destNm = path.join(out, 'node_modules');
|
|
109
|
+
|
|
110
|
+
log('Copying @huggingface/transformers (dist + win32 natives)...');
|
|
111
|
+
const hfSrc = path.join(nm, '@huggingface', 'transformers');
|
|
112
|
+
const hfDest = path.join(destNm, '@huggingface', 'transformers');
|
|
113
|
+
copyFile(path.join(hfSrc, 'package.json'), path.join(hfDest, 'package.json'));
|
|
114
|
+
copyDir(path.join(hfSrc, 'dist'), path.join(hfDest, 'dist'));
|
|
115
|
+
const hfNmSrc = path.join(hfSrc, 'node_modules');
|
|
116
|
+
const hfNmDest = path.join(hfDest, 'node_modules');
|
|
117
|
+
const hfOnnxSrc = path.join(hfNmSrc, 'onnxruntime-node');
|
|
118
|
+
const hfOnnxDest = path.join(hfNmDest, 'onnxruntime-node');
|
|
119
|
+
copyFile(path.join(hfOnnxSrc, 'package.json'), path.join(hfOnnxDest, 'package.json'));
|
|
120
|
+
copyDir(path.join(hfOnnxSrc, 'dist'), path.join(hfOnnxDest, 'dist'));
|
|
121
|
+
copyDir(path.join(hfOnnxSrc, 'lib'), path.join(hfOnnxDest, 'lib'));
|
|
122
|
+
copyDir(path.join(hfOnnxSrc, 'bin', 'napi-v3', 'win32', 'x64'), path.join(hfOnnxDest, 'bin', 'napi-v3', 'win32', 'x64'));
|
|
123
|
+
copyDir(path.join(hfNmSrc, 'onnxruntime-common'), path.join(hfNmDest, 'onnxruntime-common'));
|
|
124
|
+
|
|
125
|
+
log('Copying webtalk...');
|
|
126
|
+
copyDir(path.join(nm, 'webtalk'), path.join(destNm, 'webtalk'));
|
|
127
|
+
|
|
128
|
+
log('Copying @anthropic-ai/claude-code ripgrep (win32)...');
|
|
129
|
+
const claudeSrc = path.join(nm, '@anthropic-ai', 'claude-code');
|
|
130
|
+
const claudeDest = path.join(destNm, '@anthropic-ai', 'claude-code');
|
|
131
|
+
copyFile(path.join(claudeSrc, 'package.json'), path.join(claudeDest, 'package.json'));
|
|
132
|
+
copyDir(path.join(claudeSrc, 'vendor', 'ripgrep', 'x64-win32'), path.join(claudeDest, 'vendor', 'ripgrep', 'x64-win32'));
|
|
133
|
+
|
|
134
|
+
log('Creating data directory...');
|
|
135
|
+
fs.mkdirSync(path.join(out, 'data'), { recursive: true });
|
|
136
|
+
|
|
137
|
+
fs.writeFileSync(path.join(out, 'README.txt'), [
|
|
138
|
+
'# AgentGUI Portable',
|
|
139
|
+
'',
|
|
140
|
+
'No installation required. Double-click agentgui.exe to start.',
|
|
141
|
+
'',
|
|
142
|
+
'Web interface: http://localhost:3000/gm/',
|
|
143
|
+
'',
|
|
144
|
+
'Data is stored in the data/ folder next to the executable.',
|
|
145
|
+
'',
|
|
146
|
+
'Requirements: None - fully self-contained.',
|
|
147
|
+
].join('\n'));
|
|
148
|
+
|
|
149
|
+
const totalMB = Math.round(sizeOf(out) / 1024 / 1024);
|
|
150
|
+
log(`Build complete! Total: ${totalMB}MB Output: ${out}`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
|
|
6
|
+
function getExeDir() {
|
|
7
|
+
if (process.execPath && process.execPath !== process.argv[0]) {
|
|
8
|
+
return path.dirname(process.execPath);
|
|
9
|
+
}
|
|
10
|
+
if (process.argv[1]) {
|
|
11
|
+
const argv1 = path.resolve(process.argv[1]);
|
|
12
|
+
if (fs.existsSync(argv1) || fs.existsSync(argv1 + '.exe')) {
|
|
13
|
+
return path.dirname(argv1);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return process.cwd();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const exeDir = getExeDir();
|
|
20
|
+
const dataDir = path.join(exeDir, 'data');
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
23
|
+
|
|
24
|
+
process.env.PORTABLE_DATA_DIR = dataDir;
|
|
25
|
+
process.env.PORTABLE_EXE_DIR = exeDir;
|
|
26
|
+
process.env.BASE_URL = process.env.BASE_URL || '/gm';
|
|
27
|
+
process.env.PORT = process.env.PORT || '3000';
|
|
28
|
+
process.env.STARTUP_CWD = process.env.STARTUP_CWD || exeDir;
|
|
29
|
+
|
|
30
|
+
const port = process.env.PORT;
|
|
31
|
+
const baseUrl = process.env.BASE_URL;
|
|
32
|
+
const url = `http://localhost:${port}${baseUrl}/`;
|
|
33
|
+
|
|
34
|
+
console.log(`[AgentGUI Portable] Exe directory: ${exeDir}`);
|
|
35
|
+
console.log(`[AgentGUI Portable] Data directory: ${dataDir}`);
|
|
36
|
+
console.log(`[AgentGUI Portable] Server starting on ${url}`);
|
|
37
|
+
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
const cmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
40
|
+
spawn(cmd, [url], { shell: true, detached: true, stdio: 'ignore' }).unref();
|
|
41
|
+
}, 1500);
|
|
42
|
+
|
|
43
|
+
await import('./server.js');
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Injects a .bun section into a Windows PE executable (bun standalone format).
|
|
4
|
+
Usage: inject-pe-section.py <bun-exe> <bundle-bytes> <output-exe>
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import struct
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
def align_up(value, alignment):
|
|
12
|
+
return (value + alignment - 1) & ~(alignment - 1)
|
|
13
|
+
|
|
14
|
+
def read_u16(data, offset): return struct.unpack_from('<H', data, offset)[0]
|
|
15
|
+
def read_u32(data, offset): return struct.unpack_from('<I', data, offset)[0]
|
|
16
|
+
def read_u64(data, offset): return struct.unpack_from('<Q', data, offset)[0]
|
|
17
|
+
def write_u16(data, offset, val): struct.pack_into('<H', data, offset, val)
|
|
18
|
+
def write_u32(data, offset, val): struct.pack_into('<I', data, offset, val)
|
|
19
|
+
def write_u64(data, offset, val): struct.pack_into('<Q', data, offset, val)
|
|
20
|
+
|
|
21
|
+
def add_bun_section(pe_bytes, bundle_bytes):
|
|
22
|
+
data = bytearray(pe_bytes)
|
|
23
|
+
|
|
24
|
+
# PE header offset at 0x3C
|
|
25
|
+
e_lfanew = read_u32(data, 0x3C)
|
|
26
|
+
pe_sig = data[e_lfanew:e_lfanew+4]
|
|
27
|
+
assert pe_sig == b'PE\0\0', f"Not a PE file: {pe_sig!r}"
|
|
28
|
+
|
|
29
|
+
coff_offset = e_lfanew + 4
|
|
30
|
+
machine = read_u16(data, coff_offset)
|
|
31
|
+
num_sections = read_u16(data, coff_offset + 2)
|
|
32
|
+
size_of_optional = read_u16(data, coff_offset + 16)
|
|
33
|
+
|
|
34
|
+
opt_offset = coff_offset + 20
|
|
35
|
+
magic = read_u16(data, opt_offset)
|
|
36
|
+
assert magic == 0x20B, f"Expected PE32+ (0x20B), got 0x{magic:X}"
|
|
37
|
+
|
|
38
|
+
# Optional header fields (PE32+)
|
|
39
|
+
file_alignment = read_u32(data, opt_offset + 36)
|
|
40
|
+
section_alignment = read_u32(data, opt_offset + 32)
|
|
41
|
+
size_of_image = read_u32(data, opt_offset + 56)
|
|
42
|
+
size_of_headers = read_u32(data, opt_offset + 60)
|
|
43
|
+
checksum_offset = opt_offset + 64
|
|
44
|
+
# Data directories start at opt_offset + 112 for PE32+
|
|
45
|
+
# Security directory is entry 4
|
|
46
|
+
security_dd_offset = opt_offset + 112 + 4 * 8 # entry 4, each entry 8 bytes
|
|
47
|
+
|
|
48
|
+
# Section headers follow the optional header
|
|
49
|
+
section_headers_offset = opt_offset + size_of_optional
|
|
50
|
+
SECTION_HEADER_SIZE = 40
|
|
51
|
+
|
|
52
|
+
# Parse existing sections
|
|
53
|
+
sections = []
|
|
54
|
+
for i in range(num_sections):
|
|
55
|
+
off = section_headers_offset + i * SECTION_HEADER_SIZE
|
|
56
|
+
name = data[off:off+8]
|
|
57
|
+
virtual_size = read_u32(data, off + 8)
|
|
58
|
+
virtual_addr = read_u32(data, off + 12)
|
|
59
|
+
raw_size = read_u32(data, off + 16)
|
|
60
|
+
raw_ptr = read_u32(data, off + 20)
|
|
61
|
+
chars = read_u32(data, off + 36)
|
|
62
|
+
sections.append({
|
|
63
|
+
'name': name, 'virtual_size': virtual_size, 'virtual_addr': virtual_addr,
|
|
64
|
+
'raw_size': raw_size, 'raw_ptr': raw_ptr, 'chars': chars, 'off': off
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
if name == b'.bun\0\0\0\0':
|
|
68
|
+
raise ValueError("PE already has a .bun section")
|
|
69
|
+
|
|
70
|
+
assert num_sections < 96, "Too many sections"
|
|
71
|
+
|
|
72
|
+
# Check header space
|
|
73
|
+
new_sh_off = section_headers_offset + num_sections * SECTION_HEADER_SIZE
|
|
74
|
+
new_sh_end = new_sh_off + SECTION_HEADER_SIZE
|
|
75
|
+
first_raw = min((s['raw_ptr'] for s in sections if s['raw_size'] > 0), default=len(data))
|
|
76
|
+
new_size_of_headers = align_up(new_sh_end, file_alignment)
|
|
77
|
+
assert new_size_of_headers <= first_raw, f"Insufficient header space: need {new_size_of_headers}, first_raw={first_raw}"
|
|
78
|
+
|
|
79
|
+
# Find last file end and last va end
|
|
80
|
+
last_file_end = max((s['raw_ptr'] + s['raw_size'] for s in sections), default=0)
|
|
81
|
+
last_va_end = max(
|
|
82
|
+
(s['virtual_addr'] + align_up(max(s['virtual_size'], s['raw_size']), section_alignment)
|
|
83
|
+
for s in sections),
|
|
84
|
+
default=0
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
payload_len = len(bundle_bytes) + 8 # 8-byte u64 LE length prefix
|
|
88
|
+
raw_size_new = align_up(payload_len, file_alignment)
|
|
89
|
+
new_va = align_up(last_va_end, section_alignment)
|
|
90
|
+
new_raw = align_up(last_file_end, file_alignment)
|
|
91
|
+
|
|
92
|
+
# Extend data buffer
|
|
93
|
+
new_file_size = new_raw + raw_size_new
|
|
94
|
+
if len(data) < new_file_size:
|
|
95
|
+
data.extend(b'\0' * (new_file_size - len(data)))
|
|
96
|
+
else:
|
|
97
|
+
# Zero the new section area
|
|
98
|
+
data[new_raw:new_file_size] = b'\0' * raw_size_new
|
|
99
|
+
|
|
100
|
+
# Write new section header
|
|
101
|
+
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
|
|
102
|
+
IMAGE_SCN_MEM_READ = 0x40000000
|
|
103
|
+
sh = bytearray(SECTION_HEADER_SIZE)
|
|
104
|
+
sh[0:8] = b'.bun\0\0\0\0'
|
|
105
|
+
struct.pack_into('<I', sh, 8, payload_len) # VirtualSize
|
|
106
|
+
struct.pack_into('<I', sh, 12, new_va) # VirtualAddress
|
|
107
|
+
struct.pack_into('<I', sh, 16, raw_size_new) # SizeOfRawData
|
|
108
|
+
struct.pack_into('<I', sh, 20, new_raw) # PointerToRawData
|
|
109
|
+
struct.pack_into('<I', sh, 36, IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ)
|
|
110
|
+
data[new_sh_off:new_sh_off + SECTION_HEADER_SIZE] = sh
|
|
111
|
+
|
|
112
|
+
# Write payload: u64 LE length + bundle bytes
|
|
113
|
+
write_u64(data, new_raw, len(bundle_bytes))
|
|
114
|
+
data[new_raw + 8: new_raw + 8 + len(bundle_bytes)] = bundle_bytes
|
|
115
|
+
|
|
116
|
+
# Update COFF: number_of_sections
|
|
117
|
+
write_u16(data, coff_offset + 2, num_sections + 1)
|
|
118
|
+
|
|
119
|
+
# Update optional header
|
|
120
|
+
if size_of_headers < new_size_of_headers:
|
|
121
|
+
write_u32(data, opt_offset + 60, new_size_of_headers)
|
|
122
|
+
|
|
123
|
+
# size_of_image: aligned end of new section
|
|
124
|
+
new_size_of_image = align_up(new_va + payload_len, section_alignment)
|
|
125
|
+
write_u32(data, opt_offset + 56, new_size_of_image)
|
|
126
|
+
|
|
127
|
+
# Clear security directory (signature invalidated)
|
|
128
|
+
data[security_dd_offset:security_dd_offset + 8] = b'\0' * 8
|
|
129
|
+
|
|
130
|
+
# Recompute PE checksum
|
|
131
|
+
data = recompute_checksum(data, checksum_offset)
|
|
132
|
+
|
|
133
|
+
return bytes(data)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def recompute_checksum(data, checksum_offset):
|
|
137
|
+
# Zero out the checksum field first
|
|
138
|
+
write_u32(data, checksum_offset, 0)
|
|
139
|
+
|
|
140
|
+
checksum = 0
|
|
141
|
+
# Process data as u16 pairs
|
|
142
|
+
for i in range(0, len(data) - 1, 2):
|
|
143
|
+
val = struct.unpack_from('<H', data, i)[0]
|
|
144
|
+
checksum += val
|
|
145
|
+
if checksum > 0xFFFFFFFF:
|
|
146
|
+
checksum = (checksum & 0xFFFFFFFF) + (checksum >> 32)
|
|
147
|
+
|
|
148
|
+
if len(data) % 2:
|
|
149
|
+
checksum += data[-1]
|
|
150
|
+
|
|
151
|
+
# Fold to 32 bits
|
|
152
|
+
checksum = (checksum & 0xFFFF) + (checksum >> 16)
|
|
153
|
+
checksum += checksum >> 16
|
|
154
|
+
checksum &= 0xFFFF
|
|
155
|
+
checksum += len(data)
|
|
156
|
+
|
|
157
|
+
write_u32(data, checksum_offset, checksum)
|
|
158
|
+
return data
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == '__main__':
|
|
162
|
+
if len(sys.argv) != 4:
|
|
163
|
+
print(f"Usage: {sys.argv[0]} <bun-exe> <bundle-js> <output-exe>")
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
|
|
166
|
+
bun_path, bundle_path, out_path = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
167
|
+
|
|
168
|
+
with open(bun_path, 'rb') as f:
|
|
169
|
+
pe_bytes = f.read()
|
|
170
|
+
|
|
171
|
+
with open(bundle_path, 'rb') as f:
|
|
172
|
+
bundle_bytes = f.read()
|
|
173
|
+
|
|
174
|
+
print(f"Bun exe: {len(pe_bytes)//1024//1024}MB, Bundle: {len(bundle_bytes)//1024}KB")
|
|
175
|
+
|
|
176
|
+
result = add_bun_section(pe_bytes, bundle_bytes)
|
|
177
|
+
|
|
178
|
+
with open(out_path, 'wb') as f:
|
|
179
|
+
f.write(result)
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
os.chmod(out_path, 0o755)
|
|
183
|
+
except (PermissionError, OSError):
|
|
184
|
+
pass
|
|
185
|
+
print(f"Output: {len(result)//1024//1024}MB -> {out_path}")
|