#define _GNU_SOURCE #include #include #include "classfile.h" #include "helpers.h" #include #include #ifndef PATH_MAX #include #endif #include #include #include #include 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/ we don't have — just pop args+this */ f.sp -= nargs; if (loaded_cf) classfile_free(loaded_cf); break; } /* Guard against accidental self-resolution causing infinite recursion. */ if (target_cf == cf && target == m) { fprintf(stderr, "recursive invokespecial guard: %s.%s%s\n", class_name, name, desc); 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 \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, "", "()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; }