Reviven un juego RTS muerto de Dune: EmperorLauncher
(wheybags.com)- El RTS de 2001
Emperor: Battle for Dunetenía problemas para ejecutarse, instalarse y jugarse en línea en Windows moderno, y EmperorLauncher es un parche que lo devuelve a un estado jugable - Las mejoras clave se centran en soporte para alta resolución, límite de 60 FPS, multijugador en línea por IP directa, modo campaña cooperativa y evitar el proceso de instalación roto
- La implementación se compone de un lanzador de reemplazo para
Emperor.exe, inyección de DLL sobreGame.exe, parcheo de funciones con Microsoft Detours, hooks de renderizado de Direct3D 7, interceptación de winsock y un servidor WOL simplificado - El juego en línea deja atrás la estructura original de P2P, puertos aleatorios y NAT punching, y la tuneliza hacia una sola conexión cliente→servidor, para que solo el host del servidor tenga que preocuparse por la configuración de red
- También se encarga de copiar los archivos del CD original, extraer
.cab, aplicar el parche oficial v1.09, evitar el registro COM deWOLAPI.DLL, y manejar todo desde la instalación hasta la ejecución con una UI lanzadora en Win32
Dónde se atascaba Emperor: Battle for Dune
Emperor: Battle for Dunees un juego de estrategia en tiempo real creado en 2001 por Westwood Studios, y es la secuela deDune 2000- En sistemas modernos todavía quedan varios problemas que impiden jugarlo
- No puede ejecutarse en alta resolución adecuada para pantallas modernas
- En multijugador, la velocidad de simulación del juego no está limitada y se vuelve demasiado rápida
- Westwood Online (WOL) ya no funciona, así que jugar fuera de la LAN es difícil
- La campaña cooperativa era una función exclusiva en línea, así que no puede usarse por LAN
- El instalador incluido en el disco está roto
- Varios efectos visuales se rompen con las altas tasas de refresco de las PC modernas
- EmperorLauncher es un parche hecho para resolver estos problemas, y tanto el archivo descargable como el código fuente están publicados
Reemplazo de Emperor.exe e inicialización de Game.exe
- El
Emperor.exedel juego no es el ejecutable real, sino un wrapper delgado que lanzaGame.exe - Si se ejecuta
Game.exedirectamente no pasa nada, así que hubo que analizar el proceso de inicialización que hacíaEmperor.exey reproducirlo con un lanzador de reemplazo - Para el análisis se usó IDA
- IDA puede desensamblar ejecutables y decompilar parte del código a una forma parecida a C
- En un binario donde ya no existen la información de tipos ni las estructuras, hubo que rastrear llamadas de función y uso de la API de Windows
- Antes de ejecutar
Game.exe,Emperor.execrea un mutex y un handle de file mapping anónimo, luego procesa datos leídos desdeEmperor.daty los copia en el mapping - El proceso padre usa mensajes de Windows para enviar el valor del handle del file mapping al hilo principal del proceso hijo obtenido con
CreateProcessA- Se usa el ID de mensaje personalizado
0xBEEF - Los datos del file mapping eran las tres cadenas
"UIDATA,3DDATA,MAPS", que se entregaban al código de carga de assets del juego
- Se usa el ID de mensaje personalizado
- En vez de reimplementar directamente el código de descifrado, se puso una herramienta de volcado en lugar de
Game.exepara guardar en disco los datos recibidos, y luego el lanzador repite esa misma secuencia
Inyección de DLL y método de parcheo de funciones
- Para aplicar el parche era necesario ejecutar código del usuario dentro del proceso
Game.exe, y para eso se utilizó el método CreateRemoteThread + LoadLibrary - El procedimiento de inyección sigue este orden
- Se reserva un búfer en la memoria del proceso objetivo con
VirtualAllocEx - Se copia la cadena con la ruta del DLL usando
WriteProcessMemory - Se pasan la dirección de
LoadLibraryy el búfer con la ruta del DLL aCreateRemoteThreadpara cargar el DLL dentro del proceso objetivo - Cuando se ejecuta
DllMaindel DLL, corre el código del parche
- Se reserva un búfer en la memoria del proceso objetivo con
- El proceso se inicia en estado suspended y luego se inyecta el DLL para asegurar ejecución antes de
main - Para modificar funciones existentes se usó Microsoft Detours
- Detours reemplaza las instrucciones iniciales de la función original por un salto que redirige la llamada a una función sustituta
- Las instrucciones originales sobrescritas se copian a otra zona de memoria, y luego se crea un wrapper que salta al resto de la función original para que también pueda llamarse
- Como las páginas de código de las funciones no son escribibles por seguridad, hay que cambiar permisos con
VirtualProtecty llamar aFlushInstructionCachedespués de modificarlas
Restauración de logs de depuración
- Dentro del binario había llamadas que parecían logs de depuración, pero la función de destino real era una función vacía con solo
ret - Parece que en el build de release varias funciones vacías se fusionaron en el mismo código, y una de ellas era el logger de depuración
- Al principio se usó una heurística que interpretaba el primer argumento como un puntero a cadena y revisaba si podía imprimirse como ASCII legible
- Los accesos a punteros inválidos se atrapaban e ignoraban mediante excepciones SEH de Windows
- El método funcionaba hasta cierto punto, pero seguían existiendo falsos positivos y falsos negativos
- Después, con la función de parcheo de IDA y un script en Python, los sitios de llamada del log se movieron a otra función vacía separada
- Algunos se encontraron con heurística, y otros buscando el patrón de hacer push de una constante de cadena y luego llamar
- Los cientos de sitios de llamada restantes se anotaron manualmente
- Los logs restaurados ayudaron a depurar el multijugador WOL
- Al ver el log de assert
"MyId == INVALID_ID"durante el manejo deSC_MESSAGE_YOUR_DETAILS, se confirmó con un volcado de Wireshark que el comandoGAMEOPTse estaba enviando por error a todos los jugadores
- Al ver el log de assert
Parche gráfico de Direct3D 7
- Emperor es un juego basado en Direct3D 7, y el soporte de Direct3D 7 en Windows moderno no es completo
- El problema de alta resolución estaba relacionado con el límite de tamaño máximo de textura de 2048 en la capa wrapper de Direct3D 7, y se resolvió aprovechando código de LegacyD3DResolutionHack de UCyborg
- El juego no maneja bien pantallas fuera de la relación 4:3
- El renderizado en sí funciona, pero la UI se rompe como si estuviera exageradamente ampliada
- El renderizado del mouse dentro del juego también queda desfasado según la distancia al centro de la pantalla
- La solución fue letterboxing 4:3
- Al ejecutar el juego en modo ventana se puede usar una resolución arbitraria
- Se elimina el estilo del borde de la ventana y se vuelve a parentar la ventana del juego sobre una ventana negra de pantalla completa
- También se agregó captura del mouse para que el desplazamiento por bordes no se rompa en multimonitor o en modo ventana
- El límite de framerate se fijó a 60 FPS parcheando
IDirect3DDevice7::EndSceneEndScenese llama una vez al final de cada frame, así que es un buen punto para calcular la espera y hacer sleep del hilo- Como el puntero a
EndSceneno se exporta directamente, primero se hookean en secuenciaDirectDrawCreateExeIDirect3D7::CreateDevicepara obtener el puntero desde la vtable
Multijugador en línea y reemplazo de WOL
- El objetivo era tener multijugador por IP directa solo con port forwarding e ingreso manual de IP, sin infraestructura de lobby ni hosting
- El modo LAN funciona, pero busca servidores por UDP broadcast, así que no sirve para jugar por internet
- El menú LAN no tiene función de ingreso manual de IP
- Al principio se intentó parchear el chat LAN para indicar la IP, pero se abandonó al confirmarse que la campaña cooperativa era exclusiva de WOL
- Para revivir WOL hacían falta dos cosas
- Un servidor maestro WOL falso para que el juego supiera adónde conectarse y qué partida iniciar
- Un proxy para que los paquetes del juego funcionaran sobre una conexión IP directa
- En la estructura original de WOL, además del master server, existía un servidor “mangler”, que aparentemente coordinaba el NAT punching
- El servidor mangler original ya no existe, y el juego se quedaba colgado esperando su respuesta
- En el parche se eliminaron las llamadas al mangler
- Emperor usa un modelo de red P2P y abre conexiones bidireccionales entre cada par de jugadores, eligiendo puertos aleatorios
- Como todos los clientes deben poder recibir puertos abiertos, esta arquitectura no encaja bien con el internet moderno
- La solución fue interceptar funciones de winsock para tunelizar todas las conexiones por una única conexión cliente→servidor
- Se interceptan los mensajes que el cliente quiere enviar al servidor o a otros clientes, se encapsulan con un header y se mandan por esa conexión única
- Un hilo del lado del servidor recibe los mensajes y los distribuye a su destino
- El juego sigue creyendo que funciona en P2P, pero en la práctica solo el host del servidor necesita encargarse de la configuración de red
- Con esta configuración fue posible iniciar y unirse a partidas de campaña cooperativa
Implementación de un servidor WOL simplificado
- El master server de WOL tenía una estructura más parecida a un servidor IRC
- En
xwis.nethabía un servidor aparentemente operado por fans, y al momento de escribir esto parecía incluso tener acceso a la entrada DNS original del juego,servserv.westwood.com- Emperor no funcionaba del todo bien tal cual en xwis, pero sirvió como referencia para crear y entrar a lobbies
- También se usó como referencia la implementación pública de WOL en handle_wol.cpp de pvpgn-server
- La razón para crear un servidor propio fue que no hay garantía de que un servidor externo siga disponible para siempre
- La meta no era operar una comunidad competitiva, sino ofrecer las funciones mínimas necesarias para lanzar partidas multijugador
- WOL mezcla IRC estándar con comportamientos personalizados
- Los lobbies del juego son canales especiales
- La información del lobby usa el topic de IRC
- El chat del lobby usa
PAGEen lugar dePRIVMSG - La sincronización de configuración de partida usa mensajes
GAMEINFOcon contenido no ASCII
- La implementación básica del servidor WOL se completó por prueba y error, y aunque no es robusta si uno se sale del camino normal, funciona
Instalador y aplicación del parche v1.09
- El instalador original está roto, así que hacía falta un rodeo donde el usuario copiara el contenido del CD al disco duro y sobrescribiera el setup con uno alternativo
- EmperorLauncher incluye una función de instalación que copia archivos desde el CD original y extrae archivos
.cab.cabes un formato de archivo similar a zip, y Windows tiene una interfaz para extraerlo
- Aplicar el último parche oficial, v1.09, fue más complicado
- No funcionó el método de extraer simplemente
EM109EN.EXEcon 7zip para obtener los binarios más recientes - Dentro de los recursos de Windows se encontró un recurso grande donde era visible el header de un ejecutable
- Los primeros 4 bytes de ese recurso eran el tamaño del archivo, y lo que seguía era el archivo real
- No funcionó el método de extraer simplemente
EM109EN.EXEextrae un DLL embebido a un archivo temporal, lo carga y luego ejecuta la funciónRTPatch32@12dentro del DLLRTPatchera una herramienta de parcheo binario por diff- Se tomó como referencia la herramienta myRTP para cargar y ejecutar directamente el DLL embebido
- El DLL no leía la ruta de instalación desde el argumento recibido, sino desde el registry, así que se creó una key falsa del registry con la ubicación esperada para aplicar el parche
Manejo de Westwood Online Shared Internet Components
- La instalación original separa el juego principal Emperor de Westwood Online Shared Internet Components
- Sin este componente, WOL no funciona, y el archivo principal es
WOLAPI.DLL WOLAPI.DLLes una biblioteca de clases COM, y Emperor crea los objetos COM dentro del DLL medianteCoCreateClass- El registro COM habitual registra globalmente el CLSID y la ruta del DLL bajo
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID- Este método requiere privilegios de administrador
- Además afecta a todo el sistema, no solo al proceso del juego
- En el parche se resolvió usando redirección del registry y
OaEnablePerUserTLibRegistrationpara manejarlo como registro por usuario- No se encontró una forma de registrar la class library solo para un proceso
- Los intentos de usar directamente
DllGetClassObjectno funcionaron
UI del lanzador y resultado final
- El último paso fue una UI sencilla del lanzador para ingresar la IP y cambiar configuraciones básicas
- La UI fue escrita con controles Win32 puros
- Para una UI estática y simple fue suficiente, aunque la experiencia de crear UI directamente en Win32 fue dura
- Al final, EmperorLauncher se convirtió en una herramienta que incluye ejecución en sistemas modernos, alta resolución, límite de 60 FPS, multijugador por IP directa, campaña cooperativa, instalación y aplicación de parches
1 comentarios
Opiniones en Hacker News
Este juego tiene bastante importancia dentro de todo el género de estrategia en tiempo real. Cuando se habla de estrategia en tiempo real, suele pensarse en una estructura donde los campesinos extraen recursos y uno los protege, y Dune RTS estuvo cerca de ser el molde original.
Aunque esa estructura también se dio así por la novela original. De no ser por eso, el género podría haber tomado un camino completamente distinto. Por ejemplo, se podrían haber extraído recursos de la propia base, mientras el rival presionaba molestando edificios, o la recompensa por controlar el mapa podría haber sido algo distinto al acceso a recursos.
Dune 1 también sentó algunas bases. Al principio se parece más a una aventura point-and-click, pero hacia el final se transforma en un juego con gestión de recursos, minería, producción de tropas, combate y terraformación.
Nunca terminé de entender la parte final. El objetivo parecía ser volver verde el planeta, pero cuando había vegetación ya no salía spice. Sin embargo, el emperador seguía exigiendo más entregas de spice, y si no cumplías la cuota era game over.
También es muy posible que, como lo jugué de chico, no lo haya entendido bien. Tendría que volver a revisarlo, pero con los estándares actuales quizá no haya envejecido bien, o simplemente jugarlo hasta el final tome bastante tiempo.
Edición: no sabía que Dune 1 y 2 habían salido el mismo año. Entonces el desarrollo de Dune 2 ya debía estar en marcha, o hicieron el motor y el juego en menos de un año. Hoy, incluso con indies y herramientas más rápidas, cuesta imaginarlo.
Salió en junio de 1993, unos meses después de Dune, pero si ambos juegos estaban en desarrollo al mismo tiempo, podría haber sido un caso de varios “inventores” llegando a la misma idea. Se dice que The Settlers recibió influencia de “juegos de dioses” como Populous(https://en.wikipedia.org/wiki/Populous_(video_game)), donde el jugador tiene poderes casi divinos, como modificar el terreno, pero no controla directamente las unidades.
No solo Dune; creo que la historia en general es así hasta cierto punto.
Buen artículo y excelente trabajo. Hace unos 10 años hice algo parecido, pero con Tiberian Sun, parcheando el código de red.
Meterse así en el código de otra persona da una sensación de conexión compartida. Para mi horror, descubrí que había una pila completamente separada para jugar por módem. No era simplemente enviar TCP/IP sobre el módem.
Alguien debió pasar meses escribiendo código a medida para framing, sincronización, manejo de errores y qué hacer al volver a marcar si se cortaba la conexión. Y para cuando salió el juego, ese código ya era casi inútil.
Nunca lo usé personalmente, pero muchos juegos viejos tenían esa opción.
Bien. Me llamó la atención esta parte del artículo:
“Westwood Online (WOL) ya no funciona, así que no se puede jugar multijugador salvo por LAN”.
De chico me encantaba Command & Conquer, y sé un poco sobre Westwood Online del lado del cliente.
Si no recuerdo mal, después de que WOL cayó, XWIS.net dio mucho soporte. Al autor le convendría contactar a esa pequeña comunidad de desarrolladores. Aunque quizá para estas alturas ya esté realmente desapareciendo.
Recuerdo que el trabajo de la gente de XWIS fue reconocido incluso por EA, y ayudó bastante a mantener el soporte de WOL para C&C Renegade.
También está el proyecto FreeRA, que es algo así como el ancestro directo de las reediciones recientes de C&C en Steam y otros lugares. Quizá también puedan ayudar a revivir WOL.
Como WOL se metía mediante su propia biblioteca, es muy probable que reemplazar la biblioteca sea mucho más fácil que volver a hacer ingeniería inversa de toda la pila de WOL.
Edición: seguí leyendo el artículo y dice que también arreglaron los componentes de WOL. Mejor todavía.
Excelente artículo. El autor parece una persona tan divertida e inteligente que me darían ganas de salir a tomar algo con él una noche.
Me encantaron esas explicaciones desplegables, además de que son muy útiles. Mientras leía el artículo sentí que estaba jugando una especie de RPG de aventura de elección propia, y fue una experiencia bastante novedosa.
Además, sobre la parte que dice “CS:GO recién se retiró en 2023”, yo creía que CS:GO había sido rebautizado como CS2. ¿Estoy equivocado?
Escuché que ni siquiera en PCs de torneo podían mantener una tasa de cuadros por segundo decente en CS2, aunque el mismo hardware corría CS:GO de maravilla. Hay muchos reportes de usuarios que comparten resultados similares incluso en PCs de gama alta.
Valve quería que CS2 se viera como la continuación de CS:GO, pero no lo reemplazó de forma natural haciendo un juego mejor, sino que forzó el cambio en la base de jugadores. Como CS:GO era un gran juego, yo y otras personas seguiremos algo amargados por un tiempo.
Me parece muy divertida la imagen de los juegos modernos llenos de anuncios, pay-to-win y producidos en masa quedando por debajo de los viejos clásicos.
Con que un solo hacker ayude, el público puede desplazar esa basura de juegos. En los medios persistentes, parece que las cosas buenas del pasado terminan venciendo a las mediocres del presente.
Excelente artículo y excelente esfuerzo. Quizá pueda integrarse de algún modo con nuestro trabajo en CnCNet. Estaría bueno que vinieran a CnCNet y lo charláramos.
“Tiene un módem de 28.8 BPS”.
Es matriz activa. Un millón de colores psicodélicos.
Un artículo muy interesante y profundo. Me gustó mucho la cantidad de detalles y conocimiento compartidos sobre cómo hacer ingeniería inversa y parches para este tipo de juegos abandonados.
Vi este juego en una tienda de segunda mano del barrio, pero como solo había jugado Dune II RTS, lo dejé pasar. Ahora pienso ir a buscarlo sí o sí.
Relacionado con esto, en Steam hay un juego moderno de estrategia en tiempo real de Dune.
https://store.steampowered.com/app/1605220/Dune_Spice_Wars/.
“El diseño de UI es mi pasión”.
Buenísimo. Extraño este tipo de escritura. En varios sentidos me recuerda a las publicaciones del blog de Steve Yegge.