Aunque soy bastante novato en la materia e incluso en el foro, pero dado que se me ha comentado un par de veces que lo hiciera y estoy siempre a favor de compartir conocimiento, voy a intentar exponer en forma de pequeño tutorial como desproteger Nonamed.
Es posible que alguna de las cosas que haga no lo sean de la mejor forma posible, desde luego seguro que las hay más fáciles. Mi objetivo era intentar hacer la desprotección haciendo uso solo del Amstrad, intentando evitar usar emuladores. Hace 30 años no había emuladores y ya se desprotegían juegos… ¿cómo lo hacían?
Si os parece, expongo cómo lo hice y luego si hace falta con aportaciones de otros lo vamos perfilando y/o mejorando.
En cualquier caso, espero que sirva de ayuda o primeros pasos a aquellos que, como yo hace unos días, tienen curiosidad por desproteger un juego y no saben por dónde empezar.
¿Qué necesitamos?
- Un Amstrad CPC.
- Un casette con el Nonamed o bien un CDT y alguna manera de reproducirlo en el CPC.
- Un casette virgen o disco donde guardar la versión desprotegida.
- Un desensamblador
- Un copión o visor de cabeceras
- Un mínimo de conocimientos de la máquina, de basic y ensamblador.
Como desensamblador podemos utilizar el incluído en el DEVPAC (MONA3), efectivo pero farragoso. En este paso podemos utilizar el emulador, va, pero porque sabemos que el proceso sería exactamente el mismo que usando el hardware real, solo que muchísimo más cómodo.
Por último podemos usar un copión o visor de cabeceras para obtener cierta información sobre los primeros bloques del juego.
Empezamos
Lo primero es analizar qué tenemos en la cinta original. Para ello lo mejor es cargar el juego y ver qué va pasando.
Si lo hacemos veremos que el juego está compuesto por lo siguiente:
- Un bloque BASIC, salvado como protegido. Si hacemos un load no veremos su código al hacer list.
- Otro bloque binario o código máquina, que es el que tiene el código de carga turbo.
- Una carga turbo, correspondiente a la pantalla del juego.
- Una carga turbo, correspondiente al juego.
Vamos a ponernos manos a la obra.
Direcciones
El procedimiento que vamos a seguir para poder volcar nuestro juego de forma desprotegida es el siguiente:
- Averiguar en qué parte de la memoria está cargado, es decir, dirección de carga y longitud.
- Averiguar cual es la dirección de ejecución.
Eso siempre va a ser así, si volcamos a cinta o disco los 16KB de memoria que empiezan en &C000 habremos almacenado una captura de pantalla. Es importante por lo tanto hacerlo cuando tengamos en pantalla aquello que queramos guardar, la pantalla de carga del juego.
Hay que ser cuidadosos y no escribir nada en pantalla antes de almacenarla, puesto que lo que guardaríamos sería lo que se ve justo en el momento de hacer el volcado.
Si bien para la pantalla tenemos claro que siempre estará ahí, en el caso del juego no sabemos ni donde se almacena exactamente, ni lo que ocupa, ni su dirección de ejecución. Aquí es donde debemos empezar a investigar.
El bloque BASIC
Puestos a empezar, hagámoslo por el principio.
Como he comentado el primer bloque del juego está programado en Basic, pero si lo cargamos con un load”” y hacemos list no veremos su código, puesto que está protegido.
Se me ocurrió que la manera más sencilla de ver que contenía este fichero era copiarlo con un copión que me lo devolviera de forma desprotegida.
Aquí cada uno puede utilizar el que mejor le venga, en mi caso utilicé el Bonzo Meddler, que me permite pasar a disco el fichero desprotegido a la vez que me muestra información en pantalla.
Una vez cargado el fichero desprotegido vemos que su contenido es:
Código: Seleccionar todo
10 INK 0,0:BORDER 0:MODE 0:OPENOUT “c”:MEMORY 1999:CLOSEOUT
20 LOAD”!c
30 CALL 2000
Lo que sí es importante es que nos da la dirección de ejecución del cargador, aunque siendo sinceros, esta información la podríamos haber obtenido también a través del Bonzo o cualquier otro visor de cabeceras y ahorrarnos este paso.
El bloque binario
Como hemos visto en la última captura, este bloque está escrito en código máquina. Necesitaremos por lo tanto un desensamblador para echarle un vistazo.
Ya que tenemos cargado el fichero nonamed.bas borramos la última linea (30 y enter) y modificamos la 10 para que no cambie el modo de pantalla ni la ponga en negro.
Código: Seleccionar todo
10 OPENOUT “c”:MEMORY 1999:CLOSEOUT
20 LOAD”!c
Ahora podemos utilizar un desensamblador o un emulador, pero lo que tenemos que conseguir en cualquier caso es poder ver el código máquina que hay cargado a partir de la posición 2000 de memoria ó &07D0 en hexadecimal.
Aquí empieza la fiesta. Más que pretender entender linea por linea lo que hace el código, debemos utilizar un poco de intuición.
Lo que sabemos por el comportamiento que hemos observado mientras hemos realizado la primera carga del juego, es que este cargador lo que hace es cargar la pantalla del juego, luego carga el juego y lo ejecuta.
Si vamos siguiendo el código ensamblador pronto llegaremos a unas lineas de este estilo:
Código: Seleccionar todo
LD DE,#C000
LD BC,#4000
LDIR
Como ya he dicho, yo no soy un experto, pero esos datos coinciden con lo que hemos comentado antes sobre la dirección de la memoria de pantalla y su longitud. Por lo menos hay algo que nos resulta familiar en el código.
Sabemos que lo siguiente que hace el cargador tras mostrar la pantalla de carga es cargar el juego en sí.
Las siguientes lineas de código tras la carga de la pantalla son:
Código: Seleccionar todo
LD IX,#1E3C
LD DE, #8811
Algunos datos útiles:
&1E3C = 7740
&8811 = 34833
7740 + 34833 = 42573 = &A64D
&8811 en decimal son unos 34KB, bien podría ser la longitud del juego…. Dejamos por lo tanto estos valores como candidatos.
Ahora debemos ir en busca de la dirección de ejecución del juego.
Lo más probable es que para ejecutar el código del juego el cargador “salte” a él con una instrucción JP, por lo que buscamos esto entre las direcciones 2000 y 2320, es decir, entre la &07D0 y &0910.
Si os fijais en la dirección de memoria &0832, unas pocas lineas más abajo de por donde íbamos, tenemos la instrucción JP #9FC6 que además está dentro del rango &1E3C – &A64D que haría válidos los candidatos que hemos localizado antes.
No estamos seguros del todo, pero… podríamos probar a ver si tenemos suerte.
Desprotector
Bueno, pues ya tenemos toda la información que necesitamos para desproteger el juego, aunque aún no estamos seguro de si los valores son los correctos.
La mejor forma de averiguarlo es probándolo.
Para hacer el volcado desprotegido del juego deberíamos hacer algo como:
- Cargar el cargador normal del juego y ejecutarlo
- Dejar que cargue la pantalla del juego
- Dejar que cargue el código del juego
- Impedir que se ejecute el juego y retomar el control
- Salvar la pantalla del juego
- Salvar el código del juego
- Obtener el valor de las tintas
De momento vamos a empezar por el final, por la obtención de las tintas.
Hay información sobre la pantalla que no tendremos a priori y que tendremos que averiguar. Básicamente es el modo de pantalla y las tintas.
El modo es sencillo de averiguar a simple vista, aunque además ya lo sabemos por el código del bloque basic, es modo 0.
Necesitamos una manera de obtener por lo tanto el valor de las 16 tintas asociadas a este modo si queremos que la pantalla de carga aparezca con los colores correctos.
Obtener el valor de las tintas usando un emulador es trivial. Simplemente miramos sus valores tras haber cargado la imagen y las apuntamos, pero como ya he comentado aquí queremos hacerlo sin ayuda de emuladores.
Existe una función de BIOS que permite obtener el valor de una tinta en concreto, es la &BC35, pero no podemos llamarla desde BASIC, bueno sí, pero no recuperar el valor devuelto.
Esta función, al ser llamada deja en los registros BC los valores de las tintas primaria y secundaria para la tinta indicada en el registro A.
Haremos un pequeñísimo programa en ensamblador que recupere el valor de una tinta dada y la deje en una posición de memoria conocida:
Código: Seleccionar todo
LD A, 0
CALL #BC35
LD (#A660), BC
RET
Si ensamblamos el código máquina equivalente queda:
Código: Seleccionar todo
3E 00
CD 35 BC
ED 43 60 A6
C9
El resto de puntos de la lista es bastante sencillo, salvo lo de impedir que se ejecute el juego.
Sabemos que llegados a un punto, tras haber cargado el código del juego, el cargador de código máquina hace un JP a una dirección de memoria, lo cual hace que se ejecute el código del juego.
¿Qué pasa si sustituimos esa instrucción JP por un RET? Pues que volveremos al BASIC y tendremos el juego cargado en memoria y el control del ordenador para hacer lo que queramos, que será volcar el juego en cinta o disco.
¿Cómo podemos cambiar ese JP por un RET? Pues haciendo un poke del valor de código máquina del RET en la posición de memoria donde está el JP, que si recordais es la &0832.
Dicho y hecho, con un POKE &0832, &C9 lo tenemos. Este cambio debemos hacerlo después de haber cargado en memoria el cargador en código máquina, pero antes de ejecutarlo.
Bien, pues juntando todo lo que hemos comentado, nuestro programa desprotector quedaría de la siguiente forma.
Aclarar que tal como está, está pensado para volcar el juego en disco. En cinta sería prácticamente igual, pero habría que dejar hueco para nuestro cargador antes.
Bueno, creo que se entenderá.
Código: Seleccionar todo
10 |TAPE.IN:|DISC.OUT
20 MODE 0:BORDER 0:INK 0,0
30 OPENOUT"c":MEMORY 1999:CLOSEOUT
40 LOAD"!c"
50 POKE &832,&C9
60 CALL 2000
70 SAVE"!NONAMED.SCR",b,&C000,&4000
80 SAVE"!NONAMED.BIN",b,&1E3C,&8811,&9FC6
90 FOR n=&A650 TO &A659:READ a$:POKE n,VAL("&"+a$):NEXT n
100 FOR t=0 TO 15
110 POKE &A651,t
130 CALL &A650
140 PRINT PEEK(&A660)
150 NEXT t
200 DATA 3E,00,CD,35,BC,ED,43,60,A6,C9
El bucle que empieza en la linea 100 modifica el valor de &A651 en cada iteración, ya que es donde se almacena el número de la tinta para la cual queremos recuperar su valor.
Una vez tenemos el listado, por si acaso lo podemos guardar, por ejemplo como “unprotec.bas”.
En cualquier caso, rebobinamos la cinta, hacemos run del programa y damos al play.
Tras cargar el juego, éste se volcará en disco como NONAMED.BIN y su pantalla de carga como NONAMED.SCR. También nos mostrará el valor de las 16 tintas de la pantalla de carga. Las apuntamos.
Ahora solo nos falta hacer un pequeño programa en BASIC para cargarlos y ejecutarlos.
Por cierto, como hemos tenido precaución de poner el código de obtención de tintas fuera del rango de memoria ocupado por el juego, haciendo un call &9FC6 podemos echar unas partidillas y comprobar que el juego está bien cargado.
Cargador
El código del cargador es bastante sencillo. Ponemos el modo de pantalla correcto, así como las tintas, cargamos la pantalla, cargamos el juego y lo ejecutamos.
Quedaría más o menos así:
Código: Seleccionar todo
10 MODE 0:BORDER 0
20 INK 0,0:INK 1,16:INK 2,26:INK 3,12:INK 4,24:INK 5,4:INK 6,8:INK 7,15:INK 8,20:INK 9,1:INK 10,2:INK 11,11:INK 12,25:INK 13,17:INK 14,3:INK 15,6
30 OPENOUT”C”:MEMORY &1E3B:CLOSEOUT
40 LOAD”!NONAMED.SCR”,&C000
50 LOAD”!NONAMED.BIN”,&1E3C
60 CALL &9FC6
He dejado el listado del cargador así para que quede más claro, pero una forma más compacta y elegante de hacer lo mismo podría ser:
Código: Seleccionar todo
10 MODE 0:BORDER 0
20 FOR n=0 TO 15:READ a:INK n,a:NEXT n
30 OPENOUT”C”:MEMORY &1E3B:CLOSEOUT
40 LOAD”!NONAMED.SCR”
50 RUN”!NONAMED.BIN”
60 DATA 0,16,26,12,24,4,8,15,20,1,2,11,25,17,3,6
Cualquier comentario, aclaración o mejora será bienvenida. La idea es que al final tengamos algo claro y útil.
Saludos.