mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
Implement comprehensive multi-protocol RFID system with deterministic
card_id-based generation, MIFARE key attacks, and protocol-specific UI.
## New Protocol System (4 protocols):
- EM4100 (low security) - Instant clone, already implemented
- MIFARE_Classic_Weak_Defaults (low) - Dictionary attack succeeds (95%)
- MIFARE_Classic_Custom_Keys (medium) - Requires Darkside attack (30s)
- MIFARE_DESFire (high) - UID only, forces physical theft
## Key Features:
### 1. Protocol Foundation
- Created rfid-protocols.js with protocol definitions
- Added protocol detection, capabilities, security levels
- Defined attack durations and common MIFARE keys
### 2. Deterministic Card Generation
- Updated rfid-data.js with card_id-based generation
- Same card_id always produces same hex/UID (deterministic)
- Simplified scenario format - no manual hex/UID needed
- getCardDisplayData() supports all protocols
### 3. MIFARE Attack System
- Created rfid-attacks.js with MIFAREAttackManager
- Dictionary Attack: Instant, 95% success on weak defaults
- Darkside Attack: 30 sec (10s on weak), cracks all keys
- Nested Attack: 10 sec, uses known key to crack rest
- Protocol-aware attack behavior
### 4. UI Enhancements
- Updated rfid-ui.js with protocol-specific displays
- showProtocolInfo() with color-coded security badges
- showAttackProgress() and updateAttackProgress()
- Protocol headers with icons and frequency info
- Updated showCardDataScreen() and showEmulationScreen()
### 5. Unlock System Integration
- Updated unlock-system.js for card_id matching
- Support multiple valid cards per door (array)
- Added acceptsUIDOnly flag for DESFire UID emulation
- Backward compatible with legacy key_id format
### 6. Minigame Integration
- Updated rfid-minigame.js with attack methods
- startKeyAttack() triggers dictionary/darkside/nested
- handleCardTap() and handleEmulate() use card_id arrays
- UID-only emulation validation for DESFire
- Attack manager cleanup on minigame exit
### 7. Styling
- Added CSS for protocol headers and security badges
- Color-coded security levels (red=low, teal=medium, green=high)
- Attack progress styling with smooth transitions
- Dimmed menu items for unlikely attack options
## Scenario Format Changes:
Before (manual technical data):
```json
{
"type": "keycard",
"rfid_hex": "01AB34CD56",
"rfid_facility": 1,
"key_id": "employee_badge"
}
```
After (simplified with card_id):
```json
{
"type": "keycard",
"card_id": "employee_badge",
"rfid_protocol": "MIFARE_Classic_Weak_Defaults",
"name": "Employee Badge"
}
```
Technical data (hex/UID) generated automatically from card_id.
## Door Configuration:
Multiple valid cards per door:
```json
{
"lockType": "rfid",
"requires": ["employee_badge", "contractor_badge", "master_card"],
"acceptsUIDOnly": false
}
```
## Files Modified:
- js/minigames/rfid/rfid-protocols.js (NEW)
- js/minigames/rfid/rfid-attacks.js (NEW)
- js/minigames/rfid/rfid-data.js
- js/minigames/rfid/rfid-ui.js
- js/minigames/rfid/rfid-minigame.js
- js/systems/unlock-system.js
- css/rfid-minigame.css
- planning_notes/rfid_keycard/protocols_and_interactions/03_UPDATES_SUMMARY.md (NEW)
## Next Steps:
- Phase 5: Ink integration (syncCardProtocolsToInk)
- Test with scenarios for each protocol
- Add Ink variable documentation
Estimated implementation time: ~12 hours (Phases 1-4 complete)
752 lines
26 KiB
JavaScript
752 lines
26 KiB
JavaScript
/**
|
|
* RFID UI Renderer
|
|
*
|
|
* Renders Flipper Zero-style RFID interface:
|
|
* - Main menu (Read / Saved)
|
|
* - Tap interface (unlock mode)
|
|
* - Saved cards list
|
|
* - Emulation screen
|
|
* - Card reading screen (clone mode)
|
|
* - Card data display
|
|
* - Protocol-specific displays for all supported protocols
|
|
*
|
|
* @module rfid-ui
|
|
*/
|
|
|
|
import { getProtocolInfo, detectProtocol } from './rfid-protocols.js';
|
|
|
|
export class RFIDUIRenderer {
|
|
constructor(minigame) {
|
|
this.minigame = minigame;
|
|
this.container = minigame.gameContainer;
|
|
this.dataManager = minigame.dataManager;
|
|
console.log('🎨 RFIDUIRenderer initialized');
|
|
}
|
|
|
|
/**
|
|
* Create unlock mode interface
|
|
*/
|
|
createUnlockInterface() {
|
|
this.clear();
|
|
|
|
// Create Flipper Zero frame
|
|
const flipper = this.createFlipperFrame();
|
|
|
|
// Show main menu
|
|
this.showMainMenu('unlock');
|
|
|
|
this.container.appendChild(flipper);
|
|
}
|
|
|
|
/**
|
|
* Create clone mode interface
|
|
*/
|
|
createCloneInterface() {
|
|
this.clear();
|
|
|
|
// Create Flipper Zero frame
|
|
const flipper = this.createFlipperFrame();
|
|
|
|
// Auto-start reading if card provided
|
|
if (this.minigame.params.cardToClone) {
|
|
this.showReadingScreen();
|
|
} else {
|
|
this.showMainMenu('clone');
|
|
}
|
|
|
|
this.container.appendChild(flipper);
|
|
}
|
|
|
|
/**
|
|
* Create Flipper Zero device frame
|
|
* @returns {HTMLElement} Flipper frame element
|
|
*/
|
|
createFlipperFrame() {
|
|
const frame = document.createElement('div');
|
|
frame.className = 'flipper-zero-frame';
|
|
|
|
// Header with logo and battery
|
|
const header = document.createElement('div');
|
|
header.className = 'flipper-header';
|
|
|
|
const logo = document.createElement('div');
|
|
logo.className = 'flipper-logo';
|
|
logo.textContent = 'FLIPPER ZERO';
|
|
|
|
const battery = document.createElement('div');
|
|
battery.className = 'flipper-battery';
|
|
battery.textContent = '⚡ 100%';
|
|
|
|
header.appendChild(logo);
|
|
header.appendChild(battery);
|
|
|
|
// Screen container
|
|
const screen = document.createElement('div');
|
|
screen.className = 'flipper-screen';
|
|
screen.id = 'rfid-screen';
|
|
|
|
frame.appendChild(header);
|
|
frame.appendChild(screen);
|
|
|
|
return frame;
|
|
}
|
|
|
|
/**
|
|
* Get screen element
|
|
* @returns {HTMLElement} Screen element
|
|
*/
|
|
getScreen() {
|
|
return document.getElementById('rfid-screen');
|
|
}
|
|
|
|
/**
|
|
* Show main menu
|
|
* @param {string} mode - 'unlock' or 'clone'
|
|
*/
|
|
showMainMenu(mode) {
|
|
const screen = this.getScreen();
|
|
screen.innerHTML = '';
|
|
|
|
// Breadcrumb
|
|
const breadcrumb = document.createElement('div');
|
|
breadcrumb.className = 'flipper-breadcrumb';
|
|
breadcrumb.textContent = 'RFID';
|
|
screen.appendChild(breadcrumb);
|
|
|
|
// Menu items
|
|
const menu = document.createElement('div');
|
|
menu.className = 'flipper-menu';
|
|
|
|
if (mode === 'unlock') {
|
|
// Read option (tap cards)
|
|
const readOption = document.createElement('div');
|
|
readOption.className = 'flipper-menu-item';
|
|
readOption.textContent = '> Read';
|
|
readOption.addEventListener('click', () => this.showTapInterface());
|
|
menu.appendChild(readOption);
|
|
|
|
// Saved option (emulate)
|
|
const savedOption = document.createElement('div');
|
|
savedOption.className = 'flipper-menu-item';
|
|
savedOption.textContent = ' Saved';
|
|
savedOption.addEventListener('click', () => this.showSavedCards());
|
|
menu.appendChild(savedOption);
|
|
} else {
|
|
// Clone mode - just show "Reading..." message
|
|
const info = document.createElement('div');
|
|
info.className = 'flipper-info';
|
|
info.textContent = 'Place card...';
|
|
menu.appendChild(info);
|
|
}
|
|
|
|
screen.appendChild(menu);
|
|
}
|
|
|
|
/**
|
|
* Show tap interface for unlock mode
|
|
*/
|
|
showTapInterface() {
|
|
const screen = this.getScreen();
|
|
screen.innerHTML = '';
|
|
|
|
// Breadcrumb
|
|
const breadcrumb = document.createElement('div');
|
|
breadcrumb.className = 'flipper-breadcrumb';
|
|
breadcrumb.textContent = 'RFID > Read';
|
|
screen.appendChild(breadcrumb);
|
|
|
|
// NFC waves animation
|
|
const waves = document.createElement('div');
|
|
waves.className = 'rfid-nfc-waves-container';
|
|
waves.innerHTML = '<div class="rfid-nfc-icon">📡</div>';
|
|
screen.appendChild(waves);
|
|
|
|
// Instruction
|
|
const instruction = document.createElement('div');
|
|
instruction.className = 'flipper-info';
|
|
instruction.textContent = 'Place card near reader...';
|
|
screen.appendChild(instruction);
|
|
|
|
// List available keycards
|
|
const cardList = document.createElement('div');
|
|
cardList.className = 'flipper-card-list';
|
|
|
|
const availableCards = this.minigame.params.availableCards || [];
|
|
|
|
if (availableCards.length === 0) {
|
|
const noCards = document.createElement('div');
|
|
noCards.className = 'flipper-info-dim';
|
|
noCards.textContent = 'No keycards in inventory';
|
|
cardList.appendChild(noCards);
|
|
} else {
|
|
availableCards.forEach(card => {
|
|
const cardItem = document.createElement('div');
|
|
cardItem.className = 'flipper-menu-item';
|
|
cardItem.textContent = `> ${card.scenarioData?.name || 'Keycard'}`;
|
|
cardItem.addEventListener('click', () => {
|
|
this.minigame.handleCardTap(card);
|
|
});
|
|
cardList.appendChild(cardItem);
|
|
});
|
|
}
|
|
|
|
screen.appendChild(cardList);
|
|
|
|
// Back button
|
|
const back = document.createElement('div');
|
|
back.className = 'flipper-button-back';
|
|
back.textContent = '← Back';
|
|
back.addEventListener('click', () => this.showMainMenu('unlock'));
|
|
screen.appendChild(back);
|
|
}
|
|
|
|
/**
|
|
* Show saved cards list
|
|
*/
|
|
showSavedCards() {
|
|
const screen = this.getScreen();
|
|
screen.innerHTML = '';
|
|
|
|
// Breadcrumb
|
|
const breadcrumb = document.createElement('div');
|
|
breadcrumb.className = 'flipper-breadcrumb';
|
|
breadcrumb.textContent = 'RFID > Saved';
|
|
screen.appendChild(breadcrumb);
|
|
|
|
// Get saved cards
|
|
const savedCards = this.dataManager.getSavedCards();
|
|
|
|
if (savedCards.length === 0) {
|
|
const noCards = document.createElement('div');
|
|
noCards.className = 'flipper-info';
|
|
noCards.textContent = 'No saved cards';
|
|
screen.appendChild(noCards);
|
|
} else {
|
|
// Card list
|
|
const cardList = document.createElement('div');
|
|
cardList.className = 'flipper-card-list';
|
|
|
|
savedCards.forEach(card => {
|
|
const cardItem = document.createElement('div');
|
|
cardItem.className = 'flipper-menu-item';
|
|
cardItem.textContent = `> ${card.name}`;
|
|
cardItem.addEventListener('click', () => this.showEmulationScreen(card));
|
|
cardList.appendChild(cardItem);
|
|
});
|
|
|
|
screen.appendChild(cardList);
|
|
}
|
|
|
|
// Back button
|
|
const back = document.createElement('div');
|
|
back.className = 'flipper-button-back';
|
|
back.textContent = '← Back';
|
|
back.addEventListener('click', () => this.showMainMenu('unlock'));
|
|
screen.appendChild(back);
|
|
}
|
|
|
|
/**
|
|
* Show emulation screen (supports all protocols)
|
|
* @param {Object} card - Card to emulate
|
|
*/
|
|
showEmulationScreen(card) {
|
|
const screen = this.getScreen();
|
|
screen.innerHTML = '';
|
|
|
|
// Get protocol-specific display data
|
|
const displayData = this.dataManager.getCardDisplayData(card);
|
|
|
|
// Breadcrumb
|
|
const breadcrumb = document.createElement('div');
|
|
breadcrumb.className = 'flipper-breadcrumb';
|
|
breadcrumb.textContent = 'RFID > Saved > Emulate';
|
|
screen.appendChild(breadcrumb);
|
|
|
|
// Emulation icon
|
|
const icon = document.createElement('div');
|
|
icon.className = 'rfid-emulate-icon';
|
|
icon.textContent = '📡';
|
|
screen.appendChild(icon);
|
|
|
|
// Protocol with color indicator
|
|
const protocolDiv = document.createElement('div');
|
|
protocolDiv.className = 'flipper-info';
|
|
protocolDiv.style.borderLeft = `4px solid ${displayData.color}`;
|
|
protocolDiv.style.paddingLeft = '8px';
|
|
protocolDiv.innerHTML = `${displayData.icon} ${displayData.protocolName}`;
|
|
screen.appendChild(protocolDiv);
|
|
|
|
// Card name
|
|
const name = document.createElement('div');
|
|
name.className = 'flipper-card-name';
|
|
name.textContent = card.name || 'Card';
|
|
screen.appendChild(name);
|
|
|
|
// Card data fields
|
|
const data = document.createElement('div');
|
|
data.className = 'flipper-card-data';
|
|
|
|
// Show first 3 fields (most relevant for emulation)
|
|
displayData.fields.slice(0, 3).forEach(field => {
|
|
const fieldDiv = document.createElement('div');
|
|
fieldDiv.innerHTML = `${field.label}: ${field.value}`;
|
|
data.appendChild(fieldDiv);
|
|
});
|
|
|
|
screen.appendChild(data);
|
|
|
|
// Emulating message
|
|
const emulating = document.createElement('div');
|
|
emulating.className = 'flipper-emulating';
|
|
if (displayData.protocol === 'MIFARE_DESFire' && !card.rfid_data?.masterKeyKnown) {
|
|
emulating.textContent = 'Emulating UID only...';
|
|
} else {
|
|
emulating.textContent = 'Emulating...';
|
|
}
|
|
screen.appendChild(emulating);
|
|
|
|
// Trigger emulation after showing screen
|
|
setTimeout(() => {
|
|
this.minigame.handleEmulate(card);
|
|
}, 500);
|
|
}
|
|
|
|
/**
|
|
* Show protocol information screen with attack options
|
|
* @param {Object} cardData - Card data to display protocol info for
|
|
*/
|
|
showProtocolInfo(cardData) {
|
|
const screen = this.getScreen();
|
|
screen.innerHTML = '';
|
|
|
|
const displayData = this.dataManager.getCardDisplayData(cardData);
|
|
const protocol = displayData.protocol;
|
|
|
|
// Breadcrumb
|
|
const breadcrumb = document.createElement('div');
|
|
breadcrumb.className = 'flipper-breadcrumb';
|
|
breadcrumb.textContent = 'RFID > Info';
|
|
screen.appendChild(breadcrumb);
|
|
|
|
// Protocol header with icon and color
|
|
const header = document.createElement('div');
|
|
header.className = 'flipper-protocol-header';
|
|
header.style.borderLeft = `4px solid ${displayData.color}`;
|
|
header.innerHTML = `
|
|
<div class="protocol-header-top">
|
|
<span class="protocol-icon">${displayData.icon}</span>
|
|
<span class="protocol-name">${displayData.protocolName}</span>
|
|
</div>
|
|
<div class="protocol-meta">
|
|
<span>${displayData.frequency}</span>
|
|
<span class="security-badge security-${displayData.security}">
|
|
${displayData.security.toUpperCase()}
|
|
</span>
|
|
</div>
|
|
`;
|
|
screen.appendChild(header);
|
|
|
|
// Security note
|
|
if (displayData.securityNote) {
|
|
const note = document.createElement('div');
|
|
note.className = 'flipper-info';
|
|
note.textContent = displayData.securityNote;
|
|
screen.appendChild(note);
|
|
}
|
|
|
|
// Card data fields
|
|
const dataDiv = document.createElement('div');
|
|
dataDiv.className = 'flipper-card-data';
|
|
displayData.fields.forEach(field => {
|
|
const fieldDiv = document.createElement('div');
|
|
fieldDiv.innerHTML = `<strong>${field.label}:</strong> ${field.value}`;
|
|
dataDiv.appendChild(fieldDiv);
|
|
});
|
|
screen.appendChild(dataDiv);
|
|
|
|
// Actions based on protocol
|
|
const actions = document.createElement('div');
|
|
actions.className = 'flipper-menu';
|
|
actions.style.marginTop = '20px';
|
|
|
|
if (protocol === 'MIFARE_Classic_Weak_Defaults') {
|
|
const keysKnown = cardData.rfid_data?.sectors ?
|
|
Object.keys(cardData.rfid_data.sectors).length : 0;
|
|
|
|
if (keysKnown === 0) {
|
|
// Suggest dictionary first
|
|
const dictBtn = document.createElement('div');
|
|
dictBtn.className = 'flipper-menu-item';
|
|
dictBtn.textContent = '> Dictionary Attack (instant)';
|
|
dictBtn.addEventListener('click', () =>
|
|
this.minigame.startKeyAttack('dictionary', cardData));
|
|
actions.appendChild(dictBtn);
|
|
} else if (keysKnown < 16) {
|
|
// Some keys found
|
|
const nestedBtn = document.createElement('div');
|
|
nestedBtn.className = 'flipper-menu-item';
|
|
nestedBtn.textContent = `> Nested Attack (${16 - keysKnown} sectors)`;
|
|
nestedBtn.addEventListener('click', () =>
|
|
this.minigame.startKeyAttack('nested', cardData));
|
|
actions.appendChild(nestedBtn);
|
|
} else {
|
|
// All keys - can clone
|
|
const readBtn = document.createElement('div');
|
|
readBtn.className = 'flipper-menu-item';
|
|
readBtn.textContent = '> Read & Clone';
|
|
readBtn.addEventListener('click', () =>
|
|
this.showCardDataScreen(cardData));
|
|
actions.appendChild(readBtn);
|
|
}
|
|
|
|
} else if (protocol === 'MIFARE_Classic_Custom_Keys') {
|
|
const keysKnown = cardData.rfid_data?.sectors ?
|
|
Object.keys(cardData.rfid_data.sectors).length : 0;
|
|
|
|
if (keysKnown === 0) {
|
|
// No keys - suggest Darkside
|
|
const darksideBtn = document.createElement('div');
|
|
darksideBtn.className = 'flipper-menu-item';
|
|
darksideBtn.textContent = '> Darkside Attack (~30 sec)';
|
|
darksideBtn.addEventListener('click', () =>
|
|
this.minigame.startKeyAttack('darkside', cardData));
|
|
actions.appendChild(darksideBtn);
|
|
|
|
// Dictionary unlikely but allow try
|
|
const dictBtn = document.createElement('div');
|
|
dictBtn.className = 'flipper-menu-item flipper-menu-item-dim';
|
|
dictBtn.textContent = ' Dictionary Attack (unlikely)';
|
|
dictBtn.addEventListener('click', () =>
|
|
this.minigame.startKeyAttack('dictionary', cardData));
|
|
actions.appendChild(dictBtn);
|
|
} else if (keysKnown < 16) {
|
|
// Some keys - nested attack
|
|
const nestedBtn = document.createElement('div');
|
|
nestedBtn.className = 'flipper-menu-item';
|
|
nestedBtn.textContent = `> Nested Attack (~10 sec)`;
|
|
nestedBtn.addEventListener('click', () =>
|
|
this.minigame.startKeyAttack('nested', cardData));
|
|
actions.appendChild(nestedBtn);
|
|
} else {
|
|
// All keys
|
|
const readBtn = document.createElement('div');
|
|
readBtn.className = 'flipper-menu-item';
|
|
readBtn.textContent = '> Read & Clone';
|
|
readBtn.addEventListener('click', () =>
|
|
this.showCardDataScreen(cardData));
|
|
actions.appendChild(readBtn);
|
|
}
|
|
|
|
} else if (protocol === 'MIFARE_DESFire') {
|
|
// UID only
|
|
const uidBtn = document.createElement('div');
|
|
uidBtn.className = 'flipper-menu-item';
|
|
uidBtn.textContent = '> Save UID Only';
|
|
uidBtn.addEventListener('click', () =>
|
|
this.showCardDataScreen(cardData));
|
|
actions.appendChild(uidBtn);
|
|
|
|
} else {
|
|
// EM4100 - instant
|
|
const readBtn = document.createElement('div');
|
|
readBtn.className = 'flipper-menu-item';
|
|
readBtn.textContent = '> Read & Clone';
|
|
readBtn.addEventListener('click', () =>
|
|
this.showReadingScreen());
|
|
actions.appendChild(readBtn);
|
|
}
|
|
|
|
const cancelBtn = document.createElement('div');
|
|
cancelBtn.className = 'flipper-button-back';
|
|
cancelBtn.textContent = '← Cancel';
|
|
cancelBtn.addEventListener('click', () => this.minigame.complete(false));
|
|
actions.appendChild(cancelBtn);
|
|
|
|
screen.appendChild(actions);
|
|
}
|
|
|
|
/**
|
|
* Show card reading screen (clone mode)
|
|
*/
|
|
showReadingScreen() {
|
|
const screen = this.getScreen();
|
|
screen.innerHTML = '';
|
|
|
|
// Breadcrumb
|
|
const breadcrumb = document.createElement('div');
|
|
breadcrumb.className = 'flipper-breadcrumb';
|
|
breadcrumb.textContent = 'RFID > Read';
|
|
screen.appendChild(breadcrumb);
|
|
|
|
// Status
|
|
const status = document.createElement('div');
|
|
status.className = 'flipper-info';
|
|
status.textContent = 'Reading 1/2';
|
|
screen.appendChild(status);
|
|
|
|
// Modulation
|
|
const modulation = document.createElement('div');
|
|
modulation.className = 'flipper-info-dim';
|
|
modulation.textContent = '> ASK PSK';
|
|
screen.appendChild(modulation);
|
|
|
|
// Instruction
|
|
const instruction = document.createElement('div');
|
|
instruction.className = 'flipper-info';
|
|
instruction.textContent = "Don't move card...";
|
|
screen.appendChild(instruction);
|
|
|
|
// Progress bar
|
|
const progressContainer = document.createElement('div');
|
|
progressContainer.className = 'rfid-progress-container';
|
|
|
|
const progressBar = document.createElement('div');
|
|
progressBar.className = 'rfid-progress-bar';
|
|
progressBar.id = 'rfid-progress-bar';
|
|
|
|
progressContainer.appendChild(progressBar);
|
|
screen.appendChild(progressContainer);
|
|
|
|
// Start reading animation
|
|
this.minigame.startCardReading();
|
|
}
|
|
|
|
/**
|
|
* Update reading progress
|
|
* @param {number} progress - Progress percentage (0-100)
|
|
*/
|
|
updateReadingProgress(progress) {
|
|
const progressBar = document.getElementById('rfid-progress-bar');
|
|
if (progressBar) {
|
|
progressBar.style.width = `${progress}%`;
|
|
|
|
// Change color based on progress
|
|
if (progress < 50) {
|
|
progressBar.style.backgroundColor = '#FF8200';
|
|
} else if (progress < 100) {
|
|
progressBar.style.backgroundColor = '#FFA500';
|
|
} else {
|
|
progressBar.style.backgroundColor = '#00FF00';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show card data screen after reading (supports all protocols)
|
|
* @param {Object} cardData - Read card data
|
|
*/
|
|
showCardDataScreen(cardData) {
|
|
const screen = this.getScreen();
|
|
screen.innerHTML = '';
|
|
|
|
// Get protocol-specific display data
|
|
const displayData = this.dataManager.getCardDisplayData(cardData);
|
|
|
|
// Breadcrumb
|
|
const breadcrumb = document.createElement('div');
|
|
breadcrumb.className = 'flipper-breadcrumb';
|
|
breadcrumb.textContent = 'RFID > Read';
|
|
screen.appendChild(breadcrumb);
|
|
|
|
// Protocol header
|
|
const protocolHeader = document.createElement('div');
|
|
protocolHeader.className = 'flipper-protocol-header';
|
|
protocolHeader.style.borderLeft = `4px solid ${displayData.color}`;
|
|
protocolHeader.innerHTML = `
|
|
<div class="protocol-header-top">
|
|
<span class="protocol-icon">${displayData.icon}</span>
|
|
<span class="protocol-name">${displayData.protocolName}</span>
|
|
</div>
|
|
<div class="protocol-meta">
|
|
<span>${displayData.frequency}</span>
|
|
<span class="security-badge security-${displayData.security}">
|
|
${displayData.security.toUpperCase()}
|
|
</span>
|
|
</div>
|
|
`;
|
|
screen.appendChild(protocolHeader);
|
|
|
|
// Security note (if applicable)
|
|
if (displayData.securityNote) {
|
|
const note = document.createElement('div');
|
|
note.className = 'flipper-info';
|
|
note.textContent = displayData.securityNote;
|
|
screen.appendChild(note);
|
|
}
|
|
|
|
// Card data fields
|
|
const data = document.createElement('div');
|
|
data.className = 'flipper-card-data';
|
|
displayData.fields.forEach(field => {
|
|
const fieldDiv = document.createElement('div');
|
|
fieldDiv.innerHTML = `<strong>${field.label}:</strong> ${field.value}`;
|
|
data.appendChild(fieldDiv);
|
|
});
|
|
|
|
// For EM4100, add checksum (legacy)
|
|
if (displayData.protocol === 'EM4100') {
|
|
const hex = cardData.rfid_data?.hex || cardData.rfid_hex;
|
|
if (hex) {
|
|
const checksum = this.dataManager.calculateChecksum(hex);
|
|
const checksumDiv = document.createElement('div');
|
|
checksumDiv.innerHTML = `<strong>Checksum:</strong> 0x${checksum.toString(16).toUpperCase().padStart(2, '0')}`;
|
|
data.appendChild(checksumDiv);
|
|
}
|
|
}
|
|
|
|
screen.appendChild(data);
|
|
|
|
// Buttons
|
|
const buttons = document.createElement('div');
|
|
buttons.className = 'flipper-buttons';
|
|
|
|
const saveBtn = document.createElement('button');
|
|
saveBtn.className = 'flipper-button';
|
|
saveBtn.textContent = displayData.protocol === 'MIFARE_DESFire' ? 'Save UID' : 'Save';
|
|
saveBtn.addEventListener('click', () => this.minigame.handleSaveCard(cardData));
|
|
|
|
const cancelBtn = document.createElement('button');
|
|
cancelBtn.className = 'flipper-button flipper-button-secondary';
|
|
cancelBtn.textContent = 'Cancel';
|
|
cancelBtn.addEventListener('click', () => this.minigame.complete(false));
|
|
|
|
buttons.appendChild(saveBtn);
|
|
buttons.appendChild(cancelBtn);
|
|
screen.appendChild(buttons);
|
|
}
|
|
|
|
/**
|
|
* Show attack progress screen
|
|
* @param {Object} data - Attack data {type, progress, currentSector, etc.}
|
|
*/
|
|
showAttackProgress(data) {
|
|
const screen = this.getScreen();
|
|
screen.innerHTML = '';
|
|
|
|
// Breadcrumb
|
|
const breadcrumb = document.createElement('div');
|
|
breadcrumb.className = 'flipper-breadcrumb';
|
|
breadcrumb.textContent = `RFID > ${data.type} Attack`;
|
|
screen.appendChild(breadcrumb);
|
|
|
|
// Attack type
|
|
const type = document.createElement('div');
|
|
type.className = 'flipper-info';
|
|
type.textContent = `${data.type} Attack`;
|
|
type.style.fontSize = '18px';
|
|
type.style.marginBottom = '10px';
|
|
screen.appendChild(type);
|
|
|
|
// Status
|
|
const status = document.createElement('div');
|
|
status.className = 'flipper-info-dim';
|
|
status.id = 'attack-status';
|
|
if (data.currentSector !== undefined) {
|
|
status.textContent = `Sector ${data.currentSector}/${data.totalSectors || 16}`;
|
|
} else if (data.sectorsRemaining !== undefined) {
|
|
status.textContent = `${data.sectorsRemaining} sectors remaining`;
|
|
} else {
|
|
status.textContent = 'Working...';
|
|
}
|
|
screen.appendChild(status);
|
|
|
|
// Progress bar
|
|
const progressContainer = document.createElement('div');
|
|
progressContainer.className = 'rfid-progress-container';
|
|
progressContainer.style.marginTop = '20px';
|
|
|
|
const progressBar = document.createElement('div');
|
|
progressBar.className = 'rfid-progress-bar';
|
|
progressBar.id = 'attack-progress-bar';
|
|
progressBar.style.width = `${data.progress || 0}%`;
|
|
|
|
progressContainer.appendChild(progressBar);
|
|
screen.appendChild(progressContainer);
|
|
|
|
// Percentage
|
|
const percentage = document.createElement('div');
|
|
percentage.className = 'flipper-info';
|
|
percentage.id = 'attack-percentage';
|
|
percentage.textContent = `${Math.floor(data.progress || 0)}%`;
|
|
percentage.style.textAlign = 'center';
|
|
percentage.style.marginTop = '10px';
|
|
screen.appendChild(percentage);
|
|
}
|
|
|
|
/**
|
|
* Update attack progress
|
|
* @param {Object} data - Progress data
|
|
*/
|
|
updateAttackProgress(data) {
|
|
const progressBar = document.getElementById('attack-progress-bar');
|
|
const status = document.getElementById('attack-status');
|
|
const percentage = document.getElementById('attack-percentage');
|
|
|
|
if (progressBar) {
|
|
progressBar.style.width = `${data.progress}%`;
|
|
|
|
// Change color based on progress
|
|
if (data.progress < 50) {
|
|
progressBar.style.backgroundColor = '#FF8200';
|
|
} else if (data.progress < 100) {
|
|
progressBar.style.backgroundColor = '#FFA500';
|
|
} else {
|
|
progressBar.style.backgroundColor = '#00FF00';
|
|
}
|
|
}
|
|
|
|
if (status) {
|
|
if (data.currentSector !== undefined) {
|
|
status.textContent = `Sector ${data.currentSector}/${data.totalSectors || 16}`;
|
|
} else if (data.sectorsRemaining !== undefined) {
|
|
status.textContent = `${data.sectorsRemaining} sectors remaining`;
|
|
}
|
|
}
|
|
|
|
if (percentage) {
|
|
percentage.textContent = `${Math.floor(data.progress)}%`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show success message
|
|
* @param {string} message - Success message
|
|
*/
|
|
showSuccess(message) {
|
|
const screen = this.getScreen();
|
|
screen.innerHTML = '';
|
|
|
|
const success = document.createElement('div');
|
|
success.className = 'flipper-success';
|
|
success.innerHTML = `
|
|
<div class="flipper-success-icon">✓</div>
|
|
<div class="flipper-success-message">${message}</div>
|
|
`;
|
|
screen.appendChild(success);
|
|
}
|
|
|
|
/**
|
|
* Show error message
|
|
* @param {string} message - Error message
|
|
*/
|
|
showError(message) {
|
|
const screen = this.getScreen();
|
|
screen.innerHTML = '';
|
|
|
|
const error = document.createElement('div');
|
|
error.className = 'flipper-error';
|
|
error.innerHTML = `
|
|
<div class="flipper-error-icon">✗</div>
|
|
<div class="flipper-error-message">${message}</div>
|
|
`;
|
|
screen.appendChild(error);
|
|
}
|
|
|
|
/**
|
|
* Clear screen
|
|
*/
|
|
clear() {
|
|
this.container.innerHTML = '';
|
|
}
|
|
}
|