| 329 | |
| 330 | let finished = false; |
| 331 | function finish(success, reason) { |
| 332 | if (finished) return; |
| 333 | finished = true; |
| 334 | cleanup(); |
| 335 | |
| 336 | try { |
| 337 | if (xsdbConfigExisted && originalXsdbConfig !== null) { |
| 338 | require('fs').writeFileSync(xsdbConfigPath, originalXsdbConfig); |
| 339 | } else if (!xsdbConfigExisted && require('fs').existsSync(xsdbConfigPath)) { |
| 340 | require('fs').unlinkSync(xsdbConfigPath); |
| 341 | } |
| 342 | } catch {} |
| 343 | |
| 344 | if (child.stdin && child.stdin.writable) { |
| 345 | try { child.stdin.write("quit\n"); } catch {} |
| 346 | } |
| 347 | |
| 348 | // Allow 500ms grace period for hardware port to cleanly unbind via OS drivers |
| 349 | setTimeout(() => { |
| 350 | try { child.kill('SIGKILL'); } catch {} |
| 351 | try { |
| 352 | require('child_process').execSync('killall -9 mcsim 2>/dev/null', { stdio: 'ignore', timeout: 2000 }); |
| 353 | require('child_process').execSync('killall -9 xsl 2>/dev/null', { stdio: 'ignore', timeout: 2000 }); |
| 354 | require('child_process').execSync('killall -9 serial2xsbug 2>/dev/null', { stdio: 'ignore', timeout: 2000 }); |
| 355 | require('child_process').execSync('pkill -9 -f serial2xsbug 2>/dev/null', { stdio: 'ignore', timeout: 2000 }); |
| 356 | require('child_process').execSync('pkill -9 -f xsbug-log 2>/dev/null', { stdio: 'ignore', timeout: 2000 }); |
| 357 | require('child_process').execSync('pkill -9 -f xsdb 2>/dev/null', { stdio: 'ignore', timeout: 2000 }); |
| 358 | } catch {} |
| 359 | |
| 360 | let lines = outputBuf.replace(/\r/g, '\n').split('\n'); |
| 361 | let cleanLog = []; |
| 362 | for (let i = 0; i < lines.length; i++) { |
| 363 | let line = lines[i]; |
| 364 | let isSpam = line.startsWith('Writing at 0x') || /^\.* \(\d+ %\)$/.test(line) || line === '.'; |
| 365 | if (isSpam) { |
| 366 | let nextLine = (i + 1 < lines.length) ? lines[i+1] : ''; |
| 367 | let nextSpam = nextLine.startsWith('Writing at 0x') || /^\.* \(\d+ %\)$/.test(nextLine) || nextLine === '.'; |
| 368 | if (!nextSpam) cleanLog.push(line); |
| 369 | } else { |
| 370 | cleanLog.push(line); |
| 371 | } |
| 372 | } |
| 373 | let finalLog = cleanLog.join('\n').replace(/\n{3,}/g, '\n\n'); |
| 374 | |
| 375 | resolve({ success, reason, log: finalLog, durationMs: Date.now() - startTime }); |
| 376 | }, 500); |
| 377 | }; |
| 378 | |
| 379 | // Build phase wrapper: 3 minutes max |
| 380 | runTimeout = setTimeout(() => { |