APPLE-1 - Destripando el APPLE BASIC

Foro dedicado a la programación en todo tipo de sistemas clásicos.
dancresp
Amiga 1200
Amiga 1200
Mensajes: 1391
Registrado: 23 Dic 2008, 17:53
Sistema Favorito: MSX
primer_sistema: ZX81
Primera consola: Atari 2600
Gracias dadas: 2 veces
Gracias recibidas: 19 veces

APPLE-1 - Destripando el APPLE BASIC

Mensajepor dancresp » 29 Feb 2016, 13:09

EL EQUIPO
El Apple-1 fue uno de los primeros ordenadores personales, y el primero en combinar la placa base con una conexión para un teclado y un monitor externos, en unos tiempos en que los ordenadores se relacionaban con un panel lleno de luces e interruptores. Esto lo hizo innovador para su tiempo. Fue diseñado y construido a mano, originalmente para su uso personal, por Steve Wozniak. Su amigo Steve Jobs tuvo la idea de vender el ordenador. Fue el primer producto de Apple, presentado en abril de 1976 en el Homebrew Computer Club en Palo Alto, California.

Apple-I_Hardware.jpg
Apple-I_Hardware.jpg (63.51 KiB) Visto 1116 veces


Desde la concepción del equipo, Wozniak quería que pudiera disponer de un lenguaje de programación de alta nivel. Con la presentación por la parte de tres compañías de memorias DRAM de 4KB tuvo claro que podría usar el lenguaje BASIC. Redactó en papel el código del intérprete BASIC en lenguaje ensamblador a la espera de tener el equipo listo. Utilizaría 4 KB para almacenar el intérprete y 4 KB más para almacenar el programa en BASIC.

Para poder programar en BASIC era preciso introducir en la memoria del Apple-1 algo más de 3 KB en formato hexadecimal mediante el teclado, cosa que podía llevar entre 20 y 30 minutos, si no se cometían errores. Para hacer el equipo más práctico Wozniak diseño una pequeña placa que permitía conectar el equipo a un casete doméstico para cargar el intérprete en unos 30 segundos a una velocidad de 1200 baudios.


EL PROGRAMA MONITOR
Al conectar el Apple-I entramos en un programa monitor muy sencillo, que nos permite dar instrucciones al sistema desde la línea de comandos. Es un programa extremadamente simple que ocupa únicamente 256 bytes, alojados en la zona final de la memoria RAM (FF00-FFFF).

Su sintaxis consiste básicamente en introducir una dirección, una lista de datos o uno de los comandos disponibles. Todos los valores deben ser introducidos en formato hexadecimal, prescindiendo si se desea de los 0 de la izquierda. Se pueden introducir varios comandos en una misma línea para que se ejecuten de forma secuencial.

Apple_Monitor.gif
Apple_Monitor.gif (18.24 KiB) Visto 1116 veces


Funciones del monitor
El programa monitor dispone de un total de 4 funciones distintas:

Dirección_memoria
Al introducir la dirección y pulsar RETURN, el sistema nos mostrará el contenido de la dirección indicada. Esta dirección se guarda como la dirección activa.

.
Formato: [Dirección_Ini] . Direccion_Fin
El punto provoca un volcado de memoria desde la dirección inicial hasta la dirección final. La dirección inicial es opcional ya que por defecto coge la dirección activa. La última dirección de memoria consultada queda como la dirección activa.

:
Formato: Dirección_Ini : Valor_1 Valor_2 Valor_3 ...
Los dos puntos indican que los valores introducidos a continuación se han de ir guardando en la memoria a partir de la dirección indicada. La dirección inicial es opcional ya que por defecto coge la dirección activa. La última dirección de memoria usada queda como la dirección activa.

R
Formato: [Dirección] R
La letra R provoca que se ejecute un programa desde la dirección indicada. La dirección inicial es opcional ya que por defecto coge la dirección activa.


LA DOCUMENTACION
Tanto el manual del sistema como el del Apple BASIC son muy sencillos y disponen de muy pocas páginas.

Podemos conseguir un PDF del manual de usuario del sistema de:
- http://archive.computerhistory.org/reso ... 646518.pdf
- http://apple1.chez.com/Apple1project/Do ... Manual.pdf

Podemos conseguir un PDF del manual del Apple BASIC de:
- http://www.sbprojects.com/projects/apple1/a1basic.zip

El manual de usuario está compuesto por 15 páginas, aparentemente más pequeñas que un DIN A4, impresas a doble cara.

Apple-I_Manuales.jpg
Apple-I_Manuales.jpg (64.66 KiB) Visto 1116 veces


EL EMULADOR
Como se supone que la mayoría de nosotros no disponemos del equipo original, vamos a experimentar con él mediante el emulador "Pom1", que es gratuito, funciona muy bien y lo adjunto en el post. Simplemente hay que descomprimir el contenido del fichero RAR en una carpeta y el emulador está listo para ser usado.

El emulador funciona con la librería “SDL.dll” y no dispone de menús. Todas las acciones se deben introducir mediante combinaciones de teclas, afortunadamente son bastante intuitivas.

Descargar el emulador y el intérprete Apple BASIC:
Apple-1_Emulator.rar
(462 KiB) Descargado 39 veces


Teclas del emulador
Ctrl + L : Cargar un volcado de la memoria de un fichero en formato ASCII o binario. Podemos elegir el modo de carga.
Ctrl + S : Grabar un volcado de la memoria en un fichero en formato ASCII o binario.

Ctrl + R : Resetea el emulador en caliente. El programa monitor recupera el control pero no borra la memoria.
Ctrl + H : Resetea el emulador en frío. Se borra el contenido de la memoria.
Ctrl + Q : Salir del emulador.

Ctrl + F : Activar o desactivar el modo de pantalla completa.
Ctrl + P : Se cambia el tamaño del píxel. Hay dos tamaños disponibles.
Ctrl + N : Activa o desactiva el modo scanline. Solo funciona si el tamaño del píxel es 2.
Ctrl + C : Cambia entre los dos tipos de cursor disponibles: Bloque o Arroba.
Ctrl + B : Activar o desactivar el parpadeo del cursor.
Ctrl + A : Mostrar el “Acerca de...”.

Ctrl + T : Cambiar la velocidad del terminal. Valores comprendido entre 1 y 120. 60 por defecto.
Ctrl + E : Cambiar el tamaño de la memoria disponible. Solo dispone del modo 8 KB o 64 KB.
Ctrl + W : Permitir sobrescribir o no el contenido de la memoria ROM.
Ctrl + V : Indicar la dirección del vector de interrupción.

Los cambios que realizamos al activar o desactivar ciertas características del sistema se guardan en el fichero “pom1.cfg”. De esta forma en la siguiente ejecución del emulador ya estarán disponibles.

Usando el emulador
Al arrancar el emulador aparece la barra invertida en la parte superior izquierda de la pantalla y el cursor en la siguiente línea. El sistema ya está listo para recibir las ordenes desde el teclado.

Cargando el Apple BASIC

Con el emulador adjunto el intérprete Apple BASIC.

Para cargarlo hay que seguir los siguientes pasos:

1) Pulsar “Ctrl + L
2) Introducir “BASIC.PRG” y pulsar RETURN.
3) Pulsar “2” para indicar que el fichero es “Binary”.
4) Introducir la dirección de carga “E000” y RETURN.
5) En el monitor escribir “E000R” para ejecutarlo.

Con esta secuencia aparecerá en la pantalla “E000: 4C” y el prompt del BASIC “>” en la línea inferior.

Está listo para usar.

AppleBasic_Hello.gif
AppleBasic_Hello.gif (5.2 KiB) Visto 1116 veces


APPLE BASIC
La primera versión de Apple BASIC fue lanzada en 1976 y es muy limitada ya que solo ocupa 4KB. Está claramente basada en el Dartmouth BASIC, del que respeta muchas de sus características y limitaciones.

Pulsando “RESET” (Ctrl+H en el emulador) se sale del BASIC y se vuelve al programa monitor sin borrar el contenido de la memoria. Se puede entrar de nuevo sin perder el programa con “E2B3R” o borrando el programa con “E000R”.

Para cargar o grabar un programa en el equipo original se debe salir del BASIC y ejecutar una rutina contenida en la PROM del interface de casete.

Para cargar desde cinta:
Escribir “C100R” y pulsar RETURN.
Escribir “4A.00FFR800.FFFR”, pulsar “PLAY” en el casete y pulsar RETURN.

Para grabar en cinta
:
Escribir “C100R” y pulsar RETURN.
Escribir “4A.00FFW800.FFFW”, pulsar “REC” en el casete y pulsar RETURN.

AppleBasic_Tape.jpg
AppleBasic_Tape.jpg (62.78 KiB) Visto 1116 veces


En el emulador este proceso es bastante más simple. Pulsando “Ctrl+S” e indicando el nombre, formato del archivo y las direcciones de inicio y fin, volcaremos el contenido en un fichero.

Características del Apple BASIC
:
- Las variables numéricas solo pueden contener valores enteros comprendidos entre –32767 y +32767.
- Los nombres de las variables numéricas solo pueden tener una letra, o una letra más un dígito.
- Los nombres de las variables alfanuméricas se componen de una letra más el signo del dólar $.
- Las matrices solo pueden ser numéricas, con una única dimensión y contener un máximo de 255 elementos.
- Todas las operaciones matemáticas devuelven valores enteros.
- Permite introducir distintas sentencias en una misma línea, separándolas con el carácter de los dos puntos.
- Se pueden anidar un máximo de 8 bucles FOR o llamada GOSUB.
- No permite la edición de líneas. A tener en cuenta si se introducen sentencias muy largas.
- Puede mostrar hasta 16 mensajes de error distintos.

Comandos:
Se deben ejecutar desde el prompt del BASIC.

LIST
Lista el programa. Opcionalmente se puede indicar el rango inicial, [final].

RUN
Ejecuta el programa borrando las variables de la memoria.
El programa se puede detener pulsando cualquier tecla al finalizar la ejecución de una línea. Esto puede provocar problemas si se entra en un bucle infinito ya que no se puede detener hasta hacer un “RESET”.

CLR
Borra todas las declaraciones de variables y cancela todos los FOR y GOSUB.

DEL
Borra una línea o un rango de ellas. También se puede borrar una única línea indicando únicamente su número.

SCR
Borra el programa en BASIC y las variables definidas.

HIMEM
Indica el valor máximo de memoria disponible para programas en BASIC. Borra el programa en memoria.

LOMEM
Indica el valor mínimo de memoria disponible para programas en BASIC. Borra el programa en memoria.

Instrucciones:
Reducido número de instrucciones que pueden formar parte del programa, siguiendo un BASIC estándar.

PRINT
Imprime un texto o valor numérico en la pantalla.

TAB
Indica la posición de columna a la instrucción PRINT.

LET
Asignar un valor a una variable. Su uso es opcional.

INPUT
Introducir uno o más valores en la variable o lista de variables indicada.

DIM
Define el número de elementos de una matriz numérica de una dimensión, o el tamaño máximo de una variable alfanumérica. Una variable solo se puede dimensionar una vez.

GOTO
Salta a la línea indicada. El número puede estar indicado directamente o ser el resultado de una operación matemática.

GOSUB - RETURN
Salta a la subrutina de la línea indicada, y vuelve con un RETURN.

IF / THEN
Ejecutar una sentencia si se cumple la condición indicada. Admite el uso de AND y OR, y las condiciones clásicas.

FOR / TO / STEP - NEXT
Realizar un bucle según los parámetros indicados.

POKE
Introducir un valor en la dirección de memoria indicada.

CALL
Realizar un salto a una subrutina en código máquina.

REM
Introducir un comentario en el programa sin que afecte a su ejecución.

END
Finaliza la ejecución del programa.

Funciones:
Se les pasa un parámetro y devuelven un valor.

MOD, ABS, SGN, PEEK, RND y LEN.


ORGANIZACIÓN INTERNA DEL APPLE BASIC
Sabiendo que el intérprete BASIC se carga en un bloque de 4 KB a partir de la dirección de memoria 57344 ($F000), mis primeros pasos se han dirigido a descubrir como se guarda internamente el programa en BASIC, y sobretodo, donde.

Apple_MemoryMap.gif
Apple_MemoryMap.gif (9.35 KiB) Visto 1116 veces


A partir del mapa de memoria he podido deducir las posiciones más lógicas, que debían estar entre las direcciones 640 ($0280) y 8191 ($1FFF), si tenemos en cuenta que el sistema está configurado para tener el interface del casete conectado. En caso contrario no se si se usarían las direcciones comprendidas entre las direcciones 4096 ($1000) y 8191 ($1FFF), pero claro, deberíamos cargar el intérprete BASIC a mano...

Mediante un sencillo programa en BASIC he revisado partes de la memoria. Debido a que este BASIC solo admite números hasta el 32767, con el PEEK solo puedes acceder a la mitad de la memoria RAM. La otra mitad se debería revisar mediante el programa monitor.

Por otro lado, por limitaciones en el diseño del hardware, no hay posibilidad de acceder ni a la memoria de vídeo, ni a la memoria que contiene el juego de caracteres. 50 veces por segundo se cogen el valor almacenado en la posición de memoria $D012 y se manda a la memoria de vídeo. Según el valor se puede forzar un scroll de la pantalla por hardware.

Después de hacer una pruebas he visto que la zona de variables empieza en la dirección 2048 ($0800) y va creciendo a medida que se definen variables, y el programa en BASIC acaba en la dirección 4095 ($0FFF) y la dirección inicial se va reduciendo a medida que crece el listado en BASIC. Así, cada vez que introducimos o modificamos una línea, todo el programa se mueve a una nueva dirección, ajustando los punteros correspondientes, que no se donde están.

Creo que es la primera vez que veo este tipo de organización, ya que lo habitual es hacerlo al revés. Pero bueno, ¿quien soy yo para cuestionar a Steve Wozniak? sus motivos tendría.

Codificación de las líneas BASIC
Meter un BASIC en 4 KB no es fácil, y esto tiene un coste en el número de instrucciones disponibles, entre otras cosas.

He supuesto que internamente las instrucciones se guardan mediante un código (token) que representa a cada instrucción, así que me he puesto a identificar a que representa cada uno de estos 256 códigos. Y me he llevado algunas sorpresas.

Mediante un sencillo programa en BASIC comprendido entre las líneas 1 y 3 introduzco un valor en la posición de memoria 4094, correspondiente a la instrucción de la línea 5.

Al terminar la ejecución hago LIST y veo que aparece.

AppleBasic_ModifToken.gif
AppleBasic_ModifToken.gif (1.9 KiB) Visto 1116 veces


El proceso ha sido bastante tedioso porque lo he tenido que repetir más de 256 veces, ya que en función del código que metes, el LIST entra en un bucle infinito y se cuelga el sistema, hasta que haces un RESET con "Ctrl+H".

El resultado del proceso queda reflejado en la tabla inferior, que muestra una distribución un tanto extraña, con nombres de instrucciones, caracteres y otras cosas que no se bien bien porque están así.

Dentro de su lógica:
Entre las posiciones 4 y 17 tenemos la lista de los comandos
que podemos ejecutar desde el prompt del BASIC, pero que no se pueden incluir en una línea del programa.

Entre las posiciones 18 y 32 están los signos de las operaciones matemáticas disponibles, condiciones y operaciones lógicas. Todas ellas tiene en común que han de ir colocadas entre dos valores numéricos o variables.

En las posiciones 36 y 37 está el THEN, allá solitario...

Entre las posiciones 46 y 62 nos encontramos la lista de las funciones disponibles, que tienen en común que siempre han de pasar un valor entre paréntesis. Y aquí es donde empiezan a aparecer las "instrucciones fantasma" que según el manual no existen, y que trataré más adelante.
Entre las posiciones 77 y 107 nos encontramos el resto de las instrucciones, que a excepción de RETURN suelen llevar algún valor o expresión a continuación. Y como no, otras instrucciones fantasma.

Entre las posiciones 0 y 1, y las posiciones 128 a 159 hay una especie de “zona oscura” que cuando se hace el LIST, cuelga el intérprete. Aparentemente no hacen nada, pero seguro que no están allí por casualidad.

AppleBasic_Cuelgue.gif
AppleBasic_Cuelgue.gif (1.75 KiB) Visto 1116 veces


Entre las posiciones 160 y 223 tenemos el juego de caracteres del sistema, y curiosamente, entre las posiciones 224 y 255 se repite el bloque que hay entre las posiciones 192 y 223. Nuevamente ignoro el motivo.

Apple_TokensTable.gif
Apple_TokensTable.gif (25.48 KiB) Visto 1116 veces


Vemos que las instrucciones que admiten hasta dos valores numéricos separados por una coma, siempre tienen una coma a continuación de su token, como por ejemplo LIST, DEL, AUTO, NEXT y POKE. Pero... ¿y el THEN que hace allí?

Revisando la tabla observamos que hay instrucciones que están hasta tres veces. Por lo visto, al analizar la sintaxis de la línea antes de guardarla, en función del tipo de parámetro utiliza un token u otro, como se puede ver en el siguiente ejemplo.

Si el INPUT es de una variable alfanumérica utiliza el token 82, pero si es de una variable numérica utiliza el token 84.
El código 83 se utiliza cuando se hace un INPUT “TEXTO”,VARIABLE.

Imagen

Aprovechando el ejemplo anterior, podemos ver como se guardan las líneas en memoria:
- El byte 1 nos indica la longitud total de la línea, incluido este byte y el de final de línea.
- Los bytes 2 y 3 contiene el número de línea, con el formato: byte 2 + (byte 3 * 256).
- A partir del byte 4 hay el código BASIC, con los tokens correspondientes y el resto de caracteres.
- El último byte contiene siempre un 1, que indica al intérprete el final de la línea.

Las líneas se van interpretando una tras otra, pero cuando llega a una instrucción de salto se busca el número de línea indicado en esta zona, sumando la longitud de la siguiente línea si no coincide.

A partir de aquí, podemos engañar al intérprete falseando el tamaño de la línea, su contenido, juntar dos o más líneas o renumerarlas, entre otras muchas perrerías.

Las instrucciones fantasma
Una de las gracias de este intérprete es que contiene una serie de instrucciones que admite y que no provocan ningún error de sintaxis, pero que no hacen absolutamente nada.

Podemos localizar sus tokens en las posiciones 15 (OFF), 50 (USR), 51 (RNDX), 60 y 102 (COLOR), 103 (PLOT), 105 (HLIN) y 107 (AT).

No deja de tener su gracia que un sistema con unas capacidades gráficas limitadas a mostrar únicamente texto monocromo contenga una instrucción “COLOR”. Y no se si intentó posicionar caracteres en una posición concreta de la pantalla con AT, entre otras cosas, pero bueno, allí están.

No descarto que dejara estas cosas para no tener que modificar el fuente en ensamblador que tenía en papel, pero bueno, a ver si alguien lo sabe. Eso si, si se eliminaran estas cosas, el intérprete habría sido todavía más compacto o podría haber implementado nuevas instrucciones.

Juego de caracteres del Apple-I
Apple_CharSet.gif
Apple_CharSet.gif (14.85 KiB) Visto 1116 veces


Analizando el juego de caracteres del Apple-I vemos que tampoco existe ninguna relación entre los códigos de esta tabla y la de los tokens.

El bloque de los números comprende los códigos 48 a 57, coincidiendo con el estándar ASCII, pero en la tabla de tokens esta entre las posiciones 176 y 185. Exactamente es el código ASCII más 128.

En cambio, el bloque de las letras que en ASCII va a continuación de los números, en la tabla de caracteres está antes, entre los códigos 1 y 26. Pero en la tabla de tokens si que está a continuación, aunque repetida dos veces desde las posiciones 193 a 219, y las posiciones 225 a 250, con un valor nuevamente del código ASCII más 128.

No deja de ser curioso, aunque aparentemente la tabla de caracteres solo usa 6 bits y para según que la tabla de tokens solo utiliza 7 bits. Me queda la duda.


Las Variables
Esta versión de BASIC admite la definición de variables numéricas, matrices numéricas y variables alfanuméricas.

El nombre de las variables numéricas se componen de una letra o una letra más un dígito, y pueden contener un número entero comprendido entre –32768 y 32767. No se admiten decimales.

Las matrices numéricas tienen las mismas limitaciones y pueden tener un máximo de 255 elementos.

Los nombres de las variables alfanuméricas se componen de un letra más el signo del $ y se deben dimensionar previamente con DIM, con un tamaño máximo de 255 caracteres cada una.

La zona de variables empieza en la dirección 2048 y va creciendo a medida que se declaran nuevas variables. Ignoro donde se guarda el puntero, que se inicializa con RUN o CLR, sin borrar el contenido de la memoria.

Destacar que a efectos prácticos es un sistema muy rápido y eficiente de gestionar las variables, ya que al redimensionar las variables alfanuméricas previamente nos evitamos el problema que tienen otros BASIC de definir una tabla de nombres de variables y asociarles un puntero a la zona donde realmente está el contenido, cambiándolo cada vez que varía el valor, jun to con el consiguiente proceso de compactación cuando llegamos al final de la zona de memoria disponible.

He analizado esta parte del BASIC mediante un pequeño programa comprendido entre las líneas 5 y 8 que hacen un volcado de la memoria correspondiente, reservando las líneas 1 y sucesivas para definir las variables.

AppleBasic_Vars.gif
AppleBasic_Vars.gif (2.12 KiB) Visto 1116 veces


La información de cada variable se guarda en un bloque que ocupa un mínimo de 6 bytes, en función del tipo y tamaño. Los bloques se guardan de forma consecutiva, uno tras otro.

Todos los bloques tiene un inicio igual:
- Los bytes 1 y 2 contienen el nombre de la variable.
- El byte 3 contiene el tamaño del bloque.
- El byte 4 contiene un 8.
- El resto de bytes contienen el valor.

Ignoro el motivo por el que el nombre de la variable no se corresponde con el código del carácter correspondiente, y este código se incrementa de 2 en 2. Así, la variable A tiene el código 130 y la variable B tiene el código 132, cuando según la tabla les correspondería el 193 y el 194.


Variable numérica simple
En estos caso el bloque ocupa 6 bytes, guardando el valor en los bytes 5 y 6. Primero el byte de menor peso y después el de mayor.

Para obtener el resultado hay que multiplicar el valor del byte 6 * 255 y sumarle el valor del byte 5.

Si el nombre se compone de una única letra, el byte 2 contiene un 0, y si se compone de una letra más un dígito, el byte 2 contiene el código del dígito según la tabla de caracteres.

AppleBasic_VarA.gif
AppleBasic_VarA.gif (1.57 KiB) Visto 1116 veces

AppleBasic_VarA1.gif
AppleBasic_VarA1.gif (1.6 KiB) Visto 1116 veces


Variable matriz numérica
En estos caso el bloque ocupa 4 bytes más 2 por cada elemento de la matriz.

A partir del byte 5, cada dos bytes contienen el valor del elemento correspondiente.

AppleBasic_VarDim.gif
AppleBasic_VarDim.gif (5.31 KiB) Visto 1116 veces


Variable alfanumérica
En estos caso el bloque ocupa 5 bytes más el tamaño máximo de la cadena.

Como siempre, el contenido de la variable empieza a partir de la posición 5, y finaliza con un byte que contiene un 30.

Esto es así porque una variable alfanumérica puede tener un contenido inferior al tamaño máximo de la variable. Este byte 30 es de utilidad para la instrucción LEN.

Al ejecutar el programa se rastrea esta zona para añadir o actualizar las variables, comparando el nombre con los valores de los primeros 2 bytes de cada bloque, si no coincide se suma al puntero el tamaño de la variable contenido en el byte 3. Así hasta localizar la variable o llegar al final de la zona, que debe estar indicado por el puntero correspondiente.

AppleBasic_VarStr2.gif
AppleBasic_VarStr2.gif (2.02 KiB) Visto 1116 veces

AppleBasic_VarStr.gif
AppleBasic_VarStr.gif (2.01 KiB) Visto 1116 veces


PARA FINALIZAR
Pues nada más, que hasta aquí he llegado.

Ahora lo suyo sería desensamblar los 4 KB del intérprete BASIC y analizar como funciona y porque lo visto anteriormente está organizado de la forma como está. Por desgracia el 6502 no es mi fuerte ya que siempre se me ha dado mejor el Z80.

Si hay alguien que sabe algo que no haya explicado, o que quiera hacer alguna corrección, pues ya sabe.
Cualquier aportación será bien recibida.


Un saludo
Buscando la IP de la W.O.P.R.

Avatar de Usuario
zitror
Amiga 2500
Amiga 2500
Mensajes: 5296
Registrado: 02 Jul 2006, 00:16
Sistema Favorito: Spectrum 16Kb/48Kb
primer_sistema: Spectrum 16Kb/48Kb
Ubicación: El interior de un Z80
Gracias dadas: 175 veces
Gracias recibidas: 73 veces
Contactar:

Re: APPLE-1 - Destripando el APPLE BASIC

Mensajepor zitror » 03 Mar 2016, 23:03

Yo voy a "upear" este hilo porque lo merece, un trabajo bestial Dancresp al gran nivel que nos tienes acostumbrados =D>

Chavales, no dejéis de echarle un vistazo que merece la pena.

Salu2 ;)
(C) 1.982 Sinclair Research Ltd

La buhardilla de Zitror


Volver a “Programación”

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 3 invitados