Copy Link
Add to Bookmark
Report

SET 031 0x08

  

-[ 0x08 ]--------------------------------------------------------------------
-[ Java en moviles ]---------------------------------------------------------
-[ by FCA00000 ]-----------------------------------------------------SET-31--

Java en móviles
***************

Conté hace unos cuantos artículos cómo opera la máquina virtual Java y cómo
funciona el compilador .
También hubo un apartado para explicar la manera de modificar una clase
Java sin necesidad de tener acceso al código fuente original.

De nuevo voy a contar algo parecido, pero siguiendo en mi línea de artículos
para móviles, ahora mostraré un poco cómo funciona Java en un teléfono móvil.
Para los que entendieron el artículo anterior, éste posiblemente no les
proporcione nueva información, pero nunca se sabe.
También quiero recomendar el genial artículo escrito en la e-zine 29A sobre
virus en Java. Proporciona una visión muy detallada del funcionamiento interno
con algunas ideas muy buenas.

Según la ley española, hacer ésto sin consentimiento del autor puede ser
un delito. Es más, la simple posesión de éste artículo es un delito.
Si no quieres cometer una ilegalidad, no sigas leyendo y destruye este
fichero.

Hace bastante tiempo que existen en el mercado una gran oferta de dispositivos
pequeños con capacidad de ejecutar programas compilados en Java.
Esto incluye móviles, PDAs, reproductores de MP3, ...
Como ya era tiempo de que yo me adaptara a las tecnologías mas recientes, he
adquirido un teléfono Siemens (cómo no) modelo M65.
Las características técnicas son similares a las de otros modelos de la misma
gama: pantalla de 132x176 pixels, 16 Bits de colores, sonido polifónico MIDI-1,
vibración, teclas y cursor, cámara, infrarrojos, MMS, puerto serie, ...
Internamente tiene un procesador ARM de 32 bits, 16 Mg de flash, 11 Mg de RAM,
stack Java de 1.5 Mg, y soporta MIDP-2.0 y CLDC-1.1 con soporte de WML y HTML
sobre WAP2.0 y GPRS Clase 10. Permite video h263 .
Esto lo convierte en un móvil bastante completo y muy capaz de ejecutar
aplicaciones Java con un gran conjunto de funcionalidades.

Dado que los móviles están restringidos en tamaño, memoria, potencia, y
velocidad, sólo usan un conjunto reducido de librerías Java especifícas para
dispositivos pequeños, llamado J2ME - Java2 Mobile Edition.
Los sistemas soportados por el M65 son:
MIDP 2.0
CLDC 1.1
JSR 120 WMA 1.0
JSR 135 MMA 1.0
JSR 179 Location API
JSR 185 JTWI 1.0

El sistema base es MIDP 2.0 con el cual se pueden hacer cosas como cargar
dibujos desde ficheros, dibujar sprites en la pantalla, pintar decorados,
generar mapas hechos con iconos, crear formularios HTML con los elementos
típicos (textos, botones, listas, botones para elegir, diálogos, fechas,
tipos de letras, ...) además de generar sonidos, conectarse con la red, mandar
y recibir SMS, iniciar llamadas y otras muchas cosas más.
En fin, que es bastante extenso.
Se ha puesto especial atención al terreno de los juegos, haciendo que vayan a
una velocidad más que aceptable.
Pero no quiero contar ahora cómo hacer juegos. Primero hay que aprender a
modificar juegos ajenos.

Lo primero es conseguir algún juego. En mi móvil vienen instalados 4 que están
incluidos en el precio, así que tengo la correspondiente licencia para usarlos.

También es posible meter nuevos juegos mandando mensajes a unos servidores, y,
previo abono, aparecen en tu móvil.
Otra opción es buscar páginas de Internet que los tengan. Algunos juegos son
gratuitos, y otros ofrecen una versión de demostración. También existen otros
de pago, claro.
Finalmente, existen páginas web con juegos piratas, pero descargarlos de estas
páginas es ilegal y supone un delito. Así que haznos un favor a todos y paga
por el software que uses. Es la única manera de seguir adelante con la
industria y el desarrollo.
Existen por ahí colecciones extensas de más de 500 juegos.

Puesto que el M65 tiene puerto de infrarrojos, puedo transferir los programas
desde mi PC hasta el móvil todas las veces que deseo. Otra opción es usar el
cable serie o USB.

Todo lo que necesito está disponible para Windows. Algunas aplicaciones tienen
versiones para otros entornos, pero lamentablemente no todas.

La primera herramienta que necesito es un descompilador de Java.
Yo uso "jad" hecho por Pavel Kouznetsov.
A veces uso el GUI llamado "DJ Java Decompiler" hecho por Atanas Neshkov.

Algo fundamental es un compilador de Java. Yo uso el JSDK 1.4.1 de Sun con
las extensiones de J2ME.
Existen también muy buenos entornos gráficos para usar el compilador, pero
para este artículo no son necesarios.

La manera oficial de probar si las modificaciones funcionan son, por supuesto,
meter el programa modificado en el móvil. Sin embargo Siemens pone a
disposición de todos los desarrolladores una herramienta llamada
SMTK - Siemens Mobile Tool Kit entre las cuales incluye un emulador de móvil.
Existen para muchas versiones de móviles, por supuesto hay una para M65.
El funcionamiento es sencillo: copia el programa modificado en un directorio
del disco duro, inicia el emulador, y carga dicho programa. La emulación no
es 100% perfecta, y el emulador falla demasiado para mi gusto.
Pero siempre queda la opción de usar el móvil real.

Para los móviles Nokia existe el NDS-Nokia Developer’s Suite for J2ME.
Si quieres probar que el programa original funciona perfectamente en todos los
móviles, deberías considerar esta opción.
En general esto es muy útil para los creadores de programas.

Otra utilidad es un editor hexadecimal.
Y un programa que sirve para sacar diferencias entre un archivo y otro, tanto
en modo binario como en texto. Yo uso WinDiff.

********************

Voy con el primer ejercicio.
El programa se llama Megablaster y es uno de estos arcades en los que naves
espaciales aparecen la parte alta de la pantalla y tu nave situada en la parte
baja debe destruirlas o esquivarlas.
No es que sea una idea innovadora, pero el programa está muy bien realizado y
es fácil de jugar.
Está realizado por la empresa italiana Microforum Games. Un programador, un
dibujante, algunos testers, y poco más.

Lo primero es meterlo en el móvil: si usas Windows con infrarrojos, inicia la
aplicacion IrFTP, pon el móvil cerca del PC, elige el archivo del disco duro,
y envíalo al móvil. Entonces aparecerá una carpeta en la parte inferior
derecha, y pulsando el botón de dicho menú, copia la aplicación al directorio
que quieras dentro del móvil, No es posible jugar si no mueves antes el fichero.

Cuando empiezo a jugar voy pasando niveles y me van matando naves.
Me doy cuenta de que de vez en cuando obtengo una vida extra. Empiezo con 3, y
como soy muy malo jugando, las pierdo rápidamente.
Así que voy a modificarlo para no perder vidas nunca.

Tengo que buscar una variable que se inicialice a 3, que se incremente algunas
veces, que se decremente otras, y que se compruebe en algun momento que vale 0.
El programa está en un fichero llamado Megablaster.jar de 84 Kb.
Lo desempaqueto con la aplicacion jar que se instala con el JSDK, o también
con el WinRAR.
El paquete completo se compone de:
-unos cuantos ficheros de texto en el directorio raíz, con las intrucciones
en varios idiomas.
-un directorio "icons" con sólo un fichero navetta.png que puedo ver
incluso con Internet Explorer
-un directorio "sound" con un fichero de música hotrod.mid . Muy animada.
-otro directorio "pics\acc" con todos los gráficos
-un fichero en META-INF\MANIFEST.MF
-otro directorio "olympics" con varios ficheros *.class

El fichero META-INF\MANIFEST.MF es un texto en el que se indica la versión
mínima de APIs que necesita el programa. En este caso es MIDP-1.0 y CLDC-1.0 ,
con lo que mi móvil lo soporta prefectamente.
También se indica cual es la clase que contiene la función que inicia el juego.

Pero el meollo del asunto está en 2 clases: olympics\MainPRG.class y
olympics\b.class

Dado que las clases Java se compilan, y luego es la máquina virtual la que
ejecuta el programa, es sencillo desensamblar un programa Java para obtener
algo a medio camino entre el código original y un código binario.
Para hacer la tarea de los "rompedores de programas" mas difícil, es común que
antes de sacar el juego al público, el programa se pasa por otra aplicación
llamada enrevesador (obfuscator) que simplemente hace el código mas díficil
de entender.
Entre las técnicas de "ofuscación" hay una consistente en sustituir los
nombres de las funciones por otros consistentes en letras simples.
Así, la función
PintaNave() se sustituye por a()
Como además Java permite que las funciones tengan el mismo nombre, con tal de
que tengan distintos parametros:
SumaBonus(x) se sustituye por a(x)

En programas para móviles, este paso de enrevesamiento es obligado. Al reducir
los nombres de las funciones y las clases se ahorran algunos bytes que no
afectan al funcionamiento del programa pero hacen que ocupe menos espacio.
Incluso se ejecutan más rápido.
Hay otro paso necesario y es la pre-verificación de las clases.
La maquina virtual Java de los móviles debe ser pequeña así que el
verificador de clases es mínimo, dejando esta tarea en manos del programador,
el compilador, y otra herramienta llamada pre-verificador.
Pero esto no afecta a la descompilación de programas.

Lo primero que hago es desensamblar los programas con la utilidad jad .
Una clase de 25 Kb se convierte en un código fuente de 45 Kb con 2.500 lineas.
Me pongo a buscar cuándo algo se iguala o se compara con el valor 3.
En la clase MainPRG :
a) funcion _mthif(int i1, int j1) ----> if(i1 == 3)
b) funcion _mthnew() ----------> l = 3;
c) funcion _mthnew() ----------> if((_fldtry == 5) & (i <= 3))
d) funcion a(String s1, int i1) ----> if(l1 < 3) l1 = 3;
e) _mthif(int i1) -----------> if(E[i1]._fldgoto == 3)
f) _mthif(int i1) --------> if(E[i1].a == 3)
g) _mthfor(int i1) -------> E[i1]._fldgoto = 3;
h) _mthlong(int i1) ----> if(i1 == 3) I.a(4, "/pics/acc/fondo_03a");
i) _mthnull(int i1) ----> _fldlong[i1]._fldfor += 3 + n;

Voy a analizarlos uno por uno.

En la rutina a) el valor de i1 se compara con 1, 2, 3, 4, y 5. Como no creo
que haya una lógica distinta dependiendo del numero de vidas, supongo que
ésta no es la rutina que me interesa.
En b) hay muchas cosas que comento más tarde.
En c) veo que tanto las comparaciones anteriores y posteriores involucran
números como 220, 765 , ...
En d) querría decir: si el numero de vidas ya es 3, no añadas vidas extra.
Pero eso no ocurre, así que esta comparación no tiene nada que ver.
En e) f) y h) creo que no tiene sentido mirar si el número de vidas es
exactamente igual a 3.
En g) parece mas prometedor, pero esta función se llama con argumentos 0 y
100, que no parece que tengan nada que ver.
De todos modos la dejo en reserva por si acaso.
En b) veo que usa la variable l.
Esta variable se usa en:
i) _mthnew() para hacer if(w > G) { l = l + 1; G = G + 50000; }
ii) _mthgoto(int i1) para hacer if(l == 0) k=0;
iii) _mthdo(int i1) para hacer l = l - 1;

Por cierto, que hay otra funcion con distinto numero de argumentos:
_mthdo()
{
if(k == 0)
I.a(1, I.Q, I.B, "GAME OVER", 0);
}

O sea, que la variable k dice cuándo se acaba la partida.
Recordar que esta variable se ha puesto en _mthgoto(int i1)

Vamos, yo creo que la variable l contiene el número de vidas.
En b) haré que ponga l = 4;
y veré si empiezo con 4 vidas.

Desensamblando con
jad -a MainPRG.class

obtengo
k = 1;
// 37 69:aload_0
// 38 70:iconst_1
// 39 71:putfield #32 <Field int k>
l = 3; <-***********--******--
// 40 74:aload_0
// 41 75:iconst_3 <-***********--******--
// 42 76:putfield #35 <Field int l>
d = 1;

Asi que intento cambiar
41 75:iconst_3
por
41 75:iconst_4

Para ello lo desensamblo con java -d
y veo que en la posición 1FB7 tengo que cambiar el byte
0x06 , que significa iconst_3
por
0x07 , que significa iconst_4

Para verificarlo, desensamblo de nuevo y veo que efectivamente queda
l = 4;

Ahora llega el momento de comprobarlo. Meto el
MainPRG.class
dentro del Megablaster.jar con WinRAR, y lo transfiero al móvil.
Inicio el juego, y veo que ahora tengo 4 vidas al empezar.
Bien, así que mis pesquisas eran correctas.

Una vida extra es una ayuda, pero no muy grande.
Podría sustituir la instrucción anterior por
iconst_100
para tener 100 vidas, pero hay un problema:
la instrucción iconst_4 se codifica como un único byte: 07
Pero si quiero cargar un valor mayor de 6 debo usar la instrucción
iconst_NN
que ocupa 3 bytes. Como no puedo hacer hueco dentro del código, lo mejor es
modificar el programa para no perder ninguna vida.

Existe otra rutina que usa la variable l en MainPRG.class
void _mthdo(int i1)
{
l = l - 1;
// 0 0:aload_0
// 1 1:aload_0
// 2 2:getfield #35 <Field int l>
// 3 5:iconst_1
// 4 6:isub
// 5 7:putfield #35 <Field int l>
E[i1].a = 6;
// 6 10:aload_0
// 7 11:getfield #13 <Field a[] E>
// 8 14:iload_1
// 9 15:aaload
// 10 16:bipush 6
// 11 18:putfield #135 <Field int a.a>
// 12 21:return
}

Este es el único sitio donde se decrementa la variable l , seguramente
cuando un misil enemigo alcanza a la nave.
Así que pretendo cambiar
l = l - 1;
por
l = l - 0;

Es decir, cambiar
// 3 5:iconst_1
por
// 3 5:iconst_0
Vuelvo a desensamblar con java -d porque esta aplicación me dice los
códigos binarios, y veo que en la posición 0x1F41 hay que cambiar el byte
0x04 que significa iconst_1
por
0x03 que significa iconst_0
Vuelvo a meterlo en el Megablaster.jar , inicio el juego, dejo que me
maten, y veo con satisfacción que el número de vidas nunca decrece.
Ahora ya puedo jugar sin preocuparme.
Realmente el juego pierde todo el aliciente, pero mi objetivo era aprender
a hacer la modificación, no el juego en sí.

*************
Voy con otro caso.
Otro de los juegos que vienen con el móvil se llama Turrican2004 y es uno de
estos de scroll horizontal y vertical en los que un soldado va recorriendo
escenarios matando extraterrestres, con plataformas.
Los gráficos son muy buenos y es entretenido. No he recorrido muchos niveles
porque es muy complicado para mí. Pero ya digo que soy muy mal jugador.

Lo que me fastidia de este juego es que antes de empezar muestra durante 3
segundos una pantalla de la compañía que hizo el juego, y luego otra con el
logotipo de Siemens. Despues se para otros 2 segundos en una pantalla con
las instrucciones.
Y sólo después de esto carga el juego, lo cual lleva otros 5 segundos.
Mi objetivo es eliminar esas molestas pantallas de publicidad.
El programa Turrican2004.jar ocupa 328 Kb.
Al descomprimirlo genera 140 ficheros en
Turrican\*
La mayoría tienen extensión *.ptrx y *.ptr_optb , que no sé lo que contienen.
En todo caso parecen ficheros binarios.
Hay 70 ficheros con extensión *.png que contienen los gráficos del programa.
Uno de los ficheros se llama
splashSIEMENS.png
que contiene la imagen de una de esas pantallas de publicidad. No ha sido
difícil encontrarlo, ?eh?

Mi primera tentativa, siguiendo la ley del mínimo esfuerzo, es borrar el
fichero o simplemente cambiarle en nombre. Esto cambirá el tamaño del fichero
empaquetado Turrican2004.jar pero en los programas para moviles el tamaño
no se verifica. Bueno, se verifica si hay un fichero con el mismo nombre y
extensión .jad
Dado que no existe Turrican2004.jad estoy seguro que que puedo cambiar el
tamaño sin mayor preocupación.
Lamentablemente con este cambio lo único que obtengo es una excepción tan grande
como un caballo que impide que el juego inicie correctamente.

También hay 25 ficheros *.class en
Turrican2004\com\thq\Turrican\
que desensamblo rápidamente con
jad -a Turrican2004\com\thq\Turrican\*.class

y busco cual de ellos contiene la palabra "splashSIEMENS" :

Clase s.class
public void b()
{
if(v)
return;
v = true;
if(z)
return;
switch(s)
{
default:
break;

case 0: // '\0'
try
{
v.bA = Image.createImage("/splashTHQ.png");
v.ap = Image.createImage("/splashSIEMENS.png");
}
catch(Exception exception) { }
break;
y muchas líneas más

Esto significa que en la clase v.class hay un objeto llamado ap :
public static Image ap;
Ojo, no confundirlo con
public static int aP = -1;
Como ya he comentado antes, normalmente existen otros objetos con el mismo
nombre en otras clases:
a.class -> int ap;
d.class -> int ap;
o.class -> int ap;

Desensamblo v.class para ver dónde se usa y me encuentro con que ap
simplemente está declarada en esta clase, pero no se usa.
O sea, que únicamente carga la imagen en la memoria; no la muestra.

Esto quiere decir que se usa desde otras clases.
Así que busco dónde se usa v.ap
Obviamente sale s.class pero también q.class

Clase q.class
public void a(Graphics g1, int i1)
{
if(v.o == 0L)
v.o = System.currentTimeMillis();
if(System.currentTimeMillis() - v.o <= (long)g) goto _L2; else goto _L1
_L1:
L = true;
if(p.a != null)
{
g1.setColor(0);
g1.fillRect(0, 0, 132, 176);
g1.drawImage(p.bC, 66, 0, 17);
g1.drawImage(p.bs, 66, 0, 17);
g1.drawImage(p.a, 0, 57, 20);
} else
{
g1.setColor(0);
g1.fillRect(0, 0, 132, 176);
}
g1.setColor(255, 255, 255);
g1.fillRoundRect(16, 160, i1, 8, 8, 8);
g1.setColor(75, 75, 175);
g1.drawRoundRect(16, 160, 100, 8, 8, 8);
goto _L3
_L2:
if(System.currentTimeMillis() - v.o <= T ||
System.currentTimeMillis() - v.o >= g) goto _L5; else goto _L4
_L4:
..............
_L12:
Thread.yield();
Thread.sleep(25L);
goto _L3
_L10:
if(System.currentTimeMillis()-v.o > ae && System.currentTimeMillis()-v.o < W)
{
g1.setColor(255, 255, 255);
g1.fillRect(0, 0, 132, 176);
if(v.ap != null)
g1.drawImage(v.ap, 66, 88 - (v.ap.getHeight() >> 1), 17); <-******--******-
} else
if(System.currentTimeMillis() - v.o < (long)ae)
{
g1.setColor(255, 255, 255);
g1.fillRect(0, 0, 132, 176);
if(v.bA != null)
g1.drawImage(v.bA, 66, 88 - (v.bA.getHeight() >> 1), 17); <-******--******-
}
goto _L3
Exception exception;
exception;
_L3:
}


Lo que se extrae de esto es que se dibuja la imagen v.ap o v.bA en
función de la diferencia de tiempos entre System.currentTimeMillis() y v.o ,
que es el tiempo calculado inicialmente cuando se entra en esta rutina.

Estos tiempos se comparan con:
T (linea _L2, a mediados de línea)
g (linea _L2, antes de saltar a _L5) y justo antes de L1
ae (linea _L10, a mediados de línea)
W (linea _L10, al final de la línea)

Veo que podría poner al entrar en la rutina:
goto _L3
o también
return

Pero quizás esta rutina hace algo más. En particular veo que usa p.a!=null, y
pone L=true así que creo que lo mejor es tomar otro camino.

Las variables T, g, ae, W están usadas en esta misma clase en el constructor q()
public q()
{
super(false);
N = false;
Z = true;
J = false;
l = false;
ae = 4000;
W = 8000;
T = 13000;
g = 18000;
s = 19000;
..........

Muy bien: todas las variables están juntitas.
Los valores, escritos es hexadecimal, son:
ae = 0x0FA0;
W = 0x1F40;
T = 0x32C8;
g = 0x4650;
s = 0x4A38;
En la clase q.class busco esos bytes y los encuentro a partir de 0x16CD

0x16CD: 11 0F A0
0x16D4: 11 1F 40
0x16DB: 11 32 C8
0x16E2: 11 46 50
0x16E9: 11 4A 38
Notar que en Java el byte más significativo va delante.
Lo más sencillo es cambiarlos todos a valores mas pequeños, por
ejemplo 258 = 0x0102
Parece un golpe a ciegas, pero estas cosas funcionan así, Si funciona a la
primera, ?para qué voy a perder el tiempo?

Como antes, descompilo de nuevo la clase modificada para ver que los valores
son correctos.
Meto la clase en el Turrican.jar , lo transfiero al móvil, empiezo a jugar y
compruebo que las pantallas de presentación ya no aparecen.
Por supuesto que sigue tardando un poco porque tiene que cargar todas las
clases del juego y los gráficos, pero ahora es mucho más rápido.

*************
En este mismo juego hay una modalidad para jugar una campaña: se empieza
por un escenario simple, y a medida que adquieres más experiencia puedes
probar con otros escenarios.
Pero desde el comienzo no es posible acceder a los escenarios avanzados.
En el menú de selección sólo estan disponibles 4 niveles, mientras que hay
otros 20 que dice que estan bloqueados-LOCKED
Busco la palabra "LOCK" y la encuentro en la clase TurricanMidlet.jad :
public void startApp()
{
if(c == null)
{
v.m = getAppProperty("MIDlet-Version");
v.l = System.getProperty("microedition.locale");
v.bI = getAppProperty("language_override");
System.out.println("READ IN: " + v.bI);
if(v.m == null)
v.m = "V?.??";
v.bK = getAppProperty("CHEAT_UNLOCK") != null;
c = this;
b = new q();
b.setFullScreenMode(true);
f = Display.getDisplay(this);
f.setCurrent(b);
e = new Thread(b);
e.start();
b.a();
}

Esto es un típico inicio de juego. Toma algunas variables del entorno, por
ejemplo para saber el idioma.

Otra de las propiedades se llama "CHEAT_UNLOCK" y debería estar en uno de
los ficheros dentro del Turrican2004.jar, posiblemente en META-INF\MANIFEST.MF
que contiene la variable "MIDlet-Version"
O quizas en Turrican2004.jad , el cual contiene otras variables tales
como "language_override"

Es decir, que los propios programadores han instalado una puerta trasera
que parece hacer algún tipo de trampa, quizás incluso
para poder acceder a los otros niveles sin necesidad de haber pasado los
anteriores. ?Y porqué no puedo usar yo la misma puerta?

La línea
v.bK = getAppProperty("CHEAT_UNLOCK") != null;
quiere decir: si la propiedad "CHEAT_UNLOCK" está definida, haz v.bK=true
Si no, haz v.bK=false
Para seguir con el método anterior, voy a ver dónde se usa la variable v.bK :

La encuentro en q.class
public void run()
{
n = 0;
M.n();
if(v.bK)
M.a(true);
.....
}

O sea, que llamará al metodo a del objeto M , pero sólo si v.bK=true
Como yo no voy a crear la propiedad "CHEAT_UNLOCK" , la solución es cambiar
if(v.bK)
por
if(v.bK==false)
que es lo mismo que
if(!v.bK)

Desensamblando:
if(v.bK)
//* 4 10:getstatic #69 <Field boolean v.bK>
//* 5 13:ifeq 23
M.a(true);
// 6 16:getstatic #18 <Field p M>
// 7 19:iconst_1
// 8 20:invokevirtual #70 <Method void p.a(boolean)>

la instrucción
5 13:ifeq (significa "si es igual")
la voy a sustituir por
5 13:ifne (significa "si NO es igual")

o sea, en la posición 0x17E0 pongo 0x9A en vez de 0x99

Transfiero el juego, accedo al menú, y tengo todos los escenarios liberados !
Claro que los más avanzados son demasiado difíciles para mí. Al menos he
cumplido el objetivo de modificarlo, que era lo que quería probar.

*************
Otro juego que tengo es AdamsAppleV3 hecho por la compañia SoftexDigital.
Es un juego en el que hay que mover plataformas hasta encontrar la salida.
Incluye 5 niveles pero hay que pagar para acceder a los niveles superiores.
El pago se realiza accediendo a una página web desde el móvil. Bueno, es el
propio teléfono el que se conecta así que no hay que hacer mucho.
Con mi operador no es posible realizar estos pagos, así que sale un mensaje
al principio indicando que sólo puedo jugar al modo básico: 'Teaser' Level.
Por supuesto que como mi red no lo soporta, no puedo bajarme nuevos niveles.
Pero me gustaría probar los 5 escenarios ya incluídos, no sólo el primero.
Al menos, me gustaría eliminar ese mensaje.

El fichero AdamsAppleV3.jar ocupa 62Kb y al descomprimirlo genera, entre
otros, 6 ficheros *.class en el directorio raiz.
El mensaje que se queja de que el operador no soporta el pago está en el
fichero AdamsAppleV3.class

public void startApp()
{
try
{
if(q != null)
{
if(p)
{
n = true;
i = q;
a("Payment", "Payment not supported by the operator. You can Play
only 'Teaser' Level ", (byte)3);
p = false;
} else
{
a.setCurrent(q);
}
q.m = true;
}
}

Así que comprueba si p tiene un valor o no.
Si tiene algún valor, se queja. O sea, que habría que hacer que p sea false.
Pero no encuentro la definición de la variable p en esta clase.

En la cabecera veo que
public class AdamsAppleV3 extends b
Ah, esto quiere decir que puede estar en b.class
Efectivamente
public b()
{
.......
try
{
D.checkOperator();
}
catch(SecurityException securityexception)
{
p = true;
}
}
Muy bien: si hay algún fallo de permisos, entonces p se pone a true.
Lo más sencillo sería saber cuales permisos son los que faltan.
Si los programadores hubieran hecho un simple
System.out.println("Error=" + exception);
entonces yo sería capaz de saber si el problema es que la red no está
disponible, o que no tengo correcto el perfil de WAP, o que ha expirado mi
sesión. Lamentablemente no puedo modificar el programa lo suficiente como
para incluir un simple mensaje explicatorio.

Pero es fácil engañar al programa para que crea que puedo realizar pagos:
Tengo que cambiar
p = true;
por
p = false;

Desensamblando:
{
D.checkOperator();
// 242 548:aload_0
// 243 549:getfield #10 <Field Payment D>
// 244 552:invokevirtual #70 <Method void Payment.checkOperator()>
}
//* 245 555:goto 564
catch(SecurityException securityexception)
//* 246 558:astore_1
{
p = true;
// 247 559:aload_0
// 248 560:iconst_1 <-***********--******--
// 249 561:putfield #72 <Field boolean p>
}

esto es, cambiar
248 560:iconst_1
por
248 560:iconst_0
o sea, en la posición 0x1510 pongo 0x03 en vez de 0x04

Esta es una modificación que no he podido comprobar en el emulador, ya que
éste es siempre incapaz de comunicar con la red. La excepción no es
"SecurityException" sino "NotImplemented" , la cual no es interceptada
por el comando catch.

Pero en el propio móvil funciona perfectamente y ya no se queja.

*************
En este mismo juego hay otra modificación que me gustaría hacer: el juego
consiste en mover bloques hasta que la protagonista (Eva) llega a la salida
y se encuentra con Adán.
No es típicamente un laberinto, sino que se trata más bien de mover los
bloques sabiamente.
Como no sé la manera de colocarlos para conseguir el objetivo, decido que
es mejor manipular el programa para que no aparezcan bloques.
El mapa es de 14 columnas y 10 filas, según veo en la pantalla del móvil.
Dado que no hay ficheros binarios que contengan los mapas, supongo que están
dentro de las propias clases.
Tras mirarlas todas, descubro que en d.class hay una estructura:
public static final byte h[][][] = {
{
{10,10,10,10,10,10,10,10,10,10,10,10,10,10 },
{ 2, 0, 0, 0, 4, 4, 1, 1, 0, 0, 0, 0, 0, 0 },
{ 1, 1, 3, 3, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, 0 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
}
repetida de modo parecido 5 veces (los cuales podrían ser los niveles)

Nota: los valores están en decimal.

Bueno; no tiene 10 filas sino 8. Quizás la fila de abajo y la de arriba son
siempre iguales.
En la pantalla del juego hay 2 trofeos (corazones) en la segunda línea.
Comparándolo con esta matriz, deduzco que se corresponden con el valor 4.
Análogamente el valor 5, que sólo aparece una vez, debe de ser un pequeño árbol.
Y el valor 6, que está en la fila 7, columna 13, debe de ser el dibujo de Adán
que está al otro lado del muro (valor 1)
El valor 2 que aparece en la fila 2, columna 1 debe de ser el dibujo de Eva.
Para hacer que el juego sea realmente sencillo de terminar, simplemente
destruyo el muro que me impide el paso en la fila 7, columna 12, justo
antes del dato '6'.

Al llegar la hora de modificar el fichero d.class me llevo la sorpresa
de que los valores no están así seguidos: 0x0A, 0x0A, 0x0A, 0x0A ... como
correspondería a una secuencia de bytes, sino que cada uno ocupa 5 bytes:
0x59 0x03 0x10 0X0A 0x54
0x59 0x04 0x10 0X0A 0x54
0x59 0x05 0x10 0X0A 0x54
0x59 0x06 0x10 0X0A 0x54
Por eso supongo que Java guarda los elementos como "objetos byte", y a cada
instancia le asigna un número único.
Una pérdida de espacio, desde mi humilde opinión.
No sólo eso, sino que algunos números ocupan menos bytes. El descompilador
hace todo el trabajo de organizarlos y yo no tengo que preocuparme.
Sólo anotar cuidadosamente cuáles bytes deseo cambiar.

Para cambiarlo, sustituir en la posición 0x43A7 el valor 0x04, que
significa "muro" , por 0x07 , que significa "corazón"

Como siempre: modifico la clase, la meto en AdamsAppleV3.jar , lo transfiero
al móvil, y lo pruebo.
Ahora el primer nivel es un simple paseo.

****************
Bueno, como ha quedado comprobado, modificar los programas Java para móviles
es posible con un poco de tiempo y conocimientos simples de aprender.
Si es legal, moral, o engorda, no es tarea mía juzgarlo.

*EOF*

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT