icoa-cli 2.19.99 โ 2.19.101
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/dist/commands/ai4ctf.js +1 -700
- package/dist/commands/connect.js +1 -66
- package/dist/commands/ctf.js +1 -620
- package/dist/commands/ctf4ai-demo.js +1 -525
- package/dist/commands/env.js +1 -737
- package/dist/commands/exam.js +1 -2353
- package/dist/commands/files.js +1 -52
- package/dist/commands/hint.js +1 -119
- package/dist/commands/lang.js +1 -155
- package/dist/commands/log.js +1 -163
- package/dist/commands/note.js +1 -32
- package/dist/commands/ref.js +1 -63
- package/dist/commands/setup.js +1 -103
- package/dist/commands/shell.js +1 -55
- package/dist/commands/theme.js +1 -50
- package/dist/index.js +1 -225
- package/dist/lib/access.js +1 -246
- package/dist/lib/budget.js +1 -42
- package/dist/lib/colors.js +1 -21
- package/dist/lib/config.js +1 -60
- package/dist/lib/ctfd-client.js +1 -274
- package/dist/lib/demo-exam.js +1 -249
- package/dist/lib/demo-flags.js +1 -27
- package/dist/lib/demo-stats.js +1 -65
- package/dist/lib/exam-client.js +1 -57
- package/dist/lib/exam-setup.js +1 -23
- package/dist/lib/exam-state.js +1 -112
- package/dist/lib/gemini.js +1 -235
- package/dist/lib/i18n.js +1 -273
- package/dist/lib/log-sync.js +1 -110
- package/dist/lib/logger.js +1 -59
- package/dist/lib/paper-upgrade.js +1 -117
- package/dist/lib/platform.js +1 -86
- package/dist/lib/sandbox.js +1 -93
- package/dist/lib/terminal.js +1 -49
- package/dist/lib/theme.js +1 -108
- package/dist/lib/translation.js +1 -66
- package/dist/lib/ui.js +1 -80
- package/dist/lib/update-check.js +1 -102
- package/dist/postinstall.js +1 -48
- package/dist/repl.js +1 -1259
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -38
- package/package.json +6 -2
- package/translations/sw/i18n-snippet.ts +1 -0
package/dist/commands/exam.js
CHANGED
|
@@ -1,2353 +1 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { confirm } from '@inquirer/prompts';
|
|
3
|
-
import { ExamClient } from '../lib/exam-client.js';
|
|
4
|
-
import { getConfig, saveConfig } from '../lib/config.js';
|
|
5
|
-
import { getExamState, saveExamState, clearExamState, getExamDeadline } from '../lib/exam-state.js';
|
|
6
|
-
import { getDemoStats, recordDemoAttempt, saveRetryQueue, getRetryQueue, clearRetryQueue } from '../lib/demo-stats.js';
|
|
7
|
-
import { logCommand } from '../lib/logger.js';
|
|
8
|
-
import { printSuccess, printError, printWarning, printInfo, printTable, printHeader, printKeyValue, createSpinner, formatCountdown, } from '../lib/ui.js';
|
|
9
|
-
import { t } from '../lib/i18n.js';
|
|
10
|
-
import { getDeviceFingerprint } from '../lib/access.js';
|
|
11
|
-
import { getExamSetup, saveExamSetup, isExamSetupComplete } from '../lib/exam-setup.js';
|
|
12
|
-
import { execSync } from 'node:child_process';
|
|
13
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
14
|
-
// Fire-and-forget event POST to /api/icoa/demo-stats. Used for demo-flow
|
|
15
|
-
// choice points (demo enter, retry, back) where we want a server-side
|
|
16
|
-
// timestamp of the user's decision but have nothing more to report.
|
|
17
|
-
function reportDemoEvent(type, extra = {}) {
|
|
18
|
-
const config = getConfig();
|
|
19
|
-
fetch('https://practice.icoa2026.au/api/icoa/demo-stats', {
|
|
20
|
-
method: 'POST',
|
|
21
|
-
headers: { 'Content-Type': 'application/json' },
|
|
22
|
-
body: JSON.stringify({
|
|
23
|
-
type,
|
|
24
|
-
lang: config.language || 'en',
|
|
25
|
-
timestamp: new Date().toISOString(),
|
|
26
|
-
...extra,
|
|
27
|
-
}),
|
|
28
|
-
signal: AbortSignal.timeout(5000),
|
|
29
|
-
}).catch(() => { });
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Skippable intro animation for first-time demo users.
|
|
33
|
-
* Resolves immediately on any keypress. Max 30s auto-advance.
|
|
34
|
-
*/
|
|
35
|
-
async function playDemoIntro() {
|
|
36
|
-
let skipped = false;
|
|
37
|
-
const stdin = process.stdin;
|
|
38
|
-
const onKey = () => { skipped = true; };
|
|
39
|
-
const rawSupported = stdin.isTTY && typeof stdin.setRawMode === 'function';
|
|
40
|
-
if (rawSupported) {
|
|
41
|
-
stdin.setRawMode(true);
|
|
42
|
-
stdin.resume();
|
|
43
|
-
stdin.once('data', onKey);
|
|
44
|
-
}
|
|
45
|
-
const waitOrSkip = async (ms) => {
|
|
46
|
-
const step = 80;
|
|
47
|
-
let waited = 0;
|
|
48
|
-
while (waited < ms && !skipped) {
|
|
49
|
-
await sleep(step);
|
|
50
|
-
waited += step;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
const cleanup = () => {
|
|
54
|
-
if (rawSupported) {
|
|
55
|
-
stdin.removeListener('data', onKey);
|
|
56
|
-
try {
|
|
57
|
-
stdin.setRawMode(false);
|
|
58
|
-
}
|
|
59
|
-
catch { }
|
|
60
|
-
stdin.pause();
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
try {
|
|
64
|
-
console.clear();
|
|
65
|
-
console.log();
|
|
66
|
-
console.log(chalk.gray(' ') + chalk.dim('(press any key to skip)'));
|
|
67
|
-
console.log();
|
|
68
|
-
console.log(chalk.bold.white(' โโโ โโโโโโโ โโโโโโโ โโโโโโ'));
|
|
69
|
-
console.log(chalk.bold.white(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
70
|
-
console.log(chalk.bold.white(' โโโโโโ โโโ โโโโโโโโโโโ'));
|
|
71
|
-
console.log(chalk.bold.white(' โโโโโโ โโโ โโโโโโโโโโโ'));
|
|
72
|
-
console.log(chalk.bold.white(' โโโโโโโโโโโโโโโโโโโโโโโ โโโ'));
|
|
73
|
-
console.log(chalk.bold.white(' โโโ โโโโโโโ โโโโโโโ โโโ โโโ'));
|
|
74
|
-
console.log();
|
|
75
|
-
console.log(chalk.bold.yellow(' International Cyber Olympiad in AI'));
|
|
76
|
-
console.log(chalk.gray(' Sydney, Australia ยท Jun 27 โ Jul 2, 2026'));
|
|
77
|
-
await waitOrSkip(2500);
|
|
78
|
-
if (skipped)
|
|
79
|
-
return;
|
|
80
|
-
console.log();
|
|
81
|
-
console.log(chalk.white(' What you\'ll do today:'));
|
|
82
|
-
console.log();
|
|
83
|
-
await waitOrSkip(600);
|
|
84
|
-
if (skipped)
|
|
85
|
-
return;
|
|
86
|
-
console.log(chalk.cyan(' โฃ ') + chalk.white('Answer 10 quick questions') + chalk.gray(' (now, ~5 min)'));
|
|
87
|
-
await waitOrSkip(700);
|
|
88
|
-
if (skipped)
|
|
89
|
-
return;
|
|
90
|
-
console.log(chalk.green(' โฃ ') + chalk.white('Team up with AI to solve CTFs') + chalk.gray(' (ai4ctf)'));
|
|
91
|
-
await waitOrSkip(700);
|
|
92
|
-
if (skipped)
|
|
93
|
-
return;
|
|
94
|
-
console.log(chalk.red(' โฃ ') + chalk.white('Hack the AI\'s guardrails') + chalk.gray(' (ctf4ai)'));
|
|
95
|
-
await waitOrSkip(900);
|
|
96
|
-
if (skipped)
|
|
97
|
-
return;
|
|
98
|
-
console.log();
|
|
99
|
-
console.log(chalk.bold.white(' Your terminal ') + chalk.bold.yellow('IS') + chalk.bold.white(' the competition.'));
|
|
100
|
-
console.log();
|
|
101
|
-
await waitOrSkip(1500);
|
|
102
|
-
if (skipped)
|
|
103
|
-
return;
|
|
104
|
-
console.log(chalk.gray(' ยท 110 CTF tools pre-configured'));
|
|
105
|
-
await waitOrSkip(400);
|
|
106
|
-
if (skipped)
|
|
107
|
-
return;
|
|
108
|
-
console.log(chalk.gray(' ยท 15 languages, real-time AI translation'));
|
|
109
|
-
await waitOrSkip(400);
|
|
110
|
-
if (skipped)
|
|
111
|
-
return;
|
|
112
|
-
console.log(chalk.gray(' ยท 15,000+ concurrent participants'));
|
|
113
|
-
await waitOrSkip(800);
|
|
114
|
-
if (skipped)
|
|
115
|
-
return;
|
|
116
|
-
console.log();
|
|
117
|
-
console.log(chalk.bold.cyan(' Press any key to start demo...'));
|
|
118
|
-
await waitOrSkip(4000);
|
|
119
|
-
}
|
|
120
|
-
finally {
|
|
121
|
-
cleanup();
|
|
122
|
-
console.clear();
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
function drawProgress(percent, label) {
|
|
126
|
-
const width = 30;
|
|
127
|
-
const filled = Math.round((percent / 100) * width);
|
|
128
|
-
const empty = width - filled;
|
|
129
|
-
const bar = chalk.green('โ'.repeat(filled)) + chalk.gray('โ'.repeat(empty));
|
|
130
|
-
const pct = String(percent).padStart(3) + '%';
|
|
131
|
-
process.stdout.write(`\r\x1b[2K ${bar} ${pct} ${chalk.gray(label)}`);
|
|
132
|
-
}
|
|
133
|
-
function requireExamConnection() {
|
|
134
|
-
const config = getConfig();
|
|
135
|
-
if (!config.ctfdUrl || !config.token) {
|
|
136
|
-
printError('Not connected. Run: join <url>');
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
return new ExamClient(config.ctfdUrl, config.token);
|
|
140
|
-
}
|
|
141
|
-
function checkTimeRemaining() {
|
|
142
|
-
const deadline = getExamDeadline();
|
|
143
|
-
if (!deadline)
|
|
144
|
-
return true;
|
|
145
|
-
if (new Date() >= deadline) {
|
|
146
|
-
const state = getExamState();
|
|
147
|
-
if (state) {
|
|
148
|
-
const answered = Object.keys(state.answers).length;
|
|
149
|
-
if (state.session.examId === 'demo-free' || answered === 0) {
|
|
150
|
-
// Demo or unanswered expired exam โ auto-clear to avoid deadlock
|
|
151
|
-
clearExamState();
|
|
152
|
-
printWarning('Exam expired and cleared.');
|
|
153
|
-
printInfo('Type: demo to start again.');
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
printError('Time expired! Use "exam submit" to submit your answers.');
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
function printTimeRemaining() {
|
|
164
|
-
const deadline = getExamDeadline();
|
|
165
|
-
if (deadline) {
|
|
166
|
-
const now = new Date();
|
|
167
|
-
if (now < deadline) {
|
|
168
|
-
printKeyValue('Time Remaining', chalk.yellow.bold(formatCountdown(deadline)));
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
printKeyValue('Time', chalk.red.bold('EXPIRED'));
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// One-shot urgent warnings: show the first time we cross a threshold, then
|
|
176
|
-
// never again in the same session. Tracked on ExamState so it survives REPL
|
|
177
|
-
// restarts and resume. Called from printQuestion so it fires naturally as the
|
|
178
|
-
// user navigates (no background timer needed).
|
|
179
|
-
function printTimeWarningIfNeeded(state) {
|
|
180
|
-
const deadline = getExamDeadline();
|
|
181
|
-
if (!deadline)
|
|
182
|
-
return;
|
|
183
|
-
const remainingMs = deadline.getTime() - Date.now();
|
|
184
|
-
if (remainingMs <= 0)
|
|
185
|
-
return;
|
|
186
|
-
const minutes = remainingMs / 60000;
|
|
187
|
-
const shown = new Set(state.shownWarnings || []);
|
|
188
|
-
let warn = null;
|
|
189
|
-
if (minutes <= 1 && !shown.has('t1')) {
|
|
190
|
-
warn = {
|
|
191
|
-
key: 't1',
|
|
192
|
-
text: [
|
|
193
|
-
chalk.red.bold(' โ LESS THAN 1 MINUTE LEFT'),
|
|
194
|
-
chalk.red(' Submit now with: ') + chalk.bold('exam submit'),
|
|
195
|
-
],
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
else if (minutes <= 5 && !shown.has('t5')) {
|
|
199
|
-
warn = {
|
|
200
|
-
key: 't5',
|
|
201
|
-
text: [
|
|
202
|
-
chalk.red.bold(' โ 5 minutes remaining'),
|
|
203
|
-
chalk.yellow(' Wrap up and submit: ') + chalk.bold('exam submit'),
|
|
204
|
-
],
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
else if (minutes <= 10 && !shown.has('t10')) {
|
|
208
|
-
warn = {
|
|
209
|
-
key: 't10',
|
|
210
|
-
text: [
|
|
211
|
-
chalk.yellow.bold(' โฐ 10 minutes remaining'),
|
|
212
|
-
chalk.gray(' Review unanswered questions: ') + chalk.white('exam review'),
|
|
213
|
-
],
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
if (!warn)
|
|
217
|
-
return;
|
|
218
|
-
console.log();
|
|
219
|
-
console.log(chalk.red(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
220
|
-
for (const line of warn.text)
|
|
221
|
-
console.log(line);
|
|
222
|
-
console.log(chalk.red(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
223
|
-
shown.add(warn.key);
|
|
224
|
-
state.shownWarnings = Array.from(shown);
|
|
225
|
-
saveExamState(state);
|
|
226
|
-
}
|
|
227
|
-
// Pacing hints at Q10 (time check) and Q30 (section complete). Real exam only.
|
|
228
|
-
// Keyed by question number so they fire once per exam on any render.
|
|
229
|
-
function printPacingHint(state, currentQ) {
|
|
230
|
-
if (state.session.examId === 'demo-free')
|
|
231
|
-
return;
|
|
232
|
-
const shown = new Set(state.shownWarnings || []);
|
|
233
|
-
const total = Number(state.session.questionCount || 40);
|
|
234
|
-
const deadline = getExamDeadline();
|
|
235
|
-
// Q10: gentle time check (only if exam is 40+ questions, not 10 demo)
|
|
236
|
-
if (currentQ === 10 && total >= 40 && !shown.has('p10')) {
|
|
237
|
-
const remainingMin = deadline ? Math.max(0, Math.round((deadline.getTime() - Date.now()) / 60000)) : null;
|
|
238
|
-
console.log();
|
|
239
|
-
console.log(chalk.gray(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
240
|
-
console.log(chalk.cyan(' โฑ Time check ') + chalk.gray(`โ you're 1/4 through the exam`));
|
|
241
|
-
if (remainingMin !== null) {
|
|
242
|
-
console.log(chalk.gray(` ${remainingMin} minutes left. Keep the pace steady.`));
|
|
243
|
-
}
|
|
244
|
-
console.log(chalk.gray(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
245
|
-
shown.add('p10');
|
|
246
|
-
state.shownWarnings = Array.from(shown);
|
|
247
|
-
saveExamState(state);
|
|
248
|
-
}
|
|
249
|
-
// Q30: MCQ section complete milestone
|
|
250
|
-
if (currentQ === 30 && total >= 40 && !shown.has('p30')) {
|
|
251
|
-
const answered = Object.keys(state.answers || {}).length;
|
|
252
|
-
const mcqAnswered = Object.keys(state.answers || {}).filter((n) => Number(n) <= 30).length;
|
|
253
|
-
console.log();
|
|
254
|
-
console.log(chalk.green(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
255
|
-
console.log(chalk.bold.green(' โ MCQ section complete โ well done!'));
|
|
256
|
-
console.log(chalk.gray(` ${mcqAnswered}/30 multiple-choice answered`));
|
|
257
|
-
console.log(chalk.white(' โ Practical section begins at Q31.'));
|
|
258
|
-
console.log(chalk.gray(' Budget ~2 min per practical question. You can come back with: next / prev.'));
|
|
259
|
-
console.log(chalk.green(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
260
|
-
shown.add('p30');
|
|
261
|
-
state.shownWarnings = Array.from(shown);
|
|
262
|
-
saveExamState(state);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
// Bookmark / flag-for-review helpers
|
|
266
|
-
function isBookmarked(state, n) {
|
|
267
|
-
return Array.isArray(state.bookmarks) && state.bookmarks.includes(n);
|
|
268
|
-
}
|
|
269
|
-
function toggleBookmark(state, n) {
|
|
270
|
-
const arr = Array.isArray(state.bookmarks) ? [...state.bookmarks] : [];
|
|
271
|
-
const idx = arr.indexOf(n);
|
|
272
|
-
if (idx >= 0) {
|
|
273
|
-
arr.splice(idx, 1);
|
|
274
|
-
state.bookmarks = arr;
|
|
275
|
-
saveExamState(state);
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
|
-
arr.push(n);
|
|
279
|
-
arr.sort((a, b) => a - b);
|
|
280
|
-
state.bookmarks = arr;
|
|
281
|
-
saveExamState(state);
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
function printHowToPlay() {
|
|
285
|
-
console.log(chalk.gray(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
286
|
-
console.log(chalk.white(` ${t('howToPlay')}`));
|
|
287
|
-
console.log(chalk.yellow(' A/B/C/D') + chalk.gray(` ${t('htpAnswer')}`));
|
|
288
|
-
console.log(chalk.yellow(' help') + chalk.gray(` ${t('htpHelp')}`));
|
|
289
|
-
console.log(chalk.yellow(' next') + chalk.gray(' / ') + chalk.yellow('prev') + chalk.gray(` ${t('htpNav')}`));
|
|
290
|
-
console.log(chalk.yellow(' more help') + chalk.gray(` ${t('htpMoreHelp')}`));
|
|
291
|
-
console.log(chalk.yellow(' back') + chalk.gray(` ${t('htpBack')}`));
|
|
292
|
-
console.log(chalk.yellow(' lang') + chalk.gray(` ${t('htpLang')}`));
|
|
293
|
-
console.log(chalk.gray(' es ยท zh ยท ja ยท ko ยท ar ยท fr ยท pt'));
|
|
294
|
-
console.log(chalk.gray(' ru ยท hi ยท de ยท id ยท th ยท vi ยท tr'));
|
|
295
|
-
console.log(chalk.gray(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
296
|
-
}
|
|
297
|
-
// Australian easter eggs at percentage-based positions. The messages were
|
|
298
|
-
// originally written for a 15-question exam; we now map them by percentage
|
|
299
|
-
// so they fire at the right moment regardless of question count (10 or 15).
|
|
300
|
-
function getEasterEgg(current, total) {
|
|
301
|
-
const EGGS = [
|
|
302
|
-
{ pct: 0.20, emoji: '๐๏ธ', key: 'egg3' }, // Great start
|
|
303
|
-
{ pct: 0.33, emoji: '๐จ', key: 'egg5' }, // 1/3 done
|
|
304
|
-
{ pct: 0.50, emoji: '๐', key: 'egg7' }, // Keep going (halfway)
|
|
305
|
-
{ pct: 0.60, emoji: '๐ฆ', key: 'egg9' }, // Past halfway
|
|
306
|
-
{ pct: 0.80, emoji: '๐๏ธ', key: 'egg11' }, // Almost there
|
|
307
|
-
{ pct: 0.87, emoji: '๐ฆ', key: 'egg13' }, // 2 more to go
|
|
308
|
-
{ pct: 1.00, emoji: '๐', key: 'egg15' }, // All done
|
|
309
|
-
];
|
|
310
|
-
const seen = new Set();
|
|
311
|
-
for (const egg of EGGS) {
|
|
312
|
-
const targetQ = Math.round(egg.pct * total);
|
|
313
|
-
if (targetQ < 1 || seen.has(targetQ))
|
|
314
|
-
continue;
|
|
315
|
-
seen.add(targetQ);
|
|
316
|
-
if (current === targetQ)
|
|
317
|
-
return { emoji: egg.emoji, text: t(egg.key) };
|
|
318
|
-
}
|
|
319
|
-
return undefined;
|
|
320
|
-
}
|
|
321
|
-
function printQuestionProgress(current, total, answered) {
|
|
322
|
-
const width = 30;
|
|
323
|
-
const filled = Math.round((current / total) * width);
|
|
324
|
-
const empty = width - filled;
|
|
325
|
-
const bar = chalk.cyan('โ'.repeat(filled)) + chalk.gray('โ'.repeat(empty));
|
|
326
|
-
const pct = Math.round((current / total) * 100);
|
|
327
|
-
console.log();
|
|
328
|
-
console.log(` ${bar} ${chalk.white.bold(`${current}`)}${chalk.gray(`/${total}`)} ${chalk.gray(`(${answered} answered)`)} ${chalk.gray(`${pct}%`)}`);
|
|
329
|
-
// Time remaining โ colour-coded countdown so contestants always see where
|
|
330
|
-
// they stand. Authoritative clock lives on the server (confirmedAt +
|
|
331
|
-
// duration enforced at submit time); this is a display helper.
|
|
332
|
-
const deadline = getExamDeadline();
|
|
333
|
-
if (deadline) {
|
|
334
|
-
const remainingSec = Math.max(0, Math.round((deadline.getTime() - Date.now()) / 1000));
|
|
335
|
-
const mm = Math.floor(remainingSec / 60);
|
|
336
|
-
const ss = remainingSec % 60;
|
|
337
|
-
const timeStr = `${mm}:${String(ss).padStart(2, '0')}`;
|
|
338
|
-
const color = remainingSec <= 60 ? chalk.red.bold
|
|
339
|
-
: remainingSec <= 300 ? chalk.red
|
|
340
|
-
: remainingSec <= 600 ? chalk.yellow
|
|
341
|
-
: chalk.gray;
|
|
342
|
-
console.log(` ${chalk.gray('โฑ Time remaining:')} ${color(timeStr)} ${chalk.gray('(server-authoritative)')}`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
// Help budget per exam.md ยง3: 20 base + 10 hidden bonus (unlocked via `more help`).
|
|
346
|
-
// Demo still uses the lighter 5 + 3 set via _helpMax overrides at demo start.
|
|
347
|
-
const HELP_BASE = 20;
|
|
348
|
-
const HELP_WITH_BONUS = 30;
|
|
349
|
-
function getHelpState(state) {
|
|
350
|
-
return {
|
|
351
|
-
used: state._helpUsed || 0,
|
|
352
|
-
max: state._helpMax || HELP_BASE,
|
|
353
|
-
perQ: state._helpPerQ || {},
|
|
354
|
-
eliminated: state._eliminated || {},
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
// Section intros fire once per REPL session, first time the user enters
|
|
358
|
-
// that section. State-persisted via shownWarnings keys 's_ai4ctf' / 's_ctf4ai'
|
|
359
|
-
// so jumping back and forth doesn't re-fire them.
|
|
360
|
-
function printSectionIntro(state, currentQ) {
|
|
361
|
-
if (state.session.examId === 'demo-free')
|
|
362
|
-
return;
|
|
363
|
-
const total = Number(state.session.questionCount || 40);
|
|
364
|
-
// Only 40-question exams have the three-section structure
|
|
365
|
-
if (total < 40)
|
|
366
|
-
return;
|
|
367
|
-
const shown = new Set(state.shownWarnings || []);
|
|
368
|
-
// โโโ AI4CTF section intro (Q31 = first practical, AI-as-teammate) โโโ
|
|
369
|
-
if (currentQ >= 31 && currentQ <= 38 && !shown.has('s_ai4ctf')) {
|
|
370
|
-
console.log();
|
|
371
|
-
console.log(chalk.green(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
372
|
-
console.log(chalk.bold.green(' ๐ Section 2: AI4CTF โ AI is your teammate'));
|
|
373
|
-
console.log(chalk.green(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
374
|
-
console.log();
|
|
375
|
-
console.log(chalk.white(' Welcome to the practical CTF section. Real'));
|
|
376
|
-
console.log(chalk.white(' security tasks โ crypto, web, forensics, pwn.'));
|
|
377
|
-
console.log();
|
|
378
|
-
console.log(chalk.bold.white(' Q31โ38 (8 questions ยท 6 pts each)'));
|
|
379
|
-
console.log(chalk.gray(' Solve ') + chalk.bold('with') + chalk.gray(' AI by your side. AI is your teammate.'));
|
|
380
|
-
console.log();
|
|
381
|
-
console.log(chalk.bold.white(' How to work'));
|
|
382
|
-
console.log(chalk.gray(' Enter chat: ') + chalk.bold.green('ai4ctf') + chalk.gray(' โ ') + chalk.magenta('ai4ctf>') + chalk.gray(' prompt, just like the demo'));
|
|
383
|
-
console.log(chalk.gray(' Inside chat: ') + chalk.cyan('hint a / b / c') + chalk.gray(' ยท ') + chalk.cyan('submit ICOA{...}') + chalk.gray(' ยท ') + chalk.cyan('!python3 ...'));
|
|
384
|
-
console.log(chalk.gray(' Free chat: any message โ AI teammate'));
|
|
385
|
-
console.log(chalk.gray(' Exit chat: ') + chalk.cyan('exit') + chalk.gray(' โ back to exam, navigate with ') + chalk.cyan('next / prev'));
|
|
386
|
-
console.log();
|
|
387
|
-
console.log(chalk.bold.white(' Budget (shared across Q31โ38)'));
|
|
388
|
-
console.log(chalk.gray(' AI tokens: ') + chalk.white('25,000') + chalk.gray(' for this section'));
|
|
389
|
-
console.log(chalk.gray(' Hints A/B/C: ') + chalk.white('pre-written per question'));
|
|
390
|
-
console.log();
|
|
391
|
-
console.log(chalk.yellow(' Time still counting down. Budget ~2 min per question.'));
|
|
392
|
-
console.log(chalk.green(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
393
|
-
console.log();
|
|
394
|
-
shown.add('s_ai4ctf');
|
|
395
|
-
state.shownWarnings = Array.from(shown);
|
|
396
|
-
saveExamState(state);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
// โโโ CTF4AI section intro (Q39 = adversarial AI, highest-value questions) โโโ
|
|
400
|
-
if (currentQ >= 39 && !shown.has('s_ctf4ai')) {
|
|
401
|
-
console.log();
|
|
402
|
-
console.log(chalk.red(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
403
|
-
console.log(chalk.bold.red(' ๐ฏ Section 3: CTF4AI โ Challenge the AI'));
|
|
404
|
-
console.log(chalk.red(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
405
|
-
console.log();
|
|
406
|
-
console.log(chalk.white(' The final section flips the script: instead of'));
|
|
407
|
-
console.log(chalk.white(' using AI, you\'re ') + chalk.bold('attacking') + chalk.white(' AI systems.'));
|
|
408
|
-
console.log();
|
|
409
|
-
console.log(chalk.bold.white(' Q39โ40 (2 questions ยท 16 pts each โ highest value!)'));
|
|
410
|
-
console.log(chalk.gray(' Prompt injection ยท adversarial analysis ยท AI auditing'));
|
|
411
|
-
console.log();
|
|
412
|
-
console.log(chalk.bold.white(' How to attack'));
|
|
413
|
-
console.log(chalk.gray(' Enter chat: ') + chalk.bold.red('ctf4ai') + chalk.gray(' โ ') + chalk.red('ctf4ai>') + chalk.gray(' prompt, same shape as demo'));
|
|
414
|
-
console.log(chalk.gray(' Inside chat: ') + chalk.cyan('hint a / b / c') + chalk.gray(' ยท ') + chalk.cyan('submit ICOA{...}') + chalk.gray(' ยท ') + chalk.cyan('!python3 ...'));
|
|
415
|
-
console.log(chalk.gray(' Messages โ AI target โ craft prompts to break its rules.'));
|
|
416
|
-
console.log();
|
|
417
|
-
console.log(chalk.bold.white(' Key differences from AI4CTF'));
|
|
418
|
-
console.log(chalk.gray(' ยท AI is your ') + chalk.red('target') + chalk.gray(', not your teammate'));
|
|
419
|
-
console.log(chalk.gray(' ยท Separate budget: ') + chalk.white('25,000 tokens') + chalk.gray(' shared across Q39โ40'));
|
|
420
|
-
console.log();
|
|
421
|
-
console.log(chalk.yellow(' These are the hardest questions. Worth 21% of total score.'));
|
|
422
|
-
console.log(chalk.yellow(' If time is tight, skim Q39/40 first to decide attack order.'));
|
|
423
|
-
console.log(chalk.red(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
424
|
-
console.log();
|
|
425
|
-
shown.add('s_ctf4ai');
|
|
426
|
-
state.shownWarnings = Array.from(shown);
|
|
427
|
-
saveExamState(state);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
function printQuestion(q, answer) {
|
|
432
|
-
const state = getExamState();
|
|
433
|
-
const total = Number(state?.session.questionCount || 30);
|
|
434
|
-
const answered = Object.keys(state?.answers || {}).length;
|
|
435
|
-
const help = getHelpState(state);
|
|
436
|
-
const eliminated = help.eliminated[q.number] || [];
|
|
437
|
-
const isPractical = q.type === 'ai4ctf' || q.type === 'ctf4ai' || (q.options && !q.options.A && !q.options.B);
|
|
438
|
-
// Pacing hints (Q10 time check, Q30 section complete) โ real exam only
|
|
439
|
-
if (state)
|
|
440
|
-
printPacingHint(state, q.number);
|
|
441
|
-
// Section intros (AI4CTF at Q31, CTF4AI at Q39) โ real exam only
|
|
442
|
-
if (state && isPractical)
|
|
443
|
-
printSectionIntro(state, q.number);
|
|
444
|
-
// Urgent countdown warnings (10 / 5 / 1 minutes remaining)
|
|
445
|
-
if (state)
|
|
446
|
-
printTimeWarningIfNeeded(state);
|
|
447
|
-
// Progress bar
|
|
448
|
-
printQuestionProgress(q.number, total, answered);
|
|
449
|
-
// Bookmark indicator โ yellow โ
after the progress line if current q is marked
|
|
450
|
-
if (state && isBookmarked(state, q.number)) {
|
|
451
|
-
console.log(chalk.yellow(' โ
Marked for review'));
|
|
452
|
-
}
|
|
453
|
-
// Easter egg (position calculated by percentage of total)
|
|
454
|
-
const egg = getEasterEgg(q.number, total);
|
|
455
|
-
if (egg) {
|
|
456
|
-
console.log(chalk.yellow(` ${egg.emoji} ${egg.text}`));
|
|
457
|
-
}
|
|
458
|
-
console.log();
|
|
459
|
-
if (q.category)
|
|
460
|
-
console.log(chalk.cyan(` [${q.category}]`));
|
|
461
|
-
if (q.points && q.points > 2) {
|
|
462
|
-
console.log(chalk.cyan(` [${q.points} points]`));
|
|
463
|
-
}
|
|
464
|
-
if (isPractical) {
|
|
465
|
-
// Practical question display
|
|
466
|
-
const displayText = q.description || q.text;
|
|
467
|
-
console.log(chalk.bold.white(` Q${q.number}. `) + chalk.white(displayText.split('\n')[0]));
|
|
468
|
-
// Show remaining lines with proper indentation
|
|
469
|
-
const lines = displayText.split('\n').slice(1);
|
|
470
|
-
for (const line of lines) {
|
|
471
|
-
console.log(chalk.white(` ${line}`));
|
|
472
|
-
}
|
|
473
|
-
console.log();
|
|
474
|
-
if (answer) {
|
|
475
|
-
console.log(chalk.green(` Your answer: ${answer}`));
|
|
476
|
-
console.log();
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
else {
|
|
480
|
-
// MCQ display
|
|
481
|
-
console.log(chalk.bold.white(` Q${q.number}. `) + chalk.white(q.text));
|
|
482
|
-
console.log();
|
|
483
|
-
for (const key of ['A', 'B', 'C', 'D']) {
|
|
484
|
-
const isEliminated = eliminated.includes(key);
|
|
485
|
-
const selected = answer === key;
|
|
486
|
-
if (isEliminated) {
|
|
487
|
-
console.log(chalk.gray.strikethrough(` ${key}. ${q.options[key]}`) + chalk.red(` (${t('wrong')})`));
|
|
488
|
-
}
|
|
489
|
-
else if (selected) {
|
|
490
|
-
console.log(chalk.green.bold(` โธ ${key}. ${q.options[key]}`));
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
console.log(chalk.gray(` ${key}.`) + ' ' + chalk.white(q.options[key]));
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
console.log();
|
|
497
|
-
}
|
|
498
|
-
// Q2 tutorial hint
|
|
499
|
-
const isDemo = state?.session.examId === 'demo-free';
|
|
500
|
-
const q2Helps = help.perQ[2] || 0;
|
|
501
|
-
if (isDemo && q.number === 2 && q2Helps === 0) {
|
|
502
|
-
console.log(chalk.yellow.bold(` ${t('qTutorial')}`));
|
|
503
|
-
console.log();
|
|
504
|
-
}
|
|
505
|
-
// Full menu
|
|
506
|
-
const remaining = help.max - help.used;
|
|
507
|
-
console.log(chalk.gray(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
508
|
-
if (isPractical) {
|
|
509
|
-
const isCtf4aiQ = state && state.session.examId !== 'demo-free' && q.number >= 39;
|
|
510
|
-
const isAi4ctfExamQ = state && state.session.examId !== 'demo-free' && q.number >= 31 && q.number <= 38;
|
|
511
|
-
if (isAi4ctfExamQ) {
|
|
512
|
-
console.log(chalk.bold.green(' ai4ctf') + chalk.gray(' enter AI4CTF chat for this question (recommended)'));
|
|
513
|
-
}
|
|
514
|
-
else if (isCtf4aiQ) {
|
|
515
|
-
console.log(chalk.bold.red(' ctf4ai') + chalk.gray(' enter CTF4AI chat โ attack the AI target (recommended)'));
|
|
516
|
-
}
|
|
517
|
-
console.log(chalk.yellow(' exam answer <n> ICOA{...}') + chalk.gray(' submit flag directly'));
|
|
518
|
-
console.log(chalk.yellow(' !python3') + chalk.gray(' start Python shell'));
|
|
519
|
-
}
|
|
520
|
-
else {
|
|
521
|
-
// "used up" prompt shows until the bonus tier is reached; after that
|
|
522
|
-
// (already unlocked), show the full-used final state.
|
|
523
|
-
const bonusUnlocked = help.max >= HELP_WITH_BONUS;
|
|
524
|
-
const helpLabel = remaining > 0
|
|
525
|
-
? chalk.gray(`${t('helpRemove')} `) + chalk.yellow(`(${remaining}/${help.max})`)
|
|
526
|
-
: (!bonusUnlocked ? chalk.gray(`${t('helpUsedUp')}`) : chalk.gray(`${t('helpAllUsed')} (${help.used}/${help.max})`));
|
|
527
|
-
console.log(chalk.yellow(' A/B/C/D') + chalk.gray(` ${t('answerThis')}`));
|
|
528
|
-
console.log(chalk.yellow(' help') + ' ' + helpLabel);
|
|
529
|
-
}
|
|
530
|
-
console.log(chalk.yellow(' next') + chalk.gray(' / ') + chalk.yellow('prev') + chalk.gray(` ${t('htpNav')}`));
|
|
531
|
-
console.log(chalk.yellow(` exam q 1..${total}`) + chalk.gray(` ${t('htpJump')}`));
|
|
532
|
-
console.log(chalk.yellow(' mark') + chalk.gray(` ${t('htpMark')}`));
|
|
533
|
-
console.log(chalk.yellow(' exam review') + chalk.gray(` ${t('htpReview')}`));
|
|
534
|
-
console.log(chalk.bold.yellow(' exam submit') + chalk.gray(` ${t('htpSubmit')}`));
|
|
535
|
-
console.log(chalk.yellow(' back') + chalk.gray(` ${t('htpBack')}`));
|
|
536
|
-
console.log(chalk.yellow(' lang') + chalk.gray(` ${t('htpLang')}`));
|
|
537
|
-
console.log(chalk.gray(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
538
|
-
}
|
|
539
|
-
export function registerExamCommand(program) {
|
|
540
|
-
const exam = program.command('exam').description('National selection exam');
|
|
541
|
-
// โโโ exam nations โโโ
|
|
542
|
-
exam
|
|
543
|
-
.command('nations')
|
|
544
|
-
.description('View all participating countries')
|
|
545
|
-
.action(() => {
|
|
546
|
-
logCommand('exam nations');
|
|
547
|
-
console.log();
|
|
548
|
-
printHeader('ICOA Participating Countries and Regions');
|
|
549
|
-
console.log();
|
|
550
|
-
const nations = [
|
|
551
|
-
['AU', 'Australia'], ['BR', 'Brazil'], ['CN', 'China'],
|
|
552
|
-
['DE', 'Germany'], ['EG', 'Egypt'], ['FR', 'France'],
|
|
553
|
-
['ID', 'Indonesia'], ['IN', 'India'], ['JP', 'Japan'],
|
|
554
|
-
['KR', 'South Korea'], ['MY', 'Malaysia'], ['PE', 'Peru'],
|
|
555
|
-
['PH', 'Philippines'], ['RU', 'Russia'], ['SA', 'Saudi Arabia'],
|
|
556
|
-
['SG', 'Singapore'], ['TH', 'Thailand'], ['TR', 'Turkey'],
|
|
557
|
-
['VN', 'Vietnam'], ['ZA', 'South Africa'],
|
|
558
|
-
];
|
|
559
|
-
for (let i = 0; i < nations.length; i += 3) {
|
|
560
|
-
const row = nations.slice(i, i + 3)
|
|
561
|
-
.map(([code, name]) => ` ${chalk.cyan(code)} ${chalk.gray(name.padEnd(16))}`)
|
|
562
|
-
.join('');
|
|
563
|
-
console.log(row);
|
|
564
|
-
}
|
|
565
|
-
console.log();
|
|
566
|
-
console.log(chalk.gray(' 40+ countries and regions represented'));
|
|
567
|
-
console.log(chalk.gray(' New member? Contact: accreditation@icoa2026.au'));
|
|
568
|
-
console.log();
|
|
569
|
-
console.log(chalk.white(' Check exams for your country:'));
|
|
570
|
-
console.log(chalk.cyan(' exam list AU') + chalk.gray(' exam list PE') + chalk.gray(' exam list CN'));
|
|
571
|
-
console.log();
|
|
572
|
-
});
|
|
573
|
-
// โโโ exam list [country] โโโ
|
|
574
|
-
exam
|
|
575
|
-
.command('list [country]')
|
|
576
|
-
.description('List available exams (optional: 2-letter country code)')
|
|
577
|
-
.action(async (country) => {
|
|
578
|
-
logCommand(`exam list ${country || ''}`);
|
|
579
|
-
const client = requireExamConnection();
|
|
580
|
-
if (!client)
|
|
581
|
-
return;
|
|
582
|
-
const spinner = createSpinner('Loading exams...');
|
|
583
|
-
spinner.start();
|
|
584
|
-
try {
|
|
585
|
-
let exams = await client.getExams();
|
|
586
|
-
spinner.stop();
|
|
587
|
-
// Filter by country if specified
|
|
588
|
-
if (country) {
|
|
589
|
-
const code = country.toUpperCase();
|
|
590
|
-
exams = exams.filter((e) => e.country.toUpperCase() === code || e.country === 'ALL');
|
|
591
|
-
}
|
|
592
|
-
if (!exams || exams.length === 0) {
|
|
593
|
-
console.log();
|
|
594
|
-
if (country) {
|
|
595
|
-
printInfo(`No exams available for ${country.toUpperCase()} yet.`);
|
|
596
|
-
}
|
|
597
|
-
else {
|
|
598
|
-
printInfo('No exams available for your country yet.');
|
|
599
|
-
}
|
|
600
|
-
console.log();
|
|
601
|
-
console.log(chalk.white(' New exams are coming soon!'));
|
|
602
|
-
console.log(chalk.gray(' National Round 1 Selection exams are being prepared.'));
|
|
603
|
-
console.log();
|
|
604
|
-
console.log(chalk.gray(' In the meantime, try: ') + chalk.bold.cyan('demo'));
|
|
605
|
-
console.log();
|
|
606
|
-
const langHint = {
|
|
607
|
-
PE: 'es', CN: 'zh', JP: 'ja', KR: 'ko', BR: 'pt',
|
|
608
|
-
SA: 'ar', FR: 'fr', DE: 'de', IN: 'hi', ID: 'id',
|
|
609
|
-
TH: 'th', VN: 'vi', TR: 'tr', RU: 'ru', UA: 'uk', HT: 'ht',
|
|
610
|
-
};
|
|
611
|
-
const code = (country || '').toUpperCase();
|
|
612
|
-
if (langHint[code]) {
|
|
613
|
-
console.log(chalk.gray(` Tip: switch to your language with: lang ${langHint[code]}`));
|
|
614
|
-
console.log();
|
|
615
|
-
}
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
const rows = exams.map((e) => {
|
|
619
|
-
const statusColor = e.status === 'submitted' ? chalk.green
|
|
620
|
-
: e.status === 'in_progress' ? chalk.yellow
|
|
621
|
-
: chalk.white;
|
|
622
|
-
return [
|
|
623
|
-
chalk.white(e.id),
|
|
624
|
-
e.name,
|
|
625
|
-
chalk.cyan(e.country),
|
|
626
|
-
String(e.questionCount),
|
|
627
|
-
`${e.durationMinutes} min`,
|
|
628
|
-
statusColor(e.status),
|
|
629
|
-
];
|
|
630
|
-
});
|
|
631
|
-
const countries = [...new Set(exams.map((e) => e.country))];
|
|
632
|
-
printHeader(country ? `Exams โ ${country.toUpperCase()}` : `Available Exams (${countries.length} regions)`);
|
|
633
|
-
printTable(['ID', 'Name', 'Country', 'Questions', 'Duration', 'Status'], rows);
|
|
634
|
-
console.log();
|
|
635
|
-
console.log(chalk.gray(' Start: exam start <id>'));
|
|
636
|
-
if (countries.length > 1) {
|
|
637
|
-
console.log(chalk.gray(` Filter by country: exam list PE, exam list CN, ...`));
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
catch (err) {
|
|
641
|
-
spinner.fail('Failed to load exams');
|
|
642
|
-
printError(err.message);
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
// โโโ exam start <id> โโโ
|
|
646
|
-
exam
|
|
647
|
-
.command('start <id>')
|
|
648
|
-
.description('Start an exam (begins timer)')
|
|
649
|
-
.action(async (examId) => {
|
|
650
|
-
logCommand(`exam start ${examId}`);
|
|
651
|
-
// Check if already in an exam
|
|
652
|
-
const existing = getExamState();
|
|
653
|
-
if (existing) {
|
|
654
|
-
printWarning(`Exam "${existing.session.examName}" is already in progress.`);
|
|
655
|
-
printInfo('Use "exam review" to check progress or "exam submit" to submit.');
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
const client = requireExamConnection();
|
|
659
|
-
if (!client)
|
|
660
|
-
return;
|
|
661
|
-
const proceed = await confirm({
|
|
662
|
-
message: chalk.white('Start the exam? The timer will begin immediately.'),
|
|
663
|
-
default: true,
|
|
664
|
-
theme: { prefix: '', style: { message: (t) => t, defaultAnswer: (t) => chalk.green(t) } },
|
|
665
|
-
});
|
|
666
|
-
if (!proceed)
|
|
667
|
-
return;
|
|
668
|
-
console.log();
|
|
669
|
-
try {
|
|
670
|
-
// Step 1: Connect
|
|
671
|
-
drawProgress(0, 'Connecting to exam server...');
|
|
672
|
-
const { session, questions } = await client.startExam(examId);
|
|
673
|
-
// Step 2-4: Visual loading steps
|
|
674
|
-
drawProgress(30, 'Loading questions...');
|
|
675
|
-
await sleep(200);
|
|
676
|
-
drawProgress(60, 'Preparing exam environment...');
|
|
677
|
-
await sleep(200);
|
|
678
|
-
drawProgress(90, 'Starting timer...');
|
|
679
|
-
await sleep(150);
|
|
680
|
-
saveExamState({ session, questions, answers: {} });
|
|
681
|
-
drawProgress(100, 'Ready!');
|
|
682
|
-
console.log();
|
|
683
|
-
console.log();
|
|
684
|
-
printHeader(session.examName);
|
|
685
|
-
printKeyValue('Questions', String(session.questionCount));
|
|
686
|
-
printKeyValue('Duration', `${session.durationMinutes} minutes`);
|
|
687
|
-
printKeyValue('Country', session.country);
|
|
688
|
-
printTimeRemaining();
|
|
689
|
-
// Show first question
|
|
690
|
-
if (questions.length > 0) {
|
|
691
|
-
printQuestion(questions[0]);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
catch (err) {
|
|
695
|
-
console.log();
|
|
696
|
-
printError(err.message);
|
|
697
|
-
}
|
|
698
|
-
});
|
|
699
|
-
// โโโ exam q [n] โโโ
|
|
700
|
-
exam
|
|
701
|
-
.command('q [n]')
|
|
702
|
-
.description('View question (or all questions)')
|
|
703
|
-
.action((n) => {
|
|
704
|
-
logCommand(`exam q ${n || 'all'}`);
|
|
705
|
-
const state = getExamState();
|
|
706
|
-
if (!state) {
|
|
707
|
-
printError('No exam in progress. Use "exam start <id>" to begin.');
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
printTimeRemaining();
|
|
711
|
-
if (n) {
|
|
712
|
-
const num = parseInt(n);
|
|
713
|
-
const q = state.questions.find((q) => q.number === num);
|
|
714
|
-
if (!q) {
|
|
715
|
-
printError(`Question ${n} not found (1-${state.questions.length}).`);
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
state._lastQ = num;
|
|
719
|
-
saveExamState(state);
|
|
720
|
-
printQuestion(q, state.answers[num]);
|
|
721
|
-
}
|
|
722
|
-
else {
|
|
723
|
-
// Show all questions summary
|
|
724
|
-
printHeader(`${state.session.examName}`);
|
|
725
|
-
const answered = Object.keys(state.answers).length;
|
|
726
|
-
printKeyValue('Progress', `${answered}/${state.session.questionCount} answered`);
|
|
727
|
-
console.log();
|
|
728
|
-
for (const q of state.questions) {
|
|
729
|
-
const ans = state.answers[q.number];
|
|
730
|
-
const status = ans ? chalk.green(`[${ans}]`) : chalk.gray('[ ]');
|
|
731
|
-
const cat = q.category ? chalk.gray(` [${q.category}]`) : '';
|
|
732
|
-
console.log(` ${status} ${chalk.white(`Q${q.number}`)}${cat} ${chalk.gray(q.text.substring(0, 60))}${q.text.length > 60 ? '...' : ''}`);
|
|
733
|
-
}
|
|
734
|
-
console.log();
|
|
735
|
-
}
|
|
736
|
-
});
|
|
737
|
-
// โโโ exam mark [n] โโโ
|
|
738
|
-
// Flag the current (or given) question for review later. Toggles on/off.
|
|
739
|
-
exam
|
|
740
|
-
.command('mark [n]')
|
|
741
|
-
.description('Flag a question to review later (toggles)')
|
|
742
|
-
.action((n) => {
|
|
743
|
-
logCommand(`exam mark ${n || ''}`);
|
|
744
|
-
const state = getExamState();
|
|
745
|
-
if (!state) {
|
|
746
|
-
printError('No exam in progress.');
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
const total = state.questions.length;
|
|
750
|
-
const targetN = n ? parseInt(n, 10) : (state._lastQ || 1);
|
|
751
|
-
if (isNaN(targetN) || targetN < 1 || targetN > total) {
|
|
752
|
-
printError(`Invalid question number. Use 1..${total}.`);
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
const nowMarked = toggleBookmark(state, targetN);
|
|
756
|
-
console.log();
|
|
757
|
-
if (nowMarked) {
|
|
758
|
-
console.log(chalk.yellow(` โ
Q${targetN} flagged for review.`));
|
|
759
|
-
}
|
|
760
|
-
else {
|
|
761
|
-
console.log(chalk.gray(` โ Q${targetN} unflagged.`));
|
|
762
|
-
}
|
|
763
|
-
const all = Array.isArray(state.bookmarks) ? state.bookmarks : [];
|
|
764
|
-
if (all.length > 0) {
|
|
765
|
-
console.log(chalk.gray(` Currently flagged: ${all.map((x) => `Q${x}`).join(', ')}`));
|
|
766
|
-
}
|
|
767
|
-
console.log(chalk.gray(' See all flagged questions in: ') + chalk.white('exam review'));
|
|
768
|
-
console.log();
|
|
769
|
-
});
|
|
770
|
-
// โโโ exam unmark [n] โโโ
|
|
771
|
-
exam
|
|
772
|
-
.command('unmark [n]')
|
|
773
|
-
.description('Remove review flag from a question')
|
|
774
|
-
.action((n) => {
|
|
775
|
-
logCommand(`exam unmark ${n || ''}`);
|
|
776
|
-
const state = getExamState();
|
|
777
|
-
if (!state) {
|
|
778
|
-
printError('No exam in progress.');
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
const total = state.questions.length;
|
|
782
|
-
const targetN = n ? parseInt(n, 10) : (state._lastQ || 1);
|
|
783
|
-
if (isNaN(targetN) || targetN < 1 || targetN > total) {
|
|
784
|
-
printError(`Invalid question number. Use 1..${total}.`);
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
if (!isBookmarked(state, targetN)) {
|
|
788
|
-
console.log(chalk.gray(` Q${targetN} was not flagged.`));
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
toggleBookmark(state, targetN);
|
|
792
|
-
console.log();
|
|
793
|
-
console.log(chalk.gray(` โ Q${targetN} unflagged.`));
|
|
794
|
-
console.log();
|
|
795
|
-
});
|
|
796
|
-
// โโโ exam next โโโ
|
|
797
|
-
exam
|
|
798
|
-
.command('next')
|
|
799
|
-
.description('Go to next question')
|
|
800
|
-
.action(() => {
|
|
801
|
-
logCommand('exam next');
|
|
802
|
-
const state = getExamState();
|
|
803
|
-
if (!state) {
|
|
804
|
-
printError('No exam in progress.');
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
const current = state.questions.findIndex((q) => !state.answers[q.number]);
|
|
808
|
-
const lastViewed = state._lastQ || 1;
|
|
809
|
-
const next = Math.min(lastViewed + 1, state.questions.length);
|
|
810
|
-
state._lastQ = next;
|
|
811
|
-
saveExamState(state);
|
|
812
|
-
const q = state.questions[next - 1];
|
|
813
|
-
printQuestion(q, state.answers[q.number]);
|
|
814
|
-
});
|
|
815
|
-
// โโโ exam prev โโโ
|
|
816
|
-
exam
|
|
817
|
-
.command('prev')
|
|
818
|
-
.description('Go to previous question')
|
|
819
|
-
.action(() => {
|
|
820
|
-
logCommand('exam prev');
|
|
821
|
-
const state = getExamState();
|
|
822
|
-
if (!state) {
|
|
823
|
-
printError('No exam in progress.');
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
const lastViewed = state._lastQ || 1;
|
|
827
|
-
const prev = Math.max(lastViewed - 1, 1);
|
|
828
|
-
state._lastQ = prev;
|
|
829
|
-
saveExamState(state);
|
|
830
|
-
const q = state.questions[prev - 1];
|
|
831
|
-
printQuestion(q, state.answers[q.number]);
|
|
832
|
-
});
|
|
833
|
-
// โโโ exam help โโโ
|
|
834
|
-
exam
|
|
835
|
-
.command('help')
|
|
836
|
-
.description('Eliminate one wrong option (limited uses)')
|
|
837
|
-
.action(async () => {
|
|
838
|
-
logCommand('exam help');
|
|
839
|
-
const state = getExamState();
|
|
840
|
-
if (!state) {
|
|
841
|
-
printError('No exam in progress.');
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
const help = getHelpState(state);
|
|
845
|
-
const currentQ = state._lastQ || 1;
|
|
846
|
-
const qHelps = help.perQ[currentQ] || 0;
|
|
847
|
-
const eliminated = help.eliminated[currentQ] || [];
|
|
848
|
-
const q = state.questions.find((qq) => qq.number === currentQ);
|
|
849
|
-
if (!q)
|
|
850
|
-
return;
|
|
851
|
-
// Check: already used 2 helps on this question
|
|
852
|
-
if (qHelps >= 2) {
|
|
853
|
-
console.log();
|
|
854
|
-
console.log(chalk.yellow(' ๐ Max 2 helps per question โ you already have a 50/50!'));
|
|
855
|
-
console.log(chalk.gray(' Trust your instinct and pick one!'));
|
|
856
|
-
console.log();
|
|
857
|
-
printQuestion(q, state.answers[q.number]);
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
// Check: only 1 option left (shouldn't happen with max 2 helps, but safety)
|
|
861
|
-
const remaining = ['A', 'B', 'C', 'D'].filter((k) => !eliminated.includes(k));
|
|
862
|
-
if (remaining.length <= 2) {
|
|
863
|
-
console.log();
|
|
864
|
-
console.log(chalk.yellow(' ๐ Only 2 options left โ you got this!'));
|
|
865
|
-
console.log();
|
|
866
|
-
printQuestion(q, state.answers[q.number]);
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
// Check budget
|
|
870
|
-
if (help.used >= help.max) {
|
|
871
|
-
const bonusUnlocked = help.max >= HELP_WITH_BONUS;
|
|
872
|
-
const bonusRemaining = HELP_WITH_BONUS - help.max;
|
|
873
|
-
if (!bonusUnlocked) {
|
|
874
|
-
console.log();
|
|
875
|
-
console.log(chalk.yellow(` Help used: ${help.used}/${help.max}`));
|
|
876
|
-
console.log(chalk.white(' Need more? Type: ') + chalk.bold.yellow('more help') + chalk.gray(` (+${bonusRemaining} bonus uses)`));
|
|
877
|
-
console.log();
|
|
878
|
-
}
|
|
879
|
-
else {
|
|
880
|
-
console.log();
|
|
881
|
-
console.log(chalk.gray(` All help used (${help.used}/${help.max}). You\'re on your own! ๐ช`));
|
|
882
|
-
console.log();
|
|
883
|
-
}
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
// Apply elimination of a specific option
|
|
887
|
-
const applyEliminate = (toRemove) => {
|
|
888
|
-
eliminated.push(toRemove);
|
|
889
|
-
if (!help.eliminated[currentQ])
|
|
890
|
-
help.eliminated[currentQ] = [];
|
|
891
|
-
help.eliminated[currentQ] = eliminated;
|
|
892
|
-
help.perQ[currentQ] = qHelps + 1;
|
|
893
|
-
help.used++;
|
|
894
|
-
state._helpUsed = help.used;
|
|
895
|
-
state._helpMax = help.max;
|
|
896
|
-
state._helpPerQ = help.perQ;
|
|
897
|
-
state._eliminated = help.eliminated;
|
|
898
|
-
if (!state.interactions)
|
|
899
|
-
state.interactions = [];
|
|
900
|
-
state.interactions.push({ ts: new Date().toISOString(), q: currentQ, type: 'help_used', result: `eliminated ${toRemove}` });
|
|
901
|
-
saveExamState(state);
|
|
902
|
-
console.log();
|
|
903
|
-
console.log(chalk.yellow(` ๐ก Option ${toRemove} eliminated!`) + chalk.gray(` (help ${help.used}/${help.max})`));
|
|
904
|
-
printQuestion(q, state.answers[q.number]);
|
|
905
|
-
};
|
|
906
|
-
// Pick a random wrong option to eliminate (needs correct answer)
|
|
907
|
-
const doEliminate = (correct) => {
|
|
908
|
-
const wrongRemaining = remaining.filter((k) => k !== correct && !eliminated.includes(k));
|
|
909
|
-
if (wrongRemaining.length === 0)
|
|
910
|
-
return;
|
|
911
|
-
applyEliminate(wrongRemaining[Math.floor(Math.random() * wrongRemaining.length)]);
|
|
912
|
-
};
|
|
913
|
-
if (state.session.examId === 'demo-free' && q.answer) {
|
|
914
|
-
doEliminate(q.answer);
|
|
915
|
-
}
|
|
916
|
-
else {
|
|
917
|
-
// Server-authoritative: ask server which wrong option to eliminate
|
|
918
|
-
const config = getConfig();
|
|
919
|
-
const serverUrl = config.ctfdUrl || 'https://practice.icoa2026.au';
|
|
920
|
-
const token = state.session?.token || '';
|
|
921
|
-
try {
|
|
922
|
-
const res = await fetch(`${serverUrl}/api/icoa/exams/${state.session.examId}/help`, {
|
|
923
|
-
method: 'POST',
|
|
924
|
-
headers: { 'Content-Type': 'application/json', 'User-Agent': 'icoa-cli' },
|
|
925
|
-
body: JSON.stringify({ token, question: currentQ, eliminated }),
|
|
926
|
-
signal: AbortSignal.timeout(5000),
|
|
927
|
-
});
|
|
928
|
-
const json = await res.json();
|
|
929
|
-
const toRemove = json?.data?.eliminate;
|
|
930
|
-
if (toRemove && ['A', 'B', 'C', 'D'].includes(toRemove)) {
|
|
931
|
-
applyEliminate(toRemove);
|
|
932
|
-
}
|
|
933
|
-
else {
|
|
934
|
-
console.log();
|
|
935
|
-
console.log(chalk.gray(' Server did not return a valid option. Try again.'));
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
catch {
|
|
939
|
-
console.log();
|
|
940
|
-
console.log(chalk.gray(' Could not reach server for help. Check your connection.'));
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
});
|
|
944
|
-
// โโโ exam more-help โโโ
|
|
945
|
-
exam
|
|
946
|
-
.command('more-help')
|
|
947
|
-
.description('Unlock hidden bonus help uses')
|
|
948
|
-
.action(() => {
|
|
949
|
-
logCommand('exam more-help');
|
|
950
|
-
const state = getExamState();
|
|
951
|
-
if (!state) {
|
|
952
|
-
printError('No exam in progress.');
|
|
953
|
-
return;
|
|
954
|
-
}
|
|
955
|
-
const help = getHelpState(state);
|
|
956
|
-
// Demo (5โ8) and real exam (10โ15) each add their own bonus tier. Pick
|
|
957
|
-
// the target based on what's already in state: demo-free exam keeps the
|
|
958
|
-
// smaller step, real exams get the full 15.
|
|
959
|
-
const isDemo = state.session.examId === 'demo-free';
|
|
960
|
-
const bonusTarget = isDemo ? 8 : HELP_WITH_BONUS;
|
|
961
|
-
if (help.max >= bonusTarget) {
|
|
962
|
-
console.log(chalk.gray(' Bonus help already unlocked.'));
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
if (help.used < help.max) {
|
|
966
|
-
console.log(chalk.gray(` You still have ${help.max - help.used} helps remaining. Use them first!`));
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
const bonusDelta = bonusTarget - help.max;
|
|
970
|
-
state._helpMax = bonusTarget;
|
|
971
|
-
saveExamState(state);
|
|
972
|
-
console.log();
|
|
973
|
-
console.log(chalk.green(` ๐ +${bonusDelta} bonus help unlocked! (${bonusDelta}/${bonusTarget} remaining)`));
|
|
974
|
-
console.log(chalk.gray(' Type "help" on any question to use.'));
|
|
975
|
-
console.log();
|
|
976
|
-
});
|
|
977
|
-
// โโโ exam answer <n> <choice> โโโ
|
|
978
|
-
exam
|
|
979
|
-
.command('answer <n> <choice...>')
|
|
980
|
-
.description('Answer question N (A/B/C/D for MCQ, ICOA{flag} for practical)')
|
|
981
|
-
.action(async (n, choiceParts) => {
|
|
982
|
-
const choice = choiceParts.join(' ');
|
|
983
|
-
logCommand(`exam answer ${n} ${choice}`);
|
|
984
|
-
const state = getExamState();
|
|
985
|
-
if (!state) {
|
|
986
|
-
printError('No exam in progress. Use "exam start <id>" to begin.');
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
if (!checkTimeRemaining())
|
|
990
|
-
return;
|
|
991
|
-
const num = parseInt(n);
|
|
992
|
-
const q = state.questions.find((q) => q.number === num);
|
|
993
|
-
if (!q) {
|
|
994
|
-
printError(`Question ${n} not found (1-${state.questions.length}).`);
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
// Detect question type: practical (ai4ctf/ctf4ai) or MCQ
|
|
998
|
-
const isPractical = q.type === 'ai4ctf' || q.type === 'ctf4ai' || (q.options && !q.options.A && !q.options.B);
|
|
999
|
-
let c;
|
|
1000
|
-
if (isPractical) {
|
|
1001
|
-
c = choice.trim();
|
|
1002
|
-
if (!c) {
|
|
1003
|
-
printError('Please provide your flag: exam answer <n> ICOA{your_flag}');
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
// Reject letter-only answers on practical questions โ almost certainly
|
|
1007
|
-
// a user who typed `A` thinking MCQ was still in play. Submitting 'A'
|
|
1008
|
-
// as the flag is a footgun that would waste an attempt silently.
|
|
1009
|
-
if (/^[A-Da-d]$/.test(c)) {
|
|
1010
|
-
printError(`Q${num} is a practical question โ answer with a flag, not a letter.`);
|
|
1011
|
-
console.log(chalk.gray(' Example: ') + chalk.green(`exam answer ${num} ICOA{your_flag}`));
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
else {
|
|
1016
|
-
c = choice.toUpperCase();
|
|
1017
|
-
if (!['A', 'B', 'C', 'D'].includes(c)) {
|
|
1018
|
-
printError('Choice must be A, B, C, or D.');
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
// Q2 tutorial: must use help first before answering
|
|
1023
|
-
if (state.session.examId === 'demo-free' && num === 2) {
|
|
1024
|
-
const helpState = getHelpState(state);
|
|
1025
|
-
const q2Helps = helpState.perQ[2] || 0;
|
|
1026
|
-
if (q2Helps === 0) {
|
|
1027
|
-
console.log();
|
|
1028
|
-
console.log(chalk.yellow(' ๐ก Try typing ') + chalk.bold.yellow('help') + chalk.yellow(' first to see what it does!'));
|
|
1029
|
-
console.log(chalk.gray(' This question requires you to use help before answering.'));
|
|
1030
|
-
console.log();
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
// Log interaction
|
|
1035
|
-
const prevAnswer = state.answers[num];
|
|
1036
|
-
if (!state.interactions)
|
|
1037
|
-
state.interactions = [];
|
|
1038
|
-
state.interactions.push({
|
|
1039
|
-
ts: new Date().toISOString(),
|
|
1040
|
-
q: num,
|
|
1041
|
-
type: prevAnswer ? 'answer_changed' : 'answer_submitted',
|
|
1042
|
-
input: c,
|
|
1043
|
-
result: prevAnswer ? `changed from ${prevAnswer}` : 'new',
|
|
1044
|
-
});
|
|
1045
|
-
state.answers[num] = c;
|
|
1046
|
-
state._lastQ = num;
|
|
1047
|
-
saveExamState(state);
|
|
1048
|
-
// UX: visible "saved" indicator reassures beginners their answer is safe
|
|
1049
|
-
// even if they Ctrl+C or lose network. Neutral โ doesn't reveal correctness.
|
|
1050
|
-
console.log(chalk.gray(' โ saved'));
|
|
1051
|
-
// Per-question sync to server (real exam only). Best-effort, fire-and-forget.
|
|
1052
|
-
// If timer expires or network drops before final submit, the server still
|
|
1053
|
-
// has the answer. Final submit is authoritative and overwrites this.
|
|
1054
|
-
const tokenForSync = state.session.token;
|
|
1055
|
-
if (tokenForSync && state.session.examId !== 'demo-free') {
|
|
1056
|
-
const cfg = getConfig();
|
|
1057
|
-
const url = `${cfg.ctfdUrl || 'https://practice.icoa2026.au'}/api/icoa/exams/${state.session.examId}/answer`;
|
|
1058
|
-
fetch(url, {
|
|
1059
|
-
method: 'POST',
|
|
1060
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1061
|
-
body: JSON.stringify({ token: tokenForSync, question: num, answer: c }),
|
|
1062
|
-
signal: AbortSignal.timeout(5000),
|
|
1063
|
-
}).catch(() => { }); // silent failure โ local state still saved
|
|
1064
|
-
}
|
|
1065
|
-
const answered = Object.keys(state.answers).length;
|
|
1066
|
-
const total = state.session.questionCount;
|
|
1067
|
-
printSuccess(`Q${num}: ${c} โ (${answered}/${total} answered)`);
|
|
1068
|
-
// Demo-only: gentle per-answer feedback (grey, non-intrusive)
|
|
1069
|
-
if (state.session.examId === 'demo-free' && q.answer) {
|
|
1070
|
-
const NICE_WORDS = ['Nice one.', 'Solid pick.', 'On the money.', 'Exactly.', 'Spot on.'];
|
|
1071
|
-
const CLOSE_WORDS = ['Close โ we\'ll revisit this one.', 'Hmm, worth a second look at the end.', 'Keep it in mind for review.'];
|
|
1072
|
-
const pool = c === q.answer ? NICE_WORDS : CLOSE_WORDS;
|
|
1073
|
-
const word = pool[Math.floor(Math.random() * pool.length)];
|
|
1074
|
-
console.log(chalk.gray(` ยท ${word}`));
|
|
1075
|
-
}
|
|
1076
|
-
// Auto-show next question and update _lastQ
|
|
1077
|
-
if (num < state.questions.length) {
|
|
1078
|
-
const nextQ = state.questions[num]; // 0-indexed: questions[num] = question num+1
|
|
1079
|
-
state._lastQ = nextQ.number;
|
|
1080
|
-
saveExamState(state);
|
|
1081
|
-
printQuestion(nextQ, state.answers[nextQ.number]);
|
|
1082
|
-
}
|
|
1083
|
-
else if (answered >= state.questions.length) {
|
|
1084
|
-
console.log();
|
|
1085
|
-
console.log(chalk.green.bold(' ๐ All questions answered!'));
|
|
1086
|
-
console.log();
|
|
1087
|
-
if (state.session.examId === 'demo-free') {
|
|
1088
|
-
console.log(chalk.white(' Type ') + chalk.bold.cyan('exam submit') + chalk.white(' to see your results!'));
|
|
1089
|
-
console.log();
|
|
1090
|
-
}
|
|
1091
|
-
else {
|
|
1092
|
-
console.log(chalk.white(' Use: exam review ยท exam submit'));
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
});
|
|
1096
|
-
// โโโ exam review โโโ
|
|
1097
|
-
exam
|
|
1098
|
-
.command('review')
|
|
1099
|
-
.description('Review all answers before submitting')
|
|
1100
|
-
.action(() => {
|
|
1101
|
-
logCommand('exam review');
|
|
1102
|
-
const state = getExamState();
|
|
1103
|
-
if (!state) {
|
|
1104
|
-
printError('No exam in progress.');
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
printHeader(`Review: ${state.session.examName}`);
|
|
1108
|
-
printTimeRemaining();
|
|
1109
|
-
console.log();
|
|
1110
|
-
const cols = 6;
|
|
1111
|
-
let line = ' ';
|
|
1112
|
-
const marks = Array.isArray(state.bookmarks) ? state.bookmarks : [];
|
|
1113
|
-
for (const q of state.questions) {
|
|
1114
|
-
const ans = state.answers[q.number];
|
|
1115
|
-
const isMarked = marks.includes(q.number);
|
|
1116
|
-
const star = isMarked ? 'โ
' : ' ';
|
|
1117
|
-
if (ans) {
|
|
1118
|
-
// Answered + marked โ bold yellow; answered only โ green; unanswered + marked โ yellow; unanswered โ dim yellow
|
|
1119
|
-
if (isMarked) {
|
|
1120
|
-
line += chalk.bold.yellow(`Q${String(q.number).padStart(2)}${star}[${ans}] `);
|
|
1121
|
-
}
|
|
1122
|
-
else {
|
|
1123
|
-
line += chalk.green(`Q${String(q.number).padStart(2)} [${ans}] `);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
else {
|
|
1127
|
-
if (isMarked) {
|
|
1128
|
-
line += chalk.bold.yellow(`Q${String(q.number).padStart(2)}${star}[ ] `);
|
|
1129
|
-
}
|
|
1130
|
-
else {
|
|
1131
|
-
line += chalk.yellow(`Q${String(q.number).padStart(2)} [ ] `);
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
if (q.number % cols === 0) {
|
|
1135
|
-
console.log(line);
|
|
1136
|
-
line = ' ';
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
if (line.trim())
|
|
1140
|
-
console.log(line);
|
|
1141
|
-
const answered = Object.keys(state.answers).length;
|
|
1142
|
-
const total = state.session.questionCount;
|
|
1143
|
-
const unanswered = total - answered;
|
|
1144
|
-
console.log();
|
|
1145
|
-
printKeyValue('Answered', chalk.green.bold(`${answered}/${total}`));
|
|
1146
|
-
if (unanswered > 0) {
|
|
1147
|
-
const missing = state.questions
|
|
1148
|
-
.filter((q) => !state.answers[q.number])
|
|
1149
|
-
.map((q) => q.number)
|
|
1150
|
-
.join(', ');
|
|
1151
|
-
printKeyValue('Unanswered', chalk.yellow(`${unanswered} (Q${missing})`));
|
|
1152
|
-
}
|
|
1153
|
-
if (marks.length > 0) {
|
|
1154
|
-
printKeyValue('Flagged for review', chalk.bold.yellow(`โ
${marks.length}`) + chalk.gray(` (${marks.map((x) => `Q${x}`).join(', ')})`));
|
|
1155
|
-
}
|
|
1156
|
-
console.log();
|
|
1157
|
-
if (marks.length > 0) {
|
|
1158
|
-
console.log(chalk.gray(' Jump to a flagged question: ') + chalk.white(`exam q ${marks[0]}`));
|
|
1159
|
-
}
|
|
1160
|
-
console.log(chalk.gray(' Ready? Use ') + chalk.bold.cyan('exam submit') + chalk.gray(' to submit for grading.'));
|
|
1161
|
-
});
|
|
1162
|
-
// โโโ exam submit [confirm] โโโ
|
|
1163
|
-
exam
|
|
1164
|
-
.command('submit [confirmArg]')
|
|
1165
|
-
.description('Submit exam for grading (use: "exam submit confirm" for real exams)')
|
|
1166
|
-
.action(async (confirmArg) => {
|
|
1167
|
-
logCommand(`exam submit ${confirmArg || ''}`);
|
|
1168
|
-
const state = getExamState();
|
|
1169
|
-
if (state && confirmArg === 'confirm') {
|
|
1170
|
-
state._submitConfirmed = true;
|
|
1171
|
-
saveExamState(state);
|
|
1172
|
-
}
|
|
1173
|
-
if (!state) {
|
|
1174
|
-
printError('No exam in progress.');
|
|
1175
|
-
return;
|
|
1176
|
-
}
|
|
1177
|
-
const answered = Object.keys(state.answers).length;
|
|
1178
|
-
const total = state.session.questionCount;
|
|
1179
|
-
const unanswered = total - answered;
|
|
1180
|
-
if (answered === 0) {
|
|
1181
|
-
const deadline = getExamDeadline();
|
|
1182
|
-
const expired = deadline && new Date() >= deadline;
|
|
1183
|
-
if (state.session.examId === 'demo-free' || expired) {
|
|
1184
|
-
clearExamState();
|
|
1185
|
-
printWarning('No answers to submit. Exam cleared.');
|
|
1186
|
-
printInfo('Type: demo to start again.');
|
|
1187
|
-
}
|
|
1188
|
-
else {
|
|
1189
|
-
printWarning('No answers to submit.');
|
|
1190
|
-
printInfo('Answer some questions first: exam q 1');
|
|
1191
|
-
}
|
|
1192
|
-
return;
|
|
1193
|
-
}
|
|
1194
|
-
// Demo: submit directly. Real exam: require typed confirmation.
|
|
1195
|
-
if (state.session.examId !== 'demo-free') {
|
|
1196
|
-
const confirmedAlready = state._submitConfirmed === true;
|
|
1197
|
-
const deadline = getExamDeadline();
|
|
1198
|
-
const expired = !!(deadline && new Date() >= deadline);
|
|
1199
|
-
if (!confirmedAlready && !expired) {
|
|
1200
|
-
// First press of `exam submit` โ show a clear preflight summary and
|
|
1201
|
-
// require the user to run `exam submit confirm` to proceed. Typing
|
|
1202
|
-
// out "confirm" is the deliberate action that prevents accidental
|
|
1203
|
-
// submissions while an exam is still in progress.
|
|
1204
|
-
console.log();
|
|
1205
|
-
console.log(chalk.red(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1206
|
-
console.log(chalk.bold.red(' โ FINAL SUBMISSION โ please confirm'));
|
|
1207
|
-
console.log(chalk.red(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1208
|
-
console.log();
|
|
1209
|
-
console.log(chalk.white(` You will submit ${chalk.bold.green(`${answered}/${total}`)} answers.`));
|
|
1210
|
-
if (unanswered > 0) {
|
|
1211
|
-
const missingList = state.questions
|
|
1212
|
-
.filter((q) => !state.answers[q.number])
|
|
1213
|
-
.map((q) => `Q${q.number}`)
|
|
1214
|
-
.join(', ');
|
|
1215
|
-
console.log(chalk.yellow(` ${unanswered} unanswered: ${missingList}`));
|
|
1216
|
-
}
|
|
1217
|
-
const marks = Array.isArray(state.bookmarks) ? state.bookmarks : [];
|
|
1218
|
-
if (marks.length > 0) {
|
|
1219
|
-
console.log(chalk.yellow(` โ
${marks.length} still flagged for review: ${marks.map((x) => `Q${x}`).join(', ')}`));
|
|
1220
|
-
}
|
|
1221
|
-
console.log();
|
|
1222
|
-
console.log(chalk.white(' Answers are ') + chalk.bold.red('final') + chalk.white(' โ you cannot change them after submit.'));
|
|
1223
|
-
console.log();
|
|
1224
|
-
console.log(chalk.bold.white(' To confirm: ') + chalk.bold.cyan('exam submit confirm'));
|
|
1225
|
-
console.log(chalk.white(' To go back: ') + chalk.cyan('exam review') + chalk.gray(' or ') + chalk.cyan('back'));
|
|
1226
|
-
console.log();
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
// Clear the flag so a subsequent re-entry requires re-confirmation.
|
|
1230
|
-
if (confirmedAlready) {
|
|
1231
|
-
delete state._submitConfirmed;
|
|
1232
|
-
saveExamState(state);
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
console.log();
|
|
1236
|
-
// Demo exam: grade locally
|
|
1237
|
-
if (state.session.examId === 'demo-free') {
|
|
1238
|
-
try {
|
|
1239
|
-
drawProgress(0, t('grading'));
|
|
1240
|
-
await sleep(300);
|
|
1241
|
-
let score = 0;
|
|
1242
|
-
const wrongQuestions = [];
|
|
1243
|
-
const categoryStats = {};
|
|
1244
|
-
for (const q of state.questions) {
|
|
1245
|
-
const cat = q.category || 'Other';
|
|
1246
|
-
if (!categoryStats[cat])
|
|
1247
|
-
categoryStats[cat] = { correct: 0, total: 0 };
|
|
1248
|
-
categoryStats[cat].total++;
|
|
1249
|
-
const userAns = state.answers[q.number];
|
|
1250
|
-
if (userAns && q.answer && userAns === q.answer) {
|
|
1251
|
-
score++;
|
|
1252
|
-
categoryStats[cat].correct++;
|
|
1253
|
-
}
|
|
1254
|
-
else {
|
|
1255
|
-
wrongQuestions.push(q.number);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
wrongQuestions.sort((a, b) => a - b);
|
|
1259
|
-
drawProgress(100, t('complete'));
|
|
1260
|
-
console.log();
|
|
1261
|
-
// Elapsed time
|
|
1262
|
-
const startedMs = new Date(state.session.startedAt).getTime();
|
|
1263
|
-
const elapsedSec = Math.max(0, Math.round((Date.now() - startedMs) / 1000));
|
|
1264
|
-
const elapsedMin = Math.floor(elapsedSec / 60);
|
|
1265
|
-
const elapsedS = elapsedSec % 60;
|
|
1266
|
-
const elapsedLabel = `${elapsedMin}m ${elapsedS}s`;
|
|
1267
|
-
const questionsSnapshot = [...state.questions];
|
|
1268
|
-
const answersSnapshot = { ...state.answers };
|
|
1269
|
-
const helpState = getHelpState(state);
|
|
1270
|
-
clearExamState();
|
|
1271
|
-
const percentage = Math.round(score / total * 100);
|
|
1272
|
-
// Report demo stats to server (includes full interaction audit
|
|
1273
|
-
// trail so admin can see hint usage patterns, time per question,
|
|
1274
|
-
// answer changes โ same data we collect for real exams).
|
|
1275
|
-
const config = getConfig();
|
|
1276
|
-
fetch('https://practice.icoa2026.au/api/icoa/demo-stats', {
|
|
1277
|
-
method: 'POST',
|
|
1278
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1279
|
-
body: JSON.stringify({
|
|
1280
|
-
type: 'demo-exam',
|
|
1281
|
-
score,
|
|
1282
|
-
total,
|
|
1283
|
-
lang: config.language || 'en',
|
|
1284
|
-
help_used: helpState.used,
|
|
1285
|
-
help_max: helpState.max,
|
|
1286
|
-
tokens_used: 0,
|
|
1287
|
-
solved: percentage >= 60 ? 1 : 0,
|
|
1288
|
-
timestamp: new Date().toISOString(),
|
|
1289
|
-
interactions: state.interactions || [],
|
|
1290
|
-
}),
|
|
1291
|
-
signal: AbortSignal.timeout(5000),
|
|
1292
|
-
}).catch(() => { });
|
|
1293
|
-
console.log();
|
|
1294
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1295
|
-
console.log();
|
|
1296
|
-
console.log(chalk.bold.white(' โโโ โโโโโโโ โโโโโโโ โโโโโโ'));
|
|
1297
|
-
console.log(chalk.bold.white(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1298
|
-
console.log(chalk.bold.white(' โโโโโโ โโโ โโโโโโโโโโโ'));
|
|
1299
|
-
console.log(chalk.bold.white(' โโโโโโ โโโ โโโโโโโโโโโ'));
|
|
1300
|
-
console.log(chalk.bold.white(' โโโโโโโโโโโโโโโโโโโโโโโ โโโ'));
|
|
1301
|
-
console.log(chalk.bold.white(' โโโ โโโโโโโ โโโโโโโ โโโ โโโ'));
|
|
1302
|
-
console.log();
|
|
1303
|
-
console.log(chalk.bold(` ${t('score')}: ${score}/${total} (${percentage}%)`));
|
|
1304
|
-
console.log(chalk.bold(` ${percentage >= 60 ? chalk.green(t('passed')) : chalk.red(t('notPassed'))}`));
|
|
1305
|
-
console.log(chalk.gray(` Elapsed: ${elapsedLabel}`));
|
|
1306
|
-
console.log();
|
|
1307
|
-
console.log(chalk.yellow(' International Cyber Olympiad in AI 2026'));
|
|
1308
|
-
console.log(chalk.gray(' Sydney, Australia ยท Jun 27 - Jul 2, 2026'));
|
|
1309
|
-
console.log();
|
|
1310
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1311
|
-
// Paced reveal: give the user time to absorb each section.
|
|
1312
|
-
await sleep(3000);
|
|
1313
|
-
// Per-category breakdown with ASCII progress bars
|
|
1314
|
-
const catEntries = Object.entries(categoryStats);
|
|
1315
|
-
if (catEntries.length > 0) {
|
|
1316
|
-
console.log();
|
|
1317
|
-
console.log(chalk.bold.white(' By category'));
|
|
1318
|
-
const BAR_WIDTH = 10;
|
|
1319
|
-
for (const [cat, s] of catEntries) {
|
|
1320
|
-
const pct = s.total > 0 ? Math.round(s.correct / s.total * 100) : 0;
|
|
1321
|
-
const filled = s.total > 0 ? Math.round((s.correct / s.total) * BAR_WIDTH) : 0;
|
|
1322
|
-
const empty = BAR_WIDTH - filled;
|
|
1323
|
-
const color = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;
|
|
1324
|
-
const bar = color('โ'.repeat(filled)) + chalk.gray('โ'.repeat(empty));
|
|
1325
|
-
console.log(' ' + chalk.white(cat.padEnd(20)) + ' ' + bar + ' ' + color(`${s.correct}/${s.total}`) + chalk.gray(` (${pct}%)`));
|
|
1326
|
-
}
|
|
1327
|
-
console.log();
|
|
1328
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1329
|
-
}
|
|
1330
|
-
// Record stats locally + save retry queue
|
|
1331
|
-
const updatedStats = recordDemoAttempt(score, total);
|
|
1332
|
-
const isNewBest = score === updatedStats.best && updatedStats.attempts > 1 && score > 0;
|
|
1333
|
-
if (isNewBest) {
|
|
1334
|
-
console.log();
|
|
1335
|
-
console.log(chalk.bold.green(' โ
New personal best!'));
|
|
1336
|
-
}
|
|
1337
|
-
if (wrongQuestions.length > 0) {
|
|
1338
|
-
const wrongSnapshots = questionsSnapshot
|
|
1339
|
-
.filter((q) => wrongQuestions.includes(q.number))
|
|
1340
|
-
.map((q) => ({ ...q }));
|
|
1341
|
-
saveRetryQueue(wrongSnapshots);
|
|
1342
|
-
}
|
|
1343
|
-
else {
|
|
1344
|
-
clearRetryQueue();
|
|
1345
|
-
}
|
|
1346
|
-
await sleep(3000);
|
|
1347
|
-
// Show wrong answers with explanations
|
|
1348
|
-
if (wrongQuestions.length > 0) {
|
|
1349
|
-
console.log();
|
|
1350
|
-
console.log(chalk.yellow(` ${wrongQuestions.length} ${t('incorrectIntro')}`));
|
|
1351
|
-
console.log();
|
|
1352
|
-
for (const qn of wrongQuestions) {
|
|
1353
|
-
const q = questionsSnapshot.find((qq) => qq.number === qn);
|
|
1354
|
-
if (!q)
|
|
1355
|
-
continue;
|
|
1356
|
-
const userAns = answersSnapshot[qn];
|
|
1357
|
-
const correctAns = q.answer;
|
|
1358
|
-
console.log(chalk.white(` Q${qn}. ${q.text}`));
|
|
1359
|
-
if (userAns) {
|
|
1360
|
-
console.log(chalk.red(` ${t('yourAnswer')}: ${userAns}. ${q.options[userAns]}`));
|
|
1361
|
-
}
|
|
1362
|
-
else {
|
|
1363
|
-
console.log(chalk.yellow(` ${t('yourAnswer')}: โ`));
|
|
1364
|
-
}
|
|
1365
|
-
if (correctAns) {
|
|
1366
|
-
console.log(chalk.green(` ${t('correct')}: ${correctAns}. ${q.options[correctAns]}`));
|
|
1367
|
-
}
|
|
1368
|
-
if (q.explanation) {
|
|
1369
|
-
console.log(chalk.gray(` โ ${q.explanation}`));
|
|
1370
|
-
}
|
|
1371
|
-
console.log();
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
else {
|
|
1375
|
-
console.log();
|
|
1376
|
-
console.log(chalk.green(` ${t('perfectScore')}`));
|
|
1377
|
-
}
|
|
1378
|
-
// Stage 1 ends here. Retry hint, CTF intro, and dual-track overview
|
|
1379
|
-
// are deferred to the final report printed after ctf4ai so the flow
|
|
1380
|
-
// feels: theory โ ai4ctf โ ctf4ai โ one combined report + retry/back.
|
|
1381
|
-
// The retry queue was already persisted above, so the final report
|
|
1382
|
-
// can surface it later via getRetryQueue().
|
|
1383
|
-
await sleep(2000);
|
|
1384
|
-
console.log();
|
|
1385
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1386
|
-
console.log(chalk.white(` ${t('nextLabel')} `) + chalk.bold.green('ai4ctf') + chalk.gray(` โ ${t('ai4ctfDesc')}`));
|
|
1387
|
-
console.log(chalk.gray(` ${t('ai4ctfSub')}`));
|
|
1388
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1389
|
-
console.log();
|
|
1390
|
-
}
|
|
1391
|
-
catch (err) {
|
|
1392
|
-
console.log();
|
|
1393
|
-
printError(err.message);
|
|
1394
|
-
}
|
|
1395
|
-
return;
|
|
1396
|
-
}
|
|
1397
|
-
// Real exam: submit to server
|
|
1398
|
-
// Token-based exam: use direct fetch with exam token
|
|
1399
|
-
// CTFd-based exam: use ExamClient
|
|
1400
|
-
const examToken = state.session.token;
|
|
1401
|
-
try {
|
|
1402
|
-
drawProgress(0, 'Uploading answers...');
|
|
1403
|
-
let result;
|
|
1404
|
-
if (examToken) {
|
|
1405
|
-
// Submit via exam token
|
|
1406
|
-
const config = getConfig();
|
|
1407
|
-
const serverUrl = config.ctfdUrl || 'https://practice.icoa2026.au';
|
|
1408
|
-
const res = await fetch(`${serverUrl}/api/icoa/exams/${state.session.examId}/submit`, {
|
|
1409
|
-
method: 'POST',
|
|
1410
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1411
|
-
body: JSON.stringify({
|
|
1412
|
-
token: examToken,
|
|
1413
|
-
answers: state.answers,
|
|
1414
|
-
interactions: state.interactions || [],
|
|
1415
|
-
aiUsage: state.aiUsage || { ai4ctf: 0, ctf4ai: 0 },
|
|
1416
|
-
// Flagged-for-review questions (display numbers). Server maps
|
|
1417
|
-
// these to original pool numbers for cross-session aggregation
|
|
1418
|
-
// so the exam center can spot high-flag-rate questions.
|
|
1419
|
-
bookmarks: state.bookmarks || [],
|
|
1420
|
-
}),
|
|
1421
|
-
signal: AbortSignal.timeout(15000),
|
|
1422
|
-
});
|
|
1423
|
-
if (!res.ok) {
|
|
1424
|
-
const err = await res.json().catch(() => ({ message: 'Submit failed' }));
|
|
1425
|
-
throw new Error(err.message || 'Submit failed');
|
|
1426
|
-
}
|
|
1427
|
-
const json = await res.json();
|
|
1428
|
-
result = json.data;
|
|
1429
|
-
}
|
|
1430
|
-
else {
|
|
1431
|
-
const client = requireExamConnection();
|
|
1432
|
-
if (!client)
|
|
1433
|
-
return;
|
|
1434
|
-
result = await client.submitExam(state.session.examId, state.answers);
|
|
1435
|
-
}
|
|
1436
|
-
drawProgress(50, 'Grading...');
|
|
1437
|
-
await sleep(300);
|
|
1438
|
-
drawProgress(100, 'Complete!');
|
|
1439
|
-
console.log();
|
|
1440
|
-
// Capture stats from state BEFORE we clear it for the debrief display.
|
|
1441
|
-
const preSubmit = {
|
|
1442
|
-
answered: Object.keys(state.answers).length,
|
|
1443
|
-
total: state.session.questionCount,
|
|
1444
|
-
bookmarks: Array.isArray(state.bookmarks) ? state.bookmarks.length : 0,
|
|
1445
|
-
help: getHelpState(state),
|
|
1446
|
-
aiUsage: state.aiUsage || { ai4ctf: 0, ctf4ai: 0 },
|
|
1447
|
-
interactions: (state.interactions || []).length,
|
|
1448
|
-
durationMin: state.session.durationMinutes || 0,
|
|
1449
|
-
confirmedAt: state.session.confirmedAt || state.session.startedAt,
|
|
1450
|
-
examId: state.session.examId,
|
|
1451
|
-
};
|
|
1452
|
-
clearExamState();
|
|
1453
|
-
const elapsedSec = Math.max(0, Math.round((Date.now() - new Date(preSubmit.confirmedAt).getTime()) / 1000));
|
|
1454
|
-
const elapsedMin = Math.floor(elapsedSec / 60);
|
|
1455
|
-
const elapsedS = elapsedSec % 60;
|
|
1456
|
-
// โโโ Student debrief โ qualitative only โโโ
|
|
1457
|
-
// National Selection: student sees pass/fail and directional feedback.
|
|
1458
|
-
// Numeric scores go to the country's exam center who decides the real
|
|
1459
|
-
// selection bar. This respects national autonomy โ each country picks
|
|
1460
|
-
// who advances based on their own criteria (top N, percentile, etc.).
|
|
1461
|
-
console.log();
|
|
1462
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1463
|
-
console.log();
|
|
1464
|
-
console.log(chalk.bold.white(' โโโ โโโโโโโ โโโโโโโ โโโโโโ'));
|
|
1465
|
-
console.log(chalk.bold.white(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1466
|
-
console.log(chalk.bold.white(' โโโโโโ โโโ โโโโโโโโโโโ'));
|
|
1467
|
-
console.log(chalk.bold.white(' โโโโโโ โโโ โโโโโโโโโโโ'));
|
|
1468
|
-
console.log(chalk.bold.white(' โโโโโโโโโโโโโโโโโโโโโโโ โโโ'));
|
|
1469
|
-
console.log(chalk.bold.white(' โโโ โโโโโโโ โโโโโโโ โโโ โโโ'));
|
|
1470
|
-
console.log();
|
|
1471
|
-
const statusLine = result.passed
|
|
1472
|
-
? chalk.green.bold(' โ Submission accepted โ baseline met')
|
|
1473
|
-
: chalk.yellow.bold(' โ Submission accepted');
|
|
1474
|
-
console.log(statusLine);
|
|
1475
|
-
console.log();
|
|
1476
|
-
console.log(chalk.white(' Your answers have been recorded and sent to your national'));
|
|
1477
|
-
console.log(chalk.white(' exam center. Final scoring and selection are decided by'));
|
|
1478
|
-
console.log(chalk.white(' the organizer in your country.'));
|
|
1479
|
-
console.log();
|
|
1480
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1481
|
-
console.log();
|
|
1482
|
-
// Your run โ personal stats only, no score
|
|
1483
|
-
console.log(chalk.bold.white(' Your run'));
|
|
1484
|
-
const unanswered = preSubmit.total - preSubmit.answered;
|
|
1485
|
-
console.log(' ' + chalk.gray('Answered: ') + chalk.white(`${preSubmit.answered}/${preSubmit.total}`)
|
|
1486
|
-
+ (unanswered > 0 ? chalk.gray(` ยท ${unanswered} skipped`) : ''));
|
|
1487
|
-
console.log(' ' + chalk.gray('Time used: ') + chalk.white(`${elapsedMin}m ${elapsedS}s`)
|
|
1488
|
-
+ chalk.gray(` of ${preSubmit.durationMin}m total`));
|
|
1489
|
-
if (preSubmit.bookmarks > 0) {
|
|
1490
|
-
console.log(' ' + chalk.gray('Flagged: ') + chalk.yellow(`โ
${preSubmit.bookmarks}`));
|
|
1491
|
-
}
|
|
1492
|
-
console.log();
|
|
1493
|
-
// Qualitative category feedback: "stronger" vs "area to grow" based on
|
|
1494
|
-
// relative rank, not absolute percentages. Never reveals numbers.
|
|
1495
|
-
const catStats = result.categoryStats;
|
|
1496
|
-
if (catStats && Object.keys(catStats).length >= 2) {
|
|
1497
|
-
const ranked = Object.entries(catStats)
|
|
1498
|
-
.filter(([, s]) => s.total > 0)
|
|
1499
|
-
.map(([cat, s]) => ({ cat, pct: s.correct / s.total }))
|
|
1500
|
-
.sort((a, b) => b.pct - a.pct);
|
|
1501
|
-
const topCount = Math.min(2, Math.ceil(ranked.length / 3));
|
|
1502
|
-
const bottomCount = Math.min(2, Math.ceil(ranked.length / 3));
|
|
1503
|
-
const strong = ranked.slice(0, topCount).map((r) => r.cat);
|
|
1504
|
-
const grow = ranked.slice(-bottomCount).map((r) => r.cat);
|
|
1505
|
-
// Only show if there's actual variation (avoid "strong: X, grow: X")
|
|
1506
|
-
const overlap = strong.some((s) => grow.includes(s));
|
|
1507
|
-
if (!overlap) {
|
|
1508
|
-
console.log(chalk.bold.white(' Directional feedback'));
|
|
1509
|
-
console.log(' ' + chalk.green('Stronger here: ') + chalk.white(strong.join(', ')));
|
|
1510
|
-
console.log(' ' + chalk.yellow('Room to grow: ') + chalk.white(grow.join(', ')));
|
|
1511
|
-
console.log(chalk.gray(' (relative to other categories โ numeric scores go to your exam center)'));
|
|
1512
|
-
console.log();
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
// Assistance usage โ personal, fine to show
|
|
1516
|
-
console.log(chalk.bold.white(' Assistance used'));
|
|
1517
|
-
console.log(' ' + chalk.gray('Help: ') + chalk.white(`${preSubmit.help.used}/${preSubmit.help.max}`));
|
|
1518
|
-
console.log(' ' + chalk.gray('AI tokens: ') + chalk.white(`${preSubmit.aiUsage.ai4ctf} AI4CTF`) + chalk.gray(' ยท ') + chalk.white(`${preSubmit.aiUsage.ctf4ai} CTF4AI`));
|
|
1519
|
-
console.log();
|
|
1520
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1521
|
-
console.log();
|
|
1522
|
-
// Next steps
|
|
1523
|
-
console.log(chalk.bold.white(' What next'));
|
|
1524
|
-
console.log(chalk.gray(' ยท Your national organizer will announce selection results.'));
|
|
1525
|
-
console.log(chalk.gray(' ยท Keep sharpening: ') + chalk.white('demo') + chalk.gray(' is free, unlimited practice.'));
|
|
1526
|
-
console.log(chalk.gray(' ยท Reference guides for 38 tools: ') + chalk.white('ref'));
|
|
1527
|
-
console.log();
|
|
1528
|
-
// v2.19.98 โ Paper C โ B upgrade funnel. Students who pass the entry
|
|
1529
|
-
// MCQ paper get a platform-aware nudge toward Paper B (K-12 + AI).
|
|
1530
|
-
// No-op for non-C papers and for non-passers. See src/lib/paper-upgrade.ts.
|
|
1531
|
-
const { showCToBUpgradePrompt } = await import('../lib/paper-upgrade.js');
|
|
1532
|
-
showCToBUpgradePrompt(preSubmit.examId, result.passed);
|
|
1533
|
-
console.log(chalk.yellow(' ICOA 2026 ยท Sydney, Australia ยท Jun 27 โ Jul 2'));
|
|
1534
|
-
console.log(chalk.cyan.underline(' https://icoa2026.au'));
|
|
1535
|
-
console.log();
|
|
1536
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
1537
|
-
console.log();
|
|
1538
|
-
}
|
|
1539
|
-
catch (err) {
|
|
1540
|
-
console.log();
|
|
1541
|
-
printError(err.message);
|
|
1542
|
-
}
|
|
1543
|
-
});
|
|
1544
|
-
// โโโ exam result โโโ
|
|
1545
|
-
exam
|
|
1546
|
-
.command('result [id]')
|
|
1547
|
-
.description('View exam result')
|
|
1548
|
-
.action(async (examId) => {
|
|
1549
|
-
logCommand(`exam result ${examId || ''}`);
|
|
1550
|
-
const client = requireExamConnection();
|
|
1551
|
-
if (!client)
|
|
1552
|
-
return;
|
|
1553
|
-
if (!examId) {
|
|
1554
|
-
const state = getExamState();
|
|
1555
|
-
if (state) {
|
|
1556
|
-
examId = state.session.examId;
|
|
1557
|
-
}
|
|
1558
|
-
else {
|
|
1559
|
-
printError('Specify exam ID: exam result <id>');
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
const spinner = createSpinner('Loading result...');
|
|
1564
|
-
spinner.start();
|
|
1565
|
-
try {
|
|
1566
|
-
const result = await client.getResult(examId);
|
|
1567
|
-
spinner.succeed('Result loaded');
|
|
1568
|
-
console.log();
|
|
1569
|
-
printHeader(result.examName);
|
|
1570
|
-
printKeyValue('Score', chalk.bold(`${result.score}/${result.total}`));
|
|
1571
|
-
printKeyValue('Percentage', chalk.bold(`${result.percentage}%`));
|
|
1572
|
-
printKeyValue('Status', result.passed ? chalk.green.bold('PASSED') : chalk.red.bold('NOT PASSED'));
|
|
1573
|
-
printKeyValue('Submitted', result.submittedAt);
|
|
1574
|
-
console.log();
|
|
1575
|
-
}
|
|
1576
|
-
catch (err) {
|
|
1577
|
-
spinner.fail('Failed to load result');
|
|
1578
|
-
printError(err.message);
|
|
1579
|
-
}
|
|
1580
|
-
});
|
|
1581
|
-
// โโโ exam token <code> (Selection mode: no login needed) โโโ
|
|
1582
|
-
exam
|
|
1583
|
-
.command('token <code>')
|
|
1584
|
-
.description('Enter exam with access token (no login needed)')
|
|
1585
|
-
.action(async (code) => {
|
|
1586
|
-
logCommand(`exam token ${code}`);
|
|
1587
|
-
const { getRealExamState, saveExamState: saveExamStateFn, clearExamState: clearState } = await import('../lib/exam-state.js');
|
|
1588
|
-
const existing = getRealExamState();
|
|
1589
|
-
if (existing) {
|
|
1590
|
-
const existingToken = existing.session.token;
|
|
1591
|
-
// Auto-recover from abandoned/expired sessions. If the stored state's
|
|
1592
|
-
// deadline is >30 min in the past, the session is unusable (past the
|
|
1593
|
-
// server's submit grace window). If user types a DIFFERENT token in
|
|
1594
|
-
// this state, they clearly mean "start fresh". Clear silently rather
|
|
1595
|
-
// than locking them out forever. Prevents the stale-A-paper-content +
|
|
1596
|
-
// "Could not reload questions in new language" lockout.
|
|
1597
|
-
const deadlineNow = getExamDeadline();
|
|
1598
|
-
const typedDiffers = !!existingToken && existingToken.trim().toUpperCase() !== code.trim().toUpperCase();
|
|
1599
|
-
const expiredPast30min = deadlineNow ? (deadlineNow.getTime() + 30 * 60 * 1000) < Date.now() : false;
|
|
1600
|
-
if (typedDiffers && expiredPast30min) {
|
|
1601
|
-
clearState();
|
|
1602
|
-
console.log();
|
|
1603
|
-
console.log(chalk.gray(' (previous exam session expired โ cleared state, starting fresh with your new token)'));
|
|
1604
|
-
console.log();
|
|
1605
|
-
// Fall through to the fresh-start flow below (skip the "already in progress" guard)
|
|
1606
|
-
// Re-run this action: easiest to just continue out of the `if (existing)` block
|
|
1607
|
-
// by setting a flag. We do that by falling through โ but since we're inside
|
|
1608
|
-
// `if (existing)`, we need to proceed. Easiest: re-invoke the inner start flow.
|
|
1609
|
-
// Actually: since we cleared state, the subsequent code paths will treat it
|
|
1610
|
-
// as a fresh session. But we're inside `if (existing)`. Jump to the fresh path
|
|
1611
|
-
// by executing a goto-equivalent via function call. Simplest: return here
|
|
1612
|
-
// after instructing the user to retype the command.
|
|
1613
|
-
console.log(chalk.white(' Please re-type: ') + chalk.bold.cyan(`exam ${code}`));
|
|
1614
|
-
console.log();
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
// Migration path: pre-v2.19.55 sessions didn't persist the token on
|
|
1618
|
-
// state. Those sessions can't use `lang` or submit via the new token
|
|
1619
|
-
// path. If the user re-types the matching token, attach it so the
|
|
1620
|
-
// session can reach the server again. Device binding on the server
|
|
1621
|
-
// means only the original token holder can do this.
|
|
1622
|
-
if (!existingToken && code) {
|
|
1623
|
-
existing.session.token = code.trim();
|
|
1624
|
-
saveExamStateFn(existing);
|
|
1625
|
-
console.log();
|
|
1626
|
-
console.log(chalk.green(' โ Token re-attached to your in-progress exam.'));
|
|
1627
|
-
console.log(chalk.gray(' You can now use: ') + chalk.white('lang <code>') + chalk.gray(' ยท ') + chalk.white('exam submit'));
|
|
1628
|
-
console.log();
|
|
1629
|
-
console.log(chalk.bold.white(' Continue this exam:'));
|
|
1630
|
-
console.log(chalk.gray(' โ ') + chalk.bold.cyan('exam q 1') + chalk.gray(' resume at any question'));
|
|
1631
|
-
console.log(chalk.gray(' โ ') + chalk.bold.cyan('exam review') + chalk.gray(' see progress + flagged items'));
|
|
1632
|
-
console.log();
|
|
1633
|
-
return;
|
|
1634
|
-
}
|
|
1635
|
-
const answered = Object.keys(existing.answers).length;
|
|
1636
|
-
const total = existing.session.questionCount;
|
|
1637
|
-
const deadline = getExamDeadline();
|
|
1638
|
-
const remainingMin = deadline ? Math.max(0, Math.round((deadline.getTime() - Date.now()) / 60000)) : null;
|
|
1639
|
-
console.log();
|
|
1640
|
-
console.log(chalk.yellow(` โ An exam is already in progress on this device.`));
|
|
1641
|
-
console.log();
|
|
1642
|
-
console.log(chalk.gray(' Exam: ') + chalk.white(existing.session.examName));
|
|
1643
|
-
if (existingToken) {
|
|
1644
|
-
console.log(chalk.gray(' Token: ') + chalk.white(existingToken) + (existingToken === code ? chalk.green(' (same as you just typed)') : chalk.yellow(' (different from the one you typed)')));
|
|
1645
|
-
}
|
|
1646
|
-
console.log(chalk.gray(' Answers: ') + chalk.white(`${answered}/${total}`));
|
|
1647
|
-
if (remainingMin !== null) {
|
|
1648
|
-
console.log(chalk.gray(' Time: ') + chalk.white(`${remainingMin} min remaining`));
|
|
1649
|
-
}
|
|
1650
|
-
console.log();
|
|
1651
|
-
console.log(chalk.bold.white(' To continue this exam:'));
|
|
1652
|
-
console.log(chalk.gray(' โ ') + chalk.bold.cyan('exam q 1') + chalk.gray(' resume at any question'));
|
|
1653
|
-
console.log(chalk.gray(' โ ') + chalk.bold.cyan('exam review') + chalk.gray(' see progress + flagged items'));
|
|
1654
|
-
console.log(chalk.gray(' โ ') + chalk.bold.cyan('exam submit') + chalk.gray(' finish and submit'));
|
|
1655
|
-
if (existingToken && existingToken !== code) {
|
|
1656
|
-
console.log();
|
|
1657
|
-
console.log(chalk.yellow(' Note: you typed a different token. Each exam token is bound to'));
|
|
1658
|
-
console.log(chalk.yellow(' one device + one session. You cannot switch tokens mid-exam.'));
|
|
1659
|
-
console.log();
|
|
1660
|
-
console.log(chalk.gray(' If you need to abandon this session (e.g. expired / wrong paper),'));
|
|
1661
|
-
console.log(chalk.gray(' run ') + chalk.bold.cyan('exam reset') + chalk.gray(' to wipe local state, then re-enter the new token.'));
|
|
1662
|
-
}
|
|
1663
|
-
console.log();
|
|
1664
|
-
return;
|
|
1665
|
-
}
|
|
1666
|
-
// Gate: require exam setup
|
|
1667
|
-
// v2.19.97 โ Windows cmd users bypass the exam-setup gate entirely.
|
|
1668
|
-
// Their recommended path is C paper (MCQ only, no Python / no Unix tools),
|
|
1669
|
-
// so forcing them through `exam setup` (which installs pip packages) would
|
|
1670
|
-
// be both unnecessary and likely to fail. Server enforces tokenโexam_id
|
|
1671
|
-
// binding (1:1 FK), so if they use a B/A token on cmd they'll still get
|
|
1672
|
-
// routed to the correct exam; but the missing tools may cost them points
|
|
1673
|
-
// on Q31-36. That's the trade-off they opted into.
|
|
1674
|
-
const { isNativeWindowsCmd: _isNativeWindowsCmd } = await import('../lib/platform.js');
|
|
1675
|
-
const cmdPath = _isNativeWindowsCmd();
|
|
1676
|
-
if (!cmdPath && !isExamSetupComplete()) {
|
|
1677
|
-
console.log();
|
|
1678
|
-
printWarning('Pre-exam setup required before entering a token.');
|
|
1679
|
-
console.log(chalk.gray(' โ ') + chalk.bold.cyan('exam setup'));
|
|
1680
|
-
console.log(chalk.gray(' Or type ') + chalk.bold.cyan('back') + chalk.gray(' to return to the main menu.'));
|
|
1681
|
-
console.log();
|
|
1682
|
-
return;
|
|
1683
|
-
}
|
|
1684
|
-
// Auto-set language from token country prefix
|
|
1685
|
-
const COUNTRY_LANG = {
|
|
1686
|
-
UA: 'uk', PE: 'es', CN: 'zh', AU: 'en', JP: 'ja', KR: 'ko',
|
|
1687
|
-
BR: 'pt', SA: 'ar', FR: 'fr', DE: 'de', IN: 'hi', ID: 'id',
|
|
1688
|
-
TH: 'th', VN: 'vi', TR: 'tr', RU: 'ru', EG: 'ar', HT: 'ht',
|
|
1689
|
-
PH: 'en', MY: 'en', SG: 'en', ZA: 'en',
|
|
1690
|
-
};
|
|
1691
|
-
const countryPrefix = code.trim().substring(0, 2).toUpperCase();
|
|
1692
|
-
const detectedLang = COUNTRY_LANG[countryPrefix];
|
|
1693
|
-
const config = getConfig();
|
|
1694
|
-
const serverUrl = config.ctfdUrl || 'https://practice.icoa2026.au';
|
|
1695
|
-
const lang = detectedLang || config.language || 'en';
|
|
1696
|
-
if (detectedLang && detectedLang !== (config.language || 'en')) {
|
|
1697
|
-
saveConfig({ language: detectedLang });
|
|
1698
|
-
}
|
|
1699
|
-
console.log();
|
|
1700
|
-
drawProgress(0, 'Validating token...');
|
|
1701
|
-
try {
|
|
1702
|
-
const res = await fetch(`${serverUrl}/api/icoa/exam-token`, {
|
|
1703
|
-
method: 'POST',
|
|
1704
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1705
|
-
body: JSON.stringify({
|
|
1706
|
-
token: code.trim(),
|
|
1707
|
-
deviceHash: getDeviceFingerprint(),
|
|
1708
|
-
lang,
|
|
1709
|
-
}),
|
|
1710
|
-
signal: AbortSignal.timeout(10000),
|
|
1711
|
-
});
|
|
1712
|
-
if (!res.ok) {
|
|
1713
|
-
drawProgress(0, '');
|
|
1714
|
-
console.log();
|
|
1715
|
-
const err = await res.json().catch(() => ({ message: 'Invalid token' }));
|
|
1716
|
-
printError(err.message || 'Invalid exam token');
|
|
1717
|
-
printInfo('Check your token or contact your proctor.');
|
|
1718
|
-
return;
|
|
1719
|
-
}
|
|
1720
|
-
drawProgress(30, 'Loading exam...');
|
|
1721
|
-
const json = await res.json();
|
|
1722
|
-
const { session, questions, languages } = json.data;
|
|
1723
|
-
drawProgress(60, 'Preparing...');
|
|
1724
|
-
await sleep(200);
|
|
1725
|
-
drawProgress(100, 'Ready!');
|
|
1726
|
-
console.log();
|
|
1727
|
-
console.log();
|
|
1728
|
-
// โโ Exam info page (timer NOT started yet) โโ
|
|
1729
|
-
printHeader(session.examName);
|
|
1730
|
-
console.log();
|
|
1731
|
-
printKeyValue('Questions', `${session.questionCount} (30 MCQ + 10 practical)`);
|
|
1732
|
-
printKeyValue('Duration', `${session.durationMinutes} minutes`);
|
|
1733
|
-
printKeyValue('Total', `${session.totalScore || 150} points`);
|
|
1734
|
-
printKeyValue('Pass', `${session.passingScore || 75} points (50%)`);
|
|
1735
|
-
console.log();
|
|
1736
|
-
console.log(chalk.white(' Assistance:'));
|
|
1737
|
-
console.log(chalk.gray(' Help: 10 uses + 5 hidden bonus (MCQ only)'));
|
|
1738
|
-
console.log(chalk.gray(' Hints: A(5) B(3) C(1) โ practical only'));
|
|
1739
|
-
console.log(chalk.gray(' AI: 25K AI4CTF + 25K CTF4AI tokens'));
|
|
1740
|
-
if (languages && languages.length > 1) {
|
|
1741
|
-
console.log(chalk.gray(` Lang: ${languages.join(', ')} โ switch with: lang <code>`));
|
|
1742
|
-
}
|
|
1743
|
-
console.log();
|
|
1744
|
-
console.log(chalk.yellow(' Rules:'));
|
|
1745
|
-
console.log(chalk.gray(' โข All interactions are recorded for audit'));
|
|
1746
|
-
console.log(chalk.gray(' โข Timer auto-submits when time expires'));
|
|
1747
|
-
console.log(chalk.gray(' โข You may exit and resume with the same token'));
|
|
1748
|
-
console.log();
|
|
1749
|
-
// โโ Wait for confirmation โโ
|
|
1750
|
-
// Raw stdin read (not readline.createInterface) โ a second readline on
|
|
1751
|
-
// process.stdin fights the parent REPL's readline and leaves stdin in
|
|
1752
|
-
// a half-detached state, so subsequent commands silently get no input.
|
|
1753
|
-
await new Promise((resolve) => {
|
|
1754
|
-
process.stdout.write(chalk.bold.yellow(' Press Enter to start the exam timer... '));
|
|
1755
|
-
const wasRaw = process.stdin.isTTY ? process.stdin.isRaw : false;
|
|
1756
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
1757
|
-
process.stdin.setRawMode(false);
|
|
1758
|
-
}
|
|
1759
|
-
const onData = (chunk) => {
|
|
1760
|
-
const s = chunk.toString();
|
|
1761
|
-
if (s.includes('\n') || s.includes('\r')) {
|
|
1762
|
-
process.stdin.removeListener('data', onData);
|
|
1763
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
1764
|
-
process.stdin.setRawMode(wasRaw);
|
|
1765
|
-
}
|
|
1766
|
-
process.stdout.write('\n');
|
|
1767
|
-
resolve();
|
|
1768
|
-
}
|
|
1769
|
-
};
|
|
1770
|
-
process.stdin.on('data', onData);
|
|
1771
|
-
process.stdin.resume();
|
|
1772
|
-
});
|
|
1773
|
-
// โโ Timer starts NOW โโ
|
|
1774
|
-
const confirmedAt = new Date().toISOString();
|
|
1775
|
-
session.confirmedAt = confirmedAt;
|
|
1776
|
-
// Store the token on session so lang switching, exam submit, and
|
|
1777
|
-
// resume can reach the server without re-prompting. Device binding
|
|
1778
|
-
// means the token alone is useless on another machine, so keeping
|
|
1779
|
-
// it in exam-state.json is safe.
|
|
1780
|
-
session.token = code.trim();
|
|
1781
|
-
// Best-effort server-time sync. Corrects client clock drift (NTP-less
|
|
1782
|
-
// Kali live-boots, timezone misconfigs) so the displayed countdown
|
|
1783
|
-
// matches the server's authoritative deadline. Silent on failure โ
|
|
1784
|
-
// old servers (no endpoint) or offline clients fall back to the
|
|
1785
|
-
// local-clock-only deadline (see getExamDeadline in exam-state.ts).
|
|
1786
|
-
try {
|
|
1787
|
-
const t0 = Date.now();
|
|
1788
|
-
const tRes = await fetch(`${serverUrl}/api/icoa/server-time`, {
|
|
1789
|
-
method: 'GET',
|
|
1790
|
-
signal: AbortSignal.timeout(3000),
|
|
1791
|
-
});
|
|
1792
|
-
if (tRes.ok) {
|
|
1793
|
-
const tJson = await tRes.json();
|
|
1794
|
-
const serverMs = tJson?.data?.server_time_ms;
|
|
1795
|
-
if (typeof serverMs === 'number' && serverMs > 0) {
|
|
1796
|
-
// Offset is measured at the midpoint of the request window to
|
|
1797
|
-
// halve the latency bias (serverMs reflects when the server
|
|
1798
|
-
// read its clock; midpoint of our send/receive window is the
|
|
1799
|
-
// best single-sample approximation of when that reading aligns
|
|
1800
|
-
// with our clock).
|
|
1801
|
-
const clientMs = (t0 + Date.now()) / 2;
|
|
1802
|
-
session.clockOffsetMs = Math.round(serverMs - clientMs);
|
|
1803
|
-
session.deadlineServerMs = serverMs + session.durationMinutes * 60 * 1000;
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
catch {
|
|
1808
|
-
// Offline or old server โ keep fallback (confirmedAt + duration local).
|
|
1809
|
-
}
|
|
1810
|
-
saveExamState({ session, questions, answers: {}, interactions: [], aiUsage: { ai4ctf: 0, ctf4ai: 0 } });
|
|
1811
|
-
console.log();
|
|
1812
|
-
printSuccess('Exam started! Timer is running.');
|
|
1813
|
-
printKeyValue('Time Remaining', `${session.durationMinutes}:00`);
|
|
1814
|
-
if (questions.length > 0) {
|
|
1815
|
-
printQuestion(questions[0]);
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
catch (err) {
|
|
1819
|
-
console.log();
|
|
1820
|
-
if (err.name === 'TimeoutError') {
|
|
1821
|
-
printError('Server not reachable. Check your internet connection.');
|
|
1822
|
-
}
|
|
1823
|
-
else {
|
|
1824
|
-
printError(err.message || 'Failed to start exam');
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
});
|
|
1828
|
-
// โโโ exam demo โโโ
|
|
1829
|
-
exam
|
|
1830
|
-
.command('reset', { hidden: true })
|
|
1831
|
-
.description('Abandon current exam session and wipe local state (recovery only)')
|
|
1832
|
-
.action(async () => {
|
|
1833
|
-
logCommand('exam reset');
|
|
1834
|
-
const { getRealExamState, clearExamState: clearState } = await import('../lib/exam-state.js');
|
|
1835
|
-
const existing = getRealExamState();
|
|
1836
|
-
if (!existing) {
|
|
1837
|
-
console.log();
|
|
1838
|
-
console.log(chalk.gray(' No active exam session to reset.'));
|
|
1839
|
-
console.log();
|
|
1840
|
-
return;
|
|
1841
|
-
}
|
|
1842
|
-
const tok = existing.session.token;
|
|
1843
|
-
console.log();
|
|
1844
|
-
console.log(chalk.yellow(' โ Reset will abandon your current exam session on this device.'));
|
|
1845
|
-
console.log(chalk.gray(' Exam: ') + chalk.white(existing.session.examName));
|
|
1846
|
-
if (tok)
|
|
1847
|
-
console.log(chalk.gray(' Token: ') + chalk.white(tok));
|
|
1848
|
-
console.log(chalk.gray(' The token itself is NOT revoked server-side. If your session is'));
|
|
1849
|
-
console.log(chalk.gray(' still active and not submitted, re-entering the same token resumes it.'));
|
|
1850
|
-
console.log();
|
|
1851
|
-
console.log(chalk.gray(' Type ') + chalk.bold.cyan('reset') + chalk.gray(' to confirm, or anything else to cancel:'));
|
|
1852
|
-
// Typed-word confirmation โ matches existing exam submit confirm pattern
|
|
1853
|
-
const confirmWord = await new Promise((resolve) => {
|
|
1854
|
-
process.stdout.write(' > ');
|
|
1855
|
-
const onData = (chunk) => {
|
|
1856
|
-
const s = chunk.toString().trim();
|
|
1857
|
-
if (s.includes('\n') || s.includes('\r') || s.length > 0) {
|
|
1858
|
-
process.stdin.removeListener('data', onData);
|
|
1859
|
-
resolve(s.replace(/[\r\n]/g, ''));
|
|
1860
|
-
}
|
|
1861
|
-
};
|
|
1862
|
-
process.stdin.on('data', onData);
|
|
1863
|
-
process.stdin.resume();
|
|
1864
|
-
});
|
|
1865
|
-
if (confirmWord.toLowerCase() !== 'reset') {
|
|
1866
|
-
console.log(chalk.gray(' Cancelled.'));
|
|
1867
|
-
console.log();
|
|
1868
|
-
return;
|
|
1869
|
-
}
|
|
1870
|
-
clearState();
|
|
1871
|
-
console.log();
|
|
1872
|
-
console.log(chalk.green(' โ Exam state cleared.'));
|
|
1873
|
-
console.log(chalk.gray(' You can now enter a new token with ') + chalk.bold.cyan('exam <token>'));
|
|
1874
|
-
console.log();
|
|
1875
|
-
});
|
|
1876
|
-
exam
|
|
1877
|
-
.command('demo')
|
|
1878
|
-
.description('Try a free practice exam (no account needed)')
|
|
1879
|
-
.action(async () => {
|
|
1880
|
-
logCommand('exam demo');
|
|
1881
|
-
reportDemoEvent('demo-enter');
|
|
1882
|
-
const { pickDemoQuestions, getLocalizedDemoSession, DEMO_PICK_SIZE, DEMO_POOL_SIZE } = await import('../lib/demo-exam.js');
|
|
1883
|
-
// First-time intro animation (skippable)
|
|
1884
|
-
const cfg = getConfig();
|
|
1885
|
-
if (!cfg.demoIntroSeen) {
|
|
1886
|
-
await playDemoIntro();
|
|
1887
|
-
saveConfig({ demoIntroSeen: true });
|
|
1888
|
-
}
|
|
1889
|
-
// T2-6: Pre-start confirmation. Prevents accidental launches (user typed
|
|
1890
|
-
// `demo` instead of exploring the menu). No timer pressure, but exam
|
|
1891
|
-
// state + AI budget get allocated the moment the first question lands,
|
|
1892
|
-
// so a 1-line "are you ready?" gate is cheap insurance. Ctrl+C here
|
|
1893
|
-
// cleanly exits via the REPL's SIGINT handler.
|
|
1894
|
-
console.log();
|
|
1895
|
-
console.log(chalk.white(' Demo: ') + chalk.gray(`${DEMO_PICK_SIZE} questions drawn from a pool of ${DEMO_POOL_SIZE}. No timer. Free practice.`));
|
|
1896
|
-
console.log(chalk.gray(' You can pause with ') + chalk.cyan('Ctrl+C') + chalk.gray(' or leave with ') + chalk.cyan('back') + chalk.gray(' at any time.'));
|
|
1897
|
-
await new Promise((resolve) => {
|
|
1898
|
-
process.stdout.write(chalk.bold.yellow(' Press Enter to begin... '));
|
|
1899
|
-
const wasRaw = process.stdin.isTTY ? process.stdin.isRaw : false;
|
|
1900
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
1901
|
-
process.stdin.setRawMode(false);
|
|
1902
|
-
}
|
|
1903
|
-
const onData = (chunk) => {
|
|
1904
|
-
const s = chunk.toString();
|
|
1905
|
-
if (s.includes('\n') || s.includes('\r')) {
|
|
1906
|
-
process.stdin.removeListener('data', onData);
|
|
1907
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
1908
|
-
process.stdin.setRawMode(wasRaw);
|
|
1909
|
-
}
|
|
1910
|
-
process.stdout.write('\n');
|
|
1911
|
-
resolve();
|
|
1912
|
-
}
|
|
1913
|
-
};
|
|
1914
|
-
process.stdin.on('data', onData);
|
|
1915
|
-
process.stdin.resume();
|
|
1916
|
-
});
|
|
1917
|
-
const DEMO_QUESTIONS = pickDemoQuestions(DEMO_PICK_SIZE);
|
|
1918
|
-
const DEMO_SESSION = getLocalizedDemoSession();
|
|
1919
|
-
// Demo uses separate state file โ doesn't conflict with real exam
|
|
1920
|
-
const { getDemoState, clearExamState: clearState } = await import('../lib/exam-state.js');
|
|
1921
|
-
const existingDemo = getDemoState();
|
|
1922
|
-
if (existingDemo) {
|
|
1923
|
-
clearState('demo-free');
|
|
1924
|
-
}
|
|
1925
|
-
console.log();
|
|
1926
|
-
printHeader('ICOA Demo Exam โ Free Practice');
|
|
1927
|
-
console.log();
|
|
1928
|
-
console.log(chalk.white(' Free practice ยท No account needed ยท No time limit'));
|
|
1929
|
-
console.log(chalk.white(` ${DEMO_PICK_SIZE} random questions from a pool of ${DEMO_POOL_SIZE} ยท Pick one answer per question`));
|
|
1930
|
-
// Best score tracker
|
|
1931
|
-
const stats = getDemoStats();
|
|
1932
|
-
if (stats.attempts > 0) {
|
|
1933
|
-
const bestPct = stats.bestPercentage;
|
|
1934
|
-
const bestColor = bestPct >= 80 ? chalk.green : bestPct >= 60 ? chalk.yellow : chalk.white;
|
|
1935
|
-
console.log();
|
|
1936
|
-
console.log(chalk.gray(' Your best: ') + bestColor(`${stats.best}/${DEMO_PICK_SIZE} (${bestPct}%)`) + chalk.gray(` ยท ${stats.attempts} attempts`));
|
|
1937
|
-
}
|
|
1938
|
-
console.log();
|
|
1939
|
-
printHowToPlay();
|
|
1940
|
-
console.log();
|
|
1941
|
-
// Show language info
|
|
1942
|
-
const { getConfig: gc } = await import('../lib/config.js');
|
|
1943
|
-
const currentLang = gc().language || 'en';
|
|
1944
|
-
if (currentLang === 'en') {
|
|
1945
|
-
console.log(chalk.gray(' Questions in English. To switch language first:'));
|
|
1946
|
-
console.log(chalk.gray(' lang es (Espaรฑol) ยท lang zh (ไธญๆ) ยท lang ko (ํ๊ตญ์ด) ยท lang ja (ๆฅๆฌ่ช)'));
|
|
1947
|
-
console.log(chalk.gray(' lang fr ยท lang ar ยท lang pt ยท 15 languages supported'));
|
|
1948
|
-
}
|
|
1949
|
-
else {
|
|
1950
|
-
console.log(chalk.green(` Language: ${currentLang}`));
|
|
1951
|
-
}
|
|
1952
|
-
console.log();
|
|
1953
|
-
console.log(chalk.white(' Starting exam...'));
|
|
1954
|
-
const session = { ...DEMO_SESSION, startedAt: new Date().toISOString() };
|
|
1955
|
-
console.log();
|
|
1956
|
-
drawProgress(0, 'Preparing questions...');
|
|
1957
|
-
await sleep(200);
|
|
1958
|
-
drawProgress(40, 'Shuffling options...');
|
|
1959
|
-
await sleep(200);
|
|
1960
|
-
drawProgress(80, 'Almost ready...');
|
|
1961
|
-
await sleep(150);
|
|
1962
|
-
drawProgress(100, 'Ready!');
|
|
1963
|
-
console.log();
|
|
1964
|
-
console.log();
|
|
1965
|
-
// Demo keeps the lighter 5 + 3 help budget (10 questions, so 5 base =
|
|
1966
|
-
// half the questions; plenty without making the exam trivial).
|
|
1967
|
-
saveExamState({ session, questions: DEMO_QUESTIONS, answers: {}, _helpMax: 5 });
|
|
1968
|
-
printKeyValue('Questions', String(DEMO_PICK_SIZE));
|
|
1969
|
-
printKeyValue('Duration', 'No time limit');
|
|
1970
|
-
// Show first question
|
|
1971
|
-
printQuestion(DEMO_QUESTIONS[0]);
|
|
1972
|
-
});
|
|
1973
|
-
// โโโ exam demo-retry โโโ (runs demo with the wrong questions from last attempt)
|
|
1974
|
-
exam
|
|
1975
|
-
.command('demo-retry')
|
|
1976
|
-
.description('Retry only the questions you got wrong last demo attempt')
|
|
1977
|
-
.action(async () => {
|
|
1978
|
-
logCommand('exam demo-retry');
|
|
1979
|
-
reportDemoEvent('post-report-retry');
|
|
1980
|
-
const { getLocalizedDemoSession } = await import('../lib/demo-exam.js');
|
|
1981
|
-
const retryQueue = getRetryQueue();
|
|
1982
|
-
if (!retryQueue || retryQueue.length === 0) {
|
|
1983
|
-
console.log();
|
|
1984
|
-
console.log(chalk.yellow(' No wrong questions to retry. Run ') + chalk.bold.cyan('demo') + chalk.yellow(' first.'));
|
|
1985
|
-
console.log();
|
|
1986
|
-
return;
|
|
1987
|
-
}
|
|
1988
|
-
const existing = getExamState();
|
|
1989
|
-
if (existing) {
|
|
1990
|
-
if (existing.session.examId === 'demo-free') {
|
|
1991
|
-
clearExamState();
|
|
1992
|
-
}
|
|
1993
|
-
else {
|
|
1994
|
-
printWarning(`Exam "${existing.session.examName}" is in progress.`);
|
|
1995
|
-
printInfo('Submit it first: exam submit');
|
|
1996
|
-
return;
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
// Reshuffle each question's options (keeps learning fresh), renumber 1..n
|
|
2000
|
-
const shuffleOpts = (q) => {
|
|
2001
|
-
if (!q.answer)
|
|
2002
|
-
return q;
|
|
2003
|
-
const correctText = q.options[q.answer];
|
|
2004
|
-
const values = ['A', 'B', 'C', 'D'].map((k) => q.options[k]);
|
|
2005
|
-
for (let i = values.length - 1; i > 0; i--) {
|
|
2006
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
2007
|
-
[values[i], values[j]] = [values[j], values[i]];
|
|
2008
|
-
}
|
|
2009
|
-
const newOptions = { A: values[0], B: values[1], C: values[2], D: values[3] };
|
|
2010
|
-
const newAnswer = ['A', 'B', 'C', 'D'].find((k) => newOptions[k] === correctText);
|
|
2011
|
-
return { ...q, options: newOptions, answer: newAnswer };
|
|
2012
|
-
};
|
|
2013
|
-
const retryQuestions = retryQueue.map((q, i) => ({
|
|
2014
|
-
...shuffleOpts(q),
|
|
2015
|
-
number: i + 1,
|
|
2016
|
-
}));
|
|
2017
|
-
const baseSession = getLocalizedDemoSession();
|
|
2018
|
-
const session = {
|
|
2019
|
-
...baseSession,
|
|
2020
|
-
examName: `${baseSession.examName} (Retry wrong only)`,
|
|
2021
|
-
questionCount: retryQuestions.length,
|
|
2022
|
-
startedAt: new Date().toISOString(),
|
|
2023
|
-
};
|
|
2024
|
-
console.log();
|
|
2025
|
-
printHeader('Demo Retry โ Wrong Questions Only');
|
|
2026
|
-
console.log();
|
|
2027
|
-
console.log(chalk.white(` Practicing ${retryQuestions.length} question${retryQuestions.length === 1 ? '' : 's'} you got wrong.`));
|
|
2028
|
-
console.log(chalk.gray(' Options have been reshuffled.'));
|
|
2029
|
-
console.log();
|
|
2030
|
-
saveExamState({ session, questions: retryQuestions, answers: {} });
|
|
2031
|
-
printKeyValue('Questions', String(retryQuestions.length));
|
|
2032
|
-
printKeyValue('Duration', 'No time limit');
|
|
2033
|
-
printQuestion(retryQuestions[0]);
|
|
2034
|
-
});
|
|
2035
|
-
// โโโ exam setup โโโ
|
|
2036
|
-
exam
|
|
2037
|
-
.command('setup')
|
|
2038
|
-
.description('Install Python environment for practical exam questions')
|
|
2039
|
-
.action(async () => {
|
|
2040
|
-
logCommand('exam setup');
|
|
2041
|
-
// Gate: require at least 1 demo attempt
|
|
2042
|
-
const stats = getDemoStats();
|
|
2043
|
-
if (stats.attempts === 0) {
|
|
2044
|
-
console.log();
|
|
2045
|
-
printWarning('Complete the demo first before setting up.');
|
|
2046
|
-
console.log(chalk.gray(' โ ') + chalk.bold.cyan('exam demo'));
|
|
2047
|
-
console.log();
|
|
2048
|
-
return;
|
|
2049
|
-
}
|
|
2050
|
-
// Skip if already set up โ but only if MOST packages actually installed.
|
|
2051
|
-
// If previous run had 0 or very few successes, retry instead of saying
|
|
2052
|
-
// "already set up" (which is misleading and strands the user).
|
|
2053
|
-
const existingSetup = getExamSetup();
|
|
2054
|
-
if (existingSetup) {
|
|
2055
|
-
const installedCount = existingSetup.installedPackages.length;
|
|
2056
|
-
const totalAttempted = installedCount + existingSetup.failedPackages.length;
|
|
2057
|
-
const successRate = totalAttempted > 0 ? installedCount / totalAttempted : 0;
|
|
2058
|
-
if (installedCount >= 8 || successRate >= 0.7) {
|
|
2059
|
-
console.log();
|
|
2060
|
-
console.log(chalk.green(' โ ') + chalk.green('Environment already set up'));
|
|
2061
|
-
console.log(chalk.gray(` Completed: ${existingSetup.completedAt.split('T')[0]}`));
|
|
2062
|
-
console.log(chalk.gray(` Python: ${existingSetup.pythonVersion}`));
|
|
2063
|
-
console.log(chalk.gray(` Packages: ${installedCount} installed`));
|
|
2064
|
-
if (existingSetup.failedPackages.length > 0) {
|
|
2065
|
-
console.log(chalk.yellow(` Failed: ${existingSetup.failedPackages.join(', ')}`));
|
|
2066
|
-
}
|
|
2067
|
-
console.log();
|
|
2068
|
-
console.log(chalk.white(' Next step: ') + chalk.bold.cyan('exam <token>'));
|
|
2069
|
-
console.log(chalk.gray(' Or type ') + chalk.bold.cyan('back') + chalk.gray(' to return to the main menu.'));
|
|
2070
|
-
console.log();
|
|
2071
|
-
return;
|
|
2072
|
-
}
|
|
2073
|
-
// Prior setup mostly failed โ retry
|
|
2074
|
-
console.log();
|
|
2075
|
-
console.log(chalk.yellow(` Previous setup had only ${installedCount}/${totalAttempted} packages. Retrying...`));
|
|
2076
|
-
console.log();
|
|
2077
|
-
}
|
|
2078
|
-
console.log();
|
|
2079
|
-
printHeader('Exam Environment Setup');
|
|
2080
|
-
console.log();
|
|
2081
|
-
console.log(chalk.white(' Installing Python packages for practical questions.'));
|
|
2082
|
-
console.log(chalk.gray(' Expected: 1-2 minutes ยท ~150MB disk'));
|
|
2083
|
-
console.log();
|
|
2084
|
-
// Step 1: Check Python 3.10+ (all packages support 3.10, 3.12 recommended)
|
|
2085
|
-
const printPythonInstallGuide = (reason, currentVersion) => {
|
|
2086
|
-
const platform = process.platform;
|
|
2087
|
-
console.log();
|
|
2088
|
-
if (reason === 'missing') {
|
|
2089
|
-
printError('Python 3 not found.');
|
|
2090
|
-
}
|
|
2091
|
-
else {
|
|
2092
|
-
printError(`Python ${currentVersion} found, but 3.10+ required for the exam.`);
|
|
2093
|
-
}
|
|
2094
|
-
console.log();
|
|
2095
|
-
console.log(chalk.bold.white(' How to install Python 3.10+ (3.12 recommended):'));
|
|
2096
|
-
console.log();
|
|
2097
|
-
if (platform === 'darwin') {
|
|
2098
|
-
console.log(chalk.yellow(' macOS (Homebrew, recommended):'));
|
|
2099
|
-
console.log(chalk.green(' brew install python@3.12'));
|
|
2100
|
-
console.log();
|
|
2101
|
-
console.log(chalk.gray(' Or download installer:'));
|
|
2102
|
-
console.log(chalk.gray(' https://www.python.org/downloads/macos/'));
|
|
2103
|
-
}
|
|
2104
|
-
else if (platform === 'linux') {
|
|
2105
|
-
console.log(chalk.yellow(' Ubuntu / Debian (deadsnakes PPA):'));
|
|
2106
|
-
console.log(chalk.green(' sudo add-apt-repository ppa:deadsnakes/ppa -y'));
|
|
2107
|
-
console.log(chalk.green(' sudo apt update'));
|
|
2108
|
-
console.log(chalk.green(' sudo apt install -y python3.12 python3.12-venv python3.12-distutils'));
|
|
2109
|
-
console.log(chalk.gray(' sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1'));
|
|
2110
|
-
console.log();
|
|
2111
|
-
console.log(chalk.yellow(' Fedora / RHEL:'));
|
|
2112
|
-
console.log(chalk.green(' sudo dnf install -y python3.12'));
|
|
2113
|
-
console.log();
|
|
2114
|
-
console.log(chalk.yellow(' Arch / Manjaro:'));
|
|
2115
|
-
console.log(chalk.green(' sudo pacman -S python'));
|
|
2116
|
-
}
|
|
2117
|
-
else if (platform === 'win32') {
|
|
2118
|
-
console.log(chalk.yellow(' Windows (winget, recommended):'));
|
|
2119
|
-
console.log(chalk.green(' winget install Python.Python.3.12'));
|
|
2120
|
-
console.log();
|
|
2121
|
-
console.log(chalk.gray(' Or download installer:'));
|
|
2122
|
-
console.log(chalk.gray(' https://www.python.org/downloads/windows/'));
|
|
2123
|
-
}
|
|
2124
|
-
else {
|
|
2125
|
-
console.log(chalk.gray(' https://www.python.org/downloads/'));
|
|
2126
|
-
}
|
|
2127
|
-
console.log();
|
|
2128
|
-
console.log(chalk.white(' After installing, run: ') + chalk.bold.cyan('exam setup'));
|
|
2129
|
-
console.log();
|
|
2130
|
-
};
|
|
2131
|
-
const pythonBin = 'python3';
|
|
2132
|
-
let pythonVersion = '';
|
|
2133
|
-
try {
|
|
2134
|
-
const raw = execSync(`${pythonBin} --version`, { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
2135
|
-
pythonVersion = raw.replace('Python ', '');
|
|
2136
|
-
const parts = pythonVersion.split('.').map(Number);
|
|
2137
|
-
if (parts[0] < 3 || (parts[0] === 3 && parts[1] < 10)) {
|
|
2138
|
-
printPythonInstallGuide('too_old', pythonVersion);
|
|
2139
|
-
return;
|
|
2140
|
-
}
|
|
2141
|
-
if (parts[0] === 3 && parts[1] < 12) {
|
|
2142
|
-
console.log(chalk.green(` โ Python ${pythonVersion}`) + chalk.gray(' (works, but 3.12 recommended)'));
|
|
2143
|
-
}
|
|
2144
|
-
else {
|
|
2145
|
-
console.log(chalk.green(` โ Python ${pythonVersion}`));
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
catch {
|
|
2149
|
-
printPythonInstallGuide('missing');
|
|
2150
|
-
return;
|
|
2151
|
-
}
|
|
2152
|
-
// Step 2: Check pip
|
|
2153
|
-
try {
|
|
2154
|
-
execSync(`${pythonBin} -m pip --version`, { stdio: 'ignore', timeout: 5000 });
|
|
2155
|
-
console.log(chalk.green(' โ pip available'));
|
|
2156
|
-
}
|
|
2157
|
-
catch {
|
|
2158
|
-
console.log(chalk.yellow(' โ pip not found, trying ensurepip...'));
|
|
2159
|
-
try {
|
|
2160
|
-
execSync(`${pythonBin} -m ensurepip --upgrade`, { stdio: 'ignore', timeout: 30000 });
|
|
2161
|
-
console.log(chalk.green(' โ pip installed'));
|
|
2162
|
-
}
|
|
2163
|
-
catch {
|
|
2164
|
-
printError('Could not install pip.');
|
|
2165
|
-
return;
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
// Step 3: Install packages
|
|
2169
|
-
const PACKAGES = [
|
|
2170
|
-
'pwntools', 'cryptography', 'requests', 'beautifulsoup4',
|
|
2171
|
-
'numpy', 'pillow', 'scapy', 'sympy', 'z3-solver',
|
|
2172
|
-
'pycryptodome', 'ROPgadget', 'volatility3', 'regex',
|
|
2173
|
-
];
|
|
2174
|
-
const IMPORT_MAP = {
|
|
2175
|
-
'beautifulsoup4': 'bs4', 'pycryptodome': 'Crypto', 'z3-solver': 'z3',
|
|
2176
|
-
'pillow': 'PIL', 'pwntools': 'pwn', 'ROPgadget': 'ropgadget',
|
|
2177
|
-
};
|
|
2178
|
-
console.log();
|
|
2179
|
-
console.log(chalk.white(` Installing ${PACKAGES.length} packages...`));
|
|
2180
|
-
console.log();
|
|
2181
|
-
// Detect PEP 668 "externally-managed-environment" โ modern Ubuntu/Debian/Kali
|
|
2182
|
-
// block system-wide pip installs. Try a harmless install to detect.
|
|
2183
|
-
let pipExtraFlags = '';
|
|
2184
|
-
try {
|
|
2185
|
-
execSync(`${pythonBin} -m pip install --dry-run pip 2>&1`, { encoding: 'utf-8', timeout: 10000 });
|
|
2186
|
-
}
|
|
2187
|
-
catch (e) {
|
|
2188
|
-
const msg = String(e?.stdout || e?.stderr || e?.message || '');
|
|
2189
|
-
if (/externally-managed-environment|break-system-packages/i.test(msg)) {
|
|
2190
|
-
// Pick the safer option: --user (installs to ~/.local, no sudo needed)
|
|
2191
|
-
pipExtraFlags = '--user --break-system-packages';
|
|
2192
|
-
console.log(chalk.yellow(' โน PEP 668 detected โ using --user install (no sudo needed)'));
|
|
2193
|
-
console.log();
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
// Python 3.13 warning: pwntools may fail to build
|
|
2197
|
-
const pyParts = pythonVersion.split('.').map(Number);
|
|
2198
|
-
if (pyParts[0] === 3 && pyParts[1] >= 13) {
|
|
2199
|
-
console.log(chalk.yellow(` โ Python ${pythonVersion} detected`));
|
|
2200
|
-
console.log(chalk.gray(' Some packages (pwntools, scapy) may not have wheels yet for 3.13.'));
|
|
2201
|
-
console.log(chalk.gray(' Python 3.12 is recommended. Install:'));
|
|
2202
|
-
if (process.platform === 'linux') {
|
|
2203
|
-
console.log(chalk.green(' sudo apt install python3.12 python3.12-venv'));
|
|
2204
|
-
console.log(chalk.green(' sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1'));
|
|
2205
|
-
}
|
|
2206
|
-
console.log();
|
|
2207
|
-
}
|
|
2208
|
-
const installed = [];
|
|
2209
|
-
const failed = [];
|
|
2210
|
-
let firstError = '';
|
|
2211
|
-
for (const pkg of PACKAGES) {
|
|
2212
|
-
try {
|
|
2213
|
-
execSync(`${pythonBin} -m pip install "${pkg}" ${pipExtraFlags} --quiet 2>&1`, {
|
|
2214
|
-
encoding: 'utf-8', timeout: 180000, stdio: 'pipe',
|
|
2215
|
-
});
|
|
2216
|
-
const importName = IMPORT_MAP[pkg] || pkg;
|
|
2217
|
-
execSync(`${pythonBin} -c "import ${importName}"`, { stdio: 'ignore', timeout: 10000 });
|
|
2218
|
-
let ver = '';
|
|
2219
|
-
try {
|
|
2220
|
-
const showOut = execSync(`${pythonBin} -m pip show "${pkg}" 2>/dev/null`, {
|
|
2221
|
-
encoding: 'utf-8', timeout: 5000,
|
|
2222
|
-
});
|
|
2223
|
-
const m = showOut.match(/^Version:\s*(.+)$/m);
|
|
2224
|
-
if (m)
|
|
2225
|
-
ver = m[1].trim();
|
|
2226
|
-
}
|
|
2227
|
-
catch { }
|
|
2228
|
-
console.log(chalk.green(` โ ${pkg}`) + (ver ? chalk.gray(` (${ver})`) : ''));
|
|
2229
|
-
installed.push(ver ? `${pkg}==${ver}` : pkg);
|
|
2230
|
-
}
|
|
2231
|
-
catch (e) {
|
|
2232
|
-
const raw = String(e?.stdout || e?.stderr || e?.message || '').slice(-400);
|
|
2233
|
-
const short = raw.split('\n').filter((l) => l.trim()).slice(-2).join(' | ').slice(0, 120);
|
|
2234
|
-
console.log(chalk.red(` โ ${pkg}`) + chalk.gray(` ${short}`));
|
|
2235
|
-
failed.push({ pkg, error: short });
|
|
2236
|
-
if (!firstError)
|
|
2237
|
-
firstError = raw;
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
// If wholesale failure, show the first full error so user can debug
|
|
2241
|
-
if (installed.length === 0 && firstError) {
|
|
2242
|
-
console.log();
|
|
2243
|
-
console.log(chalk.yellow(' First error detail (for debugging):'));
|
|
2244
|
-
console.log(chalk.gray(' ' + firstError.split('\n').slice(-6).join('\n ')));
|
|
2245
|
-
}
|
|
2246
|
-
// Save state
|
|
2247
|
-
saveExamSetup({
|
|
2248
|
-
completedAt: new Date().toISOString(),
|
|
2249
|
-
pythonVersion,
|
|
2250
|
-
installedPackages: installed,
|
|
2251
|
-
failedPackages: failed.map((f) => f.pkg),
|
|
2252
|
-
});
|
|
2253
|
-
console.log();
|
|
2254
|
-
if (failed.length === 0) {
|
|
2255
|
-
printSuccess(`Environment ready! All ${PACKAGES.length} packages installed.`);
|
|
2256
|
-
}
|
|
2257
|
-
else if (installed.length === 0) {
|
|
2258
|
-
printError(`Setup failed โ 0 of ${PACKAGES.length} packages installed.`);
|
|
2259
|
-
console.log();
|
|
2260
|
-
console.log(chalk.yellow(' Troubleshooting:'));
|
|
2261
|
-
if (pyParts[0] === 3 && pyParts[1] >= 13) {
|
|
2262
|
-
console.log(chalk.gray(' Python 3.13 is too new โ most CTF libraries lack wheels.'));
|
|
2263
|
-
console.log(chalk.gray(' Install Python 3.12 and re-run exam setup.'));
|
|
2264
|
-
}
|
|
2265
|
-
else {
|
|
2266
|
-
console.log(chalk.gray(' โข Check you have internet (pip.pypa.io must be reachable)'));
|
|
2267
|
-
console.log(chalk.gray(' โข Try: ') + chalk.cyan(`${pythonBin} -m pip install pwntools`) + chalk.gray(' โ see full error'));
|
|
2268
|
-
console.log(chalk.gray(' โข On Kali/Ubuntu 22+: might need python3.12-dev + build-essential'));
|
|
2269
|
-
}
|
|
2270
|
-
console.log();
|
|
2271
|
-
console.log(chalk.white(' Rerun when fixed: ') + chalk.bold.cyan('exam setup'));
|
|
2272
|
-
}
|
|
2273
|
-
else {
|
|
2274
|
-
printWarning(`${installed.length}/${PACKAGES.length} packages installed. ${failed.length} failed: ${failed.map((f) => f.pkg).join(', ')}`);
|
|
2275
|
-
}
|
|
2276
|
-
// Python usage tutorial for beginners
|
|
2277
|
-
console.log();
|
|
2278
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
2279
|
-
console.log(chalk.bold.white(' How to use Python in ICOA CLI'));
|
|
2280
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
2281
|
-
console.log();
|
|
2282
|
-
console.log(chalk.bold.yellow(' Method 1: One-liner') + chalk.gray(' (quick tasks)'));
|
|
2283
|
-
console.log(chalk.white(' Just add ! before the command:'));
|
|
2284
|
-
console.log(chalk.green(' !python3 -c "import base64; print(base64.b64decode(\'SGVsbG8=\'))"'));
|
|
2285
|
-
console.log(chalk.gray(' โ b\'Hello\''));
|
|
2286
|
-
console.log();
|
|
2287
|
-
console.log(chalk.bold.yellow(' Method 2: Interactive Python') + chalk.gray(' (explore & experiment)'));
|
|
2288
|
-
console.log(chalk.white(' Start a Python session, type commands one by one:'));
|
|
2289
|
-
console.log(chalk.green(' !python3'));
|
|
2290
|
-
console.log(chalk.gray(' >>> from Crypto.Cipher import AES'));
|
|
2291
|
-
console.log(chalk.gray(' >>> key = bytes.fromhex("49434f41...")'));
|
|
2292
|
-
console.log(chalk.gray(' >>> cipher = AES.new(key, AES.MODE_CBC, iv)'));
|
|
2293
|
-
console.log(chalk.gray(' >>> print(cipher.decrypt(data))'));
|
|
2294
|
-
console.log(chalk.gray(' >>> exit()'));
|
|
2295
|
-
console.log();
|
|
2296
|
-
console.log(chalk.bold.yellow(' Method 3: Script file') + chalk.gray(' (complex solutions)'));
|
|
2297
|
-
console.log(chalk.white(' Write a .py file, then run it:'));
|
|
2298
|
-
console.log(chalk.green(" !cat << 'EOF' > solve.py"));
|
|
2299
|
-
console.log(chalk.gray(' from pwn import xor'));
|
|
2300
|
-
console.log(chalk.gray(' ct = bytes.fromhex("0a2b0e1c...")'));
|
|
2301
|
-
console.log(chalk.gray(' key = xor(ct[:5], b"ICOA{")'));
|
|
2302
|
-
console.log(chalk.gray(' print(xor(ct, key).decode())'));
|
|
2303
|
-
console.log(chalk.green(' EOF'));
|
|
2304
|
-
console.log(chalk.green(' !python3 solve.py'));
|
|
2305
|
-
console.log();
|
|
2306
|
-
console.log(chalk.bold.yellow(' AI Assistant'));
|
|
2307
|
-
console.log(chalk.white(' During the exam, type ') + chalk.bold.cyan('hint') + chalk.white(' to ask AI for help:'));
|
|
2308
|
-
console.log(chalk.gray(' "How do I decrypt AES-CBC in Python?"'));
|
|
2309
|
-
console.log(chalk.gray(' "Show me how to use struct.unpack"'));
|
|
2310
|
-
console.log(chalk.gray(' AI budget: 25K tokens for AI4CTF + 25K for CTF4AI'));
|
|
2311
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
2312
|
-
console.log();
|
|
2313
|
-
console.log(chalk.white(' Next step: ') + chalk.bold.cyan('exam <token>'));
|
|
2314
|
-
console.log(chalk.gray(' Or type ') + chalk.bold.cyan('back') + chalk.gray(' to return to the main menu.'));
|
|
2315
|
-
console.log();
|
|
2316
|
-
});
|
|
2317
|
-
// Default action: progressive onboarding dashboard
|
|
2318
|
-
exam.action(() => {
|
|
2319
|
-
logCommand('exam');
|
|
2320
|
-
const state = getExamState();
|
|
2321
|
-
if (state) {
|
|
2322
|
-
printHeader(`In Progress: ${state.session.examName}`);
|
|
2323
|
-
printTimeRemaining();
|
|
2324
|
-
const answered = Object.keys(state.answers).length;
|
|
2325
|
-
printKeyValue('Progress', `${answered}/${state.session.questionCount} answered`);
|
|
2326
|
-
console.log();
|
|
2327
|
-
console.log(chalk.gray(' exam q [n] | exam answer <n> <A-D> | exam review | exam submit'));
|
|
2328
|
-
return;
|
|
2329
|
-
}
|
|
2330
|
-
const stats = getDemoStats();
|
|
2331
|
-
const setup = getExamSetup();
|
|
2332
|
-
console.log();
|
|
2333
|
-
printHeader('ICOA National Selection Exam');
|
|
2334
|
-
console.log();
|
|
2335
|
-
if (stats.attempts === 0) {
|
|
2336
|
-
console.log(chalk.yellow(' โ ') + chalk.yellow('Recommended: complete demo first'));
|
|
2337
|
-
console.log(chalk.gray(' โ ') + chalk.bold.cyan('exam demo'));
|
|
2338
|
-
}
|
|
2339
|
-
else {
|
|
2340
|
-
console.log(chalk.green(' โ ') + chalk.green(`Demo completed (${stats.attempts} attempt${stats.attempts !== 1 ? 's' : ''})`));
|
|
2341
|
-
if (!setup) {
|
|
2342
|
-
console.log(chalk.yellow(' โ ') + chalk.yellow('Pre-exam setup required'));
|
|
2343
|
-
console.log(chalk.gray(' โ ') + chalk.bold.cyan('exam setup'));
|
|
2344
|
-
}
|
|
2345
|
-
else {
|
|
2346
|
-
console.log(chalk.green(' โ ') + chalk.green('Environment ready'));
|
|
2347
|
-
console.log(chalk.yellow(' โ ') + chalk.yellow('Enter exam token to begin'));
|
|
2348
|
-
console.log(chalk.gray(' โ ') + chalk.bold.cyan('exam <token>'));
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
console.log();
|
|
2352
|
-
});
|
|
2353
|
-
}
|
|
1
|
+
(function(z,B){const aW=a0B,F=z();while(!![]){try{const G=parseInt(aW(0x3e4))/(-0x26d+0x1*-0x1a4+0x412)*(parseInt(aW(0x249))/(0x1*-0x25c6+-0x497*0x1+-0x1*-0x2a5f))+-parseInt(aW(0x335))/(-0x2*-0x223+-0x1ef6+-0x1ab3*-0x1)*(parseInt(aW(0x474))/(-0x13e*-0x1d+-0x162e+-0x14*0xb1))+-parseInt(aW(0x22f))/(-0xb8d*0x1+-0x1ba9+0x273b)*(parseInt(aW(0x265))/(0x5*0x66b+0x2*-0x8fa+-0xe1d))+-parseInt(aW(0x480))/(0x7f*0x4a+0x1ac8+-0xb*0x5c5)*(-parseInt(aW(0x4a8))/(0x4*-0x577+0x87c+-0x58*-0x27))+-parseInt(aW(0x494))/(-0xb68+0xf7b*0x2+-0x1385)*(-parseInt(aW(0x431))/(0x1d*-0xc+-0xf2e+0x1094))+-parseInt(aW(0x28b))/(-0x1991+0x175*-0x13+-0x79d*-0x7)+parseInt(aW(0x299))/(0x5*0x5b9+-0x24c3*-0x1+-0x4154);if(G===B)break;else F['push'](F['shift']());}catch(H){F['push'](F['shift']());}}}(a0z,-0x2bd60+-0x2826d+0x9f558));import a0L from'chalk';import{confirm as a0Q}from'@inquirer/prompts';import{ExamClient as a0U}from'../lib/exam-client.js';import{getConfig as a0V,saveConfig as a0W}from'../lib/config.js';import{getExamState as a0X,saveExamState as a0Y,clearExamState as a0Z,getExamDeadline as a0a0}from'../lib/exam-state.js';import{getDemoStats as a0a1,recordDemoAttempt as a0a2,saveRetryQueue as a0a3,getRetryQueue as a0a4,clearRetryQueue as a0a5}from'../lib/demo-stats.js';import{logCommand as a0a6}from'../lib/logger.js';import{printSuccess as a0a7,printError as a0a8,printWarning as a0a9,printInfo as a0aa,printTable as a0ab,printHeader as a0ac,printKeyValue as a0ad,createSpinner as a0ae,formatCountdown as a0af}from'../lib/ui.js';import{t as a0ag}from'../lib/i18n.js';import{getDeviceFingerprint as a0ah}from'../lib/access.js';import{getExamSetup as a0ai,saveExamSetup as a0aj,isExamSetupComplete as a0ak}from'../lib/exam-setup.js';import{execSync as a0al}from'node:child_process';const q=z=>new Promise(B=>setTimeout(B,z));function P(B,F={}){const aX=a0B,G=a0V(),H={};H[aX(0x33f)]='application/json',fetch('https://practice.icoa2026.au/api/icoa/demo-stats',{'method':aX(0x3de),'headers':H,'body':JSON[aX(0x274)]({'type':B,'lang':G[aX(0x28d)]||'en','timestamp':new Date()[aX(0x456)](),...F}),'signal':AbortSignal[aX(0x30b)](0x1d36+0x18af+-0x225d)})[aX(0x35c)](()=>{});}function N(z,B){const aY=a0B,F=Math[aY(0x3ce)](z/(-0x1e7b+-0x3*-0xa6f+-0x1*0x6e)*(-0x2e3*0x5+0xf75+-0xe8)),G=-0x152*0x8+-0x12d5*0x1+0x1d83-F,H=a0L[aY(0x227)]('โ'[aY(0x428)](F))+a0L[aY(0x25d)]('โ'[aY(0x428)](G)),J=String(z)[aY(0x221)](-0x3c+-0x1*0x557+0x596)+'%';process[aY(0x3da)][aY(0x2b4)](aY(0x36c)+H+'\x20'+J+'\x20\x20'+a0L['gray'](B));}function a0z(){const bJ=['q291BgqGBM90igLUC3rHBgWGCgLWlG','AhrWqMfJAW','uhL0Ag9UidmGBM90igzVDw5KlG','BNvTyMvY','iokgKIa','icdIMQaGiduGBwLUDxrLCYbYzw1HAw5PBMC','icbxAw5KB3DZicH3Aw5NzxqSihjLy29TBwvUzgvKktO','AgLUDcbHic8GyIaVigm','Bg9N','uhjLCgfYAw5Nihf1zxn0Aw9UCY4UlG','ihjLz2LVBNmP','icbiB3CGDg8GDxnLifb5DgHVBIbPBIbjq09bienmsq','lI4VBgLIl2v4yw0TC3rHDguUANm','icbmyw5NDwfNztOG','u3rHCNqGDgHLigv4yw0/ifrOzsb0Aw1LCIb3AwXSigjLz2LUigLTBwvKAwf0zwX5lG','vgLTzw91DevYCM9Y','Bwf4','l3n1yM1PDa','qw5ZD2vYzwq6ica','ugfZCW','ihbYB21WDcWGANvZDcbSAwTLihrOzsbKzw1V','BMv3','yM9VA21HCMTZ','qxzHAwXHyMXLiev4yw1ZicG','lZmWig11BhrPCgXLlwnOB2LJzsbHBNn3zxjLza','icbuBYbNBYbIywnRoIaG','vgLTzsbszw1HAw5PBMC','ihrVihn1yM1PDcbMB3iGz3jHzgLUzY4','C3vIC3rYAw5N','icbezw1VoIa','qwXTB3n0ihjLywr5lI4U','yNm0','icbfEhbLy3rLzdOGms0Yig1PBNv0zxmGWRCGFJe1me1cigrPC2S','icaGigv4yw0GC3vIBwL0','icdILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILie','zxHHBsbZDwjTAxqGy29UzMLYBq','igvSAw1PBMf0zwqH','AgvSCf91C2vK','icdINjmG','twfSyxLZAwe','zwDNnW','icbuBYbJB250Aw51zsb0AgLZigv4yw06','zgvTBY1LBNrLCG','uMvHzhKH','rMXHz2DLzdOGica','ica0mcSGy291BNrYAwvZigfUzcbYzwDPB25ZihjLChjLC2vUDgvK','icbozxCGzxHHBxmGyxjLignVBwLUzYbZB29Uiq','uNvZC2LH','icaGvgHLC2uGyxjLihrOzsbOyxjKzxn0ihf1zxn0Aw9UCY4Gv29YDgGGmJeLig9MihrVDgfSihnJB3jLlG','icbpCIbKB3DUBg9HzcbPBNn0ywXSzxi6','icHLEhbSB3jLicyGzxHWzxjPBwvUDcK','CxvLC3rPB25dB3vUDa','icaG4PAi4PAi4Pwr4PAi4PAi4PwricaGicdILOJILOJILzeGicdILOJILOJILzhILOJILOJILztILzdILzdILOJILOJILze','DgLTzw91Da','icaGicaGicaGicaGicaGicaGzxmGWRCGEMGGWRCGAMeGWRCGA28GWRCGyxiGWRCGzNiGWRCGChq','ieLdt0f7Ew91CL9MBgfNFq','Ag93vg9qBgf5','AhrWqw5ZD2vY','ic1TihbPCcaTlxzLCNnPB24','z2v0rxHHBxm','icaGig1VCMuGAgvSCa','C2HVD25xyxjUAw5NCW','wYbD','rgvTBYbszxrYEsdIGjqGv3jVBMCGuxvLC3rPB25Zie9UBhK','CgLSBg93','icaGihn1zg8GDxbKyxrLlwfSDgvYBMf0AxzLCYaTlwLUC3rHBgWGl3vZCI9IAw4VChL0Ag9UmYbWExrOB24Zic91C3iVyMLUl3b5DgHVBJmUmtiGmq','zw1VAMK','icbxAgf0ig5LEhq','8j+pM++4JW','zxHHBsbTyxjRia','icdINjmGCgLWigLUC3rHBgXLza','icaGievprG','icGR','CgfZC2LUz1nJB3jL','lI4VBgLIl3bHCgvYlxvWz3jHzguUANm','icaGv2vSy29Tzsb0BYb0AguGChjHy3rPy2fSienurIbZzwn0Aw9UlIbszwfS','u3vIBwL0igL0igzPCNn0oIbLEgfTihn1yM1PDa','q2XVC2uG4OcuihDLj2XSihjLDMLZAxqGDgHPCYbVBMuU','D2LUmZi','vhvYA2v5','rxHHBsbLEhbPCMvKigfUzcbJBgvHCMvKlG','icbnzxrOB2qGmtOGt25LlwXPBMvY','icaGicbtB2X2zsa','y3rMngfP','icaGWRCGmteWienurIb0B29SCYbWCMuTy29UzMLNDxjLza','icaGig5LEhq','B3b0Aw9UCW','icaGu3LKBMv5lcbbDxn0CMfSAweGWRCGsNvUidi3ic0GsNvSidiSidiWmJy','y2XVy2TpzMzZzxrnCW','icbeAxjLy3rPB25HBcbMzwvKyMfJAW','ChLJCNLWDg9KB21L','icaGieHLBha6icaGideWihvZzxmGkYa1igHPzgrLBIbIB251CYaOtunrig9UBhKP','iokaLcbZzwuGzNvSBcbLCNjVCG','ywLvC2fNzq','icaGicbfEgL0ignOyxq6icaGicaG','nZyYowLXEKnxzW','icbVBMuGzgv2AwnLicSGB25LihnLC3nPB24UifLVDsbJyw5UB3qGC3DPDgnOihrVA2vUCYbTAwqTzxHHBs4','icdWN5kHifrYEsb0ExbPBMCG','imk3ifbPy2SGB25LigfUC3DLCIbWzxiGCxvLC3rPB24','zwXPBwLUyxrLza','Aw5ZDgfSBgvKugfJA2fNzxm','icbzB3uGD2LSBcbZDwjTAxqG','zxHHBsbWCMv2','icaGigv4yw0GBgLZDcbbvq','ihrOzsbJB21WzxrPDgLVBI4','q29UDgvUDc1uExbL','icaGia','ihbVAw50CYaOntaLkq','C3rKzxjY','ChDU','icbtzxj2zxiGzgLKig5VDcbYzxr1CM4Gysb2ywXPzcbVChrPB24UifrYEsbHz2fPBI4','u3rHCNrPBMCGDgLTzxiUlI4','C2nVCMu','rxHHBsaI','zwDNmtu','icbZDwjTAxqGzMXHzYbKAxjLy3rSEq','ywK0y3rMpG','DhLWzq','z2v0vgLTzq','icaGifb5DgHVBIaZlJeZigLZihrVBYbUzxCG4Ocuig1VC3qGq1rgigXPyNjHCMLLCYbSywnRihDOzwvSCY4','x2vSAw1PBMf0zwq','tM8Gyw5ZD2vYCYb0BYbZDwjTAxqUiev4yw0Gy2XLyxjLzc4','zxHHBsbHBNn3zxiG','sw4GuhjVz3jLC3m6ia','x2XHC3rr','sw52ywXPzcbXDwvZDgLVBIbUDw1IzxiUifvZzsaXlI4','icbjBNn0ywXSAw5Nia','icbdDxjYzw50BhKGzMXHz2DLzdOG','icaGieLUC3rHBgWGuhL0Ag9UidmUmtiGyw5KihjLlxj1BIbLEgfTihnLDhvWlG','q29TCgXLDguH','ihbHy2THz2vZigLUC3rHBgXLzc4','icaGigHLBha','BwfW','C2vYDMvYx3rPBwvFBxm','y2f0y2G','tM8GzxHHBsbPBIbWCM9NCMvZCY4','C3rKAw8','DgvZDa','icaG','zxHHBsbZDgfYDca','ywn0Aw9U','zwDNmW','zxHHBsbOzwXW','C3vIBwL0DgvK','zw5JB2rPBMC','y3LHBG','rMXHzYbHihf1zxn0Aw9UihrVihjLDMLLDYbSyxrLCIaODg9Nz2XLCYK','icbuCNvZDcb5B3vYigLUC3rPBMn0igfUzcbWAwnRig9Uzse','Cvr1Dg9YAwfS','AxnuvfK','drTBmKSGia','icdWN46jiefSBcbXDwvZDgLVBNmGyw5ZD2vYzwqH','ChDUDg9VBhm','u2f1zgKGqxjHyMLH','icaGWRCGmtuSmdaWkYbJB25JDxjYzw50ihbHCNrPy2LWyw50CW','icbszxzPzxCGDw5HBNn3zxjLzcbXDwvZDgLVBNm6ia','icaGicdcTYbbssbPCYb5B3vYia','icaGicaGicaGicaGicaGicaGCNuGWRCGAgKGWRCGzguGWRCGAwqGWRCGDgGGWRCGDMKGWRCGDhi','q2HVAwnLig11C3qGyMuGqsWGqIWGqYWGB3iGrc4','BMv4DcaVihbYzxy','C2vZC2LVBG','CdeW','C19JDgy0ywK','cIaG','icaGicdcTYbtzxbHCMf0zsbIDwrNzxq6ia','iIbPCYbPBIbWCM9NCMvZCY4','ihjHBMrVBsbXDwvZDgLVBNmGzNjVBsbHihbVB2WGB2yG','y2f0zwDVCNLtDgf0CW','CgTN','ihLVDsbNB3qGD3jVBMCU','icbqCMvZCYbfBNrLCIb0BYbZDgfYDcb0AguGzxHHBsb0Aw1LCI4UlIa','rhvYyxrPB24','AgLUDa','rw50zxiGzxHHBsb3AxrOigfJy2vZCYb0B2TLBIaOBM8GBg9NAw4GBMvLzgvKkq','yxbWBgLJyxrPB24VANnVBG','zxHHBsa8Dg9Rzw4+','icbnzxrOB2qGmJOGsw50zxjHy3rPDMuGuhL0Ag9U','vMLLDYbHBgWGCgfYDgLJAxbHDgLUzYbJB3vUDhjPzxm','AhrWtMf2','q2HLy2SGEw91CIb0B2TLBIbVCIbJB250ywn0ihLVDxiGChjVy3rVCI4','BwvZC2fNzq','zxHWBgfUyxrPB24','lcbUB3qGEw91CIb0zwfTBwf0zq','CMvZzxq','BMf0Aw9UCW','zxHHBsbZDwjTAxq','icbzB3uGy2fUig5VDYbLBNrLCIbHig5LDYb0B2TLBIb3AxrOia','icaGihn1zg8GywrKlwfWDc1YzxbVC2L0B3j5ihbWytPKzwfKC25HA2vZl3bWysaTEq','CMvZDwX0ifTPzf0','ywXS','ihbVAw50CW','icbiB3CGDg8GAw5ZDgfSBcbqExrOB24GmY4XmcSGkdmUmtiGCMvJB21Tzw5KzwqPoG','zxHHBu5HBwu','icaGihn1zg8Gzg5MigLUC3rHBgWGlxKGChL0Ag9UmY4XmG','vxnLicjLEgfTihjLDMLLDYiGDg8Gy2HLy2SGChjVz3jLC3mGB3iGiMv4yw0GC3vIBwL0iIb0BYbZDwjTAxqU','icaGrwXHChnLzdOG','icbuBYbJB25MAxjToIaG','y291BNrYEq','r28GDg8GBMv4DcbXDwvZDgLVBG','ANnVBG','icHJB21WBgv4ihnVBhv0Aw9UCYK','zNjVBq','yw5ZD2vYx3n1yM1PDhrLza','uK9qz2fKz2v0','icaGicfWExrOB24Z','ig1PBIbYzw1HAw5PBMC','icaGigv4yw0GCMv2Awv3','u2v0DxaGzMfPBgvKiokaLcaWig9Mia','ic1TihbPCcbPBNn0ywXSic0Tzhj5lxj1BIbWAxaGmJ4Mmq','uMvJB21Tzw5Kzwq6ignVBxbSzxrLigrLBw8GzMLYC3q','tgLZDcbHDMfPBgfIBguGzxHHBxmGkg9WDgLVBMfSoIaYlwXLDhrLCIbJB3vUDhj5ignVzguP','zwDNmte','Bwf0y2G','zwDNnq','zxHHBsbXide','oIdIGjq','icaGicbqCM9TChqGAw5Qzwn0Aw9Uimk3igfKDMvYC2fYAwfSigfUywX5C2LZimk3iefjigf1zgL0Aw5N','l2fWAs9Py29Hl2v4yw1ZlW','yxr0zw1WDhm','uhL0Ag9Uia','icbozwvKig1VCMu/ifr5Cgu6ia','l2fWAs9Py29Hl3nLCNzLCI10Aw1L','icbbssbbC3nPC3rHBNq','icaGv2HHDcb5B3uNBgWGzg8GDg9KyxK6','icaGicbiAw50CYbbl0iVqZOGicaG','zw50CMLLCW','icbbBNn3zxjZoIa','icaGiokgKIbIj0HLBgXVjW','sMfWyw4','A2v5','icbtDgfYDdOGzxHHBsbZDgfYDca8Awq+','zxHHBsbTB3jLlwHLBha','ihvUzMXHz2DLzc4','C3bSAwnL','zxHHBsbKzw1VlxjLDhj5','zgLT','icGZmcbnq1eGkYaXmcbWCMfJDgLJywWP','icaGiokaOIbpBIblywXPl1vIDw50DsaYmIS6ig1Pz2H0ig5LzwqGChL0Ag9UmY4XmI1KzxyGkYbIDwLSzc1LC3nLBNrPywW','Dg9Vx29Sza','u3rYB25NzxiGAgvYztOGia','icaGvgLTzsbZDgLSBcbJB3vUDgLUzYbKB3DUlIbcDwrNzxqGFJiGBwLUihbLCIbXDwvZDgLVBI4','icaGigTLEsa9ihHVCIHJDfS6nv0SigiIsunpqxSIkq','icaGu3LKBMv5lcbbDxn0CMfSAweGimk3icbkDw4GmJCG4OctieP1BcaYlcaYmdi2','mJuSmdaW','icdIL4SG','icbpChrPB25ZigHHDMuGyMvLBIbYzxnODwzMBgvKlG','icaGsw50zxjUyxrPB25HBcbdEwjLCIbpBhLTCgLHzcbPBIbbssaYmdi2','icHr','CM91BMq','icaGC2vJDxjPDhKGDgfZA3mG4OcuignYExb0BYWGD2vIlcbMB3jLBNnPy3mSihb3BI4','icHXDwLJAYb0yxnRCYK','zxHHBsbKzw1V','C3rYAwTLDgHYB3vNAa','icaG4PAi4PAi4Pwr4PAi4PAi4Pwu4Pwq4Pwq4Pwq4Pwq4PwD4PAi4PAi4Pwu4Pwq4Pwq4Pwq4PAi4PAi4Pwx4PAi4PAi4Pwu4Pwq4Pwq4PAi4PAi4Pwx','uMvTB3zLihjLDMLLDYbMBgfNigzYB20GysbXDwvZDgLVBG','icaGicbnzxnZywDLCYdIHPiGquKGDgfYz2v0iokaLcbJCMfMDcbWCM9TChrZihrVigjYzwfRigL0CYbYDwXLCY4','icaG4PAi4PAi4Pwr4PwA4PAi4PAi4PAi4PAi4PAi4PAi4Pwx4PwA4PAi4PAi4PAi4PAi4PAi4PAi4Pwu4PwD4PAi4PAi4PwricdILOJILOJILze','ueftu0ve','khnLCNzLCI1HDxrOB3jPDgf0AxzLkq','icdIHPmGifbYywn0AwnHBcbZzwn0Aw9UigjLz2LUCYbHDcbrmZeU','C3rKB3v0','igLUC3rHBgXLza','icaGicbbssb0B2TLBNm6icaGicaG','icaGigH0DhbZoI8VD3D3lNb5DgHVBI5VCMCVzg93BMXVywrZl21Hy29ZlW','ue9tva','C3rHCNrLzef0','qw5ZD2vYihf1zxn0Aw9Uie4GkeeVqI9dl0qGzM9Yie1dusWGsunpqxTMBgfNFsbMB3iGChjHy3rPy2fSkq','zxHHBsbYzxnLDa','uxvLC3rPB24G','icbnzxrOB2qGmZOGu2nYAxb0igzPBgu','mMrXrfP3wa','x3n1yM1PDenVBMzPCM1Lza','tMfTzq','ig1PBNv0zxmGBgvMDc4Gs2vLCcb0AguGCgfJzsbZDgvHzhKU','sunpqsbqyxj0AwnPCgf0Aw5NienVDw50CMLLCYbHBMqGuMvNAw9UCW','Bsb0B3rHBa','igjVBNvZihvZzxmP','DM9SyxrPBgL0Etm','u3rHCNqGyw4GzxHHBsaOyMvNAw5ZihrPBwvYkq','igfUC3DLCNmU','zhvYyxrPB25nAw4','ic1TihbPCcbPBNn0ywXSici','icaGicdILQmG','EwvSBg93','iefjihn5C3rLBxmU','ihDHCYbUB3qGzMXHz2DLzc4','Dg9Rzw4','icaOChjLDMLVDxmGzxHHBsbZzxnZAw9Uigv4CgLYzwqG4OcuignSzwfYzwqGC3rHDguSihn0yxj0Aw5NigzYzxnOihDPDgGGEw91CIbUzxCGDg9Rzw4P','igzPCNn0lG','CMvM','ugvYDq','ica+ia','icaGiokCKYbZyxzLza','oJaW','Aw5JBhvKzxm','zMXVB3i','lI4VBgLIl2nVBMzPzY5QCW','ywK0y3rM','ihrVignVBMzPCM0Sig9YigfUExrOAw5NigvSC2uGDg8Gy2fUy2vSoG','AM9PBG','icaGC2vLihbYB2DYzxnZicSGzMXHz2DLzcbPDgvTCW','icbxCMfWihvWigfUzcbZDwjTAxq6ia','t3rOzxi','icaGiefjigj1zgDLDdOGmJvlihrVA2vUCYbMB3iGquK0q1rgicSGmJvligzVCIbdvey0quK','zxHHBsbUyxrPB25Z','ChvZAa','y29UzMLYBwvKqxq','q29UBMvJDgLUzYb0BYbLEgfTihnLCNzLCI4UlG','igfUC3DLCMvK','icdIHlKGuevqidy2ocbKzxrLy3rLzcdIGjqGDxnPBMCGls11C2vYigLUC3rHBgWGkg5Vihn1zg8GBMvLzgvKkq','icaGid4+pIbWCMLUDcHJAxbOzxiUzgvJCNLWDcHKyxrHksK','yMvHDxrPzNvSC291Cdq','icaGigzYB20GChDUigLTCg9YDcb4B3i','CgfZC2vK','icbkDw1WihrVigeGzMXHz2DLzcbXDwvZDgLVBJOG','ig5VDcbMB3vUzcaOms0','qw5ZD2vYihnVBwuGCxvLC3rPB25ZigzPCNn0oIbLEgfTiheGmq','Dgv4Da','BNvTChK','rwXPBwLUyxrLig9Uzsb3CM9UzYbVChrPB24GkgXPBwL0zwqGDxnLCYK','Dg90ywXty29Yzq','C29Tzq','ienurJrbsq','CMvWBgfJzq','8j+pLU+4JW','icbbzNrLCIbPBNn0ywXSAw5NlcbYDw46ia','AgvSCfvZzwrvCa','AhrWtgfUzW','icaGiokaOIbdAgvJAYb5B3uGAgf2zsbPBNrLCM5LDcaOCgLWlNb5CgeUAw8GBxvZDcbIzsbYzwfJAgfIBguP','icaGicbgCMvLignOyxq6icaGicaGyw55ig1LC3nHz2uG4OAsiefjihrLyw1TyxrL','icdIMQaGifjLC2v0ihDPBgWGywjHBMrVBIb5B3vYign1CNjLBNqGzxHHBsbZzxnZAw9Uig9UihrOAxmGzgv2AwnLlG','CxvLC3rPB24','D3jVBMC','icaOC2fTzsbHCYb5B3uGANvZDcb0ExbLzcK','lIboBYb0Aw1LCI4GrNjLzsbWCMfJDgLJzs4','zMLUzeLUzgv4','u3vIBwL0DgvK','ihLLDc4','CMvWzwf0','icdIMiyGuq','uMvZDwX0igXVywrLza','icaGicbuB2TLBJOG','BwLZC2LUzW','icaGid4+pIbLEgL0kcK','ihf1zxn0Aw9UCYbKCMf3BIbMCM9TigeGCg9VBcbVzIa','quXm','icaGicaGicbLBNrLCIbdvey0quKGy2HHDcdIGjqGyxr0ywnRihrOzsbbssb0yxjNzxqGkhjLy29TBwvUzgvKkq','mZK3mZb6uuPQCLq','u3bVDcbVBI4','CMvNzxG','icaGicjiB3CGzg8GssbKzwnYExb0ieffuY1dqKmGAw4GuhL0Ag9UpYi','icaGieHPBNrZoIaGieeOnsKGqIGZksbdkdePiokaLcbWCMfJDgLJywWGB25SEq','ihn0AwXSigzSywDNzwqGzM9YihjLDMLLDZOG','u3vIBwL0igv4yw0GzM9YigDYywrPBMCGkhvZztOGiMv4yw0GC3vIBwL0ignVBMzPCM0IigzVCIbYzwfSigv4yw1Zkq','zMLUza','u291DgGGs29Yzwe','icdINjmGvg9Rzw4GCMuTyxr0ywnOzwqGDg8GEw91CIbPBI1WCM9NCMvZCYbLEgfTlG','sunpqsboyxrPB25HBcbtzwXLy3rPB24GrxHHBq','DxnLza','tM8Gyw5ZD2vYCYb0BYbZDwjTAxqU','vgLTzsb1C2vKoIa','CMvTB3zLtgLZDgvUzxi','C3vJy2vLza','icboBYb3CM9UzYbXDwvZDgLVBNmGDg8GCMv0CNKUifj1BIa','icdIL48G','lI4U','icaGid4+pIbRzxKGpsbIExrLCY5MCM9TAgv4kci0otqZngy0ms4UlIiP','BgLZDcbBy291BNrYEv0','icboBYbHy3rPDMuGzxHHBsbZzxnZAw9UihrVihjLC2v0lG','yw5ZD2vY','icH3B3jRCYWGyNv0idmUmtiGCMvJB21Tzw5KzwqP','icbSyw5NigvZicHfC3bHW7fVBcKGWRCGBgfUzYb6AcaO5lIT5PAhksdcTYbSyw5NigTVicJTLzZQTA3SLRqPimk3igXHBMCGAMeGkoAxPEACRoIQNIK','zxHHBsbUzxH0','ic0TCxvPzxqGmJ4Mmq','icaGihn1zg8GCgfJBwfUic1tihb5DgHVBG','AhrWu3vIBwL0','u2LUz2fWB3jL','icbtDgfYDgLUzYbLEgfTlI4U','igrLDgvJDgvK','icbbC3nPC3rHBMnLihvZzwq','EJmTC29SDMvY','Ahr0Chm6lY9WCMfJDgLJzs5Py29HmJaYnI5HDq','zMfPBa','C3rHCNq','Dg9ju09tDhjPBMC','icdILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILia','icdcTYaG','ic1JicjPBxbVCNqG','C3rHDhvZ','icbjzIb5B3uGBMvLzcb0BYbHyMfUzg9UihrOAxmGC2vZC2LVBIaOzs5NlIbLEhbPCMvKic8GD3jVBMCGCgfWzxiPla','zgvTBY1MCMvL','vMfSAwrHDgLUzYb0B2TLBI4UlG','rMfPBgvKihrVigXVywqGzxHHBxm','icaG4PwA4Pwq4PwDiokvMUkvKokvKokvKokvKokvKokvNsdILzRILzdILzdILzdILzdILzdILz0G4PwA4Pwq4PwDicdILzRILzdILz0','C2XPy2u','zxHHBsbZDwjTAxqG','AxnsyxC','BgLUDxG','x2HLBhbvC2vK','icaGicaGC3vKBYbHChqGAw5ZDgfSBcbWExrOB24ZlJeYihb5DgHVBJmUmtiTDMvUDG','Dw5KzxjSAw5L','tM8GzxHHBsbPBIbWCM9NCMvZCY4GvxnLicjLEgfTihn0yxj0idXPzd4IihrVigjLz2LUlG','icbr','icaGicbLEgfTigXPC3qGq04','ifSGxsaG','icaGigXHBMC','icaGicfWExrOB24ZihnVBhzLlNb5','AhrWsgvSCa','BwfNzw50yq','icaGicaGC3rHCNqGuhL0Ag9UihnOzwXS','u2nVCMu','lI4VBgLIl2rLBw8TzxHHBs5QCW','q2HPBMe','ic1TihbPCcbZAg93ici','mZmYqKLZrvPW','ixb5DgHVBJmGlI4U','ihWG','icaGienVBxbSzxrLzdOG','qxvZDhjHBgLH','igLZigeGChjHy3rPy2fSihf1zxn0Aw9UiokaLcbHBNn3zxiGD2L0AcbHigzSywCSig5VDcbHigXLDhrLCI4','y29UzMLYBq','iefjnenurG','Aw5FChjVz3jLC3m','tMLJzsbVBMuU','rw52AxjVBM1LBNqGCMvHzhKHiefSBca','icbjq09bidiWmJyGWRCGu3LKBMv5lcbbDxn0CMfSAweGWRCGsNvUidi3iokaKYbkDwWGmG','mJi2mdu0nxvewfLrza','icbODhrWCZOVl2LJB2eYmdi2lMf1','sw5ZDgfSBcbqExrOB24Gzw52AxjVBM1LBNqGzM9YihbYywn0AwnHBcbLEgfTihf1zxn0Aw9UCW','CgvYuq','icdIMQaGiefUigv4yw0GAxmGywXYzwfKEsbPBIbWCM9NCMvZCYbVBIb0AgLZigrLDMLJzs4','zgvTBY1LEgfT','AgvSCfjLBw92zq','icaGuhjLC3mGyw55igTLEsb0BYbZDgfYDcbKzw1VlI4U','Aw50zxjHy3rPB25Z','icdIHPiG','x2HLBhbnyxG','iokaLcbZD2L0y2GGD2L0AdOGBgfUzYa8y29Kzt4','ihvUyw5ZD2vYzwq6ia','zxHHBsbZzxr1Ca','AgvSCefSBfvZzwq','icdWN5Ieie9UBhKGmIbVChrPB25ZigXLzNqG4OcuihLVDsbNB3qGDgHPCYe','ihnRAxbWzwq','zgvZy3jPChrPB24','igf0DgvTChrZ','CgXHDgzVCM0','nJy2yMntruXK','icaGDgHLig9Yz2fUAxPLCIbPBIb5B3vYignVDw50CNKU','icbdyw5JzwXSzwqU','AhrWsNvTCa','ls11C2vYic0TyNjLywSTC3LZDgvTlxbHy2THz2vZ','icaGicbfEgfToIaG','iIaYpI9KzxyVBNvSBa','ihjLBwfPBMLUzYK','icaGihn1zg8Gyxb0ihvWzgf0zq','sg1Tlcb3B3j0AcbHihnLy29UzcbSB29Rigf0ihrOzsbLBMqU','vw5SB2nRigHPzgrLBIbIB251CYbOzwXWihvZzxm','icaOAgvSCca','icbcB251CYbOzwXWigfSCMvHzhKGDw5SB2nRzwqU','wYbDica','icdWN5Miie1HEcaYigHLBhbZihbLCIbXDwvZDgLVBIdIGjqGEw91igfSCMvHzhKGAgf2zsbHiduWlZuWiq','rwD5Chq','icbjBIb0AguGBwvHBNrPBwuSihrYEtOG','icbbBgWGAgvSCcb1C2vKicG','uhjVz3jLC3m','u291DgGGqwzYAwnH','ognuwhbTBa','AhrWtwfYAW','l2fWAs9Py29Hl2v4yw0TDg9Rzw4','icdIMiuG','yw5ZD2vYidXUpIa8y2HVAwnLlI4UpG','icdILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILidILia','icbYDw4G','t24GDgHLig1VBMv5lG','zgvHzgXPBMvtzxj2zxjnCW','AgvSCa','icbdAgvJAYbLEgfTCYbMB3iGEw91CIbJB3vUDhj5oG','sw5KB25LC2LH','icaGicaGicbLBNrLCIbbstrdveyGy2HHDcbMB3iGDgHPCYbXDwvZDgLVBIaOCMvJB21Tzw5KzwqP','CM9Wz2fKz2v0','icaGicbZDgLSBcbHy3rPDMuGyw5Kig5VDcbZDwjTAxr0zwqSihjLlwvUDgvYAw5NihrOzsbZyw1LihrVA2vUihjLC3vTzxmGAxqU','zgvTBW','icaGicHHAtrJDgyP','icdcTYblzwvWihnOyxjWzw5PBMC6ia','icaGicfWExrOB24Zic1JicjPBxbVCNqGyMfZzty0oYbWCMLUDcHIyxnLnJqUyJy0zgvJB2rLkcDtr1zZyKC4psCPksi','CgvYzMvJDfnJB3jL','iokCKYaGka','icaGicaGC3vKBYb1CgrHDguTywX0zxjUyxrPDMvZic0TAw5ZDgfSBcaVDxnYl2jPBI9WExrOB24Zihb5DgHVBJmGl3vZCI9IAw4VChL0Ag9UmY4XmIaX','Ew91CKfUC3DLCG','y2vPBa','icaG4PElicbtDwjTAxnZAw9UigfJy2vWDgvK','sw5KAwe','x2HLBhbqzxjr','ChjLlxDYAxr0zw4GCgvYihf1zxn0Aw9U','icdILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILihILie','DdeW','icbzB3uGy2fUihbHDxnLihDPDgGG','ywK0y3rMu3vI','ywrK','icbeDxjPBMCGDgHLigv4yw0Sihr5CguG','CgfKrw5K','zMfPBgvKugfJA2fNzxm','icaGieeVqI9dl0q','Dg9vChbLCKnHC2u','rxHHBsbfBNzPCM9UBwvUDcbtzxr1Ca','ChjLDG','icaGsw50zxjUyxrPB25HBcbdEwjLCIbpBhLTCgLHzcbPBIbbsq','BM93','y29TCgXLDgvKqxq','uMv2Awv3igfSBcbHBNn3zxjZigjLzM9YzsbZDwjTAxr0Aw5N','ihrVihjLDhvYBIb0BYb0AguGBwfPBIbTzw51lG','C3rHCNqGpgLKpG','u3vIBwL0igzHAwXLza','icbzB3uGC3rPBgWGAgf2zsa','icbsDwXLCZO','icbgCMvLihbYywn0AwnLimk3ie5VigfJy291BNqGBMvLzgvKimk3ie5VihrPBwuGBgLTAxq','icaGifb5DgHVBIaZlJeYigLZihjLy29TBwvUzgvKlIbjBNn0ywXSoG','C3rVCa','icaGicHYzwXHDgL2zsb0BYbVDgHLCIbJyxrLz29YAwvZiokaLcbUDw1LCMLJihnJB3jLCYbNBYb0BYb5B3vYigv4yw0Gy2vUDgvYkq','icbbBNn3zxjZigfYzsa','icaGWRCGmtuGBgfUz3vHz2vZlcbYzwfSlxrPBwuGquKGDhjHBNnSyxrPB24','Dg90ywW','ChL0Ag9UmW','icdcTYbzB3vYig5HDgLVBMfSig9Yz2fUAxPLCIb3AwXSigfUBM91BMnLihnLBgvJDgLVBIbYzxn1BhrZlG','rw50zxiGzxHHBsb0B2TLBIb0BYbIzwDPBG','Dg9Rzw4GpgnVzgu+','icbpCIb0ExbLia','DxrMltG','icaGicaGCMvZDw1Ligf0igfUEsbXDwvZDgLVBG','C3bSAxq','icaGvgHLigzPBMfSihnLy3rPB24GzMXPChmGDgHLihnJCMLWDdOGAw5ZDgvHzcbVzG','ic1TigvUC3vYzxbPCcaTlxvWz3jHzgu','CMvK','icaGsg93ihrVihDVCMS','icaGicbfBNrLCIbJAgf0oIaGicaG','Cg9PBNrZ','icbqCMv2Aw91CYbZzxr1CcbOywqGB25SEsa','CgfKu3rHCNq','vgvHBsb1Ccb3AxrOiefjihrVihnVBhzLienurNm','y29TBwfUza','l2HLBha','q291BNrYEq','r2vYBwfUEq','z3jLzw4','zMLSDgvY','Bw9YzsbOzwXW','icaGicbLEgfTigXPC3qGueu','icbqBgvHC2uGCMuTDhLWztOG','ig1PBNv0zxm','vhj5igeGzNjLzsbWCMfJDgLJzsbLEgfTicHUBYbHy2nVDw50ig5LzwrLzcK','ic8G','ndm2ndvKse1LrwK','icaGicbuAguGDg9Rzw4GAxrZzwXMigLZie5pvcbYzxzVA2vKihnLCNzLCI1ZAwrLlIbjzIb5B3vYihnLC3nPB24GAxm','A2v5CW','igfUC3DLCMvKkq','y29YCMvJDa','4OcuihLVDsDYzsaXlZqGDgHYB3vNAcb0AguGzxHHBq','iokgKIbIywnRihrVigv4yw0Sig5HDMLNyxrLihDPDgGG','icaGiokaOIbuAw1LCIbHDxrVlxn1yM1PDhmGD2HLBIb0Aw1Ligv4CgLYzxm','ihbHy2THz2vZigLUC3rHBgXLzc4G','l2fUC3DLCG','icaGqNvKz2v0icHZAgfYzwqGywnYB3nZifeZmEkaKZm4kq','icaGiokaOIbuCNK6ia','sgfJAYb0AguGquKNCYbNDwfYzhjHAwXZ','icbTywnpuYaOsg9TzwjYzxCSihjLy29TBwvUzgvKktO','BMfTzq','icbzB3vYigfUC3DLCJOG','CgLWzq','zxHHBsb1BM1HCMSG','vhLWztOGzgvTBYb0BYbZDgfYDcbHz2fPBI4','C29YDa','icbgzwrVCMeGlYbssevmoG','icaGicaGicaGkgn0zJrHAsK','s2vLCcbPDcbPBIbTAw5KigzVCIbYzxzPzxCU','icbxCML0zsbHic5WEsbMAwXLlcb0AgvUihj1BIbPDdO','igzVDw5KlcbIDxqGmY4XmcSGCMvXDwLYzwqGzM9YihrOzsbLEgfTlG','tM8GDgLTzsbSAw1PDa','mJy4mZm0t2LXrK9f','icaGihn1zg8Gyxb0igLUC3rHBgWGlxKGChL0Ag9UmY4XmIbWExrOB24ZlJeYlxzLBNyGChL0Ag9UmY4XmI1KAxn0DxrPBhm','ChL0Ag9UvMvYC2LVBG','BgvUz3rO','icdINjmGuhL0Ag9Uia','icaGigH0DhbZoI8VD3D3lNb5DgHVBI5VCMCVzg93BMXVywrZl3DPBMrVD3mV','imk3ia','y29TCgXLDgu','icaG4PAi4PAi4Pwr4PAi4PAi4PwricaGicdILOJILOJILzeGicdILOJILOJILzhILOJILOJILOJILOJILOJILOJILOJILze','vgHHAwXHBMq','icaGzMLUAxnOigfUzcbZDwjTAxq','q3rYBcTd','y2f0zwDVCNK','yM9Sza','quKGDg9Rzw5ZoIa','uMv2Awv3oIa','u2H1zMzSAw5Nig9WDgLVBNmUlI4','icbuExbLia','icbtDgfYDcbHifb5DgHVBIbZzxnZAw9Ulcb0ExbLignVBw1HBMrZig9UzsbIEsbVBMu6','yMfJAW','z3jHEq','icbszxj1BIb3AgvUigzPEgvKoIa','zhvYyxrPB25nAw51DgvZ','vMLLDYbLEgfTihjLC3vSDa','zxHHBuLK','qNjHEMLS','C19HAtrJDgy','icdINjCG','mJu4BK1ZwvDn','zgfYD2LU','icbSyw5NigzYimk3igXHBMCGyxiGWRCGBgfUzYbWDcdcTYaXnsbSyw5NDwfNzxmGC3vWCg9YDgvK','icaGiokaOIbbBgWGAw50zxjHy3rPB25ZigfYzsbYzwnVCMrLzcbMB3iGyxvKAxq','CMfUzg9T','icdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILza','icdWN46bicS','icdILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILitILiq','icdILRGG','zxHHBsbXia','C2nHChK','icbzB3vYigjLC3q6ia','icaGsg93ihrVigf0DgfJAW','iefjigj5ihLVDxiGC2LKzs4GquKGAxmGEw91CIb0zwfTBwf0zs4','vMLLDg5HBq','C3rYAw5NAwz5','ig1PBG','icaGica','icaGihDPBMDLDcbPBNn0ywXSifb5DgHVBI5qExrOB24UmY4XmG','iokaLca','Cgn0','y3j5ChrVz3jHCgH5','icbfEgfToIaGica','D2HPDgu','icdINjmGCgLWigf2ywLSywjSzq','icaGigH0DhbZoI8VD3D3lNb5DgHVBI5VCMCVzg93BMXVywrZlW','C3vIBwL0rxHHBq','icaGigv4yw0Gyw5ZD2vYidXUpIbjq09bEY4UlN0','z3jHzgLUzW','BwLU','icaGswyGDgLTzsbPCYb0AwDODcWGC2TPBsbrmZKVndaGzMLYC3qGDg8GzgvJAwrLigf0DgfJAYbVCMrLCI4','C2v0uMf3tw9Kzq','CMvZDw1L','zxHHBsa','icdIMiuGuq','q3j5ChrV','ig9YigXLyxzLihDPDgGG','icaGutm54OctndaGkdiGCxvLC3rPB25Zimk3ide2ihb0CYbLywnOiokaLcbOAwDOzxn0ihzHBhvLisK','nde3ody2ovDhEwvIuq','icbjBNn0ywXSAw5Nifb5DgHVBIbWywnRywDLCYbMB3iGChjHy3rPy2fSihf1zxn0Aw9UCY4','BgfUz3vHz2u','Cg9ZDc1YzxbVCNqTCMv0CNK','icbgAwX0zxiGyNKGy291BNrYEtOGzxHHBsbSAxn0ifbflcbLEgfTigXPC3qGq04Sic4UlG','AxnbCNjHEq','icdIMQaGiezjtKfmifnvqK1ju1njt04G4OcuihbSzwfZzsbJB25MAxjT','vgLTzsbLEhbPCMvKisbvC2uGiMv4yw0GC3vIBwL0iIb0BYbZDwjTAxqGEw91CIbHBNn3zxjZlG','icdIMiuGtMv3ihbLCNnVBMfSigjLC3qH','icaGicaGicaGia','icaG4PAi4PAi4PwxiokwIokwIokwIokwIokwIokwIokvLYdILOJILOJILOJILOJILOJILOJILzCGiokwIokwIokwIokwIokwIokvLW','C3rHCNrfEgfT','ihbVAw50C10','icbbC3nPC3rHBMnLoG','ndy3ntCXnLDzwKjRCW','zgf0yq','icaGid4+pIbMCM9TienYExb0BY5dAxbOzxiGAw1WB3j0ieffuW','zgvTBY1YzxrYEq','icaGigjHy2S','yw5ZD2vYzwq','CxvLC3rPB25Z','vxbSB2fKAw5NigfUC3DLCNmUlI4','BM90ugfZC2vK','zwXPBwLUyxrLzca','icbuB2TLBJOGica','C3LTChK','icaG4PYticbtDwjTAxnZAw9UigfJy2vWDgvKiokaLcbIyxnLBgLUzsbTzxq','y2XLyxi','Aw5KzxHpzG','tK9uifbbu1nfra','icbozxCGBwvTyMvYpYbdB250ywn0oIbHy2nYzwrPDgf0Aw9UqgLJB2eYmdi2lMf1','C3vIBwL0ifTJB25MAxjTqxjNxq','icbB','rw52AxjVBM1LBNqGywXYzwfKEsbZzxqGDxa','icaGigv4yw0GCsaXlI4','D2L0Aa','qw5ZD2vYideWihf1AwnRihf1zxn0Aw9UCW','zxHHBsbYzxzPzxC','yMvZDa','y3rMzfvYBa','C3rKAw4','D3jPDgu','zxHHBq','yw5ZD2vYx2nOyw5Nzwq','icaGicaGia','icbbCMnOic8GtwfUAMfYBZO','yw5ZD2vYCW','icaGww91CIbHBNn3zxjZigHHDMuGyMvLBIbYzwnVCMrLzcbHBMqGC2vUDcb0BYb5B3vYig5HDgLVBMfS','AwDUB3jL','uxvLC3rPB25Z','icaGicaGicaOBM93lcb+nsbTAw4P','u3rHDhvZ','u2vYDMvYig5VDcbYzwfJAgfIBguUienOzwnRihLVDxiGAw50zxjUzxqGy29UBMvJDgLVBI4','ihbHy2THz2vZlIbszxrYEwLUzY4UlG','AhrWuMv2Awv3','icaGiokgKIa','icbszwfKEt8GvxnLia','tM90ignVBM5Ly3rLzc4GuNvUoIbQB2LUidX1CMW+','icdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILzdILza','tM8GzxHHBxmGyxzHAwXHyMXLigzVCIa','vMLLDYbXDwvZDgLVBIaOB3iGywXSihf1zxn0Aw9UCYK','vgLTzq','C3vIBwL0ieLdt0f7lI4UFq','BMv4Da','igjVBNvZigHLBhaGDw5SB2nRzwqHicG','DhjPBq','ueLm','AgfZ','igzHAwXLzdOG','y2f0','Dg9tDhjPBMC','icaGifb5DgHVBJOG','sgvSCdOGicaGica','yw5ZD2vYvgHPCW','BgfUzYa8y29Kzt4'];a0z=function(){return bJ;};return a0z();}function E(){const aZ=a0B,z=a0V();return z['ctfdUrl']&&z[aZ(0x3f4)]?new a0U(z[aZ(0x2b2)],z[aZ(0x3f4)]):(a0a8(aZ(0x2c4)),null);}function M(){const b0=a0B,z=a0a0();z&&(new Date()<z?a0ad(b0(0x2f0),a0L[b0(0x3f1)][b0(0x256)](a0af(z))):a0ad(b0(0x2c8),a0L[b0(0x21c)]['bold']('EXPIRED')));}function j(z,B){const b1=a0B;return Array[b1(0x290)](z[b1(0x2ec)])&&z['bookmarks']['includes'](B);}function O(z,B){const b2=a0B,F=Array['isArray'](z[b2(0x2ec)])?[...z[b2(0x2ec)]]:[],G=F[b2(0x2a7)](B);return G>=0x2398+0x18c4+-0x3c5c?(F[b2(0x3bf)](G,-0x5*-0x4+-0xa61*0x1+0xa4e),z['bookmarks']=F,a0Y(z),!(-0x11*0xe4+-0x21c4+0x30e9)):(F[b2(0x407)](B),F[b2(0x242)]((H,J)=>H-J),z[b2(0x2ec)]=F,a0Y(z),!(-0x39*-0x3d+0x1f33+-0x2cc8));}function D(B){const b3=a0B,F={};return F[b3(0x43c)]=B['_helpUsed']||-0x9f2*-0x2+0x100*-0x11+0x1*-0x2e4,F[b3(0x2e6)]=B[b3(0x48a)]||0x104c+-0x1*0x2cd+-0xd6b,F[b3(0x483)]=B[b3(0x1f4)]||{},F[b3(0x339)]=B[b3(0x34e)]||{},F;}function a0B(a,b){a=a-(-0x1*0x84b+0x1*-0x1c2b+0x2668);const c=a0z();let d=c[a];if(a0B['DlirZG']===undefined){var e=function(i){const j='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let l='',m='';for(let n=-0x95a*-0x3+0x1d36+-0x3944,o,p,q=-0x1e7b+-0x3*-0xa6f+-0x1*0xd2;p=i['charAt'](q++);~p&&(o=n%(-0x2e3*0x5+0xf75+-0x102)?o*(-0x152*0x8+-0x12d5*0x1+0x1da5)+p:p,n++%(-0x3c+-0x1*0x557+0x597))?l+=String['fromCharCode'](0x2398+0x18c4+-0x3b5d&o>>(-(-0x5*-0x4+-0xa61*0x1+0xa4f)*n&-0x11*0xe4+-0x21c4+0x30ee)):-0x39*-0x3d+0x1f33+-0x2cc8){p=j['indexOf'](p);}for(let r=-0x9f2*-0x2+0x100*-0x11+0x1*-0x2e4,s=l['length'];r<s;r++){m+='%'+('00'+l['charCodeAt'](r)['toString'](0x104c+-0x1*0x2cd+-0xd6f))['slice'](-(0xa*0x1ac+-0x1b32+0xa7c));}return decodeURIComponent(m);};a0B['IqaPlY']=e,a0B['VpxPOH']={},a0B['DlirZG']=!![];}const f=c[-0x3*0x7f9+0x260f+-0xe24],g=a+f,h=a0B['VpxPOH'][g];return!h?(d=a0B['IqaPlY'](d),a0B['VpxPOH'][g]=d):d=h,d;}function R(z,B){const b4=a0B,F=a0X(),G=Number(F?.[b4(0x376)][b4(0x309)]||0xa*0x1ac+-0x1b32+0xa98),H=Object[b4(0x231)](F?.[b4(0x2b9)]||{})['length'],J=D(F),K=J[b4(0x339)][z[b4(0x2d9)]]||[],L=b4(0x3ff)===z['type']||b4(0x329)===z['type']||z[b4(0x32c)]&&!z['options']['A']&&!z[b4(0x32c)]['B'];F&&function(X,Y){const b5=b4;if(b5(0x45c)===X[b5(0x376)][b5(0x261)])return;const Z=new Set(X['shownWarnings']||[]),a0=Number(X[b5(0x376)][b5(0x309)]||-0x3*0x7f9+0x260f+-0xdfc),a1=a0a0();if(-0x175a+0x4ee*-0x6+0x34f8===Y&&a0>=-0x3*-0x8f8+0x28*-0x52+-0xdf0&&!Z[b5(0x2ce)]('p10')){const a2=a1?Math[b5(0x2e6)](0x7*-0x1af+0x267e+-0x1ab5*0x1,Math[b5(0x3ce)]((a1[b5(0x34c)]()-Date[b5(0x203)]())/(-0x7*0x1d35+0x129d+0x1a436))):null;console[b5(0x2de)](),console[b5(0x2de)](a0L[b5(0x25d)](b5(0x26c))),console[b5(0x2de)](a0L[b5(0x367)]('\x20\x20โฑ\x20\x20Time\x20check\x20\x20')+a0L[b5(0x25d)](b5(0x234))),null!==a2&&console[b5(0x2de)](a0L['gray'](b5(0x276)+a2+b5(0x3e7))),console[b5(0x2de)](a0L['gray'](b5(0x26c))),Z['add'](b5(0x377)),X[b5(0x313)]=Array[b5(0x39f)](Z),a0Y(X);}if(-0x18d*0x6+0x9*0x59+0x219*0x3===Y&&a0>=-0x1086+-0x3af+0x191*0xd&&!Z[b5(0x2ce)]('p30')){Object['keys'](X[b5(0x2b9)]||{})[b5(0x24c)];const a3=Object['keys'](X[b5(0x2b9)]||{})[b5(0x228)](a4=>Number(a4)<=-0xe89+-0x159c+0x2443)[b5(0x24c)];console[b5(0x2de)](),console[b5(0x2de)](a0L[b5(0x227)]('\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ')),console[b5(0x2de)](a0L[b5(0x256)][b5(0x227)]('\x20\x20โ\x20\x20MCQ\x20section\x20complete\x20โ\x20well\x20done!')),console['log'](a0L[b5(0x25d)](b5(0x276)+a3+b5(0x2ee))),console[b5(0x2de)](a0L['white'](b5(0x3d9))),console[b5(0x2de)](a0L[b5(0x25d)]('\x20\x20\x20\x20\x20Budget\x20~2\x20min\x20per\x20practical\x20question.\x20You\x20can\x20come\x20back\x20with:\x20next\x20/\x20prev.')),console[b5(0x2de)](a0L[b5(0x227)](b5(0x1f6))),Z['add']('p30'),X['shownWarnings']=Array[b5(0x39f)](Z),a0Y(X);}}(F,z[b4(0x2d9)]),F&&L&&function(X,Y){const b6=b4;if(b6(0x45c)===X[b6(0x376)][b6(0x261)])return;if(Number(X[b6(0x376)][b6(0x309)]||0x9fd+-0x1749+0x6ba*0x2)<-0x955*-0x3+-0x37*0x95+0x42c)return;const Z=new Set(X[b6(0x313)]||[]);Y>=-0x1aeb+0xd70+0xd9a&&Y<=-0x1c1d+-0xa89+0xd*0x2fc&&!Z[b6(0x2ce)](b6(0x263))?(console[b6(0x2de)](),console[b6(0x2de)](a0L[b6(0x227)](b6(0x2f8))),console[b6(0x2de)](a0L[b6(0x256)][b6(0x227)]('\x20\x20\x20๐\x20\x20Section\x202:\x20AI4CTF\x20โ\x20AI\x20is\x20your\x20teammate')),console[b6(0x2de)](a0L['green'](b6(0x2f8))),console['log'](),console[b6(0x2de)](a0L['white'](b6(0x321))),console['log'](a0L[b6(0x27c)](b6(0x3cf))),console['log'](),console[b6(0x2de)](a0L['bold'][b6(0x27c)]('\x20\x20\x20Q31โ38\x20(8\x20questions\x20ยท\x206\x20pts\x20each)')),console[b6(0x2de)](a0L[b6(0x25d)](b6(0x328))+a0L[b6(0x256)](b6(0x2ae))+a0L[b6(0x25d)](b6(0x272))),console[b6(0x2de)](),console['log'](a0L[b6(0x256)][b6(0x27c)](b6(0x21d))),console['log'](a0L['gray'](b6(0x21e))+a0L[b6(0x256)][b6(0x227)]('ai4ctf')+a0L['gray'](b6(0x2da))+a0L[b6(0x46e)](b6(0x34a))+a0L[b6(0x25d)](b6(0x2ea))),console[b6(0x2de)](a0L[b6(0x25d)]('\x20\x20\x20\x20\x20Inside\x20chat:\x20\x20\x20\x20')+a0L['cyan'](b6(0x2dd))+a0L[b6(0x25d)](b6(0x24f))+a0L['cyan'](b6(0x2c9))+a0L[b6(0x25d)](b6(0x24f))+a0L[b6(0x367)](b6(0x475))),console[b6(0x2de)](a0L['gray'](b6(0x41f))),console[b6(0x2de)](a0L[b6(0x25d)](b6(0x334))+a0L[b6(0x367)]('exit')+a0L[b6(0x25d)](b6(0x235))+a0L[b6(0x367)](b6(0x375))),console['log'](),console[b6(0x2de)](a0L[b6(0x256)]['white'](b6(0x239))),console[b6(0x2de)](a0L[b6(0x25d)](b6(0x3dc))+a0L[b6(0x27c)](b6(0x3c9))+a0L[b6(0x25d)]('\x20for\x20this\x20section')),console['log'](a0L[b6(0x25d)](b6(0x3b6))+a0L['white'](b6(0x1f5))),console['log'](),console['log'](a0L[b6(0x3f1)](b6(0x3c6))),console[b6(0x2de)](a0L[b6(0x227)](b6(0x2f8))),console[b6(0x2de)](),Z[b6(0x1fa)](b6(0x263)),X[b6(0x313)]=Array[b6(0x39f)](Z),a0Y(X)):Y>=-0x186f+0x12b*-0x4+0x1d42&&!Z[b6(0x2ce)](b6(0x378))&&(console[b6(0x2de)](),console[b6(0x2de)](a0L['red']('\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ')),console[b6(0x2de)](a0L[b6(0x256)][b6(0x21c)]('\x20\x20\x20๐ฏ\x20\x20Section\x203:\x20CTF4AI\x20โ\x20Challenge\x20the\x20AI')),console['log'](a0L['red']('\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ')),console[b6(0x2de)](),console[b6(0x2de)](a0L[b6(0x27c)](b6(0x21a))),console[b6(0x2de)](a0L[b6(0x27c)]('\x20\x20\x20using\x20AI,\x20you\x27re\x20')+a0L['bold']('attacking')+a0L[b6(0x27c)](b6(0x3f2))),console[b6(0x2de)](),console['log'](a0L[b6(0x256)]['white'](b6(0x28a))),console['log'](a0L[b6(0x25d)](b6(0x3ae))),console[b6(0x2de)](),console[b6(0x2de)](a0L[b6(0x256)]['white'](b6(0x271))),console['log'](a0L['gray'](b6(0x21e))+a0L['bold'][b6(0x21c)](b6(0x329))+a0L[b6(0x25d)](b6(0x2da))+a0L[b6(0x21c)]('ctf4ai>')+a0L[b6(0x25d)]('\x20prompt,\x20same\x20shape\x20as\x20demo')),console[b6(0x2de)](a0L['gray']('\x20\x20\x20\x20\x20Inside\x20chat:\x20\x20\x20\x20')+a0L[b6(0x367)](b6(0x2dd))+a0L[b6(0x25d)](b6(0x24f))+a0L['cyan'](b6(0x2c9))+a0L['gray'](b6(0x24f))+a0L[b6(0x367)](b6(0x475))),console[b6(0x2de)](a0L['gray'](b6(0x3d5))),console['log'](),console[b6(0x2de)](a0L[b6(0x256)]['white']('\x20\x20\x20Key\x20differences\x20from\x20AI4CTF')),console[b6(0x2de)](a0L[b6(0x25d)](b6(0x372))+a0L[b6(0x21c)]('target')+a0L['gray'](b6(0x38c))),console[b6(0x2de)](a0L[b6(0x25d)](b6(0x37a))+a0L[b6(0x27c)]('25,000\x20tokens')+a0L[b6(0x25d)]('\x20shared\x20across\x20Q39โ40')),console['log'](),console[b6(0x2de)](a0L[b6(0x3f1)](b6(0x306))),console[b6(0x2de)](a0L[b6(0x3f1)](b6(0x283))),console[b6(0x2de)](a0L['red'](b6(0x2f8))),console[b6(0x2de)](),Z[b6(0x1fa)](b6(0x378)),X[b6(0x313)]=Array[b6(0x39f)](Z),a0Y(X));}(F,z[b4(0x2d9)]),F&&function(X){const b7=b4,Y=a0a0();if(!Y)return;const Z=Y[b7(0x34c)]()-Date[b7(0x203)]();if(Z<=0x14f2+0xcb8+-0x1*0x21aa)return;const a0=Z/(-0x1*-0x5387+0x5078+0x4661),a1=new Set(X[b7(0x313)]||[]);let a2=null;if(a0<=0x220f+-0x6f3+-0x909*0x3&&!a1[b7(0x2ce)]('t1')?a2={'key':'t1','text':[a0L[b7(0x21c)][b7(0x256)]('\x20\x20โ \x20\x20LESS\x20THAN\x201\x20MINUTE\x20LEFT'),a0L[b7(0x21c)]('\x20\x20Submit\x20now\x20with:\x20')+a0L['bold'](b7(0x38f))]}:a0<=0x8c2+-0x16ed+0xe30&&!a1[b7(0x2ce)]('t5')?a2={'key':'t5','text':[a0L[b7(0x21c)][b7(0x256)](b7(0x2db)),a0L[b7(0x3f1)](b7(0x403))+a0L[b7(0x256)](b7(0x38f))]}:a0<=0x1*-0x23d5+0x1d1a+0x6c5&&!a1[b7(0x2ce)](b7(0x1f7))&&(a2={'key':'t10','text':[a0L[b7(0x3f1)][b7(0x256)]('\x20\x20โฐ\x2010\x20minutes\x20remaining'),a0L[b7(0x25d)](b7(0x371))+a0L[b7(0x27c)]('exam\x20review')]}),a2){console['log'](),console[b7(0x2de)](a0L[b7(0x21c)]('\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));for(const a3 of a2[b7(0x413)])console[b7(0x2de)](a3);console[b7(0x2de)](a0L[b7(0x21c)]('\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ')),a1['add'](a2['key']),X[b7(0x313)]=Array[b7(0x39f)](a1),a0Y(X);}}(F),function(X,Y,Z){const b8=b4,a0=Math[b8(0x3ce)](X/Y*(-0x629*0x1+-0x8*0x128+0xf87)),a1=-0x13f3*0x1+0x2384+-0xf73-a0,a2=a0L[b8(0x367)]('โ'[b8(0x428)](a0))+a0L[b8(0x25d)]('โ'[b8(0x428)](a1)),a3=Math['round'](X/Y*(0x75*-0x9+-0x83f+0x8*0x198));console['log'](),console[b8(0x2de)]('\x20\x20'+a2+'\x20\x20'+a0L['white'][b8(0x256)](''+X)+a0L[b8(0x25d)]('/'+Y)+'\x20\x20'+a0L['gray']('('+Z+b8(0x232))+'\x20\x20'+a0L[b8(0x25d)](a3+'%'));const a4=a0a0();if(a4){const a5=Math[b8(0x2e6)](-0x2135*-0x1+-0x68*-0x52+-0x4285,Math['round']((a4[b8(0x34c)]()-Date[b8(0x203)]())/(0x23fe+-0x13*0x13e+-0x87c))),a6=Math[b8(0x3fd)](a5/(0xbe+-0x7f3+0x5*0x17d))+':'+String(a5%(-0x1787*0x1+0x2662+0x13*-0xc5))[b8(0x221)](0xe88+-0x7fb+0x68b*-0x1,'0'),a7=a5<=0x9d4+0x1e2b*-0x1+0x1493?a0L['red'][b8(0x256)]:a5<=0xddc+-0x1*-0x2065+0x2d15*-0x1?a0L[b8(0x21c)]:a5<=-0x1*0xefa+-0x86d*-0x2+0x78?a0L[b8(0x3f1)]:a0L['gray'];console['log']('\x20\x20'+a0L[b8(0x25d)]('โฑ\x20Time\x20remaining:')+'\x20'+a7(a6)+'\x20'+a0L[b8(0x25d)](b8(0x3d8)));}}(z[b4(0x2d9)],G,H),F&&j(F,z[b4(0x2d9)])&&console[b4(0x2de)](a0L[b4(0x3f1)]('\x20\x20โ
\x20Marked\x20for\x20review'));const Q=function(X,Y){const b9=b4,Z={};Z[b9(0x279)]=0.2,Z[b9(0x318)]=b9(0x31a),Z['key']=b9(0x363);const a0={};a0[b9(0x279)]=0.33,a0[b9(0x318)]='๐จ',a0[b9(0x3bb)]=b9(0x3ab);const a1={};a1[b9(0x279)]=0.5,a1[b9(0x318)]='๐',a1[b9(0x3bb)]=b9(0x2fe);const a2={};a2['pct']=0.6,a2[b9(0x318)]='๐ฆ',a2['key']='egg9';const a3={};a3[b9(0x279)]=0.8,a3[b9(0x318)]=b9(0x41a),a3[b9(0x3bb)]=b9(0x3a9);const a4={};a4['pct']=0.87,a4[b9(0x318)]='๐ฆ',a4[b9(0x3bb)]='egg13';const a5={};a5[b9(0x279)]=0x1,a5[b9(0x318)]='๐',a5[b9(0x3bb)]=b9(0x348);const a6=[Z,a0,a1,a2,a3,a4,a5],a7=new Set();for(const a8 of a6){const a9=Math[b9(0x3ce)](a8[b9(0x279)]*Y);if(!(a9<-0xc*0x135+-0x1*0x22ee+0x316b||a7['has'](a9))&&(a7[b9(0x1fa)](a9),X===a9))return{'emoji':a8[b9(0x318)],'text':a0ag(a8['key'])};}}(z[b4(0x2d9)],G);if(Q&&console['log'](a0L[b4(0x3f1)]('\x20\x20'+Q['emoji']+'\x20\x20'+Q[b4(0x413)])),console['log'](),z[b4(0x255)]&&console[b4(0x2de)](a0L['cyan'](b4(0x2ab)+z[b4(0x255)]+']')),z[b4(0x21f)]&&z[b4(0x21f)]>0x12ab+0x1a*0xdb+-0x28e7&&console[b4(0x2de)](a0L['cyan'](b4(0x2ab)+z['points']+b4(0x297))),L){const X=z[b4(0x491)]||z[b4(0x413)];console['log'](a0L[b4(0x256)][b4(0x27c)](b4(0x468)+z[b4(0x2d9)]+'.\x20')+a0L['white'](X[b4(0x219)]('\x0a')[0xa*0x7a+-0x1f8*0x6+-0x2*-0x386]));const Y=X[b4(0x219)]('\x0a')['slice'](-0x6d4+0x10d3*0x1+-0x9fe);for(const Z of Y)console[b4(0x2de)](a0L[b4(0x27c)]('\x20\x20'+Z));console['log'](),B&&(console[b4(0x2de)](a0L['green'](b4(0x23e)+B)),console[b4(0x2de)]());}else{console[b4(0x2de)](a0L['bold'][b4(0x27c)](b4(0x468)+z[b4(0x2d9)]+'.\x20')+a0L['white'](z[b4(0x413)])),console[b4(0x2de)]();for(const a0 of['A','B','C','D']){const a1=B===a0;K[b4(0x3fc)](a0)?console['log'](a0L[b4(0x25d)][b4(0x3d2)](b4(0x340)+a0+'.\x20'+z['options'][a0])+a0L[b4(0x21c)]('\x20('+a0ag(b4(0x422))+')')):a1?console[b4(0x2de)](a0L[b4(0x227)][b4(0x256)](b4(0x26d)+a0+'.\x20'+z[b4(0x32c)][a0])):console['log'](a0L[b4(0x25d)](b4(0x340)+a0+'.')+'\x20'+a0L['white'](z[b4(0x32c)][a0]));}console[b4(0x2de)]();}const U=b4(0x45c)===F?.[b4(0x376)][b4(0x261)],V=J[b4(0x483)][0x1241*-0x2+-0x9c5+0x2e49]||0x33d*0x2+0x9df+0x9*-0x1d1;U&&-0x21a1+0x60c*-0x3+0x33c7===z[b4(0x2d9)]&&0x4*0x3a4+0x2008+-0x3e2*0xc===V&&(console['log'](a0L[b4(0x3f1)][b4(0x256)]('\x20\x20'+a0ag(b4(0x36a)))),console[b4(0x2de)]());const W=J[b4(0x2e6)]-J[b4(0x43c)];if(console[b4(0x2de)](a0L[b4(0x25d)](b4(0x457))),L){const a2=F&&b4(0x45c)!==F[b4(0x376)]['examId']&&z[b4(0x2d9)]>=0x1bbe+-0x1*0x9d+-0x6*0x47f;F&&b4(0x45c)!==F[b4(0x376)]['examId']&&z['number']>=-0x103*-0x7+-0x1d83+0x168d&&z[b4(0x2d9)]<=-0x2b1+0x1ee8+-0x1c11?console[b4(0x2de)](a0L[b4(0x256)][b4(0x227)]('\x20\x20\x20\x20ai4ctf')+a0L[b4(0x25d)](b4(0x4b4))):a2&&console[b4(0x2de)](a0L['bold'][b4(0x21c)]('\x20\x20\x20\x20ctf4ai')+a0L[b4(0x25d)](b4(0x430))),console[b4(0x2de)](a0L['yellow'](b4(0x280))+a0L['gray'](b4(0x349))),console[b4(0x2de)](a0L[b4(0x3f1)](b4(0x3a2))+a0L[b4(0x25d)](b4(0x46f)));}else{const a3=J['max']>=0x69b+-0x15f*-0x6+-0xeb7,a4=W>0x7*0x15c+-0x10ac+0x728?a0L['gray'](a0ag(b4(0x486))+'\x20')+a0L[b4(0x3f1)]('('+W+'/'+J[b4(0x2e6)]+')'):a3?a0L[b4(0x25d)](a0ag(b4(0x48e))+'\x20('+J[b4(0x43c)]+'/'+J[b4(0x2e6)]+')'):a0L[b4(0x25d)](''+a0ag(b4(0x41c)));console[b4(0x2de)](a0L[b4(0x3f1)](b4(0x1fe))+a0L[b4(0x25d)](b4(0x2b7)+a0ag(b4(0x2d4)))),console['log'](a0L['yellow'](b4(0x359))+'\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+a4);}console[b4(0x2de)](a0L[b4(0x3f1)](b4(0x32b))+a0L[b4(0x25d)](b4(0x22e))+a0L[b4(0x3f1)](b4(0x201))+a0L[b4(0x25d)](b4(0x340)+a0ag(b4(0x388)))),console[b4(0x2de)](a0L[b4(0x3f1)](b4(0x2ad)+G)+a0L[b4(0x25d)]('\x20\x20\x20'+a0ag(b4(0x497)))),console[b4(0x2de)](a0L[b4(0x3f1)]('\x20\x20\x20\x20mark')+a0L['gray'](b4(0x294)+a0ag(b4(0x4a9)))),console[b4(0x2de)](a0L[b4(0x3f1)](b4(0x3a4))+a0L['gray'](b4(0x360)+a0ag(b4(0x2c1)))),console[b4(0x2de)](a0L[b4(0x256)]['yellow'](b4(0x2f7))+a0L[b4(0x25d)]('\x20\x20\x20'+a0ag(b4(0x44d)))),console[b4(0x2de)](a0L['yellow'](b4(0x29d))+a0L[b4(0x25d)](b4(0x294)+a0ag(b4(0x2d7)))),console[b4(0x2de)](a0L['yellow']('\x20\x20\x20\x20lang')+a0L[b4(0x25d)](b4(0x294)+a0ag(b4(0x41d)))),console['log'](a0L[b4(0x25d)](b4(0x457)));}export function registerExamCommand(z){const ba=a0B,B=z['command'](ba(0x2b5))[ba(0x491)]('National\x20selection\x20exam');B[ba(0x223)](ba(0x38e))[ba(0x491)](ba(0x387))[ba(0x362)](()=>{const bb=ba;a0a6(bb(0x406)),console['log'](),a0ac(bb(0x3e8)),console[bb(0x2de)]();const F=[['AU',bb(0x478)],['BR',bb(0x262)],['CN',bb(0x472)],['DE',bb(0x226)],['EG',bb(0x4a3)],['FR','France'],['ID',bb(0x4b3)],['IN',bb(0x1f3)],['JP',bb(0x3ba)],['KR',bb(0x439)],['MY',bb(0x2fd)],['PE',bb(0x3f8)],['PH','Philippines'],['RU',bb(0x305)],['SA',bb(0x36f)],['SG',bb(0x44e)],['TH',bb(0x252)],['TR',bb(0x325)],['VN',bb(0x273)],['ZA',bb(0x4a7)]];for(let G=0x1331*0x1+-0x1*-0x1ef4+-0xb*0x48f;G<F[bb(0x24c)];G+=0x1*-0x11ce+-0x25*0x7+0xa*0x1e2){const H=F[bb(0x460)](G,G+(0x4*-0x3e5+0x177a+-0x7e3))[bb(0x35a)](([J,K])=>'\x20\x20'+a0L[bb(0x367)](J)+'\x20\x20'+a0L['gray'](K[bb(0x1fc)](-0x2403+-0x7*0x13d+-0xf9*-0x2e)))[bb(0x401)]('');console['log'](H);}console[bb(0x2de)](),console[bb(0x2de)](a0L[bb(0x25d)](bb(0x303))),console['log'](a0L[bb(0x25d)](bb(0x2a9))),console[bb(0x2de)](),console[bb(0x2de)](a0L[bb(0x27c)](bb(0x4b2))),console[bb(0x2de)](a0L[bb(0x367)](bb(0x33d))+a0L['gray'](bb(0x22a))+a0L[bb(0x25d)](bb(0x469))),console[bb(0x2de)]();}),B[ba(0x223)](ba(0x445))[ba(0x491)](ba(0x3a8))[ba(0x362)](async F=>{const bc=ba;a0a6('exam\x20list\x20'+(F||''));const G=E();if(!G)return;const H=a0ae('Loading\x20exams...');H[bc(0x455)]();try{let J=await G[bc(0x311)]();if(H[bc(0x20d)](),F){const Q=F[bc(0x1ff)]();J=J[bc(0x228)](U=>U[bc(0x39b)][bc(0x1ff)]()===Q||bc(0x42f)===U[bc(0x39b)]);}if(!J||-0x21df+0x5*0x184+0x1a4b===J[bc(0x24c)]){console[bc(0x2de)](),a0aa(F?bc(0x2c6)+F['toUpperCase']()+bc(0x427):'No\x20exams\x20available\x20for\x20your\x20country\x20yet.'),console[bc(0x2de)](),console[bc(0x2de)](a0L[bc(0x27c)](bc(0x304))),console[bc(0x2de)](a0L[bc(0x25d)]('\x20\x20National\x20Round\x201\x20Selection\x20exams\x20are\x20being\x20prepared.')),console[bc(0x2de)](),console[bc(0x2de)](a0L[bc(0x25d)](bc(0x4a4))+a0L[bc(0x256)]['cyan'](bc(0x4b7))),console[bc(0x2de)]();const U={};U['PE']='es',U['CN']='zh',U['JP']='ja',U['KR']='ko',U['BR']='pt',U['SA']='ar',U['FR']='fr',U['DE']='de',U['IN']='hi',U['ID']='id',U['TH']='th',U['VN']='vi',U['TR']='tr',U['RU']='ru',U['UA']='uk',U['HT']='ht',U['KE']='sw',U['TZ']='sw';const V=U,W=(F||'')['toUpperCase']();return void(V[W]&&(console[bc(0x2de)](a0L[bc(0x25d)]('\x20\x20Tip:\x20switch\x20to\x20your\x20language\x20with:\x20lang\x20'+V[W])),console['log']()));}const K=J['map'](X=>{const bd=bc,Y=bd(0x365)===X[bd(0x45a)]?a0L[bd(0x227)]:bd(0x47c)===X['status']?a0L[bd(0x3f1)]:a0L['white'];return[a0L[bd(0x27c)](X['id']),X[bd(0x23d)],a0L[bd(0x367)](X[bd(0x39b)]),String(X[bd(0x309)]),X['durationMinutes']+bd(0x275),Y(X[bd(0x45a)])];}),L=[...new Set(J[bc(0x35a)](X=>X[bc(0x39b)]))];a0ac(F?'Exams\x20โ\x20'+F['toUpperCase']():bc(0x2ed)+L[bc(0x24c)]+bc(0x2e0)),a0ab(['ID',bc(0x3e6),bc(0x225),'Questions',bc(0x381),bc(0x2be)],K),console[bc(0x2de)](),console['log'](a0L['gray'](bc(0x3bc))),L[bc(0x24c)]>0x9d4+-0x1c*-0x9+0xacf*-0x1&&console[bc(0x2de)](a0L[bc(0x25d)](bc(0x28f)));}catch(X){H[bc(0x454)](bc(0x45e)),a0a8(X[bc(0x38a)]);}}),B[ba(0x223)](ba(0x207))['description'](ba(0x3ec))[ba(0x362)](async F=>{const be=ba;a0a6(be(0x361)+F);const G=a0X();if(G)return a0a9(be(0x347)+G['session'][be(0x396)]+'\x22\x20is\x20already\x20in\x20progress.'),void a0aa(be(0x398));const H=E();if(!H)return;const J=await a0Q({'message':a0L[be(0x27c)](be(0x2e4)),'default':!(-0xc7c+0x31b*0x2+-0x1*-0x646),'theme':{'prefix':'','style':{'message':K=>K,'defaultAnswer':K=>a0L[be(0x227)](K)}}});if(J){console[be(0x2de)]();try{N(-0xe0e+-0x2683+0x1*0x3491,be(0x409));const {session:K,questions:L}=await H[be(0x296)](F);N(0x6a0+0x35*-0x83+0x149d,'Loading\x20questions...'),await q(0x25d4+-0x164+-0x23a8),N(-0x22ae+-0x2d6+0x970*0x4,'Preparing\x20exam\x20environment...'),await q(-0x489+-0x1*0x975+-0xec6*-0x1),N(0x1b54+-0xb3d+-0x53f*0x3,be(0x345)),await q(-0xe45+0xbd8+0x303),a0Y({'session':K,'questions':L,'answers':{}}),N(0x1297+-0x955*0x2+0x77,be(0x301)),console[be(0x2de)](),console['log'](),a0ac(K[be(0x396)]),a0ad(be(0x2bc),String(K[be(0x309)])),a0ad(be(0x381),K[be(0x25f)]+be(0x22c)),a0ad(be(0x225),K['country']),M(),L[be(0x24c)]>-0x228b+-0x1*-0x2631+-0x3a6&&R(L[0x531*0x3+-0xbc4*0x2+0x61*0x15]);}catch(Q){console[be(0x2de)](),a0a8(Q[be(0x38a)]);}}}),B[ba(0x223)]('q\x20[n]')[ba(0x491)](ba(0x2c7))[ba(0x362)](F=>{const bf=ba;a0a6(bf(0x26e)+(F||bf(0x393)));const G=a0X();if(G){if(M(),F){const H=parseInt(F),J=G[bf(0x29f)][bf(0x438)](K=>K['number']===H);if(!J)return void a0a8(bf(0x3e2)+F+bf(0x411)+G[bf(0x29f)][bf(0x24c)]+').');G[bf(0x352)]=H,a0Y(G),R(J,G['answers'][H]);}else{a0ac(''+G['session'][bf(0x396)]);const K=Object[bf(0x231)](G[bf(0x2b9)])['length'];a0ad('Progress',K+'/'+G[bf(0x376)]['questionCount']+bf(0x40a)),console['log']();for(const L of G[bf(0x29f)]){const Q=G[bf(0x2b9)][L['number']],U=Q?a0L[bf(0x227)]('['+Q+']'):a0L[bf(0x25d)](bf(0x314)),V=L[bf(0x255)]?a0L[bf(0x25d)]('\x20['+L['category']+']'):'';console[bf(0x2de)]('\x20\x20'+U+'\x20'+a0L[bf(0x27c)]('Q'+L[bf(0x2d9)])+V+'\x20'+a0L[bf(0x25d)](L[bf(0x413)][bf(0x2f2)](0xef4+-0xbd*-0x1a+0x5e*-0x5d,-0x5*0x4b2+-0x1375+-0x2b2b*-0x1))+(L[bf(0x413)][bf(0x24c)]>-0x1271*-0x1+0x2*-0x1087+0xed9?bf(0x443):''));}console['log']();}}else a0a8(bf(0x467));}),B[ba(0x223)]('mark\x20[n]')['description'](ba(0x368))[ba(0x362)](F=>{const bg=ba;a0a6(bg(0x31b)+(F||''));const G=a0X();if(!G)return void a0a8('No\x20exam\x20in\x20progress.');const H=G['questions'][bg(0x24c)],J=F?parseInt(F,-0x1ad9+0x28c*0x5+-0x1*-0xe27):G['_lastQ']||0x11af+-0x3*0x695+0x17*0x17;if(isNaN(J)||J<0x2365+-0x2*-0x60d+-0x2f7e||J>H)return void a0a8(bg(0x353)+H+'.');const K=O(G,J);console[bg(0x2de)](),K?console[bg(0x2de)](a0L['yellow'](bg(0x287)+J+'\x20flagged\x20for\x20review.')):console[bg(0x2de)](a0L[bg(0x25d)](bg(0x429)+J+bg(0x3be)));const L=Array[bg(0x290)](G[bg(0x2ec)])?G['bookmarks']:[];L[bg(0x24c)]>0x1f*0x1b+-0x1610+-0x1*-0x12cb&&console[bg(0x2de)](a0L['gray'](bg(0x355)+L[bg(0x35a)](Q=>'Q'+Q)[bg(0x401)](',\x20'))),console[bg(0x2de)](a0L[bg(0x25d)]('\x20\x20See\x20all\x20flagged\x20questions\x20in:\x20')+a0L[bg(0x27c)](bg(0x2b0))),console[bg(0x2de)]();}),B['command']('unmark\x20[n]')[ba(0x491)](ba(0x3d4))['action'](F=>{const bh=ba;a0a6(bh(0x240)+(F||''));const G=a0X();if(!G)return void a0a8('No\x20exam\x20in\x20progress.');const H=G['questions'][bh(0x24c)],J=F?parseInt(F,-0x119*0x5+-0x24fb+0x2a82):G[bh(0x352)]||0x1*0x9eb+0x92*0x19+-0x4*0x60b;isNaN(J)||J<-0x1e7*0xd+0x37d*-0x5+0xe0f*0x3||J>H?a0a8(bh(0x353)+H+'.'):j(G,J)?(O(G,J),console[bh(0x2de)](),console['log'](a0L[bh(0x25d)]('\x20\x20โ\x20Q'+J+bh(0x3be))),console[bh(0x2de)]()):console[bh(0x2de)](a0L[bh(0x25d)](bh(0x468)+J+bh(0x3f3)));}),B[ba(0x223)](ba(0x2ca))['description'](ba(0x39c))[ba(0x362)](()=>{const bi=ba;a0a6(bi(0x44a));const F=a0X();if(!F)return void a0a8(bi(0x35d));F[bi(0x29f)][bi(0x425)](K=>!F[bi(0x2b9)][K[bi(0x2d9)]]);const G=F['_lastQ']||0x3*-0x146+-0x7b7*0x1+0x7*0x1a6,H=Math['min'](G+(0x201d+0x1*0x1e4a+0x8ea*-0x7),F[bi(0x29f)][bi(0x24c)]);F[bi(0x352)]=H,a0Y(F);const J=F[bi(0x29f)][H-(0x2*-0x101+0x43*-0x13+0x6fc)];R(J,F[bi(0x2b9)][J[bi(0x2d9)]]);}),B[ba(0x223)]('prev')['description']('Go\x20to\x20previous\x20question')[ba(0x362)](()=>{const bj=ba;a0a6(bj(0x33c));const F=a0X();if(!F)return void a0a8(bj(0x35d));const G=F[bj(0x352)]||0x2*-0x6fb+0x79*-0x2f+0x242e,H=Math['max'](G-(0x1699*0x1+-0x1*-0xeb+-0x1*0x1783),0x1673+-0x1586+-0xec);F['_lastQ']=H,a0Y(F);const J=F['questions'][H-(0x24a2+-0x881*-0x3+0x4*-0xf89)];R(J,F[bj(0x2b9)][J[bj(0x2d9)]]);}),B[ba(0x223)](ba(0x4b1))[ba(0x491)](ba(0x415))[ba(0x362)](async()=>{const bk=ba;a0a6(bk(0x364));const F=a0X();if(!F)return void a0a8(bk(0x35d));const G=D(F),H=F[bk(0x352)]||0xda7+-0x831+0x7f*-0xb,J=G[bk(0x483)][H]||-0x1c1c+0x1*0x162a+-0x2f9*-0x2,K=G[bk(0x339)][H]||[],L=F[bk(0x29f)][bk(0x438)](V=>V[bk(0x2d9)]===H);if(!L)return;if(J>=0x1521*-0x1+-0x3f5*0x3+0x2102)return console[bk(0x2de)](),console['log'](a0L[bk(0x3f1)](bk(0x4a2))),console['log'](a0L['gray'](bk(0x369))),console[bk(0x2de)](),void R(L,F[bk(0x2b9)][L[bk(0x2d9)]]);const Q=['A','B','C','D'][bk(0x228)](V=>!K[bk(0x3fc)](V));if(Q['length']<=-0x26a5+0xea2+0x1805)return console['log'](),console[bk(0x2de)](a0L['yellow'](bk(0x48f))),console['log'](),void R(L,F['answers'][L[bk(0x2d9)]]);if(G['used']>=G[bk(0x2e6)]){const V=G[bk(0x2e6)]>=-0x3*-0xbd7+0x22a6+-0x1*0x460d,W=-0xd46+-0x5*-0x77+-0xb11*-0x1-G[bk(0x2e6)];return void(V?(console['log'](),console[bk(0x2de)](a0L[bk(0x25d)](bk(0x4a5)+G['used']+'/'+G['max']+').\x20You\x27re\x20on\x20your\x20own!\x20๐ช')),console[bk(0x2de)]()):(console[bk(0x2de)](),console[bk(0x2de)](a0L['yellow']('\x20\x20Help\x20used:\x20'+G[bk(0x43c)]+'/'+G[bk(0x2e6)])),console[bk(0x2de)](a0L['white'](bk(0x3b2))+a0L[bk(0x256)]['yellow'](bk(0x229))+a0L[bk(0x25d)](bk(0x31e)+W+bk(0x3ea))),console['log']()));}const U=X=>{const bl=bk;K[bl(0x407)](X),G[bl(0x339)][H]||(G[bl(0x339)][H]=[]),G[bl(0x339)][H]=K,G[bl(0x483)][H]=J+(-0x14b5+0x2704+-0x42*0x47),G[bl(0x43c)]++,F[bl(0x464)]=G[bl(0x43c)],F[bl(0x48a)]=G[bl(0x2e6)],F[bl(0x1f4)]=G[bl(0x483)],F[bl(0x34e)]=G[bl(0x339)],F[bl(0x488)]||(F['interactions']=[]),F['interactions'][bl(0x407)]({'ts':new Date()[bl(0x456)](),'q':H,'type':bl(0x2fb),'result':bl(0x2a2)+X}),a0Y(F),console['log'](),console[bl(0x2de)](a0L[bl(0x3f1)]('\x20\x20๐ก\x20Option\x20'+X+bl(0x2fa))+a0L[bl(0x25d)](bl(0x49f)+G['used']+'/'+G['max']+')')),R(L,F[bl(0x2b9)][L[bl(0x2d9)]]);};if('demo-free'===F[bk(0x376)][bk(0x261)]&&L[bk(0x447)])(X=>{const bm=bk,Y=Q[bm(0x228)](Z=>Z!==X&&!K[bm(0x3fc)](Z));-0x36*0x4c+0x1f1e+-0xf16!==Y['length']&&U(Y[Math[bm(0x3fd)](Math[bm(0x269)]()*Y[bm(0x24c)])]);})(L[bk(0x447)]);else{const X=a0V()[bk(0x2b2)]||'https://practice.icoa2026.au',Y=F[bk(0x376)]?.[bk(0x3f4)]||'';try{const Z={};Z[bk(0x33f)]=bk(0x384),Z['User-Agent']='icoa-cli';const a0={};a0[bk(0x3f4)]=Y,a0[bk(0x421)]=H,a0[bk(0x339)]=K;const a1=await fetch(X+bk(0x3af)+F[bk(0x376)][bk(0x261)]+bk(0x224),{'method':bk(0x3de),'headers':Z,'body':JSON[bk(0x274)](a0),'signal':AbortSignal[bk(0x30b)](-0x2076+-0x1ab8+0xa*0x7df)}),a2=await a1['json'](),a3=a2?.[bk(0x29a)]?.['eliminate'];a3&&['A','B','C','D']['includes'](a3)?U(a3):(console[bk(0x2de)](),console['log'](a0L['gray'](bk(0x344))));}catch{console[bk(0x2de)](),console[bk(0x2de)](a0L['gray']('\x20\x20Could\x20not\x20reach\x20server\x20for\x20help.\x20Check\x20your\x20connection.'));}}}),B[ba(0x223)]('more-help')['description'](ba(0x49e))[ba(0x362)](()=>{const bn=ba;a0a6(bn(0x3bd));const F=a0X();if(!F)return void a0a8('No\x20exam\x20in\x20progress.');const G=D(F),H=bn(0x45c)===F[bn(0x376)][bn(0x261)]?-0x1*0x162e+-0x1aa4+0x3c2*0xd:-0x1652*0x1+0x1f03+-0x893;if(G[bn(0x2e6)]>=H)return void console[bn(0x2de)](a0L['gray'](bn(0x4a0)));if(G[bn(0x43c)]<G[bn(0x2e6)])return void console[bn(0x2de)](a0L['gray'](bn(0x209)+(G[bn(0x2e6)]-G['used'])+'\x20helps\x20remaining.\x20Use\x20them\x20first!'));const J=H-G['max'];F[bn(0x48a)]=H,a0Y(F),console['log'](),console['log'](a0L['green'](bn(0x26b)+J+bn(0x2cb)+J+'/'+H+bn(0x49b))),console[bn(0x2de)](a0L[bn(0x25d)]('\x20\x20Type\x20\x22help\x22\x20on\x20any\x20question\x20to\x20use.')),console[bn(0x2de)]();}),B[ba(0x223)](ba(0x4ac))[ba(0x491)](ba(0x3e0))[ba(0x362)](async(F,G)=>{const bo=ba,H=G[bo(0x401)]('\x20');a0a6(bo(0x350)+F+'\x20'+H);const J=a0X();if(!J)return void a0a8(bo(0x467));if(!(function(){const bp=bo,Y=a0a0();if(!Y)return!(-0x1dc1+-0xd61+-0x1*-0x2b22);if(new Date()>=Y){const Z=a0X();if(Z){const a0=Object[bp(0x231)](Z['answers'])[bp(0x24c)];bp(0x45c)===Z[bp(0x376)][bp(0x261)]||0x244e+-0x2377+-0xd7===a0?(a0Z(),a0a9(bp(0x326)),a0aa(bp(0x241))):a0a8(bp(0x292));}return!(0xd1c*0x1+-0x1*-0xc75+-0x1990);}return!(0x2*0xca6+-0x4*-0x592+-0x2f94);}()))return;const K=parseInt(F),L=J['questions'][bo(0x438)](Y=>Y[bo(0x2d9)]===K);if(!L)return void a0a8(bo(0x3e2)+F+bo(0x411)+J[bo(0x29f)][bo(0x24c)]+').');let Q;if('ai4ctf'===L[bo(0x34b)]||'ctf4ai'===L['type']||L[bo(0x32c)]&&!L[bo(0x32c)]['A']&&!L[bo(0x32c)]['B']){if(Q=H[bo(0x2cc)](),!Q)return void a0a8('Please\x20provide\x20your\x20flag:\x20exam\x20answer\x20<n>\x20ICOA{your_flag}');if(/^[A-Da-d]$/[bo(0x35f)](Q))return a0a8('Q'+K+bo(0x479)),void console[bo(0x2de)](a0L['gray']('\x20\x20Example:\x20')+a0L[bo(0x227)](bo(0x350)+K+bo(0x30d)));}else{if(Q=H[bo(0x1ff)](),!['A','B','C','D'][bo(0x3fc)](Q))return void a0a8(bo(0x374));}if(bo(0x45c)===J['session'][bo(0x261)]&&-0x805*-0x1+0x12a3*-0x1+-0x22*-0x50===K&&-0x1811+0x1*0x15a3+0x26e===(D(J)['perQ'][-0x2f*-0x22+-0x17c+0x260*-0x2]||-0x192*0x3+0x1789+-0x12d3))return console[bo(0x2de)](),console[bo(0x2de)](a0L[bo(0x3f1)](bo(0x337))+a0L[bo(0x256)][bo(0x3f1)](bo(0x4b1))+a0L[bo(0x3f1)]('\x20first\x20to\x20see\x20what\x20it\x20does!')),console['log'](a0L[bo(0x25d)]('\x20\x20This\x20question\x20requires\x20you\x20to\x20use\x20help\x20before\x20answering.')),void console[bo(0x2de)]();const U=J[bo(0x2b9)][K];J[bo(0x488)]||(J['interactions']=[]),J['interactions'][bo(0x407)]({'ts':new Date()[bo(0x456)](),'q':K,'type':U?bo(0x2b6):bo(0x3a0),'input':Q,'result':U?'changed\x20from\x20'+U:bo(0x2eb)}),J[bo(0x2b9)][K]=Q,J[bo(0x352)]=K,a0Y(J),console[bo(0x2de)](a0L[bo(0x25d)](bo(0x3fa)));const V=J[bo(0x376)]['token'];if(V&&bo(0x45c)!==J[bo(0x376)][bo(0x261)]){const Y=(a0V()[bo(0x2b2)]||bo(0x453))+bo(0x3af)+J['session'][bo(0x261)]+bo(0x238),Z={};Z[bo(0x33f)]=bo(0x384);const a0={};a0[bo(0x3f4)]=V,a0[bo(0x421)]=K,a0['answer']=Q,fetch(Y,{'method':bo(0x3de),'headers':Z,'body':JSON[bo(0x274)](a0),'signal':AbortSignal[bo(0x30b)](0x2e0*-0x7+-0x2562+0x4d0a)})[bo(0x35c)](()=>{});}const W=Object[bo(0x231)](J[bo(0x2b9)])[bo(0x24c)],X=J[bo(0x376)]['questionCount'];if(a0a7('Q'+K+':\x20'+Q+bo(0x4bc)+W+'/'+X+bo(0x232)),bo(0x45c)===J['session'][bo(0x261)]&&L[bo(0x447)]){const a1=[bo(0x47d),'Solid\x20pick.',bo(0x4af),'Exactly.',bo(0x432)],a2=[bo(0x323),bo(0x49d),bo(0x245)],a3=Q===L[bo(0x447)]?a1:a2,a4=a3[Math[bo(0x3fd)](Math['random']()*a3[bo(0x24c)])];console['log'](a0L['gray']('\x20\x20\x20\x20ยท\x20'+a4));}if(K<J[bo(0x29f)]['length']){const a5=J[bo(0x29f)][K];J['_lastQ']=a5[bo(0x2d9)],a0Y(J),R(a5,J[bo(0x2b9)][a5[bo(0x2d9)]]);}else W>=J['questions'][bo(0x24c)]&&(console[bo(0x2de)](),console[bo(0x2de)](a0L[bo(0x227)]['bold'](bo(0x36d))),console[bo(0x2de)](),bo(0x45c)===J[bo(0x376)]['examId']?(console[bo(0x2de)](a0L[bo(0x27c)](bo(0x25a))+a0L[bo(0x256)][bo(0x367)](bo(0x38f))+a0L[bo(0x27c)]('\x20to\x20see\x20your\x20results!')),console[bo(0x2de)]()):console['log'](a0L[bo(0x27c)]('\x20\x20Use:\x20exam\x20review\x20ยท\x20exam\x20submit')));}),B[ba(0x223)]('review')[ba(0x491)](ba(0x205))[ba(0x362)](()=>{const bq=ba;a0a6('exam\x20review');const F=a0X();if(!F)return void a0a8('No\x20exam\x20in\x20progress.');a0ac(bq(0x258)+F[bq(0x376)][bq(0x396)]),M(),console[bq(0x2de)]();let G='\x20\x20';const H=Array[bq(0x290)](F['bookmarks'])?F['bookmarks']:[];for(const Q of F['questions']){const U=F[bq(0x2b9)][Q[bq(0x2d9)]],V=H[bq(0x3fc)](Q[bq(0x2d9)]),W=V?'โ
':'\x20';G+=U?V?a0L[bq(0x256)]['yellow']('Q'+String(Q['number'])[bq(0x221)](-0x1d00+0x7*0x4b1+-0x1*0x3d5)+W+'['+U+']\x20'):a0L['green']('Q'+String(Q['number'])[bq(0x221)](0x397*-0x2+-0x10ce+-0x2*-0xbff)+'\x20['+U+']\x20\x20'):V?a0L[bq(0x256)]['yellow']('Q'+String(Q[bq(0x2d9)])['padStart'](-0x40+0xa18*-0x2+0x1472)+W+bq(0x4a1)):a0L[bq(0x3f1)]('Q'+String(Q[bq(0x2d9)])[bq(0x221)](0x2b*-0x47+0x935*0x3+-0x10*0xfb)+bq(0x46a)),Q[bq(0x2d9)]%(-0x8ba+0x8*-0xb0+-0x4c0*-0x3)==-0x1f*-0xe5+-0x11de+-0x9dd&&(console[bq(0x2de)](G),G='\x20\x20');}G[bq(0x2cc)]()&&console[bq(0x2de)](G);const J=Object[bq(0x231)](F['answers'])[bq(0x24c)],K=F[bq(0x376)][bq(0x309)],L=K-J;if(console[bq(0x2de)](),a0ad('Answered',a0L[bq(0x227)][bq(0x256)](J+'/'+K)),L>0x1*-0x1796+0x8b4+0xf*0xfe){const X=F[bq(0x29f)][bq(0x228)](Y=>!F[bq(0x2b9)][Y['number']])[bq(0x35a)](Y=>Y['number'])['join'](',\x20');a0ad('Unanswered',a0L['yellow'](L+bq(0x3cd)+X+')'));}H[bq(0x24c)]>-0x1dd1*0x1+0x1d2c+-0x3*-0x37&&a0ad('Flagged\x20for\x20review',a0L[bq(0x256)][bq(0x3f1)]('โ
\x20'+H[bq(0x24c)])+a0L['gray']('\x20('+H[bq(0x35a)](Y=>'Q'+Y)[bq(0x401)](',\x20')+')')),console[bq(0x2de)](),H['length']>0x263e*-0x1+-0x1*0x1375+0x39b3&&console['log'](a0L[bq(0x25d)](bq(0x410))+a0L['white'](bq(0x26e)+H[0x1c*-0x14e+-0x13f*-0x3+-0x5*-0x68f])),console[bq(0x2de)](a0L[bq(0x25d)](bq(0x2c3))+a0L[bq(0x256)][bq(0x367)](bq(0x38f))+a0L[bq(0x25d)](bq(0x2f1)));}),B['command'](ba(0x2aa))[ba(0x491)](ba(0x437))['action'](async J=>{const br=ba;a0a6(br(0x461)+(J||''));const K=a0X();if(K&&br(0x47a)===J&&(K[br(0x3e5)]=!(-0x163*0x2+-0x1a49+0x1d0f),a0Y(K)),!K)return void a0a8(br(0x35d));const L=Object[br(0x231)](K['answers'])[br(0x24c)],Q=K[br(0x376)][br(0x309)],U=Q-L;if(0x79c*0x1+0x1*0x120e+0x19aa*-0x1===L){const W=a0a0(),X=W&&new Date()>=W;return void(br(0x45c)===K[br(0x376)][br(0x261)]||X?(a0Z(),a0a9(br(0x34f)),a0aa('Type:\x20demo\x20to\x20start\x20again.')):(a0a9(br(0x43d)),a0aa(br(0x412))));}if(br(0x45c)!==K[br(0x376)][br(0x261)]){const Y=!(0x1e9c+0x1d09+-0x3ba5)===K[br(0x3e5)],Z=a0a0(),a0=!!(Z&&new Date()>=Z);if(!Y&&!a0){if(console[br(0x2de)](),console[br(0x2de)](a0L[br(0x21c)]('\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ')),console['log'](a0L['bold'][br(0x21c)](br(0x291))),console[br(0x2de)](a0L['red'](br(0x1f6))),console[br(0x2de)](),console[br(0x2de)](a0L[br(0x27c)](br(0x33b)+a0L[br(0x256)][br(0x227)](L+'/'+Q)+br(0x3ed))),U>0x1*-0x110e+0x1*0x1ce1+-0xbd3){const a2=K['questions'][br(0x228)](a3=>!K[br(0x2b9)][a3[br(0x2d9)]])['map'](a3=>'Q'+a3[br(0x2d9)])[br(0x401)](',\x20');console[br(0x2de)](a0L[br(0x3f1)]('\x20\x20'+U+br(0x48c)+a2));}const a1=Array[br(0x290)](K[br(0x2ec)])?K[br(0x2ec)]:[];return a1[br(0x24c)]>0x1c6*0x7+-0x1406*0x1+0x79c&&console['log'](a0L[br(0x3f1)](br(0x4ab)+a1['length']+br(0x436)+a1['map'](a3=>'Q'+a3)[br(0x401)](',\x20'))),console['log'](),console[br(0x2de)](a0L[br(0x27c)](br(0x20f))+a0L[br(0x256)][br(0x21c)]('final')+a0L[br(0x27c)]('\x20โ\x20you\x20cannot\x20change\x20them\x20after\x20submit.')),console[br(0x2de)](),console['log'](a0L[br(0x256)][br(0x27c)](br(0x39a))+a0L['bold'][br(0x367)](br(0x2f9))),console[br(0x2de)](a0L['white'](br(0x2ef))+a0L[br(0x367)](br(0x2b0))+a0L[br(0x25d)]('\x20\x20or\x20\x20')+a0L[br(0x367)]('back')),void console['log']();}Y&&(delete K[br(0x3e5)],a0Y(K));}if(console[br(0x2de)](),br(0x45c)===K[br(0x376)][br(0x261)]){try{N(-0xffd*0x1+0x1bf4+0x3*-0x3fd,a0ag(br(0x281))),await q(0x1840+-0x1*0xe8d+0x3b*-0x25);let a3=-0x24fe+-0xb3*-0xd+0x1be7;const a4=[],a5={};for(const ah of K[br(0x29f)]){const ai=ah[br(0x255)]||br(0x404),aj={};aj[br(0x233)]=0x0,aj[br(0x211)]=0x0,(a5[ai]||(a5[ai]=aj),a5[ai][br(0x211)]++);const ak=K['answers'][ah[br(0x2d9)]];ak&&ah[br(0x447)]&&ak===ah[br(0x447)]?(a3++,a5[ai][br(0x233)]++):a4['push'](ah[br(0x2d9)]);}a4[br(0x242)]((al,am)=>al-am),N(0xdce+0x1a4a+-0x2a*0xf2,a0ag(br(0x250))),console[br(0x2de)]();const a6=new Date(K[br(0x376)][br(0x3df)])[br(0x34c)](),a7=Math[br(0x2e6)](-0x4b1*0x2+0xa93+-0x131,Math[br(0x3ce)]((Date[br(0x203)]()-a6)/(0x186f*0x1+-0x9*-0x25+-0x15d4))),a8=Math[br(0x3fd)](a7/(-0x121*-0x17+0x15*-0x77+-0xff8*0x1))+'m\x20'+a7%(0x59*-0x15+0x1844+-0x10bb)+'s',a9=[...K[br(0x29f)]],aa={...K[br(0x2b9)]},ab=D(K);a0Z();const ac=Math[br(0x3ce)](a3/Q*(0x1*0x2333+-0x1e64+-0x46b)),ad=a0V(),ae={};ae[br(0x33f)]='application/json',(fetch('https://practice.icoa2026.au/api/icoa/demo-stats',{'method':br(0x3de),'headers':ae,'body':JSON[br(0x274)]({'type':br(0x485),'score':a3,'total':Q,'lang':ad['language']||'en','help_used':ab[br(0x43c)],'help_max':ab[br(0x2e6)],'tokens_used':0x0,'solved':ac>=0xd01+-0x83*-0x39+-0x7a*0x58?-0x1f6*0x1+-0x281*-0x1+0x1*-0x8a:-0x1*-0x2032+-0x19ba+-0x1*0x678,'timestamp':new Date()[br(0x456)](),'interactions':K[br(0x488)]||[]}),'signal':AbortSignal[br(0x30b)](0xb4b+0x24a1+-0x719*0x4)})[br(0x35c)](()=>{}),console['log'](),console['log'](a0L[br(0x367)](br(0x26a))),console['log'](),console['log'](a0L[br(0x256)][br(0x27c)](br(0x295))),console[br(0x2de)](a0L[br(0x256)][br(0x27c)]('\x20\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโ')),console[br(0x2de)](a0L[br(0x256)]['white'](br(0x251))),console[br(0x2de)](a0L[br(0x256)][br(0x27c)](br(0x30a))),console['log'](a0L[br(0x256)][br(0x27c)]('\x20\x20\x20โโโโโโโโโโโโโโโโโโโโโโโ\x20\x20โโโ')),console[br(0x2de)](a0L[br(0x256)]['white'](br(0x45f))),console['log'](),console['log'](a0L[br(0x256)]('\x20\x20\x20'+a0ag(br(0x346))+':\x20'+a3+'/'+Q+'\x20('+ac+'%)')),console['log'](a0L[br(0x256)](br(0x360)+(ac>=0x269c+0x197+-0x27f7?a0L['green'](a0ag(br(0x40f))):a0L[br(0x21c)](a0ag(br(0x2a1)))))),console[br(0x2de)](a0L['gray'](br(0x399)+a8)),console[br(0x2de)](),console[br(0x2de)](a0L[br(0x3f1)](br(0x3cc))),console[br(0x2de)](a0L[br(0x25d)](br(0x32d))),console[br(0x2de)](),console['log'](a0L[br(0x367)](br(0x26a))),await q(0x1218+-0x1732*-0x1+-0x1d92));const af=Object[br(0x3b7)](a5);if(af['length']>0x87d+-0x1256+0x9d9){console['log'](),console[br(0x2de)](a0L[br(0x256)][br(0x27c)]('\x20\x20By\x20category'));const al=0xf*0x256+-0x679+-0x6d*0x43;for(const [am,an]of af){const ao=an['total']>0x4d*-0x51+-0x255b*-0x1+-0xcfe?Math[br(0x3ce)](an[br(0x233)]/an[br(0x211)]*(0x28*0x85+-0x9*-0x1f0+-0x25d4)):-0x1843+-0x3f*-0x93+-0xbea,ap=an[br(0x211)]>0x21*0xc5+-0x1161+0x13*-0x6c?Math[br(0x3ce)](an[br(0x233)]/an[br(0x211)]*al):-0x207*-0xb+0x78c+-0x1dd9,aq=al-ap,ar=ao>=0x2*-0x371+-0x1aff+0x2231*0x1?a0L[br(0x227)]:ao>=0x1432+0x3*-0xcf7+0x12e5?a0L[br(0x3f1)]:a0L[br(0x21c)],as=ar('โ'[br(0x428)](ap))+a0L[br(0x25d)]('โ'[br(0x428)](aq));console['log'](br(0x340)+a0L['white'](am[br(0x1fc)](-0x202*-0x13+-0x15a5+-0x106d))+'\x20'+as+'\x20\x20'+ar(an[br(0x233)]+'/'+an['total'])+a0L['gray']('\x20('+ao+'%)'));}console[br(0x2de)](),console[br(0x2de)](a0L[br(0x367)](br(0x4ad)));}const ag=a0a2(a3,Q);if(a3===ag[br(0x2b1)]&&ag[br(0x3b0)]>-0xcb3*-0x2+0x1*0x1547+0x67*-0x74&&a3>0x2*0x53f+-0x1b*0x24+-0x359*0x2&&(console[br(0x2de)](),console[br(0x2de)](a0L[br(0x256)][br(0x227)](br(0x293)))),a4[br(0x24c)]>-0x123+0x446*0x8+0x210d*-0x1){const at=a9[br(0x228)](au=>a4[br(0x3fc)](au[br(0x2d9)]))[br(0x35a)](au=>({...au}));a0a3(at);}else a0a5();if(await q(0x1683+0x10e1+-0xdd6*0x2),a4[br(0x24c)]>0x1*-0x19d3+-0x2053+-0x12*-0x33b){console[br(0x2de)](),console[br(0x2de)](a0L[br(0x3f1)]('\x20\x20'+a4[br(0x24c)]+'\x20'+a0ag('incorrectIntro'))),console[br(0x2de)]();for(const au of a4){const av=a9[br(0x438)](ay=>ay['number']===au);if(!av)continue;const aw=aa[au],ax=av[br(0x447)];console[br(0x2de)](a0L[br(0x27c)](br(0x468)+au+'.\x20'+av[br(0x413)])),aw?console['log'](a0L['red']('\x20\x20\x20\x20'+a0ag(br(0x4be))+':\x20'+aw+'.\x20'+av[br(0x32c)][aw])):console[br(0x2de)](a0L['yellow'](br(0x340)+a0ag(br(0x4be))+br(0x3ad))),ax&&console['log'](a0L[br(0x227)](br(0x340)+a0ag('correct')+':\x20'+ax+'.\x20'+av[br(0x32c)][ax])),av[br(0x38b)]&&console[br(0x2de)](a0L['gray'](br(0x2c2)+av['explanation'])),console[br(0x2de)]();}}else console[br(0x2de)](),console['log'](a0L[br(0x227)]('\x20\x20'+a0ag(br(0x4bb))));await q(0x7e4+-0xdff+0xdeb),console['log'](),console['log'](a0L['cyan']('\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ')),console[br(0x2de)](a0L[br(0x27c)]('\x20\x20'+a0ag('nextLabel')+'\x20')+a0L[br(0x256)]['green'](br(0x3ff))+a0L[br(0x25d)](br(0x278)+a0ag('ai4ctfDesc'))),console[br(0x2de)](a0L[br(0x25d)]('\x20\x20'+a0ag(br(0x1f9)))),console[br(0x2de)](a0L[br(0x367)](br(0x4ad))),console[br(0x2de)]();}catch(ay){console['log'](),a0a8(ay['message']);}return;}const V=K[br(0x376)][br(0x3f4)];try{let az;if(N(-0x243*-0xd+0x1cfc*-0x1+-0x6b,br(0x2a0)),V){const aJ={};aJ['Content-Type']=br(0x384);const aK={};aK[br(0x3ff)]=0x0,aK[br(0x329)]=0x0;const aL=a0V()['ctfdUrl']||br(0x453),aM=await fetch(aL+br(0x3af)+K['session'][br(0x261)]+br(0x2e7),{'method':br(0x3de),'headers':aJ,'body':JSON[br(0x274)]({'token':V,'answers':K[br(0x2b9)],'interactions':K[br(0x488)]||[],'aiUsage':K[br(0x333)]||aK,'bookmarks':K['bookmarks']||[]}),'signal':AbortSignal[br(0x30b)](0x5242+-0x22*-0x27+-0x1cd8)});if(!aM['ok']){const aN=await aM[br(0x39d)]()[br(0x35c)](()=>({'message':br(0x208)}));throw new Error(aN[br(0x38a)]||br(0x208));}az=(await aM[br(0x39d)]())[br(0x29a)];}else{const aO=E();if(!aO)return;az=await aO[br(0x27f)](K[br(0x376)][br(0x261)],K['answers']);}N(-0x1837+-0x1*-0x2074+0x1d*-0x47,'Grading...'),await q(-0x1a45+-0x540+0x20b1),N(-0xda*-0x17+0x45d+0xa3*-0x25,br(0x357)),console[br(0x2de)]();const aA={};aA[br(0x3ff)]=0x0,aA['ctf4ai']=0x0;const aB={'answered':Object['keys'](K[br(0x2b9)])[br(0x24c)],'total':K[br(0x376)][br(0x309)],'bookmarks':Array['isArray'](K['bookmarks'])?K['bookmarks'][br(0x24c)]:0xa6c+0xc50*0x1+-0x16bc,'help':D(K),'aiUsage':K[br(0x333)]||aA,'interactions':(K[br(0x488)]||[])[br(0x24c)],'durationMin':K[br(0x376)]['durationMinutes']||-0x16ed*0x1+0x1e*0x18+0x10f*0x13,'confirmedAt':K[br(0x376)][br(0x408)]||K[br(0x376)][br(0x3df)],'examId':K[br(0x376)][br(0x261)]};a0Z();const aC=Math[br(0x2e6)](-0x1b9f+0x2474+-0x7*0x143,Math[br(0x3ce)]((Date[br(0x203)]()-new Date(aB['confirmedAt'])[br(0x34c)]())/(0xf35*0x1+-0x65*0xd+-0x62c))),aD=Math[br(0x3fd)](aC/(-0x1f*0xe0+0x1*0x11d+0x1a3f*0x1)),aE=aC%(0x120+0x2203*-0x1+0x211f);console[br(0x2de)](),console[br(0x2de)](a0L['cyan'](br(0x2c5))),console[br(0x2de)](),console['log'](a0L[br(0x256)][br(0x27c)]('\x20\x20\x20โโโ\x20โโโโโโโ\x20โโโโโโโ\x20\x20โโโโโโ')),console[br(0x2de)](a0L[br(0x256)][br(0x27c)](br(0x3d3))),console[br(0x2de)](a0L[br(0x256)][br(0x27c)]('\x20\x20\x20โโโโโโ\x20\x20\x20\x20\x20โโโ\x20\x20\x20โโโโโโโโโโโ')),console['log'](a0L['bold'][br(0x27c)](br(0x30a))),console[br(0x2de)](a0L[br(0x256)][br(0x27c)](br(0x3d6))),console[br(0x2de)](a0L[br(0x256)][br(0x27c)](br(0x45f))),console[br(0x2de)]();const aF=az[br(0x40f)]?a0L[br(0x227)][br(0x256)](br(0x2a5)):a0L[br(0x3f1)]['bold'](br(0x1f2));console[br(0x2de)](aF),console[br(0x2de)](),console[br(0x2de)](a0L['white'](br(0x2ba))),console['log'](a0L[br(0x27c)]('\x20\x20\x20exam\x20center.\x20Final\x20scoring\x20and\x20selection\x20are\x20decided\x20by')),console[br(0x2de)](a0L['white'](br(0x495))),console[br(0x2de)](),console[br(0x2de)](a0L[br(0x367)](br(0x4ad))),console[br(0x2de)](),console[br(0x2de)](a0L[br(0x256)][br(0x27c)]('\x20\x20Your\x20run'));const aG=aB[br(0x211)]-aB[br(0x29e)];console[br(0x2de)](br(0x340)+a0L[br(0x25d)](br(0x2e8))+a0L[br(0x27c)](aB[br(0x29e)]+'/'+aB[br(0x211)])+(aG>-0x1*-0x1c73+-0x167*0x2+0x1*-0x19a5?a0L[br(0x25d)]('\x20\x20ยท\x20\x20'+aG+br(0x490)):'')),console[br(0x2de)](br(0x340)+a0L[br(0x25d)](br(0x43e))+a0L[br(0x27c)](aD+'m\x20'+aE+'s')+a0L['gray']('\x20\x20of\x20'+aB[br(0x3ee)]+br(0x3e9))),aB[br(0x2ec)]>-0x2292*-0x1+-0xbd6+0x3*-0x794&&console[br(0x2de)](br(0x340)+a0L['gray'](br(0x302))+a0L[br(0x3f1)]('โ
\x20'+aB[br(0x2ec)])),console[br(0x2de)]();const aH=az[br(0x37d)];if(aH&&Object[br(0x231)](aH)[br(0x24c)]>=0xb1*-0x17+0x3*-0x98d+0x2c90){const aP=Object['entries'](aH)[br(0x228)](([,aU])=>aU['total']>-0x1fe3+-0x1*-0xd7d+-0x1*-0x1266)[br(0x35a)](([aU,aV])=>({'cat':aU,'pct':aV[br(0x233)]/aV[br(0x211)]}))[br(0x242)]((aU,aV)=>aV[br(0x279)]-aU['pct']),aQ=Math[br(0x282)](-0x1fc+0x1*0x5f6+-0x3f8,Math[br(0x4bf)](aP['length']/(0x3e*-0x3f+-0x112a+0x206f))),aR=Math[br(0x282)](-0x6b+0x10c+0x35*-0x3,Math[br(0x4bf)](aP[br(0x24c)]/(0x253*-0x4+0x5*0x599+-0x12ae))),aS=aP[br(0x460)](-0x1e0c+-0xa57+0x2863,aQ)[br(0x35a)](aU=>aU[br(0x2d0)]),aT=aP[br(0x460)](-aR)[br(0x35a)](aU=>aU[br(0x2d0)]);aS[br(0x417)](aU=>aT[br(0x3fc)](aU))||(console[br(0x2de)](a0L[br(0x256)][br(0x27c)](br(0x32f))),console['log'](br(0x340)+a0L['green'](br(0x3c5))+a0L[br(0x27c)](aS[br(0x401)](',\x20'))),console[br(0x2de)]('\x20\x20\x20\x20'+a0L['yellow']('Room\x20to\x20grow:\x20\x20\x20')+a0L['white'](aT[br(0x401)](',\x20'))),console[br(0x2de)](a0L[br(0x25d)](br(0x20e))),console[br(0x2de)]());}console['log'](a0L['bold']['white'](br(0x451))),console[br(0x2de)](br(0x340)+a0L['gray'](br(0x2d3))+a0L[br(0x27c)](aB['help'][br(0x43c)]+'/'+aB[br(0x4b1)][br(0x2e6)])),console[br(0x2de)](br(0x340)+a0L[br(0x25d)](br(0x257))+a0L[br(0x27c)](aB[br(0x333)][br(0x3ff)]+br(0x47b))+a0L[br(0x25d)](br(0x458))+a0L[br(0x27c)](aB[br(0x333)]['ctf4ai']+br(0x418))),console[br(0x2de)](),console[br(0x2de)](a0L[br(0x367)](br(0x4ad))),console[br(0x2de)](),console['log'](a0L[br(0x256)]['white'](br(0x319))),console[br(0x2de)](a0L[br(0x25d)](br(0x213))),console[br(0x2de)](a0L[br(0x25d)](br(0x4b9))+a0L[br(0x27c)](br(0x4b7))+a0L[br(0x25d)]('\x20is\x20free,\x20unlimited\x20practice.')),console[br(0x2de)](a0L['gray']('\x20\x20ยท\x20Reference\x20guides\x20for\x2038\x20tools:\x20')+a0L[br(0x27c)](br(0x3f7))),console['log']();const {showCToBUpgradePrompt:aI}=await import(br(0x320));aI(aB[br(0x261)],az['passed']),console['log'](a0L[br(0x3f1)](br(0x47f))),console[br(0x2de)](a0L[br(0x367)][br(0x466)](br(0x481))),console[br(0x2de)](),console[br(0x2de)](a0L['cyan']('\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ')),console[br(0x2de)]();}catch(aU){console[br(0x2de)](),a0a8(aU['message']);}}),B['command'](ba(0x392))[ba(0x491)](ba(0x260))[ba(0x362)](async F=>{const bs=ba;a0a6('exam\x20result\x20'+(F||''));const G=E();if(!G)return;if(!F){const J=a0X();if(!J)return void a0a8('Specify\x20exam\x20ID:\x20exam\x20result\x20<id>');F=J[bs(0x376)][bs(0x261)];}const H=a0ae('Loading\x20result...');H[bs(0x455)]();try{const K=await G['getResult'](F);H[bs(0x440)](bs(0x42a)),console[bs(0x2de)](),a0ac(K[bs(0x396)]),a0ad(bs(0x470),a0L[bs(0x256)](K[bs(0x346)]+'/'+K[bs(0x211)])),a0ad('Percentage',a0L[bs(0x256)](K['percentage']+'%')),a0ad(bs(0x2be),K[bs(0x40f)]?a0L['green'][bs(0x256)](bs(0x3d7)):a0L['red']['bold'](bs(0x2a8))),a0ad(bs(0x426),K['submittedAt']),console[bs(0x2de)]();}catch(L){H['fail']('Failed\x20to\x20load\x20result'),a0a8(L['message']);}}),B[ba(0x223)](ba(0x215))[ba(0x491)](ba(0x383))[ba(0x362)](async H=>{const bt=ba;a0a6('exam\x20token\x20'+H);const {getRealExamState:J,saveExamState:K,clearExamState:L}=await import('../lib/exam-state.js'),Q=J();if(Q){const a0=Q[bt(0x376)][bt(0x3f4)],a1=a0a0(),a2=!!a0&&a0[bt(0x2cc)]()['toUpperCase']()!==H['trim']()[bt(0x1ff)](),a3=!!a1&&a1['getTime']()+(0x1*-0x4f903+0x28f72a+-0x886e7)<Date[bt(0x203)]();if(a2&&a3)return L(),console[bt(0x2de)](),console['log'](a0L['gray'](bt(0x3f5))),console[bt(0x2de)](),console[bt(0x2de)](a0L[bt(0x27c)](bt(0x22b))+a0L['bold'][bt(0x367)](bt(0x286)+H)),void console[bt(0x2de)]();if(!a0&&H)return Q[bt(0x376)][bt(0x3f4)]=H['trim'](),K(Q),console[bt(0x2de)](),console['log'](a0L['green'](bt(0x43a))),console[bt(0x2de)](a0L[bt(0x25d)]('\x20\x20\x20\x20You\x20can\x20now\x20use:\x20')+a0L[bt(0x27c)](bt(0x2d5))+a0L['gray'](bt(0x24f))+a0L[bt(0x27c)]('exam\x20submit')),console[bt(0x2de)](),console[bt(0x2de)](a0L[bt(0x256)]['white']('\x20\x20Continue\x20this\x20exam:')),console[bt(0x2de)](a0L[bt(0x25d)](bt(0x2c2))+a0L[bt(0x256)][bt(0x367)](bt(0x3ac))+a0L[bt(0x25d)](bt(0x218))),console[bt(0x2de)](a0L['gray'](bt(0x2c2))+a0L[bt(0x256)]['cyan']('exam\x20review')+a0L['gray']('\x20\x20\x20see\x20progress\x20+\x20flagged\x20items')),void console['log']();const a4=Object[bt(0x231)](Q['answers'])[bt(0x24c)],a5=Q['session'][bt(0x309)],a6=a0a0(),a7=a6?Math[bt(0x2e6)](0xc00+-0x43*-0x91+0x2a1*-0x13,Math['round']((a6[bt(0x34c)]()-Date[bt(0x203)]())/(-0x1baeb+-0x2255*-0xa+0x14df9))):null;return console[bt(0x2de)](),console[bt(0x2de)](a0L['yellow'](bt(0x484))),console[bt(0x2de)](),console['log'](a0L[bt(0x25d)](bt(0x27b))+a0L[bt(0x27c)](Q['session'][bt(0x396)])),a0&&console[bt(0x2de)](a0L[bt(0x25d)](bt(0x2a3))+a0L[bt(0x27c)](a0)+(a0===H?a0L[bt(0x227)](bt(0x423)):a0L[bt(0x3f1)]('\x20\x20(different\x20from\x20the\x20one\x20you\x20typed)'))),console['log'](a0L[bt(0x25d)](bt(0x3b8))+a0L[bt(0x27c)](a4+'/'+a5)),null!==a7&&console['log'](a0L[bt(0x25d)]('\x20\x20Time:\x20\x20\x20\x20')+a0L['white'](a7+bt(0x3a3))),console['log'](),console['log'](a0L[bt(0x256)][bt(0x27c)](bt(0x2ff))),console[bt(0x2de)](a0L[bt(0x25d)](bt(0x2c2))+a0L[bt(0x256)]['cyan'](bt(0x3ac))+a0L[bt(0x25d)]('\x20\x20\x20\x20\x20\x20resume\x20at\x20any\x20question')),console[bt(0x2de)](a0L[bt(0x25d)]('\x20\x20\x20\x20โ\x20')+a0L[bt(0x256)][bt(0x367)]('exam\x20review')+a0L['gray'](bt(0x402))),console[bt(0x2de)](a0L['gray']('\x20\x20\x20\x20โ\x20')+a0L['bold']['cyan'](bt(0x38f))+a0L[bt(0x25d)](bt(0x253))),a0&&a0!==H&&(console[bt(0x2de)](),console[bt(0x2de)](a0L[bt(0x3f1)]('\x20\x20Note:\x20you\x20typed\x20a\x20different\x20token.\x20Each\x20exam\x20token\x20is\x20bound\x20to')),console[bt(0x2de)](a0L[bt(0x3f1)](bt(0x336))),console['log'](),console[bt(0x2de)](a0L['gray'](bt(0x45b))),console[bt(0x2de)](a0L['gray'](bt(0x4ae))+a0L[bt(0x256)]['cyan'](bt(0x3e1))+a0L['gray']('\x20to\x20wipe\x20local\x20state,\x20then\x20re-enter\x20the\x20new\x20token.'))),void console[bt(0x2de)]();}const {isNativeWindowsCmd:U}=await import('../lib/platform.js');if(!U()&&!a0ak())return console['log'](),a0a9('Pre-exam\x20setup\x20required\x20before\x20entering\x20a\x20token.'),console[bt(0x2de)](a0L[bt(0x25d)]('\x20\x20โ\x20')+a0L[bt(0x256)]['cyan'](bt(0x48d))),console[bt(0x2de)](a0L[bt(0x25d)](bt(0x216))+a0L[bt(0x256)][bt(0x367)](bt(0x25c))+a0L[bt(0x25d)](bt(0x206))),void console[bt(0x2de)]();const V={};V['UA']='uk',V['PE']='es',V['CN']='zh',V['AU']='en',V['JP']='ja',V['KR']='ko',V['BR']='pt',V['SA']='ar',V['FR']='fr',V['DE']='de',V['IN']='hi',V['ID']='id',V['TH']='th',V['VN']='vi',V['TR']='tr',V['RU']='ru',V['EG']='ar',V['HT']='ht',V['PH']='en',V['MY']='en',V['SG']='en',V['ZA']='en',V['KE']='sw',V['TZ']='sw';const W=V[H[bt(0x2cc)]()['substring'](0x18e9+0xd*-0x216+-0x1*-0x235,-0x12d0*0x1+-0x2065*0x1+0x3337)[bt(0x1ff)]()],X=a0V(),Y=X['ctfdUrl']||bt(0x453),Z=W||X[bt(0x28d)]||'en';W&&W!==(X[bt(0x28d)]||'en')&&a0W({'language':W}),console[bt(0x2de)](),N(0x7b1+0xe9+0x16f*-0x6,bt(0x45d));try{const a8={};a8[bt(0x33f)]='application/json';const a9=await fetch(Y+bt(0x4aa),{'method':bt(0x3de),'headers':a8,'body':JSON[bt(0x274)]({'token':H[bt(0x2cc)](),'deviceHash':a0ah(),'lang':Z}),'signal':AbortSignal[bt(0x30b)](0x16e7+-0x5c9+-0x2*-0xaf9)});if(!a9['ok']){N(-0x1e1e+0xac9+0x1355,''),console[bt(0x2de)]();const ah=await a9['json']()['catch'](()=>({'message':'Invalid\x20token'}));return a0a8(ah['message']||'Invalid\x20exam\x20token'),void a0aa(bt(0x389));}N(0xa6c+-0x575+0x1*-0x4d9,'Loading\x20exam...');const aa=await a9[bt(0x39d)](),{session:ab,questions:ac,languages:ad}=aa[bt(0x29a)];N(0xec9+-0x16*-0x147+0xb3*-0x3d,'Preparing...'),await q(0x121*0x1f+-0x6a*0xa+-0x1e13*0x1),N(-0x1*0x1a6b+-0x69d*-0x1+-0x16*-0xeb,'Ready!'),console[bt(0x2de)](),console[bt(0x2de)](),a0ac(ab[bt(0x396)]),console[bt(0x2de)](),a0ad(bt(0x2bc),ab[bt(0x309)]+bt(0x3c2)),a0ad('Duration',ab[bt(0x25f)]+bt(0x22c)),a0ad('Total',(ab[bt(0x416)]||0x737*0x1+-0x1*-0x14d8+-0x1b79*0x1)+bt(0x394)),a0ad(bt(0x2e9),(ab[bt(0x31f)]||0x1606+-0x2587+0xfcc)+bt(0x341)),console[bt(0x2de)](),console[bt(0x2de)](a0L[bt(0x27c)](bt(0x298))),console[bt(0x2de)](a0L[bt(0x25d)](bt(0x331))),console['log'](a0L['gray'](bt(0x435))),console[bt(0x2de)](a0L[bt(0x25d)]('\x20\x20\x20\x20AI:\x20\x20\x20\x20\x20\x2025K\x20AI4CTF\x20+\x2025K\x20CTF4AI\x20tokens')),ad&&ad['length']>-0x1d57+-0x1*-0x949+0x140f&&console[bt(0x2de)](a0L[bt(0x25d)]('\x20\x20\x20\x20Lang:\x20\x20\x20\x20'+ad[bt(0x401)](',\x20')+bt(0x48b))),console[bt(0x2de)](),console['log'](a0L[bt(0x3f1)](bt(0x20a))),console[bt(0x2de)](a0L[bt(0x25d)](bt(0x268))),console[bt(0x2de)](a0L['gray'](bt(0x236))),console[bt(0x2de)](a0L[bt(0x25d)]('\x20\x20\x20\x20โข\x20You\x20may\x20exit\x20and\x20resume\x20with\x20the\x20same\x20token')),console['log'](),await new Promise(ai=>{const bu=bt;process['stdout']['write'](a0L[bu(0x256)][bu(0x3f1)](bu(0x380)));const aj=!!process[bu(0x2b3)]['isTTY']&&process[bu(0x2b3)][bu(0x462)];process[bu(0x2b3)][bu(0x36b)]&&process['stdin'][bu(0x284)]&&process[bu(0x2b3)][bu(0x284)](!(0x733+-0x4a*0x87+0x1fd4));const ak=al=>{const bv=bu,am=al['toString']();(am[bv(0x3fc)]('\x0a')||am[bv(0x3fc)]('\x0d'))&&(process[bv(0x2b3)][bv(0x43f)](bv(0x29a),ak),process[bv(0x2b3)]['isTTY']&&process[bv(0x2b3)][bv(0x284)]&&process['stdin'][bv(0x284)](aj),process[bv(0x3da)][bv(0x2b4)]('\x0a'),ai());};process[bu(0x2b3)]['on'](bu(0x29a),ak),process[bu(0x2b3)][bu(0x285)]();});const ae=new Date()[bt(0x456)]();ab[bt(0x408)]=ae,ab['token']=H['trim']();try{const ai=Date['now'](),aj=await fetch(Y+bt(0x3b3),{'method':'GET','signal':AbortSignal[bt(0x30b)](-0xf83*0x2+0x1749+-0x11*-0x125)});if(aj['ok']){const ak=await aj[bt(0x39d)](),al=ak?.[bt(0x29a)]?.[bt(0x35b)];if('number'==typeof al&&al>0x1*0x16cf+0xd*0x95+-0x1e60){const am=(ai+Date[bt(0x203)]())/(0xcdd+0x1b36+-0x2811);ab[bt(0x32e)]=Math['round'](al-am),ab[bt(0x4b0)]=al+(0x230e+0x89*-0x1+-0x83*0x43)*ab[bt(0x25f)]*(0x1f35+-0xc*0x310+0x973);}}}catch{}const af={};af[bt(0x3ff)]=0x0,af[bt(0x329)]=0x0;const ag={};ag[bt(0x376)]=ab,ag[bt(0x29f)]=ac,ag[bt(0x2b9)]={},ag[bt(0x488)]=[],ag['aiUsage']=af,(a0Y(ag),console['log'](),a0a7('Exam\x20started!\x20Timer\x20is\x20running.'),a0ad('Time\x20Remaining',ab['durationMinutes']+bt(0x3fb)),ac['length']>-0xb6a*0x1+0x1894+-0xd2a&&R(ac[0x1afa+0x248c*-0x1+-0x62*-0x19]));}catch(an){console[bt(0x2de)](),bt(0x2e5)===an['name']?a0a8(bt(0x2bf)):a0a8(an[bt(0x38a)]||'Failed\x20to\x20start\x20exam');}}),B['command']('reset',{'hidden':!(0x1dad+0x67e+-0x242b)})['description']('Abandon\x20current\x20exam\x20session\x20and\x20wipe\x20local\x20state\x20(recovery\x20only)')[ba(0x362)](async()=>{const bw=ba;a0a6(bw(0x3e1));const {getRealExamState:F,clearExamState:G}=await import(bw(0x2e2)),H=F();if(!H)return console[bw(0x2de)](),console[bw(0x2de)](a0L[bw(0x25d)](bw(0x446))),void console['log']();const J=H[bw(0x376)][bw(0x3f4)];if(console['log'](),console['log'](a0L[bw(0x3f1)](bw(0x420))),console[bw(0x2de)](a0L[bw(0x25d)](bw(0x499))+a0L[bw(0x27c)](H['session'][bw(0x396)])),J&&console[bw(0x2de)](a0L[bw(0x25d)](bw(0x42b))+a0L['white'](J)),console['log'](a0L[bw(0x25d)](bw(0x230))),console['log'](a0L[bw(0x25d)](bw(0x4b6))),console[bw(0x2de)](),console['log'](a0L[bw(0x25d)]('\x20\x20Type\x20')+a0L[bw(0x256)][bw(0x367)](bw(0x38d))+a0L['gray'](bw(0x400))),bw(0x38d)!==(await new Promise(K=>{const bx=bw;process[bx(0x3da)][bx(0x2b4)](bx(0x3f9));const L=Q=>{const by=bx,U=Q[by(0x2d1)]()[by(0x2cc)]();(U[by(0x3fc)]('\x0a')||U[by(0x3fc)]('\x0d')||U[by(0x24c)]>0x26e*0x1+0x1ea1+0x210f*-0x1)&&(process[by(0x2b3)][by(0x43f)]('data',L),K(U[by(0x419)](/[\r\n]/g,'')));};process[bx(0x2b3)]['on'](bx(0x29a),L),process['stdin']['resume']();}))['toLowerCase']())return console[bw(0x2de)](a0L[bw(0x25d)](bw(0x496))),void console[bw(0x2de)]();G(),console['log'](),console[bw(0x2de)](a0L[bw(0x227)]('\x20\x20โ\x20Exam\x20state\x20cleared.')),console[bw(0x2de)](a0L[bw(0x25d)](bw(0x390))+a0L[bw(0x256)][bw(0x367)]('exam\x20<token>')),console[bw(0x2de)]();}),B[ba(0x223)](ba(0x4b7))[ba(0x491)](ba(0x22d))[ba(0x362)](async()=>{const bz=ba;a0a6(bz(0x3d1)),P(bz(0x300));const {pickDemoQuestions:F,getLocalizedDemoSession:G,DEMO_PICK_SIZE:H,DEMO_POOL_SIZE:J}=await import(bz(0x471));a0V()['demoIntroSeen']||(await(async function(){const bA=bz;let Z=!(-0x18+-0x1*0x1ea1+0x1eba);const a0=process[bA(0x2b3)],a1=()=>{Z=!(0xe3c+0x111e+0x1*-0x1f5a);},a2=a0[bA(0x36b)]&&'function'==typeof a0[bA(0x284)];a2&&(a0[bA(0x284)](!(-0x2c0+-0x9a2*0x1+-0xc62*-0x1)),a0[bA(0x285)](),a0['once']('data',a1));const a3=async a4=>{let a5=-0x4eb*-0x6+0x10ff+0x94d*-0x5;for(;a5<a4&&!Z;)await q(-0xa81*0x1+-0x296+0xd67),a5+=0x14b0+0xf32+0x2*-0x11c9;};try{if(console['clear'](),console['log'](),console[bA(0x2de)](a0L['gray'](bA(0x360))+a0L[bA(0x3c1)]('(press\x20any\x20key\x20to\x20skip)')),console['log'](),console[bA(0x2de)](a0L[bA(0x256)][bA(0x27c)](bA(0x295))),console[bA(0x2de)](a0L['bold']['white'](bA(0x3d3))),console[bA(0x2de)](a0L[bA(0x256)][bA(0x27c)](bA(0x251))),console[bA(0x2de)](a0L[bA(0x256)]['white'](bA(0x30a))),console[bA(0x2de)](a0L['bold'][bA(0x27c)]('\x20\x20\x20โโโโโโโโโโโโโโโโโโโโโโโ\x20\x20โโโ')),console['log'](a0L[bA(0x256)]['white'](bA(0x45f))),console['log'](),console[bA(0x2de)](a0L['bold'][bA(0x3f1)](bA(0x202))),console['log'](a0L[bA(0x25d)](bA(0x3c8))),await a3(0x487*0x3+0x110a+-0x14db),Z)return;if(console[bA(0x2de)](),console[bA(0x2de)](a0L['white'](bA(0x3b5))),console[bA(0x2de)](),await a3(-0x1*0x2666+0x559+0x2365),Z)return;if(console[bA(0x2de)](a0L[bA(0x367)](bA(0x3f0))+a0L[bA(0x27c)](bA(0x2af))+a0L[bA(0x25d)](bA(0x2bd))),await a3(-0x8b5+0x2598+-0x53b*0x5),Z)return;if(console[bA(0x2de)](a0L['green']('\x20\x20\x20\x20\x20โฃ\x20')+a0L['white'](bA(0x222))+a0L[bA(0x25d)](bA(0x4b8))),await a3(0x1*-0x1c4f+-0x10c8+0x35*0xe7),Z)return;if(console[bA(0x2de)](a0L[bA(0x21c)](bA(0x3f0))+a0L[bA(0x27c)](bA(0x23b))+a0L[bA(0x25d)](bA(0x244))),await a3(-0x2020+-0x64a+0x29ee),Z)return;if(console[bA(0x2de)](),console[bA(0x2de)](a0L[bA(0x256)]['white']('\x20\x20\x20Your\x20terminal\x20')+a0L[bA(0x256)][bA(0x3f1)]('IS')+a0L[bA(0x256)][bA(0x27c)](bA(0x33e))),console[bA(0x2de)](),await a3(-0x139*0x1d+-0x1d1*0x6+0x1*0x3437),Z)return;if(console[bA(0x2de)](a0L['gray'](bA(0x32a))),await a3(-0x39b+0x24d6+-0x1fab),Z)return;if(console['log'](a0L[bA(0x25d)](bA(0x210))),await a3(-0x1d8*0x2+-0x266b+0x1*0x2bab),Z)return;if(console[bA(0x2de)](a0L[bA(0x25d)](bA(0x370))),await a3(0x1*0x1bfa+0x2579+0x3e53*-0x1),Z)return;console[bA(0x2de)](),console[bA(0x2de)](a0L[bA(0x256)]['cyan'](bA(0x487))),await a3(0x1*-0x1f99+0x1d2c+0x120d*0x1);}finally{((()=>{const bB=bA;if(a2){a0['removeListener']('data',a1);try{a0[bB(0x284)](!(0x1*0x1c65+0x4a3*0x3+-0xd*0x341));}catch{}a0['pause']();}})()),console[bA(0x2a6)]();}}()),a0W({'demoIntroSeen':!(-0x23e9+-0xb7*0x15+0x32ec)})),console[bz(0x2de)](),console[bz(0x2de)](a0L['white'](bz(0x2f3))+a0L[bz(0x25d)](H+bz(0x42e)+J+bz(0x424))),console[bz(0x2de)](a0L[bz(0x25d)](bz(0x1f8))+a0L[bz(0x367)](bz(0x254))+a0L[bz(0x25d)](bz(0x289))+a0L[bz(0x367)]('back')+a0L[bz(0x25d)]('\x20at\x20any\x20time.')),await new Promise(Z=>{const bC=bz;process[bC(0x3da)][bC(0x2b4)](a0L[bC(0x256)][bC(0x3f1)]('\x20\x20Press\x20Enter\x20to\x20begin...\x20'));const a0=!!process[bC(0x2b3)][bC(0x36b)]&&process[bC(0x2b3)]['isRaw'];process[bC(0x2b3)][bC(0x36b)]&&process[bC(0x2b3)][bC(0x284)]&&process[bC(0x2b3)][bC(0x284)](!(-0x1930+0x47a+0x14b7));const a1=a2=>{const bD=bC,a3=a2[bD(0x2d1)]();(a3[bD(0x3fc)]('\x0a')||a3['includes']('\x0d'))&&(process['stdin'][bD(0x43f)](bD(0x29a),a1),process[bD(0x2b3)][bD(0x36b)]&&process['stdin'][bD(0x284)]&&process['stdin']['setRawMode'](a0),process[bD(0x3da)][bD(0x2b4)]('\x0a'),Z());};process[bC(0x2b3)]['on'](bC(0x29a),a1),process[bC(0x2b3)]['resume']();});const K=F(H),L=G(),{getDemoState:Q,clearExamState:U}=await import(bz(0x2e2));Q()&&U(bz(0x45c)),console[bz(0x2de)](),a0ac('ICOA\x20Demo\x20Exam\x20โ\x20Free\x20Practice'),console[bz(0x2de)](),console[bz(0x2de)](a0L[bz(0x27c)](bz(0x20b))),console[bz(0x2de)](a0L[bz(0x27c)]('\x20\x20'+H+bz(0x37c)+J+bz(0x338)));const V=a0a1();if(V['attempts']>0x1*-0x48d+-0x1a5*-0x10+-0x15c3){const Z=V['bestPercentage'],a0=Z>=-0x1*-0x1915+0x5*0x137+-0x11a*0x1c?a0L['green']:Z>=0xfe3+0xfd9+-0x1f80?a0L[bz(0x3f1)]:a0L[bz(0x27c)];console[bz(0x2de)](),console[bz(0x2de)](a0L[bz(0x25d)](bz(0x270))+a0(V[bz(0x2b1)]+'/'+H+'\x20('+Z+'%)')+a0L['gray'](bz(0x458)+V['attempts']+bz(0x492)));}console[bz(0x2de)](),console[bz(0x2de)](a0L[bz(0x25d)]('\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ')),console[bz(0x2de)](a0L['white']('\x20\x20'+a0ag(bz(0x30e)))),console[bz(0x2de)](a0L[bz(0x3f1)](bz(0x1fe))+a0L[bz(0x25d)]('\x20\x20\x20\x20\x20\x20\x20'+a0ag(bz(0x30f)))),console[bz(0x2de)](a0L[bz(0x3f1)](bz(0x359))+a0L[bz(0x25d)](bz(0x294)+a0ag(bz(0x46d)))),console['log'](a0L[bz(0x3f1)](bz(0x32b))+a0L[bz(0x25d)](bz(0x22e))+a0L[bz(0x3f1)](bz(0x201))+a0L['gray'](bz(0x340)+a0ag('htpNav'))),console['log'](a0L['yellow'](bz(0x312))+a0L[bz(0x25d)](bz(0x276)+a0ag('htpMoreHelp'))),console[bz(0x2de)](a0L[bz(0x3f1)](bz(0x29d))+a0L[bz(0x25d)](bz(0x294)+a0ag(bz(0x2d7)))),console[bz(0x2de)](a0L['yellow'](bz(0x46b))+a0L[bz(0x25d)](bz(0x294)+a0ag(bz(0x41d)))),console['log'](a0L['gray'](bz(0x30c))),console[bz(0x2de)](a0L[bz(0x25d)](bz(0x373))),console[bz(0x2de)](a0L[bz(0x25d)]('\x20\x20โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ')),console[bz(0x2de)]();const {getConfig:W}=await import(bz(0x3fe)),X=W()[bz(0x28d)]||'en';'en'===X?(console['log'](a0L[bz(0x25d)]('\x20\x20Questions\x20in\x20English.\x20To\x20switch\x20language\x20first:')),console[bz(0x2de)](a0L[bz(0x25d)](bz(0x449))),console[bz(0x2de)](a0L[bz(0x25d)](bz(0x267)))):console[bz(0x2de)](a0L[bz(0x227)](bz(0x2e3)+X)),console[bz(0x2de)](),console['log'](a0L[bz(0x27c)](bz(0x44f)));const Y={...L,'startedAt':new Date()[bz(0x456)]()};console[bz(0x2de)](),N(0x22*-0x107+0x9e8+0x1906,bz(0x2df)),await q(0x31*0x3b+0x1d46+-0x5*0x7f5),N(-0x2*-0x8f9+0x1c1c+0x2*-0x16f3,bz(0x259)),await q(0xf5*-0xb+0x21*-0x15+0x114*0xd),N(0x1e51+-0x1842+-0x5bf,bz(0x2f4)),await q(-0x25*-0x3d+-0x1*-0x4b4+-0xcef),N(-0x1273*-0x1+-0x2e*-0x89+-0x2aad*0x1,bz(0x301)),console[bz(0x2de)](),console[bz(0x2de)](),a0Y({'session':Y,'questions':K,'answers':{},'_helpMax':0x5}),a0ad(bz(0x2bc),String(H)),a0ad(bz(0x381),bz(0x248)),R(K[0x1306+-0x64e+-0xcb8]);}),B['command'](ba(0x29c))[ba(0x491)]('Retry\x20only\x20the\x20questions\x20you\x20got\x20wrong\x20last\x20demo\x20attempt')[ba(0x362)](async()=>{const bE=ba;a0a6(bE(0x3c0)),P(bE(0x28e));const {getLocalizedDemoSession:F}=await import(bE(0x471)),G=a0a4();if(!G||-0x9b1*0x3+0x19d5+-0x53*-0xa===G['length'])return console['log'](),console[bE(0x2de)](a0L['yellow'](bE(0x441))+a0L['bold']['cyan'](bE(0x4b7))+a0L[bE(0x3f1)](bE(0x3f6))),void console['log']();const H=a0X();if(H){if(bE(0x45c)!==H[bE(0x376)][bE(0x261)])return a0a9(bE(0x347)+H[bE(0x376)]['examName']+bE(0x37b)),void a0aa(bE(0x322));a0Z();}const J=U=>{const bF=bE;if(!U[bF(0x447)])return U;const V=U[bF(0x32c)][U[bF(0x447)]],W=['A','B','C','D'][bF(0x35a)](a1=>U[bF(0x32c)][a1]);for(let a1=W[bF(0x24c)]-(0x10*0x36+-0x88a+0x52b);a1>-0x2020+0x49*0x4d+0x89*0x13;a1--){const a2=Math[bF(0x3fd)](Math[bF(0x269)]()*(a1+(0x318+-0x121*-0x2+0x1*-0x559)));[W[a1],W[a2]]=[W[a2],W[a1]];}const X={};X['A']=W[-0x4bd*-0x3+-0x1*0x1b25+0xcee],X['B']=W[-0x862*0x1+-0x1*0x6e5+-0x146*-0xc],X['C']=W[-0x22c6+-0x86b+0x2b33],X['D']=W[-0x4*0x60a+0x25a4+-0xd79];const Y=X,Z=['A','B','C','D'][bF(0x438)](a3=>Y[a3]===V),a0={...U};return a0[bF(0x32c)]=Y,a0[bF(0x447)]=Z,a0;},K=G[bE(0x35a)]((U,V)=>({...J(U),'number':V+(0x13*-0xa1+-0x290*0x6+0x1b54)})),L=F(),Q={...L,'examName':L['examName']+'\x20\x20(Retry\x20wrong\x20only)','questionCount':K[bE(0x24c)],'startedAt':new Date()[bE(0x456)]()};console[bE(0x2de)](),a0ac(bE(0x315)),console[bE(0x2de)](),console[bE(0x2de)](a0L[bE(0x27c)]('\x20\x20Practicing\x20'+K[bE(0x24c)]+'\x20question'+(0x1d6+-0x11*-0x1de+-0x2193===K['length']?'':'s')+bE(0x37f))),console['log'](a0L[bE(0x25d)](bE(0x3cb))),console[bE(0x2de)](),a0Y({'session':Q,'questions':K,'answers':{}}),a0ad(bE(0x2bc),String(K[bE(0x24c)])),a0ad(bE(0x381),bE(0x248)),R(K[0x26d0+-0x1f9f+0x1*-0x731]);}),B['command']('setup')[ba(0x491)](ba(0x482))[ba(0x362)](async()=>{const bG=ba;if(a0a6(bG(0x48d)),-0x59*0x7+0x1a63+-0x1c*0xdb===a0a1()[bG(0x3b0)])return console[bG(0x2de)](),a0a9('Complete\x20the\x20demo\x20first\x20before\x20setting\x20up.'),console['log'](a0L[bG(0x25d)](bG(0x489))+a0L[bG(0x256)]['cyan'](bG(0x3d1))),void console[bG(0x2de)]();const J=a0ai();if(J){const a2=J[bG(0x33a)][bG(0x24c)],a3=a2+J[bG(0x1fd)][bG(0x24c)];if(a2>=-0x4*0x3c7+-0x47a+-0x1*-0x139e||(a3>0x1*0x55d+-0x2a9*-0x2+-0x1*0xaaf?a2/a3:0x129c+0xa*-0x1ca+0x2e*-0x4)>=-0x2*0x44e+-0x869*0x4+0x10*0x2a4+0.7)return console['log'](),console[bG(0x2de)](a0L[bG(0x227)](bG(0x2fc))+a0L['green'](bG(0x2ac))),console['log'](a0L[bG(0x25d)](bG(0x477)+J[bG(0x204)]['split']('T')[0xa*0x65+0xaf4+-0xee6*0x1])),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x2d2)+J[bG(0x24b)])),console[bG(0x2de)](a0L[bG(0x25d)]('\x20\x20\x20\x20Packages:\x20'+a2+bG(0x3db))),J['failedPackages']['length']>-0x1829+0x65*0x53+-0x896&&console[bG(0x2de)](a0L['yellow']('\x20\x20\x20\x20Failed:\x20'+J[bG(0x1fd)][bG(0x401)](',\x20'))),console[bG(0x2de)](),console['log'](a0L[bG(0x27c)]('\x20\x20Next\x20step:\x20')+a0L[bG(0x256)]['cyan']('exam\x20<token>')),console[bG(0x2de)](a0L['gray'](bG(0x216))+a0L['bold'][bG(0x367)](bG(0x25c))+a0L[bG(0x25d)]('\x20to\x20return\x20to\x20the\x20main\x20menu.')),void console[bG(0x2de)]();console[bG(0x2de)](),console[bG(0x2de)](a0L['yellow'](bG(0x220)+a2+'/'+a3+bG(0x2c0))),console[bG(0x2de)]();}console['log'](),a0ac(bG(0x200)),console[bG(0x2de)](),console[bG(0x2de)](a0L[bG(0x27c)](bG(0x28c))),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x2f6))),console[bG(0x2de)]();const K=(a4,a5)=>{const bH=bG,a6=process[bH(0x493)];console['log'](),a0a8(bH(0x42c)===a4?bH(0x2d8):bH(0x3b1)+a5+bH(0x247)),console[bH(0x2de)](),console['log'](a0L['bold'][bH(0x27c)](bH(0x395))),console[bH(0x2de)](),bH(0x266)===a6?(console[bH(0x2de)](a0L[bH(0x3f1)](bH(0x23c))),console[bH(0x2de)](a0L[bH(0x227)]('\x20\x20\x20\x20brew\x20install\x20python@3.12')),console[bH(0x2de)](),console[bH(0x2de)](a0L[bH(0x25d)]('\x20\x20Or\x20download\x20installer:')),console[bH(0x2de)](a0L[bH(0x25d)](bH(0x3dd)))):bH(0x463)===a6?(console['log'](a0L[bH(0x3f1)]('\x20\x20Ubuntu\x20/\x20Debian\x20(deadsnakes\x20PPA):')),console[bH(0x2de)](a0L[bH(0x227)](bH(0x391))),console['log'](a0L[bH(0x227)](bH(0x49c))),console[bH(0x2de)](a0L[bH(0x227)](bH(0x24a))),console[bH(0x2de)](a0L[bH(0x25d)](bH(0x317))),console['log'](),console[bH(0x2de)](a0L[bH(0x3f1)](bH(0x243))),console['log'](a0L['green'](bH(0x397))),console[bH(0x2de)](),console[bH(0x2de)](a0L[bH(0x3f1)](bH(0x2b8))),console[bH(0x2de)](a0L[bH(0x227)](bH(0x44c)))):bH(0x324)===a6?(console[bH(0x2de)](a0L[bH(0x3f1)](bH(0x2dc))),console[bH(0x2de)](a0L[bH(0x227)](bH(0x277))),console[bH(0x2de)](),console['log'](a0L[bH(0x25d)](bH(0x307))),console[bH(0x2de)](a0L['gray'](bH(0x24e)))):console[bH(0x2de)](a0L[bH(0x25d)](bH(0x27e))),console[bH(0x2de)](),console['log'](a0L[bH(0x27c)](bH(0x41b))+a0L['bold'][bH(0x367)](bH(0x48d))),console[bH(0x2de)]();},L=bG(0x212);let Q='';try{const a4={};a4[bG(0x366)]=bG(0x217),a4[bG(0x30b)]=0x1388,Q=a0al(L+'\x20--version',a4)[bG(0x2cc)]()[bG(0x419)](bG(0x3b1),'');const a5=Q['split']('.')[bG(0x35a)](Number);if(a5[-0xb9*-0x15+-0x1e94+0xf67*0x1]<0x2022+-0x1c6a+-0x3b5||0x1a87*0x1+0x3*0x3b9+0xb*-0x36d===a5[0x24e5+-0xc5*0x16+-0x13f7]&&a5[-0x14d2+0x985+0xb4e]<0x1fbb*-0x1+0x70a+-0x18bb*-0x1)return void K(bG(0x3c4),Q);0x3*0xb48+-0x1419+-0x2*0x6de===a5[-0x1*-0xd81+-0x6*0x10+-0xd21]&&a5[0x1*-0x1e38+-0xdfb+0x1*0x2c34]<-0x13*0x50+0x21fb+-0x955*0x3?console[bG(0x2de)](a0L[bG(0x227)](bG(0x24d)+Q)+a0L[bG(0x25d)](bG(0x448))):console[bG(0x2de)](a0L[bG(0x227)]('\x20\x20โ\x20Python\x20'+Q));}catch{return void K('missing');}try{const a6={};a6[bG(0x35e)]=bG(0x2bb),a6['timeout']=0x1388,(a0al(L+bG(0x310),a6),console['log'](a0L[bG(0x227)](bG(0x27d))));}catch{console[bG(0x2de)](a0L[bG(0x3f1)]('\x20\x20โ \x20pip\x20not\x20found,\x20trying\x20ensurepip...'));try{const a7={};a7[bG(0x35e)]='ignore',a7[bG(0x30b)]=0x7530,(a0al(L+bG(0x21b),a7),console[bG(0x2de)](a0L[bG(0x227)](bG(0x31c))));}catch{return void a0a8(bG(0x2d6));}}const U={};U[bG(0x40d)]=bG(0x2f5),U[bG(0x330)]=bG(0x288),U[bG(0x452)]='z3',U['pillow']=bG(0x2cd),U[bG(0x36e)]=bG(0x343),U[bG(0x3a1)]=bG(0x4b5);const V=[bG(0x36e),bG(0x27a),'requests',bG(0x40d),bG(0x414),bG(0x316),bG(0x26f),bG(0x2a4),bG(0x452),bG(0x330),'ROPgadget',bG(0x3eb),bG(0x433)],W=U;console[bG(0x2de)](),console[bG(0x2de)](a0L[bG(0x27c)](bG(0x354)+V[bG(0x24c)]+'\x20packages...')),console['log']();let X='';try{const a8={};a8[bG(0x366)]=bG(0x217),a8[bG(0x30b)]=0x2710,a0al(L+bG(0x3a6),a8);}catch(a9){const aa=String(a9?.[bG(0x3da)]||a9?.[bG(0x342)]||a9?.['message']||'');/externally-managed-environment|break-system-packages/i[bG(0x35f)](aa)&&(X=bG(0x498),console[bG(0x2de)](a0L[bG(0x3f1)](bG(0x40b))),console['log']());}const Y=Q[bG(0x219)]('.')[bG(0x35a)](Number);0x197b+-0xd08+-0xc70===Y[0x1*-0x716+0xa3*-0x4+0x9a2]&&Y[0xdb3+-0x9be*-0x4+-0x34aa]>=-0x1*0x16d0+-0x1e67*0x1+0x3544&&(console[bG(0x2de)](a0L[bG(0x3f1)]('\x20\x20โ \x20Python\x20'+Q+bG(0x450))),console[bG(0x2de)](a0L[bG(0x25d)]('\x20\x20\x20\x20Some\x20packages\x20(pwntools,\x20scapy)\x20may\x20not\x20have\x20wheels\x20yet\x20for\x203.13.')),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x20c))),'linux'===process[bG(0x493)]&&(console[bG(0x2de)](a0L['green'](bG(0x465))),console[bG(0x2de)](a0L[bG(0x227)](bG(0x4bd)))),console['log']());const Z=[],a0=[];let a1='';for(const ab of V)try{a0al(L+bG(0x3ef)+ab+'\x22\x20'+X+bG(0x44b),{'encoding':bG(0x217),'timeout':0x2bf20,'stdio':bG(0x23f)}),a0al(L+bG(0x459)+(W[ab]||ab)+'\x22',{'stdio':bG(0x2bb),'timeout':0x2710});let ac='';try{const ad=a0al(L+bG(0x473)+ab+bG(0x49a),{'encoding':bG(0x217),'timeout':0x1388})[bG(0x3aa)](/^Version:\s*(.+)$/m);ad&&(ac=ad[0x97*-0x36+0x233d+-0x2*0x1b1][bG(0x2cc)]());}catch{}console[bG(0x2de)](a0L[bG(0x227)](bG(0x2fc)+ab)+(ac?a0L['gray']('\x20('+ac+')'):'')),Z['push'](ac?ab+'=='+ac:ab);}catch(ae){const af=String(ae?.[bG(0x3da)]||ae?.[bG(0x342)]||ae?.[bG(0x38a)]||'')['slice'](-(-0x167*0x2+0x2ba+0x1a4)),ag=af[bG(0x219)]('\x0a')[bG(0x228)](ah=>ah[bG(0x2cc)]())[bG(0x460)](-(0x1acf+0x1*-0xe3+-0x19ea))[bG(0x401)](bG(0x476))['slice'](-0x343*0x5+0x123+0xf2c,-0x204e+-0x2*-0xc2a+0x872);console[bG(0x2de)](a0L[bG(0x21c)](bG(0x264)+ab)+a0L['gray']('\x20\x20'+ag)),a0[bG(0x407)]({'pkg':ab,'error':ag}),a1||(a1=af);}-0x1*-0xc3e+-0x1466+0x828===Z[bG(0x24c)]&&a1&&(console[bG(0x2de)](),console[bG(0x2de)](a0L[bG(0x3f1)]('\x20\x20First\x20error\x20detail\x20(for\x20debugging):')),console['log'](a0L[bG(0x25d)]('\x20\x20'+a1[bG(0x219)]('\x0a')['slice'](-(-0x10f7+-0x55f*0x5+0x1*0x2bd8))[bG(0x401)](bG(0x379))))),a0aj({'completedAt':new Date()[bG(0x456)](),'pythonVersion':Q,'installedPackages':Z,'failedPackages':a0[bG(0x35a)](ah=>ah['pkg'])}),console['log'](),-0x2f9+-0x811+-0x13a*-0x9===a0['length']?a0a7(bG(0x47e)+V[bG(0x24c)]+bG(0x358)):-0x10a+0x813+-0x709===Z['length']?(a0a8(bG(0x3a5)+V['length']+bG(0x358)),console[bG(0x2de)](),console[bG(0x2de)](a0L['yellow']('\x20\x20Troubleshooting:')),-0x6ed+0x1*-0x1529+-0x1*-0x1c19===Y[-0xd3d*0x1+-0xb*-0x199+-0x6f*0xa]&&Y[0x49*0x5f+0x173c+-0x3252]>=0xa7e*-0x1+0xe6*-0x29+0x2f61?(console[bG(0x2de)](a0L[bG(0x25d)](bG(0x34d))),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x356)))):(console['log'](a0L[bG(0x25d)](bG(0x41e))),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x23a))+a0L[bG(0x367)](L+'\x20-m\x20pip\x20install\x20pwntools')+a0L[bG(0x25d)](bG(0x332))),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x3c3)))),console[bG(0x2de)](),console[bG(0x2de)](a0L['white'](bG(0x25e))+a0L[bG(0x256)][bG(0x367)](bG(0x48d)))):a0a9(Z[bG(0x24c)]+'/'+V['length']+bG(0x237)+a0[bG(0x24c)]+bG(0x2cf)+a0[bG(0x35a)](ah=>ah[bG(0x37e)])[bG(0x401)](',\x20')),console['log'](),console[bG(0x2de)](a0L[bG(0x367)](bG(0x1f6))),console['log'](a0L['bold']['white'](bG(0x2e1))),console['log'](a0L['cyan'](bG(0x1f6))),console[bG(0x2de)](),console['log'](a0L[bG(0x256)][bG(0x3f1)](bG(0x327))+a0L[bG(0x25d)](bG(0x3d0))),console['log'](a0L['white']('\x20\x20Just\x20add\x20!\x20before\x20the\x20command:')),console[bG(0x2de)](a0L['green'](bG(0x4ba))),console[bG(0x2de)](a0L['gray'](bG(0x3b9))),console[bG(0x2de)](),console[bG(0x2de)](a0L[bG(0x256)]['yellow'](bG(0x386))+a0L[bG(0x25d)](bG(0x308))),console[bG(0x2de)](a0L[bG(0x27c)](bG(0x25b))),console[bG(0x2de)](a0L['green']('\x20\x20\x20\x20!python3')),console['log'](a0L[bG(0x25d)](bG(0x29b))),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x444))),console['log'](a0L[bG(0x25d)]('\x20\x20\x20\x20>>>\x20cipher\x20=\x20AES.new(key,\x20AES.MODE_CBC,\x20iv)')),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x40c))),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x42d))),console[bG(0x2de)](),console[bG(0x2de)](a0L[bG(0x256)][bG(0x3f1)](bG(0x3e3))+a0L[bG(0x25d)](bG(0x39e))),console[bG(0x2de)](a0L[bG(0x27c)](bG(0x246))),console['log'](a0L[bG(0x227)]('\x20\x20\x20\x20!cat\x20<<\x20\x27EOF\x27\x20>\x20solve.py')),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x40e))),console[bG(0x2de)](a0L[bG(0x25d)]('\x20\x20\x20\x20ct\x20=\x20bytes.fromhex(\x220a2b0e1c...\x22)')),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x3c7))),console[bG(0x2de)](a0L[bG(0x25d)]('\x20\x20\x20\x20print(xor(ct,\x20key).decode())')),console[bG(0x2de)](a0L[bG(0x227)](bG(0x31d))),console[bG(0x2de)](a0L[bG(0x227)](bG(0x46c))),console[bG(0x2de)](),console[bG(0x2de)](a0L[bG(0x256)][bG(0x3f1)](bG(0x3b4))),console[bG(0x2de)](a0L['white'](bG(0x1fb))+a0L[bG(0x256)][bG(0x367)](bG(0x382))+a0L[bG(0x27c)]('\x20to\x20ask\x20AI\x20for\x20help:')),console[bG(0x2de)](a0L['gray'](bG(0x434))),console[bG(0x2de)](a0L[bG(0x25d)]('\x20\x20\x20\x20\x22Show\x20me\x20how\x20to\x20use\x20struct.unpack\x22')),console[bG(0x2de)](a0L[bG(0x25d)](bG(0x405))),console[bG(0x2de)](a0L[bG(0x367)](bG(0x1f6))),console['log'](),console[bG(0x2de)](a0L[bG(0x27c)]('\x20\x20Next\x20step:\x20')+a0L['bold'][bG(0x367)]('exam\x20<token>')),console['log'](a0L[bG(0x25d)](bG(0x216))+a0L[bG(0x256)]['cyan'](bG(0x25c))+a0L[bG(0x25d)](bG(0x206))),console[bG(0x2de)]();}),B[ba(0x362)](()=>{const bI=ba;a0a6(bI(0x2b5));const F=a0X();if(F){a0ac(bI(0x351)+F['session']['examName']),M();const J=Object['keys'](F['answers'])[bI(0x24c)];return a0ad(bI(0x4a6),J+'/'+F[bI(0x376)]['questionCount']+bI(0x40a)),console['log'](),void console['log'](a0L[bI(0x25d)]('\x20\x20exam\x20q\x20[n]\x20\x20|\x20exam\x20answer\x20<n>\x20<A-D>\x20|\x20exam\x20review\x20|\x20exam\x20submit'));}const G=a0a1(),H=a0ai();console[bI(0x2de)](),a0ac(bI(0x43b)),console[bI(0x2de)](),0x8*0xed+-0x1888+0x1120===G[bI(0x3b0)]?(console['log'](a0L['yellow'](bI(0x3ca))+a0L[bI(0x3f1)](bI(0x3a7))),console['log'](a0L['gray'](bI(0x2c2))+a0L[bI(0x256)][bI(0x367)](bI(0x3d1)))):(console[bI(0x2de)](a0L[bI(0x227)]('\x20\x20โ\x20')+a0L['green']('Demo\x20completed\x20('+G[bI(0x3b0)]+'\x20attempt'+(-0x7*0x4bf+-0x2485+0x1*0x45bf!==G['attempts']?'s':'')+')')),H?(console[bI(0x2de)](a0L[bI(0x227)](bI(0x442))+a0L['green']('Environment\x20ready')),console[bI(0x2de)](a0L[bI(0x3f1)](bI(0x3ca))+a0L[bI(0x3f1)](bI(0x214))),console[bI(0x2de)](a0L[bI(0x25d)]('\x20\x20\x20\x20โ\x20')+a0L[bI(0x256)][bI(0x367)](bI(0x385)))):(console[bI(0x2de)](a0L[bI(0x3f1)](bI(0x3ca))+a0L[bI(0x3f1)]('Pre-exam\x20setup\x20required')),console[bI(0x2de)](a0L['gray'](bI(0x2c2))+a0L[bI(0x256)]['cyan'](bI(0x48d))))),console[bI(0x2de)]();});}
|