Arquitectura del Mod PlugY
PlugY opera fundamentalmente como un inyector de código y un interceptor de llamadas a la API para el ejecutable Game.exe de Diablo II (compatible con versiones 1.09 a 1.14d). Al parchear la memoria en tiempo de ejecución, el módulo habilita rutinas que originalmente están restringidas a los servidores de Battle.net. Esto incluye la alteración de los límites de asignación de memoria para el inventario, la desactivación de las validaciones de red para las recetas de palabras rúnicas (Runewords) de escalera (Ladder), y la inyección de lógica personlaizada para la redistribución de puntos de atributos.
Automatización del Despliegue
La integración del mod requiere la colocación precisa de la biblioteca PlugY.dll y la modificación de los parámetros de arranque. En lugar de una instalación manual, el siguiente script de PowerShell automatiza la descarga, extracción y creación de un acceso directo con los argumentos de depuración necesarios.
# Script de automatización para el despliegue de PlugY en entornos Windows
$targetDirectory = "C:\Games\Diablo II"
$releaseArchive = "PlugY_11.02.zip"
$repositoryUrl = "https://github.com/YohannNicolas/PlugY/releases/download/v11.02/$releaseArchive"
# Descarga del binario y extracción en el directorio raíz del juego
Invoke-WebRequest -Uri $repositoryUrl -OutFile $releaseArchive
Expand-Archive -Path $releaseArchive -DestinationPath $targetDirectory -Force
# Configuración del acceso directo con parámetros de ventana y omisión de red
$wshShell = New-Object -ComObject WScript.Shell
$shortcut = $wshShell.CreateShortcut("$targetDirectory\Diablo II PlugY.lnk")
$shortcut.TargetPath = "$targetDirectory\Game.exe"
$shortcut.Arguments = "-w -skiptobnet -ns"
$shortcut.WorkingDirectory = $targetDirectory
$shortcut.Save()
Write-Host "Inyección preparada correctamente en $targetDirectory"
Configuración del Entorno mediante Python
El comportamiento del inyector de memoria se define a través del archivo de inicialización PlugY.ini. Para entornos donde se gestionan múltiples perfiles de usuario, es recomendable manipular este archivo programáticamente. El siguiente script en Python utiliza el módulo configparser para habilitar los hooks de almacenamiento y eventos mundiales.
import configparser
import os
ini_path = 'PlugY.ini'
config = configparser.ConfigParser()
if os.path.exists(ini_path):
config.read(ini_path)
# Configuración del sistema de paginación de inventario
config.set('STASH', 'ActiveBigStash', '1')
config.set('STASH', 'ActiveMultiPageStash', '1')
config.set('STASH', 'ActiveSharedStash', '1')
config.set('STASH', 'SharedStashFilename', 'SharedStashSave')
# Habilitación de redistribución de atributos y teclas de acceso rápido
config.set('STATS POINTS', 'ActiveStatsUnassignment', '1')
config.set('STATS POINTS', 'KeyUsed', '18') # Código virtual para la tecla ALT
config.set('SKILLS POINTS', 'ActiveSkillsUnassignment', '1')
# Activación de rutinas de eventos de red (Ladder emulation)
config.set('WORLD EVENT', 'ActiveWorldEvent', '1')
config.set('WORLD EVENT', 'ItemsToSell', 'The Stone of Jordan')
config.set('UBER QUEST', 'ActiveUberQuest', '1')
# Persistencia de los cambios en el archivo
with open(ini_path, 'w') as configfile:
config.write(configfile)
else:
print(f"Error: No se encontró el archivo {ini_path}")
Gestión de Datos y Persistencia
El sistema de almacenamiento compartido utiliza un archivo binario independiente (_LOD_SharedStashSave.sss). A diferencia de los archivos .d2s que encapsulan el estado de un personaje individual, este contenedor centraliza los ítems para que puedan ser accedidos por cualquier instancia del personaje en modo offline. La estructura de paginación se gestiona mediante índices lógicos, permitiendo la creación dinámica de nodos de almacenamiento.
Interfaz de Línea de Comandos (In-Game)
El inyector monitorea el búfer de entrada del cliente para interceptar cadenas de texto específicas y ejecutar rutinas locales. Las instrucciones soportadas incluyan:
| Comando | Operación en Memoria | Sintaxis de Ejemplo |
|---|---|---|
/renamepage |
Escribe una cadena UTF-8 en el encabezado de metadatos de la página actual del stash. | /renamepage Runewords |
/insertpage |
Asigna un nuevo bloque de memoria para el inventario e incrementa el índice. | /insertpage |
/deletepage |
Libera el bloque de memoria de la página actual, siempre que el array de ítems esté vacío. | /deletepage |
/page |
Realiza un salto directo al índice de memoria especificado. | /page 15 |
/players |
Sobrescribe la variable global de dificultad de sesión en tiempo real. | /players 8 |
Resolución de Problemas de Inyección
- Error de Acceso Violación (0xC0000005): Este fallo crítico ocurre cuando software de terceros (como protectores anti-trampas, antivirus o overlasy de hardware) intercepta los mismos ganchos (hooks) de la API de DirectX que PlugY intenta modificar. Se requiere añadir una exclusión en el software de seguridad o ejecutar el cliente con privilegios elevados.
- Corrupción del Archivo SharedStash: Si el proceso
Game.exefinaliza abruptamente durante una operación de I/O, el archivo_LOD_SharedStashSave.ssspuede truncarse. El inyector genera copias de seguridad incrementales en el directorioSave\Backup\, las cuales pueden restaurarse eliminando la extensión.bak. - Conflicto de Mods Base: Al combinar PlugY con modificaciones profundas de
txt files(como Median XL), los offsets de memoria hardcodeados en versiones antiguas de PlugY pueden fallar. Es imperativo utilizar la versión 11.02 o superior, que implementa escaneo de patrones dinámico en lugar de offsets estáticos.
Protocolo Multijugador TCP/IP
Al iniciar una sesión a través de la interfaz TCP/IP local, el módulo desactiva automáticamente el almacenamiento compartido. Esta restricción está diseñada a nivel de código para prevenir la corrupción de archivos por concurrencia (race conditions), ya que el archivo binario compartido no implementa mecanismos de bloqueo (file-locking) nativos para múltiples procesos de escritura simultáneos.