Analizando una demostración de 128 bytes del archivo de 1997

Es muy agradable cumplir mis deseos, especialmente del pasado lejano, tan distante que ya olvidé que alguna vez lo quise. Sé poco sobre el demoscene y ciertamente nunca seguí a los autores o su trabajo, simplemente me gustó ver lo que sucedió. A veces quería resolverlo, pero luego me faltaba conocimiento y experiencia, perseverancia posterior, y luego perdí completamente el interés en esto. Pero recientemente, mi amigo, con quien estudiamos en ese momento y que nos suministró todos los productos nuevos, incluidas demostraciones, con BBS y Fidonet, porque casi todos tenían un teléfono y un módem y una computadora al mismo tiempo, visitó CAFePARTY con sus obras. eso me hizo abrir el archivo de mi primera computadora, seleccionar una demostración y resolverlo.

pentagra.com

Evaluando objetivamente mis fortalezas, tomé una introducción de 128 bytes que me gustó visualmente. El archivo pentagra.com está firmado por Mcm , 128 bytes, última modificación 24/09/1996 18:10:14, volcado hexadecimal:

000000: b0 13 cd 10 68 00 a0 07 06 1f ac ba c8 03 ee 42
000010: b1 40 ee 40 6e 6e e2 fa b8 3f 3f bb 40 01 bf 40
000020: 05 57 b1 78 ab 03 fb e2 fb 5f b1 60 88 01 aa 03
000030: fb 03 fb e2 f7 b1 61 88 01 aa 2b fb 2b fb e2 f7
000040: bf d1 99 57 b1 78 ab 2b fb e2 fb 5f b1 8f f3 ab
000050: 81 fe 00 fa 73 12 ac 0a c0 74 0d 48 88 44 fe 88
000060: 04 88 40 ff 88 84 bf fe 03 f2 42 75 e3 e4 60 3c
000070: 01 75 a5 b8 03 00 cd 10 c3 00 00 00 00 4d 63 6d

Del mismo archivo que saqué:

  • Hiew 6.11 ( 6.50 se puede encontrar en el sitio) - Lo usé como desensamblador
  • Paquete TASM : con el que recogí el código recibido para asegurarme de no estropear nada
  • ¡Ayuda TECH de Flambeaux Software! 6.0 - referencia en línea moderadamente detallada y completa para API de DOS, funciones de BIOS, hardware y ensamblador
  • Mayko G.V. Ensamblador para PC IBM : una referencia de formato de bolsillo para todos los comandos básicos Intel 8086 y las reglas de formato de texto del programa. Sin detalles arquitectónicos y con ejemplos elementales, solo las cosas más básicas. Aquí hay casi todo lo que necesita, pero no puede escribir en ensamblador aparte del entorno.
  • Por lo tanto, el segundo libro Zubkov S.V. Ensamblador. Para DOS, Windows y Unix : una guía para los rincones de hardware y DOS

Desde la implementación mínima extrema, uno debería esperar el uso de trucos y enfoques no estándar, pero aparte de algunas suposiciones en las condiciones iniciales, no vi ningún truco técnico, pero vi un truco algorítmico. Y aquí se deben decir algunas palabras sobre la experiencia. ¿Cuál podría ser la dificultad? Ya sea en la implementación o en el algoritmo. Por ejemplo, en el comando mov di, 099d1h , puede tener miedo a una constante mágica. Pero si se encuentra en el contexto de uso, queda claro que esta es la dirección de acceso en las coordenadas de pantalla X e Y, donde X = 17, Y = 123, 320 es la resolución horizontal de la pantalla en píxeles. Juntos, esto nos da 17 + 123 * 320, la conversión de coordenadas bidimensionales a unidimensionales.

Mirando ahora lo que está sucediendo en la pantalla, puedo imaginar fácilmente cómo podría implementar esto, aunque no con 128 bytes, aunque no sea 100% similar, pero podría. Y hace 20 años, no podía, aunque saqué todas las herramientas que usaba de los estantes polvorientos y no tuve que navegar por Internet para comprender cómo funciona. Por lo tanto, en primer lugar, este es un contexto, una comprensión de QUÉ está sucediendo, por lo que la cuestión de los trucos y CÓMO hacer esto está en segundo lugar.

Que vemos

  1. 5 líneas del pentagrama. Estas no son necesariamente líneas directas inextricables según todos los cánones. Solo vemos la figura general, sin detalles
  2. El efecto de la llama, que consta de dos partes importantes: una paleta seleccionada correctamente y un algoritmo para cambiar constantemente el color de los puntos en la pantalla con elementos de incertidumbre, pero manteniendo una secuencia de paleta continua para los puntos vecinos. Por ejemplo, puede calcular toda la pantalla actual promediando los valores de los píxeles vecinos de la pantalla anterior y agregar más puntos "brillantes" en lugares aleatorios, o no en lugares aleatorios, pero de valor aleatorio, o no por casualidad, simplemente aléjese del orden lineal. Una opción es cómo se hace en DOOM . El resultado debe ser en forma de colores que fluyan entre sí, desde áreas brillantes que emergen constantemente hasta desvanecimiento

Queda por entender cómo se hizo esto. Una descripción adicional no reemplazará el conocimiento sobre la arquitectura de la computadora y las funciones de DOS o ensamblador, pero tener este conocimiento le permitirá comprender y enfocarse en la esencia de lo que está sucediendo. Después de comenzar a escribir, me di cuenta de que resulta igual con suficiente detalle, pero no podía rechazarlo para no perder en el sentido de la historia.

DOS y cargando programas .COM


El programa en el archivo .com es un código limpio, sin encabezados, solo necesita colocarlo en el lugar correcto. Esto es lo que hace DOS, o más bien la llamada al sistema 4Bh. Se están llevando a cabo muchas acciones, detengámonos en el resultado:

  • Todos los segmentos registran CS, DS, ES, SS cargados con un solo valor
  • 65536 bytes están reservados para todo el programa, exactamente un segmento al que indican todos los registros de segmento. Los primeros 256 bytes están ocupados por el encabezado del sistema: PSP (Prefijo de segmento de programa). En CS: 0, el primer campo de la PSP, se encuentra el comando INT 20h, para finalizar el programa actual y transferir el control al proceso padre. El programa en sí mismo comienza con la dirección CS: 100h y ocupa los siguientes 128 bytes
  • La palabra 0000h se inserta en la pila, el registro SP es FFFEh. Esto significa que los dos últimos bytes en este segmento en la dirección SS: FFFEh se restablecen. De hecho, esta es la dirección de retorno más cercana del procedimiento, lo que nos llevará al comando de finalización en CS: 0
  • Los registros AL y AH contienen un indicador de error para determinar las letras de unidad del primer y segundo argumento cuando se llama al programa. Si no hay errores, entonces son 0, si los hay, entonces FFh

Sinceramente creí que en el caso general el estado de los registros no está definido. Pero en el código analizado, en mi opinión, se hace una suposición muy audaz sobre su estado inicial, en particular sobre los registros CX, SI y la bandera de dirección DF. No encontré confirmación de esto en la lista de fuentes que resultó arriba, así que fui a revisar las fuentes de MS-DOS 2.0 :

  • Sobre el DF, podemos suponer que el comando cld lo cld porque este último usa la dirección hacia adelante antes de transferir el control a los avances de línea, por lo tanto, el DF se reinicia. Aunque no hay un uso explícito de cld en este lugar, el comando para borrar el indicador de dirección se encuentra con bastante frecuencia antes de muchas otras transferencias
  • SI contiene 100h, porque se usa para determinar el desplazamiento que el contador de comandos IP cargará en el registro
  • CX es igual a FFh, porque se usa como un contador con un valor inicial de 80 h para transferir el contenido de toda la línea de comando y, en consecuencia, después de transferirlo es 0. Y después de eso, CL, como variable temporal, carga FFh y se usa para establecer el indicador de error de la letra de unidad en AL y AH

No hay fuentes de versiones más nuevas, pero hay fuentes de DOSBox :

 reg_ax=reg_bx=0;reg_cx=0xff; reg_dx=pspseg; reg_si=RealOff(csip); reg_di=RealOff(sssp); 

Es decir, coincide con lo que vi en el código fuente de MS-DOS (¡segunda versión!). Puede ver los valores iniciales de otros registros, aquí es una inicialización explícita y especial. Para MS-DOS, los valores de los registros que no sean AX, segmento y pila son rudimentos de su uso para otros fines; esto no es un dogma o estándar, por lo tanto, no se mencionan en ninguna parte. Pero, por otro lado, el ecosistema que se ha formado y todo el dolor de Microsoft al admitir la compatibilidad con versiones anteriores, obligando a arrastrar todos los valores generados aleatoriamente detrás de él, se está volviendo un poco comprensible, porque los programadores están tan acostumbrados a ellos.

Finalmente, para nosotros este conocimiento es suficiente, comenzamos a restaurar el programa desde los encabezados:

 .186 .model tiny .code .startup 

Determinamos el tipo de procesador 80186, porque usamos el comando outsb , que apareció solo en este modelo. Un segmento de código y un punto de entrada al programa, que, junto con la definición del modelo de memoria tiny , permitirá al compilador calcular correctamente todos los desplazamientos de variables y transiciones. Al construir tlink , se tlink el tlink /t ; en la salida, esto dará un archivo .com .

Gráficos y paleta


Para cambiar al modo gráfico, debe recurrir a la función BIOS, para lo cual se llama una interrupción de 10 h, AH = 0, en AL ponemos el identificador del modo deseado - 13 h:

 mov al, 13h ;b0 13 int 10h ;cd 10 

Tenga en cuenta que no tocamos AH, suponiendo que haya cero, de acuerdo con las condiciones de carga del programa. El modo seleccionado corresponde a una resolución gráfica de 320 por 200 píxeles con una paleta de 256 colores. Para mostrar un punto en la pantalla, debe escribir en el área de memoria, que comienza con la dirección A000h: 0, el byte correspondiente al color. Rellene registros de datos de segmento con este valor:

 push 0a000h ;68 00 a0 pop es ;07 push es ;06 pop ds ;1f 

Lógicamente, la memoria está organizada como una matriz bidimensional en la que se muestran las coordenadas de la pantalla, 0: 0 corresponde a la esquina superior izquierda. Después de cambiar el modo, se llena con ceros: negro en la paleta predeterminada. La fórmula para traducir al desplazamiento lineal es X + Y * L , donde L es la resolución horizontal, en nuestro caso 320. En esta forma, escribiré en aquellos lugares donde se usan las constantes, al traducir el texto del programa se calculan automáticamente.

Para cambiar la paleta, accedemos directamente al equipo utilizando los puertos de entrada / salida:

 lodsb ;ac mov dx, 03c8h ;ba c8 03 out dx, al ;ee 

El primer comando carga en AL el byte de datos ubicado en DS: SI. En DS, hemos cargado la dirección del segmento de la memoria de video y sabemos que está llena de ceros, en SI, en el caso general, no se sabe que al menos 0. No nos importa donde SI lo indique, casi con certeza ingresamos en la memoria de video que ocupa con esta resolución 320 * 200 = 64000 bytes, casi todo el segmento. Por lo tanto, esperamos que después de este comando AL = 0. Se agrega o resta una unidad a SI, depende de la configuración del indicador de dirección DF. Si bien esto tampoco es particularmente importante para nosotros, no importa dónde se mueva el SI, todavía permanecemos en el área de memoria de video llena de ceros.

A continuación, cargue el DX con el número de puerto 03C8h, cuya salida determina qué color de 256 vamos a anular. En nuestro caso, es 0 de AL.

El color está codificado en la paleta RGB y para esto debe escribir en el puerto 03C9h (uno más de 3C8h) tres veces seguidas, una vez para cada uno de los componentes. El brillo máximo del componente es 63, el mínimo es 0.

 inc dx ;42 mov cl, 64 ;b1 40 PALETTE: out dx, al ;ee inc ax ;40 outsb ;6e outsb ;6e loop PALETTE ;e2 fa(-6),    6   

Aumente DX en uno para que tenga el número de puerto deseado. CL es nuestro contador de ciclos de 64, y suponemos que CH = 0, como se describió anteriormente en función de las condiciones de carga inicial. Luego, enviamos el primer componente al puerto: el rojo, cuyo brillo se almacenará en AL, es lo que cambiaremos, en el primer paso 0. Después de eso, aumentamos su brillo en uno para mostrarlo en la próxima iteración. A continuación, ejecutamos dos comandos outsb escriben en el puerto, cuyo número está contenido en DX, el byte del área de memoria DS: SI, recuerde que tenemos ceros allí. SI cada vez cambia por uno.

Tan pronto como deducimos los tres componentes, se agrega automáticamente una unidad al número de color. Por lo tanto, no es necesario redefinir el color enviando al puerto 3C8h si los colores están en una fila, según sea necesario. El comando de loop reducirá CX en uno, si se obtiene un valor distinto de cero, irá al comienzo del ciclo, si es 0, luego al siguiente comando después del ciclo.

Un total de 64 repeticiones. En cada repetición, determinamos para el color, comenzando de 0 a 63, el componente rojo con brillo que coincide con el número de color actual. Restablecemos los componentes verde y azul para obtener una paleta de brillo rojo mínimo a máximo:

paleta


Líneas


Configure el color inicial y los valores de coordenadas:

 LINES: mov ax, 03f3fh ;b8 3f 3f mov bx, 0+1*320 ;bb 40 01 mov di, 64+4*320 ;bf 40 05 push di ;57 

En AL y AH cargamos el máximo color posible (más brillante) 63 (3Fh), respectivamente, AX define dos puntos a la vez. BX: resolución horizontal máxima. En el futuro, esto se usará para sumar o restar una línea de las coordenadas actuales. DI - coordina 64: 4, guárdelos en la pila.

Dibuja la primera línea desde la esquina superior izquierda hasta el extremo derecho :

 mov cl, 120 ;b1 78 LINE1: stosw ;ab add di, bx ;03 fb loop LINE1 ;e2 fb(-5) 

Configure el contador: este será el número de líneas. A continuación, guarde la palabra (dos bytes) de AX en la dirección ES: DI. Esta acción mostrará dos puntos en la pantalla con el color máximo de nuestra paleta, porque el ES está configurado para la memoria de video y las coordenadas específicas se configuran en DI. Después de esta acción, se agregarán 2 a la DI, ya que se escribieron dos bytes. Obviamente no establecemos el indicador de dirección del DF y confiamos en el hecho de que se restablece, nuevamente recordamos nuestras condiciones iniciales para cargar el programa. De lo contrario, los dos serían eliminados, lo que no permitiría dibujar la línea deseada.

A continuación, DI = DI + BX, que es equivalente a aumentar la coordenada Y en uno. Por lo tanto, en el cuerpo del ciclo, se dibujan dos puntos en una línea, la coordenada X aumenta en 2, y la coordenada Y en 1 y esta acción se repite 120 veces, la imagen con el resultado es ligeramente inferior.

La segunda línea es de arriba a la izquierda :

 pop di ;5f mov cl, 96 ;b1 60 LINE2: mov [bx+di], al ;88 01 stosb ;aa add di, bx ;03 fb add di, bx ;03 fb loop LINE2 ;e2 f7(-9) 

Restauramos las coordenadas iniciales 64: 4 y configuramos el contador en 96 repeticiones. Imprimimos un punto, pero una línea debajo de las coordenadas actuales. Como antes, esto se logra agregando un valor de BX, solo sin guardar las nuevas coordenadas. La construcción [bx+di] o [bx][di] se denomina direccionamiento base con indexación y funciona a nivel de procesador, no el traductor. El registro de segmento predeterminado con BX es DS. Después de lo cual mostramos el segundo punto, pero ya en las coordenadas actuales. DI y, por lo tanto, X aumenta en uno, ya que solo se stosb comando de transferencia de bytes: stosb . Los dos últimos comandos del cuerpo del ciclo son un aumento de Y en 2, para lo cual nuevamente usamos BX.

Después de dibujar dos líneas, se obtiene la siguiente imagen cerca de la esquina superior izquierda:

línea 1,2


Coordenadas izquierda y superior, a la derecha de la dirección de desplazamiento de línea en la memoria de video. El punto 64: 4 se dibujará dos veces.

La tercera línea es desde la esquina superior derecha hasta la superior :

 mov cl, 97 ;b1 61 LINE3: mov [bx+di], al ;88 01 stosb ;aa sub di, bx ;2b fb sub di, bx ;2b fb loop LINE3 ;e2 f7(-9) 

DI ya contiene el valor de coordenadas deseado 160: 196, necesitamos dibujar una línea desde la parte superior donde terminó la línea anterior, moviendo la pantalla hacia arriba manteniendo el mismo ángulo. En consecuencia, el ciclo es casi idéntico. CX se incrementa en 1, porque la coordenada Y actual es 2 más (más baja) que donde terminó la línea anterior, ya se calculó para la siguiente iteración. Por lo tanto, para llegar a la esquina superior, debe dar un paso adicional. El movimiento a lo largo de X continúa en la misma dirección, más uno después de cada iteración, y a lo largo de Y, en lugar de sumar, restamos los dos. Los puntos se muestran en el mismo orden, primero inferior y luego superior.

línea 3


La cuarta línea es desde el extremo izquierdo hasta la esquina superior derecha:

 mov di, 17+123*320 ;bf d1 99 push di ;57 mov cl, 120 ;b1 78 LINE4: stosw ;ab sub di, bx ;2b fb(-5) loop LINE4 

Estamos nuevamente en las coordenadas necesarias, pero esto no se usa, aparentemente para no cambiar la bandera de dirección del DF. Por lo tanto, las nuevas coordenadas se colocan en la DI y se almacenan en la pila.

Además, todo es idéntico a la primera línea, solo la coordenada Y no crece, pero disminuye, subimos.

La quinta línea es horizontal:

 pop di ;5f mov cl, 143 ;b1 8f rep stosw ;f3 ab 

Aquí todo es simple, se utiliza el mecanismo de retransmisión por microprocesador, ya que la línea horizontal corresponde a un simple aumento en la dirección de cada punto siguiente. En DI, se restaura la dirección correspondiente a la coordenada de la esquina extrema izquierda, almacenada en el paso anterior. Se establece el número de repeticiones en CX y el prefijo de repetición se aplica con el comando de transferencia de palabras.

Después de esta acción, tenemos un pentagrama completamente dibujado en el color más brillante. 80 bytes utilizados y 48 en reserva.

Magia de fuego


Establecemos las condiciones de contorno para los cálculos:

 FLAME: cmp si, 320*200 ;81 fe 00 fa jae NEXT_PIXEL ;73 12 lodsb ;ac or al,al ;0a c0 jz NEXT_PIXEL ;74 0d 

En SI habrá la coordenada del punto actual para los cálculos, si vamos más allá de los límites de la pantalla, entonces no realizamos ningún cálculo con este punto, procedemos a calcular el siguiente.

lodsb carga un byte desde el área DS: SI en AL, es decir, el color del punto en las coordenadas actuales. Si es 0, entonces tampoco hacemos nada y pasamos al siguiente punto.

Nuevo cálculo de color

Este es el algoritmo principal para cambiar los valores de color en la pantalla, esto no es una llama, esta es la base para ello. Calculamos los puntos vecinos y logramos la continuidad del color:

 dec ax ;48 mov [si-2], al ;88 44 fe mov [si], al ;88 04 mov [bx+si-1], al ;88 40 ff mov [si-1-1*320], al ;88 84 bf fe 

Reste de AX, de hecho de AL, una unidad que contiene un valor de color distinto de cero obtenido de las coordenadas actuales. A continuación, anotamos el valor obtenido en todos los puntos vecinos, en relación con la coordenada actual, es decir, un poco de ellos, según nuestra paleta.

Como después de lodsb , el valor SI aumentó en uno y ya no corresponde al punto cuyo color leemos en AL, esto tiene que ser ajustado. Tenga en cuenta que los comandos de transferencia de bytes stosb ya no se usan; en su lugar, mov se usa para señalar la dirección donde se colocará el valor. Si aceptamos que las coordenadas actuales son X: Y, para ellas SI-1, entonces:

  • mov [si-2], al - graba un nuevo color en el punto X-1: Y, a la izquierda del actual. 2 se resta de SI debido a la razón descrita anteriormente, ya que ya se le ha agregado una unidad adicional
  • mov [si], al - graba un nuevo color en el punto X + 1: Y, a la derecha del actual. SI ya tiene X + 1
  • mov [bx+si-1], al - escribiendo un nuevo color en el punto X: Y + 1, debajo del actual. Nuevamente use BX para Y + 1
  • mov [si-1-1*320], al - escribiendo un nuevo color en el punto X: Y-1, arriba del actual. No podremos usar BX, ya que necesitamos eliminar la coordenada, la arquitectura del procesador no nos permite hacerlo de esta forma, por lo tanto, se usa una constante de acuerdo con la fórmula de reducción de coordenadas

El registro de segmento es DS, que se usa por defecto con SI y BX.

En ninguna parte se verifica la situación cuando el punto toca el borde de la pantalla. Esto no puede conducir a una falla, ya que siempre estaremos dentro de los límites del segmento de video. Un punto vecino puede caer en un área no reportada con direcciones superiores a 64,000 o en una línea adyacente, lo que no nos hace daño e incluso ayuda un poco, como se verá en la descripción adicional.

La misma magia, el cálculo de las coordenadas del siguiente punto.

 NEXT_PIXEL: add si, dx ;03 f2 inc dx ;42 jnz FLAME ;75 e3(-29) 

Volvamos un poco, no establecimos específicamente el valor inicial de SI en ningún lado, y en DX todavía tenemos el número del puerto de entrada de salida que usamos para la paleta. Realizamos solo tres acciones simples SI = SI + DX, obviamente esto establecerá nuevas coordenadas, ¿cuáles? DX = DX + 1 y si DX no es igual a 0, entonces volviendo al algoritmo básico para obtener y calcular puntos vecinos, es decir, ¿DX es algún tipo de contador?

Sabemos que debemos recorrer todos los puntos y calcular los cambios de brillo de sus vecinos. Si hace esto en una fila, probablemente obtendremos un gradiente estático, tal vez no del todo uniforme, pero sin cambios alrededor de nuestras líneas.Conocemos el tamaño de nuestra pantalla y cuántos puntos debemos sortear, pero aquí casi lo descuidamos, más precisamente, elegimos el valor de cierre 65536 en lugar del 64000 exacto. DX es realmente un contador, solo 65536. Pero por qué su valor inicial no es importante y por qué tomamos ¿Es el valor final mayor que el total de puntos en la pantalla?

Porque vamos por puntos no seguidos y no todos. Cada coordenada lineal posterior es mayor que la anterior por el valor de DX. Es decir, en SI la suma de los elementos DX de una progresión aritmética simple: 0,1,2,3,4,5,6, ..., 362,363, ..., 65535. Esto ya nos da no linealidad, si comienzas con SI = 0 y DX = 0, entonces en SI obtenemos: 0,1,3,4,6,10,15,21, ..., 65341,65703, ..., 2147450880.

Pero eso no es todo, ya que la dimensión SI es de 16 bits, no podemos obtener un valor mayor que 65535, se produce un desbordamiento y el resto en SI sigue siendo el módulo 65536. La fórmula de cálculo de coordenadas lineales toma la forma SI = (SI + DX) MOD 65536, que rompe completamente el orden continuo: 0,1,3,4,6,10,15,21, ..., 65341,167,530,894, ...

Ahora recordamos que SI no se inicializa de ninguna manera, es decir, la próxima vez que volvamos a este ciclo entonces comenzaremos desde la coordenada donde la dejamos, y no desde 0 o alguna dada. Esto agregará caos a nuestra secuencia: alargue la cantidad de elementos que no se repiten. De lo contrario, el recorrido de los puntos siempre sería el mismo, aunque no lineal. Un efecto de llama estaría presente, pero no tan claramente. Si hablamos del truco, entonces esto es todo.DX, siempre, excepto el primer uso, comienza implícitamente en 0 como resultado de un desbordamiento inc dx.

Y nuestros valores límite agregan un poco más de caos, ya que para SI> = 64000 no se dibujarán puntos y la secuencia de salida está ligeramente confundida. Y omitir todos los puntos con un valor cero conduce al efecto de ignición en los primeros segundos del programa. Esto se debe a que el ciclo completo termina más rápido, ya que la mayoría de los puntos no se procesan. Pero lo más importante, debido a que el brillo para la mayoría de los puntos solo aumentará, no pueden ser oscurecidos por las secciones de atenuación vecinas: simplemente no existen todavía y no se calculan valores cero. Después de que desaparezcan las áreas completamente negras, se establece el equilibrio, algunas áreas aumentarán el brillo y otras disminuirán.

Como resultado, ya no podemos hablar de ningún orden o gradiente, los puntos no se distribuyen, cada vez en una nueva secuencia, incluida la repetición de varias veces o saltar por completo. Esto conduce a la formación de regiones de diferente brillo mezcladas entre sí, que cambian en cada nueva iteración.

Pero esto no es todo, si no agrega nuevos puntos brillantes, al final todos serán reembolsados. Por lo tanto, después de que el DX alcanza su valor máximo, volvemos a dibujar cinco líneas brillantes una y otra vez contando todos los puntos en la pantalla:

 in al, 60h ;e4 60 cmp al, 01h ;3c 01 jne LINES ;75 a5(-91) 

Pero antes de eso, leemos desde el puerto 60h, este es el teclado, el código de escaneo de la última tecla presionada. Para ESC es igual a 1. Si es así, se presionó la tecla ESC, nos movemos hacia la salida.

Finalización


Vale la pena prestar atención al actualizar la pantalla actual, que lleva un tiempo, no puede salir del programa, es decir, la reacción al ESC se retrasará. Si durante la espera y después de ESC se presiona alguna tecla, seguiremos en el programa, solo se puede leer el último código de escaneo desde el puerto. Una cosa más, no reemplazamos ni usamos las funciones del sistema DOS y BIOS para esto, independientemente de lo que leamos del puerto, la tecla presionada se coloca en un búfer circular y probablemente el siguiente programa la leerá desde allí una vez que se complete nuestra introducción, el archivo más probable gerente o command.com. Esto conducirá a su procesamiento, por ejemplo, Volkov Commander en ESC ocultará sus paneles.

Queda por volver al modo de texto 3:

 mov ax, 03h ;b8 03 00 int 10h ;cd 10 

Se supone que estábamos en este modo antes del lanzamiento del programa, pero en el caso general este puede no ser el caso. Aquí actualizamos todo el AX, porque sabemos con certeza que AH no contiene 0.

Ahora puede salir:

 retn ;c3 

Este es un comando cercano a la salida de un procedimiento que toma el valor de la palabra colocada allí (dos bytes) de la pila y lo carga en el contador de comandos IP. Según las condiciones iniciales, tenemos ceros en la pila, esto nos llevará a la dirección CS: 0, donde, como sabemos, se encuentra el código de comando int 20h: apagado.

Y 7 bytes para derechos de autor:

 dd 0h ;00 00 00 00 db 'Mcm' ;4d 63 6d end 

Podemos decir que todavía hay un lugar que gastaría en una inicialización más rigurosa, pero como todo funciona en DOSBox moderno, el autor probablemente hizo todo bien.

Veamos una vez más:

  1. ,
  2. 4 , : X+1 Y+2, X+2 Y+1. , . ,
  3. SI=(SI+DX) MOD 65536, DX , , , SI. 1. 65536 , , . , — add si, dx inc dx , ,
  4. ESC ,

.
 .186 .model tiny .code .startup mov al, 13h int 10h push 0a000h pop es push es pop ds lodsb mov dx, 03c8h out dx, al inc dx mov cl, 040h PALETTE: out dx, al inc ax outsb outsb loop PALETTE LINES: mov ax, 03f3fh mov bx, 0+1*320 mov di, 64+4*320 push di mov cl, 120 LINE1: stosw add di, bx loop LINE1 pop di mov cl, 96 LINE2: mov [bx+di], al stosb add di, bx add di, bx loop LINE2 mov cl, 97 LINE3: mov [bx+di], al stosb sub di, bx sub di, bx loop LINE3 mov di, 17+123*320 push di mov cl, 120 LINE4: stosw sub di, bx loop LINE4 pop di mov cl, 143 rep stosw FLAME: cmp si, 320*200 jae NEXT_PIXEL lodsb or al,al jz NEXT_PIXEL dec ax mov [si-2], al mov [si], al mov [bx+si-1], al mov [si-1-1*320], al NEXT_PIXEL: add si, dx inc dx jnz FLAME in al, 60h cmp al, 01h jne LINES mov ax, 03h int 10h retn dd 0h db 'Mcm' end 

Para compilar, debe hacer: tasm pentagra.asmy tlink /t pentagra.obj.

No sé si quedó claro qué y cómo se implementó, pero me parece que se utilizó un enfoque hermoso e inusual para crear el efecto de llama. Aunque no tengo nada con lo que comparar, tal vez todos lo hicieron, y ahora tú puedes hacer lo mismo.

Source: https://habr.com/ru/post/482826/


All Articles