1 puntos por GN⁺ 2024-07-15 | 1 comentarios | Compartir por WhatsApp
  • El RTS de 2001 Emperor: Battle for Dune tení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 sobre Game.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 de WOLAPI.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 Dune es un juego de estrategia en tiempo real creado en 2001 por Westwood Studios, y es la secuela de Dune 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.exe del juego no es el ejecutable real, sino un wrapper delgado que lanza Game.exe
  • Si se ejecuta Game.exe directamente no pasa nada, así que hubo que analizar el proceso de inicialización que hacía Emperor.exe y 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.exe crea un mutex y un handle de file mapping anónimo, luego procesa datos leídos desde Emperor.dat y 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
  • En vez de reimplementar directamente el código de descifrado, se puso una herramienta de volcado en lugar de Game.exe para 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 LoadLibrary y el búfer con la ruta del DLL a CreateRemoteThread para cargar el DLL dentro del proceso objetivo
    • Cuando se ejecuta DllMain del DLL, corre el código del parche
  • 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 VirtualProtect y llamar a FlushInstructionCache despué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 de SC_MESSAGE_YOUR_DETAILS, se confirmó con un volcado de Wireshark que el comando GAMEOPT se estaba enviando por error a todos los jugadores

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::EndScene
    • EndScene se 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 EndScene no se exporta directamente, primero se hookean en secuencia DirectDrawCreateEx e IDirect3D7::CreateDevice para 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.net habí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 PAGE en lugar de PRIVMSG
    • La sincronización de configuración de partida usa mensajes GAMEINFO con 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
    • .cab es 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.EXE con 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
  • EM109EN.EXE extrae un DLL embebido a un archivo temporal, lo carga y luego ejecuta la función RTPatch32@12 dentro del DLL
    • RTPatch era 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.DLL es una biblioteca de clases COM, y Emperor crea los objetos COM dentro del DLL mediante CoCreateClass
  • 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 OaEnablePerUserTLibRegistration para manejarlo como registro por usuario
    • No se encontró una forma de registrar la class library solo para un proceso
    • Los intentos de usar directamente DllGetClassObject no 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

 
GN⁺ 2024-07-15
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.

    • Para ser precisos, este artículo no trata sobre Dune 2, sino sobre Emperor: Battle for Dune.
    • Como señaló otra persona, este probablemente es la secuela de Dune 2, que es el que muchos tienen en mente.
      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.
    • Siempre pensé que The Settlers(https://en.wikipedia.org/wiki/The_Settlers_(1993_video_game)) fue el primer juego de estrategia en tiempo real.
      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.
    • Dawn of War es un buen ejemplo de estrategia en tiempo real que se aparta del paradigma típico de extraer minerales.
    • Quizá al final igual se habría ido en esa dirección. La historia se apoya bastante en el arquetipo de “los campesinos como base de recursos, los gobernantes como estrategas”.
      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.

    • Creo que eso habrá sido por otro motivo. El objetivo probablemente no era conectarse a internet por dial-up, sino llamar directamente a otro módem.
      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?

    • Para algunos, CS2 se considera un downgrade frente a CS:GO. Yo también lo veo así.
      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.
    • CS2 se publicó con el mismo ID de aplicación que CS:GO, pero es un juego completamente rehecho con un motor nuevo. No es un rebranding.
    • A diferencia del antiguo CS:GO basado en Source 1, CS2 usa el motor Source 2 con renderizador DX11 o Vulkan.
  • 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.

    • La arquitectura RISC lo va a cambiar todo.
  • 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í.

    • Me pregunto si correrá en Wine.
  • 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/.

    • Llamarlo estrategia en tiempo real es un poco forzado. Está más cerca de un juego 4X, tiene un ritmo más lento y el combate tampoco es tan importante.
    • Más allá de que otros digan que Spice Wars es 4X, lo más cercano a una estrategia en tiempo real moderna de Dune fue Homeworld: Deserts of Kharak.
  • “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.