@erik9994857/cag 1.0.2 → 2.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.
@@ -1,499 +1,929 @@
1
- var path = require("path");
2
- var fs = require("fs");
3
- var childProcess = require("child_process");
4
-
5
- function ExeBuilder(engine) {
6
- this.engine = engine;
7
- this.info = engine.getInfo();
8
- this.projectRoot = engine.projectRoot;
9
- this.outputDir = path.join(this.projectRoot, "dist");
10
- this.tempDir = path.join(this.outputDir, ".build-temp");
11
- this.projectName = this.info.ID.split(".")[1] || this.info.ID.split(".")[0] || "cag-app";
12
- }
13
-
14
- ExeBuilder.prototype.build = function (options) {
15
- options = options || {};
16
- var platform = options.platform || this.detectPlatform();
17
- var arch = options.arch || "x64";
18
- var nodeVersion = options.nodeVersion || "node18";
19
-
20
- this.ensureDirectories();
21
- this.ensurePkgInstalled();
22
-
23
- var entryFile = this.generateEntryPoint();
24
- var packageFile = this.generatePackageJson();
25
-
26
- var target = nodeVersion + "-" + platform + "-" + arch;
27
- var outputName = this.projectName;
28
-
29
- if (platform === "win") {
30
- outputName += ".exe";
31
- } else if (platform === "macos") {
32
- outputName += ".app";
33
- }
34
-
35
- var outputPath = path.join(this.outputDir, outputName);
36
-
37
- this.runPkg(entryFile, outputPath, target);
38
- this.cleanup();
39
-
40
- return {
41
- outputPath: outputPath,
42
- outputName: outputName,
43
- platform: platform,
44
- arch: arch,
45
- target: target,
46
- size: this.getFileSize(outputPath)
47
- };
48
- };
49
-
50
- ExeBuilder.prototype.buildAll = function (options) {
51
- options = options || {};
52
- var nodeVersion = options.nodeVersion || "node18";
53
- var results = [];
54
-
55
- var targets = [
56
- { platform: "win", arch: "x64", ext: ".exe" },
57
- { platform: "macos", arch: "x64", ext: ".app" },
58
- { platform: "linux", arch: "x64", ext: "" }
59
- ];
60
-
61
- this.ensureDirectories();
62
- this.ensurePkgInstalled();
63
-
64
- var entryFile = this.generateEntryPoint();
65
- this.generatePackageJson();
66
-
67
- for (var i = 0; i < targets.length; i++) {
68
- var t = targets[i];
69
- var target = nodeVersion + "-" + t.platform + "-" + t.arch;
70
- var outputName = this.projectName + "-" + t.platform + "-" + t.arch + t.ext;
71
- var outputPath = path.join(this.outputDir, outputName);
72
-
73
- try {
74
- this.runPkg(entryFile, outputPath, target);
75
- results.push({
76
- outputPath: outputPath,
77
- outputName: outputName,
78
- platform: t.platform,
79
- arch: t.arch,
80
- target: target,
81
- size: this.getFileSize(outputPath),
82
- success: true
83
- });
84
- } catch (e) {
85
- results.push({
86
- outputName: outputName,
87
- platform: t.platform,
88
- arch: t.arch,
89
- target: target,
90
- success: false,
91
- error: e.message
92
- });
93
- }
94
- }
95
-
96
- this.cleanup();
97
- return results;
98
- };
99
-
100
- ExeBuilder.prototype.ensureDirectories = function () {
101
- if (!fs.existsSync(this.outputDir)) {
102
- fs.mkdirSync(this.outputDir, { recursive: true });
103
- }
104
- if (!fs.existsSync(this.tempDir)) {
105
- fs.mkdirSync(this.tempDir, { recursive: true });
106
- }
107
- };
108
-
109
- ExeBuilder.prototype.ensurePkgInstalled = function () {
110
- var found = this.findPkgBin();
111
- if (found !== "npx pkg") return;
112
-
113
- var npxCheck = null;
114
- try {
115
- npxCheck = childProcess.execSync("npx pkg --version", { stdio: "pipe" });
116
- } catch (e) {
117
- npxCheck = null;
118
- }
119
-
120
- if (npxCheck) return;
121
-
122
- try {
123
- childProcess.execSync("npm install -g pkg@5.8.1", {
124
- cwd: this.projectRoot,
125
- stdio: "inherit"
126
- });
127
- } catch (e) {
128
- try {
129
- childProcess.execSync("npm install pkg@5.8.1", {
130
- cwd: this.projectRoot,
131
- stdio: "inherit"
132
- });
133
- } catch (e2) {
134
- throw new Error(
135
- "Failed to install pkg. Run manually: npm install -g pkg"
136
- );
137
- }
138
- }
139
- };
140
-
141
- ExeBuilder.prototype.generateEntryPoint = function () {
142
- var codeFiles = this.engine.getCodeFiles();
143
- var resourceMap = this.engine.getResourceMap();
144
- var dependencies = this.engine.getDependencies();
145
-
146
- var embeddedCode = {};
147
- for (var i = 0; i < codeFiles.length; i++) {
148
- var fileName = path.basename(codeFiles[i]);
149
- var content = fs.readFileSync(codeFiles[i], "utf-8");
150
- embeddedCode[fileName] = content;
151
- }
152
-
153
- var embeddedResources = {};
154
- var resourceNames = Object.keys(resourceMap);
155
- for (var j = 0; j < resourceNames.length; j++) {
156
- var name = resourceNames[j];
157
- var res = resourceMap[name];
158
- var entry = { name: name, paired: res.paired, modelData: null, textureData: null };
159
-
160
- if (res.model && fs.existsSync(res.model)) {
161
- try {
162
- entry.modelData = JSON.parse(fs.readFileSync(res.model, "utf-8"));
163
- } catch (e) {
164
- entry.modelData = null;
165
- }
166
- }
167
-
168
- if (res.texture && fs.existsSync(res.texture)) {
169
- entry.textureData = fs.readFileSync(res.texture).toString("base64");
170
- entry.textureExt = path.extname(res.texture).toLowerCase();
171
- }
172
-
173
- embeddedResources[name] = entry;
174
- }
175
-
176
- var mainCagContent = "";
177
- var mainCagPath = path.join(this.projectRoot, "main.cag");
178
- if (fs.existsSync(mainCagPath)) {
179
- mainCagContent = fs.readFileSync(mainCagPath, "utf-8");
180
- }
181
-
182
- var infoCagContent = "";
183
- var infoCagPath = path.join(this.projectRoot, "info.cag");
184
- if (fs.existsSync(infoCagPath)) {
185
- infoCagContent = fs.readFileSync(infoCagPath, "utf-8");
186
- }
187
-
188
- var entrySource = "";
189
- entrySource += "var PROJECT_INFO = " + JSON.stringify(this.info) + ";\n";
190
- entrySource += "var PROJECT_DEPENDENCIES = " + JSON.stringify(dependencies) + ";\n";
191
- entrySource += "var EMBEDDED_CODE = " + JSON.stringify(embeddedCode) + ";\n";
192
- entrySource += "var EMBEDDED_RESOURCES = " + JSON.stringify(embeddedResources) + ";\n";
193
- entrySource += "var MAIN_CAG = " + JSON.stringify(mainCagContent) + ";\n";
194
- entrySource += "var INFO_CAG = " + JSON.stringify(infoCagContent) + ";\n";
195
- entrySource += "\n";
196
- entrySource += this.getRuntimeSource();
197
-
198
- var entryPath = path.join(this.tempDir, "entry.js");
199
- fs.writeFileSync(entryPath, entrySource);
200
-
201
- return entryPath;
202
- };
203
-
204
- ExeBuilder.prototype.generatePackageJson = function () {
205
- var pkg = {
206
- name: this.projectName,
207
- version: this.info.version || "1.0.0",
208
- main: "entry.js",
209
- bin: "entry.js",
210
- pkg: {
211
- assets: [],
212
- targets: ["node18"],
213
- outputPath: this.outputDir
214
- }
215
- };
216
-
217
- var pkgPath = path.join(this.tempDir, "package.json");
218
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
219
- return pkgPath;
220
- };
221
-
222
- ExeBuilder.prototype.getRuntimeSource = function () {
223
- var src = "";
224
-
225
- src += "function CagRuntime() {\n";
226
- src += " this.info = PROJECT_INFO;\n";
227
- src += " this.dependencies = PROJECT_DEPENDENCIES;\n";
228
- src += " this.code = EMBEDDED_CODE;\n";
229
- src += " this.resources = EMBEDDED_RESOURCES;\n";
230
- src += " this.loadedModels = {};\n";
231
- src += " this.worlds = {};\n";
232
- src += " this.activeWorld = null;\n";
233
- src += " this.outputs = [];\n";
234
- src += "}\n\n";
235
-
236
- src += "CagRuntime.prototype.run = function () {\n";
237
- src += " this.print('========================================');\n";
238
- src += " this.print(' ' + this.info.ID);\n";
239
- src += " this.print(' Version: ' + this.info.version);\n";
240
- src += " this.print('========================================');\n";
241
- src += " this.print('');\n";
242
- src += " this.print('Dependencies: ' + this.dependencies.join(', '));\n";
243
- src += " this.print('Code files: ' + Object.keys(this.code).length);\n";
244
- src += " this.print('Resources: ' + Object.keys(this.resources).length);\n";
245
- src += " this.print('');\n";
246
- src += " var fileNames = Object.keys(this.code);\n";
247
- src += " for (var i = 0; i < fileNames.length; i++) {\n";
248
- src += " this.executeFile(fileNames[i], this.code[fileNames[i]]);\n";
249
- src += " }\n";
250
- src += " this.print('');\n";
251
- src += " this.print('Execution complete.');\n";
252
- src += " this.print('');\n";
253
- src += " this.print('Press Enter to exit...');\n";
254
- src += " this.waitForExit();\n";
255
- src += "};\n\n";
256
-
257
- src += "CagRuntime.prototype.executeFile = function (fileName, content) {\n";
258
- src += " this.print('Executing: ' + fileName);\n";
259
- src += " var lines = content.split('\\n');\n";
260
- src += " var imports = [];\n";
261
- src += " for (var i = 0; i < lines.length; i++) {\n";
262
- src += " var line = lines[i].trim();\n";
263
- src += " if (line === '') continue;\n";
264
- src += " if (line.startsWith('Import ')) {\n";
265
- src += " var impName = line.substring(7).trim();\n";
266
- src += " imports.push(impName);\n";
267
- src += " this.print(' Loaded: ' + impName);\n";
268
- src += " continue;\n";
269
- src += " }\n";
270
- src += " if (line.startsWith('{')) {\n";
271
- src += " this.executeFunction(line, imports);\n";
272
- src += " }\n";
273
- src += " }\n";
274
- src += "};\n\n";
275
-
276
- src += "CagRuntime.prototype.executeFunction = function (line, imports) {\n";
277
- src += " var inner = line.substring(1).trim();\n";
278
- src += " var parts = inner.split(/\\s+/);\n";
279
- src += " if (parts[0] !== 'function' || parts[1] !== 'use') return;\n";
280
- src += " var apiParts = [];\n";
281
- src += " var idx = 2;\n";
282
- src += " var apiName = parts[idx];\n";
283
- src += " if (idx + 2 < parts.length && parts[idx + 1] === undefined) idx++;\n";
284
- src += " var combined = parts.slice(2).join(' ');\n";
285
- src += " var apis = ['Model.API', 'WorldGen.API', 'Pull.API'];\n";
286
- src += " var foundApi = null;\n";
287
- src += " for (var a = 0; a < apis.length; a++) {\n";
288
- src += " if (combined.indexOf(apis[a]) === 0) {\n";
289
- src += " foundApi = apis[a];\n";
290
- src += " break;\n";
291
- src += " }\n";
292
- src += " }\n";
293
- src += " if (!foundApi) {\n";
294
- src += " this.print(' ERROR: Unknown API in: ' + line);\n";
295
- src += " return;\n";
296
- src += " }\n";
297
- src += " var afterApi = combined.substring(foundApi.length).trim();\n";
298
- src += " var actionParts = afterApi.split(/\\s+/);\n";
299
- src += " var action = actionParts[0] || '';\n";
300
- src += " var params = actionParts.slice(1);\n";
301
- src += " if (foundApi === 'Model.API') this.execModelAPI(action, params);\n";
302
- src += " else if (foundApi === 'WorldGen.API') this.execWorldGenAPI(action, params);\n";
303
- src += " else if (foundApi === 'Pull.API') this.execPullAPI(action, params);\n";
304
- src += "};\n\n";
305
-
306
- src += "CagRuntime.prototype.execModelAPI = function (action, params) {\n";
307
- src += " if (action === 'UseModel' || action === '1') {\n";
308
- src += " var modelName = params[0] || 'unknown';\n";
309
- src += " if (this.resources[modelName]) {\n";
310
- src += " this.loadedModels[modelName] = this.resources[modelName];\n";
311
- src += " this.print(' > Model loaded: ' + modelName + ' [paired: ' + this.resources[modelName].paired + ']');\n";
312
- src += " } else {\n";
313
- src += " this.loadedModels[modelName] = { name: modelName, paired: false };\n";
314
- src += " this.print(' > Model loaded (default): ' + modelName);\n";
315
- src += " }\n";
316
- src += " } else if (action === 'RemoveModel' || action === '2') {\n";
317
- src += " var rName = params[0] || 'unknown';\n";
318
- src += " delete this.loadedModels[rName];\n";
319
- src += " this.print(' > Model removed: ' + rName);\n";
320
- src += " } else if (action === 'ListModels' || action === '3') {\n";
321
- src += " var names = Object.keys(this.loadedModels);\n";
322
- src += " this.print(' > Loaded models: ' + (names.length > 0 ? names.join(', ') : 'none'));\n";
323
- src += " } else {\n";
324
- src += " this.print(' > Model.API.' + action + '(' + params.join(', ') + ')');\n";
325
- src += " }\n";
326
- src += "};\n\n";
327
-
328
- src += "CagRuntime.prototype.execWorldGenAPI = function (action, params) {\n";
329
- src += " if (action === 'generate' || action === '1') {\n";
330
- src += " var topLayer = null;\n";
331
- src += " for (var i = 0; i < params.length; i++) {\n";
332
- src += " if (params[i] === '=' && i + 1 < params.length) {\n";
333
- src += " topLayer = params[i + 1];\n";
334
- src += " }\n";
335
- src += " if (params[i] === 'top' && i + 3 < params.length && params[i + 1] === 'layer') {\n";
336
- src += " topLayer = params[i + 3] || params[i + 2];\n";
337
- src += " }\n";
338
- src += " }\n";
339
- src += " var worldId = 'world_' + Date.now();\n";
340
- src += " this.worlds[worldId] = { id: worldId, topLayer: topLayer, generated: true };\n";
341
- src += " this.activeWorld = worldId;\n";
342
- src += " this.print(' > World generated: ' + worldId + (topLayer ? ' (top: ' + topLayer + ')' : ''));\n";
343
- src += " } else if (action === 'destroy' || action === '2') {\n";
344
- src += " this.print(' > World destroyed');\n";
345
- src += " } else if (action === 'modify' || action === '3') {\n";
346
- src += " this.print(' > World modified: ' + params.join(' '));\n";
347
- src += " } else {\n";
348
- src += " this.print(' > WorldGen.API.' + action + '(' + params.join(', ') + ')');\n";
349
- src += " }\n";
350
- src += "};\n\n";
351
-
352
- src += "CagRuntime.prototype.execPullAPI = function (action, params) {\n";
353
- src += " if (action === 'retrieve' || action === '1') {\n";
354
- src += " var modelId = params[0] || 'unknown';\n";
355
- src += " this.print(' > Retrieved model: ' + modelId);\n";
356
- src += " } else if (action === 'search' || action === '2') {\n";
357
- src += " this.print(' > Search results for: ' + (params[0] || ''));\n";
358
- src += " } else if (action === 'list' || action === '3') {\n";
359
- src += " this.print(' > Listing available models');\n";
360
- src += " } else {\n";
361
- src += " this.print(' > Pull.API.' + action + '(' + params.join(', ') + ')');\n";
362
- src += " }\n";
363
- src += "};\n\n";
364
-
365
- src += "CagRuntime.prototype.print = function (text) {\n";
366
- src += " process.stdout.write(text + '\\n');\n";
367
- src += " this.outputs.push(text);\n";
368
- src += "};\n\n";
369
-
370
- src += "CagRuntime.prototype.waitForExit = function () {\n";
371
- src += " if (process.stdin.isTTY) {\n";
372
- src += " process.stdin.setRawMode(true);\n";
373
- src += " process.stdin.resume();\n";
374
- src += " process.stdin.once('data', function () {\n";
375
- src += " process.exit(0);\n";
376
- src += " });\n";
377
- src += " } else {\n";
378
- src += " setTimeout(function () { process.exit(0); }, 100);\n";
379
- src += " }\n";
380
- src += "};\n\n";
381
-
382
- src += "var runtime = new CagRuntime();\n";
383
- src += "runtime.run();\n";
384
-
385
- return src;
386
- };
387
-
388
- ExeBuilder.prototype.runPkg = function (entryFile, outputPath, target) {
389
- var pkgBin = this.findPkgBin();
390
- var cmd;
391
-
392
- if (pkgBin === "npx pkg") {
393
- cmd = 'npx pkg "' + entryFile + '" --target ' + target + ' --output "' + outputPath + '"';
394
- } else {
395
- cmd = '"' + pkgBin + '" "' + entryFile + '" --target ' + target + ' --output "' + outputPath + '"';
396
- }
397
-
398
- try {
399
- childProcess.execSync(cmd, {
400
- cwd: this.tempDir,
401
- stdio: "inherit",
402
- shell: true
403
- });
404
- } catch (e) {
405
- throw new Error("pkg build failed for target " + target + ": " + e.message);
406
- }
407
-
408
- if (!fs.existsSync(outputPath)) {
409
- throw new Error("Build completed but output file not found: " + outputPath);
410
- }
411
- };
412
-
413
- ExeBuilder.prototype.findPkgBin = function () {
414
- var projectLocal = path.join(this.projectRoot, "node_modules", ".bin", "pkg");
415
- if (fs.existsSync(projectLocal)) return projectLocal;
416
- var projectLocalCmd = projectLocal + ".cmd";
417
- if (fs.existsSync(projectLocalCmd)) return projectLocalCmd;
418
-
419
- var cagRoot = path.join(__dirname, "..", "..");
420
- var cagLocal = path.join(cagRoot, "node_modules", ".bin", "pkg");
421
- if (fs.existsSync(cagLocal)) return cagLocal;
422
- var cagLocalCmd = cagLocal + ".cmd";
423
- if (fs.existsSync(cagLocalCmd)) return cagLocalCmd;
424
-
425
- var npmGlobalPrefix = "";
426
- try {
427
- npmGlobalPrefix = childProcess.execSync("npm config get prefix", {
428
- stdio: "pipe"
429
- }).toString().trim();
430
- } catch (e) {
431
- npmGlobalPrefix = "";
432
- }
433
-
434
- if (npmGlobalPrefix) {
435
- var isWin = process.platform === "win32";
436
- var globalBin;
437
- if (isWin) {
438
- globalBin = path.join(npmGlobalPrefix, "pkg.cmd");
439
- if (fs.existsSync(globalBin)) return globalBin;
440
- globalBin = path.join(npmGlobalPrefix, "node_modules", ".bin", "pkg.cmd");
441
- if (fs.existsSync(globalBin)) return globalBin;
442
- } else {
443
- globalBin = path.join(npmGlobalPrefix, "bin", "pkg");
444
- if (fs.existsSync(globalBin)) return globalBin;
445
- }
446
- }
447
-
448
- try {
449
- var whereResult = childProcess.execSync(
450
- process.platform === "win32" ? "where pkg 2>nul" : "which pkg 2>/dev/null",
451
- { stdio: "pipe" }
452
- ).toString().trim().split("\n")[0].trim();
453
- if (whereResult && fs.existsSync(whereResult)) return whereResult;
454
- } catch (e) {}
455
-
456
- return "npx pkg";
457
- };
458
-
459
- ExeBuilder.prototype.cleanup = function () {
460
- this.removeDirectory(this.tempDir);
461
- };
462
-
463
- ExeBuilder.prototype.removeDirectory = function (dir) {
464
- if (!fs.existsSync(dir)) return;
465
-
466
- var entries = fs.readdirSync(dir, { withFileTypes: true });
467
-
468
- for (var i = 0; i < entries.length; i++) {
469
- var fullPath = path.join(dir, entries[i].name);
470
- if (entries[i].isDirectory()) {
471
- this.removeDirectory(fullPath);
472
- } else {
473
- fs.unlinkSync(fullPath);
474
- }
475
- }
476
-
477
- fs.rmdirSync(dir);
478
- };
479
-
480
- ExeBuilder.prototype.getFileSize = function (filePath) {
481
- if (!fs.existsSync(filePath)) return 0;
482
- var stats = fs.statSync(filePath);
483
- return stats.size;
484
- };
485
-
486
- ExeBuilder.prototype.formatSize = function (bytes) {
487
- if (bytes < 1024) return bytes + " B";
488
- if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
489
- return (bytes / (1024 * 1024)).toFixed(1) + " MB";
490
- };
491
-
492
- ExeBuilder.prototype.detectPlatform = function () {
493
- var p = process.platform;
494
- if (p === "win32") return "win";
495
- if (p === "darwin") return "macos";
496
- return "linux";
497
- };
498
-
499
- module.exports = ExeBuilder;
1
+ var path = require("path");
2
+ var fs = require("fs");
3
+ var childProcess = require("child_process");
4
+
5
+ function ExeBuilder(engine) {
6
+ this.engine = engine;
7
+ this.info = engine.getInfo();
8
+ this.projectRoot = engine.projectRoot;
9
+ this.outputDir = path.join(this.projectRoot, "dist");
10
+ this.tempDir = path.join(this.outputDir, ".cag-build");
11
+ this.projectName = this.info.ID.split(".")[1] || this.info.ID.split(".")[0] || "cag-app";
12
+ }
13
+
14
+ ExeBuilder.prototype.build = function (options) {
15
+ options = options || {};
16
+ var platform = options.platform || this.detectPlatform();
17
+ var arch = options.arch || "x64";
18
+
19
+ this.ensureDirectories();
20
+ var html = this.getRendererSource();
21
+ var exeName, exePath;
22
+
23
+ if (platform === "win") {
24
+ var iconPath = path.join(this.tempDir, "icon.ico");
25
+ this.generateIcon(iconPath);
26
+ var csSource = this.generateCSharpSource(html);
27
+ var csPath = path.join(this.tempDir, "CaGLauncher.cs");
28
+ fs.writeFileSync(csPath, csSource);
29
+ exeName = this.projectName + ".exe";
30
+ exePath = path.join(this.outputDir, exeName);
31
+ this.compileCSharp(csPath, exePath, iconPath);
32
+ } else if (platform === "macos") {
33
+ exeName = this.projectName + ".app";
34
+ exePath = path.join(this.outputDir, exeName);
35
+ this.generateMacApp(html, exePath);
36
+ } else {
37
+ exeName = this.projectName;
38
+ exePath = path.join(this.outputDir, exeName);
39
+ this.generateLinuxApp(html, exePath);
40
+ }
41
+
42
+ this.cleanup();
43
+
44
+ return {
45
+ outputPath: exePath,
46
+ outputDir: this.outputDir,
47
+ outputName: exeName,
48
+ platform: platform,
49
+ arch: arch,
50
+ size: this.getFileSize(exePath) || this.getDirectorySize(exePath),
51
+ success: true
52
+ };
53
+ };
54
+
55
+ ExeBuilder.prototype.buildAll = function () {
56
+ var results = [];
57
+ var targets = [
58
+ { platform: "win", arch: "x64", ext: ".exe" },
59
+ { platform: "macos", arch: "x64", ext: ".app" },
60
+ { platform: "linux", arch: "x64", ext: "" }
61
+ ];
62
+
63
+ this.ensureDirectories();
64
+ var html = this.getRendererSource();
65
+
66
+ for (var i = 0; i < targets.length; i++) {
67
+ var t = targets[i];
68
+ try {
69
+ var exeName, exePath;
70
+ if (t.platform === "win") {
71
+ var iconPath = path.join(this.tempDir, "icon.ico");
72
+ this.generateIcon(iconPath);
73
+ var csSource = this.generateCSharpSource(html);
74
+ var csPath = path.join(this.tempDir, "CaGLauncher.cs");
75
+ fs.writeFileSync(csPath, csSource);
76
+ exeName = this.projectName + "-win-" + t.arch + t.ext;
77
+ exePath = path.join(this.outputDir, exeName);
78
+ this.compileCSharp(csPath, exePath, iconPath);
79
+ } else if (t.platform === "macos") {
80
+ exeName = this.projectName + "-macos-" + t.arch + t.ext;
81
+ exePath = path.join(this.outputDir, exeName);
82
+ this.generateMacApp(html, exePath);
83
+ } else {
84
+ exeName = this.projectName + "-linux-" + t.arch;
85
+ exePath = path.join(this.outputDir, exeName);
86
+ this.generateLinuxApp(html, exePath);
87
+ }
88
+ results.push({
89
+ outputPath: exePath,
90
+ outputDir: this.outputDir,
91
+ outputName: exeName,
92
+ platform: t.platform,
93
+ arch: t.arch,
94
+ size: this.getFileSize(exePath) || this.getDirectorySize(exePath),
95
+ success: true
96
+ });
97
+ } catch (e) {
98
+ results.push({
99
+ outputName: this.projectName + t.ext,
100
+ platform: t.platform,
101
+ arch: t.arch,
102
+ success: false,
103
+ error: e.message
104
+ });
105
+ }
106
+ }
107
+
108
+ this.cleanup();
109
+ return results;
110
+ };
111
+
112
+ ExeBuilder.prototype.ensureDirectories = function () {
113
+ if (!fs.existsSync(this.outputDir)) fs.mkdirSync(this.outputDir, { recursive: true });
114
+ if (fs.existsSync(this.tempDir)) this.removeDirectory(this.tempDir);
115
+ fs.mkdirSync(this.tempDir, { recursive: true });
116
+ };
117
+
118
+ ExeBuilder.prototype.generateIcon = function (outputPath) {
119
+ var width = 32;
120
+ var height = 32;
121
+ var header = Buffer.alloc(6);
122
+ header.writeUInt16LE(0, 0);
123
+ header.writeUInt16LE(1, 2);
124
+ header.writeUInt16LE(1, 4);
125
+ var bmpHeader = Buffer.alloc(40);
126
+ bmpHeader.writeUInt32LE(40, 0);
127
+ bmpHeader.writeInt32LE(width, 4);
128
+ bmpHeader.writeInt32LE(height * 2, 8);
129
+ bmpHeader.writeUInt16LE(1, 12);
130
+ bmpHeader.writeUInt16LE(32, 14);
131
+ bmpHeader.writeUInt32LE(0, 16);
132
+ bmpHeader.writeUInt32LE(width * height * 4, 20);
133
+ var pixelData = Buffer.alloc(width * height * 4);
134
+ for (var i = 0; i < width * height; i++) {
135
+ pixelData.writeUInt8(0xFF, i * 4);
136
+ pixelData.writeUInt8(0xFF, i * 4 + 1);
137
+ pixelData.writeUInt8(0xFF, i * 4 + 2);
138
+ pixelData.writeUInt8(0xFF, i * 4 + 3);
139
+ }
140
+ var andMask = Buffer.alloc(width * height / 8, 0x00);
141
+ var imageDataSize = bmpHeader.length + pixelData.length + andMask.length;
142
+ var dirEntry = Buffer.alloc(16);
143
+ dirEntry.writeUInt8(width, 0);
144
+ dirEntry.writeUInt8(height, 1);
145
+ dirEntry.writeUInt8(0, 2);
146
+ dirEntry.writeUInt8(0, 3);
147
+ dirEntry.writeUInt16LE(1, 4);
148
+ dirEntry.writeUInt16LE(32, 6);
149
+ dirEntry.writeUInt32LE(imageDataSize, 8);
150
+ dirEntry.writeUInt32LE(6 + 16, 12);
151
+ var ico = Buffer.concat([header, dirEntry, bmpHeader, pixelData, andMask]);
152
+ fs.writeFileSync(outputPath, ico);
153
+ };
154
+
155
+ ExeBuilder.prototype.generateCSharpSource = function (html) {
156
+ var b64 = Buffer.from(html, 'utf-8').toString('base64');
157
+ var cs = [
158
+ 'using System;',
159
+ 'using System.IO;',
160
+ 'using System.Diagnostics;',
161
+ 'using System.Text;',
162
+ '',
163
+ 'class CaGLauncher {',
164
+ ' static string DATA = "' + b64 + '";',
165
+ '',
166
+ ' [STAThread]',
167
+ ' static void Main() {',
168
+ ' string html = Encoding.UTF8.GetString(Convert.FromBase64String(DATA));',
169
+ ' string tempDir = Path.Combine(Path.GetTempPath(), "cag_" + Guid.NewGuid().ToString("N"));',
170
+ ' Directory.CreateDirectory(tempDir);',
171
+ ' string htmlPath = Path.Combine(tempDir, "game.html");',
172
+ ' File.WriteAllText(htmlPath, html, Encoding.UTF8);',
173
+ ' string profileDir = Path.Combine(tempDir, "profile");',
174
+ ' Directory.CreateDirectory(profileDir);',
175
+ ' string browser = FindBrowser();',
176
+ ' if (browser != null) {',
177
+ ' string fileUrl = "file:///" + htmlPath.Replace("\\\\", "/");',
178
+ ' string profUrl = profileDir.Replace("\\\\", "/");',
179
+ ' ProcessStartInfo psi = new ProcessStartInfo();',
180
+ ' psi.FileName = browser;',
181
+ ' psi.Arguments = "--app=\\"" + fileUrl + "\\" --window-size=1280,720 --user-data-dir=\\"" + profUrl + "\\" --no-first-run --disable-extensions --disable-default-apps";',
182
+ ' psi.UseShellExecute = false;',
183
+ ' Process p = Process.Start(psi);',
184
+ ' p.WaitForExit();',
185
+ ' } else {',
186
+ ' Process.Start(new ProcessStartInfo(htmlPath) { UseShellExecute = true });',
187
+ ' System.Threading.Thread.Sleep(3000);',
188
+ ' }',
189
+ ' try { Directory.Delete(tempDir, true); } catch {}',
190
+ ' }',
191
+ '',
192
+ ' static string FindBrowser() {',
193
+ ' string[] paths = new string[] {',
194
+ ' Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft", "Edge", "Application", "msedge.exe"),',
195
+ ' Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft", "Edge", "Application", "msedge.exe"),',
196
+ ' Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Google", "Chrome", "Application", "chrome.exe"),',
197
+ ' Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Google", "Chrome", "Application", "chrome.exe"),',
198
+ ' Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Google", "Chrome", "Application", "chrome.exe")',
199
+ ' };',
200
+ ' foreach (string p in paths) {',
201
+ ' if (File.Exists(p)) return p;',
202
+ ' }',
203
+ ' return null;',
204
+ ' }',
205
+ '}'
206
+ ].join('\n');
207
+ return cs;
208
+ };
209
+
210
+ ExeBuilder.prototype.findCscCompiler = function () {
211
+ var frameworkPaths = [
212
+ 'C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe',
213
+ 'C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe'
214
+ ];
215
+ for (var i = 0; i < frameworkPaths.length; i++) {
216
+ if (fs.existsSync(frameworkPaths[i])) return frameworkPaths[i];
217
+ }
218
+ try {
219
+ childProcess.execSync('csc /help', { stdio: 'pipe', timeout: 5000 });
220
+ return 'csc';
221
+ } catch (e) {}
222
+ return null;
223
+ };
224
+
225
+ ExeBuilder.prototype.compileCSharp = function (csPath, outputPath, iconPath) {
226
+ var csc = this.findCscCompiler();
227
+ if (!csc) {
228
+ throw new Error('C# compiler (csc.exe) not found. Ensure .NET Framework is installed.');
229
+ }
230
+ var cmd = '"' + csc + '" /target:winexe /out:"' + outputPath + '"';
231
+ if (iconPath && fs.existsSync(iconPath)) {
232
+ cmd += ' /win32icon:"' + iconPath + '"';
233
+ }
234
+ cmd += ' /optimize "' + csPath + '"';
235
+ childProcess.execSync(cmd, {
236
+ cwd: this.tempDir,
237
+ stdio: 'inherit',
238
+ shell: true
239
+ });
240
+ };
241
+
242
+ ExeBuilder.prototype.getRendererSource = function () {
243
+ var blocks = this.generateWorldBlocks();
244
+ var info = this.info;
245
+
246
+ var css = [
247
+ "* { margin: 0; padding: 0; box-sizing: border-box; }",
248
+ "body { background: #1a1a2e; overflow: hidden; font-family: monospace; }",
249
+ "#loading { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #1a1a2e; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 999; color: #fff; transition: opacity 0.5s; }",
250
+ "#loading.fade-out { opacity: 0; pointer-events: none; }",
251
+ "#loading h1 { font-size: 64px; color: #4ecca3; margin-bottom: 4px; letter-spacing: 6px; text-shadow: 0 0 30px rgba(78,204,163,0.3); }",
252
+ "#loading h2 { font-size: 14px; color: #666; margin-bottom: 30px; font-weight: normal; letter-spacing: 2px; }",
253
+ "#load-status { font-size: 12px; color: #555; margin-bottom: 12px; min-height: 18px; transition: color 0.3s; }",
254
+ "#bar-outer { width: 300px; height: 3px; background: #2a2a3e; border-radius: 2px; overflow: hidden; }",
255
+ "#bar-inner { width: 0%; height: 100%; background: linear-gradient(90deg, #4ecca3, #45b7d1); border-radius: 2px; transition: width 0.4s ease-out; }",
256
+ "#loading .version-tag { font-size: 11px; color: #444; margin-top: 20px; }",
257
+ "#click-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.65); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 998; cursor: pointer; transition: opacity 0.3s; }",
258
+ "#click-overlay:hover { background: rgba(0,0,0,0.55); }",
259
+ "#click-overlay .play-icon { width: 80px; height: 80px; border: 3px solid #4ecca3; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 16px; transition: transform 0.2s, border-color 0.2s; }",
260
+ "#click-overlay:hover .play-icon { transform: scale(1.1); border-color: #45b7d1; }",
261
+ "#click-overlay .play-triangle { width: 0; height: 0; border-style: solid; border-width: 15px 0 15px 28px; border-color: transparent transparent transparent #4ecca3; margin-left: 6px; }",
262
+ "#click-overlay span { color: #888; font-size: 16px; letter-spacing: 3px; }",
263
+ "canvas { display: block; position: fixed; top: 0; left: 0; }",
264
+ "#hud { position: fixed; top: 10px; left: 10px; z-index: 100; pointer-events: none; }",
265
+ "#hud-box { background: rgba(0,0,0,0.7); padding: 10px 14px; border-left: 3px solid #4ecca3; backdrop-filter: blur(4px); border-radius: 0 4px 4px 0; }",
266
+ "#hud-box div { color: #bbb; font-size: 11px; line-height: 17px; }",
267
+ "#hud-box .hud-title { color: #4ecca3; font-size: 13px; font-weight: bold; margin-bottom: 3px; letter-spacing: 1px; }",
268
+ "#hud-box .hud-sep { border-top: 1px solid #333; margin: 4px 0; }",
269
+ "#crosshair { position: fixed; top: 50%; left: 50%; width: 24px; height: 24px; margin: -12px 0 0 -12px; z-index: 100; pointer-events: none; }",
270
+ "#crosshair::before { content: ''; position: absolute; top: 11px; left: 3px; width: 18px; height: 2px; background: rgba(255,255,255,0.6); border-radius: 1px; }",
271
+ "#crosshair::after { content: ''; position: absolute; top: 3px; left: 11px; width: 2px; height: 18px; background: rgba(255,255,255,0.6); border-radius: 1px; }",
272
+ "#controls-hint { position: fixed; bottom: 10px; right: 10px; z-index: 100; pointer-events: none; background: rgba(0,0,0,0.55); padding: 8px 14px; color: #666; font-size: 10px; border-radius: 4px; line-height: 16px; backdrop-filter: blur(4px); }",
273
+ "#controls-hint b { color: #888; }"
274
+ ].join("\n");
275
+
276
+ var vs = [
277
+ "attribute vec3 aPos;",
278
+ "attribute vec3 aColor;",
279
+ "attribute vec3 aNormal;",
280
+ "uniform mat4 uProj;",
281
+ "uniform mat4 uView;",
282
+ "varying vec3 vColor;",
283
+ "varying vec3 vNormal;",
284
+ "varying vec3 vWorldPos;",
285
+ "void main() {",
286
+ " vColor = aColor;",
287
+ " vNormal = aNormal;",
288
+ " vWorldPos = aPos;",
289
+ " gl_Position = uProj * uView * vec4(aPos, 1.0);",
290
+ "}"
291
+ ].join("\\n");
292
+
293
+ var fs_shader = [
294
+ "precision mediump float;",
295
+ "varying vec3 vColor;",
296
+ "varying vec3 vNormal;",
297
+ "varying vec3 vWorldPos;",
298
+ "uniform vec3 uSunDir;",
299
+ "uniform vec3 uSunColor;",
300
+ "uniform vec3 uAmbient;",
301
+ "uniform vec3 uFogColor;",
302
+ "uniform float uFogNear;",
303
+ "uniform float uFogFar;",
304
+ "uniform vec3 uCamPos;",
305
+ "void main() {",
306
+ " vec3 norm = normalize(vNormal);",
307
+ " float diff = max(dot(norm, normalize(uSunDir)), 0.0);",
308
+ " vec3 lit = vColor * (uAmbient + uSunColor * diff);",
309
+ " float dist = length(vWorldPos - uCamPos);",
310
+ " float fog = clamp((dist - uFogNear) / (uFogFar - uFogNear), 0.0, 1.0);",
311
+ " vec3 final = mix(lit, uFogColor, fog);",
312
+ " gl_FragColor = vec4(final, 1.0);",
313
+ "}"
314
+ ].join("\\n");
315
+
316
+ var js = [];
317
+ js.push("var BLOCKS = " + JSON.stringify(blocks) + ";");
318
+ js.push("var INFO = " + JSON.stringify(info) + ";");
319
+ js.push("");
320
+ js.push("var VS_SRC = \"" + vs + "\";");
321
+ js.push("var FS_SRC = \"" + fs_shader + "\";");
322
+ js.push("");
323
+ js.push("var cag = {");
324
+ js.push(" gl: null, canvas: null, program: null,");
325
+ js.push(" camX: 16, camY: 12, camZ: 16,");
326
+ js.push(" pitch: -0.5, yaw: 0.8,");
327
+ js.push(" keys: {}, locked: false,");
328
+ js.push(" velY: 0, onGround: false, gravity: 0.015,");
329
+ js.push(" fps: 0, frames: 0, lastFps: 0,");
330
+ js.push(" vbo: null, vertCount: 0,");
331
+ js.push(" blockMap: {},");
332
+ js.push(" uProj: null, uView: null, uSunDir: null, uSunColor: null,");
333
+ js.push(" uAmbient: null, uFogColor: null, uFogNear: null, uFogFar: null, uCamPos: null,");
334
+ js.push("");
335
+ js.push(" init: function () {");
336
+ js.push(" this.canvas = document.getElementById('cag-canvas');");
337
+ js.push(" this.gl = this.canvas.getContext('webgl', { antialias: true });");
338
+ js.push(" if (!this.gl) { alert('WebGL not supported'); return; }");
339
+ js.push(" this.resize();");
340
+ js.push(" this.buildBlockMap();");
341
+ js.push(" this.compileShaders();");
342
+ js.push(" this.buildMesh();");
343
+ js.push(" this.bindInput();");
344
+ js.push(" var loadEl = document.getElementById('loading');");
345
+ js.push(" loadEl.classList.add('fade-out');");
346
+ js.push(" setTimeout(function () {");
347
+ js.push(" loadEl.style.display = 'none';");
348
+ js.push(" document.getElementById('click-overlay').style.display = 'flex';");
349
+ js.push(" }, 500);");
350
+ js.push(" this.lastFps = performance.now();");
351
+ js.push(" this.loop();");
352
+ js.push(" },");
353
+ js.push("");
354
+ js.push(" buildBlockMap: function () {");
355
+ js.push(" for (var i = 0; i < BLOCKS.length; i++) {");
356
+ js.push(" var b = BLOCKS[i];");
357
+ js.push(" this.blockMap[b.x + ',' + b.y + ',' + b.z] = b;");
358
+ js.push(" }");
359
+ js.push(" },");
360
+ js.push("");
361
+ js.push(" hasBlock: function (x, y, z) {");
362
+ js.push(" return this.blockMap[x + ',' + y + ',' + z] !== undefined;");
363
+ js.push(" },");
364
+ js.push("");
365
+ js.push(" compileShaders: function () {");
366
+ js.push(" var gl = this.gl;");
367
+ js.push(" var vs = gl.createShader(gl.VERTEX_SHADER);");
368
+ js.push(" gl.shaderSource(vs, VS_SRC); gl.compileShader(vs);");
369
+ js.push(" var fs = gl.createShader(gl.FRAGMENT_SHADER);");
370
+ js.push(" gl.shaderSource(fs, FS_SRC); gl.compileShader(fs);");
371
+ js.push(" this.program = gl.createProgram();");
372
+ js.push(" gl.attachShader(this.program, vs);");
373
+ js.push(" gl.attachShader(this.program, fs);");
374
+ js.push(" gl.linkProgram(this.program);");
375
+ js.push(" gl.useProgram(this.program);");
376
+ js.push(" this.uProj = gl.getUniformLocation(this.program, 'uProj');");
377
+ js.push(" this.uView = gl.getUniformLocation(this.program, 'uView');");
378
+ js.push(" this.uSunDir = gl.getUniformLocation(this.program, 'uSunDir');");
379
+ js.push(" this.uSunColor = gl.getUniformLocation(this.program, 'uSunColor');");
380
+ js.push(" this.uAmbient = gl.getUniformLocation(this.program, 'uAmbient');");
381
+ js.push(" this.uFogColor = gl.getUniformLocation(this.program, 'uFogColor');");
382
+ js.push(" this.uFogNear = gl.getUniformLocation(this.program, 'uFogNear');");
383
+ js.push(" this.uFogFar = gl.getUniformLocation(this.program, 'uFogFar');");
384
+ js.push(" this.uCamPos = gl.getUniformLocation(this.program, 'uCamPos');");
385
+ js.push(" gl.uniform3f(this.uSunDir, 0.4, 0.8, 0.3);");
386
+ js.push(" gl.uniform3f(this.uSunColor, 1.0, 0.95, 0.85);");
387
+ js.push(" gl.uniform3f(this.uAmbient, 0.35, 0.38, 0.5);");
388
+ js.push(" gl.uniform3f(this.uFogColor, 0.53, 0.81, 0.92);");
389
+ js.push(" gl.uniform1f(this.uFogNear, 30.0);");
390
+ js.push(" gl.uniform1f(this.uFogFar, 70.0);");
391
+ js.push(" },");
392
+ js.push("");
393
+ js.push(" getBlockColor: function (b, face) {");
394
+ js.push(" var r, g, bl;");
395
+ js.push(" if (b.t === 'grass') {");
396
+ js.push(" if (face === 'top') { r = 0.34; g = 0.68; bl = 0.24; }");
397
+ js.push(" else if (face === 'bottom') { r = 0.55; g = 0.38; bl = 0.18; }");
398
+ js.push(" else { r = 0.42; g = 0.50; bl = 0.22; }");
399
+ js.push(" } else if (b.t === 'dirt') {");
400
+ js.push(" r = 0.55; g = 0.38; bl = 0.18;");
401
+ js.push(" } else if (b.t === 'stone') {");
402
+ js.push(" if (face === 'top') { r = 0.58; g = 0.58; bl = 0.58; }");
403
+ js.push(" else { r = 0.50; g = 0.50; bl = 0.50; }");
404
+ js.push(" } else if (b.t === 'sand') {");
405
+ js.push(" if (face === 'top') { r = 0.86; g = 0.82; bl = 0.58; }");
406
+ js.push(" else { r = 0.80; g = 0.76; bl = 0.52; }");
407
+ js.push(" } else if (b.t === 'water') {");
408
+ js.push(" if (face === 'top') { r = 0.15; g = 0.45; bl = 0.75; }");
409
+ js.push(" else { r = 0.10; g = 0.35; bl = 0.65; }");
410
+ js.push(" } else if (b.t === 'snow') {");
411
+ js.push(" if (face === 'top') { r = 0.95; g = 0.96; bl = 0.98; }");
412
+ js.push(" else { r = 0.88; g = 0.90; bl = 0.92; }");
413
+ js.push(" } else if (b.t === 'wood' || b.t === 'log') {");
414
+ js.push(" if (face === 'top' || face === 'bottom') { r = 0.62; g = 0.52; bl = 0.30; }");
415
+ js.push(" else { r = 0.45; g = 0.32; bl = 0.18; }");
416
+ js.push(" } else if (b.t === 'leaf' || b.t === 'leaves') {");
417
+ js.push(" r = 0.20; g = 0.55; bl = 0.15;");
418
+ js.push(" } else if (b.t === 'planks') {");
419
+ js.push(" r = 0.72; g = 0.58; bl = 0.35;");
420
+ js.push(" } else if (b.t === 'cobblestone' || b.t === 'cobble') {");
421
+ js.push(" if (face === 'top') { r = 0.52; g = 0.52; bl = 0.52; }");
422
+ js.push(" else { r = 0.44; g = 0.44; bl = 0.44; }");
423
+ js.push(" } else if (b.t === 'gravel') {");
424
+ js.push(" r = 0.56; g = 0.54; bl = 0.50;");
425
+ js.push(" } else if (b.t === 'clay') {");
426
+ js.push(" r = 0.62; g = 0.60; bl = 0.58;");
427
+ js.push(" } else if (b.t === 'iron' || b.t === 'ore') {");
428
+ js.push(" r = 0.65; g = 0.60; bl = 0.55;");
429
+ js.push(" } else if (b.t === 'gold') {");
430
+ js.push(" r = 0.90; g = 0.78; bl = 0.20;");
431
+ js.push(" } else if (b.t === 'diamond') {");
432
+ js.push(" r = 0.30; g = 0.85; bl = 0.88;");
433
+ js.push(" } else if (b.t === 'lava') {");
434
+ js.push(" r = 0.95; g = 0.35; bl = 0.05;");
435
+ js.push(" } else if (b.t === 'brick') {");
436
+ js.push(" r = 0.65; g = 0.32; bl = 0.25;");
437
+ js.push(" } else {");
438
+ js.push(" var hash = 0;");
439
+ js.push(" for (var ci = 0; ci < b.t.length; ci++) hash = ((hash << 5) - hash) + b.t.charCodeAt(ci);");
440
+ js.push(" r = 0.3 + (((hash & 0xFF) / 255) * 0.5);");
441
+ js.push(" g = 0.3 + ((((hash >> 8) & 0xFF) / 255) * 0.5);");
442
+ js.push(" bl = 0.3 + ((((hash >> 16) & 0xFF) / 255) * 0.5);");
443
+ js.push(" }");
444
+ js.push(" var v = 0.85 + (b.y / 12.0) * 0.15;");
445
+ js.push(" return [r * v, g * v, bl * v];");
446
+ js.push(" },");
447
+ js.push("");
448
+ js.push(" buildMesh: function () {");
449
+ js.push(" var verts = [];");
450
+ js.push(" var self = this;");
451
+ js.push(" function quad(x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4, nx,ny,nz, c) {");
452
+ js.push(" verts.push(x1,y1,z1, c[0],c[1],c[2], nx,ny,nz);");
453
+ js.push(" verts.push(x2,y2,z2, c[0],c[1],c[2], nx,ny,nz);");
454
+ js.push(" verts.push(x3,y3,z3, c[0],c[1],c[2], nx,ny,nz);");
455
+ js.push(" verts.push(x1,y1,z1, c[0],c[1],c[2], nx,ny,nz);");
456
+ js.push(" verts.push(x3,y3,z3, c[0],c[1],c[2], nx,ny,nz);");
457
+ js.push(" verts.push(x4,y4,z4, c[0],c[1],c[2], nx,ny,nz);");
458
+ js.push(" }");
459
+ js.push(" for (var i = 0; i < BLOCKS.length; i++) {");
460
+ js.push(" var b = BLOCKS[i];");
461
+ js.push(" var x = b.x, y = b.y, z = b.z;");
462
+ js.push(" if (!self.hasBlock(x, y+1, z)) {");
463
+ js.push(" var ct = self.getBlockColor(b, 'top');");
464
+ js.push(" quad(x,y+1,z, x+1,y+1,z, x+1,y+1,z+1, x,y+1,z+1, 0,1,0, ct);");
465
+ js.push(" }");
466
+ js.push(" if (!self.hasBlock(x, y-1, z)) {");
467
+ js.push(" var cb = self.getBlockColor(b, 'bottom');");
468
+ js.push(" quad(x,y,z+1, x+1,y,z+1, x+1,y,z, x,y,z, 0,-1,0, cb);");
469
+ js.push(" }");
470
+ js.push(" if (!self.hasBlock(x, y, z-1)) {");
471
+ js.push(" var cn = self.getBlockColor(b, 'side');");
472
+ js.push(" quad(x,y,z, x+1,y,z, x+1,y+1,z, x,y+1,z, 0,0,-1, cn);");
473
+ js.push(" }");
474
+ js.push(" if (!self.hasBlock(x, y, z+1)) {");
475
+ js.push(" var cs = self.getBlockColor(b, 'side');");
476
+ js.push(" quad(x+1,y,z+1, x,y,z+1, x,y+1,z+1, x+1,y+1,z+1, 0,0,1, cs);");
477
+ js.push(" }");
478
+ js.push(" if (!self.hasBlock(x-1, y, z)) {");
479
+ js.push(" var cw = self.getBlockColor(b, 'side');");
480
+ js.push(" quad(x,y,z+1, x,y,z, x,y+1,z, x,y+1,z+1, -1,0,0, cw);");
481
+ js.push(" }");
482
+ js.push(" if (!self.hasBlock(x+1, y, z)) {");
483
+ js.push(" var ce = self.getBlockColor(b, 'side');");
484
+ js.push(" quad(x+1,y,z, x+1,y,z+1, x+1,y+1,z+1, x+1,y+1,z, 1,0,0, ce);");
485
+ js.push(" }");
486
+ js.push(" }");
487
+ js.push(" this.vertCount = verts.length / 9;");
488
+ js.push(" var gl = this.gl;");
489
+ js.push(" this.vbo = gl.createBuffer();");
490
+ js.push(" gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);");
491
+ js.push(" gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);");
492
+ js.push(" var aPos = gl.getAttribLocation(this.program, 'aPos');");
493
+ js.push(" var aColor = gl.getAttribLocation(this.program, 'aColor');");
494
+ js.push(" var aNormal = gl.getAttribLocation(this.program, 'aNormal');");
495
+ js.push(" gl.enableVertexAttribArray(aPos);");
496
+ js.push(" gl.enableVertexAttribArray(aColor);");
497
+ js.push(" gl.enableVertexAttribArray(aNormal);");
498
+ js.push(" gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 36, 0);");
499
+ js.push(" gl.vertexAttribPointer(aColor, 3, gl.FLOAT, false, 36, 12);");
500
+ js.push(" gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 36, 24);");
501
+ js.push(" },");
502
+ js.push("");
503
+ js.push(" resize: function () {");
504
+ js.push(" this.canvas.width = window.innerWidth;");
505
+ js.push(" this.canvas.height = window.innerHeight;");
506
+ js.push(" if (this.gl) this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);");
507
+ js.push(" },");
508
+ js.push("");
509
+ js.push(" bindInput: function () {");
510
+ js.push(" var self = this;");
511
+ js.push(" document.addEventListener('keydown', function (e) { self.keys[e.code] = true; });");
512
+ js.push(" document.addEventListener('keyup', function (e) { self.keys[e.code] = false; });");
513
+ js.push(" window.addEventListener('resize', function () { self.resize(); });");
514
+ js.push(" document.addEventListener('mousemove', function (e) {");
515
+ js.push(" if (!self.locked) return;");
516
+ js.push(" self.yaw += e.movementX * 0.003;");
517
+ js.push(" self.pitch -= e.movementY * 0.003;");
518
+ js.push(" if (self.pitch > 1.5) self.pitch = 1.5;");
519
+ js.push(" if (self.pitch < -1.5) self.pitch = -1.5;");
520
+ js.push(" });");
521
+ js.push(" document.addEventListener('pointerlockchange', function () {");
522
+ js.push(" self.locked = document.pointerLockElement === self.canvas;");
523
+ js.push(" var ov = document.getElementById('click-overlay');");
524
+ js.push(" ov.style.display = self.locked ? 'none' : 'flex';");
525
+ js.push(" });");
526
+ js.push(" document.getElementById('click-overlay').addEventListener('click', function () {");
527
+ js.push(" self.canvas.requestPointerLock();");
528
+ js.push(" });");
529
+ js.push(" },");
530
+ js.push("");
531
+ js.push(" getGroundY: function (x, z) {");
532
+ js.push(" var bx = Math.floor(x);");
533
+ js.push(" var bz = Math.floor(z);");
534
+ js.push(" for (var y = 30; y >= 0; y--) {");
535
+ js.push(" if (this.hasBlock(bx, y, bz)) return y + 1;");
536
+ js.push(" }");
537
+ js.push(" return 1;");
538
+ js.push(" },");
539
+ js.push("");
540
+ js.push(" update: function () {");
541
+ js.push(" if (!this.locked) return;");
542
+ js.push(" var spd = 0.08;");
543
+ js.push(" if (this.keys['ShiftLeft'] || this.keys['ShiftRight']) spd = 0.16;");
544
+ js.push(" var dx = 0, dz = 0;");
545
+ js.push(" var sy = Math.sin(this.yaw), cy = Math.cos(this.yaw);");
546
+ js.push(" if (this.keys['KeyW'] || this.keys['ArrowUp']) { dx -= sy * spd; dz -= cy * spd; }");
547
+ js.push(" if (this.keys['KeyS'] || this.keys['ArrowDown']) { dx += sy * spd; dz += cy * spd; }");
548
+ js.push(" if (this.keys['KeyA'] || this.keys['ArrowLeft']) { dx -= cy * spd; dz += sy * spd; }");
549
+ js.push(" if (this.keys['KeyD'] || this.keys['ArrowRight']) { dx += cy * spd; dz -= sy * spd; }");
550
+ js.push(" var nx = this.camX + dx;");
551
+ js.push(" var nz = this.camZ + dz;");
552
+ js.push(" var feet = Math.floor(this.camY - 1.6);");
553
+ js.push(" var bnx = Math.floor(nx);");
554
+ js.push(" var bnz = Math.floor(nz);");
555
+ js.push(" if (!this.hasBlock(bnx, feet, Math.floor(this.camZ)) && !this.hasBlock(bnx, feet+1, Math.floor(this.camZ))) {");
556
+ js.push(" this.camX = nx;");
557
+ js.push(" }");
558
+ js.push(" if (!this.hasBlock(Math.floor(this.camX), feet, bnz) && !this.hasBlock(Math.floor(this.camX), feet+1, bnz)) {");
559
+ js.push(" this.camZ = nz;");
560
+ js.push(" }");
561
+ js.push(" this.velY -= this.gravity;");
562
+ js.push(" this.camY += this.velY;");
563
+ js.push(" var gy = this.getGroundY(this.camX, this.camZ) + 1.6;");
564
+ js.push(" if (this.camY <= gy) {");
565
+ js.push(" this.camY = gy;");
566
+ js.push(" this.velY = 0;");
567
+ js.push(" this.onGround = true;");
568
+ js.push(" } else {");
569
+ js.push(" this.onGround = false;");
570
+ js.push(" }");
571
+ js.push(" if (this.keys['Space'] && this.onGround) {");
572
+ js.push(" this.velY = 0.18;");
573
+ js.push(" this.onGround = false;");
574
+ js.push(" }");
575
+ js.push(" },");
576
+ js.push("");
577
+ js.push(" perspective: function (fov, aspect, near, far) {");
578
+ js.push(" var f = 1.0 / Math.tan(fov / 2);");
579
+ js.push(" var nf = 1.0 / (near - far);");
580
+ js.push(" return new Float32Array([");
581
+ js.push(" f/aspect, 0, 0, 0,");
582
+ js.push(" 0, f, 0, 0,");
583
+ js.push(" 0, 0, (far+near)*nf, -1,");
584
+ js.push(" 0, 0, 2*far*near*nf, 0");
585
+ js.push(" ]);");
586
+ js.push(" },");
587
+ js.push("");
588
+ js.push(" lookAt: function () {");
589
+ js.push(" var cp = Math.cos(this.pitch), sp = Math.sin(this.pitch);");
590
+ js.push(" var cy = Math.cos(this.yaw), sy = Math.sin(this.yaw);");
591
+ js.push(" var fx = -sy * cp, fy = sp, fz = -cy * cp;");
592
+ js.push(" var len = Math.sqrt(fx*fx+fy*fy+fz*fz);");
593
+ js.push(" fx/=len; fy/=len; fz/=len;");
594
+ js.push(" var ux = 0, uy = 1, uz = 0;");
595
+ js.push(" var rx = fy*uz - fz*uy, ry = fz*ux - fx*uz, rz = fx*uy - fy*ux;");
596
+ js.push(" len = Math.sqrt(rx*rx+ry*ry+rz*rz);");
597
+ js.push(" rx/=len; ry/=len; rz/=len;");
598
+ js.push(" ux = ry*fz - rz*fy; uy = rz*fx - rx*fz; uz = rx*fy - ry*fx;");
599
+ js.push(" var tx = -rx*this.camX - ry*this.camY - rz*this.camZ;");
600
+ js.push(" var ty = -ux*this.camX - uy*this.camY - uz*this.camZ;");
601
+ js.push(" var tz = fx*this.camX + fy*this.camY + fz*this.camZ;");
602
+ js.push(" return new Float32Array([");
603
+ js.push(" rx, ux, -fx, 0,");
604
+ js.push(" ry, uy, -fy, 0,");
605
+ js.push(" rz, uz, -fz, 0,");
606
+ js.push(" tx, ty, tz, 1");
607
+ js.push(" ]);");
608
+ js.push(" },");
609
+ js.push("");
610
+ js.push(" render: function () {");
611
+ js.push(" var gl = this.gl;");
612
+ js.push(" gl.clearColor(0.53, 0.81, 0.92, 1.0);");
613
+ js.push(" gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);");
614
+ js.push(" gl.enable(gl.DEPTH_TEST);");
615
+ js.push(" gl.enable(gl.CULL_FACE);");
616
+ js.push(" gl.cullFace(gl.BACK);");
617
+ js.push(" var aspect = this.canvas.width / this.canvas.height;");
618
+ js.push(" var proj = this.perspective(1.2, aspect, 0.1, 200.0);");
619
+ js.push(" var view = this.lookAt();");
620
+ js.push(" gl.uniformMatrix4fv(this.uProj, false, proj);");
621
+ js.push(" gl.uniformMatrix4fv(this.uView, false, view);");
622
+ js.push(" gl.uniform3f(this.uCamPos, this.camX, this.camY, this.camZ);");
623
+ js.push(" gl.drawArrays(gl.TRIANGLES, 0, this.vertCount);");
624
+ js.push(" this.updateHUD();");
625
+ js.push(" },");
626
+ js.push("");
627
+ js.push(" getStandingBlock: function () {");
628
+ js.push(" var bx = Math.floor(this.camX);");
629
+ js.push(" var bz = Math.floor(this.camZ);");
630
+ js.push(" var by = Math.floor(this.camY - 1.7);");
631
+ js.push(" var b = this.blockMap[bx + ',' + by + ',' + bz];");
632
+ js.push(" return b ? b.t : 'air';");
633
+ js.push(" },");
634
+ js.push("");
635
+ js.push(" updateHUD: function () {");
636
+ js.push(" var h = document.getElementById('hud-info');");
637
+ js.push(" if (!h) return;");
638
+ js.push(" var standing = this.getStandingBlock();");
639
+ js.push(" var sprinting = this.keys['ShiftLeft'] || this.keys['ShiftRight'];");
640
+ js.push(" var tris = Math.floor(this.vertCount / 3);");
641
+ js.push(" h.innerHTML = '<div class=\"hud-title\">CaG Engine</div>' +");
642
+ js.push(" '<div>' + INFO.ID + ' v' + INFO.version + '</div>' +");
643
+ js.push(" '<div class=\"hud-sep\"></div>' +");
644
+ js.push(" '<div>FPS: ' + this.fps + ' | Tris: ' + tris.toLocaleString() + '</div>' +");
645
+ js.push(" '<div>Blocks: ' + BLOCKS.length.toLocaleString() + '</div>' +");
646
+ js.push(" '<div class=\"hud-sep\"></div>' +");
647
+ js.push(" '<div>X: ' + this.camX.toFixed(1) + ' Y: ' + this.camY.toFixed(1) + ' Z: ' + this.camZ.toFixed(1) + '</div>' +");
648
+ js.push(" '<div>Standing: ' + standing + (sprinting ? ' [SPRINT]' : '') + '</div>';");
649
+ js.push(" },");
650
+ js.push("");
651
+ js.push(" loop: function () {");
652
+ js.push(" this.update();");
653
+ js.push(" this.render();");
654
+ js.push(" this.frames++;");
655
+ js.push(" var now = performance.now();");
656
+ js.push(" if (now - this.lastFps >= 1000) {");
657
+ js.push(" this.fps = this.frames;");
658
+ js.push(" this.frames = 0;");
659
+ js.push(" this.lastFps = now;");
660
+ js.push(" }");
661
+ js.push(" requestAnimationFrame(this.loop.bind(this));");
662
+ js.push(" }");
663
+ js.push("};");
664
+ js.push("");
665
+ js.push("var bar = document.getElementById('bar-inner');");
666
+ js.push("var status = document.getElementById('load-status');");
667
+ js.push("bar.style.width = '10%';");
668
+ js.push("status.textContent = 'Initializing CaG Engine...';");
669
+ js.push("setTimeout(function () {");
670
+ js.push(" bar.style.width = '25%';");
671
+ js.push(" status.textContent = 'Parsing project data...';");
672
+ js.push("}, 300);");
673
+ js.push("setTimeout(function () {");
674
+ js.push(" bar.style.width = '45%';");
675
+ js.push(" status.textContent = 'Generating terrain (' + BLOCKS.length.toLocaleString() + ' blocks)...';");
676
+ js.push("}, 600);");
677
+ js.push("setTimeout(function () {");
678
+ js.push(" bar.style.width = '65%';");
679
+ js.push(" status.textContent = 'Building mesh geometry...';");
680
+ js.push("}, 900);");
681
+ js.push("setTimeout(function () {");
682
+ js.push(" bar.style.width = '80%';");
683
+ js.push(" status.textContent = 'Compiling CaG shaders...';");
684
+ js.push("}, 1200);");
685
+ js.push("setTimeout(function () {");
686
+ js.push(" bar.style.width = '95%';");
687
+ js.push(" status.textContent = 'Preparing renderer...';");
688
+ js.push("}, 1500);");
689
+ js.push("setTimeout(function () {");
690
+ js.push(" bar.style.width = '100%';");
691
+ js.push(" status.textContent = 'Ready';");
692
+ js.push(" status.style.color = '#4ecca3';");
693
+ js.push("}, 1800);");
694
+ js.push("setTimeout(function () { cag.init(); }, 2200);");
695
+
696
+ var html = "<!DOCTYPE html>\n<html>\n<head>\n";
697
+ html += "<meta charset='utf-8'>\n";
698
+ html += "<title>" + info.ID + " - CaG Engine</title>\n";
699
+ html += "<style>\n" + css + "\n</style>\n";
700
+ html += "</head>\n<body>\n";
701
+ html += "<div id='loading'>\n";
702
+ html += " <h1>CaG</h1>\n";
703
+ html += " <h2>" + info.ID + "</h2>\n";
704
+ html += " <div id='load-status'>Starting...</div>\n";
705
+ html += " <div id='bar-outer'><div id='bar-inner'></div></div>\n";
706
+ html += " <div class='version-tag'>v" + (info.version || "1.0.0") + "</div>\n";
707
+ html += "</div>\n";
708
+ html += "<div id='click-overlay' style='display:none'><div class='play-icon'><div class='play-triangle'></div></div><span>CLICK TO PLAY</span></div>\n";
709
+ html += "<div id='crosshair'></div>\n";
710
+ html += "<div id='hud'><div id='hud-box'><div id='hud-info'></div></div></div>\n";
711
+ html += "<div id='controls-hint'><b>WASD</b> Move &nbsp; <b>Mouse</b> Look &nbsp; <b>Space</b> Jump &nbsp; <b>Shift</b> Sprint &nbsp; <b>F11</b> Fullscreen &nbsp; <b>Esc</b> Release</div>\n";
712
+ html += "<canvas id='cag-canvas'></canvas>\n";
713
+ html += "<script>\n" + js.join("\n") + "\n</script>\n";
714
+ html += "</body>\n</html>";
715
+
716
+ return html;
717
+ };
718
+
719
+ ExeBuilder.prototype.generateWorldBlocks = function () {
720
+ var codeFiles = this.engine.getCodeFiles();
721
+ var topLayer = "grass";
722
+ var fillLayer = "dirt";
723
+ var bottomLayer = "stone";
724
+ var size = 32;
725
+ var seed = 54321;
726
+ var waterLevel = 3;
727
+ var hasWater = false;
728
+
729
+ for (var i = 0; i < codeFiles.length; i++) {
730
+ var content = fs.readFileSync(codeFiles[i], "utf-8");
731
+ var lines = content.split("\n");
732
+ for (var j = 0; j < lines.length; j++) {
733
+ var line = lines[j].trim();
734
+ if (line.indexOf("top") !== -1 && line.indexOf("layer") !== -1 && line.indexOf("=") !== -1) {
735
+ var eqIdx = line.lastIndexOf("=");
736
+ var val = line.substring(eqIdx + 1).trim();
737
+ if (val) topLayer = val.toLowerCase();
738
+ }
739
+ if (line.indexOf("fill") !== -1 && line.indexOf("layer") !== -1 && line.indexOf("=") !== -1) {
740
+ var fIdx = line.lastIndexOf("=");
741
+ var fVal = line.substring(fIdx + 1).trim();
742
+ if (fVal) fillLayer = fVal.toLowerCase();
743
+ }
744
+ if (line.indexOf("bottom") !== -1 && line.indexOf("layer") !== -1 && line.indexOf("=") !== -1) {
745
+ var bIdx = line.lastIndexOf("=");
746
+ var bVal = line.substring(bIdx + 1).trim();
747
+ if (bVal) bottomLayer = bVal.toLowerCase();
748
+ }
749
+ if (line.indexOf("water") !== -1) {
750
+ hasWater = true;
751
+ }
752
+ if (line.indexOf("size") !== -1 && line.indexOf("=") !== -1) {
753
+ var sIdx = line.lastIndexOf("=");
754
+ var sVal = parseInt(line.substring(sIdx + 1).trim(), 10);
755
+ if (!isNaN(sVal) && sVal >= 8 && sVal <= 128) size = sVal;
756
+ }
757
+ if (line.indexOf("seed") !== -1 && line.indexOf("=") !== -1) {
758
+ var sdIdx = line.lastIndexOf("=");
759
+ var sdVal = parseInt(line.substring(sdIdx + 1).trim(), 10);
760
+ if (!isNaN(sdVal)) seed = sdVal;
761
+ }
762
+ }
763
+ }
764
+
765
+ var blocks = [];
766
+
767
+ var noise = function (x, z, s, scale) {
768
+ var nx = x / scale;
769
+ var nz = z / scale;
770
+ var a = Math.sin(nx * 12.9898 + nz * 78.233 + s) * 43758.5453;
771
+ return a - Math.floor(a);
772
+ };
773
+
774
+ var smoothHeight = function (x, z) {
775
+ var h1 = noise(x, z, seed, 8.0) * 4;
776
+ var h2 = noise(x, z, seed + 100, 16.0) * 6;
777
+ var h3 = noise(x, z, seed + 200, 4.0) * 2;
778
+ var h4 = noise(x + 0.5, z + 0.5, seed + 300, 12.0) * 3;
779
+
780
+ var avg = 0;
781
+ var count = 0;
782
+ for (var dx = -1; dx <= 1; dx++) {
783
+ for (var dz = -1; dz <= 1; dz++) {
784
+ var n1 = noise(x + dx, z + dz, seed, 8.0) * 4;
785
+ var n2 = noise(x + dx, z + dz, seed + 100, 16.0) * 6;
786
+ avg += n1 + n2;
787
+ count++;
788
+ }
789
+ }
790
+ avg = avg / count;
791
+
792
+ var raw = (h1 + h2 + h3 + h4) * 0.4 + avg * 0.6;
793
+ return Math.max(1, Math.min(Math.floor(raw), 12));
794
+ };
795
+
796
+ for (var x = 0; x < size; x++) {
797
+ for (var z = 0; z < size; z++) {
798
+ var height = smoothHeight(x, z);
799
+ for (var y = 0; y <= height; y++) {
800
+ var blockType;
801
+ if (y === height) {
802
+ blockType = topLayer;
803
+ } else if (y === 0) {
804
+ blockType = bottomLayer;
805
+ } else if (y >= height - 1) {
806
+ blockType = topLayer === "grass" ? fillLayer : topLayer;
807
+ } else {
808
+ blockType = fillLayer;
809
+ }
810
+ blocks.push({ x: x, y: y, z: z, t: blockType });
811
+ }
812
+ if (hasWater && height < waterLevel) {
813
+ for (var wy = height + 1; wy <= waterLevel; wy++) {
814
+ blocks.push({ x: x, y: wy, z: z, t: "water" });
815
+ }
816
+ }
817
+ }
818
+ }
819
+
820
+ return blocks;
821
+ };
822
+
823
+ ExeBuilder.prototype.generateMacApp = function (html, appPath) {
824
+ var contentsDir = path.join(appPath, 'Contents');
825
+ var macosDir = path.join(contentsDir, 'MacOS');
826
+ var resourcesDir = path.join(contentsDir, 'Resources');
827
+ fs.mkdirSync(macosDir, { recursive: true });
828
+ fs.mkdirSync(resourcesDir, { recursive: true });
829
+ fs.writeFileSync(path.join(resourcesDir, 'game.html'), html);
830
+ var launcher = '#!/bin/bash\n';
831
+ launcher += 'DIR="$(cd "$(dirname "$0")" && pwd)"\n';
832
+ launcher += 'HTML="$DIR/../Resources/game.html"\n';
833
+ launcher += 'if [ -d "/Applications/Google Chrome.app" ]; then\n';
834
+ launcher += ' open -a "Google Chrome" --args --app="file://$HTML" --window-size=1280,720 --disable-extensions --no-first-run\n';
835
+ launcher += 'elif [ -d "/Applications/Microsoft Edge.app" ]; then\n';
836
+ launcher += ' open -a "Microsoft Edge" --args --app="file://$HTML" --window-size=1280,720 --disable-extensions --no-first-run\n';
837
+ launcher += 'else\n';
838
+ launcher += ' open "$HTML"\n';
839
+ launcher += 'fi\n';
840
+ var launcherPath = path.join(macosDir, this.projectName);
841
+ fs.writeFileSync(launcherPath, launcher);
842
+ try { fs.chmodSync(launcherPath, 0o755); } catch (e) {}
843
+ var plist = '<?xml version="1.0" encoding="UTF-8"?>\n';
844
+ plist += '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n';
845
+ plist += '<plist version="1.0">\n<dict>\n';
846
+ plist += ' <key>CFBundleExecutable</key>\n <string>' + this.projectName + '</string>\n';
847
+ plist += ' <key>CFBundleName</key>\n <string>' + this.info.ID + '</string>\n';
848
+ plist += ' <key>CFBundleIdentifier</key>\n <string>' + this.info.ID + '</string>\n';
849
+ plist += ' <key>CFBundleVersion</key>\n <string>' + (this.info.version || '1.0.0') + '</string>\n';
850
+ plist += ' <key>CFBundlePackageType</key>\n <string>APPL</string>\n';
851
+ plist += '</dict>\n</plist>\n';
852
+ fs.writeFileSync(path.join(contentsDir, 'Info.plist'), plist);
853
+ };
854
+
855
+ ExeBuilder.prototype.generateLinuxApp = function (html, outputPath) {
856
+ var b64 = Buffer.from(html, 'utf-8').toString('base64');
857
+ var script = '#!/bin/bash\n';
858
+ script += 'TEMP_DIR=$(mktemp -d /tmp/cag_XXXXXX)\n';
859
+ script += 'HTML_PATH="$TEMP_DIR/game.html"\n';
860
+ script += 'echo "' + b64 + '" | base64 -d > "$HTML_PATH"\n';
861
+ script += 'cleanup() { rm -rf "$TEMP_DIR"; }\n';
862
+ script += 'trap cleanup EXIT\n';
863
+ script += 'if command -v google-chrome &>/dev/null; then\n';
864
+ script += ' google-chrome --app="file://$HTML_PATH" --window-size=1280,720 --disable-extensions --no-first-run 2>/dev/null\n';
865
+ script += 'elif command -v chromium-browser &>/dev/null; then\n';
866
+ script += ' chromium-browser --app="file://$HTML_PATH" --window-size=1280,720 --disable-extensions --no-first-run 2>/dev/null\n';
867
+ script += 'elif command -v chromium &>/dev/null; then\n';
868
+ script += ' chromium --app="file://$HTML_PATH" --window-size=1280,720 --disable-extensions --no-first-run 2>/dev/null\n';
869
+ script += 'elif command -v microsoft-edge &>/dev/null; then\n';
870
+ script += ' microsoft-edge --app="file://$HTML_PATH" --window-size=1280,720 --disable-extensions --no-first-run 2>/dev/null\n';
871
+ script += 'else\n';
872
+ script += ' xdg-open "$HTML_PATH" 2>/dev/null || open "$HTML_PATH"\n';
873
+ script += 'fi\n';
874
+ fs.writeFileSync(outputPath, script);
875
+ try { fs.chmodSync(outputPath, 0o755); } catch (e) {}
876
+ };
877
+
878
+ ExeBuilder.prototype.cleanup = function () {
879
+ this.removeDirectory(this.tempDir);
880
+ };
881
+
882
+ ExeBuilder.prototype.removeDirectory = function (dir) {
883
+ if (!fs.existsSync(dir)) return;
884
+ var entries = fs.readdirSync(dir, { withFileTypes: true });
885
+ for (var i = 0; i < entries.length; i++) {
886
+ var fullPath = path.join(dir, entries[i].name);
887
+ if (entries[i].isDirectory()) {
888
+ this.removeDirectory(fullPath);
889
+ } else {
890
+ fs.unlinkSync(fullPath);
891
+ }
892
+ }
893
+ fs.rmdirSync(dir);
894
+ };
895
+
896
+ ExeBuilder.prototype.getFileSize = function (filePath) {
897
+ if (!fs.existsSync(filePath)) return 0;
898
+ return fs.statSync(filePath).size;
899
+ };
900
+
901
+ ExeBuilder.prototype.getDirectorySize = function (dir) {
902
+ if (!fs.existsSync(dir)) return 0;
903
+ var total = 0;
904
+ var entries = fs.readdirSync(dir, { withFileTypes: true });
905
+ for (var i = 0; i < entries.length; i++) {
906
+ var fullPath = path.join(dir, entries[i].name);
907
+ if (entries[i].isDirectory()) {
908
+ total += this.getDirectorySize(fullPath);
909
+ } else {
910
+ total += fs.statSync(fullPath).size;
911
+ }
912
+ }
913
+ return total;
914
+ };
915
+
916
+ ExeBuilder.prototype.formatSize = function (bytes) {
917
+ if (bytes < 1024) return bytes + " B";
918
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
919
+ return (bytes / (1024 * 1024)).toFixed(1) + " MB";
920
+ };
921
+
922
+ ExeBuilder.prototype.detectPlatform = function () {
923
+ var p = process.platform;
924
+ if (p === "win32") return "win";
925
+ if (p === "darwin") return "macos";
926
+ return "linux";
927
+ };
928
+
929
+ module.exports = ExeBuilder;