Advertisement
Guest User

Untitled

a guest
Jun 7th, 2025
85
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Ultimate GBA ROM Patcher
  3.  * Combines SRAM patching and battery patching functionality with exact original logic
  4.  */
  5.  
  6. class UltimateROMPatcher {
  7.     constructor() {
  8.         this.progressSteps = [
  9.             'Validating ROM file',
  10.             'Applying SRAM patches',
  11.             'Injecting battery payload',
  12.             'Hooking save functions',
  13.             'Finalizing ROM'
  14.         ];
  15.         this.currentStep = 0;
  16.        
  17.         // Initialize UI elements
  18.         this.initializeElements();
  19.         this.bindEvents();
  20.        
  21.         // Initialize UI state
  22.         this.initializeUIState();
  23.        
  24.         // Initialize patterns and payload from original projects
  25.         this.initializeSramPatterns();
  26.         this.initializeBatteryPatterns();
  27.     }
  28.  
  29.     initializeElements() {
  30.         this.elements = {
  31.             romFile: document.getElementById('romFile'),
  32.             enableSramPatching: document.getElementById('enableSramPatching'),
  33.             enableBatteryPatching: document.getElementById('enableBatteryPatching'),
  34.             batteryModeGroup: document.getElementById('batteryModeGroup'),
  35.             batteryModeRadios: document.querySelectorAll('input[name="batteryMode"]'),
  36.             patchButton: document.getElementById('patchButton'),
  37.             fileInfo: document.getElementById('fileInfo'),
  38.             progressSection: document.getElementById('progressSection'),
  39.             progressFill: document.getElementById('progressFill'),
  40.             progressText: document.getElementById('progressText'),
  41.             progressSteps: document.getElementById('progressSteps'),
  42.             statusMessages: document.getElementById('statusMessages'),
  43.             downloadSection: document.getElementById('downloadSection'),
  44.             downloadLink: document.getElementById('downloadLink')
  45.         };
  46.     }
  47.  
  48.     bindEvents() {
  49.         this.elements.romFile.addEventListener('change', (e) => this.handleFileSelect(e));
  50.         this.elements.patchButton.addEventListener('click', () => this.startPatching());
  51.         this.elements.enableBatteryPatching.addEventListener('change', (e) => this.toggleBatteryMode(e));
  52.        
  53.         // Initialize drag and drop functionality
  54.         this.initializeDragAndDrop();
  55.     }
  56.  
  57.     initializeUIState() {
  58.         // Set initial state for battery mode options based on checkbox
  59.         const batteryEnabled = this.elements.enableBatteryPatching.checked;
  60.         this.toggleBatteryModeState(batteryEnabled);
  61.     }
  62.  
  63.     toggleBatteryMode(event) {
  64.         const isEnabled = event.target.checked;
  65.         this.toggleBatteryModeState(isEnabled);
  66.     }
  67.  
  68.     toggleBatteryModeState(isEnabled) {
  69.         const batteryRadios = this.elements.batteryModeRadios;
  70.        
  71.         batteryRadios.forEach(radio => {
  72.             radio.disabled = !isEnabled;
  73.         });
  74.        
  75.         this.elements.batteryModeGroup.classList.toggle('disabled', !isEnabled);
  76.     }
  77.  
  78.     // DRAG AND DROP FUNCTIONALITY
  79.     initializeDragAndDrop() {
  80.         const dropZone = document.getElementById('dropZone');
  81.        
  82.         // Prevent default drag behaviors
  83.         ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
  84.             dropZone.addEventListener(eventName, this.preventDefaults.bind(this), false);
  85.             document.body.addEventListener(eventName, this.preventDefaults.bind(this), false);
  86.         });
  87.  
  88.         // Highlight drop zone when item is dragged over it
  89.         ['dragenter', 'dragover'].forEach(eventName => {
  90.             dropZone.addEventListener(eventName, this.highlight.bind(this), false);
  91.         });
  92.  
  93.         ['dragleave', 'drop'].forEach(eventName => {
  94.             dropZone.addEventListener(eventName, this.unhighlight.bind(this), false);
  95.         });
  96.  
  97.         // Handle dropped files
  98.         dropZone.addEventListener('drop', this.handleDrop.bind(this), false);
  99.     }
  100.  
  101.     preventDefaults(e) {
  102.         e.preventDefault();
  103.         e.stopPropagation();
  104.     }
  105.  
  106.     highlight(e) {
  107.         const dropZone = document.getElementById('dropZone');
  108.         dropZone.classList.add('drag-over');
  109.        
  110.         // Check if dragged item contains files
  111.         if (e.dataTransfer.items) {
  112.             const hasFiles = Array.from(e.dataTransfer.items).some(item => item.kind === 'file');
  113.             if (hasFiles) {
  114.                 dropZone.classList.add('drag-active');
  115.             }
  116.         }
  117.     }
  118.  
  119.     unhighlight(e) {
  120.         const dropZone = document.getElementById('dropZone');
  121.         dropZone.classList.remove('drag-over', 'drag-active');
  122.     }
  123.  
  124.     handleDrop(e) {
  125.         const dt = e.dataTransfer;
  126.         const files = dt.files;
  127.        
  128.         this.handleFiles(files);
  129.     }
  130.  
  131.     handleFiles(files) {
  132.         if (files.length === 0) {
  133.             this.showFileInfo('❌ No files dropped', 'error');
  134.             return;
  135.         }
  136.        
  137.         if (files.length > 1) {
  138.             this.showFileInfo('❌ Please drop only one ROM file', 'error');
  139.             this.elements.patchButton.disabled = true;
  140.             return;
  141.         }
  142.        
  143.         const file = files[0];
  144.        
  145.         // Validate file type
  146.         if (!file.name.toLowerCase().endsWith('.gba')) {
  147.             this.showFileInfo('❌ Please drop a .gba file', 'error');
  148.             this.elements.patchButton.disabled = true;
  149.             return;
  150.         }
  151.        
  152.         // Validate file size (32MB max)
  153.         const maxSize = 32 * 1024 * 1024; // 32MB
  154.         if (file.size > maxSize) {
  155.             this.showFileInfo('❌ ROM file too large (max 32MB)', 'error');
  156.             this.elements.patchButton.disabled = true;
  157.             return;
  158.         }
  159.        
  160.         // Update the file input with the dropped file
  161.         this.updateFileInput(file);
  162.        
  163.         // Process the file as if it was selected normally
  164.         this.showFileInfo(`✅ ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`, 'success');
  165.         this.elements.patchButton.disabled = false;
  166.        
  167.         // Update drop zone text
  168.         this.updateDropZoneText('ROM file loaded - Ready to patch!');
  169.     }
  170.  
  171.     updateFileInput(file) {
  172.         // Create a new FileList with the dropped file
  173.         const dt = new DataTransfer();
  174.         dt.items.add(file);
  175.         this.elements.romFile.files = dt.files;
  176.     }
  177.  
  178.     updateDropZoneText(text) {
  179.         const fileText = document.querySelector('.file-text');
  180.         if (fileText) {
  181.             fileText.textContent = text;
  182.         }
  183.     }
  184.  
  185.     // EXACT SRAM PATCHING LOGIC FROM ORIGINAL PROJECT - COMPLETE VERSION
  186.     initializeSramPatterns() {
  187.         // Define byte patterns from Patterns.h - EXACT COPY
  188.         const IDENT_FLASH1M_V102 = [0x46, 0x4C, 0x41, 0x53, 0x48, 0x31, 0x4D, 0x5F, 0x56, 0x31, 0x30, 0x32, 0x00];
  189.         const FLASH1M_V102_MARKER_1 = [0x05, 0x4b, 0xaa, 0x21, 0x19, 0x70, 0x05, 0x4a, 0x55, 0x21, 0x11, 0x70, 0xb0, 0x21, 0x19, 0x70, 0xe0, 0x21, 0x09, 0x05, 0x08, 0x70, 0x70, 0x47];
  190.         const FLASH1M_V102_REPLACE_1 = [0x05, 0x4B, 0x80, 0x21, 0x09, 0x02, 0x09, 0x22, 0x12, 0x06, 0x9F, 0x44, 0x90, 0x21, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00, 0x08, 0x70, 0x70, 0x47];
  191.         const FLASH1M_V102_MARKER_2 = [0x55, 0x55, 0x00, 0x0e, 0xaa, 0x2a, 0x00, 0x0e, 0x30, 0xb5, 0x91, 0xb0, 0x68, 0x46, 0x00, 0xf0, 0xf3, 0xf8, 0x6d, 0x46, 0x01, 0x35];
  192.         const FLASH1M_V102_REPLACE_2 = [0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x30, 0xB5, 0x91, 0xB0, 0x68, 0x46, 0x00, 0xF0, 0xF3, 0xF8, 0x6D, 0x46, 0x01, 0x35];
  193.         const FLASH1M_V102_MARKER_3 = [0x06, 0x4A, 0xAA, 0x20, 0x10, 0x70, 0x05, 0x49, 0x55, 0x20, 0x08, 0x70, 0x90, 0x20, 0x10, 0x70, 0x10, 0xA9, 0x03, 0x4A, 0x10, 0x1C, 0x08, 0xE0, 0x00, 0x00, 0x55, 0x55, 0x00, 0x0E, 0xAA, 0x2A, 0x00, 0x0E, 0x20, 0x4E, 0x00, 0x00, 0x08, 0x88, 0x01, 0x38, 0x08, 0x80, 0x08, 0x88, 0x00, 0x28, 0xF9, 0xD1, 0x0C, 0x48];
  194.         const FLASH1M_V102_REPLACE_3 = [0x06, 0x4A, 0xAA, 0x20, 0x00, 0x00, 0x05, 0x49, 0x55, 0x20, 0x00, 0x00, 0x90, 0x20, 0x00, 0x00, 0x10, 0xA9, 0x03, 0x4A, 0x10, 0x1C, 0x08, 0xE0, 0x00, 0x00, 0x55, 0x55, 0x00, 0x0E, 0xAA, 0x2A, 0x00, 0x0E, 0x20, 0x4E, 0x00, 0x00, 0x08, 0x88, 0x01, 0x38, 0x08, 0x80, 0x08, 0x88, 0x00, 0x28, 0xF9, 0xD1, 0x0C, 0x48, 0x13, 0x20, 0x13, 0x20, 0x00, 0x06, 0x04, 0x0C, 0xE0, 0x20, 0x00, 0x05, 0x62, 0x20, 0x62, 0x20, 0x00, 0x06, 0x00, 0x0E, 0x04, 0x43, 0x07, 0x49, 0xAA, 0x20, 0x00, 0x00, 0x07, 0x4A, 0x55, 0x20, 0x00, 0x00, 0xF0, 0x20, 0x00, 0x00, 0x00, 0x00];
  195.         const FLASH1M_V102_MARKER_4 = [0x14, 0x49, 0xAA, 0x24, 0x0C, 0x70, 0x13, 0x4B, 0x55, 0x22, 0x1A, 0x70, 0x80, 0x20, 0x08, 0x70, 0x0C, 0x70, 0x1A, 0x70, 0x10, 0x20, 0x08, 0x70];
  196.         const FLASH1M_V102_REPLACE_4 = [0x0E, 0x21, 0x09, 0x06, 0xFF, 0x24, 0x80, 0x22, 0x13, 0x4B, 0x52, 0x02, 0x01, 0x3A, 0x8C, 0x54, 0xFC, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
  197.         const FLASH1M_V102_MARKER_5 = [0x13, 0x49, 0xAA, 0x25, 0x0D, 0x70, 0x13, 0x4B, 0x55, 0x22, 0x1A, 0x70, 0x80, 0x20, 0x08, 0x70, 0x0D, 0x70, 0x1A, 0x70, 0x30, 0x20, 0x20, 0x70];
  198.         const FLASH1M_V102_REPLACE_5 = [0x13, 0x49, 0xFF, 0x25, 0x08, 0x22, 0x00, 0x00, 0x52, 0x02, 0x01, 0x3A, 0xA5, 0x54, 0xFC, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
  199.         const FLASH1M_V102_MARKER_6 = [0x0A, 0x4C, 0xAA, 0x22, 0x22, 0x70, 0x09, 0x4B, 0x55, 0x22, 0x1A, 0x70, 0xA0, 0x22, 0x22, 0x70, 0x02, 0x78, 0x0A, 0x70];
  200.         const FLASH1M_V102_REPLACE_6 = [0x0A, 0x4C, 0xAA, 0x22, 0x00, 0x00, 0x09, 0x4B, 0x55, 0x22, 0x00, 0x00, 0xA0, 0x22, 0x00, 0x00, 0x02, 0x78, 0x0A, 0x70];
  201.  
  202.         const IDENT_FLASH1M_V103 = [0x46, 0x4C, 0x41, 0x53, 0x48, 0x31, 0x4D, 0x5F, 0x56, 0x31, 0x30, 0x33, 0x00];
  203.         const FLASH1M_V103_MARKER_1 = [0x05, 0x4b, 0xaa, 0x21, 0x19, 0x70, 0x05, 0x4a, 0x55, 0x21, 0x11, 0x70, 0xb0, 0x21, 0x19, 0x70, 0xe0, 0x21, 0x09, 0x05, 0x08, 0x70, 0x70, 0x47];
  204.         const FLASH1M_V103_REPLACE_1 = [0x05, 0x4B, 0x80, 0x21, 0x09, 0x02, 0x09, 0x22, 0x12, 0x06, 0x9F, 0x44, 0x90, 0x21, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00, 0x08, 0x70, 0x70, 0x47];
  205.         const FLASH1M_V103_MARKER_2 = [0x55, 0x55, 0x00, 0x0e, 0xaa, 0x2a, 0x00, 0x0e, 0x30, 0xb5, 0x91, 0xb0, 0x68, 0x46, 0x00, 0xf0, 0xf3, 0xf8, 0x6d, 0x46, 0x01, 0x35];
  206.         const FLASH1M_V103_REPLACE_2 = [0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x30, 0xB5, 0x91, 0xB0, 0x68, 0x46, 0x00, 0xF0, 0xF3, 0xF8, 0x6D, 0x46, 0x01, 0x35];
  207.         const FLASH1M_V103_MARKER_3 = [0x06, 0x4A, 0xAA, 0x20, 0x10, 0x70, 0x05, 0x49, 0x55, 0x20, 0x08, 0x70, 0x90, 0x20, 0x10, 0x70, 0x10, 0xA9, 0x03, 0x4A, 0x10, 0x1C, 0x08, 0xE0, 0x00, 0x00, 0x55, 0x55, 0x00, 0x0E, 0xAA, 0x2A, 0x00, 0x0E, 0x20, 0x4E, 0x00, 0x00, 0x08, 0x88, 0x01, 0x38, 0x08, 0x80, 0x08, 0x88, 0x00, 0x28, 0xF9, 0xD1, 0x0C, 0x48];
  208.         const FLASH1M_V103_REPLACE_3 = [0x06, 0x4A, 0xAA, 0x20, 0x00, 0x00, 0x05, 0x49, 0x55, 0x20, 0x00, 0x00, 0x90, 0x20, 0x00, 0x00, 0x10, 0xA9, 0x03, 0x4A, 0x10, 0x1C, 0x08, 0xE0, 0x00, 0x00, 0x55, 0x55, 0x00, 0x0E, 0xAA, 0x2A, 0x00, 0x0E, 0x20, 0x4E, 0x00, 0x00, 0x08, 0x88, 0x01, 0x38, 0x08, 0x80, 0x08, 0x88, 0x00, 0x28, 0xF9, 0xD1, 0x0C, 0x48, 0x13, 0x20, 0x13, 0x20, 0x00, 0x06, 0x04, 0x0C, 0xE0, 0x20, 0x00, 0x05, 0x62, 0x20, 0x62, 0x20, 0x00, 0x06, 0x00, 0x0E, 0x04, 0x43, 0x07, 0x49, 0xAA, 0x20, 0x00, 0x00, 0x07, 0x4A, 0x55, 0x20, 0x00, 0x00, 0xF0, 0x20, 0x00, 0x00, 0x00, 0x00];
  209.         const FLASH1M_V103_MARKER_4 = [0x14, 0x49, 0xAA, 0x24, 0x0C, 0x70, 0x13, 0x4B, 0x55, 0x22, 0x1A, 0x70, 0x80, 0x20, 0x08, 0x70, 0x0C, 0x70, 0x1A, 0x70, 0x10, 0x20, 0x08, 0x70];
  210.         const FLASH1M_V103_REPLACE_4 = [0x0E, 0x21, 0x09, 0x06, 0xFF, 0x24, 0x80, 0x22, 0x13, 0x4B, 0x52, 0x02, 0x01, 0x3A, 0x8C, 0x54, 0xFC, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
  211.         const FLASH1M_V103_MARKER_5 = [0x14, 0x49, 0xAA, 0x25, 0x0D, 0x70, 0x14, 0x4B, 0x55, 0x22, 0x1A, 0x70, 0x80, 0x20, 0x08, 0x70, 0x0D, 0x70, 0x1A, 0x70, 0x30, 0x20, 0x20, 0x70];
  212.         const FLASH1M_V103_REPLACE_5 = [0x14, 0x49, 0xFF, 0x25, 0x08, 0x22, 0x00, 0x00, 0x52, 0x02, 0x01, 0x3A, 0xA5, 0x54, 0xFC, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
  213.         const FLASH1M_V103_MARKER_6 = [0x0C, 0x4A, 0xAA, 0x20, 0x10, 0x70, 0x0B, 0x49, 0x55, 0x20, 0x08, 0x70, 0xA0, 0x20, 0x10, 0x70, 0x27, 0x70, 0x09, 0x48];
  214.         const FLASH1M_V103_REPLACE_6 = [0x0C, 0x4A, 0xAA, 0x20, 0x00, 0x00, 0x0B, 0x49, 0x55, 0x20, 0x00, 0x00, 0xA0, 0x20, 0x00, 0x00, 0x27, 0x70, 0x09, 0x48];
  215.         const FLASH1M_V103_MARKER_7 = [0x0A, 0x4C, 0xAA, 0x22, 0x22, 0x70, 0x09, 0x4B, 0x55, 0x22, 0x1A, 0x70, 0xA0, 0x22, 0x22, 0x70, 0x02, 0x78, 0x0A, 0x70];
  216.         const FLASH1M_V103_REPLACE_7 = [0x0A, 0x4C, 0xAA, 0x22, 0x00, 0x00, 0x09, 0x4B, 0x55, 0x22, 0x00, 0x00, 0xA0, 0x22, 0x00, 0x00, 0x02, 0x78, 0x0A, 0x70];
  217.  
  218.         const IDENT_FLASH512 = [0x46, 0x4C, 0x41, 0x53, 0x48, 0x35, 0x31, 0x32, 0x00];
  219.         const FLASH512_MARKER_1 = [0xf0, 0xb5, 0xa0, 0xb0, 0x0d, 0x1c, 0x16, 0x1c, 0x1f, 0x1c, 0x03, 0x04, 0x1c, 0x0c, 0x0f, 0x4a, 0x10, 0x88, 0x0f, 0x49, 0x08, 0x40, 0x03, 0x21, 0x08, 0x43, 0x10, 0x80, 0x0d, 0x48, 0x00, 0x68, 0x01, 0x68, 0x80, 0x20, 0x80, 0x02];
  220.         const FLASH512_REPLACE_1 = [0x70, 0xb5, 0xa0, 0xb0, 0x00, 0x03, 0x40, 0x18, 0xe0, 0x21, 0x09, 0x05, 0x09, 0x18, 0x08, 0x78, 0x10, 0x70, 0x01, 0x3b, 0x01, 0x32, 0x01, 0x31, 0x00, 0x2b, 0xf8, 0xd1, 0x00, 0x20, 0x20, 0xb0, 0x70, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  221.         const FLASH512_MARKER_2 = [0xff, 0xf7, 0x88, 0xfd, 0x00, 0x04, 0x03, 0x0c];
  222.         const FLASH512_REPLACE_2 = [0x1b, 0x23, 0x1b, 0x02, 0x32, 0x20, 0x03, 0x43];
  223.         const FLASH512_MARKER_3 = [0x70, 0xb5, 0x90, 0xb0, 0x15, 0x4d, 0x29, 0x88];
  224.         const FLASH512_REPLACE_3 = [0x00, 0xb5, 0x00, 0x20, 0x02, 0xbc, 0x08, 0x47];
  225.         const FLASH512_MARKER_4 = [0x70, 0xb5, 0x46, 0x46, 0x40, 0xb4, 0x90, 0xb0];
  226.         const FLASH512_REPLACE_4 = [0x00, 0xb5, 0x00, 0x20, 0x02, 0xbc, 0x08, 0x47];
  227.         const FLASH512_MARKER_5 = [0xf0, 0xb5, 0x90, 0xb0, 0x0f, 0x1c, 0x00, 0x04, 0x04, 0x0c, 0x03, 0x48, 0x00, 0x68, 0x40, 0x89, 0x84, 0x42, 0x05, 0xd3, 0x01, 0x48, 0x41, 0xe0];
  228.         const FLASH512_REPLACE_5 = [0x7c, 0xb5, 0x90, 0xb0, 0x00, 0x03, 0x0a, 0x1c, 0xe0, 0x21, 0x09, 0x05, 0x09, 0x18, 0x01, 0x23, 0x1b, 0x03, 0x10, 0x78, 0x08, 0x70, 0x01, 0x3b, 0x01, 0x32, 0x01, 0x31, 0x00, 0x2b, 0xf8, 0xd1, 0x00, 0x20, 0x10, 0xb0, 0x7c, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  229.  
  230.         // Additional patterns from C++ source - EXACT COPY FROM ORIGINAL
  231.         const IDENT_FLASH_V120 = [0x46, 0x4C, 0x41, 0x53, 0x48, 0x5F, 0x56, 0x31, 0x32, 0x30]; // "FLASH_V120"
  232.         const IDENT_FLASH_V121 = [0x46, 0x4C, 0x41, 0x53, 0x48, 0x5F, 0x56, 0x31, 0x32, 0x31]; // "FLASH_V121"
  233.         const IDENT_FLASH_V123 = [0x46, 0x4C, 0x41, 0x53, 0x48, 0x5F, 0x56, 0x31, 0x32, 0x33]; // "FLASH_V123"
  234.         const IDENT_FLASH_V124 = [0x46, 0x4C, 0x41, 0x53, 0x48, 0x5F, 0x56, 0x31, 0x32, 0x34]; // "FLASH_V124"
  235.         const IDENT_FLASH_V125 = [0x46, 0x4C, 0x41, 0x53, 0x48, 0x5F, 0x56, 0x31, 0x32, 0x35]; // "FLASH_V125"
  236.         const IDENT_FLASH_V126 = [0x46, 0x4C, 0x41, 0x53, 0x48, 0x5F, 0x56, 0x31, 0x32, 0x36]; // "FLASH_V126"
  237.         const IDENT_EEPROM_V120 = [0x45, 0x45, 0x50, 0x52, 0x4F, 0x4D, 0x5F, 0x56, 0x31, 0x32, 0x30]; // "EEPROM_V120"
  238.         const IDENT_EEPROM_V121 = [0x45, 0x45, 0x50, 0x52, 0x4F, 0x4D, 0x5F, 0x56, 0x31, 0x32, 0x31]; // "EEPROM_V121"
  239.         const IDENT_EEPROM_V122 = [0x45, 0x45, 0x50, 0x52, 0x4F, 0x4D, 0x5F, 0x56, 0x31, 0x32, 0x32]; // "EEPROM_V122"
  240.         const IDENT_EEPROM_V124 = [0x45, 0x45, 0x50, 0x52, 0x4F, 0x4D, 0x5F, 0x56, 0x31, 0x32, 0x34]; // "EEPROM_V124"
  241.         const IDENT_EEPROM_V126 = [0x45, 0x45, 0x50, 0x52, 0x4F, 0x4D, 0x5F, 0x56, 0x31, 0x32, 0x36]; // "EEPROM_V126"
  242.  
  243.         // Patch patterns from C++ source (flash.cpp and eeprom.cpp) - EXACT COPY FROM ORIGINAL
  244.  
  245.         // FLASH_1 (FLASH_V120, FLASH_V121)
  246.         const FLASH_1_MARKER_1 = [0x90, 0xb5, 0x93, 0xb0, 0x6f, 0x46, 0x39, 0x1d, 0x08, 0x1c, 0x00, 0xf0];
  247.         const FLASH_1_REPLACE_1 = [0x00, 0xb5, 0x3d, 0x20, 0x00, 0x02, 0x1f, 0x21, 0x08, 0x43, 0x02, 0xbc, 0x08, 0x47];
  248.         const FLASH_1_MARKER_2 = [0x80, 0xb5, 0x94, 0xb0, 0x6f, 0x46, 0x39, 0x1c, 0x08, 0x80, 0x38, 0x1c, 0x01, 0x88, 0x0f, 0x29, 0x04, 0xd9, 0x01, 0x48, 0x56, 0xe0, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x23, 0x48, 0x23, 0x49, 0x0a, 0x88, 0x23];
  249.         const FLASH_1_REPLACE_2 = [0x7c, 0xb5, 0x00, 0x07, 0x00, 0x0c, 0xe0, 0x21, 0x09, 0x05, 0x09, 0x18, 0x01, 0x23, 0x1b, 0x03, 0xff, 0x20, 0x08, 0x70, 0x01, 0x3b, 0x01, 0x31, 0x00, 0x2b, 0xfa, 0xd1, 0x00, 0x20, 0x7c, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  250.         const FLASH_1_MARKER_3 = [0x80, 0xb5, 0x94, 0xb0, 0x6f, 0x46, 0x79, 0x60, 0x39, 0x1c, 0x08, 0x80, 0x38, 0x1c, 0x01, 0x88, 0x0f, 0x29, 0x03, 0xd9, 0x00, 0x48, 0x73, 0xe0, 0xff, 0x80, 0x00, 0x00, 0x38, 0x1c, 0x01, 0x88];
  251.         const FLASH_1_REPLACE_3 = [0x7c, 0xb5, 0x90, 0xb0, 0x00, 0x03, 0x0a, 0x1c, 0xe0, 0x21, 0x09, 0x05, 0x09, 0x18, 0x01, 0x23, 0x1b, 0x03, 0x10, 0x78, 0x08, 0x70, 0x01, 0x3b, 0x01, 0x32, 0x01, 0x31, 0x00, 0x2b, 0xf8, 0xd1, 0x00, 0x20, 0x10, 0xb0, 0x7c, 0xbc, 0x08, 0xbc, 0x08, 0x47];
  252.  
  253.         // FLASH_2 (FLASH_V123, FLASH_V124)
  254.         const FLASH_2_MARKER_1 = [0xff, 0xf7, 0xaa, 0xff, 0x00, 0x04, 0x03, 0x0c];
  255.         const FLASH_2_REPLACE_1 = [0x1b, 0x23, 0x1b, 0x02, 0x32, 0x20, 0x03, 0x43];
  256.         const FLASH_2_MARKER_2 = [0x70, 0xb5, 0x90, 0xb0, 0x15, 0x4d];
  257.         const FLASH_2_REPLACE_2 = [0x00, 0x20, 0x70, 0x47, 0x15, 0x4d];
  258.         const FLASH_2_MARKER_3 = [0x70, 0xb5, 0x46, 0x46, 0x40, 0xb4, 0x90, 0xb0, 0x00];
  259.         const FLASH_2_REPLACE_3 = [0x00, 0x20, 0x70, 0x47, 0x40, 0xb4, 0x90, 0xb0, 0x00];
  260.         const FLASH_2_MARKER_4 = [0xf0, 0xb5, 0x90, 0xb0, 0x0f, 0x1c, 0x00, 0x04, 0x04, 0x0c, 0x0f, 0x2c, 0x04, 0xd9, 0x01, 0x48, 0x40, 0xe0, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x20, 0x1c, 0xff, 0xf7, 0xd7, 0xfe, 0x00, 0x04, 0x05, 0x0c, 0x00, 0x2d, 0x35, 0xd1];
  261.         const FLASH_2_REPLACE_4 = [0x70, 0xb5, 0x00, 0x03, 0x0a, 0x1c, 0xe0, 0x21, 0x09, 0x05, 0x41, 0x18, 0x01, 0x23, 0x1b, 0x03, 0x10, 0x78, 0x08, 0x70, 0x01, 0x3b, 0x01, 0x32, 0x01, 0x31, 0x00, 0x2b, 0xf8, 0xd1, 0x00, 0x20, 0x70, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  262.  
  263.         // FLASH_3 (FLASH_V125, FLASH_V126)
  264.         const FLASH_3_MARKER_1 = [0xff, 0xf7, 0xaa, 0xff, 0x00, 0x04, 0x03, 0x0c];
  265.         const FLASH_3_REPLACE_1 = [0x1b, 0x23, 0x1b, 0x02, 0x32, 0x20, 0x03, 0x43];
  266.         const FLASH_3_MARKER_2 = [0x70, 0xb5, 0x90, 0xb0, 0x15, 0x4d];
  267.         const FLASH_3_REPLACE_2 = [0x00, 0x20, 0x70, 0x47, 0x15, 0x4d];
  268.         const FLASH_3_MARKER_3 = [0x70, 0xb5, 0x46, 0x46, 0x40, 0xb4, 0x90, 0xb0, 0x00];
  269.         const FLASH_3_REPLACE_3 = [0x00, 0x20, 0x70, 0x47, 0x40, 0xb4, 0x90, 0xb0, 0x00];
  270.         const FLASH_3_MARKER_4 = [0xf0, 0xb5, 0x90, 0xb0, 0x0f, 0x1c, 0x00, 0x04, 0x04, 0x0c, 0x0f, 0x2c, 0x04, 0xd9, 0x01, 0x48, 0x40, 0xe0, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x20, 0x1c, 0xff, 0xf7, 0xd7, 0xfe, 0x00, 0x04, 0x05, 0x0c, 0x00, 0x2d, 0x35, 0xd1];
  271.         const FLASH_3_REPLACE_4 = [0x70, 0xb5, 0x00, 0x03, 0x0a, 0x1c, 0xe0, 0x21, 0x09, 0x05, 0x41, 0x18, 0x01, 0x23, 0x1b, 0x03, 0x10, 0x78, 0x08, 0x70, 0x01, 0x3b, 0x01, 0x32, 0x01, 0x31, 0x00, 0x2b, 0xf8, 0xd1, 0x00, 0x20, 0x70, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  272.  
  273.         // EEPROM (EEPROM_V120, EEPROM_V121, EEPROM_V122) - Note: C++ uses 0 as wildcard
  274.         const EEPROM_MARKER_1 = [0xa2, 0xb0, 0x0d, 0x1c, 0x00, 0x04, 0x03, 0x0c, 0x03, 0x48, 0x00, 0x68, 0x80, 0x88, 0x83, 0x42, 0x05, 0xd3, 0x01, 0x48, 0x00, 0xe0];
  275.         const EEPROM_REPLACE_1 = [0x00, 0x04, 0x0a, 0x1c, 0x40, 0x0b, 0xe0, 0x21, 0x09, 0x05, 0x41, 0x18, 0x07, 0x31, 0x00, 0x23, 0x08, 0x78, 0x10, 0x70, 0x01, 0x33, 0x01, 0x32, 0x01, 0x39, 0x07, 0x2b, 0xf8, 0xd9, 0x00, 0x20, 0x70, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  276.         const EEPROM_MARKER_2 = [0x30, 0xb5, 0xa9, 0xb0, 0x0d, 0x1c, 0x00, 0x04, 0x04, 0x0c, 0x03, 0x48, 0x00, 0x68, 0x80, 0x88, 0x84, 0x42, 0x05, 0xd3, 0x01, 0x48, 0x00, 0xe0];
  277.         const EEPROM_REPLACE_2 = [0x70, 0xb5, 0x00, 0x04, 0x0a, 0x1c, 0x40, 0x0b, 0xe0, 0x21, 0x09, 0x05, 0x41, 0x18, 0x07, 0x31, 0x00, 0x23, 0x10, 0x78, 0x08, 0x70, 0x01, 0x33, 0x01, 0x32, 0x01, 0x39, 0x07, 0x2b, 0xf8, 0xd9, 0x00, 0x20, 0x70, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  278.  
  279.         // EEPROM_V124
  280.         const EEPROM_V124_MARKER_1 = [0xa2, 0xb0, 0x0d, 0x1c, 0x00, 0x04, 0x03, 0x0c, 0x03, 0x48, 0x00, 0x68, 0x80, 0x88, 0x83, 0x42, 0x05, 0xd3, 0x01, 0x48, 0x00, 0xe0];
  281.         const EEPROM_V124_REPLACE_1 = [0x00, 0x04, 0x0a, 0x1c, 0x40, 0x0b, 0xe0, 0x21, 0x09, 0x05, 0x41, 0x18, 0x07, 0x31, 0x00, 0x23, 0x08, 0x78, 0x10, 0x70, 0x01, 0x33, 0x01, 0x32, 0x01, 0x39, 0x07, 0x2b, 0xf8, 0xd9, 0x00, 0x20, 0x70, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  282.         const EEPROM_V124_MARKER_2 = [0xf0, 0xb5, 0xac, 0xb0, 0x0d, 0x1c, 0x00, 0x04, 0x01, 0x0c, 0x12, 0x06, 0x17, 0x0e, 0x03, 0x48, 0x00, 0x68, 0x80, 0x88, 0x81, 0x42, 0x05, 0xd3];
  283.         const EEPROM_V124_REPLACE_2 = [0x70, 0xb5, 0x00, 0x04, 0x0a, 0x1c, 0x40, 0x0b, 0xe0, 0x21, 0x09, 0x05, 0x41, 0x18, 0x07, 0x31, 0x00, 0x23, 0x10, 0x78, 0x08, 0x70, 0x01, 0x33, 0x01, 0x32, 0x01, 0x39, 0x07, 0x2b, 0xf8, 0xd9, 0x00, 0x20, 0x70, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  284.  
  285.         // EEPROM_V126
  286.         const EEPROM_V126_MARKER_1 = [0xa2, 0xb0, 0x0d, 0x1c, 0x00, 0x04, 0x03, 0x0c, 0x03, 0x48, 0x00, 0x68, 0x80, 0x88, 0x83, 0x42, 0x05, 0xd3, 0x01, 0x48, 0x4a, 0xe0];
  287.         const EEPROM_V126_REPLACE_1 = [0x00, 0x04, 0x0a, 0x1c, 0x40, 0x0b, 0xe0, 0x21, 0x09, 0x05, 0x41, 0x18, 0x07, 0x31, 0x00, 0x23, 0x08, 0x78, 0x10, 0x70, 0x01, 0x33, 0x01, 0x32, 0x01, 0x39, 0x07, 0x2b, 0xf8, 0xd9, 0x00, 0x20, 0x70, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  288.         const EEPROM_V126_MARKER_2 = [0xf0, 0xb5, 0x47, 0x46, 0x80, 0xb4, 0xac, 0xb0, 0x0e, 0x1c, 0x00, 0x04, 0x05, 0x0c, 0x12, 0x06, 0x12, 0x0e, 0x90, 0x46, 0x03, 0x48, 0x00, 0x68];
  289.         const EEPROM_V126_REPLACE_2 = [0x70, 0xb5, 0x00, 0x04, 0x0a, 0x1c, 0x40, 0x0b, 0xe0, 0x21, 0x09, 0x05, 0x41, 0x18, 0x07, 0x31, 0x00, 0x23, 0x10, 0x78, 0x08, 0x70, 0x01, 0x33, 0x01, 0x32, 0x01, 0x39, 0x07, 0x2b, 0xf8, 0xd9, 0x00, 0x20, 0x70, 0xbc, 0x02, 0xbc, 0x08, 0x47];
  290.  
  291.         // Build complete flash patterns exactly like original - EXACT COPY FROM ORIGINAL
  292.         this.flashPatterns = [
  293.             { IdentPattern: { Pattern: IDENT_FLASH1M_V102, PatternLength: IDENT_FLASH1M_V102.length }, Patches: [{ Marker: { Pattern: FLASH1M_V102_MARKER_1, PatternLength: FLASH1M_V102_MARKER_1.length }, Replace: { Pattern: FLASH1M_V102_REPLACE_1, PatternLength: FLASH1M_V102_REPLACE_1.length } }, { Marker: { Pattern: FLASH1M_V102_MARKER_2, PatternLength: FLASH1M_V102_MARKER_2.length }, Replace: { Pattern: FLASH1M_V102_REPLACE_2, PatternLength: FLASH1M_V102_REPLACE_2.length } }, { Marker: { Pattern: FLASH1M_V102_MARKER_3, PatternLength: FLASH1M_V102_MARKER_3.length }, Replace: { Pattern: FLASH1M_V102_REPLACE_3, PatternLength: FLASH1M_V102_REPLACE_3.length } }, { Marker: { Pattern: FLASH1M_V102_MARKER_4, PatternLength: FLASH1M_V102_MARKER_4.length }, Replace: { Pattern: FLASH1M_V102_REPLACE_4, PatternLength: FLASH1M_V102_REPLACE_4.length } }, { Marker: { Pattern: FLASH1M_V102_MARKER_5, PatternLength: FLASH1M_V102_MARKER_5.length }, Replace: { Pattern: FLASH1M_V102_REPLACE_5, PatternLength: FLASH1M_V102_REPLACE_5.length } }, { Marker: { Pattern: FLASH1M_V102_MARKER_6, PatternLength: FLASH1M_V102_MARKER_6.length }, Replace: { Pattern: FLASH1M_V102_REPLACE_6, PatternLength: FLASH1M_V102_REPLACE_6.length } }], PatchCount: 6, FlashType: 'FLASH1M_V102' },
  294.             { IdentPattern: { Pattern: IDENT_FLASH1M_V103, PatternLength: IDENT_FLASH1M_V103.length }, Patches: [{ Marker: { Pattern: FLASH1M_V103_MARKER_1, PatternLength: FLASH1M_V103_MARKER_1.length }, Replace: { Pattern: FLASH1M_V103_REPLACE_1, PatternLength: FLASH1M_V103_REPLACE_1.length } }, { Marker: { Pattern: FLASH1M_V103_MARKER_2, PatternLength: FLASH1M_V103_MARKER_2.length }, Replace: { Pattern: FLASH1M_V103_REPLACE_2, PatternLength: FLASH1M_V103_REPLACE_2.length } }, { Marker: { Pattern: FLASH1M_V103_MARKER_3, PatternLength: FLASH1M_V103_MARKER_3.length }, Replace: { Pattern: FLASH1M_V103_REPLACE_3, PatternLength: FLASH1M_V103_REPLACE_3.length } }, { Marker: { Pattern: FLASH1M_V103_MARKER_4, PatternLength: FLASH1M_V103_MARKER_4.length }, Replace: { Pattern: FLASH1M_V103_REPLACE_4, PatternLength: FLASH1M_V103_REPLACE_4.length } }, { Marker: { Pattern: FLASH1M_V103_MARKER_5, PatternLength: FLASH1M_V103_MARKER_5.length }, Replace: { Pattern: FLASH1M_V103_REPLACE_5, PatternLength: FLASH1M_V103_REPLACE_5.length } }, { Marker: { Pattern: FLASH1M_V103_MARKER_6, PatternLength: FLASH1M_V103_MARKER_6.length }, Replace: { Pattern: FLASH1M_V103_REPLACE_6, PatternLength: FLASH1M_V103_REPLACE_6.length } }, { Marker: { Pattern: FLASH1M_V103_MARKER_7, PatternLength: FLASH1M_V103_MARKER_7.length }, Replace: { Pattern: FLASH1M_V103_REPLACE_7, PatternLength: FLASH1M_V103_REPLACE_7.length } }], PatchCount: 7, FlashType: 'FLASH1M_V103' },
  295.             { IdentPattern: { Pattern: IDENT_FLASH512, PatternLength: IDENT_FLASH512.length }, Patches: [{ Marker: { Pattern: FLASH512_MARKER_1, PatternLength: FLASH512_MARKER_1.length }, Replace: { Pattern: FLASH512_REPLACE_1, PatternLength: FLASH512_REPLACE_1.length } }, { Marker: { Pattern: FLASH512_MARKER_2, PatternLength: FLASH512_MARKER_2.length }, Replace: { Pattern: FLASH512_REPLACE_2, PatternLength: FLASH512_REPLACE_2.length } }, { Marker: { Pattern: FLASH512_MARKER_3, PatternLength: FLASH512_MARKER_3.length }, Replace: { Pattern: FLASH512_REPLACE_3, PatternLength: FLASH512_REPLACE_3.length } }, { Marker: { Pattern: FLASH512_MARKER_4, PatternLength: FLASH512_MARKER_4.length }, Replace: { Pattern: FLASH512_REPLACE_4, PatternLength: FLASH512_REPLACE_4.length } }, { Marker: { Pattern: FLASH512_MARKER_5, PatternLength: FLASH512_MARKER_5.length }, Replace: { Pattern: FLASH512_REPLACE_5, PatternLength: FLASH512_REPLACE_5.length } }], PatchCount: 5, FlashType: 'FLASH512' },
  296.             // Additional flash types from C++ source - EXACT COPY FROM ORIGINAL
  297.             { IdentPattern: { Pattern: IDENT_FLASH_V120, PatternLength: IDENT_FLASH_V120.length }, Patches: [{ Marker: { Pattern: FLASH_1_MARKER_1, PatternLength: FLASH_1_MARKER_1.length }, Replace: { Pattern: FLASH_1_REPLACE_1, PatternLength: FLASH_1_REPLACE_1.length } }, { Marker: { Pattern: FLASH_1_MARKER_2, PatternLength: FLASH_1_MARKER_2.length }, Replace: { Pattern: FLASH_1_REPLACE_2, PatternLength: FLASH_1_REPLACE_2.length } }, { Marker: { Pattern: FLASH_1_MARKER_3, PatternLength: FLASH_1_MARKER_3.length }, Replace: { Pattern: FLASH_1_REPLACE_3, PatternLength: FLASH_1_REPLACE_3.length } }], PatchCount: 3, FlashType: 'FLASH_V120' },
  298.             { IdentPattern: { Pattern: IDENT_FLASH_V121, PatternLength: IDENT_FLASH_V121.length }, Patches: [{ Marker: { Pattern: FLASH_1_MARKER_1, PatternLength: FLASH_1_MARKER_1.length }, Replace: { Pattern: FLASH_1_REPLACE_1, PatternLength: FLASH_1_REPLACE_1.length } }, { Marker: { Pattern: FLASH_1_MARKER_2, PatternLength: FLASH_1_MARKER_2.length }, Replace: { Pattern: FLASH_1_REPLACE_2, PatternLength: FLASH_1_REPLACE_2.length } }, { Marker: { Pattern: FLASH_1_MARKER_3, PatternLength: FLASH_1_MARKER_3.length }, Replace: { Pattern: FLASH_1_REPLACE_3, PatternLength: FLASH_1_REPLACE_3.length } }], PatchCount: 3, FlashType: 'FLASH_V121' },
  299.             { IdentPattern: { Pattern: IDENT_FLASH_V123, PatternLength: IDENT_FLASH_V123.length }, Patches: [{ Marker: { Pattern: FLASH_2_MARKER_1, PatternLength: FLASH_2_MARKER_1.length }, Replace: { Pattern: FLASH_2_REPLACE_1, PatternLength: FLASH_2_REPLACE_1.length } }, { Marker: { Pattern: FLASH_2_MARKER_2, PatternLength: FLASH_2_MARKER_2.length }, Replace: { Pattern: FLASH_2_REPLACE_2, PatternLength: FLASH_2_REPLACE_2.length } }, { Marker: { Pattern: FLASH_2_MARKER_3, PatternLength: FLASH_2_MARKER_3.length }, Replace: { Pattern: FLASH_2_REPLACE_3, PatternLength: FLASH_2_REPLACE_3.length } }, { Marker: { Pattern: FLASH_2_MARKER_4, PatternLength: FLASH_2_MARKER_4.length }, Replace: { Pattern: FLASH_2_REPLACE_4, PatternLength: FLASH_2_REPLACE_4.length } }], PatchCount: 4, FlashType: 'FLASH_V123' },
  300.             { IdentPattern: { Pattern: IDENT_FLASH_V124, PatternLength: IDENT_FLASH_V124.length }, Patches: [{ Marker: { Pattern: FLASH_2_MARKER_1, PatternLength: FLASH_2_MARKER_1.length }, Replace: { Pattern: FLASH_2_REPLACE_1, PatternLength: FLASH_2_REPLACE_1.length } }, { Marker: { Pattern: FLASH_2_MARKER_2, PatternLength: FLASH_2_MARKER_2.length }, Replace: { Pattern: FLASH_2_REPLACE_2, PatternLength: FLASH_2_REPLACE_2.length } }, { Marker: { Pattern: FLASH_2_MARKER_3, PatternLength: FLASH_2_MARKER_3.length }, Replace: { Pattern: FLASH_2_REPLACE_3, PatternLength: FLASH_2_REPLACE_3.length } }, { Marker: { Pattern: FLASH_2_MARKER_4, PatternLength: FLASH_2_MARKER_4.length }, Replace: { Pattern: FLASH_2_REPLACE_4, PatternLength: FLASH_2_REPLACE_4.length } }], PatchCount: 4, FlashType: 'FLASH_V124' },
  301.             { IdentPattern: { Pattern: IDENT_FLASH_V125, PatternLength: IDENT_FLASH_V125.length }, Patches: [{ Marker: { Pattern: FLASH_3_MARKER_1, PatternLength: FLASH_3_MARKER_1.length }, Replace: { Pattern: FLASH_3_REPLACE_1, PatternLength: FLASH_3_REPLACE_1.length } }, { Marker: { Pattern: FLASH_3_MARKER_2, PatternLength: FLASH_3_MARKER_2.length }, Replace: { Pattern: FLASH_3_REPLACE_2, PatternLength: FLASH_3_REPLACE_2.length } }, { Marker: { Pattern: FLASH_3_MARKER_3, PatternLength: FLASH_3_MARKER_3.length }, Replace: { Pattern: FLASH_3_REPLACE_3, PatternLength: FLASH_3_REPLACE_3.length } }, { Marker: { Pattern: FLASH_3_MARKER_4, PatternLength: FLASH_3_MARKER_4.length }, Replace: { Pattern: FLASH_3_REPLACE_4, PatternLength: FLASH_3_REPLACE_4.length } }], PatchCount: 4, FlashType: 'FLASH_V125' },
  302.             { IdentPattern: { Pattern: IDENT_FLASH_V126, PatternLength: IDENT_FLASH_V126.length }, Patches: [{ Marker: { Pattern: FLASH_3_MARKER_1, PatternLength: FLASH_3_MARKER_1.length }, Replace: { Pattern: FLASH_3_REPLACE_1, PatternLength: FLASH_3_REPLACE_1.length } }, { Marker: { Pattern: FLASH_3_MARKER_2, PatternLength: FLASH_3_MARKER_2.length }, Replace: { Pattern: FLASH_3_REPLACE_2, PatternLength: FLASH_3_REPLACE_2.length } }, { Marker: { Pattern: FLASH_3_MARKER_3, PatternLength: FLASH_3_MARKER_3.length }, Replace: { Pattern: FLASH_3_REPLACE_3, PatternLength: FLASH_3_REPLACE_3.length } }, { Marker: { Pattern: FLASH_3_MARKER_4, PatternLength: FLASH_3_MARKER_4.length }, Replace: { Pattern: FLASH_3_REPLACE_4, PatternLength: FLASH_3_REPLACE_4.length } }], PatchCount: 4, FlashType: 'FLASH_V126' },
  303.             // EEPROM types - EXACT COPY FROM ORIGINAL
  304.             { IdentPattern: { Pattern: IDENT_EEPROM_V120, PatternLength: IDENT_EEPROM_V120.length }, Patches: [{ Marker: { Pattern: EEPROM_MARKER_1, PatternLength: EEPROM_MARKER_1.length }, Replace: { Pattern: EEPROM_REPLACE_1, PatternLength: EEPROM_REPLACE_1.length } }, { Marker: { Pattern: EEPROM_MARKER_2, PatternLength: EEPROM_MARKER_2.length }, Replace: { Pattern: EEPROM_REPLACE_2, PatternLength: EEPROM_REPLACE_2.length } }], PatchCount: 2, FlashType: 'EEPROM_V120' },
  305.             { IdentPattern: { Pattern: IDENT_EEPROM_V121, PatternLength: IDENT_EEPROM_V121.length }, Patches: [{ Marker: { Pattern: EEPROM_MARKER_1, PatternLength: EEPROM_MARKER_1.length }, Replace: { Pattern: EEPROM_REPLACE_1, PatternLength: EEPROM_REPLACE_1.length } }, { Marker: { Pattern: EEPROM_MARKER_2, PatternLength: EEPROM_MARKER_2.length }, Replace: { Pattern: EEPROM_REPLACE_2, PatternLength: EEPROM_REPLACE_2.length } }], PatchCount: 2, FlashType: 'EEPROM_V121' },
  306.             { IdentPattern: { Pattern: IDENT_EEPROM_V122, PatternLength: IDENT_EEPROM_V122.length }, Patches: [{ Marker: { Pattern: EEPROM_MARKER_1, PatternLength: EEPROM_MARKER_1.length }, Replace: { Pattern: EEPROM_REPLACE_1, PatternLength: EEPROM_REPLACE_1.length } }, { Marker: { Pattern: EEPROM_MARKER_2, PatternLength: EEPROM_MARKER_2.length }, Replace: { Pattern: EEPROM_REPLACE_2, PatternLength: EEPROM_REPLACE_2.length } }], PatchCount: 2, FlashType: 'EEPROM_V122' },
  307.             { IdentPattern: { Pattern: IDENT_EEPROM_V124, PatternLength: IDENT_EEPROM_V124.length }, Patches: [{ Marker: { Pattern: EEPROM_V124_MARKER_1, PatternLength: EEPROM_V124_MARKER_1.length }, Replace: { Pattern: EEPROM_V124_REPLACE_1, PatternLength: EEPROM_V124_REPLACE_1.length } }, { Marker: { Pattern: EEPROM_V124_MARKER_2, PatternLength: EEPROM_V124_MARKER_2.length }, Replace: { Pattern: EEPROM_V124_REPLACE_2, PatternLength: EEPROM_V124_REPLACE_2.length } }], PatchCount: 2, FlashType: 'EEPROM_V124' },
  308.             { IdentPattern: { Pattern: IDENT_EEPROM_V126, PatternLength: IDENT_EEPROM_V126.length }, Patches: [{ Marker: { Pattern: EEPROM_V126_MARKER_1, PatternLength: EEPROM_V126_MARKER_1.length }, Replace: { Pattern: EEPROM_V126_REPLACE_1, PatternLength: EEPROM_V126_REPLACE_1.length } }, { Marker: { Pattern: EEPROM_V126_MARKER_2, PatternLength: EEPROM_V126_MARKER_2.length }, Replace: { Pattern: EEPROM_V126_REPLACE_2, PatternLength: EEPROM_V126_REPLACE_2.length } }], PatchCount: 2, FlashType: 'EEPROM_V126' }
  309.         ];
  310.     }
  311.  
  312.     // EXACT BATTERY PATCHING LOGIC FROM ORIGINAL PROJECT
  313.     initializeBatteryPatterns() {
  314.         // Embedded payload binary data extracted from working ROM - EXACT COPY
  315.         this.embeddedPayloadBin = new Uint8Array([
  316.             0xc0, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
  317.             0x54, 0x00, 0x00, 0x00, 0xcd, 0x00, 0x00, 0x00, 0x1d, 0x01, 0x00, 0x00,
  318.             0xb9, 0x00, 0x00, 0x00, 0x47, 0x01, 0x00, 0x00, 0x1f, 0xb4, 0x00, 0xb5,
  319.             0x0a, 0x4c, 0x20, 0x88, 0x01, 0xb4, 0x00, 0x20, 0x20, 0x80, 0x73, 0xa0,
  320.             0xfe, 0x46, 0x00, 0x47, 0x01, 0xbc, 0x20, 0x80, 0x01, 0xbc, 0x86, 0x46,
  321.             0x1f, 0xbc, 0xc0, 0x46, 0xc0, 0x46, 0xc0, 0x46, 0xc0, 0x46, 0x70, 0x47,
  322.             0xc0, 0x46, 0xc0, 0x46, 0xc0, 0x46, 0xc0, 0x46, 0x08, 0x02, 0x00, 0x04,
  323.             0x01, 0x03, 0xa0, 0xe3, 0x5c, 0x10, 0x1f, 0xe5, 0x00, 0x00, 0x51, 0xe3,
  324.             0x19, 0x1e, 0x8f, 0xe2, 0xf8, 0x10, 0x8f, 0x12, 0x04, 0x10, 0x00, 0xe5,
  325.             0xfc, 0x00, 0x8f, 0xe2, 0x07, 0x0c, 0x80, 0xe2, 0x0e, 0x14, 0xa0, 0xe3,
  326.             0x78, 0x20, 0x1f, 0xe5, 0x01, 0x20, 0x82, 0xe0, 0x09, 0x34, 0xa0, 0xe3,
  327.             0x80, 0x40, 0xa0, 0xe3, 0x03, 0x40, 0xc1, 0xe5, 0x21, 0x48, 0xa0, 0xe1,
  328.             0x01, 0x40, 0x04, 0xe2, 0xb0, 0x40, 0xc3, 0xe1, 0x00, 0x00, 0xa0, 0xe1,
  329.             0x01, 0x40, 0xd0, 0xe4, 0x01, 0x40, 0xc1, 0xe4, 0x02, 0x00, 0x51, 0xe1,
  330.             0xf7, 0xff, 0xff, 0x3a, 0x00, 0x40, 0xa0, 0xe3, 0xb0, 0x40, 0xc3, 0xe1,
  331.             0xbc, 0xf0, 0x1f, 0xe5, 0x00, 0x03, 0x0e, 0x22, 0x12, 0x06, 0x10, 0x43,
  332.             0x01, 0x22, 0x12, 0x03, 0x03, 0x1c, 0x08, 0x1c, 0x19, 0x1c, 0xff, 0xe7,
  333.             0x00, 0xb5, 0xf0, 0xb4, 0x11, 0x4e, 0x37, 0x88, 0x00, 0x23, 0x33, 0x80,
  334.             0x09, 0x24, 0x24, 0x06, 0x0d, 0x0c, 0x01, 0x23, 0x1d, 0x40, 0x25, 0x80,
  335.             0x12, 0x18, 0x04, 0x78, 0x0d, 0x78, 0xac, 0x42, 0x01, 0xd0, 0x01, 0x23,
  336.             0x0c, 0x70, 0x01, 0x30, 0x01, 0x31, 0x90, 0x42, 0xf5, 0xd3, 0x00, 0x2b,
  337.             0x06, 0xd0, 0x78, 0x46, 0xfe, 0x38, 0x00, 0x88, 0x00, 0x28, 0x01, 0xd1,
  338.             0x00, 0xf0, 0x22, 0xf8, 0x37, 0x80, 0x00, 0x20, 0xf0, 0xbc, 0x02, 0xbc,
  339.             0x08, 0x47, 0x00, 0x00, 0x08, 0x02, 0x00, 0x04, 0x10, 0xb5, 0x0a, 0x1c,
  340.             0x08, 0x32, 0x6b, 0x46, 0x0c, 0x78, 0x01, 0x31, 0x01, 0x3b, 0x1c, 0x70,
  341.             0x91, 0x42, 0xf9, 0xd1, 0x0e, 0x21, 0x09, 0x06, 0xc0, 0x00, 0x09, 0x18,
  342.             0x08, 0x22, 0x18, 0x1c, 0x9d, 0x46, 0xff, 0xf7, 0xc5, 0xff, 0x02, 0xb0,
  343.             0x10, 0xbd, 0x01, 0xb4, 0x00, 0xf0, 0x02, 0xf8, 0x01, 0xbc, 0x00, 0x47,
  344.             0x16, 0xa0, 0x04, 0x21, 0x09, 0x06, 0x10, 0x39, 0x66, 0x22, 0x4a, 0x81,
  345.             0xc8, 0x60, 0x4a, 0x82, 0x70, 0x47, 0x00, 0x00, 0x30, 0x31, 0x90, 0xe5,
  346.             0xf3, 0x00, 0x33, 0xe3, 0x0c, 0xf0, 0x10, 0x15, 0x01, 0x10, 0xa0, 0xe3,
  347.             0xb2, 0x10, 0xc0, 0xe1, 0x9f, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x29, 0xe1,
  348.             0x04, 0xe0, 0x2d, 0xe5, 0x1c, 0x00, 0x00, 0xeb, 0x04, 0xe0, 0x9d, 0xe4,
  349.             0x92, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x29, 0xe1, 0x01, 0x03, 0xa0, 0xe3,
  350.             0xb2, 0x00, 0xc0, 0xe1, 0x30, 0x31, 0x90, 0xe5, 0xf3, 0x00, 0x33, 0xe3,
  351.             0xfc, 0xff, 0xff, 0x0a, 0x0c, 0xf0, 0x10, 0xe5, 0x00, 0x12, 0x90, 0xe5,
  352.             0x01, 0x08, 0x11, 0xe3, 0x0c, 0xf0, 0x10, 0x05, 0xb6, 0x10, 0x50, 0xe1,
  353.             0x01, 0x10, 0x51, 0xe2, 0xb6, 0x10, 0x40, 0xe1, 0x0c, 0xf0, 0x10, 0x15,
  354.             0x9f, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x29, 0xe1, 0x04, 0xe0, 0x2d, 0xe5,
  355.             0x08, 0x00, 0x00, 0xeb, 0x04, 0xe0, 0x9d, 0xe4, 0x92, 0x30, 0xa0, 0xe3,
  356.             0x03, 0xf0, 0x29, 0xe1, 0x01, 0x03, 0xa0, 0xe3, 0xb2, 0x00, 0xc0, 0xe1,
  357.             0x04, 0x10, 0x8f, 0xe2, 0x04, 0x10, 0x00, 0xe5, 0xff, 0xff, 0xff, 0xea,
  358.             0x0c, 0xf0, 0x10, 0xe5, 0x01, 0x03, 0xa0, 0xe3, 0xb0, 0x28, 0xd0, 0xe1,
  359.             0xb4, 0x38, 0xd0, 0xe1, 0x0c, 0x00, 0x2d, 0xe9, 0xb4, 0x08, 0xc0, 0xe1,
  360.             0xba, 0x3b, 0xd0, 0xe1, 0x04, 0x30, 0x2d, 0xe5, 0xba, 0x0b, 0xc0, 0xe1,
  361.             0xb6, 0x3c, 0xd0, 0xe1, 0x04, 0x30, 0x2d, 0xe5, 0xb6, 0x0c, 0xc0, 0xe1,
  362.             0xb2, 0x3d, 0xd0, 0xe1, 0x04, 0x30, 0x2d, 0xe5, 0xb2, 0x0d, 0xc0, 0xe1,
  363.             0xbe, 0x3d, 0xd0, 0xe1, 0x04, 0x30, 0x2d, 0xe5, 0xbe, 0x0d, 0xc0, 0xe1,
  364.             0x04, 0xe0, 0x2d, 0xe5, 0xf0, 0x00, 0x2d, 0xe9, 0x62, 0x4e, 0x8f, 0xe2,
  365.             0x00, 0x00, 0xa0, 0xe1, 0x02, 0x43, 0x44, 0xe2, 0x54, 0x52, 0x1f, 0xe5,
  366.             0x94, 0x60, 0x8f, 0xe2, 0x99, 0x7f, 0x4f, 0xe2, 0x0c, 0x00, 0xb6, 0xe8,
  367.             0x00, 0x00, 0x52, 0xe3, 0x12, 0x00, 0x00, 0x0a, 0x07, 0x20, 0x82, 0xe0,
  368.             0x07, 0x30, 0x83, 0xe0, 0x39, 0x00, 0x00, 0xeb, 0x00, 0x00, 0x50, 0xe3,
  369.             0x01, 0x00, 0x00, 0x1a, 0x10, 0x60, 0x86, 0xe2, 0xf5, 0xff, 0xff, 0xea,
  370.             0x0c, 0x00, 0xb6, 0xe8, 0x04, 0x00, 0xa0, 0xe1, 0x05, 0x10, 0xa0, 0xe1,
  371.             0x07, 0x20, 0x82, 0xe0, 0x07, 0x30, 0x83, 0xe0, 0x2f, 0x00, 0x00, 0xeb,
  372.             0x0c, 0x00, 0xb6, 0xe8, 0x04, 0x00, 0xa0, 0xe1, 0x05, 0x10, 0xa0, 0xe1,
  373.             0x07, 0x20, 0x82, 0xe0, 0x07, 0x30, 0x83, 0xe0, 0x29, 0x00, 0x00, 0xeb,
  374.             0xf0, 0x00, 0xbd, 0xe8, 0x04, 0xe0, 0x9d, 0xe4, 0x01, 0x03, 0xa0, 0xe3,
  375.             0x04, 0x30, 0x9d, 0xe4, 0xbe, 0x3d, 0xc0, 0xe1, 0x04, 0x30, 0x9d, 0xe4,
  376.             0xb2, 0x3d, 0xc0, 0xe1, 0x04, 0x30, 0x9d, 0xe4, 0xb6, 0x3c, 0xc0, 0xe1,
  377.             0x04, 0x30, 0x9d, 0xe4, 0xba, 0x3b, 0xc0, 0xe1, 0x0c, 0x00, 0xbd, 0xe8,
  378.             0xb4, 0x38, 0xc0, 0xe1, 0xb0, 0x28, 0xc0, 0xe1, 0x1e, 0xff, 0x2f, 0xe1,
  379.             0x95, 0x03, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0xf9, 0x03, 0x00, 0x00,
  380.             0x2c, 0x04, 0x00, 0x00, 0x2d, 0x04, 0x00, 0x00, 0x94, 0x04, 0x00, 0x00,
  381.             0xe5, 0x06, 0x00, 0x00, 0x48, 0x07, 0x00, 0x00, 0x49, 0x07, 0x00, 0x00,
  382.             0x9c, 0x07, 0x00, 0x00, 0x9d, 0x07, 0x00, 0x00, 0x60, 0x08, 0x00, 0x00,
  383.             0x95, 0x04, 0x00, 0x00, 0xd4, 0x04, 0x00, 0x00, 0xd5, 0x04, 0x00, 0x00,
  384.             0x2c, 0x05, 0x00, 0x00, 0x2d, 0x05, 0x00, 0x00, 0xbc, 0x05, 0x00, 0x00,
  385.             0xbd, 0x05, 0x00, 0x00, 0xfc, 0x05, 0x00, 0x00, 0xfd, 0x05, 0x00, 0x00,
  386.             0x54, 0x06, 0x00, 0x00, 0x55, 0x06, 0x00, 0x00, 0xe4, 0x06, 0x00, 0x00,
  387.             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  388.             0x30, 0x40, 0x2d, 0xe9, 0x0d, 0x40, 0xa0, 0xe1, 0x01, 0x20, 0xc2, 0xe3,
  389.             0x04, 0x50, 0x33, 0xe5, 0x04, 0x50, 0x2d, 0xe5, 0x03, 0x00, 0x52, 0xe1,
  390.             0xfb, 0xff, 0xff, 0x1a, 0x01, 0x20, 0x8d, 0xe2, 0x0f, 0xe0, 0xa0, 0xe1,
  391.             0x12, 0xff, 0x2f, 0xe1, 0x04, 0xd0, 0xa0, 0xe1, 0x30, 0x40, 0xbd, 0xe8,
  392.             0x1e, 0xff, 0x2f, 0xe1, 0x80, 0x23, 0xff, 0x22, 0x1b, 0x05, 0x13, 0xb5,
  393.             0x18, 0x68, 0x1a, 0x80, 0xc0, 0x46, 0x90, 0x21, 0x19, 0x80, 0xc0, 0x46,
  394.             0x19, 0x68, 0x1a, 0x80, 0xc0, 0x46, 0x88, 0x42, 0x03, 0xd1, 0x00, 0x20,
  395.             0x16, 0xbc, 0x02, 0xbc, 0x08, 0x47, 0x42, 0x20, 0x0c, 0x49, 0x08, 0x80,
  396.             0xc0, 0x46, 0x0c, 0x48, 0x04, 0x78, 0x96, 0x20, 0x08, 0x80, 0xc0, 0x46,
  397.             0x1a, 0x80, 0xc0, 0x46, 0x95, 0x38, 0x96, 0x2c, 0xee, 0xd0, 0x00, 0x23,
  398.             0x80, 0x22, 0x01, 0x93, 0xd2, 0x00, 0x01, 0x9b, 0x93, 0x42, 0xe6, 0xda,
  399.             0xc0, 0x46, 0x01, 0x9b, 0x01, 0x33, 0x01, 0x93, 0xf7, 0xe7, 0xc0, 0x46,
  400.             0x58, 0x00, 0x00, 0x08, 0xb2, 0x00, 0x00, 0x08, 0x01, 0x23, 0x98, 0x43,
  401.             0x80, 0x23, 0x1b, 0x05, 0xc0, 0x18, 0xff, 0x23, 0x03, 0x80, 0xc0, 0x46,
  402.             0x60, 0x22, 0x02, 0x80, 0xc0, 0x46, 0x70, 0x32, 0x02, 0x80, 0xc0, 0x46,
  403.             0x20, 0x21, 0x01, 0x80, 0xc0, 0x46, 0x02, 0x80, 0xc0, 0x46, 0xc0, 0x46,
  404.             0x02, 0x88, 0x80, 0x2a, 0xfb, 0xd1, 0x03, 0x80, 0xc0, 0x46, 0x70, 0x47,
  405.             0xf7, 0xb5, 0x02, 0x00, 0x01, 0x91, 0x01, 0x21, 0x90, 0x27, 0x00, 0x23,
  406.             0x80, 0x26, 0x8a, 0x43, 0x94, 0x46, 0x3f, 0x05, 0x3b, 0x80, 0x36, 0x05,
  407.             0x01, 0x9a, 0x9a, 0x42, 0x06, 0xd8, 0xff, 0x23, 0x88, 0x43, 0x83, 0x53,
  408.             0xc0, 0x46, 0xf7, 0xbc, 0x01, 0xbc, 0x00, 0x47, 0x80, 0x22, 0x52, 0x02,
  409.             0x93, 0x42, 0x00, 0xd1, 0x39, 0x80, 0x80, 0x24, 0xc2, 0x18, 0x8a, 0x43,
  410.             0x24, 0x05, 0x12, 0x19, 0x40, 0x24, 0x14, 0x80, 0xc0, 0x46, 0x07, 0x4c,
  411.             0x1d, 0x5d, 0x01, 0x3c, 0x1c, 0x5d, 0x2d, 0x02, 0x2c, 0x43, 0x14, 0x80,
  412.             0xc0, 0x46, 0xc0, 0x46, 0x62, 0x46, 0x92, 0x5b, 0x80, 0x2a, 0xfa, 0xd1,
  413.             0x02, 0x33, 0xd9, 0xe7, 0x01, 0x00, 0x00, 0x0e, 0x80, 0x23, 0xf0, 0x22,
  414.             0x1b, 0x05, 0x30, 0xb5, 0x18, 0x68, 0x1a, 0x80, 0xc0, 0x46, 0xa9, 0x24,
  415.             0x09, 0x49, 0x0c, 0x80, 0xc0, 0x46, 0x56, 0x25, 0x08, 0x4c, 0x25, 0x80,
  416.             0xc0, 0x46, 0x90, 0x24, 0x0c, 0x80, 0xc0, 0x46, 0x19, 0x68, 0x1a, 0x80,
  417.             0xc0, 0x46, 0x40, 0x1a, 0x43, 0x1e, 0x98, 0x41, 0x30, 0xbc, 0x02, 0xbc,
  418.             0x08, 0x47, 0xc0, 0x46, 0xaa, 0x0a, 0x00, 0x08, 0x54, 0x05, 0x00, 0x08,
  419.             0x01, 0x23, 0x98, 0x43, 0x80, 0x23, 0x1b, 0x05, 0xc0, 0x18, 0xf0, 0x23,
  420.             0x30, 0xb5, 0x03, 0x80, 0xc0, 0x46, 0xa9, 0x24, 0x0d, 0x4b, 0x1c, 0x80,
  421.             0xc0, 0x46, 0x56, 0x21, 0x0c, 0x4a, 0x11, 0x80, 0xc0, 0x46, 0x80, 0x25,
  422.             0x1d, 0x80, 0xc0, 0x46, 0x1c, 0x80, 0xc0, 0x46, 0x11, 0x80, 0xc0, 0x46,
  423.             0x30, 0x23, 0x03, 0x80, 0xc0, 0x46, 0x07, 0x4b, 0xc0, 0x46, 0x02, 0x88,
  424.             0x9a, 0x42, 0xfb, 0xd1, 0xf0, 0x23, 0x03, 0x80, 0xc0, 0x46, 0x30, 0xbc,
  425.             0x01, 0xbc, 0x00, 0x47, 0xaa, 0x0a, 0x00, 0x08, 0x54, 0x05, 0x00, 0x08,
  426.             0xff, 0xff, 0x00, 0x00, 0xf7, 0xb5, 0x90, 0x22, 0x00, 0x23, 0x84, 0x46,
  427.             0x01, 0x20, 0x12, 0x05, 0x01, 0x91, 0x13, 0x80, 0x01, 0x9a, 0x9a, 0x42,
  428.             0x09, 0xd8, 0x63, 0x46, 0x80, 0x22, 0xf0, 0x21, 0x83, 0x43, 0x12, 0x05,
  429.             0x99, 0x52, 0xc0, 0x46, 0xf7, 0xbc, 0x01, 0xbc, 0x00, 0x47, 0x80, 0x22,
  430.             0x52, 0x02, 0x93, 0x42, 0x02, 0xd1, 0x90, 0x22, 0x12, 0x05, 0x10, 0x80,
  431.             0xa9, 0x21, 0x12, 0x4a, 0x11, 0x80, 0xc0, 0x46, 0x56, 0x25, 0x11, 0x49,
  432.             0x0d, 0x80, 0xc0, 0x46, 0xa0, 0x21, 0x11, 0x80, 0xc0, 0x46, 0x0f, 0x4a,
  433.             0x9e, 0x18, 0x01, 0x3a, 0x9d, 0x18, 0x62, 0x46, 0x80, 0x21, 0xd2, 0x18,
  434.             0x37, 0x78, 0x82, 0x43, 0x09, 0x05, 0x52, 0x18, 0x29, 0x78, 0x3f, 0x02,
  435.             0x39, 0x43, 0x11, 0x80, 0xc0, 0x46, 0xc0, 0x46, 0x31, 0x78, 0x09, 0x02,
  436.             0x0c, 0x00, 0x17, 0x88, 0x29, 0x78, 0x0c, 0x43, 0xa7, 0x42, 0xf6, 0xd1,
  437.             0x02, 0x33, 0xc5, 0xe7, 0xaa, 0x0a, 0x00, 0x08, 0x54, 0x05, 0x00, 0x08,
  438.             0x01, 0x00, 0x00, 0x0e, 0x80, 0x23, 0xf0, 0x22, 0x1b, 0x05, 0x30, 0xb5,
  439.             0x18, 0x68, 0x1a, 0x80, 0xc0, 0x46, 0xaa, 0x24, 0x09, 0x49, 0x0c, 0x80,
  440.             0xc0, 0x46, 0x55, 0x25, 0x08, 0x4c, 0x25, 0x80, 0xc0, 0x46, 0x90, 0x24,
  441.             0x0c, 0x80, 0xc0, 0x46, 0x19, 0x68, 0x1a, 0x80, 0xc0, 0x46, 0x40, 0x1a,
  442.             0x43, 0x1e, 0x98, 0x41, 0x30, 0xbc, 0x02, 0xbc, 0x08, 0x47, 0xc0, 0x46,
  443.             0xaa, 0x0a, 0x00, 0x08, 0x54, 0x05, 0x00, 0x08, 0x01, 0x23, 0x98, 0x43,
  444.             0x80, 0x23, 0x1b, 0x05, 0xc0, 0x18, 0xf0, 0x23, 0x30, 0xb5, 0x03, 0x80,
  445.             0xc0, 0x46, 0xaa, 0x24, 0x0d, 0x4b, 0x1c, 0x80, 0xc0, 0x46, 0x55, 0x21,
  446.             0x0c, 0x4a, 0x11, 0x80, 0xc0, 0x46, 0x80, 0x25, 0x1d, 0x80, 0xc0, 0x46,
  447.             0x1c, 0x80, 0xc0, 0x46, 0x11, 0x80, 0xc0, 0x46, 0x30, 0x23, 0x03, 0x80,
  448.             0xc0, 0x46, 0x07, 0x4b, 0xc0, 0x46, 0x02, 0x88, 0x9a, 0x42, 0xfb, 0xd1,
  449.             0xf0, 0x23, 0x03, 0x80, 0xc0, 0x46, 0x30, 0xbc, 0x01, 0xbc, 0x00, 0x47,
  450.             0xaa, 0x0a, 0x00, 0x08, 0x54, 0x05, 0x00, 0x08, 0xff, 0xff, 0x00, 0x00,
  451.             0xf7, 0xb5, 0x90, 0x22, 0x00, 0x23, 0x84, 0x46, 0x01, 0x20, 0x12, 0x05,
  452.             0x01, 0x91, 0x13, 0x80, 0x01, 0x9a, 0x9a, 0x42, 0x09, 0xd8, 0x63, 0x46,
  453.             0x80, 0x22, 0xf0, 0x21, 0x83, 0x43, 0x12, 0x05, 0x99, 0x52, 0xc0, 0x46,
  454.             0xf7, 0xbc, 0x01, 0xbc, 0x00, 0x47, 0x80, 0x22, 0x52, 0x02, 0x93, 0x42,
  455.             0x02, 0xd1, 0x90, 0x22, 0x12, 0x05, 0x10, 0x80, 0xaa, 0x21, 0x12, 0x4a,
  456.             0x11, 0x80, 0xc0, 0x46, 0x55, 0x25, 0x11, 0x49, 0x0d, 0x80, 0xc0, 0x46,
  457.             0xa0, 0x21, 0x11, 0x80, 0xc0, 0x46, 0x0f, 0x4a, 0x9e, 0x18, 0x01, 0x3a,
  458.             0x9d, 0x18, 0x62, 0x46, 0x80, 0x21, 0xd2, 0x18, 0x37, 0x78, 0x82, 0x43,
  459.             0x09, 0x05, 0x52, 0x18, 0x29, 0x78, 0x3f, 0x02, 0x39, 0x43, 0x11, 0x80,
  460.             0xc0, 0x46, 0xc0, 0x46, 0x31, 0x78, 0x09, 0x02, 0x0c, 0x00, 0x17, 0x88,
  461.             0x29, 0x78, 0x0c, 0x43, 0xa7, 0x42, 0xf6, 0xd1, 0x02, 0x33, 0xc5, 0xe7,
  462.             0xaa, 0x0a, 0x00, 0x08, 0x54, 0x05, 0x00, 0x08, 0x01, 0x00, 0x00, 0x0e,
  463.             0x80, 0x23, 0xff, 0x22, 0x1b, 0x05, 0x13, 0xb5, 0x18, 0x68, 0x1a, 0x80,
  464.             0xc0, 0x46, 0x90, 0x21, 0x19, 0x80, 0xc0, 0x46, 0x19, 0x68, 0x1a, 0x80,
  465.             0xc0, 0x46, 0x88, 0x42, 0x03, 0xd1, 0x00, 0x20, 0x16, 0xbc, 0x02, 0xbc,
  466.             0x08, 0x47, 0x42, 0x20, 0x0c, 0x49, 0x08, 0x80, 0xc0, 0x46, 0x96, 0x24,
  467.             0x0b, 0x48, 0x00, 0x78, 0x0c, 0x80, 0xc0, 0x46, 0x1a, 0x80, 0xc0, 0x46,
  468.             0xa0, 0x42, 0xee, 0xd0, 0x00, 0x23, 0x80, 0x22, 0x01, 0x93, 0xd2, 0x00,
  469.             0x01, 0x9b, 0x93, 0x42, 0x01, 0xdb, 0x01, 0x20, 0xe6, 0xe7, 0xc0, 0x46,
  470.             0x01, 0x9b, 0x01, 0x33, 0x01, 0x93, 0xf5, 0xe7, 0x58, 0x00, 0x00, 0x08,
  471.             0xb2, 0x00, 0x00, 0x08, 0x01, 0x23, 0x98, 0x43, 0x80, 0x23, 0x1b, 0x05,
  472.             0xc0, 0x18, 0xff, 0x23, 0x82, 0xb0, 0x03, 0x80, 0xc0, 0x46, 0x9f, 0x3b,
  473.             0x03, 0x80, 0xc0, 0x46, 0x70, 0x33, 0x03, 0x80, 0xc0, 0x46, 0x20, 0x22,
  474.             0x02, 0x80, 0xc0, 0x46, 0x03, 0x80, 0xc0, 0x46, 0x50, 0x3b, 0xc0, 0x46,
  475.             0x02, 0x88, 0x1a, 0x42, 0xfb, 0xd0, 0xff, 0x23, 0x03, 0x80, 0xc0, 0x46,
  476.             0x00, 0x23, 0x80, 0x22, 0x01, 0x93, 0xd2, 0x00, 0x01, 0x9b, 0x93, 0x42,
  477.             0x01, 0xdb, 0x02, 0xb0, 0x70, 0x47, 0xc0, 0x46, 0x01, 0x9b, 0x01, 0x33,
  478.             0x01, 0x93, 0xf5, 0xe7, 0x90, 0x22, 0x00, 0x23, 0xf0, 0xb5, 0x12, 0x05,
  479.             0x87, 0xb0, 0x01, 0x90, 0x02, 0x91, 0x13, 0x80, 0x80, 0x22, 0x01, 0x27,
  480.             0x94, 0x46, 0x02, 0x9a, 0x9a, 0x42, 0x0a, 0xd8, 0x00, 0x23, 0x80, 0x22,
  481.             0x05, 0x93, 0xd2, 0x00, 0x05, 0x9b, 0x93, 0x42, 0x42, 0xdb, 0x07, 0xb0,
  482.             0xf0, 0xbc, 0x01, 0xbc, 0x00, 0x47, 0x80, 0x22, 0x52, 0x02, 0x93, 0x42,
  483.             0x02, 0xd1, 0x90, 0x22, 0x12, 0x05, 0x17, 0x80, 0x01, 0x9a, 0xd0, 0x18,
  484.             0x02, 0x00, 0x80, 0x21, 0xba, 0x43, 0x09, 0x05, 0x52, 0x18, 0xea, 0x21,
  485.             0x11, 0x80, 0xc0, 0x46, 0xc0, 0x46, 0x64, 0x46, 0x11, 0x88, 0x21, 0x42,
  486.             0xfa, 0xd0, 0x17, 0x49, 0x11, 0x80, 0xc0, 0x46, 0xe0, 0x21, 0x16, 0x4c,
  487.             0x1c, 0x19, 0x09, 0x05, 0x03, 0x94, 0x59, 0x18, 0x04, 0x00, 0xbc, 0x43,
  488.             0x26, 0x00, 0x4d, 0x78, 0x0c, 0x78, 0x2d, 0x02, 0x2c, 0x43, 0x35, 0x00,
  489.             0x80, 0x26, 0x36, 0x05, 0xac, 0x53, 0xc0, 0x46, 0x03, 0x9c, 0x02, 0x31,
  490.             0x02, 0x30, 0x8c, 0x42, 0xee, 0xd1, 0xd0, 0x21, 0x11, 0x80, 0xc0, 0x46,
  491.             0xc0, 0x46, 0x60, 0x46, 0x11, 0x88, 0x01, 0x42, 0xfa, 0xd0, 0xff, 0x21,
  492.             0x11, 0x80, 0xc0, 0x46, 0x80, 0x22, 0xd2, 0x00, 0x9b, 0x18, 0xb2, 0xe7,
  493.             0xc0, 0x46, 0x05, 0x9b, 0x01, 0x33, 0x05, 0x93, 0xb4, 0xe7, 0xc0, 0x46,
  494.             0xff, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0e, 0x3c, 0x33, 0x20, 0x66,
  495.             0x72, 0x6f, 0x6d, 0x20, 0x4d, 0x61, 0x6e, 0x69, 0x61, 0x63, 0x70, 0x08
  496.         ]);
  497.  
  498.         // Define payload offsets (from payload.c .word directives) - EXACT COPY
  499.         this.PAYLOAD_OFFSETS = {
  500.             ORIGINAL_ENTRYPOINT_ADDR: 0,
  501.             FLUSH_MODE: 4,
  502.             SAVE_SIZE: 8,
  503.             PATCHED_ENTRYPOINT: 12,
  504.             WRITE_SRAM_PATCHED: 16,
  505.             WRITE_EEPROM_PATCHED: 20,
  506.             WRITE_FLASH_PATCHED: 24,
  507.             WRITE_EEPROM_V111_POSTHOOK: 28
  508.         };
  509.  
  510.         // Define signatures (from patcher.c) - EXACT COPY
  511.         this.signature = new TextEncoder().encode("<3 from Maniac");
  512.         this.thumbBranchThunk = new Uint8Array([0x00, 0x4b, 0x18, 0x47]); // ldr r3, [pc, # 0]; bx r3
  513.         this.armBranchThunk = new Uint8Array([0x00, 0x30, 0x9f, 0xe5, 0x13, 0xff, 0x2f, 0xe1]); // ldr r0, [pc, #0x1c]; ldr r1, [pc, #0x1c], bx r1
  514.  
  515.         this.writeSramSignature = new Uint8Array([0x30, 0xB5, 0x05, 0x1C, 0x0C, 0x1C, 0x13, 0x1C, 0x0B, 0x4A, 0x10, 0x88, 0x0B, 0x49, 0x08, 0x40]);
  516.         this.writeSram2Signature = new Uint8Array([0x80, 0xb5, 0x83, 0xb0, 0x6f, 0x46, 0x38, 0x60, 0x79, 0x60, 0xba, 0x60, 0x09, 0x48, 0x09, 0x49]);
  517.         this.writeSramRamSignature = new Uint8Array([0x04, 0xC0, 0x90, 0xE4, 0x01, 0xC0, 0xC1, 0xE4, 0x2C, 0xC4, 0xA0, 0xE1, 0x01, 0xC0, 0xC1, 0xE4]);
  518.         this.writeEepromSignature = new Uint8Array([0x70, 0xB5, 0x00, 0x04, 0x0A, 0x1C, 0x40, 0x0B, 0xE0, 0x21, 0x09, 0x05, 0x41, 0x18, 0x07, 0x31, 0x00, 0x23, 0x10, 0x78]);
  519.         this.writeFlashSignature = new Uint8Array([0x70, 0xB5, 0x00, 0x03, 0x0A, 0x1C, 0xE0, 0x21, 0x09, 0x05, 0x41, 0x18, 0x01, 0x23, 0x1B, 0x03]);
  520.         this.writeFlash2Signature = new Uint8Array([0x7C, 0xB5, 0x90, 0xB0, 0x00, 0x03, 0x0A, 0x1C, 0xE0, 0x21, 0x09, 0x05, 0x09, 0x18, 0x01, 0x23]);
  521.         this.writeFlash3Signature = new Uint8Array([0xF0, 0xB5, 0x90, 0xB0, 0x0F, 0x1C, 0x00, 0x04, 0x04, 0x0C, 0x03, 0x48, 0x00, 0x68, 0x40, 0x89]);
  522.         this.writeEepromV111Signature = new Uint8Array([0x0A, 0x88, 0x80, 0x21, 0x09, 0x06, 0x0A, 0x43, 0x02, 0x60, 0x07, 0x48, 0x00, 0x47, 0x00, 0x00]);
  523.         this.writeEepromV11EpiloguePatch = new Uint8Array([0x07, 0x49, 0x08, 0x47]); // ldr r0, [pc, #0x1c]; ldr r1, [pc, #0x1c], bx r1
  524.     }
  525.  
  526.     // File handling
  527.     handleFileSelect(event) {
  528.         const file = event.target.files[0];
  529.         if (file) {
  530.             if (!file.name.toLowerCase().endsWith('.gba')) {
  531.                 this.showFileInfo('❌ Please select a .gba file', 'error');
  532.                 this.elements.patchButton.disabled = true;
  533.                 this.updateDropZoneText('Drop ROM file here or click to browse');
  534.                 return;
  535.             }
  536.  
  537.             // Validate file size (32MB max)
  538.             const maxSize = 32 * 1024 * 1024; // 32MB
  539.             if (file.size > maxSize) {
  540.                 this.showFileInfo('❌ ROM file too large (max 32MB)', 'error');
  541.                 this.elements.patchButton.disabled = true;
  542.                 this.updateDropZoneText('Drop ROM file here or click to browse');
  543.                 return;
  544.             }
  545.  
  546.             this.showFileInfo(`✅ ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`, 'success');
  547.             this.elements.patchButton.disabled = false;
  548.             this.updateDropZoneText('ROM file loaded - Ready to patch!');
  549.         } else {
  550.             this.elements.patchButton.disabled = true;
  551.             this.hideFileInfo();
  552.             this.updateDropZoneText('Drop ROM file here or click to browse');
  553.         }
  554.     }
  555.  
  556.     showFileInfo(message, type = 'info') {
  557.         this.elements.fileInfo.textContent = message;
  558.         this.elements.fileInfo.className = `file-info show ${type}`;
  559.     }
  560.  
  561.     hideFileInfo() {
  562.         this.elements.fileInfo.classList.remove('show');
  563.         this.updateDropZoneText('Drop ROM file here or click to browse');
  564.     }
  565.  
  566.     // Progress tracking
  567.     updateProgress(step, percentage, message) {
  568.         this.currentStep = step;
  569.         this.elements.progressFill.style.width = `${percentage}%`;
  570.         this.elements.progressText.textContent = message;
  571.         this.updateProgressSteps();
  572.     }
  573.  
  574.     updateProgressSteps() {
  575.         const stepsHtml = this.progressSteps.map((stepText, index) => {
  576.             let stepClass = 'pending';
  577.             let icon = '⏳';
  578.            
  579.             if (index < this.currentStep) {
  580.                 stepClass = 'completed';
  581.                 icon = '✅';
  582.             } else if (index === this.currentStep) {
  583.                 stepClass = 'active';
  584.                 icon = '🔄';
  585.             }
  586.  
  587.             return `<div class="progress-step ${stepClass}">
  588.                 <span class="step-icon">${icon}</span>
  589.                 <span>${stepText}</span>
  590.             </div>`;
  591.         }).join('');
  592.  
  593.         this.elements.progressSteps.innerHTML = stepsHtml;
  594.     }
  595.  
  596.     showProgress() {
  597.         this.elements.progressSection.classList.remove('hidden');
  598.         this.elements.statusMessages.classList.add('show');
  599.         this.elements.statusMessages.textContent = '';
  600.     }
  601.  
  602.     hideProgress() {
  603.         this.elements.progressSection.classList.add('hidden');
  604.     }
  605.  
  606.     logMessage(message) {
  607.         this.elements.statusMessages.textContent += message + '\n';
  608.         this.elements.statusMessages.scrollTop = this.elements.statusMessages.scrollHeight;
  609.     }
  610.  
  611.     // EXACT UTILITY FUNCTIONS FROM ORIGINAL BATTERY PATCHER
  612.     readUint32(dataView, offset) {
  613.         return dataView.getUint32(offset, true); // true for little-endian
  614.     }
  615.  
  616.     writeUint32(dataView, offset, value) {
  617.         dataView.setUint32(offset, value, true); // true for little-endian
  618.     }
  619.  
  620.     findBytes(haystack, needle, stride = 1) {
  621.         const haystackLen = haystack.length;
  622.         const needleLen = needle.length;
  623.         if (needleLen > haystackLen) return -1;
  624.  
  625.         for (let i = 0; i <= haystackLen - needleLen; i += stride) {
  626.             let match = true;
  627.             for (let j = 0; j < needleLen; j++) {
  628.                 if (haystack[i + j] !== needle[j]) {
  629.                     match = false;
  630.                     break;
  631.                 }
  632.             }
  633.             if (match) {
  634.                 return i; // Return the starting index of the match
  635.             }
  636.         }
  637.         return -1; // Not found
  638.     }
  639.  
  640.     // EXACT SRAM FUNCTIONS FROM ORIGINAL PROJECT
  641.     getPosition(data, pattern) {
  642.         console.log('Searching for pattern:', pattern);
  643.         for (let i = 0; i <= data.length - pattern.length; i++) {
  644.             let match = true;
  645.             for (let j = 0; j < pattern.length; j++) {
  646.                 // Treat 0x00 in pattern as a wildcard
  647.                 if (pattern[j] !== 0x00 && data[i + j] !== pattern[j]) {
  648.                     match = false;
  649.                     break;
  650.                 }
  651.             }
  652.             if (match) {
  653.                 console.log('Pattern found at position:', i);
  654.                 return i;
  655.             }
  656.         }
  657.         console.log('Pattern not found.');
  658.         return -1;
  659.     }
  660.  
  661.     getFlashPattern(data) {
  662.         console.log('Identifying flash type...');
  663.         for (let i = 0; i < this.flashPatterns.length; i++) {
  664.             const flashPattern = this.flashPatterns[i];
  665.             console.log('Checking flash type:', flashPattern.FlashType);
  666.             const position = this.getPosition(data, flashPattern.IdentPattern.Pattern);
  667.             if (position !== -1) {
  668.                 console.log('Flash type identified:', flashPattern.FlashType);
  669.                 return flashPattern;
  670.             }
  671.         }
  672.         console.log('Unknown flash type.');
  673.         return null;
  674.     }
  675.  
  676.     applyPatches(data, flashPattern) {
  677.         if (!flashPattern) {
  678.             console.log('Unknown flash type. No patches applied.');
  679.             return data;
  680.         }
  681.  
  682.         console.log('Applying patches for:', flashPattern.FlashType);
  683.         let patchedData = new Uint8Array(data); // Create a mutable copy
  684.  
  685.         for (let i = 0; i < flashPattern.PatchCount; i++) {
  686.             const patch = flashPattern.Patches[i];
  687.             const position = this.getPosition(patchedData, patch.Marker.Pattern);
  688.             if (position !== -1) {
  689.                 console.log(`Applying patch ${i + 1} at position ${position}`);
  690.                 // Simulate memcpy
  691.                 for (let j = 0; j < patch.Replace.PatternLength; j++) {
  692.                     patchedData[position + j] = patch.Replace.Pattern[j];
  693.                 }
  694.             } else {
  695.                 console.log(`ERROR: didn't find pattern #${i + 1} for ${flashPattern.FlashType}`);
  696.                // Depending on desired behavior, you might want to stop or continue
  697.                // For now, we'll just log the error and continue
  698.             }
  699.         }
  700.         console.log('Patching done.');
  701.         return patchedData;
  702.     }
  703.  
  704.     // EXACT SRAM PATCHING FUNCTION FROM ORIGINAL
  705.     async applySramPatches(fileData) {
  706.         this.logMessage('Starting SRAM patching...');
  707.        
  708.         const flashType = this.getFlashPattern(fileData);
  709.         console.log("flashtype", flashType);
  710.         const patchedData = this.applyPatches(fileData, flashType);
  711.  
  712.         if (flashType) {
  713.             this.logMessage(`SRAM patches applied for: ${flashType.FlashType}`);
  714.         } else {
  715.             this.logMessage('No SRAM patterns found - ROM may already use SRAM');
  716.         }
  717.  
  718.         return patchedData;
  719.     }
  720.  
  721.     // EXACT BATTERY PATCHING FUNCTION FROM ORIGINAL (adapted for new UI)
  722.     async applyBatteryPatches(romData, batteryMode) {
  723.         this.logMessage('Starting battery patching...');
  724.  
  725.         let romSize = romData.length;
  726.         let dataView = new DataView(romData.buffer);
  727.  
  728.         const MAX_ROM_SIZE = 0x02000000; // 32MB
  729.         if (romSize > MAX_ROM_SIZE) {
  730.             throw new Error(`ROM too large (${romSize} bytes). Max size is ${MAX_ROM_SIZE} bytes.`);
  731.         }
  732.  
  733.         const ALIGNMENT = 0x40000; // 256KB
  734.         if (romSize % ALIGNMENT !== 0) {
  735.             this.logMessage('ROM has been trimmed and is misaligned. Padding to 256KB alignment.');
  736.             const originalSize = romSize;
  737.             romSize = (Math.floor(romSize / ALIGNMENT) + 1) * ALIGNMENT;
  738.             if (romSize > MAX_ROM_SIZE) {
  739.                 throw new Error(`ROM too large after padding (${romSize} bytes). Max size is ${MAX_ROM_SIZE} bytes.`);
  740.             }
  741.             const paddedRomData = new Uint8Array(romSize).fill(0xFF);
  742.             paddedRomData.set(romData.slice(0, originalSize));
  743.             romData = paddedRomData;
  744.             dataView = new DataView(romData.buffer); // Update DataView for padded data
  745.         }
  746.  
  747.         // Check if ROM already patched (signature check)
  748.         if (this.findBytes(romData, this.signature, 4) !== -1) {
  749.             throw new Error('Signature found. ROM already patched!');
  750.         }
  751.  
  752.         // Patch all references to IRQ handler address variable
  753.         const oldIrqAddr = new Uint8Array([0xfc, 0x7f, 0x00, 0x03]);
  754.         const newIrqAddr = new Uint8Array([0xf4, 0x7f, 0x00, 0x03]);
  755.         let foundIrq = 0;
  756.  
  757.         for (let i = 0; i <= romSize - oldIrqAddr.length; i += 4) {
  758.             if (this.findBytes(romData.slice(i, i + oldIrqAddr.length), oldIrqAddr, 1) === 0) {
  759.                 foundIrq++;
  760.                 this.logMessage(`Found a reference to the IRQ handler address at 0x${i.toString(16)}, patching`);
  761.                 romData.set(newIrqAddr, i);
  762.             }
  763.         }
  764.  
  765.         if (!foundIrq) {
  766.             this.logMessage('Could not find any reference to the IRQ handler. Has the ROM already been patched?');
  767.         }
  768.  
  769.         // Find a location to insert the payload immediately before a 0x40000 byte sector
  770.         let payloadBase = -1;
  771.  
  772.         // Use 2160 for calculation to match C version (possible alignment), but install full 2164-byte payload
  773.         const calculationPayloadLen = 2160;
  774.  
  775.         // Original logic to find a location to insert the payload immediately before a 0x40000 byte sector
  776.         for (let i = romSize - ALIGNMENT - calculationPayloadLen; i >= 0; i -= ALIGNMENT) {
  777.             let isAllZeroes = true;
  778.             let isAllOnes = true;
  779.             // Check the sector and the space needed for the payload
  780.             for (let j = 0; j < ALIGNMENT + calculationPayloadLen; ++j) { // Check the ALIGNMENT + calculationPayloadLen bytes
  781.                 if (i + j >= romSize) { // Ensure we don't go out of bounds
  782.                     isAllZeroes = false;
  783.                     isAllOnes = false;
  784.                     break;
  785.                 }
  786.                 if (romData[i + j] !== 0) {
  787.                     isAllZeroes = false;
  788.                 }
  789.                 if (romData[i + j] !== 0xFF) {
  790.                     isAllOnes = false;
  791.                 }
  792.             }
  793.             if (isAllZeroes || isAllOnes) {
  794.                 payloadBase = i;
  795.                 break;
  796.             }
  797.         }
  798.  
  799.         if (payloadBase < 0) {
  800.             this.logMessage("ROM too small to install payload.");
  801.             if (romSize + ALIGNMENT * 2 > MAX_ROM_SIZE) {
  802.                 throw new Error("ROM already max size. Cannot expand. Cannot install payload.");
  803.             } else {
  804.                 this.logMessage("Expanding ROM.");
  805.                 const originalSize = romSize;
  806.                 romSize += ALIGNMENT * 2;
  807.                 const expandedRomData = new Uint8Array(romSize).fill(0xFF);
  808.                 expandedRomData.set(romData.slice(0, originalSize));
  809.                 romData = expandedRomData;
  810.                 dataView = new DataView(romData.buffer); // Update DataView for expanded data
  811.                 // Recalculate payloadBase after expansion
  812.                 payloadBase = romSize - ALIGNMENT - calculationPayloadLen;
  813.                 // After expansion, the calculated payloadBase should be valid,
  814.                 // but we still need to check if the new location is suitable (all 0s or 0xFFs)
  815.                 let isAllZeroes = true;
  816.                 let isAllOnes = true;
  817.                 for (let j = 0; j < ALIGNMENT + calculationPayloadLen; ++j) {
  818.                     if (payloadBase + j >= romSize) {
  819.                         isAllZeroes = false;
  820.                         isAllOnes = false;
  821.                         break;
  822.                     }
  823.                     if (romData[payloadBase + j] !== 0) {
  824.                         isAllZeroes = false;
  825.                     }
  826.                     if (romData[payloadBase + j] !== 0xFF) {
  827.                         isAllOnes = false;
  828.                     }
  829.                 }
  830.                 if (!(isAllZeroes || isAllOnes)) {
  831.                     throw new Error("Expanded ROM location is not suitable for payload.");
  832.                 }
  833.             }
  834.         }
  835.  
  836.         this.logMessage(`Installing payload at offset 0x${payloadBase.toString(16)}, save file stored at 0x${(payloadBase + this.embeddedPayloadBin.length).toString(16)}`);
  837.         // Install only the first 2160 bytes to match C version behavior
  838.         romData.set(this.embeddedPayloadBin.slice(0, calculationPayloadLen), payloadBase);
  839.  
  840.         // Patch the ROM entrypoint and set payload offsets
  841.         // The original entrypoint is an ARM branch instruction at offset 0
  842.         // The offset is stored in the lower 24 bits, shifted right by 2, and is relative to PC + 8
  843.         const originalEntrypointInstruction = this.readUint32(dataView, 0);
  844.         const originalEntrypointOffset = (originalEntrypointInstruction & 0x00FFFFFF) << 2;
  845.         const originalEntrypointAddress = 0x08000000 + 8 + originalEntrypointOffset;
  846.  
  847.         this.logMessage(`Original entrypoint instruction: 0x${originalEntrypointInstruction.toString(16)}, calculated offset: 0x${originalEntrypointOffset.toString(16)}, original entrypoint address: 0x${originalEntrypointAddress.toString(16)}`);
  848.  
  849.         // Set original entrypoint address in the payload
  850.         this.writeUint32(dataView, payloadBase + this.PAYLOAD_OFFSETS.ORIGINAL_ENTRYPOINT_ADDR, originalEntrypointAddress);
  851.  
  852.         // Set flush mode based on batteryMode
  853.         const flushMode = batteryMode === 'auto' ? 0 : 1;
  854.         this.writeUint32(dataView, payloadBase + this.PAYLOAD_OFFSETS.FLUSH_MODE, flushMode);
  855.         this.logMessage(`Selected mode: ${flushMode === 0 ? 'Auto' : 'Keypad'}`);
  856.  
  857.         // Calculate and set the new entrypoint address
  858.         // The new entrypoint is within the injected payload
  859.         const newEntrypointAddress = 0x08000000 + payloadBase + this.readUint32(new DataView(this.embeddedPayloadBin.buffer), this.PAYLOAD_OFFSETS.PATCHED_ENTRYPOINT);
  860.         this.logMessage(`New entrypoint address: 0x${newEntrypointAddress.toString(16)}`);
  861.         // Patch the ROM's entrypoint (ARM branch instruction) to jump to the new entrypoint
  862.         // The offset for the ARM branch is relative to PC + 8, and is the target address minus (PC + 8), shifted right by 2
  863.         const newEntrypointOffset = (newEntrypointAddress - 0x08000008) >> 2;
  864.         this.writeUint32(dataView, 0, 0xea000000 | newEntrypointOffset); // ARM branch instruction
  865.  
  866.         // Patch write functions to hook into the payload
  867.         let foundWriteLocation = false;
  868.         // Iterate through the ROM data to find signatures
  869.         // The original C code iterates by 2 bytes, likely because Thumb instructions are 2 bytes
  870.         for (let i = 0; i <= romSize - 64; i += 2) {
  871.             let signatureMatch = -1;
  872.             let patchType = null;
  873.             let saveSize = 0;
  874.             let patchOffset = 0; // Offset within the found signature location to apply the patch
  875.             let payloadHookOffset = 0; // Offset within the payload to jump to
  876.  
  877.             // Check for each signature
  878.             if ((signatureMatch = this.findBytes(romData.slice(i, i + this.writeSramSignature.length), this.writeSramSignature, 1)) === 0) {
  879.                 patchType = 'thumb';
  880.                 saveSize = 0x8000; // 32KB SRAM
  881.                 patchOffset = 0;
  882.                 payloadHookOffset = this.PAYLOAD_OFFSETS.WRITE_SRAM_PATCHED;
  883.                 this.logMessage(`WriteSram identified at offset 0x${i.toString(16)}, patching`);
  884.             } else if ((signatureMatch = this.findBytes(romData.slice(i, i + this.writeSram2Signature.length), this.writeSram2Signature, 1)) === 0) {
  885.                 patchType = 'thumb';
  886.                 saveSize = 0x8000; // 32KB SRAM
  887.                 patchOffset = 0;
  888.                 payloadHookOffset = this.PAYLOAD_OFFSETS.WRITE_SRAM_PATCHED;
  889.                 this.logMessage(`WriteSram 2 identified at offset 0x${i.toString(16)}, patching`);
  890.             } else if ((signatureMatch = this.findBytes(romData.slice(i, i + this.writeSramRamSignature.length), this.writeSramRamSignature, 1)) === 0) {
  891.                 patchType = 'arm';
  892.                 saveSize = 0x8000; // 32KB SRAM
  893.                 patchOffset = 0;
  894.                 payloadHookOffset = this.PAYLOAD_OFFSETS.WRITE_SRAM_PATCHED;
  895.                 this.logMessage(`WriteSramFast identified at offset 0x${i.toString(16)}, patching`);
  896.             } else if ((signatureMatch = this.findBytes(romData.slice(i, i + this.writeEepromSignature.length), this.writeEepromSignature, 1)) === 0) {
  897.                 patchType = 'thumb';
  898.                 saveSize = 0x2000; // 8KB EEPROM (assuming 64kbit for safety)
  899.                 patchOffset = 0;
  900.                 payloadHookOffset = this.PAYLOAD_OFFSETS.WRITE_EEPROM_PATCHED;
  901.                 this.logMessage(`SRAM-patched ProgramEepromDword identified at offset 0x${i.toString(16)}, patching`);
  902.             } else if ((signatureMatch = this.findBytes(romData.slice(i, i + this.writeFlashSignature.length), this.writeFlashSignature, 1)) === 0) {
  903.                 patchType = 'thumb';
  904.                 saveSize = 0x10000; // 64KB Flash
  905.                 patchOffset = 0;
  906.                 payloadHookOffset = this.PAYLOAD_OFFSETS.WRITE_FLASH_PATCHED;
  907.                 this.logMessage(`SRAM-patched flash write function 1 identified at offset 0x${i.toString(16)}, patching`);
  908.             } else if ((signatureMatch = this.findBytes(romData.slice(i, i + this.writeFlash2Signature.length), this.writeFlash2Signature, 1)) === 0) {
  909.                 patchType = 'thumb';
  910.                 saveSize = 0x10000; // 64KB Flash
  911.                 patchOffset = 0;
  912.                 payloadHookOffset = this.PAYLOAD_OFFSETS.WRITE_FLASH_PATCHED;
  913.                 this.logMessage(`SRAM-patched flash write function 2 identified at offset 0x${i.toString(16)}, patching`);
  914.             } else if ((signatureMatch = this.findBytes(romData.slice(i, i + this.writeFlash3Signature.length), this.writeFlash3Signature, 1)) === 0) {
  915.                 patchType = 'thumb';
  916.                 saveSize = 0x20000; // 128KB Flash (assumed for this signature)
  917.                 patchOffset = 0;
  918.                 payloadHookOffset = this.PAYLOAD_OFFSETS.WRITE_FLASH_PATCHED;
  919.                 this.logMessage(`Flash write function 3 identified at offset 0x${i.toString(16)}, patching`);
  920.             } else if ((signatureMatch = this.findBytes(romData.slice(i, i + this.writeEepromV111Signature.length), this.writeEepromV111Signature, 1)) === 0) {
  921.                 // This is a post-hook patch, applied 12 bytes into the signature
  922.                 patchType = 'thumb_epilogue';
  923.                 saveSize = 0x2000; // 8KB EEPROM (assuming 64kbit for safety)
  924.                 patchOffset = 12;
  925.                 payloadHookOffset = this.PAYLOAD_OFFSETS.WRITE_EEPROM_V111_POSTHOOK;
  926.                 this.logMessage(`SRAM-patched EEPROM_V111 epilogue identified at offset 0x${i.toString(16)}, patching`);
  927.             }
  928.  
  929.             if (patchType !== null) {
  930.                 foundWriteLocation = true;
  931.                 // Set save size in the payload (only if a write function was found)
  932.                 this.writeUint32(dataView, payloadBase + this.PAYLOAD_OFFSETS.SAVE_SIZE, saveSize);
  933.                 this.logMessage(`Save size set to 0x${saveSize.toString(16)}`);
  934.  
  935.                 if (flushMode === 0) { // Only patch in auto mode
  936.                     if (patchType === 'thumb') {
  937.                         romData.set(this.thumbBranchThunk, i + patchOffset);
  938.                         // The address to jump to is 0x08000000 + payload_base + offset_in_payload
  939.                         // For Thumb, the address is stored as a word immediately after the thunk
  940.                         const jumpAddress = 0x08000000 + payloadBase + this.readUint32(new DataView(this.embeddedPayloadBin.buffer), payloadHookOffset);
  941.                         // Set bit 0 for Thumb state jump
  942.                         this.writeUint32(dataView, i + patchOffset + this.thumbBranchThunk.length, jumpAddress);
  943.                         this.logMessage(`Patched Thumb function at 0x${(i + patchOffset).toString(16)} to jump to 0x${jumpAddress.toString(16)}`);
  944.  
  945.                     } else if (patchType === 'arm') {
  946.                         romData.set(this.armBranchThunk, i + patchOffset);
  947.                         // For ARM, the address is stored as a word 8 bytes after the thunk
  948.                         const jumpAddress = 0x08000000 + payloadBase + this.readUint32(new DataView(this.embeddedPayloadBin.buffer), payloadHookOffset);
  949.                         this.writeUint32(dataView, i + patchOffset + this.armBranchThunk.length, jumpAddress);
  950.                         this.logMessage(`Patched ARM function at 0x${(i + patchOffset).toString(16)} to jump to 0x${jumpAddress.toString(16)}`);
  951.  
  952.                     } else if (patchType === 'thumb_epilogue') {
  953.                         romData.set(this.writeEepromV11EpiloguePatch, i + patchOffset);
  954.                         // The address to jump to is 0x08000000 + payload_base + offset_in_payload
  955.                         // For Thumb, the address is stored as a word immediately after the thunk
  956.                         const jumpAddress = 0x08000000 + payloadBase + this.readUint32(new DataView(this.embeddedPayloadBin.buffer), payloadHookOffset);
  957.                         // Corrected offset to match C code: write at i + patchOffset - 1
  958.                         // Set bit 0 for Thumb state jump
  959.                         this.writeUint32(dataView, i + patchOffset - 1, jumpAddress);
  960.                         this.logMessage(`Patched Thumb epilogue function at 0x${(i + patchOffset).toString(16)} to jump to 0x${jumpAddress.toString(16)}`);
  961.                     }
  962.                 }
  963.             }
  964.         }
  965.  
  966.         if (!foundWriteLocation) {
  967.             if (flushMode === 0) {
  968.                 this.logMessage("Could not find a write function to hook. Are you sure the game has save functionality and has been SRAM patched with GBATA?");
  969.             } else {
  970.                 this.logMessage("Unsure what save type this is. Defaulting to 128KB save");
  971.                 this.writeUint32(dataView, payloadBase + this.PAYLOAD_OFFSETS.SAVE_SIZE, 0x20000); // Default to 128KB
  972.                 this.logMessage(`Save size defaulted to 0x${(0x20000).toString(16)}`);
  973.             }
  974.         }
  975.  
  976.         this.logMessage('Battery patching completed');
  977.         return romData;
  978.     }
  979.  
  980.     // Main patching workflow
  981.     async startPatching() {
  982.         const file = this.elements.romFile.files[0];
  983.         if (!file) return;
  984.  
  985.         try {
  986.             this.elements.patchButton.disabled = true;
  987.             this.showProgress();
  988.            
  989.             // Step 1: Validate file
  990.             this.updateProgress(0, 10, 'Validating ROM file...');
  991.             await this.delay(100);
  992.            
  993.             const arrayBuffer = await file.arrayBuffer();
  994.             let romData = new Uint8Array(arrayBuffer);
  995.            
  996.             if (romData.length > 0x02000000) {
  997.                 throw new Error('ROM file too large (max 32MB)');
  998.             }
  999.            
  1000.             this.logMessage(`ROM loaded: ${file.name} (${(romData.length / 1024 / 1024).toFixed(2)} MB)`);
  1001.  
  1002.             // Step 2: Apply SRAM patches (if enabled)
  1003.             this.updateProgress(1, 25, 'Applying SRAM patches...');
  1004.             await this.delay(100);
  1005.            
  1006.             if (this.elements.enableSramPatching.checked) {
  1007.                 romData = await this.applySramPatches(romData);
  1008.             } else {
  1009.                 this.logMessage('SRAM patching skipped (disabled by user)');
  1010.             }
  1011.  
  1012.             // Step 3: Apply battery patches (if enabled)
  1013.             if (this.elements.enableBatteryPatching.checked) {
  1014.                 this.updateProgress(2, 50, 'Injecting battery payload...');
  1015.                 await this.delay(100);
  1016.                
  1017.                 const batteryMode = document.querySelector('input[name="batteryMode"]:checked').value;
  1018.                 this.logMessage(`Selected battery mode: ${batteryMode}`);
  1019.                
  1020.                 this.updateProgress(3, 75, 'Hooking save functions...');
  1021.                 await this.delay(100);
  1022.                
  1023.                 romData = await this.applyBatteryPatches(romData, batteryMode);
  1024.             } else {
  1025.                 this.logMessage('Battery patching skipped (disabled by user)');
  1026.                 this.updateProgress(3, 75, 'Skipping battery patches...');
  1027.                 await this.delay(100);
  1028.             }
  1029.  
  1030.             // Step 4: Finalize
  1031.             this.updateProgress(4, 90, 'Finalizing ROM...');
  1032.             await this.delay(100);
  1033.            
  1034.             const blob = new Blob([romData], { type: 'application/octet-stream' });
  1035.             const url = URL.createObjectURL(blob);
  1036.            
  1037.             const originalFileName = file.name;
  1038.             const baseName = originalFileName.substring(0, originalFileName.lastIndexOf('.'));
  1039.            
  1040.             let suffix = '';
  1041.             const sramEnabled = this.elements.enableSramPatching.checked;
  1042.             const batteryEnabled = this.elements.enableBatteryPatching.checked;
  1043.            
  1044.             if (sramEnabled && batteryEnabled) {
  1045.                 const batteryMode = document.querySelector('input[name="batteryMode"]:checked').value;
  1046.                 suffix = batteryMode === 'auto' ? '_ultimate_auto.gba' : '_ultimate_keypad.gba';
  1047.             } else if (sramEnabled) {
  1048.                 suffix = '_sram_only.gba';
  1049.             } else if (batteryEnabled) {
  1050.                 const batteryMode = document.querySelector('input[name="batteryMode"]:checked').value;
  1051.                 suffix = batteryMode === 'auto' ? '_battery_auto.gba' : '_battery_keypad.gba';
  1052.             } else {
  1053.                 suffix = '_no_patches.gba';
  1054.             }
  1055.            
  1056.             const newFileName = `${baseName}${suffix}`;
  1057.            
  1058.             this.elements.downloadLink.href = url;
  1059.             this.elements.downloadLink.download = newFileName;
  1060.             this.elements.downloadLink.querySelector('.download-text').textContent = `Download ${newFileName}`;
  1061.            
  1062.             // Step 5: Complete
  1063.             this.updateProgress(5, 100, 'Patching complete!');
  1064.             this.logMessage(`\n✅ SUCCESS: ROM patched and ready for download`);
  1065.             this.logMessage(`📁 Output file: ${newFileName}`);
  1066.            
  1067.             this.elements.downloadSection.classList.remove('hidden');
  1068.             this.hideProgress();
  1069.            
  1070.             // Clean up URL after download
  1071.             this.elements.downloadLink.addEventListener('click', () => {
  1072.                 setTimeout(() => URL.revokeObjectURL(url), 100);
  1073.             });
  1074.  
  1075.         } catch (error) {
  1076.             this.logMessage(`\n❌ ERROR: ${error.message}`);
  1077.             this.updateProgress(this.currentStep, 0, 'Patching failed');
  1078.             console.error(error);
  1079.         } finally {
  1080.             this.elements.patchButton.disabled = false;
  1081.         }
  1082.     }
  1083.  
  1084.     // Utility delay function for smooth progress updates
  1085.     delay(ms) {
  1086.         return new Promise(resolve => setTimeout(resolve, ms));
  1087.     }
  1088. }
  1089.  
  1090. // Initialize the application when DOM is loaded
  1091. document.addEventListener('DOMContentLoaded', () => {
  1092.     new UltimateROMPatcher();
  1093. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement