Initial commit
This commit is contained in:
commit
dc5f530210
19 files changed
+1936
No files matched your search
+41
@@ -0,0 +1,41 @@
|
||||
import javax.microedition.midlet.*;
|
||||
import javax.microedition.lcdui.*;
|
||||
|
||||
public class Hello extends MIDlet {
|
||||
private Display display;
|
||||
private MyCanvas canvas;
|
||||
|
||||
public void startApp() {
|
||||
canvas = new MyCanvas();
|
||||
display = Display.getDisplay(this);
|
||||
display.setCurrent(canvas);
|
||||
System.out.println("Hello J2ME!");
|
||||
}
|
||||
|
||||
public void pauseApp() {}
|
||||
public void destroyApp(boolean unconditional) {}
|
||||
}
|
||||
|
||||
class MyCanvas extends Canvas {
|
||||
protected void paint(Graphics g) {
|
||||
// Clear screen (black background)
|
||||
g.setColor(0, 0, 0);
|
||||
g.fillRect(0, 0, getWidth(), getHeight());
|
||||
|
||||
// Draw a red rectangle
|
||||
g.setColor(255, 0, 0);
|
||||
g.fillRect(20, 20, 80, 40);
|
||||
|
||||
// Draw a green line
|
||||
g.setColor(0, 255, 0);
|
||||
g.drawLine(0, 0, getWidth(), getHeight());
|
||||
|
||||
// Draw white text
|
||||
g.setColor(255, 255, 255);
|
||||
g.drawString("Hello J2ME!", 10, 80, Graphics.TOP | Graphics.LEFT);
|
||||
|
||||
// Draw a blue circle (approximated with drawArc)
|
||||
g.setColor(0, 0, 255);
|
||||
g.drawArc(100, 100, 50, 50, 0, 360);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -g -std=c99 -ffreestanding
|
||||
|
||||
J2ME_JARS = lib/cldcapi10.jar:lib/midpapi20.jar
|
||||
|
||||
.PHONY: all clean run_parser run_exec
|
||||
|
||||
all: test_parser exec_code
|
||||
|
||||
test_parser: test_parser.c classfile.c classfile.h
|
||||
$(CC) $(CFLAGS) -o test_parser test_parser.c classfile.c
|
||||
|
||||
exec_code: exec_code.c classfile.c classfile.h helpers.h helpers.c
|
||||
$(CC) $(CFLAGS) -o exec_code exec_code.c classfile.c helpers.c -lSDL2 -lSDL2_ttf -lm
|
||||
|
||||
Hello.class: Hello.java
|
||||
javac -classpath $(J2ME_JARS) Hello.java
|
||||
|
||||
run_parser: test_parser Hello.class
|
||||
./test_parser Hello.class
|
||||
|
||||
run_exec: exec_code Hello.class
|
||||
export LIBGL_ALWAYS_SOFTWARE=1 && ./exec_code Hello.class
|
||||
|
||||
clean:
|
||||
rm -f test_parser exec_code *.class
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "classfile.h"
|
||||
|
||||
static uint8_t read_u1(FILE *f) {
|
||||
return (uint8_t)fgetc(f);
|
||||
}
|
||||
static uint16_t read_u2(FILE *f) {
|
||||
uint16_t a = read_u1(f), b = read_u1(f);
|
||||
return (a << 8) | b;
|
||||
}
|
||||
static uint32_t read_u4(FILE *f) {
|
||||
uint32_t a = read_u2(f), b = read_u2(f);
|
||||
return (a << 16) | b;
|
||||
}
|
||||
static uint64_t read_u8(FILE *f) {
|
||||
uint64_t a = read_u4(f), b = read_u4(f);
|
||||
return (a << 32) | b;
|
||||
}
|
||||
|
||||
static void skip_attr(FILE *f) {
|
||||
uint32_t len = read_u4(f);
|
||||
fseek(f, len, SEEK_CUR);
|
||||
}
|
||||
|
||||
static void parse_code_attr(FILE *f, CodeAttr *code) {
|
||||
read_u4(f); /* attribute_length (ignored) */
|
||||
code->max_stack = read_u2(f);
|
||||
code->max_locals = read_u2(f);
|
||||
code->code_len = read_u4(f);
|
||||
code->code = malloc(code->code_len);
|
||||
fread(code->code, 1, code->code_len, f);
|
||||
|
||||
code->exception_table_len = read_u2(f);
|
||||
code->exception_table = NULL;
|
||||
if (code->exception_table_len > 0) {
|
||||
code->exception_table = malloc(
|
||||
code->exception_table_len * sizeof(ExceptionHandler));
|
||||
for (int i = 0; i < code->exception_table_len; i++) {
|
||||
code->exception_table[i].start_pc = read_u2(f);
|
||||
code->exception_table[i].end_pc = read_u2(f);
|
||||
code->exception_table[i].handler_pc = read_u2(f);
|
||||
code->exception_table[i].catch_type = read_u2(f);
|
||||
}
|
||||
}
|
||||
|
||||
/* skip Code sub-attributes (LineNumberTable, LocalVariableTable …) */
|
||||
uint16_t attr_count = read_u2(f);
|
||||
for (int i = 0; i < attr_count; i++) {
|
||||
read_u2(f); /* attribute_name_index */
|
||||
skip_attr(f);
|
||||
}
|
||||
}
|
||||
|
||||
static int parse_constant_pool(FILE *f, ClassFile *cf) {
|
||||
cf->cp = calloc(cf->cp_count, sizeof(CPEntry));
|
||||
if (!cf->cp) return 0;
|
||||
|
||||
/* spec: index starts at 1; long/double consume two slots */
|
||||
for (int i = 1; i < cf->cp_count; i++) {
|
||||
CPEntry *e = &cf->cp[i];
|
||||
e->tag = read_u1(f);
|
||||
switch (e->tag) {
|
||||
case CP_UTF8: {
|
||||
e->utf8.len = read_u2(f);
|
||||
e->utf8.bytes = malloc(e->utf8.len + 1);
|
||||
fread(e->utf8.bytes, 1, e->utf8.len, f);
|
||||
e->utf8.bytes[e->utf8.len] = '\0';
|
||||
break;
|
||||
}
|
||||
case CP_INTEGER:
|
||||
e->integer.value = (int32_t)read_u4(f);
|
||||
break;
|
||||
case CP_FLOAT: {
|
||||
uint32_t raw = read_u4(f);
|
||||
memcpy(&e->flt.value, &raw, 4);
|
||||
break;
|
||||
}
|
||||
case CP_LONG:
|
||||
e->lng.value = (int64_t)read_u8(f);
|
||||
i++; /* long takes two CP slots */
|
||||
break;
|
||||
case CP_DOUBLE: {
|
||||
uint64_t raw = read_u8(f);
|
||||
memcpy(&e->dbl.value, &raw, 8);
|
||||
i++; /* double takes two CP slots */
|
||||
break;
|
||||
}
|
||||
case CP_CLASS:
|
||||
case CP_STRING:
|
||||
e->cls_or_str.index = read_u2(f);
|
||||
break;
|
||||
case CP_FIELDREF:
|
||||
case CP_METHODREF:
|
||||
case CP_IFACE_METHODREF:
|
||||
e->ref.class_index = read_u2(f);
|
||||
e->ref.name_and_type_index = read_u2(f);
|
||||
break;
|
||||
case CP_NAME_AND_TYPE:
|
||||
e->nat.name_index = read_u2(f);
|
||||
e->nat.descriptor_index = read_u2(f);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "classfile: unknown CP tag %d at index %d\n",
|
||||
e->tag, i);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ── parse fields ───────────────────────────────────────────────────────── */
|
||||
static int parse_fields(FILE *f, ClassFile *cf) {
|
||||
cf->fields = calloc(cf->fields_count, sizeof(Field));
|
||||
if (!cf->fields && cf->fields_count > 0) return 0;
|
||||
|
||||
for (int i = 0; i < cf->fields_count; i++) {
|
||||
cf->fields[i].access_flags = read_u2(f);
|
||||
cf->fields[i].name_index = read_u2(f);
|
||||
cf->fields[i].descriptor_index = read_u2(f);
|
||||
|
||||
/* skip field attributes (ConstantValue, etc.) */
|
||||
uint16_t ac = read_u2(f);
|
||||
for (int j = 0; j < ac; j++) {
|
||||
read_u2(f);
|
||||
skip_attr(f);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ── parse methods ──────────────────────────────────────────────────────── */
|
||||
static int parse_methods(FILE *f, ClassFile *cf) {
|
||||
cf->methods = calloc(cf->methods_count, sizeof(Method));
|
||||
if (!cf->methods && cf->methods_count > 0) return 0;
|
||||
|
||||
for (int i = 0; i < cf->methods_count; i++) {
|
||||
Method *m = &cf->methods[i];
|
||||
m->access_flags = read_u2(f);
|
||||
m->name_index = read_u2(f);
|
||||
m->descriptor_index = read_u2(f);
|
||||
m->has_code = 0;
|
||||
|
||||
uint16_t ac = read_u2(f);
|
||||
for (int j = 0; j < ac; j++) {
|
||||
uint16_t attr_name_idx = read_u2(f);
|
||||
const char *attr_name = cp_utf8(cf, attr_name_idx);
|
||||
|
||||
if (strcmp(attr_name, "Code") == 0) {
|
||||
parse_code_attr(f, &m->code);
|
||||
m->has_code = 1;
|
||||
} else {
|
||||
skip_attr(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ── public: load a .class file ─────────────────────────────────────────── */
|
||||
ClassFile *classfile_load(const char *path) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) {
|
||||
perror(path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t magic = read_u4(f);
|
||||
if (magic != 0xCAFEBABE) {
|
||||
fprintf(stderr, "%s: not a class file (magic=0x%08X)\n", path, magic);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ClassFile *cf = calloc(1, sizeof(ClassFile));
|
||||
cf->minor_version = read_u2(f);
|
||||
cf->major_version = read_u2(f);
|
||||
|
||||
/* constant pool */
|
||||
cf->cp_count = read_u2(f);
|
||||
if (!parse_constant_pool(f, cf)) goto fail;
|
||||
|
||||
cf->access_flags = read_u2(f);
|
||||
cf->this_class = read_u2(f);
|
||||
cf->super_class = read_u2(f);
|
||||
|
||||
/* interfaces */
|
||||
cf->interfaces_count = read_u2(f);
|
||||
cf->interfaces = malloc(cf->interfaces_count * sizeof(uint16_t));
|
||||
for (int i = 0; i < cf->interfaces_count; i++)
|
||||
cf->interfaces[i] = read_u2(f);
|
||||
|
||||
/* fields */
|
||||
cf->fields_count = read_u2(f);
|
||||
if (!parse_fields(f, cf)) goto fail;
|
||||
|
||||
/* methods */
|
||||
cf->methods_count = read_u2(f);
|
||||
if (!parse_methods(f, cf)) goto fail;
|
||||
|
||||
/* skip class-level attributes (SourceFile, etc.) */
|
||||
uint16_t ac = read_u2(f);
|
||||
for (int i = 0; i < ac; i++) {
|
||||
read_u2(f);
|
||||
skip_attr(f);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return cf;
|
||||
|
||||
fail:
|
||||
fclose(f);
|
||||
classfile_free(cf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ── public: find a method by name + descriptor ─────────────────────────── */
|
||||
Method *classfile_find_method(ClassFile *cf, const char *name, const char *descriptor) {
|
||||
for (int i = 0; i < cf->methods_count; i++) {
|
||||
const char *n = cp_utf8(cf, cf->methods[i].name_index);
|
||||
const char *d = cp_utf8(cf, cf->methods[i].descriptor_index);
|
||||
if (strcmp(n, name) == 0 && strcmp(d, descriptor) == 0)
|
||||
return &cf->methods[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ── public: free everything ────────────────────────────────────────────── */
|
||||
void classfile_free(ClassFile *cf) {
|
||||
if (!cf) return;
|
||||
if (cf->cp) {
|
||||
for (int i = 1; i < cf->cp_count; i++)
|
||||
if (cf->cp[i].tag == CP_UTF8)
|
||||
free(cf->cp[i].utf8.bytes);
|
||||
free(cf->cp);
|
||||
}
|
||||
if (cf->interfaces) free(cf->interfaces);
|
||||
if (cf->fields) free(cf->fields);
|
||||
if (cf->methods) {
|
||||
for (int i = 0; i < cf->methods_count; i++) {
|
||||
if (cf->methods[i].has_code) {
|
||||
free(cf->methods[i].code.code);
|
||||
free(cf->methods[i].code.exception_table);
|
||||
}
|
||||
}
|
||||
free(cf->methods);
|
||||
}
|
||||
free(cf);
|
||||
}
|
||||
|
||||
/* ── public: debug dump ─────────────────────────────────────────────────── */
|
||||
void classfile_dump(const ClassFile *cf) {
|
||||
printf("Class file version: %d.%d\n",
|
||||
cf->major_version, cf->minor_version);
|
||||
printf("This class : %s\n",
|
||||
cp_utf8(cf, cf->cp[cf->this_class].cls_or_str.index));
|
||||
printf("Super class: %s\n",
|
||||
cf->super_class
|
||||
? cp_utf8(cf, cf->cp[cf->super_class].cls_or_str.index)
|
||||
: "(none)");
|
||||
|
||||
printf("\nConstant pool (%d entries):\n", cf->cp_count - 1);
|
||||
for (int i = 1; i < cf->cp_count; i++) {
|
||||
CPEntry *e = &cf->cp[i];
|
||||
printf(" #%-3d ", i);
|
||||
switch (e->tag) {
|
||||
case CP_UTF8: printf("Utf8 \"%s\"\n", e->utf8.bytes); break;
|
||||
case CP_INTEGER: printf("Integer %d\n", e->integer.value); break;
|
||||
case CP_FLOAT: printf("Float %f\n", e->flt.value); break;
|
||||
case CP_LONG: printf("Long %lld\n", (long long)e->lng.value); break;
|
||||
case CP_DOUBLE: printf("Double %f\n", e->dbl.value); break;
|
||||
case CP_CLASS: printf("Class #%d\n", e->cls_or_str.index); break;
|
||||
case CP_STRING: printf("String #%d\n", e->cls_or_str.index); break;
|
||||
case CP_FIELDREF:
|
||||
printf("Fieldref #%d.#%d\n",
|
||||
e->ref.class_index, e->ref.name_and_type_index); break;
|
||||
case CP_METHODREF:
|
||||
printf("Methodref #%d.#%d\n",
|
||||
e->ref.class_index, e->ref.name_and_type_index); break;
|
||||
case CP_NAME_AND_TYPE:
|
||||
printf("NameAndType #%d:#%d\n",
|
||||
e->nat.name_index, e->nat.descriptor_index); break;
|
||||
default: printf("(tag %d)\n", e->tag);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nMethods (%d):\n", cf->methods_count);
|
||||
for (int i = 0; i < cf->methods_count; i++) {
|
||||
Method *m = &cf->methods[i];
|
||||
printf(" %s %s [code=%s, max_stack=%d, max_locals=%d, len=%d]\n",
|
||||
cp_utf8(cf, m->name_index),
|
||||
cp_utf8(cf, m->descriptor_index),
|
||||
m->has_code ? "yes" : "no",
|
||||
m->has_code ? m->code.max_stack : 0,
|
||||
m->has_code ? m->code.max_locals : 0,
|
||||
m->has_code ? (int)m->code.code_len : 0);
|
||||
}
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
#ifndef CLASSFILE_H
|
||||
#define CLASSFILE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.htmls table 4.3
|
||||
#define CP_UTF8 1
|
||||
#define CP_INTEGER 3
|
||||
#define CP_FLOAT 4
|
||||
#define CP_LONG 5
|
||||
#define CP_DOUBLE 6
|
||||
#define CP_CLASS 7
|
||||
#define CP_STRING 8
|
||||
#define CP_FIELDREF 9
|
||||
#define CP_METHODREF 10
|
||||
#define CP_IFACE_METHODREF 11
|
||||
#define CP_NAME_AND_TYPE 12
|
||||
|
||||
// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html table 4.1
|
||||
#define ACC_PUBLIC 0x0001
|
||||
#define ACC_PRIVATE 0x0002
|
||||
#define ACC_PROTECTED 0x0004
|
||||
#define ACC_STATIC 0x0008
|
||||
#define ACC_FINAL 0x0010
|
||||
#define ACC_SYNCHRONIZED 0x0020
|
||||
#define ACC_NATIVE 0x0100
|
||||
#define ACC_ABSTRACT 0x0400
|
||||
|
||||
/* ── opcodes we handle ──────────────────────────────────────────────────── */
|
||||
#define OP_NOP 0x00
|
||||
#define OP_ACONST_NULL 0x01
|
||||
#define OP_ICONST_M1 0x02
|
||||
#define OP_ICONST_0 0x03
|
||||
#define OP_ICONST_1 0x04
|
||||
#define OP_ICONST_2 0x05
|
||||
#define OP_ICONST_3 0x06
|
||||
#define OP_ICONST_4 0x07
|
||||
#define OP_ICONST_5 0x08
|
||||
#define OP_LCONST_0 0x09
|
||||
#define OP_LCONST_1 0x0A
|
||||
#define OP_BIPUSH 0x10
|
||||
#define OP_SIPUSH 0x11
|
||||
#define OP_LDC 0x12
|
||||
#define OP_LDC_W 0x13
|
||||
#define OP_LDC2_W 0x14
|
||||
#define OP_ILOAD 0x15
|
||||
#define OP_ALOAD 0x19
|
||||
#define OP_ILOAD_0 0x1A
|
||||
#define OP_ILOAD_1 0x1B
|
||||
#define OP_ILOAD_2 0x1C
|
||||
#define OP_ILOAD_3 0x1D
|
||||
#define OP_ALOAD_0 0x2A
|
||||
#define OP_ALOAD_1 0x2B
|
||||
#define OP_ALOAD_2 0x2C
|
||||
#define OP_ALOAD_3 0x2D
|
||||
#define OP_IALOAD 0x2E
|
||||
#define OP_ISTORE 0x36
|
||||
#define OP_ASTORE 0x3A
|
||||
#define OP_ISTORE_0 0x3B
|
||||
#define OP_ISTORE_1 0x3C
|
||||
#define OP_ISTORE_2 0x3D
|
||||
#define OP_ISTORE_3 0x3E
|
||||
#define OP_ASTORE_0 0x4B
|
||||
#define OP_ASTORE_1 0x4C
|
||||
#define OP_ASTORE_2 0x4D
|
||||
#define OP_ASTORE_3 0x4E
|
||||
#define OP_IASTORE 0x4F
|
||||
#define OP_POP 0x57
|
||||
#define OP_POP2 0x58
|
||||
#define OP_DUP 0x59
|
||||
#define OP_DUP_X1 0x5A
|
||||
#define OP_SWAP 0x5F
|
||||
#define OP_IADD 0x60
|
||||
#define OP_LADD 0x61
|
||||
#define OP_ISUB 0x64
|
||||
#define OP_IMUL 0x68
|
||||
#define OP_IDIV 0x6C
|
||||
#define OP_IREM 0x70
|
||||
#define OP_INEG 0x74
|
||||
#define OP_ISHL 0x78
|
||||
#define OP_ISHR 0x7A
|
||||
#define OP_IUSHR 0x7C
|
||||
#define OP_IAND 0x7E
|
||||
#define OP_IOR 0x80
|
||||
#define OP_IXOR 0x82
|
||||
#define OP_IINC 0x84
|
||||
#define OP_I2L 0x85
|
||||
#define OP_I2C 0x92
|
||||
#define OP_LCMP 0x94
|
||||
#define OP_IFEQ 0x99
|
||||
#define OP_IFNE 0x9A
|
||||
#define OP_IFLT 0x9B
|
||||
#define OP_IFGE 0x9C
|
||||
#define OP_IFGT 0x9D
|
||||
#define OP_IFLE 0x9E
|
||||
#define OP_IF_ICMPEQ 0x9F
|
||||
#define OP_IF_ICMPNE 0xA0
|
||||
#define OP_IF_ICMPLT 0xA1
|
||||
#define OP_IF_ICMPGE 0xA2
|
||||
#define OP_IF_ICMPGT 0xA3
|
||||
#define OP_IF_ICMPLE 0xA4
|
||||
#define OP_IF_ACMPEQ 0xA5
|
||||
#define OP_IF_ACMPNE 0xA6
|
||||
#define OP_GOTO 0xA7
|
||||
#define OP_IRETURN 0xAC
|
||||
#define OP_LRETURN 0xAD
|
||||
#define OP_ARETURN 0xB0
|
||||
#define OP_RETURN 0xB1
|
||||
#define OP_GETSTATIC 0xB2
|
||||
#define OP_PUTSTATIC 0xB3
|
||||
#define OP_GETFIELD 0xB4
|
||||
#define OP_PUTFIELD 0xB5
|
||||
#define OP_INVOKEVIRTUAL 0xB6
|
||||
#define OP_INVOKESPECIAL 0xB7
|
||||
#define OP_INVOKESTATIC 0xB8
|
||||
#define OP_NEW 0xBB
|
||||
#define OP_NEWARRAY 0xBC
|
||||
#define OP_ANEWARRAY 0xBD
|
||||
#define OP_ARRAYLENGTH 0xBE
|
||||
#define OP_ATHROW 0xBF
|
||||
#define OP_CHECKCAST 0xC0
|
||||
#define OP_INSTANCEOF 0xC1
|
||||
#define OP_IFNULL 0xC6
|
||||
#define OP_IFNONNULL 0xC7
|
||||
|
||||
|
||||
|
||||
/* ── constant pool entry ────────────────────────────────────────────────── */
|
||||
typedef struct {
|
||||
uint8_t tag;
|
||||
union {
|
||||
/* CP_UTF8 */
|
||||
struct { uint16_t len; char *bytes; } utf8;
|
||||
/* CP_INTEGER */
|
||||
struct { int32_t value; } integer;
|
||||
/* CP_FLOAT */
|
||||
struct { float value; } flt;
|
||||
/* CP_LONG */
|
||||
struct { int64_t value; } lng;
|
||||
/* CP_DOUBLE */
|
||||
struct { double value; } dbl;
|
||||
/* CP_CLASS, CP_STRING */
|
||||
struct { uint16_t index; } cls_or_str;
|
||||
/* CP_FIELDREF, CP_METHODREF, CP_IFACE_METHODREF */
|
||||
struct { uint16_t class_index; uint16_t name_and_type_index; } ref;
|
||||
/* CP_NAME_AND_TYPE */
|
||||
struct { uint16_t name_index; uint16_t descriptor_index; } nat;
|
||||
};
|
||||
} CPEntry;
|
||||
|
||||
/* ── exception handler table entry ─────────────────────────────────────── */
|
||||
typedef struct {
|
||||
uint16_t start_pc;
|
||||
uint16_t end_pc;
|
||||
uint16_t handler_pc;
|
||||
uint16_t catch_type; /* CP index to Class, or 0 = catch all */
|
||||
} ExceptionHandler;
|
||||
|
||||
/* ── Code attribute (lives inside a Method) ─────────────────────────────── */
|
||||
typedef struct {
|
||||
uint16_t max_stack;
|
||||
uint16_t max_locals;
|
||||
uint32_t code_len;
|
||||
uint8_t *code;
|
||||
uint16_t exception_table_len;
|
||||
ExceptionHandler *exception_table;
|
||||
} CodeAttr;
|
||||
|
||||
/* ── field ──────────────────────────────────────────────────────────────── */
|
||||
typedef struct {
|
||||
uint16_t access_flags;
|
||||
uint16_t name_index; /* → CP_UTF8 */
|
||||
uint16_t descriptor_index; /* → CP_UTF8 */
|
||||
} Field;
|
||||
|
||||
/* ── method ─────────────────────────────────────────────────────────────── */
|
||||
typedef struct {
|
||||
uint16_t access_flags;
|
||||
uint16_t name_index; /* → CP_UTF8 */
|
||||
uint16_t descriptor_index; /* → CP_UTF8 */
|
||||
CodeAttr code; /* zeroed if native/abstract */
|
||||
int has_code;
|
||||
} Method;
|
||||
|
||||
/* ── top-level class file ───────────────────────────────────────────────── */
|
||||
typedef struct {
|
||||
uint16_t minor_version;
|
||||
uint16_t major_version; /* 46 = Java 1.2 … 50 = Java 6 */
|
||||
|
||||
uint16_t cp_count; /* constant_pool_count from spec (1-indexed) */
|
||||
CPEntry *cp; /* cp[1..cp_count-1], cp[0] unused */
|
||||
|
||||
uint16_t access_flags;
|
||||
uint16_t this_class; /* CP index */
|
||||
uint16_t super_class; /* CP index (0 = java/lang/Object) */
|
||||
|
||||
uint16_t interfaces_count;
|
||||
uint16_t *interfaces; /* array of CP indices */
|
||||
|
||||
uint16_t fields_count;
|
||||
Field *fields;
|
||||
|
||||
uint16_t methods_count;
|
||||
Method *methods;
|
||||
} ClassFile;
|
||||
|
||||
/* ── helper: resolve UTF8 string from CP ───────────────────────────────── */
|
||||
static inline const char *cp_utf8(const ClassFile *cf, uint16_t idx) {
|
||||
if (idx == 0 || idx >= cf->cp_count) return "";
|
||||
if (cf->cp[idx].tag != CP_UTF8) return "";
|
||||
return cf->cp[idx].utf8.bytes;
|
||||
}
|
||||
|
||||
/* ── function declarations ──────────────────────────────────────────────── */
|
||||
ClassFile *classfile_load(const char *path);
|
||||
void classfile_free(ClassFile *cf);
|
||||
Method *classfile_find_method(ClassFile *cf, const char *name, const char *descriptor);
|
||||
void classfile_dump(const ClassFile *cf); /* debug print */
|
||||
|
||||
#endif /* CLASSFILE_H */
|
||||
+652
@@ -0,0 +1,652 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include "classfile.h"
|
||||
#include "helpers.h"
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#ifndef PATH_MAX
|
||||
#include <linux/limits.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static int ends_with(const char *s, const char *suffix) {
|
||||
size_t ls = strlen(s), lq = strlen(suffix);
|
||||
if (lq > ls) return 0;
|
||||
return strcmp(s + ls - lq, suffix) == 0;
|
||||
}
|
||||
|
||||
static void trim_inplace(char *s) {
|
||||
char *p = s;
|
||||
while (*p && isspace((unsigned char)*p)) p++;
|
||||
if (p != s) memmove(s, p, strlen(p) + 1);
|
||||
|
||||
size_t n = strlen(s);
|
||||
while (n > 0 && isspace((unsigned char)s[n - 1])) s[--n] = '\0';
|
||||
}
|
||||
|
||||
static void class_name_to_path(char *name) {
|
||||
for (char *p = name; *p; p++) if (*p == '.') *p = '/';
|
||||
if (!ends_with(name, ".class")) strcat(name, ".class");
|
||||
}
|
||||
|
||||
static int shell_quote(const char *in, char *out, size_t out_sz) {
|
||||
size_t k = 0;
|
||||
if (out_sz < 3) return 0;
|
||||
|
||||
out[k++] = '\'';
|
||||
for (const char *p = in; *p; p++) {
|
||||
if (*p == '\'') {
|
||||
if (k + 4 >= out_sz) return 0;
|
||||
out[k++] = '\'';
|
||||
out[k++] = '\\';
|
||||
out[k++] = '\'';
|
||||
out[k++] = '\'';
|
||||
} else {
|
||||
if (k + 1 >= out_sz) return 0;
|
||||
out[k++] = *p;
|
||||
}
|
||||
}
|
||||
if (k + 2 > out_sz) return 0;
|
||||
out[k++] = '\'';
|
||||
out[k] = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int find_first_class_file(const char *root, char *out_path, size_t out_sz) {
|
||||
DIR *dir = opendir(root);
|
||||
if (!dir) return 0;
|
||||
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
|
||||
|
||||
char full[PATH_MAX];
|
||||
snprintf(full, sizeof(full), "%s/%s", root, ent->d_name);
|
||||
|
||||
struct stat st;
|
||||
if (stat(full, &st) != 0) continue;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (find_first_class_file(full, out_path, out_sz)) {
|
||||
closedir(dir);
|
||||
return 1;
|
||||
}
|
||||
} else if (S_ISREG(st.st_mode) && ends_with(ent->d_name, ".class")) {
|
||||
strncpy(out_path, full, out_sz - 1);
|
||||
out_path[out_sz - 1] = '\0';
|
||||
closedir(dir);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int resolve_jar_entry_class(const char *extract_dir, char *out_class_file, size_t out_sz) {
|
||||
char manifest[PATH_MAX];
|
||||
snprintf(manifest, sizeof(manifest), "%s/META-INF/MANIFEST.MF", extract_dir);
|
||||
|
||||
FILE *fp = fopen(manifest, "r");
|
||||
if (!fp) return find_first_class_file(extract_dir, out_class_file, out_sz);
|
||||
|
||||
char line[1024];
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
if (strncmp(line, "MIDlet-1:", 9) == 0) {
|
||||
char value[512];
|
||||
strncpy(value, line + 9, sizeof(value) - 1);
|
||||
value[sizeof(value) - 1] = '\0';
|
||||
trim_inplace(value);
|
||||
|
||||
char *a = strrchr(value, ',');
|
||||
if (a) {
|
||||
a++;
|
||||
trim_inplace(a);
|
||||
class_name_to_path(a);
|
||||
snprintf(out_class_file, out_sz, "%s/%s", extract_dir, a);
|
||||
fclose(fp);
|
||||
return access(out_class_file, R_OK) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (strncmp(line, "Main-Class:", 11) == 0) {
|
||||
char value[512];
|
||||
strncpy(value, line + 11, sizeof(value) - 1);
|
||||
value[sizeof(value) - 1] = '\0';
|
||||
trim_inplace(value);
|
||||
class_name_to_path(value);
|
||||
snprintf(out_class_file, out_sz, "%s/%s", extract_dir, value);
|
||||
fclose(fp);
|
||||
return access(out_class_file, R_OK) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return find_first_class_file(extract_dir, out_class_file, out_sz);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
intptr_t locals[256];
|
||||
intptr_t stack[256];
|
||||
int sp;
|
||||
uint8_t *code;
|
||||
int pc;
|
||||
} Frame;
|
||||
|
||||
static intptr_t g_static_fields[65536];
|
||||
|
||||
static const char *resolve_class_name(ClassFile *cf, CPEntry *methodref) {
|
||||
uint16_t cls_idx = methodref->ref.class_index;
|
||||
uint16_t utf_idx = cf->cp[cls_idx].cls_or_str.index;
|
||||
return cp_utf8(cf, utf_idx);
|
||||
}
|
||||
|
||||
static int resolve_field_info(ClassFile *cf, uint16_t fieldref_idx, const char **class_name, const char **field_name, const char **field_desc) {
|
||||
if (!cf || fieldref_idx == 0 || fieldref_idx >= cf->cp_count) return 0;
|
||||
CPEntry *ref = &cf->cp[fieldref_idx];
|
||||
if (ref->tag != CP_FIELDREF) return 0;
|
||||
|
||||
uint16_t cls_idx = ref->ref.class_index;
|
||||
uint16_t nat_idx = ref->ref.name_and_type_index;
|
||||
if (cls_idx == 0 || cls_idx >= cf->cp_count) return 0;
|
||||
if (nat_idx == 0 || nat_idx >= cf->cp_count) return 0;
|
||||
if (cf->cp[cls_idx].tag != CP_CLASS) return 0;
|
||||
if (cf->cp[nat_idx].tag != CP_NAME_AND_TYPE) return 0;
|
||||
|
||||
CPEntry *nat = &cf->cp[nat_idx];
|
||||
if (class_name) *class_name = cp_utf8(cf, cf->cp[cls_idx].cls_or_str.index);
|
||||
if (field_name) *field_name = cp_utf8(cf, nat->nat.name_index);
|
||||
if (field_desc) *field_desc = cp_utf8(cf, nat->nat.descriptor_index);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const char *current_class_name(ClassFile *cf) {
|
||||
if (!cf || cf->this_class == 0 || cf->this_class >= cf->cp_count) return "";
|
||||
if (cf->cp[cf->this_class].tag != CP_CLASS) return "";
|
||||
return cp_utf8(cf, cf->cp[cf->this_class].cls_or_str.index);
|
||||
}
|
||||
|
||||
static int field_slot(ClassFile *cf, uint16_t fieldref_idx) {
|
||||
CPEntry *ref = &cf->cp[fieldref_idx];
|
||||
CPEntry *nat = &cf->cp[ref->ref.name_and_type_index];
|
||||
const char *fname = cp_utf8(cf, nat->nat.name_index);
|
||||
for (int i = 0; i < cf->fields_count; i++)
|
||||
if (strcmp(cp_utf8(cf, cf->fields[i].name_index), fname) == 0)
|
||||
return i;
|
||||
fprintf(stderr, "field not found: %s\n", fname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int execute(ClassFile *cf, Method *m, intptr_t *args, int argc) {
|
||||
Frame f = {0};
|
||||
f.code = m->code.code;
|
||||
|
||||
/* copy arguments into locals */
|
||||
for (int i = 0; i < argc; i++)
|
||||
f.locals[i] = args[i];
|
||||
|
||||
while (1) {
|
||||
uint8_t op = f.code[f.pc++];
|
||||
switch (op) {
|
||||
case OP_ALOAD_0: f.stack[f.sp++] = f.locals[0]; break;
|
||||
case OP_ALOAD_1: f.stack[f.sp++] = f.locals[1]; break;
|
||||
case OP_ALOAD_2: f.stack[f.sp++] = f.locals[2]; break;
|
||||
case OP_ALOAD_3: f.stack[f.sp++] = f.locals[3]; break;
|
||||
case OP_NEW: {
|
||||
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc + 1];
|
||||
f.pc += 2;
|
||||
CPEntry *cls = &cf->cp[idx];
|
||||
Object *obj = heap_alloc(cls, 0);
|
||||
f.stack[f.sp++] = OBJ_TO_SLOT(obj);
|
||||
break;
|
||||
}
|
||||
case OP_DUP: f.stack[f.sp] = f.stack[f.sp - 1]; f.sp++; break;
|
||||
case OP_ICONST_0: f.stack[f.sp++] = 0; break;
|
||||
case OP_ICONST_1: f.stack[f.sp++] = 1; break;
|
||||
case OP_ICONST_2: f.stack[f.sp++] = 2; break;
|
||||
case OP_ICONST_3: f.stack[f.sp++] = 3; break;
|
||||
case OP_ICONST_4: f.stack[f.sp++] = 4; break;
|
||||
case OP_ICONST_5: f.stack[f.sp++] = 5; break;
|
||||
|
||||
case OP_BIPUSH: f.stack[f.sp++] = (int8_t)f.code[f.pc++]; break;
|
||||
case OP_SIPUSH: {
|
||||
int16_t v = (int16_t)((f.code[f.pc] << 8) | f.code[f.pc + 1]);
|
||||
f.pc += 2;
|
||||
f.stack[f.sp++] = v;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_ISTORE_0: f.locals[0] = f.stack[--f.sp]; break;
|
||||
case OP_ISTORE_1: f.locals[1] = f.stack[--f.sp]; break;
|
||||
case OP_ISTORE_2: f.locals[2] = f.stack[--f.sp]; break;
|
||||
case OP_ISTORE_3: f.locals[3] = f.stack[--f.sp]; break;
|
||||
|
||||
case OP_ILOAD_0: f.stack[f.sp++] = f.locals[0]; break;
|
||||
case OP_ILOAD_1: f.stack[f.sp++] = f.locals[1]; break;
|
||||
case OP_ILOAD_2: f.stack[f.sp++] = f.locals[2]; break;
|
||||
case OP_ILOAD_3: f.stack[f.sp++] = f.locals[3]; break;
|
||||
|
||||
case OP_IADD: {
|
||||
int b = f.stack[--f.sp];
|
||||
int a = f.stack[--f.sp];
|
||||
f.stack[f.sp++] = a + b;
|
||||
break;
|
||||
}
|
||||
case OP_ISUB:{
|
||||
int b = f.stack[--f.sp];
|
||||
int a = f.stack[--f.sp];
|
||||
f.stack[f.sp++] = a - b;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_IF_ICMPGT: {
|
||||
int16_t offset = (f.code[f.pc] << 8) | f.code[f.pc+1];
|
||||
f.pc += 2;
|
||||
int b = f.stack[--f.sp];
|
||||
int a = f.stack[--f.sp];
|
||||
if (a > b) f.pc += offset - 3;
|
||||
break;
|
||||
}
|
||||
case OP_IFNULL: {
|
||||
int16_t offset = (f.code[f.pc] << 8) | f.code[f.pc + 1];
|
||||
f.pc += 2;
|
||||
intptr_t ref = f.stack[--f.sp];
|
||||
if (ref == 0) f.pc += offset - 3;
|
||||
break;
|
||||
}
|
||||
case OP_IFNONNULL: {
|
||||
int16_t offset = (f.code[f.pc] << 8) | f.code[f.pc + 1];
|
||||
f.pc += 2;
|
||||
intptr_t ref = f.stack[--f.sp];
|
||||
if (ref != 0) f.pc += offset - 3;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_IRETURN: return f.stack[--f.sp];
|
||||
case OP_RETURN: return 0;
|
||||
case OP_GETSTATIC: {
|
||||
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc + 1];
|
||||
f.pc += 2;
|
||||
const char *cls = NULL, *name = NULL, *desc = NULL;
|
||||
if (resolve_field_info(cf, idx, &cls, &name, &desc) &&
|
||||
strcmp(cls, "java/lang/System") == 0 &&
|
||||
strcmp(name, "out") == 0 &&
|
||||
strcmp(desc, "Ljava/io/PrintStream;") == 0) {
|
||||
f.stack[f.sp++] = 0x57D0; /* fake PrintStream/static handle */
|
||||
break;
|
||||
}
|
||||
f.stack[f.sp++] = g_static_fields[idx];
|
||||
break;
|
||||
}
|
||||
case OP_PUTSTATIC: {
|
||||
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc + 1];
|
||||
f.pc += 2;
|
||||
g_static_fields[idx] = f.stack[--f.sp];
|
||||
break;
|
||||
}
|
||||
case OP_INVOKEVIRTUAL: {
|
||||
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc+1];
|
||||
f.pc += 2;
|
||||
CPEntry *methodref = &cf->cp[idx];
|
||||
CPEntry *nat = &cf->cp[methodref->ref.name_and_type_index];
|
||||
const char *name = cp_utf8(cf, nat->nat.name_index);
|
||||
const char *desc = cp_utf8(cf, nat->nat.descriptor_index);
|
||||
const char *class_name = resolve_class_name(cf, methodref);
|
||||
|
||||
if (midp_native_call(cf, class_name, name, desc, f.stack, &f.sp, op)) break;
|
||||
|
||||
int nargs = 1; /* +1 for 'this' */
|
||||
const char *d = desc + 1;
|
||||
while (*d && *d != ')') {
|
||||
switch (*d) {
|
||||
case 'I': case 'Z': case 'B': case 'C': case 'S': case 'F':
|
||||
nargs++; d++; break;
|
||||
case 'J': case 'D': nargs += 2; d++; break;
|
||||
case 'L': nargs++; while (*d && *d != ';') d++; d++; break;
|
||||
case '[': nargs++;
|
||||
while (*d == '[') d++;
|
||||
if (*d == 'L') while (*d && *d != ';') d++;
|
||||
d++; break;
|
||||
default: d++; break;
|
||||
}
|
||||
}
|
||||
ClassFile *target_cf = cf;
|
||||
ClassFile *loaded_cf = NULL;
|
||||
Method *target = classfile_find_method(cf, name, desc);
|
||||
if (!target && class_name && *class_name) {
|
||||
char class_path[PATH_MAX];
|
||||
snprintf(class_path, sizeof(class_path), "%s.class", class_name);
|
||||
loaded_cf = classfile_load(class_path);
|
||||
if (loaded_cf) {
|
||||
target = classfile_find_method(loaded_cf, name, desc);
|
||||
if (target) target_cf = loaded_cf;
|
||||
}
|
||||
}
|
||||
if (!target) {
|
||||
if (loaded_cf) classfile_free(loaded_cf);
|
||||
fprintf(stderr, "method not found: %s.%s%s\n", class_name, name, desc);
|
||||
return -1;
|
||||
}
|
||||
intptr_t vargs[32];
|
||||
for (int i = nargs - 1; i >= 0; i--) vargs[i] = f.stack[--f.sp];
|
||||
int result = execute(target_cf, target, vargs, nargs);
|
||||
if (loaded_cf) classfile_free(loaded_cf);
|
||||
const char *ret = strchr(desc, ')') + 1;
|
||||
if (*ret != 'V') f.stack[f.sp++] = result;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_LDC: {
|
||||
uint8_t idx = f.code[f.pc++];
|
||||
CPEntry *e = &cf->cp[idx];
|
||||
switch (e->tag) {
|
||||
case CP_INTEGER: f.stack[f.sp++] = e->integer.value; break;
|
||||
case CP_FLOAT: { int b; memcpy(&b, &e->flt.value, 4); f.stack[f.sp++] = b; break; }
|
||||
case CP_STRING: f.stack[f.sp++] = (int)idx; break;
|
||||
default: fprintf(stderr, "ldc: unhandled tag %d\n", e->tag); return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_LDC_W:
|
||||
case OP_LDC2_W: {
|
||||
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc+1];
|
||||
f.pc += 2;
|
||||
CPEntry *e = &cf->cp[idx];
|
||||
switch (e->tag) {
|
||||
case CP_INTEGER: f.stack[f.sp++] = e->integer.value; break;
|
||||
case CP_LONG:
|
||||
f.stack[f.sp++] = (int)(e->lng.value & 0xFFFFFFFF);
|
||||
f.stack[f.sp++] = (int)(e->lng.value >> 32);
|
||||
break;
|
||||
case CP_STRING: f.stack[f.sp++] = (int)idx; break;
|
||||
default: fprintf(stderr, "ldc_w: unhandled tag %d\n", e->tag); return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_INVOKESTATIC: {
|
||||
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc+1];
|
||||
f.pc += 2;
|
||||
CPEntry *methodref = &cf->cp[idx];
|
||||
CPEntry *nat = &cf->cp[methodref->ref.name_and_type_index];
|
||||
const char *name = cp_utf8(cf, nat->nat.name_index);
|
||||
const char *desc = cp_utf8(cf, nat->nat.descriptor_index);
|
||||
const char *class_name = resolve_class_name(cf, methodref);
|
||||
|
||||
if (midp_native_call(cf, class_name, name, desc, f.stack, &f.sp, op)) break;
|
||||
|
||||
ClassFile *target_cf = cf;
|
||||
ClassFile *loaded_cf = NULL;
|
||||
Method *target = classfile_find_method(cf, name, desc);
|
||||
if (!target && class_name && *class_name) {
|
||||
char class_path[PATH_MAX];
|
||||
snprintf(class_path, sizeof(class_path), "%s.class", class_name);
|
||||
loaded_cf = classfile_load(class_path);
|
||||
if (loaded_cf) {
|
||||
target = classfile_find_method(loaded_cf, name, desc);
|
||||
if (target) target_cf = loaded_cf;
|
||||
}
|
||||
}
|
||||
if (!target) {
|
||||
if (loaded_cf) classfile_free(loaded_cf);
|
||||
fprintf(stderr, "method not found: %s%s\n", name, desc);
|
||||
return -1;
|
||||
}
|
||||
int nargs = 0;
|
||||
const char *d = desc + 1; /* skip '(' */
|
||||
while (*d && *d != ')') {
|
||||
switch (*d) {
|
||||
case 'I': case 'Z': case 'B':
|
||||
case 'C': case 'S': case 'F':
|
||||
nargs++; d++;
|
||||
break;
|
||||
case 'J': case 'D': /* long/double take 2 slots */
|
||||
nargs += 2; d++;
|
||||
break;
|
||||
case 'L': /* object ref */
|
||||
nargs++;
|
||||
while (*d && *d != ';') d++;
|
||||
d++; /* skip ';' */
|
||||
break;
|
||||
case '[': /* array */
|
||||
nargs++;
|
||||
while (*d == '[') d++; /* skip dimensions */
|
||||
if (*d == 'L') while (*d && *d != ';') d++;
|
||||
d++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
intptr_t args[16];
|
||||
for (int i = nargs - 1; i >= 0; i--) args[i] = f.stack[--f.sp];
|
||||
int result = execute(target_cf, target, args, nargs);
|
||||
if (loaded_cf) classfile_free(loaded_cf);
|
||||
const char *ret = strchr(desc, ')') + 1;
|
||||
if (*ret != 'V') f.stack[f.sp++] = result;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_ASTORE_0: f.locals[0] = f.stack[--f.sp]; break;
|
||||
case OP_ASTORE_1: f.locals[1] = f.stack[--f.sp]; break;
|
||||
case OP_ASTORE_2: f.locals[2] = f.stack[--f.sp]; break;
|
||||
case OP_ASTORE_3: f.locals[3] = f.stack[--f.sp]; break;
|
||||
|
||||
case OP_INVOKESPECIAL: {
|
||||
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc+1];
|
||||
f.pc += 2;
|
||||
CPEntry *methodref = &cf->cp[idx];
|
||||
CPEntry *nat = &cf->cp[methodref->ref.name_and_type_index];
|
||||
const char *name = cp_utf8(cf, nat->nat.name_index);
|
||||
const char *desc = cp_utf8(cf, nat->nat.descriptor_index);
|
||||
const char *class_name = resolve_class_name(cf, methodref);
|
||||
const char *this_name = current_class_name(cf);
|
||||
|
||||
/* count args + implicit 'this' */
|
||||
int nargs = 1;
|
||||
const char *d = desc + 1;
|
||||
while (*d && *d != ')') {
|
||||
switch (*d) {
|
||||
case 'I': case 'Z': case 'B': case 'C': case 'S': case 'F':
|
||||
nargs++; d++; break;
|
||||
case 'J': case 'D': nargs += 2; d++; break;
|
||||
case 'L': nargs++; while (*d && *d != ';') d++; d++; break;
|
||||
case '[': nargs++;
|
||||
while (*d == '[') d++;
|
||||
if (*d == 'L') while (*d && *d != ';') d++;
|
||||
d++; break;
|
||||
default: d++; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (midp_native_call(cf, class_name, name, desc, f.stack, &f.sp, op)) break;
|
||||
|
||||
ClassFile *target_cf = cf;
|
||||
ClassFile *loaded_cf = NULL;
|
||||
Method *target = NULL;
|
||||
|
||||
if (class_name && *class_name && strcmp(class_name, this_name) != 0) {
|
||||
char class_path[PATH_MAX];
|
||||
snprintf(class_path, sizeof(class_path), "%s.class", class_name);
|
||||
loaded_cf = classfile_load(class_path);
|
||||
if (loaded_cf) {
|
||||
target = classfile_find_method(loaded_cf, name, desc);
|
||||
if (target) target_cf = loaded_cf;
|
||||
}
|
||||
} else {
|
||||
target = classfile_find_method(cf, name, desc);
|
||||
}
|
||||
if (!target) {
|
||||
/* super/<init> we don't have — just pop args+this */
|
||||
f.sp -= nargs;
|
||||
if (loaded_cf) classfile_free(loaded_cf);
|
||||
break;
|
||||
}
|
||||
intptr_t sargs[32];
|
||||
for (int i = nargs - 1; i >= 0; i--) sargs[i] = f.stack[--f.sp];
|
||||
execute(target_cf, target, sargs, nargs);
|
||||
if (loaded_cf) classfile_free(loaded_cf);
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_PUTFIELD: {
|
||||
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc + 1];
|
||||
f.pc += 2;
|
||||
intptr_t val = f.stack[--f.sp];
|
||||
Object *obj = SLOT_TO_OBJ(f.stack[--f.sp]);
|
||||
if (!obj) { fprintf(stderr, "NPE: putfield at pc=%d\n", f.pc - 3); return -1; }
|
||||
int slot = field_slot(cf, idx);
|
||||
if (slot < 0) return -1;
|
||||
if (slot >= obj->fields_count) {
|
||||
obj->fields = realloc(obj->fields, (slot + 1) * sizeof(intptr_t));
|
||||
for (int i = obj->fields_count; i <= slot; i++) obj->fields[i] = 0;
|
||||
obj->fields_count = slot + 1;
|
||||
}
|
||||
obj->fields[slot] = val;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_GETFIELD: {
|
||||
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc + 1];
|
||||
f.pc += 2;
|
||||
Object *obj = SLOT_TO_OBJ(f.stack[--f.sp]);
|
||||
if (!obj) { fprintf(stderr, "NPE: getfield at pc=%d\n", f.pc - 3); return -1; }
|
||||
int slot = field_slot(cf, idx);
|
||||
if (slot < 0) return -1;
|
||||
if (slot >= obj->fields_count) {
|
||||
obj->fields = realloc(obj->fields, (slot + 1) * sizeof(intptr_t));
|
||||
for (int i = obj->fields_count; i <= slot; i++) obj->fields[i] = 0;
|
||||
obj->fields_count = slot + 1;
|
||||
}
|
||||
f.stack[f.sp++] = obj->fields[slot];
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
printf("unknown opcode 0x%02X at pc=%d\n", op, f.pc-1);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int exit_code = 0;
|
||||
char input_path[PATH_MAX];
|
||||
char original_cwd[PATH_MAX];
|
||||
char jar_temp_dir[PATH_MAX] = {0};
|
||||
int changed_dir = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "usage: %s <file.class|file.jar>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
strncpy(input_path, argv[1], sizeof(input_path) - 1);
|
||||
input_path[sizeof(input_path) - 1] = '\0';
|
||||
|
||||
if (ends_with(input_path, ".jar")) {
|
||||
if (!getcwd(original_cwd, sizeof(original_cwd))) {
|
||||
perror("getcwd");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char tmpl[] = "/tmp/kolibrios-jar-XXXXXX";
|
||||
char *tmp = mkdtemp(tmpl);
|
||||
if (!tmp) {
|
||||
perror("mkdtemp");
|
||||
return 1;
|
||||
}
|
||||
strncpy(jar_temp_dir, tmp, sizeof(jar_temp_dir) - 1);
|
||||
|
||||
char qjar[PATH_MAX * 2], qtmp[PATH_MAX * 2], cmd[PATH_MAX * 5];
|
||||
if (!shell_quote(input_path, qjar, sizeof(qjar)) ||
|
||||
!shell_quote(jar_temp_dir, qtmp, sizeof(qtmp))) {
|
||||
fprintf(stderr, "path too long for jar extraction\n");
|
||||
return 1;
|
||||
}
|
||||
snprintf(cmd, sizeof(cmd), "unzip -oq %s -d %s >/dev/null", qjar, qtmp);
|
||||
if (system(cmd) != 0) {
|
||||
fprintf(stderr, "failed to extract jar: %s\n", input_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!resolve_jar_entry_class(jar_temp_dir, input_path, sizeof(input_path))) {
|
||||
fprintf(stderr, "could not resolve entry class from jar manifest: %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (chdir(jar_temp_dir) != 0) {
|
||||
perror("chdir");
|
||||
return 1;
|
||||
}
|
||||
changed_dir = 1;
|
||||
}
|
||||
|
||||
midp_sdl_init(240, 320);
|
||||
|
||||
/* step 1 - load the class file */
|
||||
ClassFile *cf = classfile_load(input_path);
|
||||
if (!cf) {
|
||||
midp_sdl_destroy();
|
||||
if (changed_dir) chdir(original_cwd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* step 2 - execute entry point */
|
||||
Method *m = classfile_find_method(cf, "main", "([Ljava/lang/String;)V");
|
||||
if (!m) {
|
||||
Method *start = classfile_find_method(cf, "startApp", "()V");
|
||||
if (!start) {
|
||||
fprintf(stderr, "no main or startApp method found\n");
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
Object *self = heap_alloc(&cf->cp[cf->this_class], cf->fields_count);
|
||||
if (!self) {
|
||||
fprintf(stderr, "failed to allocate MIDlet instance\n");
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
Method *ctor = classfile_find_method(cf, "<init>", "()V");
|
||||
if (ctor) {
|
||||
intptr_t cargs[1] = { OBJ_TO_SLOT(self) };
|
||||
if (execute(cf, ctor, cargs, 1) < 0) {
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
intptr_t sargs[1] = { OBJ_TO_SLOT(self) };
|
||||
if (execute(cf, start, sargs, 1) < 0) {
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
} else {
|
||||
if (execute(cf, m, NULL, 0) < 0) {
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep emulator window visible after startup work. */
|
||||
midp_sdl_wait(5000);
|
||||
|
||||
cleanup:
|
||||
classfile_free(cf);
|
||||
midp_sdl_destroy();
|
||||
if (changed_dir) {
|
||||
char qtmp[PATH_MAX * 2], cmd[PATH_MAX * 3];
|
||||
chdir(original_cwd);
|
||||
if (shell_quote(jar_temp_dir, qtmp, sizeof(qtmp))) {
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", qtmp);
|
||||
system(cmd);
|
||||
}
|
||||
}
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,439 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include "helpers.h"
|
||||
|
||||
#define HEAP_POOL_SIZE 4096
|
||||
|
||||
static Object pool[HEAP_POOL_SIZE];
|
||||
static Object *free_list = NULL;
|
||||
static int initialized = 0;
|
||||
|
||||
static void heap_init(void) {
|
||||
for (int i = 0; i < HEAP_POOL_SIZE - 1; i++)
|
||||
pool[i].next_free = &pool[i + 1];
|
||||
pool[HEAP_POOL_SIZE - 1].next_free = NULL;
|
||||
free_list = &pool[0];
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
Object *heap_alloc(CPEntry *cls, uint16_t fields_count) {
|
||||
if (!initialized) heap_init();
|
||||
|
||||
if (!free_list) {
|
||||
fprintf(stderr, "heap: out of memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Object *obj = free_list;
|
||||
free_list = obj->next_free;
|
||||
obj->next_free = NULL;
|
||||
obj->cls = cls;
|
||||
obj->fields_count = fields_count;
|
||||
obj->fields = calloc(fields_count, sizeof(intptr_t));
|
||||
return obj;
|
||||
}
|
||||
|
||||
void heap_free(Object *obj) {
|
||||
if (!obj) return;
|
||||
free(obj->fields);
|
||||
obj->fields = NULL;
|
||||
obj->next_free = free_list;
|
||||
free_list = obj;
|
||||
}
|
||||
|
||||
void heap_destroy(void) {
|
||||
for (int i = 0; i < HEAP_POOL_SIZE; i++)
|
||||
if (pool[i].next_free == NULL && pool[i].fields)
|
||||
free(pool[i].fields);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* SDL2 globals */
|
||||
static SDL_Window *g_win = NULL;
|
||||
static SDL_Renderer *g_ren = NULL;
|
||||
static TTF_Font *g_font = NULL;
|
||||
|
||||
extern int execute(ClassFile *cf, Method *m, intptr_t *args, int argc);
|
||||
|
||||
/* Fake "Display" object slot — just a sentinel int, not a real Object */
|
||||
#define DISPLAY_SENTINEL 0xD15F1A7
|
||||
|
||||
void midp_sdl_init(int w, int h) {
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
if (TTF_Init() != 0) {
|
||||
fprintf(stderr, "SDL_ttf init failed: %s\n", TTF_GetError());
|
||||
}
|
||||
|
||||
g_win = SDL_CreateWindow("MIDlet", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, 0);
|
||||
g_ren = SDL_CreateRenderer(g_win, -1, SDL_RENDERER_ACCELERATED);
|
||||
|
||||
if (!g_font) {
|
||||
const char *font_candidates[] = {
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/truetype/liberation2/LiberationSans-Regular.ttf",
|
||||
"/usr/share/fonts/truetype/freefont/FreeSans.ttf"
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(font_candidates) / sizeof(font_candidates[0]); i++) {
|
||||
g_font = TTF_OpenFont(font_candidates[i], 16);
|
||||
if (g_font) break;
|
||||
}
|
||||
if (!g_font) {
|
||||
fprintf(stderr, "SDL_ttf font load failed: %s\n", TTF_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawColor(g_ren, 0, 0, 0, 255);
|
||||
SDL_RenderClear(g_ren);
|
||||
SDL_RenderPresent(g_ren);
|
||||
}
|
||||
|
||||
void midp_sdl_destroy(void) {
|
||||
if (g_font) {
|
||||
TTF_CloseFont(g_font);
|
||||
g_font = NULL;
|
||||
}
|
||||
if (TTF_WasInit()) TTF_Quit();
|
||||
SDL_DestroyRenderer(g_ren);
|
||||
SDL_DestroyWindow(g_win);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
void midp_sdl_wait(int milliseconds) {
|
||||
Uint32 start = SDL_GetTicks();
|
||||
SDL_Event ev;
|
||||
|
||||
while ((int)(SDL_GetTicks() - start) < milliseconds) {
|
||||
while (SDL_PollEvent(&ev)) {
|
||||
if (ev.type == SDL_QUIT) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
SDL_Delay(16);
|
||||
}
|
||||
}
|
||||
|
||||
/* helper: match class/name/desc triple */
|
||||
static int is(const char *a, const char *b) { return strcmp(a, b) == 0; }
|
||||
|
||||
static void draw_text_ttf(const char *text, int x, int y) {
|
||||
if (!text || !*text || !g_font || !g_ren) return;
|
||||
|
||||
Uint8 r = 255, g = 255, b = 255, a = 255;
|
||||
SDL_GetRenderDrawColor(g_ren, &r, &g, &b, &a);
|
||||
|
||||
SDL_Color color = { r, g, b, a };
|
||||
SDL_Surface *surface = TTF_RenderUTF8_Blended(g_font, text, color);
|
||||
if (!surface) return;
|
||||
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(g_ren, surface);
|
||||
if (!texture) {
|
||||
SDL_FreeSurface(surface);
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Rect dst = { x, y, surface->w, surface->h };
|
||||
SDL_FreeSurface(surface);
|
||||
SDL_RenderCopy(g_ren, texture, NULL, &dst);
|
||||
SDL_DestroyTexture(texture);
|
||||
}
|
||||
|
||||
static void draw_arc_outline(int cx, int cy, int rx, int ry, int start_deg, int arc_deg) {
|
||||
int end_deg = start_deg + arc_deg;
|
||||
for (int deg = start_deg; deg <= end_deg; deg++) {
|
||||
double rad = deg * 3.14159265358979323846 / 180.0;
|
||||
int x = cx + (int)(rx * cos(rad));
|
||||
int y = cy + (int)(ry * sin(rad));
|
||||
SDL_RenderDrawPoint(g_ren, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
static int invoke_canvas_paint(ClassFile *caller_cf, intptr_t canvas_slot) {
|
||||
Object *canvas = SLOT_TO_OBJ(canvas_slot);
|
||||
if (!canvas || !canvas->cls || canvas->cls->tag != CP_CLASS) return 0;
|
||||
|
||||
const char *canvas_class = cp_utf8(caller_cf, canvas->cls->cls_or_str.index);
|
||||
if (!canvas_class || !canvas_class[0]) return 0;
|
||||
|
||||
char class_path[512];
|
||||
snprintf(class_path, sizeof(class_path), "%s.class", canvas_class);
|
||||
|
||||
ClassFile *canvas_cf = classfile_load(class_path);
|
||||
if (!canvas_cf) {
|
||||
fprintf(stderr, "cannot load canvas class file: %s\n", class_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Method *paint = classfile_find_method(canvas_cf, "paint",
|
||||
"(Ljavax/microedition/lcdui/Graphics;)V");
|
||||
if (!paint) {
|
||||
classfile_free(canvas_cf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
intptr_t args[2] = { canvas_slot, 0xC0FFEE };
|
||||
int rc = execute(canvas_cf, paint, args, 2);
|
||||
classfile_free(canvas_cf);
|
||||
|
||||
SDL_RenderPresent(g_ren);
|
||||
return rc >= 0;
|
||||
}
|
||||
|
||||
int midp_native_call(ClassFile *cf, const char *class_name, const char *name, const char *desc, intptr_t *stack, int *sp, int opcode __attribute__((unused)))
|
||||
{
|
||||
if (is(desc, "()I") && is(name, "getWidth")) {
|
||||
int w, h;
|
||||
(void)h;
|
||||
SDL_GetWindowSize(g_win, &w, &h);
|
||||
(*sp)--; /* pop 'this' */
|
||||
stack[(*sp)++] = w;
|
||||
return 1;
|
||||
}
|
||||
if (is(desc, "()I") && is(name, "getHeight")) {
|
||||
int w, h;
|
||||
(void)w;
|
||||
SDL_GetWindowSize(g_win, &w, &h);
|
||||
(*sp)--; /* pop 'this' */
|
||||
stack[(*sp)++] = h;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* javax.microedition.lcdui.Display */
|
||||
|
||||
if (is(class_name, "javax/microedition/lcdui/Display")) {
|
||||
|
||||
/* Display.getDisplay(MIDlet) → Display */
|
||||
if (is(name, "getDisplay") &&
|
||||
is(desc, "(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;")) {
|
||||
(*sp)--; /* pop MIDlet ref */
|
||||
stack[(*sp)++] = DISPLAY_SENTINEL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Display.setCurrent(Displayable) → void */
|
||||
if (is(name, "setCurrent") &&
|
||||
is(desc, "(Ljavax/microedition/lcdui/Displayable;)V")) {
|
||||
intptr_t displayable = stack[--(*sp)];
|
||||
intptr_t display_ref = stack[--(*sp)]; /* 'this' for virtual */
|
||||
(void)displayable; (void)display_ref;
|
||||
|
||||
/* Clear and show a frame so emulator window is visibly active. */
|
||||
SDL_SetRenderDrawColor(g_ren, 0, 0, 0, 255);
|
||||
SDL_RenderClear(g_ren);
|
||||
|
||||
/* Try to run original Canvas.paint(Graphics). */
|
||||
invoke_canvas_paint(cf, displayable);
|
||||
|
||||
SDL_RenderPresent(g_ren);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* javax.microedition.lcdui.Graphics ─ */
|
||||
|
||||
if (is(class_name, "javax/microedition/lcdui/Graphics")) {
|
||||
|
||||
/* setColor(int) */
|
||||
if (is(name, "setColor") && is(desc, "(I)V")) {
|
||||
int rgb = stack[--(*sp)];
|
||||
(*sp)--; /* pop 'this' */
|
||||
SDL_SetRenderDrawColor(g_ren,
|
||||
(rgb >> 16) & 0xFF,
|
||||
(rgb >> 8) & 0xFF,
|
||||
rgb & 0xFF, 255);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* setColor(r, g, b) */
|
||||
if (is(name, "setColor") && is(desc, "(III)V")) {
|
||||
int b = stack[--(*sp)] & 0xFF;
|
||||
int g = stack[--(*sp)] & 0xFF;
|
||||
int r = stack[--(*sp)] & 0xFF;
|
||||
(*sp)--; /* pop 'this' */
|
||||
SDL_SetRenderDrawColor(g_ren, r, g, b, 255);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* fillRect(x, y, w, h) */
|
||||
if (is(name, "fillRect") && is(desc, "(IIII)V")) {
|
||||
int h2 = stack[--(*sp)];
|
||||
int w2 = stack[--(*sp)];
|
||||
int y = stack[--(*sp)];
|
||||
int x = stack[--(*sp)];
|
||||
(*sp)--; /* pop 'this' */
|
||||
SDL_Rect r = { x, y, w2, h2 };
|
||||
SDL_RenderFillRect(g_ren, &r);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* drawRect(x, y, w, h) */
|
||||
if (is(name, "drawRect") && is(desc, "(IIII)V")) {
|
||||
int h2 = stack[--(*sp)];
|
||||
int w2 = stack[--(*sp)];
|
||||
int y = stack[--(*sp)];
|
||||
int x = stack[--(*sp)];
|
||||
(*sp)--;
|
||||
SDL_Rect r = { x, y, w2, h2 };
|
||||
SDL_RenderDrawRect(g_ren, &r);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* drawLine(x1, y1, x2, y2) */
|
||||
if (is(name, "drawLine") && is(desc, "(IIII)V")) {
|
||||
int y2 = stack[--(*sp)];
|
||||
int x2 = stack[--(*sp)];
|
||||
int y1 = stack[--(*sp)];
|
||||
int x1 = stack[--(*sp)];
|
||||
(*sp)--;
|
||||
SDL_RenderDrawLine(g_ren, x1, y1, x2, y2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* drawString(String, x, y, anchor) - anchor ignored for now */
|
||||
if (is(name, "drawString") && is(desc, "(Ljava/lang/String;III)V")) {
|
||||
int anchor = stack[--(*sp)];
|
||||
int y = stack[--(*sp)];
|
||||
int x = stack[--(*sp)];
|
||||
int sidx = stack[--(*sp)];
|
||||
(*sp)--;
|
||||
(void)anchor;
|
||||
if (sidx > 0 && sidx < cf->cp_count && cf->cp[sidx].tag == CP_STRING) {
|
||||
const char *text = cp_utf8(cf, cf->cp[sidx].cls_or_str.index);
|
||||
draw_text_ttf(text, x, y);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (is(name, "drawArc") && is(desc, "(IIIIII)V")) {
|
||||
int arc_deg = stack[--(*sp)];
|
||||
int start = stack[--(*sp)];
|
||||
int h2 = stack[--(*sp)];
|
||||
int w2 = stack[--(*sp)];
|
||||
int y = stack[--(*sp)];
|
||||
int x = stack[--(*sp)];
|
||||
(*sp)--;
|
||||
|
||||
int cx = x + (w2 / 2);
|
||||
int cy = y + (h2 / 2);
|
||||
int rx = w2 / 2;
|
||||
int ry = h2 / 2;
|
||||
draw_arc_outline(cx, cy, rx, ry, start, arc_deg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* flushGraphics / repaint stubs — just present */
|
||||
if (is(name, "flushGraphics") || is(name, "repaint")) {
|
||||
/* pop whatever args there are, then 'this' */
|
||||
/* for safety just drain back to a known depth — caller handles */
|
||||
SDL_RenderPresent(g_ren);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* javax.microedition.lcdui.Canvas */
|
||||
|
||||
if (is(class_name, "javax/microedition/lcdui/Canvas")) {
|
||||
|
||||
/* Canvas.repaint() */
|
||||
if (is(name, "repaint") && is(desc, "()V")) {
|
||||
(*sp)--; /* pop 'this' */
|
||||
SDL_RenderPresent(g_ren);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Canvas.getGraphics() → Graphics (stub: return sentinel) */
|
||||
if (is(name, "getGraphics") &&
|
||||
is(desc, "()Ljavax/microedition/lcdui/Graphics;")) {
|
||||
(*sp)--; /* pop 'this' */
|
||||
stack[(*sp)++] = 0xC0FFEE; /* fake Graphics handle */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* java.io.PrintStream ─ */
|
||||
|
||||
if (is(class_name, "java/io/PrintStream")) {
|
||||
if (is(name, "println")) {
|
||||
if (is(desc, "(I)V")) {
|
||||
int val = stack[--(*sp)]; (*sp)--;
|
||||
printf("%d\n", val);
|
||||
return 1;
|
||||
}
|
||||
if (is(desc, "(Ljava/lang/String;)V")) {
|
||||
int str_idx = stack[--(*sp)]; (*sp)--;
|
||||
CPEntry *e = &cf->cp[str_idx];
|
||||
if (e->tag == CP_STRING)
|
||||
printf("%s\n", cp_utf8(cf, e->cls_or_str.index));
|
||||
return 1;
|
||||
}
|
||||
if (is(desc, "(Z)V")) {
|
||||
int val = stack[--(*sp)]; (*sp)--;
|
||||
printf("%s\n", val ? "true" : "false");
|
||||
return 1;
|
||||
}
|
||||
if (is(desc, "()V")) { (*sp)--; printf("\n"); return 1; }
|
||||
}
|
||||
if (is(name, "print")) {
|
||||
if (is(desc, "(I)V")) {
|
||||
int val = stack[--(*sp)]; (*sp)--;
|
||||
printf("%d", val);
|
||||
return 1;
|
||||
}
|
||||
if (is(desc, "(Ljava/lang/String;)V")) {
|
||||
int str_idx = stack[--(*sp)]; (*sp)--;
|
||||
CPEntry *e = &cf->cp[str_idx];
|
||||
if (e->tag == CP_STRING)
|
||||
printf("%s", cp_utf8(cf, e->cls_or_str.index));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* java.lang.Thread (minimal stubs) */
|
||||
|
||||
if (is(class_name, "java/lang/Thread")) {
|
||||
if (is(name, "sleep")) {
|
||||
long long ms = stack[--(*sp)];
|
||||
(*sp)--; /* long takes 2 slots */
|
||||
SDL_Delay((uint32_t)ms);
|
||||
return 1;
|
||||
}
|
||||
if (is(name, "start") || is(name, "join")) {
|
||||
(*sp)--;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* javax.microedition.midlet.MIDlet (common stubs) */
|
||||
|
||||
if (is(name, "getAppProperty") && is(desc, "(Ljava/lang/String;)Ljava/lang/String;")) {
|
||||
/* this, key -> null */
|
||||
(*sp) -= 2;
|
||||
stack[(*sp)++] = 0;
|
||||
return 1;
|
||||
}
|
||||
if (is(name, "notifyDestroyed") && is(desc, "()V")) {
|
||||
(*sp)--; /* this */
|
||||
return 1;
|
||||
}
|
||||
if (is(name, "notifyPaused") && is(desc, "()V")) {
|
||||
(*sp)--; /* this */
|
||||
return 1;
|
||||
}
|
||||
if (is(name, "platformRequest") && is(desc, "(Ljava/lang/String;)Z")) {
|
||||
(*sp) -= 2; /* this, url */
|
||||
stack[(*sp)++] = 0;
|
||||
return 1;
|
||||
}
|
||||
if (is(name, "checkPermission") && is(desc, "(Ljava/lang/String;)I")) {
|
||||
(*sp) -= 2; /* this, permission */
|
||||
stack[(*sp)++] = 1; /* allowed */
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0; /* not handled */
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef HEALPER_H
|
||||
#define HEALPER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "classfile.h"
|
||||
|
||||
typedef struct Object {
|
||||
CPEntry *cls;
|
||||
uint16_t fields_count;
|
||||
intptr_t *fields;
|
||||
struct Object *next_free;
|
||||
} Object;
|
||||
|
||||
#define OBJ_TO_SLOT(o) ((intptr_t)(uintptr_t)(o))
|
||||
#define SLOT_TO_OBJ(s) ((Object *)(uintptr_t)(intptr_t)(s))
|
||||
|
||||
int midp_native_call(ClassFile *cf, const char *class_name, const char *name, const char *desc, intptr_t *stack, int *sp, int opcode);
|
||||
void midp_sdl_init(int w, int h);
|
||||
void midp_sdl_wait(int milliseconds);
|
||||
void midp_sdl_destroy(void);
|
||||
|
||||
Object *heap_alloc(CPEntry *cls, uint16_t fields_count);
|
||||
void heap_free(Object *obj);
|
||||
void heap_destroy(void);
|
||||
|
||||
#endif
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,29 @@
|
||||
#include <stdio.h>
|
||||
#include "classfile.h"
|
||||
|
||||
int execute(ClassFile *cf, Method *m, int *args, int argc);
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "usage: %s <file.class>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* step 1 - load the class file */
|
||||
ClassFile *cf = classfile_load(argv[1]);
|
||||
if (!cf) return 1;
|
||||
|
||||
/* step 2 - find the main method */
|
||||
Method *m = classfile_find_method(cf, "main", "([Ljava/lang/String;)V");
|
||||
if (!m) m = classfile_find_method(cf, "startApp", "()V");
|
||||
if (!m) {
|
||||
fprintf(stderr, "no main or startApp method found\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* step 3 - execute it */
|
||||
execute(cf, m, NULL, 0);
|
||||
|
||||
classfile_free(cf);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user