From 2a4ee59e4f7e42d05c5a937cc6b1d75fe8dbf862 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Sat, 15 Nov 2025 23:48:15 +0000 Subject: [PATCH] docs(rfid): Add Ink integration and comprehensive variable documentation Add Ink integration for RFID card protocols, allowing conversation scripts to detect and respond to different card security levels and protocols. ## Ink Integration (Phase 5): ### 1. Added syncCardProtocolsToInk() Method - Location: js/minigames/person-chat/person-chat-conversation.js - Auto-syncs when syncItemsToInk() is called - Generates rfid_data if card uses card_id pattern - Syncs variables for each keycard NPC holds ### 2. Per-Card Variables Synced: - {prefix}_protocol - Protocol name - {prefix}_name - Card display name - {prefix}_card_id - Logical identifier - {prefix}_security - "low", "medium", "high" - {prefix}_instant_clone - Boolean (EM4100, weak MIFARE) - {prefix}_needs_attack - Boolean (custom key MIFARE) - {prefix}_uid_only - Boolean (DESFire) - {prefix}_uid - Card UID (MIFARE) - {prefix}_hex - Card hex ID (EM4100) ### 3. Prefix Pattern: - First card: card_protocol, card_name, card_card_id, etc. - Second card: card2_protocol, card2_name, card2_card_id, etc. ## Documentation: ### scenarios/ink/README_RFID_VARIABLES.md (NEW) Comprehensive guide for scenario designers covering: 1. **Variable Declarations** - Required Ink variable setup 2. **Variable Reference** - Complete table of all variables 3. **Protocol Characteristics** - Details for each of 4 protocols 4. **Usage Examples**: - Simple EM4100 clone - Multi-protocol detection - Conditional dialogue based on protocol 5. **Ink Tags** - Suggested tag patterns for RFID actions 6. **Scenario JSON Format** - How to define keycards 7. **Tips for Scenario Designers** - Best practices 8. **Complete Example Scenario** - Full working Ink script 9. **Troubleshooting** - Common issues and solutions ## Example Ink Usage: ```ink VAR card_protocol = "" VAR card_security = "" VAR card_instant_clone = false {card_security == "low": "This is a low-security card. Easy to clone!" # clone_keycard:{card_card_id} -> cloned } {card_needs_attack: "Need to run Darkside attack..." # save_uid_and_start_attack:{card_card_id}|{card_uid} -> wait_for_attack } ``` ## Benefits: 1. **Scenario designers** can write protocol-aware dialogue 2. **NPCs** can react realistically to card security levels 3. **Players** get different experiences based on card type 4. **Automatic** - no manual variable management needed ## Files Modified: - js/minigames/person-chat/person-chat-conversation.js - scenarios/ink/README_RFID_VARIABLES.md (NEW) ## Next Steps: - Create test scenarios for each protocol - Add Ink tag handlers for suggested patterns - Test with various card combinations Phase 5 (Ink Integration) complete! --- .../person-chat/person-chat-conversation.js | 82 ++++- scenarios/ink/README_RFID_VARIABLES.md | 347 ++++++++++++++++++ 2 files changed, 423 insertions(+), 6 deletions(-) create mode 100644 scenarios/ink/README_RFID_VARIABLES.md diff --git a/js/minigames/person-chat/person-chat-conversation.js b/js/minigames/person-chat/person-chat-conversation.js index 4cfb21e..d4d910b 100644 --- a/js/minigames/person-chat/person-chat-conversation.js +++ b/js/minigames/person-chat/person-chat-conversation.js @@ -120,29 +120,29 @@ export default class PersonChatConversation { */ syncItemsToInk() { if (!this.inkEngine || !this.inkEngine.story) return; - + const npc = this.npc; if (!npc || !npc.itemsHeld) return; - + const varState = this.inkEngine.story.variablesState; if (!varState._defaultGlobalVariables) return; - + // Count items by type const itemCounts = {}; npc.itemsHeld.forEach(item => { itemCounts[item.type] = (itemCounts[item.type] || 0) + 1; }); - + // Get all declared has_* variables from the story const declaredVars = Array.from(varState._defaultGlobalVariables.keys()); const hasItemVars = declaredVars.filter(varName => varName.startsWith('has_')); - + // Sync all has_* variables - set to true if NPC has item, false if not hasItemVars.forEach(varName => { // Extract item type from variable name (e.g., "has_lockpick" -> "lockpick") const itemType = varName.replace(/^has_/, ''); const hasItem = (itemCounts[itemType] || 0) > 0; - + try { this.inkEngine.setVariable(varName, hasItem); console.log(`✅ Synced ${varName} = ${hasItem} for NPC ${npc.id} (${itemCounts[itemType] || 0} items)`); @@ -150,6 +150,76 @@ export default class PersonChatConversation { console.warn(`⚠️ Could not sync ${varName}:`, err.message); } }); + + // Also sync card protocol information + this.syncCardProtocolsToInk(); + } + + /** + * Sync RFID card protocol information to Ink variables + * Allows Ink scripts to detect and respond to different card protocols + */ + syncCardProtocolsToInk() { + if (!this.inkEngine || !this.npc || !this.npc.itemsHeld) return; + + // Filter for keycards + const keycards = this.npc.itemsHeld.filter(item => item.type === 'keycard'); + + // Get RFID data manager if available + const dataManager = window.rfidDataManager || (window.RFIDDataManager ? new window.RFIDDataManager() : null); + + keycards.forEach((card, index) => { + const protocol = card.rfid_protocol || 'EM4100'; + const prefix = index === 0 ? 'card' : `card${index + 1}`; + + // Ensure rfid_data exists (generate if using card_id) + if (!card.rfid_data && card.card_id && dataManager) { + card.rfid_data = dataManager.generateRFIDDataFromCardId(card.card_id, protocol); + } + + try { + // Basic card info + this.inkEngine.setVariable(`${prefix}_protocol`, protocol); + this.inkEngine.setVariable(`${prefix}_name`, card.name || 'Card'); + this.inkEngine.setVariable(`${prefix}_card_id`, card.card_id || card.key_id || ''); + + // Security level (low, medium, high) + let security = 'low'; + if (protocol === 'MIFARE_Classic_Custom_Keys') { + security = 'medium'; + } else if (protocol === 'MIFARE_DESFire') { + security = 'high'; + } + this.inkEngine.setVariable(`${prefix}_security`, security); + + // Simplified booleans for common checks + const isInstantClone = protocol === 'EM4100' || protocol === 'MIFARE_Classic_Weak_Defaults'; + this.inkEngine.setVariable(`${prefix}_instant_clone`, isInstantClone); + + const needsAttack = protocol === 'MIFARE_Classic_Custom_Keys'; + this.inkEngine.setVariable(`${prefix}_needs_attack`, needsAttack); + + const isUIDOnly = protocol === 'MIFARE_DESFire'; + this.inkEngine.setVariable(`${prefix}_uid_only`, isUIDOnly); + + // Set UID or hex based on protocol + if (card.rfid_data?.uid) { + this.inkEngine.setVariable(`${prefix}_uid`, card.rfid_data.uid); + } else { + this.inkEngine.setVariable(`${prefix}_uid`, ''); + } + + if (card.rfid_data?.hex) { + this.inkEngine.setVariable(`${prefix}_hex`, card.rfid_data.hex); + } else { + this.inkEngine.setVariable(`${prefix}_hex`, ''); + } + + console.log(`✅ Synced ${prefix}: ${protocol} (card_id: ${card.card_id || card.key_id})`); + } catch (err) { + console.warn(`⚠️ Could not sync card protocol for ${prefix}:`, err.message); + } + }); } /** diff --git a/scenarios/ink/README_RFID_VARIABLES.md b/scenarios/ink/README_RFID_VARIABLES.md new file mode 100644 index 0000000..690246f --- /dev/null +++ b/scenarios/ink/README_RFID_VARIABLES.md @@ -0,0 +1,347 @@ +# RFID Protocol Variables for Ink + +This document describes the RFID card protocol variables that are automatically synced to Ink scripts when an NPC holds keycards. + +## Overview + +When an NPC has keycards in their `itemsHeld` array, the conversation system automatically syncs protocol-specific information to Ink variables. This allows your Ink scripts to react differently based on the card's security level and protocol. + +## Required Variable Declarations + +Add these to the top of your Ink script (adjust based on how many cards the NPC might have): + +```ink +// Card protocol info (auto-synced from NPC itemsHeld) +VAR card_protocol = "" // Protocol name +VAR card_name = "" // Display name +VAR card_card_id = "" // Logical card ID +VAR card_uid = "" // UID (for MIFARE cards) +VAR card_hex = "" // Hex ID (for EM4100 cards) +VAR card_security = "" // "low", "medium", "high" +VAR card_instant_clone = false // true for EM4100 and weak MIFARE +VAR card_needs_attack = false // true for custom key MIFARE +VAR card_uid_only = false // true for DESFire + +// For second card (if NPC has multiple keycards) +VAR card2_protocol = "" +VAR card2_name = "" +// ... (same pattern as above) +``` + +## Variable Reference + +### Per-Card Variables + +Each card gets a prefix: `card`, `card2`, `card3`, etc. + +| Variable | Type | Description | Example Values | +|----------|------|-------------|----------------| +| `{prefix}_protocol` | string | RFID protocol name | `"EM4100"`, `"MIFARE_Classic_Weak_Defaults"`, `"MIFARE_Classic_Custom_Keys"`, `"MIFARE_DESFire"` | +| `{prefix}_name` | string | Card display name | `"Employee Badge"`, `"Security Card"` | +| `{prefix}_card_id` | string | Logical card identifier | `"employee_badge"`, `"master_card"` | +| `{prefix}_security` | string | Security level | `"low"`, `"medium"`, `"high"` | +| `{prefix}_instant_clone` | boolean | Can clone instantly? | `true` for EM4100 and weak MIFARE | +| `{prefix}_needs_attack` | boolean | Needs key attack? | `true` for custom key MIFARE | +| `{prefix}_uid_only` | boolean | UID-only emulation? | `true` for DESFire | +| `{prefix}_uid` | string | Card UID (if MIFARE) | `"A1B2C3D4"` | +| `{prefix}_hex` | string | Card hex ID (if EM4100) | `"01AB34CD56"` | + +## Protocol Characteristics + +### EM4100 (Low Security) +- **Instant clone**: Yes +- **Attack required**: No +- **Full emulation**: Yes +- **Use case**: Entry-level cards, parking garage, old hotel keys + +```ink +{card_protocol == "EM4100": + + [Scan badge] + # clone_keycard:{card_card_id} + Easy! This old 125kHz card clones instantly. + -> cloned +} +``` + +### MIFARE Classic - Weak Defaults (Low Security) +- **Instant clone**: Dictionary attack succeeds (~95%) +- **Attack required**: Dictionary (instant) +- **Full emulation**: Yes +- **Use case**: Cheap hotels, old transit cards, poorly maintained systems + +```ink +{card_instant_clone && card_protocol == "MIFARE_Classic_Weak_Defaults": + + [Scan badge] + # clone_keycard:{card_card_id} + This card uses factory default keys - dictionary attack works! + -> cloned +} +``` + +### MIFARE Classic - Custom Keys (Medium Security) +- **Instant clone**: No +- **Attack required**: Darkside (~30 seconds) +- **Full emulation**: Yes (after cracking keys) +- **Use case**: Corporate badges, banks, government facilities + +```ink +{card_needs_attack: + + [Scan badge] + # save_uid_and_start_attack:{card_card_id}|{card_uid} + Custom keys detected. Need to run Darkside attack... + This will take about 30 seconds. + -> wait_for_attack +} +``` + +### MIFARE DESFire (High Security) +- **Instant clone**: No +- **Attack required**: N/A (impossible to crack) +- **Full emulation**: UID only (works on poorly-configured readers) +- **Use case**: High-security government, military, modern banking + +```ink +{card_uid_only: + + [Try to scan] + # save_uid_only:{card_card_id}|{card_uid} + High security card - I can only save the UID. + It might work on poorly-configured readers. + -> uid_saved +} +``` + +## Usage Examples + +### Example 1: Simple EM4100 Clone + +```ink +=== meet_security_guard === +{has_keycard: + + [Ask about the badge] + You see a badge clipped to their belt. + {card_protocol == "EM4100": + "It's just a basic proximity card. Nothing special." + -> offer_to_scan + } +} + +=== offer_to_scan === ++ [Offer to "check" their badge] + You offer to scan their badge with your Flipper Zero. + {card_instant_clone: + # clone_keycard:{card_card_id} + "Sure, go ahead!" They hold it out to you. + Your device quickly reads and clones the card. + -> cloned_success + } +``` + +### Example 2: Multi-Protocol Detection + +```ink +=== analyze_card === +{card_security == "low": + "This is a low-security card. Easy to clone!" + -> easy_clone +} +{card_security == "medium": + "Medium security. I'll need to run an attack." + -> needs_attack +} +{card_security == "high": + "High security DESFire. I can only get the UID." + -> uid_only +} + +=== easy_clone === ++ [Clone it] + # clone_keycard:{card_card_id} + Done! Cloned instantly. + -> END + +=== needs_attack === ++ [Run Darkside attack] + # save_uid_and_start_attack:{card_card_id}|{card_uid} + Starting attack... this will take about 30 seconds. + -> wait_for_crack + +=== uid_only === ++ [Save UID] + # save_uid_only:{card_card_id}|{card_uid} + UID saved. Might work on weak readers. + -> END +``` + +### Example 3: Conditional Dialogue Based on Protocol + +```ink +=== guard_conversation === +Guard: "This is my access badge." + ++ [Ask about security] + You: "What kind of badge is it?" + {card_protocol == "EM4100": + Guard: "Just a basic prox card. Works fine." + // Easy target + } + {card_protocol == "MIFARE_Classic_Weak_Defaults": + Guard: "It's a MIFARE card. Standard issue." + // Still easy, but sounds more secure + } + {card_protocol == "MIFARE_Classic_Custom_Keys": + Guard: "MIFARE Classic with custom encryption." + // They know a bit about security + } + {card_protocol == "MIFARE_DESFire": + Guard: "DESFire EV2. Military-grade security." + // High-security environment + } + -> guard_conversation + ++ {has_rfid_cloner} [Ask to scan it] + {card_instant_clone: + // Easy clone + Guard: "Sure, go ahead." + # clone_keycard:{card_card_id} + -> cloned + } + {card_needs_attack: + // Need attack + Guard: "I don't know... this is secure." + -> need_persuasion + } + {card_uid_only: + // UID only + Guard: "No way. This is a DESFire card." + -> refused + } +``` + +## Ink Tags for RFID Actions + +Use these tags to trigger RFID operations from Ink: + +| Tag | Description | Example | +|-----|-------------|---------| +| `# clone_keycard:{card_id}` | Clone a card instantly | `# clone_keycard:employee_badge` | +| `# save_uid_only:{card_id}\|{uid}` | Save UID only (DESFire) | `# save_uid_only:exec_card\|A1B2C3D4E5F6` | +| `# save_uid_and_start_attack:{card_id}\|{uid}` | Start Darkside attack | `# save_uid_and_start_attack:secure_badge\|12345678` | + +Note: The actual implementation of these tags depends on your tag handler. These are suggested patterns. + +## Scenario JSON Format + +Define keycards in your scenario using the simplified `card_id` format: + +```json +{ + "npcId": "security_guard", + "itemsHeld": [ + { + "type": "keycard", + "card_id": "employee_badge", + "rfid_protocol": "EM4100", + "name": "Employee Badge" + } + ] +} +``` + +The technical RFID data (hex, UID, etc.) is generated automatically from `card_id`. + +## Tips for Scenario Designers + +1. **Check security level first**: Use `card_security` for quick branching +2. **Use boolean helpers**: `card_instant_clone`, `card_needs_attack`, `card_uid_only` are easier than checking protocol names +3. **Provide context**: NPCs with high-security cards should acknowledge the security ("This is a DESFire card") +4. **Realistic behavior**: Security-conscious NPCs shouldn't casually hand over DESFire cards +5. **Time pressure**: Custom key attacks take ~30 seconds - use this for tension + +## Complete Example Scenario + +```ink +VAR card_protocol = "" +VAR card_name = "" +VAR card_card_id = "" +VAR card_security = "" +VAR card_instant_clone = false +VAR card_needs_attack = false +VAR card_uid_only = false +VAR card_uid = "" +VAR has_rfid_cloner = false + +-> hotel_reception + +=== hotel_reception === +You approach the hotel reception desk. +{has_keycard: + The receptionist has a {card_name} on a lanyard. + + + [Ask about room access] + "All our rooms use RFID cards for security." + {card_security == "low": + "Basic prox cards. Nothing fancy." + } + {card_security == "medium": + "We use MIFARE Classic with custom keys." + } + {card_security == "high": + "DESFire cards. Top of the line security." + } + -> reception_menu +} +-> reception_menu + +=== reception_menu === ++ [Ask to see their card] + {card_instant_clone: + "Sure!" They hold it out carelessly. + # clone_keycard:{card_card_id} + Your Flipper Zero quickly clones it. + -> cloned_success + } + {card_needs_attack: + "I... suppose?" They seem hesitant. + You'll need to run an attack. + -> attempt_attack + } + {card_uid_only: + "No way. These are secure." + You can only get the UID if you steal it. + -> refused + } + ++ [Leave] + -> END + +=== cloned_success === +Success! You now have a copy of {card_name}. +-> END + +=== attempt_attack === ++ [Run Darkside attack] + # save_uid_and_start_attack:{card_card_id}|{card_uid} + Starting attack... + [30 seconds pass] + Success! All keys cracked. + -> END + +=== refused === +You'll need to find another way. +-> END +``` + +## Troubleshooting + +**Variables not syncing?** +- Ensure variables are declared at the top of your Ink script +- Check console for sync messages: `✅ Synced card: ...` +- Verify NPC has keycards in `itemsHeld` array + +**Protocol always shows EM4100?** +- Check that `rfid_protocol` is set in scenario JSON +- Default protocol is EM4100 if not specified + +**Boolean helpers not working?** +- Make sure to declare boolean variables: `VAR card_instant_clone = false` +- Don't use string comparisons for booleans