static int ndless_load(const char *docpath, NUC_FILE *docfile, void **base, int (**entry_address_ptr)(int, char*[])) { struct nuc_stat docstat; if (nuc_stat(docpath, &docstat)) { puts("ndless_load: can't open doc"); return 1; } void *docptr; if(emu_debug_alloc_ptr) { if(emu_debug_alloc_size < docstat.st_size) { puts("ndless_load: emu_debug_alloc_size too small!"); docptr = malloc(docstat.st_size); } else docptr = emu_debug_alloc_ptr; } else docptr = malloc(docstat.st_size); if (!docptr) { puts("ndless_load: can't malloc"); return 1; } if (!nuc_fread(docptr, docstat.st_size, 1, docfile)) { puts("ndless_load: can't read doc"); if (!emu_debug_alloc_ptr) free(docptr); return 1; } uint32_t *ptr32 = docptr, *ptr32_end = ptr32 + ((docstat.st_size / 4) < 512 ? (docstat.st_size / 4) : 512); while(ptr32 < ptr32_end) { // Found an embedded Zehn file if(*ptr32 == 0x6e68655a && *(ptr32 + 1) == 1) { nuc_fseek(docfile, (uint8_t*)(ptr32) - (uint8_t*)(docptr), SEEK_SET); int ret = zehn_load(docfile, base, entry_address_ptr); switch(ret) { case 0: // Execute as Zehn case 2: // Valid Zehn, but don't execute if(!emu_debug_alloc_ptr) free(docptr); return ret; case 1: // Invalid Zehn break; } } ptr32++; } *base = docptr; *entry_address_ptr = (int (*)(int argc, char *argv[]))(docptr + sizeof(PRGMSIG)); return 0; }
extern "C" int zehn_load(NUC_FILE *file, void **mem_ptr, int (**entry)(int,char*[]), bool *supports_hww) { Zehn_header header; // The Zehn file may not begin at the file start size_t file_start = nuc_ftell(file); if(nuc_fread(&header, sizeof(header), 1, file) != 1) return 1; if(header.signature != ZEHN_SIGNATURE || header.version != ZEHN_VERSION || header.file_size > header.alloc_size) { puts("[Zehn] This Zehn file is not supported!"); return 1; } Storage<Zehn_reloc> relocs(header.reloc_count); Storage<Zehn_flag> flags(header.flag_count); Storage<uint8_t> extra_data(header.extra_size); if(nuc_fread(reinterpret_cast<void*>(relocs.data), sizeof(Zehn_reloc), header.reloc_count, file) != header.reloc_count || nuc_fread(reinterpret_cast<void*>(flags.data), sizeof(Zehn_flag), header.flag_count, file) != header.flag_count || nuc_fread(reinterpret_cast<void*>(extra_data.data), 1, header.extra_size, file) != header.extra_size) { puts("[Zehn] File read failed!"); return 1; } size_t remaining_mem = header.alloc_size - nuc_ftell(file) + file_start, remaining_file = header.file_size - nuc_ftell(file) + file_start; if(emu_debug_alloc_ptr) { if(emu_debug_alloc_size < remaining_mem) { puts("[Zehn] emu_debug_alloc_size too small!"); *mem_ptr = malloc(remaining_mem); } else *mem_ptr = emu_debug_alloc_ptr; } else *mem_ptr = malloc(remaining_mem); uint8_t *base = reinterpret_cast<uint8_t*>(*mem_ptr); if(!base) { puts("[Zehn] Memory allocation failed!"); return 1; } if(relocs.data[0].type == Zehn_reloc_type::FILE_COMPRESSED) { if(relocs.data[0].offset != static_cast<int>(Zehn_compress_type::ZLIB)) { puts("[Zehn] Compression format not supported!"); return 1; } Storage<uint8_t> compressed(remaining_file); if(nuc_fread(compressed.data, remaining_file, 1, file) != 1) { puts("[Zehn] File read failed!"); return 1; } uLongf dest_len = remaining_mem; if(uncompress(base, &dest_len, compressed.data, remaining_file) != Z_OK) { puts("[Zehn] Decompression failed!"); return 1; } std::fill(base + dest_len, base + remaining_mem, 0); } else { if(nuc_fread(base, remaining_file, 1, file) != 1) { puts("[Zehn] File read failed!"); return 1; } // Fill rest with zeros (.bss and other NOBITS sections) std::fill(base + remaining_file, base + remaining_mem, 0); } const char *application_name = "(unknown)", *application_author = "(unknown)", *application_notice = "(no notice)"; unsigned int application_version = 1, ndless_version_min = 0, ndless_version_max = UINT_MAX, ndless_revision_min = 0, ndless_revision_max = UINT_MAX; // Iterate through each flag for(Zehn_flag &f : flags) { const char *ptr; switch(f.type) { case Zehn_flag_type::EXECUTABLE_NAME: if(!zehn_check_string(extra_data.begin(), f, 255, &application_name)) { puts("[Zehn] Invalid application name!"); return 1; } break; case Zehn_flag_type::EXECUTABLE_NOTICE: if(zehn_check_string(extra_data.begin(), f, 1024, &ptr)) application_notice = ptr; break; case Zehn_flag_type::EXECUTABLE_AUTHOR: if(zehn_check_string(extra_data.begin(), f, 128, &ptr)) application_author = ptr; break; case Zehn_flag_type::EXECUTABLE_VERSION: application_version = f.data; break; case Zehn_flag_type::NDLESS_VERSION_MIN: ndless_version_min = f.data; break; case Zehn_flag_type::NDLESS_REVISION_MIN: ndless_revision_min = f.data; break; case Zehn_flag_type::NDLESS_VERSION_MAX: ndless_version_max = f.data; break; case Zehn_flag_type::NDLESS_REVISION_MAX: ndless_revision_max = f.data; break; case Zehn_flag_type::RUNS_ON_COLOR: if(f.data == false && has_colors) { msgbox("Error", "The application %s doesn't support CX and CM calculators!", application_name); return 2; } break; case Zehn_flag_type::RUNS_ON_CLICKPAD: if(f.data == false && !is_touchpad) { msgbox("Error", "The application %s doesn't support clickpads!", application_name); return 2; } break; case Zehn_flag_type::RUNS_ON_TOUCHPAD: if(f.data == false && is_touchpad) { msgbox("Error", "The application %s doesn't support touchpads!", application_name); return 2; } break; case Zehn_flag_type::RUNS_ON_32MB: if(f.data == false && (!has_colors || is_cm)) { msgbox("Error", "The application %s requires more than 32MB of RAM!", application_name); return 2; } break; case Zehn_flag_type::RUNS_ON_HWW: *supports_hww = f.data; break; default: break; } } // Show some information about the executable if(isKeyPressed(KEY_NSPIRE_CAT)) { char info[1536]; sprintf(info, "Name: %s Version: %u\nAuthor: %s\nNotice: %s", application_name, application_version, application_author, application_notice); show_msgbox("Information about the executable", info); return 2; } if(NDLESS_VERSION < ndless_version_min || (NDLESS_VERSION == ndless_version_min && NDLESS_REVISION < ndless_revision_min)) { msgbox("Error", "The application %s requires at least ndless %d.%d.%d!", application_name, ndless_version_min / 10, ndless_version_min % 10, ndless_revision_min); return 2; } if(NDLESS_VERSION > ndless_version_max || (NDLESS_VERSION == ndless_version_max && NDLESS_REVISION > ndless_revision_max)) { if(ndless_revision_max != UINT_MAX) msgbox("Error", "The application %s requires ndless %d.%d.%d or older!", application_name, ndless_version_max / 10, ndless_version_max % 10, ndless_revision_max); else msgbox("Error", "The application %s requires ndless %d.%d or older!", application_name, ndless_version_max / 10, ndless_version_max % 10); return 2; } // Iterate through the reloc table for(Zehn_reloc &r : relocs) { if(r.offset >= remaining_mem) { puts("[Zehn] Wrong reloc in Zehn file!"); return 1; } // No alignment guaranteed! uint32_t *place = reinterpret_cast<uint32_t*>(base + r.offset); switch(r.type) { //Handled above case Zehn_reloc_type::FILE_COMPRESSED: break; case Zehn_reloc_type::UNALIGNED_RELOC: if(r.offset != 0) { printf("[Zehn] Unexpected UNALIGNED_RELOC value %lu!\n", r.offset); return 1; } break; case Zehn_reloc_type::ADD_BASE: wu32(place, ru32(place) + reinterpret_cast<uint32_t>(base)); break; case Zehn_reloc_type::ADD_BASE_GOT: { uint32_t u32; while((u32 = ru32(place)) != 0xFFFFFFFF) wu32(place++, u32 + reinterpret_cast<uint32_t>(base)); break; } case Zehn_reloc_type::SET_ZERO: wu32(place, 0); break; default: printf("[Zehn] Unsupported reloc %d!\n", static_cast<int>(r.type)); return 1; } } *entry = reinterpret_cast<int (*)(int,char*[])>(base + header.entry_offset); return 0; }
// Run a program. Returns 0xDEAD if can't run it or 0xBEEF if the error dialog should be skipped. Else returns the program return code. // If resident_ptr isn't NULL, the program's memory block isn't freed and is stored in resident_ptr. It may be freed later with ld_free(). // Resident program shouldn't use argv after returning. // argsn/args don't include the program path. args doesn't need to be NULL terminated. Can be 0/NULL. int ld_exec_with_args(const char *path, int argsn, char *args[], void **resident_ptr) { char prgm_path[FILENAME_MAX]; char doc_path[FILENAME_MAX]; // non const unsigned i; char **argv = NULL; char **argvptr; int argc; int ret; BOOL isassoc = FALSE; strcpy(doc_path, path); strcpy(prgm_path, path); // may deffer if using file association // File association char extbuf[FILENAME_MAX]; strcpy(extbuf, prgm_path); char *ext = strrchr(extbuf, '.'); if (!ext || ext == extbuf) { puts("ld_exec: can't find file extension"); return 0xDEAD; // shouldn't happen, all files have a .tns extension } *ext = '\0'; // keep the extension before .tns ext = strrchr(extbuf, '.'); unsigned pathlen = strlen(extbuf); // without '.' #define MAX_EXT_LEN 8 if (ext && extbuf + pathlen - ext <= (MAX_EXT_LEN+1) && extbuf + pathlen - ext > 1) { // looks like an extension cfg_open(); char ext_key[4 + MAX_EXT_LEN + 1]; // ext.extension strcpy(ext_key, "ext"); strcat(ext_key, ext); char *prgm_name_noext = cfg_get(ext_key); if (prgm_name_noext) { char prgm_name[FILENAME_MAX + 4]; strcpy(prgm_name, prgm_name_noext); strcat(prgm_name, ".tns"); struct assoc_file_each_cb_ctx context = {prgm_name, prgm_path, &isassoc}; file_each("/", assoc_file_each_cb, &context); } cfg_close(); } ld_bin_format = LD_ERROR_BIN; uint32_t signature; NUC_FILE *prgm = nuc_fopen(prgm_path, "rb"); if(nuc_fread(&signature, sizeof(signature), 1, prgm) != 1) { // empty file? nuc_fclose(prgm); return 0xDEAD; } nuc_fseek(prgm, 0, SEEK_SET); void *base = 0; int (*entry)(int argc, char *argv[]); switch(signature) { case 0x00475250: //"PRG\0" if((ret = ndless_load(prgm_path, prgm, &base, &entry)) == 0) { nuc_fclose(prgm); ld_bin_format = LD_PRG_BIN; break; } nuc_fclose(prgm); return ret == 1 ? 0xDEAD : 0xBEEF; case 0x544c4662: //"bFLT" if(bflt_load(prgm, &base, &entry) == 0) { nuc_fclose(prgm); ld_bin_format = LD_BFLT_BIN; break; } nuc_fclose(prgm); return 0xDEAD; case 0x6e68655a: //"Zehn" if((ret = zehn_load(prgm, &base, &entry)) == 0) { nuc_fclose(prgm); ld_bin_format = LD_ZEHN_BIN; break; } if(base && base != emu_debug_alloc_ptr) free(base); nuc_fclose(prgm); return ret == 1 ? 0xDEAD : 0xBEEF; default: nuc_fclose(prgm); return 0xDEAD; } int intmask = TCT_Local_Control_Interrupts(-1); /* TODO workaround: disable the interrupts to avoid the clock on the screen */ wait_no_key_pressed(); // let the user release the Enter key, to avoid being read by the program void *savedscr = malloc(SCREEN_BYTES_SIZE); if (!savedscr) { puts("ld_exec: can't malloc savedscr"); ret = 0xDEAD; goto ld_exec_with_args_quit; } memcpy(savedscr, (void*) SCREEN_BASE_ADDRESS, SCREEN_BYTES_SIZE); argc = 1 + argsn; if (isassoc) argc++; argv = malloc((argc + 1) * sizeof(char*)); if (!argv) { puts("ld_exec: can't malloc argv"); ret = 0xDEAD; goto ld_exec_with_args_quit; } argv[0] = prgm_path; argvptr = &argv[1]; if (isassoc) { argv[1] = doc_path; argvptr++; } if (args) memcpy(argvptr, args, argsn * sizeof(char*)); argv[argc] = NULL; if (has_colors) { volatile unsigned *palette = (volatile unsigned*)0xC0000200; for (i = 0; i < 16/2; i++) *palette++ = ((i * 2 + 1) << (1 + 16)) | ((i * 2 + 1) << (6 + 16)) | ((i * 2 + 1) << (11 + 16)) | ((i * 2) << 1) | ((i * 2) << 6) | ((i * 2) << 11); // set the grayscale palette ut_disable_watchdog(); // seems to be sometimes renabled by the OS } is_current_prgm_resident = FALSE; clear_cache(); ret = entry(argc, argv); /* run the program */ if (has_colors) lcd_incolor(); // in case not restored by the program if (!plh_noscrredraw) memcpy((void*) SCREEN_BASE_ADDRESS, savedscr, SCREEN_BYTES_SIZE); ld_exec_with_args_quit: free(savedscr); wait_no_key_pressed(); // let the user release the key used to exit the program, to avoid being read by the OS TCT_Local_Control_Interrupts(intmask); if (ret != 0xDEAD && resident_ptr) { *resident_ptr = base; return ret; } if (is_current_prgm_resident) // required by the program itself return ret; if (!emu_debug_alloc_ptr) free(base); free(argv); return ret; }