Browse Source

git de papel

Matthew Trejo 4 months ago
commit
36b876afed
14 changed files with 1022 additions and 0 deletions
  1. 203 0
      .gitignore
  2. 122 0
      README.md
  3. 89 0
      config.py
  4. 1 0
      core/__init__.py
  5. 162 0
      core/memory_manager.py
  6. 50 0
      main.py
  7. 116 0
      patcher.py
  8. 1 0
      patches/__init__.py
  9. 86 0
      patches/patch_factory.py
  10. 1 0
      patterns/__init__.py
  11. 94 0
      patterns/pattern_searcher.py
  12. 10 0
      requirements.txt
  13. 1 0
      utils/__init__.py
  14. 86 0
      utils/helpers.py

+ 203 - 0
.gitignore

@@ -0,0 +1,203 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[codz]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py.cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# UV
+#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#uv.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+#poetry.toml
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#   pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
+#   https://pdm-project.org/en/latest/usage/project/#working-with-version-control
+#pdm.lock
+#pdm.toml
+.pdm-python
+.pdm-build/
+
+# pixi
+#   Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
+#pixi.lock
+#   Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
+#   in the .venv directory. It is recommended not to include this directory in version control.
+.pixi
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.envrc
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+# Abstra
+# Abstra is an AI-powered process automation framework.
+# Ignore directories containing user credentials, local state, and settings.
+# Learn more at https://abstra.io/docs
+.abstra/
+
+# Visual Studio Code
+#  Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 
+#  that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
+#  and can be added to the global gitignore or merged into this file. However, if you prefer, 
+#  you could uncomment the following to ignore the entire vscode folder
+# .vscode/
+
+# Ruff stuff:
+.ruff_cache/
+
+# PyPI configuration file
+.pypirc
+
+# Marimo
+marimo/_static/
+marimo/_lsp/
+__marimo__/
+
+# Streamlit
+.streamlit/secrets.toml

+ 122 - 0
README.md

@@ -0,0 +1,122 @@
+# Patcher Avanzado de Stamina para PEAK.exe
+
+Un patcher de memoria avanzado que utiliza patrones de diseño para modificar el comportamiento del stamina en el juego PEAK.exe.
+
+## 🏗️ Arquitectura del Proyecto
+
+El proyecto ha sido reorganizado siguiendo patrones de diseño para mejorar la mantenibilidad y escalabilidad:
+
+### Estructura de Directorios
+
+```
+peakGame/
+├── main.py                 # Punto de entrada principal
+├── patcher.py             # Clase principal (Facade Pattern)
+├── config.py              # Configuración centralizada
+├── advanced_patcher.py    # Archivo original (legacy)
+├── core/
+│   ├── __init__.py
+│   └── memory_manager.py  # Gestión de memoria del proceso
+├── patterns/
+│   ├── __init__.py
+│   └── pattern_searcher.py # Búsqueda de patrones (Strategy Pattern)
+├── patches/
+│   ├── __init__.py
+│   └── patch_factory.py   # Creación de parches (Factory Pattern)
+├── utils/
+│   ├── __init__.py
+│   └── helpers.py         # Utilidades auxiliares
+└── venv/                  # Entorno virtual
+```
+
+## 🎯 Patrones de Diseño Implementados
+
+### 1. **Strategy Pattern** - Búsqueda de Patrones
+- **Ubicación**: `patterns/pattern_searcher.py`
+- **Propósito**: Permite diferentes estrategias de búsqueda de patrones en memoria
+- **Beneficio**: Fácil extensión para nuevos tipos de patrones
+
+### 2. **Factory Pattern** - Creación de Parches
+- **Ubicación**: `patches/patch_factory.py`
+- **Propósito**: Crea diferentes tipos de parches de manera uniforme
+- **Beneficio**: Extensibilidad para nuevos tipos de parches (vida, munición, etc.)
+
+### 3. **Facade Pattern** - Coordinación de Módulos
+- **Ubicación**: `patcher.py`
+- **Propósito**: Proporciona una interfaz simplificada para coordinar todos los módulos
+- **Beneficio**: Simplifica el uso y oculta la complejidad interna
+
+## 🚀 Uso
+
+### Ejecución Básica
+```bash
+python main.py
+```
+
+### Requisitos
+- Python 3.7+
+- psutil
+- Permisos de administrador
+- PEAK.exe ejecutándose
+
+## 📋 Características
+
+- ✅ **Búsqueda optimizada**: Chunks de 128KB con overlap inteligente
+- ✅ **Backup automático**: Copia de seguridad del código original
+- ✅ **Verificación**: Confirma que el parche se aplicó correctamente
+- ✅ **Interfaz amigable**: Mensajes claros y confirmaciones
+- ✅ **Arquitectura modular**: Fácil mantenimiento y extensión
+- ✅ **Manejo de errores**: Gestión robusta de excepciones
+
+## 🔧 Extensibilidad
+
+### Agregar Nuevos Tipos de Parches
+
+1. **Crear nueva clase de parche** en `patches/patch_factory.py`:
+```python
+class NewPatch(Patch):
+    def create_patch(self, original_data: bytes) -> bytes:
+        # Implementar lógica del parche
+        pass
+```
+
+2. **Registrar en la factory**:
+```python
+_patch_types = {
+    'stamina': StaminaPatch,
+    'health': HealthPatch,
+    'new_type': NewPatch  # Agregar aquí
+}
+```
+
+### Agregar Nuevos Buscadores de Patrones
+
+1. **Crear nueva clase** heredando de `PatternSearcher`
+2. **Implementar métodos abstractos** `get_patterns()` y `search()`
+3. **Usar en el patcher principal**
+
+## ⚠️ Advertencias
+
+- **Uso bajo tu propia responsabilidad**
+- **Ejecutar como administrador**
+- **Crear backup antes de usar**
+- **Solo para uso educativo/personal**
+
+## 🔄 Migración desde Versión Anterior
+
+El archivo `advanced_patcher.py` original se mantiene para compatibilidad. La nueva versión modular está en `main.py`.
+
+## 📝 Notas de Desarrollo
+
+- **Configuración centralizada** en `config.py`
+- **Separación de responsabilidades** por módulos
+- **Código reutilizable** y testeable
+- **Documentación inline** en todas las clases
+
+## 🤝 Contribuciones
+
+Para contribuir:
+1. Seguir los patrones de diseño establecidos
+2. Documentar nuevas funcionalidades
+3. Mantener la separación de responsabilidades
+4. Agregar tests cuando sea posible

+ 89 - 0
config.py

@@ -0,0 +1,89 @@
+"""Configuración centralizada para el patcher avanzado"""
+
+# Configuración del proceso objetivo
+TARGET_PROCESS = {
+    'name': "PEAK.exe"
+}
+
+# Configuración de búsqueda de memoria
+MEMORY_SEARCH_CONFIG = {
+    'chunk_size': 0x20000,  # 128KB chunks
+    'overlap': 256,         # Overlap para patrones en bordes
+    'max_address': 0x7FFFFFFFFFFFFFFF  # Espacio de direcciones de 64-bit
+}
+
+# Configuración de volcado hexadecimal
+HEX_DUMP_CONFIG = {
+    'bytes_per_line': 16,
+    'address_width': 8
+}
+
+# Configuración de backup
+BACKUP_CONFIG = {
+    'enabled': False,
+    'filename_format': "backup_0x{address:X}_{timestamp}.bin"
+}
+
+# Configuración de parches
+PATCH_CONFIG = {
+    'stamina': {
+        'description': "Parche de stamina infinita - establece el stamina siempre a 100%",
+        'instructions': [
+            0x75, 0x16,                           # jne (salto original)
+            0xB8, 0x00, 0x00, 0x80, 0x3F,        # mov eax, 0x3F800000 (1.0f)
+            0x66, 0x0F, 0x6E, 0xE8,              # movd xmm5, eax
+            0x90, 0x90, 0x90, 0x90, 0x90,        # nop padding
+            0xF3, 0x0F, 0x11, 0xAE, 0x8C, 0x01, 0x00, 0x00  # movss [rsi+18C],xmm5
+        ]
+    }
+}
+
+# Mensajes de la interfaz de usuario
+UI_MESSAGES = {
+    'banner': {
+        'title': "🎮 === Patcher Avanzado de Stamina para PEAK.exe ===",
+        'description': "Este programa modifica la memoria para que el stamina siempre sea 100%",
+        'warning': "⚠️  ADVERTENCIA: Usa bajo tu propia responsabilidad",
+        'admin_note': "📝 Asegúrate de ejecutar como administrador"
+    },
+    'success': {
+        'process_opened': "✅ Proceso {process_name} abierto exitosamente (PID: {pid})",
+        'patch_applied': "✅ ¡Parche aplicado exitosamente!",
+        'verification_successful': "✅ Verificación exitosa: El parche se aplicó correctamente",
+        'backup_saved': "💾 Copia de seguridad guardada en: {filename}",
+        'patching_completed': "\n🎉 ¡Patcheo completado exitosamente!",
+        'stamina_maintained': "💪 El stamina ahora debería mantenerse al 100%"
+    },
+    'error': {
+        'process_not_found': "❌ No se pudo encontrar el proceso {process_name}",
+        'process_not_opened': "❌ No se pudo abrir el proceso {process_name}",
+        'no_patterns_found': "❌ No se encontraron patrones de stamina",
+        'memory_read_failed': "❌ No se pudo leer la memoria original",
+        'patch_failed': "❌ Error al aplicar el patch",
+        'verification_failed': "❌ Error en la verificación - El patch no se aplicó correctamente",
+        'operation_cancelled': "❌ Operación cancelada por el usuario",
+        'patching_failed': "\n❌ El patcheo falló",
+        'patching_error': "\n💥 Error durante el patcheo: {error}",
+        'user_interrupted': "\n\n⏹️  Operación interrumpida por el usuario",
+        'unexpected_error': "\n💥 Error inesperado: {error}"
+    },
+    'info': {
+        'searching_patterns': "🔍 Buscando patrones en {region_count} regiones de memoria...",
+        'pattern_found': "   ✅ {pattern_name} encontrado en: 0x{address:X}",
+        'using_address': "\n🎯 Usando dirección: 0x{address:X}",
+        'searching_stamina': "\n🔍 Buscando patrones de stamina...",
+        'original_code': "\n📖 Código original:",
+        'patch_to_apply': "\n🔧 Patch a aplicar:",
+        'applying_patch': "\n✍️ Aplicando patch...",
+        'starting_patch_process': "\n🎮 Iniciando proceso de patcheo...",
+        'goodbye': "\n👋 ¡Hasta luego!"
+    }
+}
+
+# Configuración de logging (para futuras mejoras)
+LOGGING_CONFIG = {
+    'enabled': False,
+    'level': 'INFO',
+    'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+    'filename': 'patcher.log'
+}

+ 1 - 0
core/__init__.py

@@ -0,0 +1 @@
+# Core module for the advanced patcher

+ 162 - 0
core/memory_manager.py

@@ -0,0 +1,162 @@
+import ctypes
+from ctypes import wintypes
+import psutil
+from typing import Optional, List, Dict
+import sys
+import os
+
+# Agregar el directorio padre al path para importar config
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from config import MEMORY_SEARCH_CONFIG, UI_MESSAGES
+
+# Constantes de Windows API
+PROCESS_ALL_ACCESS = 0x1F0FFF
+MEM_COMMIT = 0x1000
+MEM_RESERVE = 0x2000
+PAGE_EXECUTE_READWRITE = 0x40
+PAGE_READWRITE = 0x04
+PAGE_EXECUTE_READ = 0x20
+
+# Definir MEMORY_BASIC_INFORMATION
+class MEMORY_BASIC_INFORMATION(ctypes.Structure):
+    _fields_ = [
+        ("BaseAddress", ctypes.c_void_p),
+        ("AllocationBase", ctypes.c_void_p),
+        ("AllocationProtect", wintypes.DWORD),
+        ("RegionSize", ctypes.c_size_t),
+        ("State", wintypes.DWORD),
+        ("Protect", wintypes.DWORD),
+        ("Type", wintypes.DWORD)
+    ]
+
+# Cargar kernel32
+kernel32 = ctypes.windll.kernel32
+
+class MemoryManager:
+    """Gestor de memoria para operaciones de lectura/escritura en procesos"""
+    
+    def __init__(self):
+        self.process_name = None
+        self.process_handle = None
+        self.process_id = None
+    
+    def find_process(self) -> bool:
+        """Encuentra el proceso por nombre"""
+        try:
+            for proc in psutil.process_iter(['pid', 'name']):
+                if proc.info['name'].lower() == self.process_name.lower():
+                    self.process_id = proc.info['pid']
+                    return True
+        except (psutil.NoSuchProcess, psutil.AccessDenied):
+            pass
+        return False
+    
+    def open_process(self, process_name: str) -> bool:
+        """Abre el proceso para lectura/escritura"""
+        self.process_name = process_name
+        if not self.process_id:
+            if not self.find_process():
+                print(UI_MESSAGES['error']['process_not_found'].format(process_name=self.process_name))
+                print("   Asegúrate de que el juego esté ejecutándose.")
+                return False
+        
+        self.process_handle = kernel32.OpenProcess(
+            PROCESS_ALL_ACCESS, False, self.process_id
+        )
+        
+        if not self.process_handle:
+            print(UI_MESSAGES['error']['process_not_opened'].format(process_name=self.process_name))
+            print("   Ejecuta este programa como administrador.")
+            return False
+        
+        print(UI_MESSAGES['success']['process_opened'].format(process_name=self.process_name, pid=self.process_id))
+        return True
+    
+    def read_memory(self, address: int, size: int) -> Optional[bytes]:
+        """Lee memoria del proceso"""
+        buffer = ctypes.create_string_buffer(size)
+        bytes_read = ctypes.c_size_t()
+        
+        success = kernel32.ReadProcessMemory(
+            self.process_handle,
+            ctypes.c_void_p(address),
+            buffer,
+            size,
+            ctypes.byref(bytes_read)
+        )
+        
+        if success:
+            return buffer.raw[:bytes_read.value]
+        return None
+    
+    def write_memory(self, address: int, data: bytes) -> bool:
+        """Escribe memoria en el proceso"""
+        # Cambiar protección de memoria
+        old_protect = wintypes.DWORD()
+        kernel32.VirtualProtectEx(
+            self.process_handle,
+            ctypes.c_void_p(address),
+            len(data),
+            PAGE_EXECUTE_READWRITE,
+            ctypes.byref(old_protect)
+        )
+        
+        bytes_written = ctypes.c_size_t()
+        success = kernel32.WriteProcessMemory(
+            self.process_handle,
+            ctypes.c_void_p(address),
+            data,
+            len(data),
+            ctypes.byref(bytes_written)
+        )
+        
+        # Restaurar protección original
+        kernel32.VirtualProtectEx(
+            self.process_handle,
+            ctypes.c_void_p(address),
+            len(data),
+            old_protect.value,
+            ctypes.byref(old_protect)
+        )
+        
+        return success and bytes_written.value == len(data)
+    
+    def get_memory_regions(self) -> List[Dict]:
+        """Obtiene las regiones de memoria ejecutables del proceso"""
+        regions = []
+        address = 0
+        
+        while address < MEMORY_SEARCH_CONFIG['max_address']:
+            mbi = MEMORY_BASIC_INFORMATION()
+            size = kernel32.VirtualQueryEx(
+                self.process_handle,
+                ctypes.c_void_p(address),
+                ctypes.byref(mbi),
+                ctypes.sizeof(mbi)
+            )
+            
+            if size == 0:
+                break
+            
+            if mbi.BaseAddress is None or mbi.RegionSize is None:
+                address += 0x1000
+                continue
+            
+            # Solo regiones ejecutables
+            if (mbi.State == MEM_COMMIT and 
+                mbi.Protect in [PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE]):
+                regions.append({
+                    'base': mbi.BaseAddress,
+                    'size': mbi.RegionSize,
+                    'protect': mbi.Protect
+                })
+            
+            address = mbi.BaseAddress + mbi.RegionSize
+        
+        return regions
+    
+    def close_process(self):
+        """Cierra el handle del proceso"""
+        if self.process_handle:
+            kernel32.CloseHandle(self.process_handle)
+            self.process_handle = None

+ 50 - 0
main.py

@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+"""
+Patcher Avanzado de Stamina para PEAK.exe
+
+Este programa utiliza patrones de diseño para organizar el código:
+- Strategy Pattern: Para diferentes tipos de búsqueda de patrones
+- Factory Pattern: Para crear diferentes tipos de parches
+- Facade Pattern: Para coordinar todos los módulos
+
+Autor: Sistema de Patcheo Avanzado
+Versión: 2.0
+"""
+
+import sys
+import traceback
+import os
+
+# Agregar el directorio actual al path para importar config
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+from config import UI_MESSAGES
+from patcher import AdvancedPatcher
+from utils.helpers import UserInterface
+
+def main():
+    """Función principal de la aplicación"""
+    # Mostrar banner
+    UserInterface.print_banner()
+    
+    # Crear instancia del patcher
+    patcher = AdvancedPatcher()
+    success = False
+    
+    try:
+        # Ejecutar el proceso de patcheo
+        success = patcher.run()
+    
+    except KeyboardInterrupt:
+        print(UI_MESSAGES['error']['user_interrupted'])
+    except Exception as e:
+        print(UI_MESSAGES['error']['unexpected_error'].format(error=e))
+        traceback.print_exc()
+    
+    finally:
+        # Asegurar limpieza de recursos
+        patcher.cleanup()
+        input("\n⏎ Presiona Enter para salir...")
+        return 0 if success else 1
+
+if __name__ == "__main__":
+    sys.exit(main())

+ 116 - 0
patcher.py

@@ -0,0 +1,116 @@
+import sys
+import os
+
+# Agregar el directorio padre al path para importar config
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+from config import TARGET_PROCESS, UI_MESSAGES
+from core.memory_manager import MemoryManager
+from patterns.pattern_searcher import StaminaPatternSearcher
+from patches.patch_factory import PatchFactory
+from utils.helpers import HexDumper, BackupManager, UserInterface
+
+class AdvancedPatcher:
+    """Clase principal del patcher que coordina todos los módulos (Facade Pattern)"""
+    
+    def __init__(self):
+        self.memory_manager = MemoryManager()
+        self.pattern_searcher = StaminaPatternSearcher()
+        self.patch_factory = PatchFactory()
+        self.hex_dumper = HexDumper()
+        self.backup_manager = BackupManager(self.memory_manager)
+        self.ui = UserInterface()
+        self.target_process = TARGET_PROCESS['name']
+    
+    def apply_stamina_patch(self) -> bool:
+        """Aplica el parche de stamina usando todos los módulos"""
+        # Buscar y abrir el proceso
+        if not self.memory_manager.open_process(self.target_process):
+            print(UI_MESSAGES['error']['process_not_found'].format(process_name=self.target_process))
+            return False
+    
+        print(UI_MESSAGES['info']['searching_stamina'])
+        
+        # Buscar patrones
+        found_addresses = self.pattern_searcher.search(self.memory_manager)
+        
+        if not found_addresses:
+            print(UI_MESSAGES['error']['no_patterns_found'])
+            return False
+        
+        # Mostrar direcciones encontradas
+        self.ui.print_found_addresses(found_addresses)
+        
+        # Seleccionar dirección objetivo
+        target_address = self.ui.select_target_address(found_addresses)
+        
+        # Crear el parche
+        patch = self.patch_factory.create_patch('stamina')
+        if not patch:
+            print("❌ No se pudo crear el parche de stamina")
+            return False
+        
+        # Leer código original
+        original_size = patch.get_patch_size()
+        original_code = self.memory_manager.read_memory(target_address, original_size)
+        if not original_code:
+            print("❌ No se pudo leer el código original")
+            return False
+        
+        # Crear backup
+        self.backup_manager.backup_original_code(target_address, len(original_code))
+        
+        # Mostrar código original
+        print(UI_MESSAGES['info']['original_code'])
+        self.hex_dumper.print_hex_dump(original_code, target_address)
+        
+        # Generar datos del parche
+        patch_data = patch.create_patch(original_code)
+        
+        # Mostrar código del parche
+        print(UI_MESSAGES['info']['patch_to_apply'])
+        self.hex_dumper.print_hex_dump(patch_data)
+        
+        # Confirmar aplicación
+        if not self.ui.confirm_patch():
+            print(UI_MESSAGES['error']['operation_cancelled'])
+            return False
+        
+        # Aplicar el parche
+        print(UI_MESSAGES['info']['applying_patch'])
+        if self.memory_manager.write_memory(target_address, patch_data):
+            print(UI_MESSAGES['success']['patch_applied'])
+            
+            # Verificar que el patch se escribió correctamente
+            verification_data = self.memory_manager.read_memory(target_address, len(patch_data))
+            if verification_data == patch_data:
+                print(UI_MESSAGES['success']['verification_successful'])
+                return True
+            else:
+                print(UI_MESSAGES['error']['verification_failed'])
+                return False
+        else:
+            print(UI_MESSAGES['error']['patch_failed'])
+            return False
+    
+    def cleanup(self):
+        """Limpia los recursos utilizados"""
+        self.memory_manager.close_process()
+    
+    def run(self) -> bool:
+        """Ejecuta el proceso completo de patcheo"""
+        print(UI_MESSAGES['info']['starting_patch_process'])
+        
+        success = False
+        try:
+            success = self.apply_stamina_patch()
+            if success:
+                print(UI_MESSAGES['success']['patching_completed'])
+                print(UI_MESSAGES['success']['stamina_maintained'])
+            else:
+                print(UI_MESSAGES['error']['patching_failed'])
+        except Exception as e:
+            print(UI_MESSAGES['error']['patching_error'].format(error=e))
+        finally:
+            self.cleanup()
+            
+        return success

+ 1 - 0
patches/__init__.py

@@ -0,0 +1 @@
+# Patches module for creating different types of memory patches

+ 86 - 0
patches/patch_factory.py

@@ -0,0 +1,86 @@
+from abc import ABC, abstractmethod
+from typing import Optional
+import sys
+import os
+
+# Agregar el directorio padre al path para importar config
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from config import PATCH_CONFIG
+
+class Patch(ABC):
+    """Clase base para todos los tipos de parches"""
+    
+    @abstractmethod
+    def create_patch(self, original_data: bytes) -> bytes:
+        """Crea el código del parche"""
+        pass
+    
+    @abstractmethod
+    def get_patch_size(self) -> int:
+        """Retorna el tamaño del parche"""
+        pass
+    
+    @abstractmethod
+    def get_description(self) -> str:
+        """Retorna la descripción del parche"""
+        pass
+
+class StaminaPatch(Patch):
+    """Parche específico para stamina infinita"""
+    
+    def create_patch(self, original_data: bytes) -> bytes:
+        """Crea el código de parche para stamina infinita"""
+        # Obtener configuración del patch desde config
+        stamina_config = PATCH_CONFIG['stamina']
+        
+        # Preservar el byte del salto original
+        jump_offset = original_data[1] if len(original_data) > 1 else 0x16
+        
+        # Usar instrucciones desde configuración
+        instructions = stamina_config['instructions'].copy()
+        instructions[1] = jump_offset  # Actualizar el offset del salto
+        
+        return bytes(instructions)
+    
+    def get_patch_size(self) -> int:
+        """Retorna el tamaño del parche de stamina"""
+        return 18
+    
+    def get_description(self) -> str:
+        """Descripción del parche de stamina"""
+        return PATCH_CONFIG['stamina']['description']
+
+class HealthPatch(Patch):
+    """Parche para vida infinita (ejemplo de extensibilidad)"""
+    
+    def create_patch(self, original_data: bytes) -> bytes:
+        """Placeholder para parche de vida"""
+        # Implementación futura
+        return b'\x90' * 8  # NOPs por ahora
+    
+    def get_patch_size(self) -> int:
+        return 8
+    
+    def get_description(self) -> str:
+        return "Parche de vida infinita (no implementado)"
+
+class PatchFactory:
+    """Factory para crear diferentes tipos de parches"""
+    
+    _patch_types = {
+        'stamina': StaminaPatch,
+        'health': HealthPatch
+    }
+    
+    @classmethod
+    def create_patch(cls, patch_type: str) -> Optional[Patch]:
+        """Crea un parche del tipo especificado"""
+        patch_class = cls._patch_types.get(patch_type.lower())
+        if patch_class:
+            return patch_class()
+        return None
+    
+    @classmethod
+    def get_available_patches(cls) -> list:
+        """Retorna la lista de parches disponibles"""
+        return list(cls._patch_types.keys())

+ 1 - 0
patterns/__init__.py

@@ -0,0 +1 @@
+# Patterns module for memory pattern searching

+ 94 - 0
patterns/pattern_searcher.py

@@ -0,0 +1,94 @@
+from abc import ABC, abstractmethod
+from typing import List, Tuple, Dict, Optional
+import sys
+import os
+
+# Agregar el directorio padre al path para importar config
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from config import MEMORY_SEARCH_CONFIG, UI_MESSAGES
+from core.memory_manager import MemoryManager
+
+class PatternSearcher(ABC):
+    """Interfaz base para buscadores de patrones (Strategy Pattern)"""
+    
+    def __init__(self):
+        pass
+    
+    @abstractmethod
+    def get_patterns(self) -> Dict[str, Tuple[bytes, List[str]]]:
+        """Retorna los patrones a buscar"""
+        pass
+    
+    @abstractmethod
+    def search(self, memory_manager: MemoryManager) -> List[Tuple[str, int]]:
+        """Busca los patrones en memoria"""
+        pass
+    
+    def pattern_matches(self, data: bytes, pattern: bytes, mask: List[str], offset: int) -> bool:
+        """Verifica si un patrón coincide en una posición específica"""
+        pattern_len = len(pattern)
+        if offset + pattern_len > len(data):
+            return False
+        
+        data_slice = data[offset:offset + pattern_len]
+        for i, (p_byte, m_char) in enumerate(zip(pattern, mask)):
+            if m_char != '?' and data_slice[i] != p_byte:
+                return False
+        return True
+
+class StaminaPatternSearcher(PatternSearcher):
+    """Buscador específico para patrones de stamina"""
+    
+    def get_patterns(self) -> Dict[str, Tuple[bytes, List[str]]]:
+        """Define los patrones específicos para stamina"""
+        return {
+            "stamina_pattern_1": (
+                bytes([0x75, 0x00, 0xF3, 0x0F, 0x10, 0x44, 0x24, 0x08, 
+                      0xF3, 0x0F, 0x5A, 0xC0, 0xF2, 0x0F, 0x5A, 0xE8, 
+                      0xF3, 0x0F, 0x11, 0xAE, 0x8C, 0x01, 0x00, 0x00]),
+                ['x', '?', 'x', 'x', 'x', 'x', 'x', 'x', 
+                 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 
+                 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x']
+            )
+        }
+    
+    def search(self, memory_manager: MemoryManager) -> List[Tuple[str, int]]:
+        """Busca patrones de stamina en memoria"""
+        patterns = self.get_patterns()
+        regions = memory_manager.get_memory_regions()
+        found_addresses = []
+        
+        print(UI_MESSAGES['info']['searching_patterns'].format(region_count=len(regions)))
+        
+        # Pre-calcular longitudes de patrones
+        pattern_info = {name: (pattern, mask, len(pattern)) 
+                       for name, (pattern, mask) in patterns.items()}
+        
+        for region_idx, region in enumerate(regions):
+            print(f"   Región {region_idx + 1}/{len(regions)}: 0x{region['base']:X} - 0x{region['base'] + region['size']:X}")
+            
+            # Configuración optimizada de chunks desde config
+            chunk_size = MEMORY_SEARCH_CONFIG['chunk_size']
+            overlap = MEMORY_SEARCH_CONFIG['overlap']
+            
+            for offset in range(0, region['size'], chunk_size):
+                current_address = region['base'] + offset
+                read_size = min(chunk_size + overlap, region['size'] - offset)
+                
+                data = memory_manager.read_memory(current_address, read_size)
+                if not data:
+                    continue
+                
+                # Buscar cada patrón
+                data_len = len(data)
+                for pattern_name, (pattern, mask, pattern_len) in pattern_info.items():
+                    search_limit = data_len - pattern_len + 1
+                    for i in range(search_limit):
+                        if self.pattern_matches(data, pattern, mask, i):
+                            found_address = current_address + i
+                            found_addresses.append((pattern_name, found_address))
+                            print(UI_MESSAGES['info']['pattern_found'].format(pattern_name=pattern_name, address=found_address))
+                            print("   🛑 Deteniendo búsqueda - primer patrón encontrado")
+                            return found_addresses
+        
+        return found_addresses

+ 10 - 0
requirements.txt

@@ -0,0 +1,10 @@
+# Dependencias del Patcher Avanzado de Stamina
+
+# Biblioteca para información de procesos del sistema
+psutil>=5.8.0
+
+# Bibliotecas estándar de Python (incluidas por defecto)
+# ctypes - Para interactuar con APIs de Windows
+# time - Para timestamps de backup
+# typing - Para type hints
+# abc - Para clases abstractas

+ 1 - 0
utils/__init__.py

@@ -0,0 +1 @@
+# Utils module for auxiliary functions

+ 86 - 0
utils/helpers.py

@@ -0,0 +1,86 @@
+import time
+from typing import Optional
+import sys
+import os
+
+# Agregar el directorio padre al path para importar config
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from config import HEX_DUMP_CONFIG, BACKUP_CONFIG, UI_MESSAGES
+from core.memory_manager import MemoryManager
+
+class HexDumper:
+    """Utilidad para volcados hexadecimales"""
+    
+    @staticmethod
+    def print_hex_dump(data: bytes, base_address: int = 0):
+        """Imprime un volcado hexadecimal de los datos"""
+        bytes_per_line = HEX_DUMP_CONFIG['bytes_per_line']
+        address_width = HEX_DUMP_CONFIG['address_width']
+        
+        for i in range(0, len(data), bytes_per_line):
+            line_data = data[i:i+bytes_per_line]
+            hex_part = ' '.join(f'{b:02X}' for b in line_data)
+            ascii_part = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in line_data)
+            print(f"   0x{base_address + i:0{address_width}X}: {hex_part:<48} {ascii_part}")
+
+class BackupManager:
+    """Gestor de copias de seguridad"""
+    
+    def __init__(self, memory_manager: MemoryManager):
+        self.memory_manager = memory_manager
+    
+    def backup_original_code(self, address: int, size: int) -> Optional[str]:
+        """Crea una copia de seguridad del código original"""
+        data = self.memory_manager.read_memory(address, size)
+        if data and BACKUP_CONFIG['enabled']:
+            timestamp = int(time.time())
+            filename = BACKUP_CONFIG['filename_format'].format(address=address, timestamp=timestamp)
+            try:
+                with open(filename, 'wb') as f:
+                    f.write(data)
+                print(UI_MESSAGES['success']['backup_saved'].format(filename=filename))
+                return filename
+            except IOError as e:
+                print(f"❌ Error al guardar backup: {e}")
+        return None
+
+class UserInterface:
+    """Utilidades para interacción con el usuario"""
+    
+    @staticmethod
+    def confirm_action(message: str) -> bool:
+        """Solicita confirmación del usuario"""
+        response = input(f"\n❓ {message} (s/N): ").strip().lower()
+        return response in ['s', 'si', 'sí', 'y', 'yes']
+    
+    @staticmethod
+    def confirm_patch() -> bool:
+        """Solicita confirmación para aplicar el patch"""
+        response = input("\n❓ ¿Aplicar el parche? (s/N): ").strip().lower()
+        return response in ['s', 'si', 'sí', 'y', 'yes']
+    
+    @staticmethod
+    def print_banner():
+        """Imprime el banner de la aplicación"""
+        banner = UI_MESSAGES['banner']
+        print(banner['title'])
+        print(banner['description'])
+        print(banner['warning'])
+        print(banner['admin_note'] + "\n")
+    
+    @staticmethod
+    def print_found_addresses(found_addresses: list):
+        """Imprime las direcciones encontradas"""
+        print(f"\n📍 Se encontraron {len(found_addresses)} coincidencias:")
+        for i, (pattern_name, address) in enumerate(found_addresses):
+            print(f"   {i + 1}. {pattern_name}: 0x{address:X}")
+    
+    @staticmethod
+    def select_target_address(found_addresses: list) -> int:
+        """Selecciona la dirección objetivo de la lista"""
+        target_address = next(
+            (address for pattern_name, address in found_addresses if "pattern_1" in pattern_name),
+            found_addresses[0][1]  # Fallback a la primera coincidencia
+        )
+        print(UI_MESSAGES['info']['using_address'].format(address=target_address))
+        return target_address