#30 No worries, yo uso mucho python, es por seguir a las masas.
Esta noche escribo el tocho de rigor, pero os dejo con 1 gif que habla por si solo.
Me ha costado un pelín más de lo que esperaba porque he tenido que hacer algo de ingeniería inversa para controlar un paquete que en mis notas de CP1 original no estaba pero resulta que si xD. ¿Quién cojones usa el K/D reset en un pserver?
#33
Si, ahora es que tengo tiempo y estoy aprovechándome del trabajo previo.
Cuando mencioné que estaba haciendo un refactor asesino al ppio era para poder usar (en python) la estructura de paquete/handler que usaban las sources que ya manejaba allá por ¿2016? Aunque estuvieran regular ejecutadas, lo básico estaba bien. Ahora con la experiencia implemento todo más rápido. En cuanto sabes qué hace cada byte y qué se supone que debes contestar, pillas velocidad. ¿Qué me retrasa? Cuando tengo que sentarme a desarrollar algún sistema entero como nos pasará con las salas, los vehículos, los modos de juego etc.
Ejemplo básico: cuando lleguemos al "juego" tendré que ver cómo gestiono esta vez que casi todos los paquetes son el 30000 con 1 byte que te indica si el paquete es de "curar", "hacer daño" o 1138164195579 mierdas más que hay. Y no, un switch a lo Undertale no es una solución válida. Ahí invertiré unos días en los que no tendré nada que enseñar, pero una vez esté satisfecho, luego ir subpaquete a subpaquete suele ser trivial.
#34 Puedes hacer como hacian los servers (al menos en los mmos), usa archivos de configuracion por un tubo, los cargas en memoria al principio, y tirando. Asi no tienes ni que tocar código para mapear todo. (estos chinos con chorrocientos archivos .ini )
#35
Pues si y no jeje
Como ya comenté en #26 cargar en memoria una especie de configuración con ficheros del cliente tiene mucho sentido en algunos aspectos. Es más, lo de cargarlo todo en DB como hacían algunos pservers era aberrante porque de facto era como cargar una config, como bien dices.
En otros aspectos si quiero implementar la lógica y sobrescribir lo que dice el cliente. WarRock no era nada autoritativo y estoy convencido de que una vez lleguemos al "Ingame", podría simplemente reenviar el paquete 30000 tal cual y la mayoría de cosas "funcionaría". Pero esa no es la gracia: la gracia es intentar llevarte toda la lógica posible al server side jeje manteniendo toda la funcionalidad igual
La gran baza que tengo es que aunque hace 10 años no era nada bueno programando (vergüenza ajena), si era bastante decentillo como modder (saludos Activision) y el cliente de WarRock lo tengo muy visto. Eso + haber sido un viciado ayuda a predecir lo que debería ocurrir de memoria.
Warrock, el juego con el q todo cheater se aburría haciendo chorradas.
Pocos juegos he visto con tanta confianza en el cliente.
Ayer al final no hice el tocho alguno, pero es que reflexionando no pienso que fuera interesante. A fin de cuentas, con toda la info. del cliente dumpeada en el server, los procesos de compra son los que imagináis: comprobar que se puede de mil formas posibles, hacer la transacción y mandar un paquete de inventario y slots.
Lo más interesante es que WR CP1 ya implementa el primer objeto que cambia un byte en el paquete de ItemShop para señalizar que el objeto no se está comprando, sino "usando" desde la item shop. No estaba implementado en ninguna de mis sources antiguas pero con un poco de análisis me di cuenta de que seguía la estructura de paquetes ya de CP2/CP3 (anómalo en este sentido) y solo tuve que replicarlo. De todas formas todo el código está en GitHub así que cualquier interesado puede cotillear.
En cualquier caso, volviendo a este esquema:
Con la tienda ya lista, tenemos que pasar a los canales. Los canales definen qué tipo de salas de juego encontramos y además cada canal tiene su propio chat y sospecho que su propia userlist, aunque aquí ya me baila mucho la memoria...
El cambio de canal en si no es complicado, el paquete es tal que así:
El paquete es el 28673 y solo hay UN BYTE que representa el canal al que el jugador quiere ir: CQC/UO/BG. El 0 está reservado para el canal nulo.
Sin embargo, además de devolverle el paquete confirmando el cambio, hay que mandar alguna cosa más:
- La lista de salas en ese canal.
- (CREO) que la userlist realmente va aquí aunque yo la esté mandando con la autorización, que justo se manda al entrar a CQC que es el "default" del juego.
Bueno, podemos simular la lista de salas con una clase sencillita y así no tenemos que implementar todo del tirón ¿No? Seguramente sean 3-4 bytes: el id, el nombre de la sala y algún dato... Por si acaso, reviso algunas sources antiguas
// MAIN ONE
private void addRoomInfo(Entities.Room r) {
// ROOM INFORMATION //
Append(r.ID); // Room Id
Append(1); // ?
Append((byte)r.State); // Room state: 1 = Waiting | 2 = Playing
Append(r.Master); // The slot index of the room master.
Append(r.Displayname); // The name of the room that will displayed in the roomlist.
Append(r.HasPassword); // Tells the client if the room has a password.
Append(r.MaximumPlayers); // The maximum player count that is allowed in the room.
Append(r.Players.Count); // The current player count that is currently in the room.
Append(r.Map); // The current map that is selected.
Append((r.Mode == 0) ? r.Setting : 0); // CQC Rounds
Append((r.Mode > 0) ? r.Setting : 0); // FFA & TDM Kills
Append(0); // Time left?
Append((byte)r.Mode); // The current game mode of the room.
Append(4); // ?
Append(r.IsJoinable); // This is the join state of the room. 0 = Unjoinable | 1 = Joinable
Append(0); // ?
Append(r.Supermaster); // Does the room have the supermaster buf enabled? 0 = NO | 1 = YES
Append(r.Type); // Room Type, Unused in chapter 1.
Append(r.LevelLimit); // This is the value of the level limit.
Append(r.FriendlyFire); // Indicates if the room is a premium only room. 0 = NO | 1 = YES
Append(r.EnableVoteKick); // Indicates if the vote kick function is enabled. 0 = NO | 1 = YES
Append(r.AutoStart); // Indicates if the auto start function is enabled. 0 = NO | 1 = YES
Append(0); // This was a number of average ping (before patch G1-17).
Append(r.PingLimit); // This is the value of the ping limit. 1 = GREEN , 2 = YELLOW, 3 = ALL.
// CLAN BLOCK
Append(r.isClanWar); // Is clan war? -1 = NO >0 = YES
// IF ENABLED ADD 2 BLOCKS WITH BOTH OF THE CLAN IDS.
}
Pues resulta que si que voy a tener que simular algún dato más xd Bueno no pasa nada. Manos a la obra y a ver qué sale de aquí al final de la semana.
Mira no conozco el juego y no entiendo el 90% de lo que pones en cada tochal pero con el ritmo y la pasión que llevas ole tus cojones
Ostia yo era publiquero pero unos amigos estaban en el equipo español y otro creo que era el seleccionador
#39
Pero preguntad sin miedo que no muerdo jaja En resumidas cuentas lo que estoy implementando ahora mismo son los paquetes y la lógica del lobby del juego. El lobby estaba dividido en 3 canales: uno para salas de juego de CQC (en la beta coreana se llamaba modo CS descaradamente), otro para UO que son mapas medianos y el último para BG que eran mapas enormes.
Cada canal tiene su propia lista de salas y sus propios chats.
#40
Joder la SEW jajaja tengo un vago recuerdo de Masnak jugando. Otra razón para hacer el emulador ahora, que todavía recuerdo cómo funcionaba el juego.
#41 creo que eran en el juego voysobrao, spales y elcordobes
Volveré a probar cuando lo tengas
#43 esos coño jajaaja, somos todos del mismo grupo de amigos pero yo andaba al dod y no me acabaron enreando.
Si lo sacas esto se lo diré, volvemos los cordobeses, a ver si juegan algo
#44
¡Adelante! Yo tengo un grupo de WhatsApp mega viejo con algunos que jugaron hace casi 20 años jajajaj pero aviso que no garantizo nada ni tengo ninguna fecha para terminar esto. Es más, si quisiera jugar me desquitaría usando alguno de mis proyectos antiguos aunque fuera un poco caca.
BTW es posible (solo posible) que esté terminando ya el cambiar de canal resulta que fakear las salas era más sencillo de lo que recordaba. El cliente no comprueba los ID y el RoomList es visual. Por otro lado eso es bueno, quiere decir que me puedo llevar todo al server.
RPV: Café + initial D = turbo
Lo primero, dar las gracias a @raul_ct nuestro eterno reemplazo en mafia por echarme un cable haciendo los primeros tests.
Con los canales implementados, lo siguiente era el chat, que no tiene mucho misterio EXCEPTO la forma en la que se manda el mensaje. Esta tarde en un hueco que tenga me explayo, pero es una tremenda tontería que si no lo sabes hacer, aunque tengas todo bien, no mandarás mensaje alguno.
Hacer pruebas con Raúl puso de manifiesto un bug que ya no recordaba y que tiene mucho que ver con el cliente original. En 2008, WR forzaba un layout inglés del teclado al iniciar el cliente. ¿Qué pasa? Que ni mi emulador en linux ni el Windows que tiene ahora Raúl fuerzan el teclado así, por lo que podemos mandar caracteres que el juego no consideraba originalmente :laugh: Estos caracteres hay que escaparlos y como hacía el decoding nada más pasar el paquete por XOR bueno... imaginaos el percal. Nada que no se arregle con un poco de elbow grease.
Resultado final:
Con los canales, los chats (bueno, casi todos, falta el de clan y los de salas) y la tienda el lobby está "completo". La user list se actualiza correctamente en función del canal en el que estás, puedes comprar y mandar mensajes, cambiar de canal, entrar y salir... incluso cambiarte el nick al empezar a jugar. Ya no queda otra cosa más que empezar a programar las salas y de ahí, al juego propiamente dicho.
Detalle curioso de cómo funciona el chat en este juego. El paquete tal cual, tras pasar por XOR, viene a ser algo así:
bytearray(b'43001079 29696 3 0 NULL DarkRaptor\x1d>>\x1dHola\x1dmundo \n')
Por partes: como siempre el ID del paquete viene inmediatamente después de los time ticks. En este caso es el 29696 AKA chat. El siguiente byte "3" es el canal o medio por el que se manda el mensaje. 3 = Lobby (Channel). Es decir, lo reciben todos los jugadores que se encuentren en el lobby de ese canal. 0 es el receiver ID y NULL es el target name, que en este caso es nulo porque no hay target, sino que es un broadcast.
TODA la roña que viene después es, en batiburrillo, el nick del que manda el mensaje + el separador >> (mirad la foto de arriba) más el mensaje. Hasta aquí todo bien, algo incómodo pero se puede procesar fácil:
message_delim = f">>{chr(0x1D)}"
message_parts = in_message.split(message_delim)
real_message = message_parts[1].replace(chr(0x1D), chr(0x20))
real_message = real_message.strip()
Tan solo tenemos que construir un paquete de chat (cuya estructura conozco) y mandárselo a todos los destinatarios. ¿No? ¿no? JAJAJA NOPE.
Además de mandarle el nickname por separado, el paquete de vuelta espera que el mensaje esté tal que así:
def message_translator(human_readable: str):
# Define the special characters
delimiter = ">>" + chr(0x1D)
space_char = chr(0x20)
con[i]trol_char = chr(0x1D)
# Insert the delimiter at the beginning of the message
coded_message = delimiter + human_readable
# Replace spaces with the control character
coded_message = coded_message.replace(space_char, control_char)
return coded_message
O dicho de otra forma, tenemos que reconstruir la cadena "DarkRaptor>>Hola mundo" añadiendo de vuelta el ">>" y mandando en un bloque aparte el displayname. Si no lo haces, el cliente cruje y repite el último mensaje que mandaste, aunque te hayas desconectado. En fin, WarRock
Y con esto y gracias a la ayuda de @raul_ct, me doy por satisfecho con el lobby por ahora. Empezamos con las salas, donde la cosa se complica bastante. Pasamos de muchos paquetes individuales (si la cagas palma tu sesión solo) a muchos paquetes que se envían a toda la sala (hazlo mal y F sala).
En otro orden de cosas, he descubierto que el cliente en Windows 10 va como el puto culo. Las armas disparan como pistolas. Sospecho que es alguna movida de DirectX. A mi en Linux con mi wine y mi proton de rigor me va de lujo (mejor que en el Windows XP/Vista de la época) así que estoy haciendo pruebas a ver si doy con la tecla. De momento he probado a meterle dxvk y no mejora la cosa, aunque se que lo he metido bien por cómo cambia el dibujado de algunos elementos. En fin
Hoy es uno de estos días en los que me siento muy estúpido y además me sirve como recordatorio de por qué escribo este hilo.
Resulta que como comenté ayer tenía un problema con el cliente en W10
En otro orden de cosas, he descubierto que el cliente en Windows 10 va como el puto culo. Las armas disparan como pistolas. Sospecho que es alguna movida de DirectX. A mi en Linux con mi wine y mi proton de rigor me va de lujo (mejor que en el Windows XP/Vista de la época) así que estoy haciendo pruebas a ver si doy con la tecla. De momento he probado a meterle dxvk y no mejora la cosa, aunque se que lo he metido bien por cómo cambia el dibujado de algunos elementos.
Esto me traía un poco de cabeza porque la mayoría de mis amigos no saben usar Linux donde yo lo pruebo todo. El caso es que #9 con más cabeza que yo me decía: "ya lo arreglamos una vez y no era restaurando" y yo venga a comerme el coco. He perdido DOS FUCKING días inyectando Vulkan, haciendo cambios de librerías, probando mil mierdas de configuración. ¿Qué no he hecho? Llevar una puta bitácora.
Hoy, haciendo un experimento que no tiene nada que ver me dió por mirar el chat de WhatsApp del equipo /dev/ original con el que desarrollé el primer server. Estaba buscando datos para repetir un experimento con el cliente de la open beta (2006). Encuentro este mensaje MIO, de hace 6 años:
El WarRock CP1 va bien en todos lados. En Windows 10 solo hay que desactivar el virus ese llamado xbox game services
Total, que me da por mirar en el PC de mi mujer. No puede ser... Desactivo el game bar y el otro servicio (optimizador de juegos se llama xdddd). Pruebo: no pasa nada. Antes de calentarme editando el registro, me da por reiniciar y... AHORA SI JODER. AHORA SI CORRE.
Y por cosas como esta hago este hilo y escribo, aunque sobre todo lo lea yo. Porque hubo un tiempo en el que me acordaba de todo esto pero como no guardo nunca nada de nada, acabo tropezando varias veces con las mismas piedras. Lo peor es que siempre he tenido una memoria razonablemente buena, pero el tiempo supongo que no perdona.
#50 que cadena de registro? es para un amigo
edit> creo que lo he cambiado, ahora solo tengo que probarlo
Bueno, volvemos al desarrollo del servidor. Ayer implementé una clase de "Sala" muy básica con las propiedades principales que debe controlar y además implementé el paquete y handler de crear sala.
Hasta aquí es todo muy parecido a lo que he ido haciendo hasta ahora: capturar el paquete, sanear el input del jugador y enviarle el paquete de respuesta. Sin embargo ahora todo se complica un pelín, ya que hasta ahora casi todo funcionaba tal que así:
Cliente ----> Acción ---- > Servidor ------ > Respuesta ----- > Cliente
Y ahora con las salas tenemos que actualizar:
- Al jugador (el master de la sala u otro) que realiza acciones como cambiar de mapa o invitar a otro jugador a unirse.
- Al resto de jugadores de la sala que deben ver esos cambios. ¿Se cambia de mapa? Debemos actualizarlos a todos.
- Al lobby, que ve como el estado de la sala cambia.
WarRock tiene un puñado de paquetes en este sentido. Sacado de mis notas acerca de un DUMP de un capítulo posterior:
public static readonly int ROOM_LIST = 29184;
public static readonly int ROOM_INFO_CHANGE = 29200;
public static readonly int ROOM_INFO_CHANGE_EX = 29201;
public static readonly int CREATE_ROOM = 29440;
public static readonly int JOIN_ROOM = 29456;
public static readonly int QUICK_JOIN = 29472;
public static readonly int CLAN_QUICK_JOIN = 29536;
public static readonly int GUEST_JOIN = 29488;
public static readonly int EXIT_ROOM = 29504;
public static readonly int EXPEL_PLAYER = 29505;
public static readonly int INVITATION = 29520;
El capítulo 1 internacional en 2008 no usaba ni el 29536 (recuerdo que las clan wars iban de otra forma) no el 29488. Lo cual no quiere decir que no estén en el cliente y se puedan desbloquear a posteriori
El problema es que la mayoría de mis sources y servers viejos solo usan algunos de estos y otros no se si están deprecados (ROOM_INFO_CHANGE_EX por ej.) de la beta. Es decir, como suele pasar en la emulación, muchas veces llegamos a comportamientos similares con una implementación totalmente distinta.
Pasa algo parecido con la Userlist. Este es el DUMP del capítulo 2:
public static readonly int USER_LIST = 28928;
public static readonly int USER_LIST_EX = 28960;
public static readonly int USER_LIST_MODIFY = 28944;
No conozco a nadie que use USER_LIST_MODIFY y USER_LIST_EX está deprecado en este cliente casi seguro. Lo que he hecho yo por ej. es mandar siempre la user list entera en el lobby. ¿Quiere decir esto que esté mal? Pues no lo se. Igual el cliente acepta tanto un reset entero de la userlist (y yo jamás lo sabría) como un update parcial. Hay que investigarlo.
Y finalmente, durante las salas se usa ya el infame paquete 30000 cuyo comportamiento cambia en función del byte 4, así que tenemos bastante tela que cortar.
En resumen
Para hacer una implementación en condiciones de las salas, voy a necesitar:
- Escribir funciones para mandar paquetes tanto a los jugadores de la sala como a los del lobby y mantenerlos actualizados.
- Sanear el input que nos manden los usuarios con respecto a las opciones de la sala.
- Investigar algún paquete huérfano que anda por ahí para hacer una implementación lo más cercana a la original posible.
Creo que me voy a ir programando un pequeño bot para no tener que pedirle a la gente que entre a ver cosas jeje
Llevo 4 días sin actualizar esto porque tenía que darle un empujón a lo que viene a ser mi curro. Pero ahora que el cliente está contento y las deadlines bajo control, actualizo con 0 imágenes pero buenas noticias.
Ya tengo lo básico de las salas listo. Se pueden crear, destruir (lol) y los jugadores en el lobby ven la creación y destrucción de salas. Me queda implementar un último paquete como tal, el que le manda a todos los jugadores de la sala la info. de los jugadores que entran, incluidos sus IPEndpoints para el UDP.
En este punto se abren dos tareas que deben realizarse en paralelo:
a) Implementar una manera lógica de gestionar el paquete 30000. Y no, un switch no es una manera lógica por mucho que esto sea /gamedev/. Me estoy planteando hacer otro patrón de factoría dentro del que ya está montado ahora mismo para asignar a cada paquete un handler. Solo que en este caso, se le asignaría a cada subpaquete (definido por el byte 4 del paquete 30000). Por lo que he estado leyendo de viejas sources, el paquete a pesar de ser bastante grande casi siempre tiene los mismos targets. Postearé más info cuando lo tenga más desgranado.
b) El FUCKING UDP que aquí falta documentación por todos lados y lo que hacían otros p-server era una redirección lamentable. Esto va a ser divertido estudiarlo xd aunque el cliente cruje con errores de UDP así que va a ser doloroso el ensayo y error.
Este finde tengo pendientes unas pruebas, por lo que creo que empezaré por el paquete 30000, que ya controla el cambio de mapa y otras opciones del lobby de la sala, por lo que es ideal para ver si los cambios en una sala se propagan bien al lobby. Es decir, que si yo como master de la sala cambio de mapa, en el lobby deben poder verlo todos los jugadores.
#54 El otro día modifiqué dos paquetes para que el cliente de la beta funcionara y como en ese cliente se puede hacer training en cualquier mapa... estuve pillando helicópteros y motos en Pargona por los #feels.
#55 ¿Como pruebas qué paquetes UDP tienes que enviar? Usas el cliente con un servidor que funcione y inspeccionas el tráfico? o te pones a mandar cualquier cosa hasta que no pete?
#57
Es una buena pregunta. Por suerte, la simpleza del WarRock ayuda mucho en esto. Al UDP le pasa algo muy parecido al paquete 30000 (TCP): el servidor original del juego seguramente reenviaba todo as is. Lo veremos más claro cuando reimplementemos dicho paquete.
Las responsabilidades del server con respecto al UDP son 3:
- Informar (por TCP) a todos los jugadores de cuáles son los IPEndpoints del resto.
- Responder a los paquetes UDP de ping o lo que yo entiendo que es un ping xd
- Tunneling: es decir, usar al servidor como intermediario entre paquetes UDP cuando no se puede establecer una conexión directa player - player
Ahora mismo, si reenvío lo que llega por UDP as is, el juego funciona al 100%. O al menos esa fue mi experiencia hace un lustro. Lo que ocurre es que la implementación era muy básica. Ahora por ej. conozco los cabezales de todos los paquetes UDP (por un dump del cliente) y me gustaría hacer algo más sofisticado e incluso estudiar si hay algo que pudiéramos sanear, aunque sea del tunneling. Cuando llegue el momento me pondré técnico, pero puedes consultar la implementación que usamos hace un lustro mi equipo y yo aquí
Las conexiones jugador - jugador no hay nada que podamos hacer por ellas porque no pasan por mi. Pero ¿el tunneling? a eso le podemos dar una vuelta.
Algunas imágenes de hoy. Gracias a @raul_ct una vez más por ayudarme a depurar todo esto.
Entrando en salas
Saliendo de salas y adquiriendo el máster de la misma
Fails de sincronización (ya arreglados)
El código de salas es bastante complicado y aún debo darle una vuelta y organizarlo, pero 6 paquetes y sus respectivos handlers después, ya se pueden crear salas, salir de salas, adquirir el máster, destruir salas, ver el listado completo etc. Me faltan un par de paquetes tontunos: room invite y quick join pero antes ha llegado la hora de encargarnos del PAQUETE por excelencia:
DO_GAME_PROCESS = 0x7530
Que controla medio juego. Aquí me pondré algo técnico pero primero voy a leer y hacer pruebas para refrescar un poco su estructura, que no me acuerdo de nada.
Esta mañana estaba implementando todos los controles de la sala ahora que el paquete DO_GAME_PROCESS ya se puede procesar y tal. Y claro, el botón de "Start" me reclamaba, me miraba... y dije, bueno, aunque luego tenga que borrar todo, supongo que puedo probar a ensamblar a lo cutre la cadena de paquetes que hay que mandar para empezar una partida y así me lo anoto para cuando lo haga "bien".
Little did I know...
El juego se traga sin problemas respuestas al paquete 30000 aunque sean absurdas (vehículos sin vida por ej). Así que he aprovechado para hacer un intento mega cutre de implementar el STUN server y el tunneling para casos en los que los jugadores no pueden conectarse entre ellos por UDP: esto es precisamente lo que pasa cuando hosteo en mi red local y pruebo con dos ordenadores random. Funcionar funciona, pero es mega cutre xd
En fin, en realidad esto sirve de poco, pero me hace ilusión ver que técnicamente ya podemos llegar al juego en si. Ahora empieza una parte bastante bonita porque hay que implementar los modos de juego a partir de 4-5 palancas que te da el cliente (paquetes) y es bastante menos árido que estar validando mapas o el inventario.