diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e4a354df64..41147bbb57 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -355,6 +355,12 @@ set(LVGL_SRC libs/lvgl/src/lv_widgets/lv_win.c ) +list(APPEND PAWN_SRC + pawn/amx.c + pawn/amxpool.c + pawn/heatshrink_decoder.c +) + list(APPEND IMAGE_FILES displayapp/icons/battery/batteryicon.c ) @@ -398,6 +404,7 @@ list(APPEND SOURCE_FILES displayapp/screens/Alarm.cpp displayapp/screens/Styles.cpp displayapp/screens/WeatherSymbols.cpp + displayapp/screens/Pawn.cpp displayapp/Colors.cpp displayapp/widgets/Counter.cpp displayapp/widgets/PageIndicator.cpp @@ -900,6 +907,47 @@ target_compile_options(lvgl PRIVATE $<$: ${ASM_FLAGS}> ) +# pawn +add_library(pawn STATIC ${PAWN_SRC}) +target_include_directories(pawn SYSTEM PUBLIC . ../) +target_include_directories(pawn SYSTEM PUBLIC ${INCLUDES_FROM_LIBS}) +target_compile_options(pawn PRIVATE + ${COMMON_FLAGS} + -DNDEBUG + -DAMX_ANSIONLY + -DAMX_NODYNALOAD + -DAMX_DONT_RELOCATE + + # -DAMX_ALIGN + # -DAMX_ALLOT + # -DAMX_DEFCALLBACK + -DAMX_CLEANUP + # -DAMX_CLONE + -DAMX_EXEC + # -DAMX_FLAGS + -DAMX_INIT + # -DAMX_MEMINFO + # -DAMX_NAMELENGTH + # -DAMX_NATIVEINFO + -DAMX_PUSHXXX + -DAMX_RAISEERROR + # -DAMX_REGISTER + -DAMX_SETCALLBACK + # -DAMX_SETDEBUGHOOK + # -DAMX_UTF8XXX + # -DAMX_XXXNATIVES + -DAMX_XXXPUBLICS + -DAMX_XXXPUBVARS + -DAMX_XXXSTRING + # -DAMX_XXXTAGS + # -DAMX_XXXUSERDATA + # -DAMX_VERIFYADDR + $<$: ${DEBUG_FLAGS}> + $<$: ${RELEASE_FLAGS}> + $<$: ${CXX_FLAGS}> + $<$: ${ASM_FLAGS}> + ) + # LITTLEFS_SRC add_library(littlefs STATIC ${LITTLEFS_SRC}) target_include_directories(littlefs SYSTEM PUBLIC . ../) @@ -918,7 +966,7 @@ set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld") add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES}) set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME}) -target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps) +target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl pawn littlefs infinitime_fonts infinitime_apps) target_compile_options(${EXECUTABLE_NAME} PUBLIC ${COMMON_FLAGS} ${WARNING_FLAGS} @@ -952,7 +1000,7 @@ set(IMAGE_MCUBOOT_FILE_NAME_BIN ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERS set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip) set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld") add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES}) -target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps) +target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl pawn littlefs infinitime_fonts infinitime_apps) set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME}) target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC ${COMMON_FLAGS} diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index 25926edc40..de1b35120a 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -4,6 +4,7 @@ #include "displayapp/screens/Alarm.h" #include "displayapp/screens/Dice.h" +#include "displayapp/screens/Pawn.h" #include "displayapp/screens/Timer.h" #include "displayapp/screens/Twos.h" #include "displayapp/screens/Tile.h" diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index d440b598d1..93928c28eb 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -31,6 +31,7 @@ namespace Pinetime { Dice, Weather, PassKey, + Pawn, QuickSettings, Settings, SettingWatchFace, diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index 93196ed6a0..3dcfb7a5eb 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -2,6 +2,7 @@ if(DEFINED ENABLE_USERAPPS) set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware") else () set(DEFAULT_USER_APP_TYPES "Apps::StopWatch") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Pawn") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Alarm") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Timer") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Steps") diff --git a/src/displayapp/screens/Pawn.cpp b/src/displayapp/screens/Pawn.cpp new file mode 100644 index 0000000000..5266ae2303 --- /dev/null +++ b/src/displayapp/screens/Pawn.cpp @@ -0,0 +1,723 @@ +#include "Pawn.h" +#include +#include + +#include "components/heartrate/HeartRateController.h" + +using namespace Pinetime::Applications::Screens; + +#define LOG_PREFIX "[Pawn] " + +enum { + PAWN_ERR_PARAMCOUNT = 100, + PAWN_ERR_MISSINGHANDLER, + PAWN_ERR_INVALIDSTRING, + PAWN_ERR_INVALIDSETTING, + PAWN_ERR_INVALIDTRAMPOLINE, + PAWN_ERR_FILE, + + PAWN_ERR_FIRST = PAWN_ERR_PARAMCOUNT, +}; + +#define ASSERT_PARAMS(n) \ + if (params[0] != n * sizeof(cell)) { \ + amx_RaiseError(amx, PAWN_ERR_PARAMCOUNT); \ + return 0; \ + } + +#define PARAMS_OBJ(i) ((lv_obj_t*) params[i]) + +#define PAWN_INST ((Pawn*) amx->userdata[0]) + +constexpr int max_overlay_size = 2048; + +static void label_set_text(AMX* amx, lv_obj_t* label, cell str) { + char* text; + amx_StrParam_Type(amx, str, text, char*); + if (text != NULL) + lv_label_set_text(label, text); + else + lv_label_set_text_static(label, ""); +} + +static void event_handler(lv_obj_t* obj, lv_event_t event) { + if (event == LV_EVENT_DELETE) + return; + + AMX* amx = (AMX*) lv_obj_get_user_data(lv_scr_act()); + int handler_index = (int) lv_obj_get_user_data(obj); + + if (PAWN_INST->is_errored) + return; + + amx_Push(amx, event); + int result = amx_Exec(amx, nullptr, handler_index); + if (result != AMX_ERR_NONE) { + PAWN_INST->QueueError(result); + } +} + +static cell AMX_NATIVE_CALL F_lv_label_create(AMX* amx, const cell* params) { + ASSERT_PARAMS(3); + + lv_obj_t* label = lv_label_create(PARAMS_OBJ(1) ?: lv_scr_act(), PARAMS_OBJ(2)); + label_set_text(amx, label, params[3]); + + return (cell) label; +} + +static cell AMX_NATIVE_CALL F_lv_obj_set_event_cb(AMX* amx, const cell* params) { + ASSERT_PARAMS(2); + + lv_obj_t* obj = PARAMS_OBJ(1); + + char* name; + amx_StrParam_Type(amx, params[2], name, char*); + if (name != NULL) { + int index; + if (amx_FindPublic(amx, name, &index) == AMX_ERR_NONE) { + lv_obj_set_user_data(obj, (void*) index); + lv_obj_set_event_cb(obj, event_handler); + } else { + amx_RaiseError(amx, PAWN_ERR_MISSINGHANDLER); + } + } + + return 0; +} + +static cell AMX_NATIVE_CALL F_lv_label_set_text(AMX* amx, const cell* params) { + ASSERT_PARAMS(2); + + label_set_text(amx, PARAMS_OBJ(1), params[2]); + return 0; +} + +static cell AMX_NATIVE_CALL F_lv_obj_set_style_local_int(AMX* amx, const cell* params) { + ASSERT_PARAMS(5); + + lv_obj_t* obj = PARAMS_OBJ(1); + cell prop = params[2]; + cell value = params[3]; + cell part = params[4]; + cell state = params[5]; + + _lv_obj_set_style_local_int(obj, part, prop | (state << LV_STYLE_STATE_POS), value); + return 0; +} + +static cell AMX_NATIVE_CALL F_lv_obj_set_style_local_color(AMX* amx, const cell* params) { + ASSERT_PARAMS(5); + + lv_obj_t* obj = PARAMS_OBJ(1); + cell prop = params[2]; + cell value = params[3]; + cell part = params[4]; + cell state = params[5]; + + _lv_obj_set_style_local_color(obj, part, prop | (state << LV_STYLE_STATE_POS), lv_color_hex(value)); + return 0; +} + +static cell AMX_NATIVE_CALL F_lv_obj_set_style_local_opa(AMX* amx, const cell* params) { + ASSERT_PARAMS(5); + + lv_obj_t* obj = PARAMS_OBJ(1); + cell prop = params[2]; + cell value = params[3]; + cell part = params[4]; + cell state = params[5]; + + _lv_obj_set_style_local_opa(obj, part, prop | (state << LV_STYLE_STATE_POS), value); + return 0; +} + +static cell AMX_NATIVE_CALL F_lv_obj_set_style_local_ptr(AMX* amx, const cell* params) { + ASSERT_PARAMS(5); + + lv_obj_t* obj = PARAMS_OBJ(1); + cell prop = params[2]; + cell* value = amx_Address(amx, params[3]); + cell part = params[4]; + cell state = params[5]; + + _lv_obj_set_style_local_ptr(obj, part, prop | (state << LV_STYLE_STATE_POS), (void*) value); + return 0; +} + +/** + * Hand-written implementation of sprintf with limited functionality in order to support reading strings from parameters. + * Supported interpolations: + * %% + * %d and %x, including padding flags + * %s with packed strings + */ +static cell AMX_NATIVE_CALL F_sprintf(AMX* amx, const cell* params) { + // param[0] is the number of total parameter bytes, divide it by cell size to get the parameter count + int args_count = params[0] / sizeof(cell); + if (args_count < 4) { + amx_RaiseError(amx, PAWN_ERR_PARAMCOUNT); + return 0; + } + + cell* output = amx_Address(amx, params[1]); + cell max_size = params[2] * sizeof(cell); // We assume the output array is packed so each cell contains one character per byte + // TODO: add a separate sprintf_unpacked function? + + char buf[max_size]; + char* bufc = buf; + char* bufmax = buf + max_size - 1; + + char* fmt; + amx_StrParam_Type(amx, params[3], fmt, char*); + if (fmt == NULL) { + amx_RaiseError(amx, PAWN_ERR_INVALIDSTRING); + return 0; + } + + size_t paramc = 4; + + size_t fmt_len = strlen(fmt); + bool in_pc = false; + + char flags[4]; + size_t flagc = 0; + + for (size_t i = 0; i < fmt_len; i++) { + char c = fmt[i]; + + if (c == '%') { + if (in_pc) { + *bufc++ = '%'; + in_pc = false; + } else { + flagc = 0; + in_pc = true; + } + } else if (in_pc) { + switch (c) { + case 'x': + case 'd': { + int padding = 0; + char pad = ' '; + + if (flagc == 1) { + padding = flags[0] - '0'; + } else if (flagc == 2) { + pad = flags[0]; + padding = flags[1] - '0'; + } + + std::to_chars_result result = std::to_chars(bufc, bufmax, (int) *amx_Address(amx, params[paramc++]), c == 'x' ? 16 : 10); + + int padlen = padding - (result.ptr - bufc); + for (int n = 0; n < padlen && bufc < bufmax; n++) { + bufc[1] = bufc[0]; + bufc[0] = pad; + bufc++; + } + + bufc = result.ptr + (padlen > 0 ? padlen : 0); + in_pc = false; + break; + } + + case 's': { + cell param = params[paramc++]; + int len; + amx_StrLen(amx_Address(amx, param), &len); + + if (len > 0 && bufc + len < bufmax - 1) { + amx_GetString(bufc, amx_Address(amx, param), 0, len + 1); + bufc += len; + } + in_pc = false; + break; + } + + default: + if (flagc < sizeof(flags)) + flags[flagc++] = c; + break; + } + } else { + *bufc++ = c; + } + } + *bufc = 0; + + amx_SetString(output, buf, 1, 0, max_size); + + return bufc - buf; +} + +cell AMX_NATIVE_CALL F_read_datetime(AMX* amx, const cell* params) { + ASSERT_PARAMS(1); + + Pawn* pawn = PAWN_INST; + + pawn->currentDateTime = std::chrono::time_point_cast(pawn->controllers.dateTimeController.CurrentDateTime()); + + cell* ret = amx_Address(amx, params[1]); + cell data[] = { + pawn->controllers.dateTimeController.Minutes(), + pawn->controllers.dateTimeController.Hours(), + pawn->controllers.dateTimeController.Day(), + pawn->controllers.dateTimeController.Year(), + }; + + if (memcmp(ret, data, sizeof(data)) == 0) { + return 0; + } + memcpy(ret, data, sizeof(data)); + return 1; +} + +static cell AMX_NATIVE_CALL F_read_datetime_short_str(AMX* amx, const cell* params) { + ASSERT_PARAMS(2); + + Pawn* pawn = PAWN_INST; + + cell* ret_day = amx_Address(amx, params[1]); + cell* ret_month = amx_Address(amx, params[2]); + + if (ret_day != NULL) { + const char* day = pawn->controllers.dateTimeController.DayOfWeekShortToString(); + amx_SetString(ret_day, day, true, false, 4); + } + if (ret_month != NULL) { + const char* month = pawn->controllers.dateTimeController.MonthShortToString(); + amx_SetString(ret_month, month, true, false, 4); + } + + return 0; +} + +static cell AMX_NATIVE_CALL F_status_icons_create(AMX* amx, const cell*) { + Pawn* pawn = PAWN_INST; + + if (!pawn->statusIcons) { + pawn->statusIcons = std::make_unique(pawn->controllers.batteryController, + pawn->controllers.bleController, + pawn->controllers.alarmController); + pawn->statusIcons->Create(); + } + + return 0; +} + +static cell AMX_NATIVE_CALL F_status_icons_update(AMX* amx, const cell*) { + Pawn* pawn = PAWN_INST; + + if (pawn->statusIcons != nullptr) + pawn->statusIcons->Update(); + + return 0; +} + +static cell AMX_NATIVE_CALL F_has_new_notifications(AMX* amx, const cell*) { + return PAWN_INST->controllers.notificationManager.AreNewNotificationsAvailable(); +} + +static cell AMX_NATIVE_CALL F_get_setting(AMX* amx, const cell* params) { + ASSERT_PARAMS(1); + +#define SETTING(n, m) \ + case n: \ + return (cell) PAWN_INST->controllers.settingsController.m(); + + switch (params[1]) { + SETTING(0, GetWatchFace) + SETTING(1, GetChimeOption) + SETTING(2, GetPrideFlag) + SETTING(3, GetClockType) + SETTING(4, GetWeatherFormat) + SETTING(5, GetNotificationStatus) + SETTING(6, GetStepsGoal) + + default: + amx_RaiseError(amx, PAWN_ERR_INVALIDSETTING); + return 0; + } + +#undef SETTING +} + +static cell AMX_NATIVE_CALL F_get_heartrate(AMX* amx, const cell*) { + return PAWN_INST->controllers.heartRateController.HeartRate(); +} + +static cell AMX_NATIVE_CALL F_get_heartrate_state(AMX* amx, const cell*) { + return (cell) PAWN_INST->controllers.heartRateController.State(); +} + +static cell AMX_NATIVE_CALL F_get_step_number(AMX* amx, const cell*) { + return PAWN_INST->controllers.motionController.NbSteps(); +} + +static cell AMX_NATIVE_CALL F_raise_error(AMX* amx, const cell* params) { + ASSERT_PARAMS(1); + + amx_RaiseError(amx, params[1]); + + return 0; +} + +static const uintptr_t natives[] = { + // Indices start at -1000 + (uintptr_t) lv_label_create, + (uintptr_t) lv_btn_create, + (uintptr_t) lv_obj_set_pos, + (uintptr_t) lv_obj_set_size, + (uintptr_t) lv_obj_align, + (uintptr_t) lv_obj_realign, +}; + +static const AMX_NATIVE lvgl_proxys[] = { + // Indices start at -3000 + F_lv_label_create, + F_lv_obj_set_event_cb, + F_lv_label_set_text, + F_lv_obj_set_style_local_int, + F_lv_obj_set_style_local_color, + F_lv_obj_set_style_local_opa, + F_lv_obj_set_style_local_ptr, +}; + +static const AMX_NATIVE pawn_proxys[] = { + // Indices start at -2000 + F_sprintf, + F_read_datetime, + F_read_datetime_short_str, + F_status_icons_create, + F_status_icons_update, + F_has_new_notifications, + F_get_setting, + F_get_heartrate, + F_get_heartrate_state, + F_get_step_number, + F_raise_error, +}; + +static cell trampoline(unsigned int index, const cell* params) { + int param_count = params[0] / sizeof(cell); + + if (index >= sizeof(natives) / sizeof(natives[0])) + return PAWN_ERR_INVALIDTRAMPOLINE; + + uintptr_t addr = natives[index] | 1; // Set lowest bit to enable thumb mode (always must be enabled on ARMv7-M) + + cell ret; + + params++; // Skip parameter count + + asm volatile(".syntax unified\n" + ".thumb\n" + + // Save stack pointer in case we push excess arguments into stack + "mov r5, sp\n" + + // Load first argument into R0 or jump out + "subs %[count], #1\n" + "bmi call\n" + "ldr r0, [%[params]]\n" + "add %[params], #4\n" + + // Load second argument into R1 or jump out + "subs %[count], #1\n" + "bmi call\n" + "ldr r1, [%[params]]\n" + "add %[params], #4\n" + + // Load third argument into R2 or jump out + "subs %[count], #1\n" + "bmi call\n" + "ldr r2, [%[params]]\n" + "add %[params], #4\n" + + // Load fourth argument into R3 or jump out + "subs %[count], #1\n" + "bmi call\n" + "ldr r3, [%[params]]\n" + "add %[params], #4\n" + + // Push remaining argument into stack in reverse order + "loop: subs %[count], #1\n" + " bmi call\n" + " ldr r12, [%[params], +%[count], LSL 2]\n" + " push {r12}\n" + " b loop\n" + + "call: blx %[addr]\n" // Call function + " mov sp, r5\n" // Restore stack + " mov %[ret], r0\n" // Move returned value into ret + + : [ret] "=r"(ret), [count] "+r"(param_count), [params] "+r"(params) + : [addr] "r"(addr) + : "r0", "r1", "r2", "r3", "r5", "r12", "lr", "memory"); + + return ret; +} + +static int AMXAPI prun_Overlay(AMX* amx, int index) { + AMX_HEADER* hdr; + AMX_OVERLAYINFO* tbl; + + hdr = (AMX_HEADER*) amx->base; + tbl = (AMX_OVERLAYINFO*) (amx->base + hdr->overlays) + index; + + amx->codesize = tbl->size; + amx->code = (unsigned char*) amx_poolfind(&PAWN_INST->amx_pool, index); + + if (amx->code == NULL) { + NRF_LOG_INFO(LOG_PREFIX "Reading overlay %d, %d bytes", index, tbl->size); + + if ((amx->code = (unsigned char*) amx_poolalloc(&PAWN_INST->amx_pool, tbl->size, index)) == NULL) + return AMX_ERR_OVERLAY; + + if (PAWN_INST->file->Read(amx->code, tbl->size, hdr->cod + tbl->offset) < (size_t) tbl->size) + return PAWN_ERR_FILE; + } + + return AMX_ERR_NONE; +} + +static int AMXAPI prun_Callback(struct tagAMX* amx, cell index, cell* result, const cell* params) { + amx->error = AMX_ERR_NONE; + + if (index <= -3000) { // LVGL proxys + *result = lvgl_proxys[-(index + 3000)](amx, params); + } else if (index <= -2000) { // InfiniTime proxys + *result = pawn_proxys[-(index + 2000)](amx, params); + } else { // Direct trampolines + *result = trampoline(-(index + 1000), params); + } + + return amx->error; +} + +int Pawn::LoadProgram() { + NRF_LOG_INFO(LOG_PREFIX "Loading program"); + + int result; + AMX_HEADER hdr; + if (file->Read((uint8_t*) &hdr, sizeof(hdr), 0) < sizeof(hdr)) + return PAWN_ERR_FILE; + + if (hdr.magic != AMX_MAGIC) + return AMX_ERR_FORMAT; + + memset(&amx, 0, sizeof(amx)); + amx.userdata[0] = this; + + header = std::make_unique(hdr.cod); + if (header == NULL) + return AMX_ERR_MEMORY; + + if (file->Read((uint8_t*) header.get(), hdr.cod, 0) < (size_t) hdr.cod) + return PAWN_ERR_FILE; + + datablock = std::make_unique(hdr.stp - hdr.dat); // This block contains data, heap and stack + if (datablock == NULL) + return AMX_ERR_MEMORY; + + if (file->Read((uint8_t*) datablock.get(), hdr.hea - hdr.dat, hdr.dat) < (size_t) hdr.hea - hdr.dat) + return PAWN_ERR_FILE; + + amx.data = (unsigned char*) datablock.get(); + + if (hdr.flags & AMX_FLAG_OVERLAY) { + NRF_LOG_INFO(LOG_PREFIX "Program has overlays"); + + constexpr int overlaypool_overhead = 8; + overlaypool = std::make_unique(max_overlay_size + overlaypool_overhead); + if (overlaypool == NULL) + return AMX_ERR_MEMORY; + + amx_poolinit(&amx_pool, overlaypool.get(), max_overlay_size + overlaypool_overhead); + + amx.overlay = prun_Overlay; + + result = amx_Init(&amx, header.get()); + } else { + NRF_LOG_INFO(LOG_PREFIX "Program doesn't have overlays"); + + amx.flags |= AMX_FLAG_DSEG_INIT; + + // Happy path: the file is backed by a const array and we can reference it directly + const uint8_t* code = file->GetConst(); + + if (code == nullptr || true) { + // Slow path: we must read the whole file into memory + NRF_LOG_INFO(LOG_PREFIX "Loading program into RAM"); + + filecode = std::make_unique(hdr.size); + if (file->Read(filecode.get(), hdr.size, 0) < (size_t) hdr.size) + return PAWN_ERR_FILE; + + code = filecode.get(); + } else { + NRF_LOG_INFO(LOG_PREFIX "Loaded program from constant") + } + + result = amx_Init(&amx, (void*) code); + } + + return result; +} + +#include "program.h" + +Pawn::Pawn(AppControllers& controllers) : Pawn(controllers, std::make_unique(controllers.filesystem, "/flash.amx")) { +} + +Pawn::Pawn(AppControllers& controllers, std::unique_ptr file) : controllers(controllers), file(std::move(file)) { + int result = LoadProgram(); + if (result != AMX_ERR_NONE) { + ShowError(result); + return; + } + + lv_obj_set_user_data(lv_scr_act(), &amx); + + amx_SetCallback(&amx, prun_Callback); + + cell* var; + if (amx_FindPubVar(&amx, "font_jmec", &var) == AMX_ERR_NONE) + *var = (cell) &jetbrains_mono_extrabold_compressed; + if (amx_FindPubVar(&amx, "screen", &var) == AMX_ERR_NONE) + *var = (cell) lv_scr_act(); + + result = amx_Exec(&amx, NULL, AMX_EXEC_MAIN); + if (result != AMX_ERR_NONE) { + ShowError(result); + return; + } + + if (amx_FindPublic(&amx, "@refresh", &refresh_index) == AMX_ERR_NONE) { + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); + } + if (amx_FindPublic(&amx, "@touch", &touch_index) != AMX_ERR_NONE) { + touch_index = -1; + } + if (amx_FindPublic(&amx, "@gesture", &gesture_index) != AMX_ERR_NONE) { + gesture_index = -1; + } +} + +void Pawn::CleanUI() { + if (taskRefresh) { + lv_task_del(taskRefresh); + taskRefresh = nullptr; + } + + statusIcons.reset(); + + lv_obj_clean(lv_scr_act()); +} + +Pawn::~Pawn() { + CleanUI(); + + amx_Cleanup(&amx); +} + +void Pawn::Refresh() { + int result = amx_Exec(&amx, NULL, refresh_index); + if (result != AMX_ERR_NONE) { + ShowError(result); + } +} + +void Pawn::ShowError(unsigned int amx_err) { + static const char* amx_err_msgs[] = { + nullptr, "EXIT", "ASSERT", "STACKERR", "BOUNDS", "MEMACCESS", "INVINSTR", "STACKLOW", "HEAPLOW", "CALLBACK", + "NATIVE", "DIVIDE", "SLEEP", "INVSTATE", nullptr, nullptr, "MEMORY", "FORMAT", "VERSION", "NOTFOUND", + "INDEX", "DEBUG", "INIT", "USERDATA", "INIT_JIT", "PARAMS", "DOMAIN", "GENERAL", "OVERLAY", + }; + static const char* pawn_err_msgs[] = { + "parameter count mismatch", // PAWN_ERR_PARAMCOUNT + "missing event handler", // PAWN_ERR_MISSINGHANDLER + "invalid string", // PAWN_ERR_INVALIDSTRING + "invalid setting", // PAWN_ERR_INVALIDSETTING + "invalid trampoline", // PAWN_ERR_INVALIDTRAMPOLINE + "file read error", // PAWN_ERR_FILE + }; + + if (amx_err == AMX_ERR_EXIT) { + running = false; + return; + } + + if (amx_err > 0 && amx_err < PAWN_ERR_FIRST && amx_err < sizeof(amx_err_msgs) / sizeof(*amx_err_msgs)) { + ShowError(amx_err_msgs[amx_err]); + } else if (amx_err >= PAWN_ERR_FIRST && amx_err - PAWN_ERR_FIRST < sizeof(pawn_err_msgs) / sizeof(*amx_err_msgs)) { + ShowError(pawn_err_msgs[amx_err - PAWN_ERR_FIRST]); + } else { + char msg[25]; + snprintf(msg, sizeof(msg), "unknown error %d", amx_err); + ShowError(msg); + } +} + +void Pawn::ShowError(const char* msg) { + is_errored = true; + CleanUI(); + + lv_obj_t* msglbl = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(msglbl, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_make(255, 0, 0)); + lv_label_set_long_mode(msglbl, LV_LABEL_LONG_BREAK); + lv_label_set_text_fmt(msglbl, "Execution aborted:\n%s\n\nCIP: 0x%X", msg, amx.cip); + lv_obj_set_width(msglbl, 240); + lv_label_set_align(msglbl, LV_LABEL_ALIGN_CENTER); + lv_obj_align(msglbl, NULL, LV_ALIGN_CENTER, 0, 0); +} + +void Pawn::QueueError(unsigned int amx_err) { + if (this->queued_error != 0) + return; + + this->queued_error = amx_err; + lv_async_call( + [](void* user_data) { + Pawn* pawn = static_cast(user_data); + pawn->ShowError(pawn->queued_error); + }, + this); +} + +bool Pawn::OnTouchEvent(TouchEvents event) { + if (gesture_index < 0 || is_errored) + return false; + + cell ret; + + amx_Push(&amx, (cell) event); + + int result = amx_Exec(&amx, &ret, gesture_index); + if (result != AMX_ERR_NONE) { + ShowError(result); + return true; + } + + return ret != 0; +} + +bool Pawn::OnTouchEvent(uint16_t x, uint16_t y) { + if (touch_index < 0 || is_errored) + return false; + + cell ret; + + amx_Push(&amx, y); + amx_Push(&amx, x); + + int result = amx_Exec(&amx, &ret, touch_index); + if (result != AMX_ERR_NONE) { + ShowError(result); + return true; + } + + return ret != 0; +} diff --git a/src/displayapp/screens/Pawn.h b/src/displayapp/screens/Pawn.h new file mode 100644 index 0000000000..5af495066b --- /dev/null +++ b/src/displayapp/screens/Pawn.h @@ -0,0 +1,200 @@ +#pragma once + +#include "displayapp/apps/Apps.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/Controllers.h" +#include "components/datetime/DateTimeController.h" +#include "utility/DirtyValue.h" +#include "displayapp/widgets/StatusIcons.h" +#include + +#include "pawn/amx.h" +#include "pawn/amxpool.h" +#include "pawn/heatshrink_decoder.h" + +namespace Pinetime { + namespace Applications { + namespace Screens { + + class Pawn : public Screen { + public: + struct File { + virtual ~File() = default; + + virtual const uint8_t* GetConst() { + return nullptr; + } + + virtual void Seek(size_t position) = 0; + virtual size_t Read(uint8_t* buffer, size_t size) = 0; + + size_t Read(uint8_t* buffer, size_t size, size_t position) { + Seek(position); + return Read(buffer, size); + } + }; + + class ConstFile : public File { + const uint8_t* backing; + size_t size, position; + + public: + ConstFile(const uint8_t* backing, size_t size) : backing(backing), size(size) { + } + + const uint8_t* GetConst() override { + return backing; + } + + void Seek(size_t position) override { + this->position = position; + } + + size_t Read(uint8_t* buffer, size_t size) override { + if (position >= this->size) + return 0; + if (position + size > this->size) + size = this->size - position; + memcpy(buffer, backing + position, size); + position += size; + return size; + } + }; + + class LfsFile : public File { + Controllers::FS& fs; + lfs_file_t file; + bool ok; + + public: + LfsFile(Controllers::FS& fs, const char* path) : fs(fs) { + ok = fs.FileOpen(&file, path, LFS_O_RDONLY) == LFS_ERR_OK; + } + + ~LfsFile() override { + if (ok) + fs.FileClose(&file); + } + + void Seek(size_t position) override { + fs.FileSeek(&file, position); + } + + size_t Read(uint8_t* buffer, size_t size) override { + if (!ok) + return 0; + return fs.FileRead(&file, buffer, size); + } + }; + + class HeatshrinkFile : public File { + std::unique_ptr inner; + heatshrink_decoder decoder; + + size_t real_pos; + uint8_t pending_inner_read[100]; + size_t pending_pos = 0, pending_size = 0; + + void Reset() { + heatshrink_decoder_reset(&decoder); + pending_size = 0; + pending_pos = 0; + real_pos = 0; + inner->Seek(0); + } + + public: + HeatshrinkFile(std::unique_ptr inner) : inner(std::move(inner)) { + Reset(); + } + + /** + * Seek to a specified position in the *uncompressed* file. + */ + void Seek(size_t position) override { + if (position < this->real_pos) // We have to rewind + Reset(); + + uint8_t discard[50]; + while (this->real_pos < position) { + size_t remaining = position - this->real_pos; + Read(discard, remaining > sizeof(discard) ? sizeof(discard) : remaining); + } + } + + size_t Read(uint8_t* buffer, size_t size) override { + size_t actual_read, total_read = 0; + + while (total_read < size) { + HSD_poll_res res = heatshrink_decoder_poll(&decoder, buffer + total_read, size - total_read, &actual_read); + total_read += actual_read; + real_pos += actual_read; + + if (res == HSDR_POLL_EMPTY) { + if (pending_size == 0) { + pending_size = inner->Read(pending_inner_read, sizeof(pending_inner_read)); + pending_pos = 0; + } + + heatshrink_decoder_sink(&decoder, pending_inner_read + pending_pos, pending_size, &actual_read); + pending_size -= actual_read; + pending_pos += actual_read; + } + } + + return total_read; + } + }; + + Pawn(AppControllers& controllers); + Pawn(AppControllers& controllers, std::unique_ptr file); + ~Pawn() override; + + void Refresh() override; + + void QueueError(unsigned int amx_err); + void ShowError(unsigned int amx_err); + void ShowError(const char* msg); + + bool OnTouchEvent(TouchEvents event) override; + bool OnTouchEvent(uint16_t x, uint16_t y) override; + + Utility::DirtyValue> currentDateTime {}; + AppControllers& controllers; + + std::unique_ptr statusIcons; + + amxPool amx_pool; + std::unique_ptr file; + + bool is_errored = false; + + private: + AMX amx; + + int refresh_index, touch_index, gesture_index; + lv_task_t* taskRefresh = 0; + unsigned int queued_error = 0; + + std::unique_ptr header, datablock, overlaypool, filecode; + + int LoadProgram(); + void CleanUI(); + }; + } + + template <> + struct AppTraits { + static constexpr Apps app = Apps::Pawn; + static constexpr const char* icon = "P"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Pawn(controllers); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; + }; + } +} diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp index a037abfe16..4903909c56 100644 --- a/src/displayapp/screens/WatchFaceDigital.cpp +++ b/src/displayapp/screens/WatchFaceDigital.cpp @@ -1,194 +1,9 @@ #include "displayapp/screens/WatchFaceDigital.h" -#include -#include - -#include "displayapp/screens/NotificationIcon.h" -#include "displayapp/screens/Symbols.h" -#include "displayapp/screens/WeatherSymbols.h" -#include "components/battery/BatteryController.h" -#include "components/ble/BleController.h" -#include "components/ble/NotificationManager.h" -#include "components/heartrate/HeartRateController.h" -#include "components/motion/MotionController.h" -#include "components/ble/SimpleWeatherService.h" -#include "components/settings/Settings.h" +#include "watchface_digital.h" +#include "WatchFaceDigital.h" using namespace Pinetime::Applications::Screens; -WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, - const Controllers::Battery& batteryController, - const Controllers::Ble& bleController, - const Controllers::AlarmController& alarmController, - Controllers::NotificationManager& notificationManager, - Controllers::Settings& settingsController, - Controllers::HeartRateController& heartRateController, - Controllers::MotionController& motionController, - Controllers::SimpleWeatherService& weatherService) - : currentDateTime {{}}, - dateTimeController {dateTimeController}, - notificationManager {notificationManager}, - settingsController {settingsController}, - heartRateController {heartRateController}, - motionController {motionController}, - weatherService {weatherService}, - statusIcons(batteryController, bleController, alarmController) { - - statusIcons.Create(); - - notificationIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_LIME); - lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false)); - lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); - - weatherIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); - lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); - lv_label_set_text(weatherIcon, ""); - lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 50); - lv_obj_set_auto_realign(weatherIcon, true); - - temperature = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); - lv_label_set_text(temperature, ""); - lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 50); - - label_date = lv_label_create(lv_scr_act(), nullptr); - lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60); - lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); - - label_time = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed); - - lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0); - - label_time_ampm = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_text_static(label_time_ampm, ""); - lv_obj_align(label_time_ampm, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -30, -55); - - heartbeatIcon = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat); - lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B)); - lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); - - heartbeatValue = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B)); - lv_label_set_text_static(heartbeatValue, ""); - lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0); - - stepValue = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7)); - lv_label_set_text_static(stepValue, "0"); - lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); - - stepIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7)); - lv_label_set_text_static(stepIcon, Symbols::shoe); - lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); - - taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); - Refresh(); -} - -WatchFaceDigital::~WatchFaceDigital() { - lv_task_del(taskRefresh); - lv_obj_clean(lv_scr_act()); -} - -void WatchFaceDigital::Refresh() { - statusIcons.Update(); - - notificationState = notificationManager.AreNewNotificationsAvailable(); - if (notificationState.IsUpdated()) { - lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); - } - - currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); - - if (currentDateTime.IsUpdated()) { - uint8_t hour = dateTimeController.Hours(); - uint8_t minute = dateTimeController.Minutes(); - - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - char ampmChar[3] = "AM"; - if (hour == 0) { - hour = 12; - } else if (hour == 12) { - ampmChar[0] = 'P'; - } else if (hour > 12) { - hour = hour - 12; - ampmChar[0] = 'P'; - } - lv_label_set_text(label_time_ampm, ampmChar); - lv_label_set_text_fmt(label_time, "%2d:%02d", hour, minute); - lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0); - } else { - lv_label_set_text_fmt(label_time, "%02d:%02d", hour, minute); - lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - } - - currentDate = std::chrono::time_point_cast(currentDateTime.Get()); - if (currentDate.IsUpdated()) { - uint16_t year = dateTimeController.Year(); - uint8_t day = dateTimeController.Day(); - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) { - lv_label_set_text_fmt(label_date, - "%s %d %s %d", - dateTimeController.DayOfWeekShortToString(), - day, - dateTimeController.MonthShortToString(), - year); - } else { - lv_label_set_text_fmt(label_date, - "%s %s %d %d", - dateTimeController.DayOfWeekShortToString(), - dateTimeController.MonthShortToString(), - day, - year); - } - lv_obj_realign(label_date); - } - } - - heartbeat = heartRateController.HeartRate(); - heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped; - if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) { - if (heartbeatRunning.Get()) { - lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B)); - lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get()); - } else { - lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B)); - lv_label_set_text_static(heartbeatValue, ""); - } - - lv_obj_realign(heartbeatIcon); - lv_obj_realign(heartbeatValue); - } - - stepCount = motionController.NbSteps(); - if (stepCount.IsUpdated()) { - lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); - lv_obj_realign(stepValue); - lv_obj_realign(stepIcon); - } - - currentWeather = weatherService.Current(); - if (currentWeather.IsUpdated()) { - auto optCurrentWeather = currentWeather.Get(); - if (optCurrentWeather) { - int16_t temp = optCurrentWeather->temperature.Celsius(); - char tempUnit = 'C'; - if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { - temp = optCurrentWeather->temperature.Fahrenheit(); - tempUnit = 'F'; - } - lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); - lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight())); - } else { - lv_label_set_text_static(temperature, ""); - lv_label_set_text(weatherIcon, ""); - } - lv_obj_realign(temperature); - lv_obj_realign(weatherIcon); - } +WatchFaceDigital::WatchFaceDigital(AppControllers& controllers) : Pawn(controllers, std::make_unique(std::make_unique(watchface_digital, watchface_digital_len))) { } diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h index e3a1ac649a..5a073881a3 100644 --- a/src/displayapp/screens/WatchFaceDigital.h +++ b/src/displayapp/screens/WatchFaceDigital.h @@ -4,76 +4,17 @@ #include #include #include -#include "displayapp/screens/Screen.h" -#include "components/datetime/DateTimeController.h" -#include "components/ble/SimpleWeatherService.h" -#include "components/ble/BleController.h" -#include "displayapp/widgets/StatusIcons.h" +#include "displayapp/screens/Pawn.h" #include "utility/DirtyValue.h" #include "displayapp/apps/Apps.h" namespace Pinetime { - namespace Controllers { - class Settings; - class Battery; - class Ble; - class AlarmController; - class NotificationManager; - class HeartRateController; - class MotionController; - } - namespace Applications { namespace Screens { - class WatchFaceDigital : public Screen { + class WatchFaceDigital : public Pawn { public: - WatchFaceDigital(Controllers::DateTime& dateTimeController, - const Controllers::Battery& batteryController, - const Controllers::Ble& bleController, - const Controllers::AlarmController& alarmController, - Controllers::NotificationManager& notificationManager, - Controllers::Settings& settingsController, - Controllers::HeartRateController& heartRateController, - Controllers::MotionController& motionController, - Controllers::SimpleWeatherService& weather); - ~WatchFaceDigital() override; - - void Refresh() override; - - private: - uint8_t displayedHour = -1; - uint8_t displayedMinute = -1; - - Utility::DirtyValue> currentDateTime {}; - Utility::DirtyValue stepCount {}; - Utility::DirtyValue heartbeat {}; - Utility::DirtyValue heartbeatRunning {}; - Utility::DirtyValue notificationState {}; - Utility::DirtyValue> currentWeather {}; - - Utility::DirtyValue> currentDate; - - lv_obj_t* label_time; - lv_obj_t* label_time_ampm; - lv_obj_t* label_date; - lv_obj_t* heartbeatIcon; - lv_obj_t* heartbeatValue; - lv_obj_t* stepIcon; - lv_obj_t* stepValue; - lv_obj_t* notificationIcon; - lv_obj_t* weatherIcon; - lv_obj_t* temperature; - - Controllers::DateTime& dateTimeController; - Controllers::NotificationManager& notificationManager; - Controllers::Settings& settingsController; - Controllers::HeartRateController& heartRateController; - Controllers::MotionController& motionController; - Controllers::SimpleWeatherService& weatherService; - - lv_task_t* taskRefresh; - Widgets::StatusIcons statusIcons; + WatchFaceDigital(AppControllers& controllers); }; } @@ -83,15 +24,7 @@ namespace Pinetime { static constexpr const char* name = "Digital"; static Screens::Screen* Create(AppControllers& controllers) { - return new Screens::WatchFaceDigital(controllers.dateTimeController, - controllers.batteryController, - controllers.bleController, - controllers.alarmController, - controllers.notificationManager, - controllers.settingsController, - controllers.heartRateController, - controllers.motionController, - *controllers.weatherController); + return new Screens::WatchFaceDigital(controllers); }; static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { diff --git a/src/displayapp/screens/program.h b/src/displayapp/screens/program.h new file mode 100644 index 0000000000..3b5cf68e35 --- /dev/null +++ b/src/displayapp/screens/program.h @@ -0,0 +1,20 @@ +const unsigned char program[] = { + 0xc4, 0x00, 0x00, 0x00, 0xe0, 0xf1, 0x0b, 0x0b, 0x04, 0x00, 0x08, 0x00, + 0x50, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, + 0x8c, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x73, 0x63, + 0x72, 0x65, 0x65, 0x6e, 0x00, 0x00, 0x00, 0x00, 0xad, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x9c, 0x00, 0xfc, 0xff, 0x92, 0x00, 0x04, 0x00, + 0x8e, 0x00, 0x00, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x48, 0xf4, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x88, 0x00, 0xfc, 0xff, + 0x8e, 0x00, 0x14, 0x00, 0x8e, 0x00, 0x14, 0x00, 0x90, 0x00, 0xfc, 0xff, + 0x70, 0x00, 0x00, 0x00, 0x16, 0xfc, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, + 0x92, 0x00, 0x08, 0x00, 0x90, 0x00, 0xfc, 0xff, 0x70, 0x00, 0x00, 0x00, + 0x46, 0xf4, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x04, 0x00, + 0x66, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6c, 0x65, 0x48, 0x6f, 0x77, 0x20, 0x6f, + 0x00, 0x64, 0x6c, 0x72 +}; +unsigned int program_len = 196; diff --git a/src/displayapp/screens/watchface_digital.h b/src/displayapp/screens/watchface_digital.h new file mode 100644 index 0000000000..238a4c1db5 --- /dev/null +++ b/src/displayapp/screens/watchface_digital.h @@ -0,0 +1,77 @@ +const unsigned char watchface_digital[] = { + 0xd0, 0x42, 0x00, 0x63, 0xe0, 0xf8, 0xc2, 0xe1, 0x70, 0x58, 0x00, 0x20, + 0x72, 0x00, 0xf2, 0xee, 0x41, 0xc2, 0x6b, 0x68, 0x84, 0x80, 0xc6, 0x04, + 0x0f, 0x29, 0xe0, 0x0c, 0xa8, 0x80, 0x3a, 0xaa, 0x00, 0xca, 0xe8, 0x07, + 0x68, 0x08, 0x0c, 0xae, 0xc0, 0x32, 0x82, 0x00, 0xca, 0xfe, 0x4f, 0x6c, + 0x48, 0x1d, 0x82, 0x67, 0x3c, 0x81, 0x80, 0xc6, 0x20, 0x08, 0x29, 0xa0, + 0x2c, 0xa1, 0x00, 0xb2, 0x09, 0x9c, 0xc0, 0x06, 0x51, 0xf8, 0x05, 0x02, + 0xe5, 0x65, 0xb3, 0x00, 0x86, 0xe7, 0x68, 0x80, 0x59, 0xad, 0xf6, 0xeb, + 0xa5, 0x7e, 0xd5, 0x6d, 0xb2, 0xd8, 0xe0, 0x17, 0x3b, 0x18, 0x40, 0x6c, + 0xb6, 0xe8, 0x06, 0xb4, 0x3e, 0x51, 0xe0, 0x19, 0x67, 0x20, 0x1f, 0xcf, + 0xfe, 0x02, 0x01, 0x0c, 0x80, 0x45, 0x41, 0x65, 0x7e, 0x22, 0x8c, 0x02, + 0x6a, 0x07, 0x2c, 0x40, 0x4c, 0x81, 0x66, 0x63, 0x10, 0xb8, 0x81, 0xc4, + 0x16, 0x22, 0x31, 0x28, 0x8d, 0x38, 0x5e, 0xd7, 0x00, 0x19, 0x4b, 0x7f, + 0x1f, 0xff, 0xf9, 0xec, 0xf1, 0xd0, 0x03, 0x60, 0x83, 0xce, 0x54, 0x0f, + 0x23, 0x99, 0xe1, 0xd0, 0x25, 0x92, 0x80, 0x53, 0x02, 0xe4, 0x00, 0x1c, + 0x78, 0xad, 0xa9, 0x1f, 0x40, 0xd8, 0x91, 0x44, 0x36, 0x72, 0x00, 0xba, + 0x00, 0xcf, 0x2a, 0xf1, 0x32, 0x09, 0x62, 0x4f, 0x02, 0x27, 0x20, 0xb8, + 0x89, 0xce, 0x88, 0x27, 0x28, 0xa0, 0x7d, 0x80, 0x6a, 0xf5, 0x12, 0x19, + 0x87, 0xde, 0x28, 0x86, 0x10, 0xfa, 0x17, 0xc6, 0x10, 0x5f, 0xf2, 0xfb, + 0x49, 0x0b, 0xf9, 0x1b, 0x00, 0x09, 0x7d, 0x82, 0xe2, 0x5f, 0xf0, 0x1a, + 0xac, 0x44, 0xbe, 0xa1, 0xf5, 0x2f, 0xca, 0xff, 0xcb, 0xef, 0x28, 0x2f, + 0xf7, 0x38, 0xbe, 0xc1, 0x71, 0x2f, 0xf8, 0x0c, 0x60, 0xa0, 0x32, 0x89, + 0x96, 0xd0, 0x3e, 0xe5, 0xf9, 0xa8, 0x17, 0xfc, 0xbe, 0xd3, 0x02, 0xfe, + 0x79, 0xff, 0xe0, 0x71, 0x2f, 0xa8, 0x5c, 0x4b, 0xfe, 0x03, 0x58, 0x32, + 0xfd, 0x83, 0xea, 0x5f, 0x92, 0x68, 0x97, 0xfc, 0xbe, 0xb2, 0xc2, 0xff, + 0x97, 0xd8, 0x2e, 0x25, 0xff, 0x01, 0x8f, 0xef, 0xfe, 0x03, 0x18, 0x81, + 0x6d, 0x03, 0xee, 0x5f, 0x9a, 0x61, 0x7f, 0xcb, 0xed, 0x1c, 0x0b, 0x94, + 0xf0, 0xc7, 0x09, 0xb5, 0x09, 0x88, 0xee, 0xcf, 0xf7, 0x99, 0x00, 0x09, + 0xfd, 0x84, 0x6a, 0x7f, 0x72, 0xff, 0x97, 0xe4, 0x3a, 0x25, 0xb9, 0x82, + 0x40, 0x2a, 0xa0, 0xb3, 0x1f, 0xa0, 0x5c, 0x44, 0xe7, 0x42, 0x5f, 0xe0, + 0x0c, 0xe1, 0x08, 0x36, 0x0f, 0xaa, 0xff, 0xcb, 0xfe, 0x5f, 0x18, 0xc0, + 0x5c, 0xb9, 0x2f, 0x72, 0x1d, 0xd8, 0x4c, 0x47, 0x77, 0x66, 0x01, 0x95, + 0x38, 0x06, 0x51, 0xe1, 0xdb, 0x4b, 0x3f, 0x01, 0x71, 0x03, 0x11, 0x4c, + 0xaa, 0x80, 0xf4, 0x97, 0x83, 0xc9, 0x32, 0x20, 0x01, 0x91, 0x80, 0xcb, + 0xa9, 0x01, 0x01, 0x8e, 0x72, 0x00, 0x38, 0x1c, 0x18, 0x6c, 0xb4, 0x0a, + 0xf2, 0x83, 0x84, 0xcb, 0x10, 0x0f, 0x2b, 0xe4, 0x02, 0x69, 0x00, 0xd2, + 0x40, 0x03, 0x82, 0x23, 0x30, 0x78, 0x80, 0x06, 0xfc, 0x13, 0x2c, 0x24, + 0x01, 0x02, 0x36, 0x13, 0x39, 0x80, 0xc8, 0x5e, 0x36, 0x48, 0x04, 0x88, + 0x0e, 0x49, 0x33, 0x05, 0x8d, 0xa1, 0x02, 0x48, 0x79, 0xa3, 0x2a, 0xc9, + 0x32, 0x20, 0xf9, 0x89, 0xa5, 0xc8, 0xde, 0x64, 0x33, 0x92, 0x01, 0xca, + 0x38, 0x03, 0x2b, 0xd8, 0x0c, 0x82, 0xc4, 0xd3, 0x42, 0x39, 0x85, 0xcc, + 0xc6, 0xf5, 0xf3, 0x1b, 0x03, 0xcd, 0x26, 0x24, 0xc1, 0x47, 0x9a, 0xec, + 0xf2, 0x89, 0x7e, 0x43, 0xad, 0xb1, 0x0f, 0x57, 0x84, 0x3a, 0x63, 0xa0, + 0x10, 0x57, 0x59, 0x4e, 0x0f, 0x69, 0x30, 0x75, 0x90, 0x9c, 0xc1, 0xe6, + 0x8f, 0x10, 0x48, 0xa7, 0xf4, 0x1e, 0xa9, 0xf7, 0x11, 0x89, 0xa4, 0x40, + 0x62, 0x81, 0x10, 0x99, 0xcb, 0x86, 0xe4, 0x23, 0x33, 0x7d, 0xd2, 0x0c, + 0x68, 0x87, 0x14, 0x98, 0x86, 0x44, 0xce, 0x60, 0xb5, 0x3c, 0x88, 0x2c, + 0xc7, 0x6e, 0x03, 0x1c, 0x61, 0xff, 0xff, 0xee, 0xf3, 0x51, 0x9d, 0x00, + 0x67, 0xe3, 0x32, 0x27, 0xd8, 0xfd, 0x73, 0x03, 0xff, 0x1f, 0xa2, 0xfd, + 0x41, 0x22, 0xbf, 0xd0, 0x7a, 0xaf, 0xdf, 0x39, 0x00, 0x02, 0x88, 0x3c, + 0xe5, 0x63, 0x32, 0x03, 0x14, 0x02, 0x2c, 0x03, 0x22, 0x58, 0xaf, 0x44, + 0x06, 0x22, 0xd1, 0xa6, 0x82, 0xca, 0x06, 0x03, 0x26, 0x39, 0x92, 0x45, + 0xae, 0x61, 0x71, 0x05, 0x9c, 0x51, 0x26, 0x91, 0x04, 0xfa, 0x25, 0x44, + 0x16, 0x7a, 0x84, 0x5a, 0x16, 0xc6, 0x40, 0x6b, 0xc2, 0xd8, 0x99, 0x04, + 0x1e, 0x72, 0x92, 0xde, 0x6b, 0x0b, 0x78, 0xcd, 0x11, 0x1a, 0xca, 0x04, + 0x79, 0xb0, 0x11, 0xe4, 0x90, 0x06, 0x45, 0x91, 0x00, 0x0d, 0xf8, 0x56, + 0x40, 0xb3, 0x3f, 0x9d, 0x98, 0x06, 0x47, 0xf9, 0x80, 0x91, 0xd0, 0x3e, + 0x69, 0xf3, 0xe6, 0x2a, 0xd0, 0x86, 0x2a, 0xd1, 0x1e, 0x88, 0x3c, 0xec, + 0x85, 0x34, 0x01, 0xae, 0x56, 0x01, 0x02, 0x80, 0x46, 0xe3, 0x7c, 0xe8, + 0x06, 0x27, 0x00, 0xaf, 0x32, 0x68, 0x9e, 0xce, 0x88, 0xaf, 0x22, 0x99, + 0xe4, 0x93, 0x64, 0x03, 0x1d, 0xa0, 0x9c, 0xa0, 0xa0, 0x72, 0x78, 0x88, + 0x6c, 0xe6, 0x09, 0x72, 0x74, 0x88, 0x6c, 0xc1, 0xe2, 0x2f, 0x19, 0x43, + 0x7f, 0x0f, 0xe7, 0x40, 0x2d, 0xf8, 0x00, 0x4b, 0x7e, 0x5b, 0x6d, 0xc0, + 0xff, 0xc4, 0x71, 0x13, 0x5b, 0x0f, 0xfe, 0x08, 0x0f, 0x40, 0xfa, 0x83, + 0xdc, 0x16, 0x72, 0x73, 0xf9, 0x01, 0x8a, 0x01, 0x16, 0x31, 0xa6, 0xf0, + 0x06, 0x43, 0x91, 0xa6, 0x82, 0xc9, 0xb2, 0x20, 0x01, 0x6d, 0x9d, 0x60, + 0xb6, 0x81, 0x71, 0x01, 0x8f, 0x01, 0x6f, 0xeb, 0x7a, 0x98, 0x1d, 0xf8, + 0x1e, 0xa7, 0x79, 0x96, 0x03, 0xf3, 0x39, 0x00, 0x02, 0x8d, 0x98, 0x0e, + 0x54, 0xe3, 0xc1, 0x80, 0x1f, 0x3d, 0x88, 0xf7, 0xc0, 0x31, 0x79, 0x50, + 0x18, 0xea, 0x70, 0x80, 0x31, 0x00, 0x78, 0x03, 0xc0, 0x1e, 0x00, 0xd3, + 0xe0, 0x80, 0x3d, 0xa2, 0x34, 0x10, 0x19, 0x54, 0x27, 0x56, 0x49, 0x94, + 0x94, 0x04, 0x33, 0x09, 0x28, 0x44, 0xc0, 0xe6, 0x02, 0x29, 0xd0, 0x28, + 0xc4, 0xaf, 0x25, 0x90, 0x5c, 0xc3, 0xe2, 0x04, 0x20, 0x88, 0x81, 0x04, + 0x16, 0x60, 0x11, 0x90, 0x06, 0xc4, 0x16, 0x44, 0xdf, 0x19, 0x11, 0x0d, + 0x01, 0x6e +}; +unsigned int watchface_digital_len = 878; diff --git a/src/pawn/amx.c b/src/pawn/amx.c new file mode 100644 index 0000000000..635316da8d --- /dev/null +++ b/src/pawn/amx.c @@ -0,0 +1,3871 @@ +/* Pawn Abstract Machine (for the Pawn language) + * + * Copyright (c) CompuPhase, 1997-2023 + * + * 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. + * + * Version: $Id: amx.c 7487 2025-08-21 18:37:33Z thiadmer $ + */ + +#define WIN32_LEAN_AND_MEAN +#if defined _UNICODE || defined __UNICODE__ || defined UNICODE +# if !defined UNICODE /* for Windows API */ +# define UNICODE +# endif +# if !defined _UNICODE /* for C library */ +# define _UNICODE +# endif +#endif + +#include +#include +#include /* for wchar_t */ +#include /* for getenv() */ +#include +#include "osdefs.h" +#if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + #include + #if !defined AMX_NODYNALOAD + #include + #endif + #if defined AMX_JIT + #include + #include + #endif + #if defined __APPLE__ + #include + #include + #include + #endif +#endif +#if !defined AMX_ANSIONLY && (defined __LCC__ || defined __LINUX__ || defined __APPLE__) + #include /* for wcslen() */ +#endif + +#if defined __ECOS__ + /* eCos puts include files in cyg/package_name */ + #include +#else + #include "amx.h" +#endif + +#if (defined _Windows && !defined AMX_NODYNALOAD) || (defined AMX_JIT && __WIN32__) + #include +#endif + + +/* When one or more of the AMX_funcname macros are defined, we want + * to compile only those functions. However, when none of these macros + * is present, we want to compile everything. + */ +#if defined AMX_ALIGN || defined AMX_ALLOT || defined AMX_CLEANUP + #define AMX_EXPLIT_FUNCTIONS +#endif +#if defined AMX_CLONE || defined AMX_DEFCALLBACK || defined AMX_EXEC + #define AMX_EXPLIT_FUNCTIONS +#endif +#if defined AMX_FLAGS || defined AMX_INIT || defined AMX_MEMINFO + #define AMX_EXPLIT_FUNCTIONS +#endif +#if defined AMX_NAMELENGTH || defined AMX_NATIVEINFO || defined AMX_PUSHXXX + #define AMX_EXPLIT_FUNCTIONS +#endif +#if defined AMX_RAISEERROR || defined AMX_REGISTER || defined AMX_SETCALLBACK + #define AMX_EXPLIT_FUNCTIONS +#endif +#if defined AMX_SETDEBUGHOOK || defined AMX_UTF8XXX || defined AMX_XXXNATIVES + #define AMX_EXPLIT_FUNCTIONS +#endif +#if defined AMX_XXXPUBLICS || defined AMX_XXXPUBVARS || defined AMX_XXXSTRING + #define AMX_EXPLIT_FUNCTIONS +#endif +#if defined AMX_XXXTAGS || defined AMX_XXXUSERDATA || defined AMX_VERIFYADDR + #define AMX_EXPLIT_FUNCTIONS +#endif +#if !defined AMX_EXPLIT_FUNCTIONS + /* no constant set, set them all (except amx_InitJIT() and other JIT support) */ + #define AMX_ALIGN /* amx_Align16(), amx_Align32() and amx_Align64() */ + #define AMX_ALLOT /* amx_Allot() and amx_Release() */ + #define AMX_DEFCALLBACK /* amx_Callback() */ + #define AMX_CLEANUP /* amx_Cleanup() */ + #define AMX_CLONE /* amx_Clone() */ + #define AMX_EXEC /* amx_Exec() */ + #define AMX_FLAGS /* amx_Flags() */ + #define AMX_INIT /* amx_Init() */ + #define AMX_MEMINFO /* amx_MemInfo() */ + #define AMX_NAMELENGTH /* amx_NameLength() */ + #define AMX_NATIVEINFO /* amx_NativeInfo() */ + #define AMX_PUSHXXX /* amx_Push(), amx_PushAddress(), amx_PushArray() and amx_PushString() */ + #define AMX_RAISEERROR /* amx_RaiseError() */ + #define AMX_REGISTER /* amx_Register() */ + #define AMX_SETCALLBACK /* amx_SetCallback() */ + #define AMX_SETDEBUGHOOK /* amx_SetDebugHook() */ + #define AMX_UTF8XXX /* amx_UTF8Check(), amx_UTF8Get(), amx_UTF8Len() and amx_UTF8Put() */ + #define AMX_XXXNATIVES /* amx_NumNatives(), amx_GetNative() and amx_FindNative() */ + #define AMX_XXXPUBLICS /* amx_NumPublics(), amx_GetPublic() and amx_FindPublic() */ + #define AMX_XXXPUBVARS /* amx_NumPubVars(), amx_GetPubVar() and amx_FindPubVar() */ + #define AMX_XXXSTRING /* amx_StrLen(), amx_GetString() and amx_SetString() */ + #define AMX_XXXTAGS /* amx_NumTags(), amx_GetTag() and amx_FindTagId() */ + #define AMX_XXXUSERDATA /* amx_GetUserData() and amx_SetUserData() */ + #define AMX_VERIFYADDR /* amx_VerifyAddress() */ +#endif +#undef AMX_EXPLIT_FUNCTIONS +#if defined AMX_ANSIONLY + #undef AMX_UTF8XXX /* no UTF-8 support in ANSI/ASCII-only version */ +#endif +#if defined AMX_NO_NATIVEINFO + #undef AMX_NATIVEINFO +#endif +#if AMX_USERNUM <= 0 + #undef AMX_XXXUSERDATA +#endif +#if defined AMX_JIT + /* JIT is incompatible with macro instructions, packed opcodes and overlays */ + #if !defined AMX_NO_MACRO_INSTR + #define AMX_NO_MACRO_INSTR + #endif + #if !defined AMX_NO_PACKED_OPC + #define AMX_NO_PACKED_OPC + #endif + #if !defined AMX_NO_OVERLAY + #define AMX_NO_OVERLAY + #endif +#endif +#if (defined AMX_ASM || defined AMX_JIT) && !defined AMX_ALTCORE + /* do not use the standard ANSI-C amx_Exec() function */ + #define AMX_ALTCORE +#endif +#if !defined AMX_NO_PACKED_OPC && !defined AMX_TOKENTHREADING + #define AMX_TOKENTHREADING /* packed opcodes require token threading (or switch threading) */ +#endif +#if defined AMX_DONT_RELOCATE + #define AMX_TOKENTHREADING /* direct threading cannot be used when opcodes cannot be relocated */ +#endif + +#if defined __64BIT__ + #define NATIVEADDR(addr,high) (AMX_NATIVE)((intptr_t)(addr) | ((intptr_t)((uint64_t)high<<32))) +#else + #define NATIVEADDR(addr,high) (AMX_NATIVE)(intptr_t)(addr) +#endif + +#if defined AMX_ALTCORE + #if defined __WIN32__ + /* For Watcom C/C++ use register calling convention (faster); for + * Microsoft C/C++ (and most other C compilers) use "cdecl". + * The important point is that you assemble AMXEXEC.ASM with the matching + * calling convention, or the right JIT, respectively. + * AMXJITR.ASM is for Watcom's register calling convention, AMXJITS.ASM and + * AMXJITSN.ASM are for "cdecl". + */ + #if defined __WATCOMC__ && !defined STACKARGS + /* register calling convention; the #pragma tells the compiler into which + * registers the parameters should go + */ + extern cell amx_exec_run(AMX *amx,cell *retval,unsigned char *data); + #pragma aux amx_exec_run parm [eax] [edx] [ebx]; + extern int amx_exec_list(const AMX *amx,const cell **opcodelist,int *numopcodes); + #pragma aux amx_exec_run parm [eax] [edx] [ebx]; + extern cell amx_jit_compile(void *pcode, void *jumparray, void *nativecode); + #pragma aux amx_exec_run parm [eax] [edx] [ebx]; + extern cell amx_jit_run(AMX *amx,cell *retval,unsigned char *data); + #pragma aux amx_jit_run parm [eax] [edx] [ebx]; + extern int amx_jit_list(const AMX *amx,const cell **opcodelist,int *numopcodes); + #pragma aux amx_exec_run parm [eax] [edx] [ebx]; + #elif defined __GNUC__ + /* force "cdecl" by adding an "attribute" to the declaration */ + extern cell amx_exec_run(AMX *amx,cell *retval,unsigned char *data) __attribute__((cdecl)); + extern int amx_exec_list(const AMX *amx,const cell **opcodelist,int *numopcodes) __attribute__((cdecl)); + extern cell amx_jit_compile(void *pcode, void *jumparray, void *nativecode) __attribute__((cdecl)); + extern cell amx_jit_run(AMX *amx,cell *retval,unsigned char *data) __attribute__((cdecl)); + extern int amx_jit_list(const AMX *amx,const cell **opcodelist,int *numopcodes) __attribute__((cdecl)); + #else + /* force "cdecl" by specifying it as a "function class" with the "__cdecl" keyword */ + extern cell __cdecl amx_exec_run(AMX *amx,cell *retval,unsigned char *data); + extern int __cdecl amx_exec_list(const AMX *amx,const cell **opcodelist,int *numopcodes); + extern cell __cdecl amx_jit_compile(void *pcode, void *jumparray, void *nativecode); + extern cell __cdecl amx_jit_run(AMX *amx,cell *retval,unsigned char *data); + extern int __cdecl amx_jit_list(const AMX *amx,const cell **opcodelist,int *numopcodes); + #endif + #else /* __WIN32__ */ + /* assume no specific calling conventions for other platforms than Windows */ + extern cell amx_exec_run(AMX *amx,cell *retval,unsigned char *data); + extern int amx_exec_list(const AMX *amx,const cell **opcodelist,int *numopcodes); + extern cell amx_jit_compile(void *pcode, void *jumparray, void *nativecode); + extern cell amx_jit_run(AMX *amx,cell *retval,unsigned char *data); + extern int amx_jit_list(const AMX *amx,const cell **opcodelist,int *numopcodes); + #endif /* __WIN32__ */ +#else + int amx_exec_list(AMX *amx,const cell **opcodelist,int *numopcodes); +#endif /* AMX_ALTCORE */ + +typedef enum { + OP_NOP, + OP_LOAD_PRI, + OP_LOAD_ALT, + OP_LOAD_S_PRI, + OP_LOAD_S_ALT, + OP_LREF_S_PRI, + OP_LREF_S_ALT, + OP_LOAD_I, + OP_LODB_I, + OP_CONST_PRI, + OP_CONST_ALT, + OP_ADDR_PRI, + OP_ADDR_ALT, + OP_STOR, + OP_STOR_S, + OP_SREF_S, + OP_STOR_I, + OP_STRB_I, + OP_ALIGN_PRI, + OP_LCTRL, + OP_SCTRL, + OP_XCHG, + OP_PUSH_PRI, + OP_PUSH_ALT, + OP_PUSHR_PRI, + OP_POP_PRI, + OP_POP_ALT, + OP_PICK, + OP_STACK, + OP_HEAP, + OP_PROC, + OP_RET, + OP_RETN, + OP_CALL, + OP_JUMP, + OP_JZER, + OP_JNZ, + OP_SHL, + OP_SHR, + OP_SSHR, + OP_SHL_C_PRI, + OP_SHL_C_ALT, + OP_SMUL, + OP_SDIV, + OP_ADD, + OP_SUB, + OP_AND, + OP_OR, + OP_XOR, + OP_NOT, + OP_NEG, + OP_INVERT, + OP_EQ, + OP_NEQ, + OP_SLESS, + OP_SLEQ, + OP_SGRTR, + OP_SGEQ, + OP_INC_PRI, + OP_INC_ALT, + OP_INC_I, + OP_DEC_PRI, + OP_DEC_ALT, + OP_DEC_I, + OP_MOVS, + OP_CMPS, + OP_FILL, + OP_HALT, + OP_BOUNDS, + OP_SYSREQ, + OP_SWITCH, + OP_SWAP_PRI, + OP_SWAP_ALT, + OP_BREAK, + OP_CASETBL, + /* patched instructions */ + OP_SYSREQ_D, + OP_SYSREQ_ND, + /* overlay instructions */ + OP_CALL_OVL, + OP_RETN_OVL, + OP_SWITCH_OVL, + OP_CASETBL_OVL, +#if !defined AMX_NO_MACRO_INSTR + /* supplemental & macro instructions */ + OP_LIDX, + OP_LIDX_B, + OP_IDXADDR, + OP_IDXADDR_B, + OP_PUSH_C, + OP_PUSH, + OP_PUSH_S, + OP_PUSH_ADR, + OP_PUSHR_C, + OP_PUSHR_S, + OP_PUSHR_ADR, + OP_JEQ, + OP_JNEQ, + OP_JSLESS, + OP_JSLEQ, + OP_JSGRTR, + OP_JSGEQ, + OP_SDIV_INV, + OP_SUB_INV, + OP_ADD_C, + OP_SMUL_C, + OP_ZERO_PRI, + OP_ZERO_ALT, + OP_ZERO, + OP_ZERO_S, + OP_EQ_C_PRI, + OP_EQ_C_ALT, + OP_INC, + OP_INC_S, + OP_DEC, + OP_DEC_S, + /* macro instructions */ + OP_SYSREQ_N, + OP_PUSHM_C, + OP_PUSHM, + OP_PUSHM_S, + OP_PUSHM_ADR, + OP_PUSHRM_C, + OP_PUSHRM_S, + OP_PUSHRM_ADR, + OP_LOAD2, + OP_LOAD2_S, + OP_CONST, + OP_CONST_S, +#endif +#if !defined AMX_NO_PACKED_OPC + /* packed instructions */ + OP_LOAD_P_PRI, + OP_LOAD_P_ALT, + OP_LOAD_P_S_PRI, + OP_LOAD_P_S_ALT, + OP_LREF_P_S_PRI, + OP_LREF_P_S_ALT, + OP_LODB_P_I, + OP_CONST_P_PRI, + OP_CONST_P_ALT, + OP_ADDR_P_PRI, + OP_ADDR_P_ALT, + OP_STOR_P, + OP_STOR_P_S, + OP_SREF_P_S, + OP_STRB_P_I, + OP_LIDX_P_B, + OP_IDXADDR_P_B, + OP_ALIGN_P_PRI, + OP_PUSH_P_C, + OP_PUSH_P, + OP_PUSH_P_S, + OP_PUSH_P_ADR, + OP_PUSHR_P_C, + OP_PUSHR_P_S, + OP_PUSHR_P_ADR, + OP_PUSHM_P_C, + OP_PUSHM_P, + OP_PUSHM_P_S, + OP_PUSHM_P_ADR, + OP_PUSHRM_P_C, + OP_PUSHRM_P_S, + OP_PUSHRM_P_ADR, + OP_STACK_P, + OP_HEAP_P, + OP_SHL_P_C_PRI, + OP_SHL_P_C_ALT, + OP_ADD_P_C, + OP_SMUL_P_C, + OP_ZERO_P, + OP_ZERO_P_S, + OP_EQ_P_C_PRI, + OP_EQ_P_C_ALT, + OP_INC_P, + OP_INC_P_S, + OP_DEC_P, + OP_DEC_P_S, + OP_MOVS_P, + OP_CMPS_P, + OP_FILL_P, + OP_HALT_P, + OP_BOUNDS_P, +#endif + /* ----- */ + OP_NUM_OPCODES +} OPCODE; + +#define NUMENTRIES(hdr,field,nextfield) \ + (unsigned)(((hdr)->nextfield - (hdr)->field) / (hdr)->defsize) +#define GETENTRY(hdr,table,index) \ + (AMX_FUNCSTUB *)((unsigned char*)(hdr) + (unsigned)(hdr)->table + (unsigned)index*(hdr)->defsize) +#define GETENTRYNAME(hdr,entry) \ + (char *)((unsigned char*)(hdr) + (unsigned)((AMX_FUNCSTUB*)(entry))->nameofs) + +#if !defined NDEBUG + static int check_endian(void) + { + uint16_t val=0x00ff; + unsigned char *ptr=(unsigned char *)&val; + /* "ptr" points to the starting address of "val". If that address + * holds the byte "0xff", the computer stored the low byte of "val" + * at the lower address, and so the memory lay out is Little Endian. + */ + assert(*ptr==0xff || *ptr==0x00); + #if BYTE_ORDER==BIG_ENDIAN + return *ptr==0x00; /* return "true" if big endian */ + #else + return *ptr==0xff; /* return "true" if little endian */ + #endif + } +#endif + +#if BYTE_ORDER==BIG_ENDIAN || PAWN_CELL_SIZE==16 + void amx_Swap16(uint16_t *v) + { + unsigned char *s = (unsigned char *)v; + unsigned char t; + + assert_static(sizeof(*v)==2); + /* swap two bytes */ + t=s[0]; + s[0]=s[1]; + s[1]=t; + } +#endif + +#if BYTE_ORDER==BIG_ENDIAN || PAWN_CELL_SIZE==32 + void amx_Swap32(uint32_t *v) + { + unsigned char *s = (unsigned char *)v; + unsigned char t; + + assert_static(sizeof(*v)==4); + /* swap outer two bytes */ + t=s[0]; + s[0]=s[3]; + s[3]=t; + /* swap inner two bytes */ + t=s[1]; + s[1]=s[2]; + s[2]=t; + } +#endif + +#if (BYTE_ORDER==BIG_ENDIAN || PAWN_CELL_SIZE==64) && (defined _I64_MAX || defined __x86_64__ || defined HAVE_I64) + void amx_Swap64(uint64_t *v) + { + unsigned char *s = (unsigned char *)v; + unsigned char t; + + assert(sizeof(*v)==8); + + t=s[0]; + s[0]=s[7]; + s[7]=t; + + t=s[1]; + s[1]=s[6]; + s[6]=t; + + t=s[2]; + s[2]=s[5]; + s[5]=t; + + t=s[3]; + s[3]=s[4]; + s[4]=t; + } +#endif + +#if defined AMX_ALIGN || defined AMX_INIT +uint16_t * AMXAPI amx_Align16(uint16_t *v) +{ + assert_static(sizeof(*v)==2); + assert(check_endian()); + #if BYTE_ORDER==BIG_ENDIAN + amx_Swap16(v); + #endif + return v; +} + +uint32_t * AMXAPI amx_Align32(uint32_t *v) +{ + assert_static(sizeof(*v)==4); + assert(check_endian()); + #if BYTE_ORDER==BIG_ENDIAN + amx_Swap32(v); + #endif + return v; +} + +#if defined _I64_MAX || defined __x86_64__ || defined HAVE_I64 +uint64_t * AMXAPI amx_Align64(uint64_t *v) +{ + assert(sizeof(*v)==8); + assert(check_endian()); + #if BYTE_ORDER==BIG_ENDIAN + amx_Swap64(v); + #endif + return v; +} +#endif /* _I64_MAX || __x86_64__ || HAVE_I64 */ +#endif /* AMX_ALIGN || AMX_INIT */ + +#if defined AMX_FLAGS +int AMXAPI amx_Flags(AMX *amx,uint16_t *flags) +{ + AMX_HEADER *hdr; + + *flags=0; + if (amx==NULL) + return AMX_ERR_FORMAT; + hdr=(AMX_HEADER *)amx->base; + if (hdr->magic!=AMX_MAGIC) + return AMX_ERR_FORMAT; + if (hdr->file_version>CUR_FILE_VERSION || hdr->amx_versionflags; + return AMX_ERR_NONE; +} +#endif /* AMX_FLAGS */ + +#if defined AMX_DEFCALLBACK +int AMXAPI amx_Callback(AMX *amx, cell index, cell *result, const cell *params) +{ +#if defined AMX_NATIVETABLE + extern AMX_NATIVE const AMX_NATIVETABLE[]; +#endif + AMX_HEADER *hdr; + AMX_FUNCSTUB *func; + AMX_NATIVE f; + + assert(amx!=NULL); + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + assert(hdr->natives<=hdr->libraries); +#if defined AMX_NATIVETABLE + if (index<0) { + /* size of AMX_NATIVETABLE is unknown here, so we cannot verify index */ + f=(AMX_NATIVETABLE)[-(index+1)]; + } else { +#endif + assert(index>=0 && index<(cell)NUMENTRIES(hdr,natives,libraries)); + func=GETENTRY(hdr,natives,index); + f=NATIVEADDR(func->address,func->nameofs); +#if defined AMX_NATIVETABLE + } /* if */ +#endif + assert(f!=NULL); + + /* Now that we have found the function, patch the program so that any + * subsequent call will call the function directly (bypassing this + * callback). + * This trick cannot work in the JIT, because the program would need to + * be re-JIT-compiled after patching a P-code instruction. + */ + #if !defined AMX_DONT_RELOCATE + assert((amx->flags & AMX_FLAG_JITC)==0 || amx->sysreq_d==0); + if (amx->sysreq_d!=0) { + /* at the point of the call, the CIP pseudo-register points directly + * behind the SYSREQ(.N) instruction and its parameter(s) + */ + unsigned char *code=amx->code+(int)amx->cip-sizeof(cell); + if (amx->flags & AMX_FLAG_SYSREQN) /* SYSREQ.N has 2 parameters */ + code-=sizeof(cell); + assert(amx->code!=NULL); + assert(amx->cip>=4 && amx->cip<(hdr->dat - hdr->cod)); + assert(sizeof(f)<=sizeof(cell)); /* function pointer must fit in a cell */ + assert(*(cell*)code==index); + #if defined AMX_TOKENTHREADING || !(defined __GNUC__ || defined __ICC || defined AMX_ASM || defined AMX_JIT) + assert(!(amx->flags & AMX_FLAG_SYSREQN) && *(cell*)(code-sizeof(cell))==OP_SYSREQ + || (amx->flags & AMX_FLAG_SYSREQN) && *(cell*)(code-sizeof(cell))==OP_SYSREQ_N); + #endif + *(cell*)(code-sizeof(cell))=amx->sysreq_d; + *(cell*)code=(cell)(intptr_t)f; + } /* if */ + #endif + + /* Note: + * params[0] == number of bytes for the additional parameters passed to the native function + * params[1] == first argument + * etc. + */ + + amx->error=AMX_ERR_NONE; + *result = f(amx,params); + return amx->error; +} +#endif /* defined AMX_DEFCALLBACK */ + + +#if defined AMX_JIT + /* convert from relative addresses to absolute physical addresses */ + #define RELOC_ABS(base,off) (*(ucell *)((base)+(int)(off)) += (ucell)(base)+(int)(off)-sizeof(cell)) +#else + #define JUMPREL(ip) ((cell*)((intptr_t)(ip)+*(cell*)(ip)-sizeof(cell))) +#endif +#if defined AMX_ASM || defined AMX_JIT + #define RELOCATE_ADDR(base,v) ((v)+((ucell)(base))) +#else + #define RELOCATE_ADDR(base,v) (v) +#endif + +#define DBGPARAM(v) ( (v)=*(cell *)(amx->code+(int)cip), cip+=sizeof(cell) ) + +#if !defined GETOPCODE + #if defined AMX_NO_PACKED_OPC + #define GETOPCODE(c) (OPCODE)(c) + #else + #define GETOPCODE(c) (OPCODE)((c) & ((1UL << sizeof(cell)*4)-1)) + #endif +#endif +#if !defined GETPARAM_P + #define GETPARAM_P(v,o) ( v=((cell)(o) >> (int)(sizeof(cell)*4)) ) +#endif + +#if defined AMX_INIT + +static int VerifyPcode(AMX *amx) +{ + AMX_HEADER *hdr; + cell cip,tgt,opmask; + int sysreq_flg,max_opcode; + int datasize,stacksize; + const cell *opcode_list; + #if defined AMX_JIT + int opcode_count=0; + int reloc_count=0; + int jit_codesize=0; + #endif + + assert(amx!=NULL); + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + amx->flags|=AMX_FLAG_VERIFY; + datasize=hdr->hea-hdr->dat; + stacksize=hdr->stp-hdr->hea; + + #if defined AMX_ASM && defined AMX_JIT + if ((amx->flags & AMX_FLAG_JITC)!=0) + jit_codesize=amx_jit_list(amx,&opcode_list,&max_opcode); + else + amx_exec_list(amx,&opcode_list,&max_opcode); + #elif defined AMX_JIT + jit_codesize=amx_jit_list(amx,&opcode_list,&max_opcode); + #else + amx_exec_list(amx,&opcode_list,&max_opcode); + #endif + #if defined AMX_TOKENTHREADING + opcode_list=NULL; /* avoid token translation if token threading is in effect */ + #endif + #if defined AMX_NO_PACKED_OPC + opmask= ~0; + #else + opmask=(1UL << sizeof(cell)*4)-1; + #endif + + /* sanity checks */ + assert_static(OP_XCHG==21); + assert_static(OP_SMUL==42); + assert_static(OP_MOVS==64); + #if !defined AMX_NO_MACRO_INSTR + assert_static(OP_LIDX==81); + assert_static(OP_ZERO_PRI==102); + assert_static(OP_LOAD2==120); + #endif + #if !defined AMX_NO_PACKED_OPC + assert_static(OP_LOAD_P_PRI==124); + assert_static(OP_ALIGN_P_PRI==141); + assert_static(OP_BOUNDS_P==174); + #endif + + sysreq_flg=0; + if (opcode_list!=NULL) { + if (amx->sysreq_d==opcode_list[OP_SYSREQ_D]) + sysreq_flg=0x01; + else if (amx->sysreq_d==opcode_list[OP_SYSREQ_ND]) + sysreq_flg=0x02; + } else { + if (amx->sysreq_d==OP_SYSREQ_D) + sysreq_flg=0x01; + else if (amx->sysreq_d==OP_SYSREQ_ND) + sysreq_flg=0x02; + } /* if */ + amx->sysreq_d=0; /* preset */ + + /* start browsing code */ + assert(amx->code!=NULL); /* should already have been set in amx_Init() */ + for (cip=0; cipcodesize; ) { + cell op=*(cell *)(amx->code+(int)cip); + if ((op & opmask)>=max_opcode) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_INVINSTR; + } /* if */ + /* relocate opcode (only works if the size of an opcode is at least + * as big as the size of a pointer (jump address); so basically we + * rely on the opcode and a pointer being 32-bit + */ + if (opcode_list!=NULL) { + /* verify that opcode_list[op]!=NULL, if it is, this instruction + * is unsupported + */ + if (opcode_list[op & opmask]==0) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_INVINSTR; + } /* if */ + *(cell *)(amx->code+(int)cip)=opcode_list[op & opmask]; + } /* if */ + #if defined AMX_JIT + opcode_count++; + #endif + cip+=sizeof(cell); + switch (op & opmask) { +#if !defined AMX_NO_MACRO_INSTR + case OP_PUSHM_C: /* instructions with variable number of parameters */ + case OP_PUSHM: + case OP_PUSHM_S: + case OP_PUSHM_ADR: + case OP_PUSHRM_C: + case OP_PUSHRM_S: + case OP_PUSHRM_ADR: + tgt=*(cell*)(amx->code+(int)cip); /* get count */ + cip+=sizeof(cell)*(tgt+1); + break; + + case OP_LOAD2: + tgt=*(cell*)(amx->code+(int)cip); /* verify both addresses */ + if (tgt<0 || tgt>=datasize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + tgt=*(cell*)(amx->code+(int)cip+(int)sizeof(cell)); + if (tgt<0 || tgt>=datasize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + cip+=sizeof(cell)*2; + break; + + case OP_LOAD2_S: + tgt=*(cell*)(amx->code+(int)cip); /* verify both addresses */ + if (tgt<-stacksize || tgt>stacksize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + tgt=*(cell*)(amx->code+(int)cip+(int)sizeof(cell)); + if (tgt<-stacksize || tgt>stacksize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + cip+=sizeof(cell)*2; + break; + + case OP_CONST: + tgt=*(cell*)(amx->code+(int)cip); /* verify address */ + if (tgt<0 || tgt>=datasize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + cip+=sizeof(cell)*2; + break; + + case OP_CONST_S: + tgt=*(cell*)(amx->code+(int)cip); /* verify both addresses */ + if (tgt<-stacksize || tgt>stacksize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + cip+=sizeof(cell)*2; + break; +#endif /* !defined AMX_NO_MACRO_INSTR */ + +#if !defined AMX_NO_PACKED_OPC + case OP_LODB_P_I: /* instructions with 1 parameter packed inside the same cell */ + case OP_CONST_P_PRI: + case OP_CONST_P_ALT: + case OP_ADDR_P_PRI: + case OP_ADDR_P_ALT: + case OP_STRB_P_I: + case OP_LIDX_P_B: + case OP_IDXADDR_P_B: + case OP_ALIGN_P_PRI: + case OP_PUSH_P_C: + case OP_PUSH_P: + case OP_PUSH_P_S: + case OP_PUSH_P_ADR: + case OP_PUSHR_P_C: + case OP_PUSHR_P_S: + case OP_PUSHR_P_ADR: + case OP_STACK_P: + case OP_HEAP_P: + case OP_SHL_P_C_PRI: + case OP_SHL_P_C_ALT: + case OP_ADD_P_C: + case OP_SMUL_P_C: + case OP_ZERO_P: + case OP_ZERO_P_S: + case OP_EQ_P_C_PRI: + case OP_EQ_P_C_ALT: + case OP_MOVS_P: + case OP_CMPS_P: + case OP_FILL_P: + case OP_HALT_P: + case OP_BOUNDS_P: + break; + + case OP_LOAD_P_PRI: /* data instructions with 1 parameter packed inside the same cell */ + case OP_LOAD_P_ALT: + case OP_STOR_P: + case OP_INC_P: + case OP_DEC_P: + GETPARAM_P(tgt,op); /* verify address */ + if (tgt<0 || tgt>=datasize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + break; + + case OP_LOAD_P_S_PRI: /* stack instructions with 1 parameter packed inside the same cell */ + case OP_LOAD_P_S_ALT: + case OP_LREF_P_S_PRI: + case OP_LREF_P_S_ALT: + case OP_STOR_P_S: + case OP_SREF_P_S: + case OP_INC_P_S: + case OP_DEC_P_S: + GETPARAM_P(tgt,op); /* verify address */ + if (tgt<-stacksize || tgt>stacksize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + break; + + case OP_PUSHM_P_C: /* instructions with variable number of parameters */ + case OP_PUSHM_P: + case OP_PUSHM_P_S: + case OP_PUSHM_P_ADR: + case OP_PUSHRM_P_C: + case OP_PUSHRM_P_S: + case OP_PUSHRM_P_ADR: + GETPARAM_P(tgt,op); /* verify address */ + cip+=sizeof(cell)*tgt; + break; +#endif /* !defined AMX_NO_PACKED_OPC */ + + case OP_LODB_I: /* instructions with 1 parameter (not packed) */ + case OP_CONST_PRI: + case OP_CONST_ALT: + case OP_ADDR_PRI: + case OP_ADDR_ALT: + case OP_STRB_I: + case OP_ALIGN_PRI: + case OP_LCTRL: + case OP_SCTRL: + case OP_PICK: + case OP_STACK: + case OP_HEAP: + case OP_SHL_C_PRI: + case OP_SHL_C_ALT: + case OP_MOVS: + case OP_CMPS: + case OP_FILL: + case OP_HALT: + case OP_BOUNDS: +#if !defined AMX_NO_MACRO_INSTR + case OP_LIDX_B: + case OP_IDXADDR_B: + case OP_PUSH_C: + case OP_PUSH_ADR: + case OP_PUSHR_C: + case OP_PUSHR_ADR: + case OP_ADD_C: + case OP_SMUL_C: + case OP_ZERO: + case OP_ZERO_S: + case OP_EQ_C_PRI: + case OP_EQ_C_ALT: +#endif + cip+=sizeof(cell); + break; + + case OP_LOAD_PRI: + case OP_LOAD_ALT: + case OP_STOR: +#if !defined AMX_NO_MACRO_INSTR + case OP_PUSH: + case OP_INC: + case OP_DEC: +#endif + tgt=*(cell*)(amx->code+(int)cip); /* verify address */ + if (tgt<0 || tgt>=datasize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + cip+=sizeof(cell); + break; + + case OP_LOAD_S_PRI: + case OP_LOAD_S_ALT: + case OP_LREF_S_PRI: + case OP_LREF_S_ALT: + case OP_STOR_S: + case OP_SREF_S: +#if !defined AMX_NO_MACRO_INSTR + case OP_PUSH_S: + case OP_PUSHR_S: + case OP_INC_S: + case OP_DEC_S: +#endif + tgt=*(cell*)(amx->code+(int)cip); /* verify address */ + if (tgt<-stacksize || tgt>stacksize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + cip+=sizeof(cell); + break; + + case OP_NOP: /* instructions without parameters */ + case OP_LOAD_I: + case OP_STOR_I: + case OP_XCHG: + case OP_PUSH_PRI: + case OP_PUSH_ALT: + case OP_PUSHR_PRI: + case OP_POP_PRI: + case OP_POP_ALT: + case OP_PROC: + case OP_RET: + case OP_RETN: + case OP_SHL: + case OP_SHR: + case OP_SSHR: + case OP_SMUL: + case OP_SDIV: + case OP_ADD: + case OP_SUB: + case OP_AND: + case OP_OR: + case OP_XOR: + case OP_NOT: + case OP_NEG: + case OP_INVERT: + case OP_EQ: + case OP_NEQ: + case OP_SLESS: + case OP_SLEQ: + case OP_SGRTR: + case OP_SGEQ: + case OP_INC_PRI: + case OP_INC_ALT: + case OP_INC_I: + case OP_DEC_PRI: + case OP_DEC_ALT: + case OP_DEC_I: + case OP_SWAP_PRI: + case OP_SWAP_ALT: + case OP_BREAK: +#if !defined AMX_NO_MACRO_INSTR + case OP_LIDX: + case OP_IDXADDR: + case OP_SDIV_INV: + case OP_SUB_INV: + case OP_ZERO_PRI: + case OP_ZERO_ALT: +#endif + break; + + case OP_CALL: /* opcodes that need relocation (JIT only), or conversion to position-independent code */ + case OP_JUMP: + case OP_JZER: + case OP_JNZ: + case OP_SWITCH: +#if !defined AMX_NO_MACRO_INSTR + case OP_JEQ: + case OP_JNEQ: + case OP_JSLESS: + case OP_JSLEQ: + case OP_JSGRTR: + case OP_JSGEQ: +#endif + tgt=*(cell*)(amx->code+(int)cip)+cip-sizeof(cell); + if (tgt<0 || tgt>amx->codesize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + #if defined AMX_JIT + reloc_count++; + RELOC_ABS(amx->code, cip); /* change to absolute physical address */ + #endif + cip+=sizeof(cell); + break; + +#if !defined AMX_NO_OVERLAY + /* overlay opcodes (overlays must be enabled) */ + case OP_SWITCH_OVL: + assert(hdr->file_version>=10); + tgt=*(cell*)(amx->code+(int)cip)+cip-sizeof(cell); + if (tgt<0 || tgt>amx->codesize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + /* fall through */ + case OP_CALL_OVL: + cip+=sizeof(cell); + /* fall through */ + case OP_RETN_OVL: + assert(hdr->overlays!=0 && hdr->overlays!=hdr->nametable); + #if defined AMX_JIT + if ((amx->flags & AMX_FLAG_JITC)!=0) + return AMX_ERR_OVERLAY; /* JIT does not support overlays */ + #endif + if (amx->overlay==NULL) + return AMX_ERR_OVERLAY; /* no overlay callback */ + break; + case OP_CASETBL_OVL: { + cell num; + DBGPARAM(num); /* number of records follows the opcode */ + cip+=(2*num + 1)*sizeof(cell); + if (amx->overlay==NULL) + return AMX_ERR_OVERLAY; /* no overlay callback */ + break; + } /* case */ +#endif + + case OP_SYSREQ: + cip+=sizeof(cell); + sysreq_flg|=0x01; /* mark SYSREQ found */ + break; +#if !defined AMX_NO_MACRO_INSTR + case OP_SYSREQ_N: + cip+=sizeof(cell)*2; + sysreq_flg|=0x02; /* mark SYSREQ.N found */ + break; +#endif + + case OP_CASETBL: { + cell num,offs; + int i; + DBGPARAM(num); /* number of records follows the opcode */ + for (i=0; i<=num; i++) { + offs=cip+2*i*sizeof(cell); + tgt=*(cell*)(amx->code+(int)offs)+offs-sizeof(cell); + if (tgt<0 || tgt>amx->codesize) { + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_BOUNDS; + } /* if */ + #if defined AMX_JIT + RELOC_ABS(amx->code, cip+2*i*sizeof(cell)); + reloc_count++; + #endif + } /* for */ + cip+=(2*num + 1)*sizeof(cell); + break; + } /* case */ + + default: + amx->flags &= ~AMX_FLAG_VERIFY; + return AMX_ERR_INVINSTR; + } /* switch */ + } /* for */ + + #if !defined AMX_DONT_RELOCATE + /* only either type of system request opcode should be found (otherwise, + * we probably have a non-conforming compiler + */ + if ((sysreq_flg==0x01 || sysreq_flg==0x02) && (amx->flags & AMX_FLAG_JITC)==0) { + /* to use direct system requests, a function pointer must fit in a cell; + * because the native function's address will be stored as the parameter + * of SYSREQ.(N)D + */ + if (sizeof(AMX_NATIVE)<=sizeof(cell)) { + if (opcode_list!=NULL) + amx->sysreq_d=(sysreq_flg==0x01) ? opcode_list[OP_SYSREQ_D] : opcode_list[OP_SYSREQ_ND]; + else + amx->sysreq_d=(sysreq_flg==0x01) ? OP_SYSREQ_D : OP_SYSREQ_ND; + } /* if */ + } /* if */ + #endif + + #if defined AMX_JIT + /* adjust the code size to mean: estimated code size of the native code + * (instead of the size of the P-code) + */ + amx->codesize=jit_codesize*opcode_count + hdr->cod + (hdr->stp - hdr->dat); + amx->reloc_size=2*sizeof(cell)*reloc_count; + #endif + + amx->flags &= ~AMX_FLAG_VERIFY; + amx->flags |= AMX_FLAG_INIT; + if (sysreq_flg & 0x02) + amx->flags |= AMX_FLAG_SYSREQN; + + return AMX_ERR_NONE; +} + +/* definitions used for amx_Init() and amx_Cleanup() */ +#if (defined _Windows || defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__) && !defined AMX_NODYNALOAD + typedef int AMXEXPORT (AMXAPI _FAR *AMX_ENTRY)(AMX _FAR *amx); + + static void getlibname(char *libname,const char *source) + { + #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + char *root=getenv("AMXLIB"); + #endif + + assert(libname!=NULL && source!=NULL); + libname[0]='\0'; + #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + if (root!=NULL && *root!='\0') { + strcpy(libname,root); + if (libname[strlen(libname)-1]!='/') + strcat(libname,"/"); + } /* if */ + #endif + strcat(libname,"amx"); + strcat(libname,source); + #if defined _Windows + strcat(libname,".dll"); + #elif defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ + strcat(libname,".so"); + #elif defined __APPLE__ + strcat(libname,".dylib"); + #endif + } +#endif + +int AMXAPI amx_Init(AMX *amx,void *program) +{ + AMX_HEADER *hdr; + int err; + uint16_t *namelength; + unsigned char *data; + + if ((amx->flags & AMX_FLAG_INIT)!=0) + return AMX_ERR_INIT; /* already initialized (may not do so twice) */ + + hdr=(AMX_HEADER *)program; + /* the header is in Little Endian, on a Big Endian machine, swap all + * multi-byte words + */ + assert(check_endian()); + #if BYTE_ORDER==BIG_ENDIAN + amx_Align32((uint32_t*)&hdr->size); + amx_Align16(&hdr->magic); + amx_Align16((uint16_t*)&hdr->flags); + amx_Align16((uint16_t*)&hdr->defsize); + amx_Align32((uint32_t*)&hdr->cod); + amx_Align32((uint32_t*)&hdr->dat); + amx_Align32((uint32_t*)&hdr->hea); + amx_Align32((uint32_t*)&hdr->stp); + amx_Align32((uint32_t*)&hdr->cip); + amx_Align32((uint32_t*)&hdr->publics); + amx_Align32((uint32_t*)&hdr->natives); + amx_Align32((uint32_t*)&hdr->libraries); + amx_Align32((uint32_t*)&hdr->pubvars); + amx_Align32((uint32_t*)&hdr->tags); + if (hdr->file_version>=10) + amx_Align32((uint32_t*)&hdr->overlays); + #endif + + if (hdr->magic!=AMX_MAGIC) + return AMX_ERR_FORMAT; + if (hdr->file_version>CUR_FILE_VERSION || hdr->amx_versiondefsize!=sizeof(AMX_FUNCSTUB)) + return AMX_ERR_FORMAT; + /* check the maximum name length in the separate name table */ + #if BYTE_ORDER==BIG_ENDIAN + amx_Align32((uint32_t*)&hdr->nametable); + #endif + namelength=(uint16_t*)((unsigned char*)program + (unsigned)hdr->nametable); + amx_Align16(namelength); + if (*namelength>sNAMEMAX) + return AMX_ERR_FORMAT; + if (hdr->stp<=0) + return AMX_ERR_FORMAT; + assert(hdr->hea == hdr->size); + #if BYTE_ORDER==BIG_ENDIAN + { + ucell *code=(ucell *)((unsigned char *)program+(int)hdr->cod); + while (code<(ucell *)((unsigned char *)program+(int)hdr->hea)) + amx_SwapCell(code++); + } + #endif + + amx->base=(unsigned char *)program; + + /* set initial values */ + amx->hlw=hdr->hea - hdr->dat; /* stack and heap relative to data segment */ + amx->stp=hdr->stp - hdr->dat - sizeof(cell); + amx->hea=amx->hlw; + amx->stk=amx->stp; + #if defined AMX_DEFCALLBACK + if (amx->callback==NULL) + amx->callback=amx_Callback; + #endif + + /* when running P-code from ROM (with the header with the native function + * table in RAM), the "code" field must be set to a non-NULL value on + * initialization, before calling amx_Init(); in an overlay scheme, the + * code field is modified dynamically by the overlay callback + */ + if (amx->code==NULL) + amx->code=amx->base+(int)hdr->cod; + if (amx->codesize==0) + amx->codesize=hdr->dat-hdr->cod; + + /* to split the data segment off the code segment, the "data" field must + * be set to a non-NULL value on initialization, before calling amx_Init(); + * you may also need to explicitly initialize the data section with the + * contents read from the AMX file + */ + if (amx->data!=NULL) { + data=amx->data; + if ((amx->flags & AMX_FLAG_DSEG_INIT)==0 && amx->overlay==NULL) + memcpy(data,amx->base+(int)hdr->dat,(size_t)(hdr->hea-hdr->dat)); + } else { + data=amx->base+(int)hdr->dat; + } /* if */ + + /* Set a zero cell at the top of the stack, which functions + * as a sentinel for strings. + */ + * (cell *)(data+(int)(hdr->stp-hdr->dat-sizeof(cell)))=0; + + /* also align all addresses in the public function, public variable, + * public tag and native function tables --offsets into the name table + * (if present) must also be swapped. + */ + #if BYTE_ORDER==BIG_ENDIAN + { /* local */ + AMX_FUNCSTUB *fs; + int i,num; + + fs=GETENTRY(hdr,natives,0); + num=NUMENTRIES(hdr,natives,libraries); + for (i=0; iaddress); /* redundant, because it should be zero */ + amx_Align32(&fs->nameofs); + fs=(AMX_FUNCSTUB*)((unsigned char *)fs+hdr->defsize); + } /* for */ + + fs=GETENTRY(hdr,publics,0); + assert(hdr->publics<=hdr->natives); + num=NUMENTRIES(hdr,publics,natives); + for (i=0; iaddress); + amx_Align32(&fs->nameofs); + fs=(AMX_FUNCSTUB*)((unsigned char *)fs+hdr->defsize); + } /* for */ + + fs=GETENTRY(hdr,pubvars,0); + assert(hdr->pubvars<=hdr->tags); + num=NUMENTRIES(hdr,pubvars,tags); + for (i=0; iaddress); + amx_Align32(&fs->nameofs); + fs=(AMX_FUNCSTUB*)((unsigned char *)fs+hdr->defsize); + } /* for */ + + fs=GETENTRY(hdr,tags,0); + if (hdr->file_version<7) { /* file version 7 introduced the name table */ + assert(hdr->tags<=hdr->cod); + num=NUMENTRIES(hdr,tags,cod); + } else { + assert(hdr->tags<=hdr->nametable); + num=NUMENTRIES(hdr,tags,nametable); + } /* if */ + for (i=0; iaddress); + amx_Align32(&fs->nameofs); + fs=(AMX_FUNCSTUB*)((unsigned char *)fs+hdr->defsize); + } /* for */ + } /* local */ + #endif + + /* verify P-code and relocate address in the case of the JIT */ + if ((hdr->flags & AMX_FLAG_OVERLAY)==0) { + err=VerifyPcode(amx); + } else { + int i; + err=(amx->overlay==NULL) ? AMX_ERR_OVERLAY : AMX_ERR_NONE; + /* load every overlay on initialization and verify explicitly; we must + * do this to know whether to use new or old system requests + */ + for (i=0; err==AMX_ERR_NONE && i<(int)((hdr->nametable - hdr->overlays)/sizeof(AMX_OVERLAYINFO)); i++) { + err=amx->overlay(amx, i); + if (err==AMX_ERR_NONE) + err=VerifyPcode(amx); + } /* for */ + } /* if */ + if (err!=AMX_ERR_NONE) + return err; + + /* load any extension modules that the AMX refers to */ + #if (defined _Windows || defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__) && !defined AMX_NODYNALOAD + { /* local */ + #if defined _Windows + char libname[sNAMEMAX+8]; /* +1 for '\0', +3 for 'amx' prefix, +4 for extension */ + HINSTANCE hlib; + #elif defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + char libname[_MAX_PATH]; + void *hlib; + #endif + int numlibraries,i; + AMX_FUNCSTUB *lib; + AMX_ENTRY libinit; + hdr=(AMX_HEADER *)amx->base; + numlibraries=NUMENTRIES(hdr,libraries,pubvars); + for (i=0; i0) { + char ptr=strrchr(pathbuf,'/'); + assert(ptr!=NULL); + *ptr='\0'; + asprintf(&ptr,"%s/%s",pathbuf,libname); + assert(ptr!=NULL); + hlib=dlopen(ptr,RTLD_NOW); + free(ptr); + } + if (hlib==NULL) { + /* if failed, try to search elsewhere */ + hlib=dlopen(libname,RTLD_NOW); + } + #endif + if (hlib!=NULL) { + /* a library that cannot be loaded or that does not have the required + * initialization function is simply ignored + */ + char funcname[sNAMEMAX+9]; /* +1 for '\0', +4 for 'amx_', +4 for 'Init' */ + strcpy(funcname,"amx_"); + strcat(funcname,GETENTRYNAME(hdr,lib)); + strcat(funcname,"Init"); + #if defined _Windows + libinit=(AMX_ENTRY)GetProcAddress(hlib,funcname); + #elif defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + libinit=(AMX_ENTRY)dlsym(hlib,funcname); + #endif + if (libinit!=NULL) + libinit(amx); + } /* if */ + lib->address=(ucell)(intptr_t)hlib; + } /* for */ + } /* local */ + #endif + + return AMX_ERR_NONE; +} + +#if defined AMX_JIT + + #define CODESIZE_JIT 8192 /* approximate size of the code for the JIT */ + + #if defined __WIN32__ /* this also applies to Win32 "console" applications */ + + #define ALIGN(addr) (addr) + + #define PROT_READ 0x1 /* page can be read */ + #define PROT_WRITE 0x2 /* page can be written */ + #define PROT_EXEC 0x4 /* page can be executed */ + #define PROT_NONE 0x0 /* page can not be accessed */ + + static int mprotect(void *addr, size_t len, int prot) + { + DWORD prev, p = 0; + if ((prot & PROT_WRITE)!=0) + p = PAGE_EXECUTE_READWRITE; + else + p |= PAGE_EXECUTE_READ; + return !VirtualProtect(addr, len, p, &prev); + } + + #elif defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + + /* Linux, BSD and OSX already have mprotect() */ + #define ALIGN(addr) (char *)(((long)addr + sysconf(_SC_PAGESIZE)-1) & ~(sysconf(_SC_PAGESIZE)-1)) + + #else + + // TODO: Add cases for Mac OS/X and other operating systems + + /* DOS32 has no imposed limits on its segments */ + #define ALIGN(addr) (addr) + #define mprotect(addr, len, prot) (0) + + #endif /* #if defined __WIN32 __ */ + +int AMXAPI amx_InitJIT(AMX *amx, void *reloc_table, void *native_code) +{ + int res; + AMX_HEADER *hdr; + + if ((amx->flags & AMX_FLAG_JITC)==0) + return AMX_ERR_INIT_JIT; /* flag not set, this AMX is not prepared for JIT */ + + /* copy the prefix */ + memcpy(native_code, amx->base, ((AMX_HEADER *)(amx->base))->cod); + hdr = native_code; + if (hdr->file_version>MAX_FILE_VER_JIT) + return AMX_ERR_VERSION; /* JIT may not support the newest file version(s) */ + /* the JIT does not support overlays, but this is already checked in VerifyPcode() + * the same goes for macro instructions: not supported in the JIT, but already + * checked in VerifyPcode() + */ + + /* Patching SYSREQ(.N) opcodes to SYSREQ.(N)D cannot work in the JIT, because + * the program would need to be re-JIT-compiled after patching a P-code + * instruction. If this field is not zero, something went wrong in + * VerifyPcode(). + */ + assert(amx->sysreq_d==0); + + if (mprotect(ALIGN(amx_jit_compile), CODESIZE_JIT, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) + return AMX_ERR_INIT_JIT; + + /* MP: added check for correct compilation */ + if ((res = amx_jit_compile(amx->base, reloc_table, native_code)) == 0) { + /* update the required memory size (the previous value was a + * conservative estimate, now we know the exact size) + */ + amx->codesize = (hdr->dat + hdr->stp + sizeof(cell)) & ~(sizeof(cell)-1); + /* The compiled code is relocatable, since only relative jumps are + * used for destinations within the generated code, and absolute + * addresses are only for jumps into the runtime, which is fixed + * in memory. + */ + /* set the new pointers */ + amx->base = (unsigned char*)native_code; + amx->code = amx->base + (int)hdr->cod; + amx->cip = hdr->cip; + } /* if */ + + return (res == 0) ? AMX_ERR_NONE : AMX_ERR_INIT_JIT; +} + +#endif /* #if defined AMX_JIT */ + +#endif /* AMX_INIT */ + +#if defined AMX_CLEANUP +int AMXAPI amx_Cleanup(AMX *amx) +{ + #if (defined _Windows || defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__) && !defined AMX_NODYNALOAD + #if defined _Windows + char libname[sNAMEMAX+8]; /* +1 for '\0', +3 for 'amx' prefix, +4 for extension */ + HINSTANCE hlib; + #elif defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + char libname[_MAX_PATH]; + void *hlib; + #endif + AMX_HEADER *hdr; + AMX_FUNCSTUB *lib; + AMX_ENTRY libcleanup; + int numlibraries,i; + #endif + + /* unload all extension modules */ + #if (defined _Windows || defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__) && !defined AMX_NODYNALOAD + hdr=(AMX_HEADER *)amx->base; + assert(hdr->magic==AMX_MAGIC); + numlibraries=NUMENTRIES(hdr,libraries,pubvars); + for (i=0; iaddress!=0) { + getlibname(libname,GETENTRYNAME(hdr,lib)); + #if defined _Windows + #if defined __WIN32__ + hlib=GetModuleHandleA(libname); + #else + hlib=GetModuleHandle(libname); + if (hlib<=HINSTANCE_ERROR) + hlib=NULL; + #endif + #elif defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + hlib=dlopen(libname,RTLD_NOLOAD); + #endif + if (hlib!=NULL) { + char funcname[sNAMEMAX+12]; /* +1 for '\0', +4 for 'amx_', +7 for 'Cleanup' */ + strcpy(funcname,"amx_"); + strcat(funcname,GETENTRYNAME(hdr,lib)); + strcat(funcname,"Cleanup"); + #if defined _Windows + libcleanup=(AMX_ENTRY)GetProcAddress(hlib,funcname); + #elif defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + libcleanup=(AMX_ENTRY)dlsym(hlib,funcname); + #endif + if (libcleanup!=NULL) + libcleanup(amx); + #if defined _Windows + FreeLibrary(hlib); + #elif defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + dlclose(hlib); + #endif + } /* if (hlib!=NULL) */ + } /* if (lib->address!=0) */ + } /* for */ + #else + (void)amx; + #endif + return AMX_ERR_NONE; +} +#endif /* AMX_CLEANUP */ + +#if defined AMX_CLONE +int AMXAPI amx_Clone(AMX *amxClone, AMX *amxSource, void *data) +{ + AMX_HEADER *hdr; + unsigned char _FAR *dataSource; + + if (amxSource==NULL) + return AMX_ERR_FORMAT; + if (amxClone==NULL) + return AMX_ERR_PARAMS; + if ((amxSource->flags & AMX_FLAG_INIT)==0) + return AMX_ERR_INIT; + hdr=(AMX_HEADER *)amxSource->base; + if (hdr->magic!=AMX_MAGIC) + return AMX_ERR_FORMAT; + if (hdr->file_version>CUR_FILE_VERSION || hdr->amx_versionbase=amxSource->base; + amxClone->code=amxSource->code; + amxClone->codesize=amxSource->codesize; + amxClone->hlw=hdr->hea - hdr->dat; /* stack and heap relative to data segment */ + amxClone->stp=hdr->stp - hdr->dat - sizeof(cell); + amxClone->hea=amxClone->hlw; + amxClone->stk=amxClone->stp; + if (amxClone->callback==NULL) + amxClone->callback=amxSource->callback; + if (amxClone->debug==NULL) + amxClone->debug=amxSource->debug; + amxClone->flags=amxSource->flags; + + /* copy the data segment; the stack and the heap can be left uninitialized */ + assert(data!=NULL); + amxClone->data=(unsigned char _FAR *)data; + dataSource=(amxSource->data!=NULL) ? amxSource->data : amxSource->base+(int)hdr->dat; + memcpy(amxClone->data,dataSource,(size_t)(hdr->hea-hdr->dat)); + + /* Set a zero cell at the top of the stack, which functions + * as a sentinel for strings. + */ + * (cell *)(amxClone->data+(int)amxClone->stp) = 0; + + return AMX_ERR_NONE; +} +#endif /* AMX_CLONE */ + +#if defined AMX_MEMINFO +int AMXAPI amx_MemInfo(AMX *amx, long *codesize, long *datasize, long *stackheap) +{ + AMX_HEADER *hdr; + + if (amx==NULL) + return AMX_ERR_FORMAT; + hdr=(AMX_HEADER *)amx->base; + if (hdr->magic!=AMX_MAGIC) + return AMX_ERR_FORMAT; + if (hdr->file_version>CUR_FILE_VERSION || hdr->amx_versioncodesize; + if (datasize!=NULL) + *datasize=hdr->hea - hdr->dat; + if (stackheap!=NULL) + *stackheap=hdr->stp - hdr->hea; + + return AMX_ERR_NONE; +} +#endif /* AMX_MEMINFO */ + +#if defined AMX_NAMELENGTH +int AMXAPI amx_NameLength(AMX *amx, int *length) +{ + AMX_HEADER *hdr; + uint16_t *namelength; + + assert(amx!=NULL); + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + namelength=(uint16_t*)(amx->base + (unsigned)hdr->nametable); + *length=*namelength; + return AMX_ERR_NONE; +} +#endif /* AMX_NAMELENGTH */ + +#if defined AMX_XXXNATIVES +int AMXAPI amx_NumNatives(AMX *amx, int *number) +{ + AMX_HEADER *hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + assert(hdr->natives<=hdr->libraries); + *number=NUMENTRIES(hdr,natives,libraries); + return AMX_ERR_NONE; +} + +int AMXAPI amx_GetNative(AMX *amx, int index, char *name) +{ + AMX_HEADER *hdr; + AMX_FUNCSTUB *func; + + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + assert(hdr->natives<=hdr->libraries); + if (index>=(cell)NUMENTRIES(hdr,natives,libraries)) + return AMX_ERR_INDEX; + + func=GETENTRY(hdr,natives,index); + strcpy(name,GETENTRYNAME(hdr,func)); + return AMX_ERR_NONE; +} + +int AMXAPI amx_FindNative(AMX *amx, const char *name, int *index) +{ + int idx,last; + char pname[sNAMEMAX+1]; + + amx_NumNatives(amx, &last); + /* linear search, the natives table is not sorted alphabetically */ + for (idx=0; idxbase; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + assert(hdr->publics<=hdr->natives); + *number=NUMENTRIES(hdr,publics,natives); + return AMX_ERR_NONE; +} + +int AMXAPI amx_GetPublic(AMX *amx, int index, char *name, ucell *address) +{ + AMX_HEADER *hdr; + AMX_FUNCSTUB *func; + + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + assert(hdr->publics<=hdr->natives); + if (index>=(cell)NUMENTRIES(hdr,publics,natives)) + return AMX_ERR_INDEX; + + func=GETENTRY(hdr,publics,index); + if (name!=NULL) + strcpy(name,GETENTRYNAME(hdr,func)); + if (address!=NULL) + *address=func->address; + return AMX_ERR_NONE; +} + +int AMXAPI amx_FindPublic(AMX *amx, const char *name, int *index) +{ + int first,last,result; + char pname[sNAMEMAX+1]; + + amx_NumPublics(amx, &last); + last--; /* last valid index is 1 less than the number of functions */ + first=0; + /* binary search */ + while (first<=last) { + int mid=(first+last)/2; + amx_GetPublic(amx,mid,pname,NULL); + result=strcmp(pname,name); + if (result>0) { + last=mid-1; + } else if (result<0) { + first=mid+1; + } else { + *index=mid; + return AMX_ERR_NONE; + } /* if */ + } /* while */ + /* not found, set to an invalid index, so amx_Exec() on this index will fail + * with an error + */ + *index=INT_MAX; + return AMX_ERR_NOTFOUND; +} +#endif /* AMX_XXXPUBLICS */ + +#if defined AMX_XXXPUBVARS +int AMXAPI amx_NumPubVars(AMX *amx, int *number) +{ + AMX_HEADER *hdr; + + assert(amx!=NULL); + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + assert(hdr->pubvars<=hdr->tags); + *number=NUMENTRIES(hdr,pubvars,tags); + return AMX_ERR_NONE; +} + +int AMXAPI amx_GetPubVar(AMX *amx, int index, char *name, cell **address) +{ + AMX_HEADER *hdr; + AMX_FUNCSTUB *var; + unsigned char *data; + + assert(amx!=NULL); + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + assert(hdr->pubvars<=hdr->tags); + if (index>=(cell)NUMENTRIES(hdr,pubvars,tags)) + return AMX_ERR_INDEX; + + var=GETENTRY(hdr,pubvars,index); + strcpy(name,GETENTRYNAME(hdr,var)); + data=(amx->data!=NULL) ? amx->data : amx->base+(int)hdr->dat; + assert(address!=NULL); + *address=(cell *)(data+(int)var->address); + return AMX_ERR_NONE; +} + +int AMXAPI amx_FindPubVar(AMX *amx, const char *name, cell **address) +{ + int first,last,result; + char pname[sNAMEMAX+1]; + + amx_NumPubVars(amx,&last); + last--; /* last valid index is 1 less than the number of functions */ + first=0; + /* binary search */ + while (first<=last) { + int mid=(first+last)/2; + amx_GetPubVar(amx,mid,pname,address); + result=strcmp(pname,name); + if (result>0) + last=mid-1; + else if (result<0) + first=mid+1; + else + return AMX_ERR_NONE; + } /* while */ + /* not found */ + assert(address!=NULL); + *address=NULL; + return AMX_ERR_NOTFOUND; +} +#endif /* AMX_XXXPUBVARS */ + +#if defined AMX_XXXTAGS +int AMXAPI amx_NumTags(AMX *amx, int *number) +{ + AMX_HEADER *hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + if (hdr->file_version<5) { /* the tagname table appeared in file format 5 */ + *number=0; + return AMX_ERR_VERSION; + } /* if */ + if (hdr->file_version<7) { /* file version 7 introduced the name table */ + assert(hdr->tags<=hdr->cod); + *number=NUMENTRIES(hdr,tags,cod); + } else { + assert(hdr->tags<=hdr->nametable); + *number=NUMENTRIES(hdr,tags,nametable); + } /* if */ + return AMX_ERR_NONE; +} + +int AMXAPI amx_GetTag(AMX *amx, int index, char *tagname, cell *tag_id) +{ + AMX_HEADER *hdr; + AMX_FUNCSTUB *tag; + + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + if (hdr->file_version<5) { /* the tagname table appeared in file format 5 */ + *tagname='\0'; + *tag_id=0; + return AMX_ERR_VERSION; + } /* if */ + + if (hdr->file_version<7) { /* file version 7 introduced the name table */ + assert(hdr->tags<=hdr->cod); + if (index>=(cell)NUMENTRIES(hdr,tags,cod)) + return AMX_ERR_INDEX; + } else { + assert(hdr->tags<=hdr->nametable); + if (index>=(cell)NUMENTRIES(hdr,tags,nametable)) + return AMX_ERR_INDEX; + } /* if */ + + tag=GETENTRY(hdr,tags,index); + strcpy(tagname,GETENTRYNAME(hdr,tag)); + *tag_id=tag->address; + + return AMX_ERR_NONE; +} + +int AMXAPI amx_FindTagId(AMX *amx, cell tag_id, char *tagname) +{ + int first,last; + cell mid_id; + + #if !defined NDEBUG + /* verify that the tagname table is sorted on the tag_id */ + amx_NumTags(amx, &last); + if (last>0) { + cell cur_id; + amx_GetTag(amx,0,tagname,&cur_id); + for (first=1; firsttag_id) + last=mid-1; + else if (mid_idusertags[index]!=tag; index++) + /* nothing */; + if (index>=AMX_USERNUM) + return AMX_ERR_USERDATA; + *ptr=amx->userdata[index]; + return AMX_ERR_NONE; +} + +int AMXAPI amx_SetUserData(AMX *amx, long tag, void *ptr) +{ + int index; + + assert(amx!=NULL); + assert(tag!=0); + /* try to find existing tag */ + for (index=0; indexusertags[index]!=tag; index++) + /* nothing */; + /* if not found, try to find empty tag */ + if (index>=AMX_USERNUM) + for (index=0; indexusertags[index]!=0; index++) + /* nothing */; + /* if still not found, quit with error */ + if (index>=AMX_USERNUM) + return AMX_ERR_INDEX; + /* set the tag and the value */ + amx->usertags[index]=tag; + amx->userdata[index]=ptr; + return AMX_ERR_NONE; +} +#endif /* AMX_XXXUSERDATA */ + +#if defined AMX_REGISTER +static AMX_NATIVE findfunction(char *name, const AMX_NATIVE_INFO *list, int number) +{ + int i; + + assert(list!=NULL); + for (i=0; (ibase; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + assert(hdr->natives<=hdr->libraries); + numnatives=NUMENTRIES(hdr,natives,libraries); + + err=AMX_ERR_NONE; + func=GETENTRY(hdr,natives,0); + for (i=0; iaddress==0) { + /* this function is not yet located */ + funcptr=(list!=NULL) ? findfunction(GETENTRYNAME(hdr,func),list,number) : NULL; + if (funcptr!=NULL) { + func->address=(uint32_t)(intptr_t)funcptr; + #if defined __64BIT__ + /* for 64-bit version the high part of the pointer must be stored too */ + func->nameofs=(uint32_t)((intptr_t)funcptr >> 32); + #endif + } else { + err=AMX_ERR_NOTFOUND; + } + } /* if */ + func=(AMX_FUNCSTUB*)((unsigned char*)func+hdr->defsize); + } /* for */ + if (err==AMX_ERR_NONE) + amx->flags|=AMX_FLAG_NTVREG; + return err; +} +#endif /* AMX_REGISTER */ + +#if defined AMX_NATIVEINFO +AMX_NATIVE_INFO * AMXAPI amx_NativeInfo(const char *name, AMX_NATIVE func) +{ + static AMX_NATIVE_INFO n; + n.name=name; + n.func=func; + return &n; +} +#endif /* AMX_NATIVEINFO */ + + +#define STKMARGIN ((cell)(16*sizeof(cell))) + +#if defined AMX_PUSHXXX + +int AMXAPI amx_Push(AMX *amx, cell value) +{ + AMX_HEADER *hdr; + unsigned char *data; + + if (amx->hea+STKMARGIN>amx->stk) + return AMX_ERR_STACKERR; + hdr=(AMX_HEADER *)amx->base; + data=(amx->data!=NULL) ? amx->data : amx->base+(int)hdr->dat; + amx->stk-=sizeof(cell); + amx->paramcount+=1; + *(cell *)(data+(int)amx->stk)=value; + return AMX_ERR_NONE; +} + +int AMXAPI amx_PushAddress(AMX *amx, cell *address) +{ + AMX_HEADER *hdr; + unsigned char *data; + cell xaddr; + + /* reverse relocate the address */ + assert(amx!=NULL); + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + data=(amx->data!=NULL) ? amx->data : amx->base+(int)hdr->dat; + xaddr=(cell)((unsigned char*)address-data); + if ((ucell)xaddr>=(ucell)amx->stp) + return AMX_ERR_MEMACCESS; + return amx_Push(amx,xaddr); +} + +int AMXAPI amx_PushArray(AMX *amx, cell **address, const cell array[], int numcells) +{ + cell xaddr,*paddr; + int err; + + assert(amx!=NULL); + assert(array!=NULL); + + xaddr=amx->hea; /* save, before it is modified by amx_Allot() */ + err=amx_Allot(amx,numcells,&paddr); + if (err==AMX_ERR_NONE) { + memcpy(paddr,array,numcells*sizeof(cell)); + err=amx_Push(amx,xaddr); + } /* if */ + if (address!=NULL) + *address=paddr; + return err; +} + +int AMXAPI amx_PushString(AMX *amx, cell **address, const char *string, int pack, int use_wchar) +{ + cell xaddr,*paddr; + int numcells,err; + + assert(amx!=NULL); + assert(string!=NULL); + + #if defined AMX_ANSIONLY + numcells=strlen(string) + 1; + #else + numcells= (int)(use_wchar ? wcslen((const wchar_t*)string) : strlen(string)) + 1; + #endif + if (pack) + numcells=(numcells+sizeof(cell)-1)/sizeof(cell); + xaddr=amx->hea; /* save, before it is modified by amx_Allot() */ + err=amx_Allot(amx,numcells,&paddr); + if (err==AMX_ERR_NONE) { + amx_SetString(paddr,string,pack,use_wchar,numcells); + err=amx_Push(amx,xaddr); + } /* if */ + if (address!=NULL) + *address=paddr; + return err; +} +#endif /* AMX_PUSHXXX */ + +#if defined AMX_EXEC + +/* It is assumed that the abstract machine can simply access the memory area + * for the global data and the stack. If this is not the case, you need to + * define the macro sets _R() and _W(), for reading and writing to memory. + */ +#if !defined _R + #define _R_DEFAULT /* mark default memory access */ + #define _R(base,addr) (* (cell *)((unsigned char*)(base)+(int)(addr))) + #define _R8(base,addr) (* (unsigned char *)((unsigned char*)(base)+(int)(addr))) + #define _R16(base,addr) (* (uint16_t *)((unsigned char*)(base)+(int)(addr))) + #define _R32(base,addr) (* (uint32_t *)((unsigned char*)(base)+(int)(addr))) +#endif +#if !defined _W + #define _W_DEFAULT /* mark default memory access */ + #define _W(base,addr,value) ((*(cell *)((unsigned char*)(base)+(int)(addr)))=(cell)(value)) + #define _W8(base,addr,value) ((*(unsigned char *)((unsigned char*)(base)+(int)(addr)))=(unsigned char)(value)) + #define _W16(base,addr,value) ((*(uint16_t *)((unsigned char*)(base)+(int)(addr)))=(uint16_t)(value)) + #define _W32(base,addr,value) ((*(uint32_t *)((unsigned char*)(base)+(int)(addr)))=(uint32_t)(value)) +#endif + +#if -8/3==-2 && 8/-3==-2 + #define TRUNC_SDIV /* signed divisions are truncated on this platform */ +#else + #define IABS(a) ((a)>=0 ? (a) : (-a)) +#endif + +/* The pseudo-instructions come from the code stream. Normally, these are just + * accessed from memory. When the instructions must be fetched in some other + * way, the definition below must be pre-defined. + * N.B.: + * - reading from a code address should increment the instruction pointer + * (called "cip") + * - only cell-sized accesses occur in code memory + */ +#if !defined _RCODE + #define _RCODE() ( *cip++ ) +#endif + +#if !defined GETPARAM + #define GETPARAM(v) ( v=_RCODE() ) /* read a parameter from the opcode stream */ +#endif +#if !defined SKIPPARAM + #define SKIPPARAM(n) ( cip=(cell *)cip+(n) ) /* for obsolete opcodes */ +#endif + +#define AMXPUSH(v) ( amx->stk-=sizeof(cell), *(cell*)(data+amx->stk)=(v) ) +#define ABORT(amx,v) { (amx)->stk=reset_stk; (amx)->hea=reset_hea; return v; } + + +#if !defined AMX_ALTCORE +int amx_exec_list(AMX *amx,const cell **opcodelist,int *numopcodes) +{ + (void)amx; + assert(opcodelist!=NULL); + *opcodelist=NULL; + assert(numopcodes!=NULL); + *numopcodes=OP_NUM_OPCODES; + return 0; +} +#endif + +int AMXAPI amx_Exec(AMX *amx, cell *retval, int index) +{ + AMX_HEADER *hdr; + AMX_FUNCSTUB *func; + unsigned char *data; + cell reset_stk,reset_hea; + int i; +#if !defined AMX_ALTCORE + cell pri,alt,stk,frm,hea; + cell *cip,op,offs,val; +#endif + + assert(amx!=NULL); + if ((amx->flags & AMX_FLAG_INIT)==0) + return AMX_ERR_INIT; + if (amx->callback==NULL) + return AMX_ERR_CALLBACK; + + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + + if ((amx->flags & AMX_FLAG_NTVREG)==0) { + /* verify that all native functions have been registered (or do not + * need registering) + */ + int numnatives; + assert(hdr->natives<=hdr->libraries); + numnatives=NUMENTRIES(hdr,natives,libraries); + func=GETENTRY(hdr,natives,0); + for (i=0; iaddress!=0; i++) + func=(AMX_FUNCSTUB*)((unsigned char*)func+hdr->defsize); + if (iflags|=AMX_FLAG_NTVREG; /* no need to check this again */ + } /* if */ + assert((amx->flags & AMX_FLAG_VERIFY)==0); + + /* set up the registers */ + assert(hdr!=NULL && hdr->magic==AMX_MAGIC); + assert(amx->code!=NULL || hdr->overlays!=hdr->nametable); + data=(amx->data!=NULL) ? amx->data : amx->base+(int)hdr->dat; + reset_stk=amx->stk; + reset_hea=amx->hea; + amx->error=AMX_ERR_NONE; + + /* get the start address */ + if (index==AMX_EXEC_MAIN) { + if (hdr->cip<0) + return AMX_ERR_INDEX; + amx->cip=hdr->cip; + if (hdr->overlays!=hdr->nametable) { + assert(hdr->overlays!=0); + assert(amx->overlay!=NULL); + amx->ovl_index=(int)hdr->cip; + if ((i=amx->overlay(amx,amx->ovl_index))!=AMX_ERR_NONE) + return i; + amx->cip=0; + } /* if */ + } else if (index==AMX_EXEC_CONT) { + /* restore registers reset_stk & reset_hea */ + reset_stk=amx->reset_stk; + reset_hea=amx->reset_hea; + if (hdr->overlays!=hdr->nametable) { + assert(hdr->overlays!=0); + assert(amx->overlay!=NULL); + if ((i=amx->overlay(amx,amx->ovl_index))!=AMX_ERR_NONE) + return i; + } /* if */ + } else if (index<0) { + return AMX_ERR_INDEX; + } else { + if (index>=(int)NUMENTRIES(hdr,publics,natives)) + return AMX_ERR_INDEX; + func=GETENTRY(hdr,publics,index); + amx->cip=func->address; + if (hdr->overlays!=hdr->nametable) { + assert(hdr->overlays!=0); + assert(amx->overlay!=NULL); + amx->ovl_index=func->address; + if ((i=amx->overlay(amx,amx->ovl_index))!=AMX_ERR_NONE) + return i; + amx->cip=0; + } /* if */ + } /* if */ + /* check values just copied */ + if (amx->stk>amx->stp) + return AMX_ERR_STACKLOW; + if (amx->heahlw) + return AMX_ERR_HEAPLOW; + assert(check_endian()); + + /* sanity checks */ + assert_static(OP_XCHG==21); + assert_static(OP_SMUL==42); + assert_static(OP_MOVS==64); + #if !defined AMX_NO_MACRO_INSTR + assert_static(OP_LIDX==81); + assert_static(OP_ZERO_PRI==102); + assert_static(OP_LOAD2==120); + #endif + #if !defined AMX_NO_PACKED_OPC + assert_static(OP_LOAD_P_PRI==124); + assert_static(OP_ALIGN_P_PRI==141); + assert_static(OP_BOUNDS_P==174); + #endif + #if PAWN_CELL_SIZE==16 + assert_static(sizeof(cell)==2); + #elif PAWN_CELL_SIZE==32 + assert_static(sizeof(cell)==4); + #elif PAWN_CELL_SIZE==64 + assert_static(sizeof(cell)==8); + #else + #error Unsupported cell size + #endif + + if (index!=AMX_EXEC_CONT) { + reset_stk+=amx->paramcount*sizeof(cell); + AMXPUSH(amx->paramcount*sizeof(cell)); + amx->paramcount=0; /* push the parameter count to the stack & reset */ + AMXPUSH(0); /* return address (for overlays: overlay 0, offset 0) */ + } /* if */ + /* check stack/heap before starting to run */ + if (amx->hea+STKMARGIN>amx->stk) + return AMX_ERR_STACKERR; + +#if defined AMX_ALTCORE + + /* start running either the ARM or 80x86 assembler abstract machine or the JIT */ + #if defined AMX_ASM && defined AMX_JIT + if ((amx->flags & AMX_FLAG_JITC)!=0) + i = amx_jit_run(amx,retval,data); + else + i = amx_exec_run(amx,retval,data); + #elif defined AMX_JIT + i = amx_jit_run(amx,retval,data); + #else + /* also for GNU GCC and Intel C/C++ versions */ + i = amx_exec_run(amx,retval,data); + #endif + if (i == AMX_ERR_SLEEP) { + amx->reset_stk=reset_stk; + amx->reset_hea=reset_hea; + } else { + /* remove parameters from the stack; do this the "hard" way, because + * the assembler version has no internal knowledge of the local + * variables, so any "clean" way would be a kludge anyway. + */ + amx->stk=reset_stk; + amx->hea=reset_hea; + } /* if */ + return i; + +#else + + #define CHKMARGIN() if (hea+STKMARGIN>stk) return AMX_ERR_STACKERR + #define CHKSTACK() if (stk>amx->stp) return AMX_ERR_STACKLOW + #define CHKHEAP() if (heahlw) return AMX_ERR_HEAPLOW + + /* PUSH() and POP() are defined in terms of the _R() and _W() macros */ + #define PUSH(v) ( stk-=sizeof(cell), _W(data,stk,v) ) + #define POP(v) ( v=_R(data,stk), stk+=sizeof(cell) ) + + /* set up registers for ANSI-C core: pri, alt, frm, cip, hea, stk */ + pri=amx->pri; + alt=amx->alt; + frm=amx->frm; + cip=(cell *)(amx->code+(int)amx->cip); + hea=amx->hea; + stk=amx->stk; + + /* start running */ + for ( ;; ) { + op=_RCODE(); + switch (GETOPCODE(op)) { + /* core instruction set */ + case OP_NOP: + break; + case OP_LOAD_PRI: + GETPARAM(offs); + pri=_R(data,offs); + break; + case OP_LOAD_ALT: + GETPARAM(offs); + alt=_R(data,offs); + break; + case OP_LOAD_S_PRI: + GETPARAM(offs); + pri=_R(data,frm+offs); + break; + case OP_LOAD_S_ALT: + GETPARAM(offs); + alt=_R(data,frm+offs); + break; + case OP_LREF_S_PRI: + GETPARAM(offs); + offs=_R(data,frm+offs); + pri=_R(data,offs); + break; + case OP_LREF_S_ALT: + GETPARAM(offs); + offs=_R(data,frm+offs); + alt=_R(data,offs); + break; + case OP_LOAD_I: + /* verify address */ + if (pri>=hea && pri=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + pri=_R(data,pri); + break; + case OP_LODB_I: + GETPARAM(offs); + __lodb_i: + /* verify address */ + if (pri>=hea && pri=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + switch ((int)offs) { + case 1: + pri=_R8(data,pri); + break; + case 2: + pri=_R16(data,pri); + break; + case 4: + pri=_R32(data,pri); + break; + } /* switch */ + break; + case OP_CONST_PRI: + GETPARAM(pri); + break; + case OP_CONST_ALT: + GETPARAM(alt); + break; + case OP_ADDR_PRI: + GETPARAM(pri); + pri+=frm; + break; + case OP_ADDR_ALT: + GETPARAM(alt); + alt+=frm; + break; + case OP_STOR: + GETPARAM(offs); + _W(data,offs,pri); + break; + case OP_STOR_S: + GETPARAM(offs); + _W(data,frm+offs,pri); + break; + case OP_SREF_S: + GETPARAM(offs); + offs=_R(data,frm+offs); + _W(data,offs,pri); + break; + case OP_STOR_I: + /* verify address */ + if (alt>=hea && alt=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + _W(data,alt,pri); + break; + case OP_STRB_I: + GETPARAM(offs); + __strb_i: + /* verify address */ + if (alt>=hea && alt=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + switch ((int)offs) { + case 1: + _W8(data,alt,pri); + break; + case 2: + _W16(data,alt,pri); + break; + case 4: + _W32(data,alt,pri); + break; + } /* switch */ + break; + case OP_ALIGN_PRI: + GETPARAM(offs); + #if BYTE_ORDER==LITTLE_ENDIAN + if ((size_t)offscod; + break; + case 1: + pri=hdr->dat; + break; + case 2: + pri=hea; + break; + case 3: + pri=amx->stp; + break; + case 4: + pri=stk; + break; + case 5: + pri=frm; + break; + case 6: + pri=(cell)((unsigned char *)cip-amx->code); + break; + } /* switch */ + break; + case OP_SCTRL: + GETPARAM(offs); + switch ((int)offs) { + case 0: + case 1: + case 3: + /* cannot change these parameters */ + break; + case 2: + hea=pri; + break; + case 4: + stk=pri; + break; + case 5: + frm=pri; + break; + case 6: + cip=(cell *)(amx->code + (int)pri); + break; + } /* switch */ + break; + case OP_XCHG: + offs=pri; /* offs is a temporary variable */ + pri=alt; + alt=offs; + break; + case OP_PUSH_PRI: + PUSH(pri); + break; + case OP_PUSH_ALT: + PUSH(alt); + break; + case OP_PUSHR_PRI: + PUSH((intptr_t)data+pri); + break; + case OP_POP_PRI: + POP(pri); + break; + case OP_POP_ALT: + POP(alt); + break; + case OP_PICK: + GETPARAM(offs); + pri=_R(data,stk+offs); + break; + case OP_STACK: + GETPARAM(offs); + stk+=offs; + alt=stk; + CHKMARGIN(); + CHKSTACK(); + break; + case OP_HEAP: + GETPARAM(offs); + alt=hea; + hea+=offs; + CHKMARGIN(); + CHKHEAP(); + break; + case OP_PROC: + PUSH(frm); + frm=stk; + CHKMARGIN(); + break; + case OP_RET: + POP(frm); + POP(offs); + /* verify the return address */ + if ((long)offs>=amx->codesize) + ABORT(amx,AMX_ERR_MEMACCESS); + cip=(cell *)(amx->code+(int)offs); + break; + case OP_RETN: + POP(frm); + POP(offs); + /* verify the return address */ + if ((long)offs>=amx->codesize) + ABORT(amx,AMX_ERR_MEMACCESS); + cip=(cell *)(amx->code+(int)offs); + stk+=_R(data,stk)+sizeof(cell); /* remove parameters from the stack */ + break; + case OP_CALL: + PUSH(((unsigned char *)cip-amx->code)+sizeof(cell));/* skip address */ + cip=JUMPREL(cip); /* jump to the address */ + break; + case OP_JUMP: + /* since the GETPARAM() macro modifies cip, you cannot + * do GETPARAM(cip) directly */ + cip=JUMPREL(cip); + break; + case OP_JZER: + if (pri==0) + cip=JUMPREL(cip); + else + SKIPPARAM(1); + break; + case OP_JNZ: + if (pri!=0) + cip=JUMPREL(cip); + else + SKIPPARAM(1); + break; + case OP_SHL: + pri<<=alt; + break; + case OP_SHR: + pri=(ucell)pri >> (int)alt; + break; + case OP_SSHR: + pri>>=alt; + break; + case OP_SHL_C_PRI: + GETPARAM(offs); + pri<<=offs; + break; + case OP_SHL_C_ALT: + GETPARAM(offs); + alt<<=offs; + break; + case OP_SMUL: + pri*=alt; + break; + case OP_SDIV: + if (pri==0) + ABORT(amx,AMX_ERR_DIVIDE); + /* use floored division and matching remainder */ + offs=pri; + #if defined TRUNC_SDIV + pri=alt/offs; + alt=alt%offs; + #else + val=alt; /* portable routine for truncated division */ + pri=IABS(alt)/IABS(offs); + if ((cell)(val ^ offs)<0) + pri=-pri; + alt=val-pri*offs; /* calculate the matching remainder */ + #endif + /* now "fiddle" with the values to get floored division */ + if (alt!=0 && (cell)(alt ^ offs)<0) { + pri--; + alt+=offs; + } /* if */ + break; + case OP_ADD: + pri+=alt; + break; + case OP_SUB: + pri=alt-pri; + break; + case OP_AND: + pri&=alt; + break; + case OP_OR: + pri|=alt; + break; + case OP_XOR: + pri^=alt; + break; + case OP_NOT: + pri=!pri; + break; + case OP_NEG: + pri=-pri; + break; + case OP_INVERT: + pri=~pri; + break; + case OP_EQ: + pri= pri==alt ? 1 : 0; + break; + case OP_NEQ: + pri= pri!=alt ? 1 : 0; + break; + case OP_SLESS: + pri= prialt ? 1 : 0; + break; + case OP_SGEQ: + pri= pri>=alt ? 1 : 0; + break; + case OP_INC_PRI: + pri++; + break; + case OP_INC_ALT: + alt++; + break; + case OP_INC_I: + #if defined _R_DEFAULT + *(cell *)(data+(int)pri) += 1; + #else + val=_R(data,pri); + _W(data,pri,val+1); + #endif + break; + case OP_DEC_PRI: + pri--; + break; + case OP_DEC_ALT: + alt--; + break; + case OP_DEC_I: + #if defined _R_DEFAULT + *(cell *)(data+(int)pri) -= 1; + #else + val=_R(data,pri); + _W(data,pri,val-1); + #endif + break; + case OP_MOVS: + GETPARAM(offs); + __movs: + /* verify top & bottom memory addresses, for both source and destination + * addresses + */ + if (pri>=hea && pri=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + if ((pri+offs)>hea && (pri+offs)(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + if (alt>=hea && alt=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + if ((alt+offs)>hea && (alt+offs)(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + #if defined _R_DEFAULT + memcpy(data+(int)alt, data+(int)pri, (int)offs); + #else + for (i=0; i+4=hea && pri=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + if ((pri+offs)>hea && (pri+offs)(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + if (alt>=hea && alt=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + if ((alt+offs)>hea && (alt+offs)(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + #if defined _R_DEFAULT + pri=memcmp(data+(int)alt, data+(int)pri, (int)offs); + #else + val=0; + for (i=0; i+4=hea && alt=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + if ((alt+offs)>hea && (alt+offs)(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + for (i=(int)alt; (size_t)offs>=sizeof(cell); i+=sizeof(cell), offs-=sizeof(cell)) + _W32(data,i,pri); + break; + case OP_HALT: + GETPARAM(offs); + __halt: + if (retval!=NULL) + *retval=pri; + /* store complete status (stk and hea are already set in the ABORT macro) */ + amx->frm=frm; + amx->pri=pri; + amx->alt=alt; + amx->cip=(cell)((unsigned char*)cip-amx->code); + if (offs==AMX_ERR_SLEEP) { + amx->stk=stk; + amx->hea=hea; + amx->reset_stk=reset_stk; + amx->reset_hea=reset_hea; + return (int)offs; + } /* if */ + ABORT(amx,(int)offs); + case OP_BOUNDS: + GETPARAM(offs); + if ((ucell)pri>(ucell)offs) { + amx->cip=(cell)((unsigned char *)cip-amx->code); + ABORT(amx,AMX_ERR_BOUNDS); + } /* if */ + break; + case OP_SYSREQ: + GETPARAM(offs); + /* save a few registers */ + amx->cip=(cell)((unsigned char *)cip-amx->code); + amx->hea=hea; + amx->frm=frm; + amx->stk=stk; + i=amx->callback(amx,offs,&pri,(cell *)(data+(int)stk)); + if (i!=AMX_ERR_NONE) { + if (i==AMX_ERR_SLEEP) { + amx->pri=pri; + amx->alt=alt; + amx->reset_stk=reset_stk; + amx->reset_hea=reset_hea; + return i; + } /* if */ + ABORT(amx,i); + } /* if */ + break; + case OP_SWITCH: { + cell *cptr=JUMPREL(cip)+1;/* +1, to skip the "casetbl" opcode */ + assert(*JUMPREL(cip)==OP_CASETBL); + cip=JUMPREL(cptr+1); /* preset to "none-matched" case */ + i=(int)*cptr; /* number of records in the case table */ + for (cptr+=2; i>0 && *cptr!=pri; i--,cptr+=2) + /* nothing */; + if (i>0) + cip=JUMPREL(cptr+1); /* case found */ + break; + } /* case */ + case OP_SWAP_PRI: + offs=_R(data,stk); + _W32(data,stk,pri); + pri=offs; + break; + case OP_SWAP_ALT: + offs=_R(data,stk); + _W32(data,stk,alt); + alt=offs; + break; + case OP_BREAK: + assert((amx->flags & AMX_FLAG_VERIFY)==0); + if (amx->debug!=NULL) { + /* store status */ + amx->frm=frm; + amx->stk=stk; + amx->hea=hea; + amx->cip=(cell)((unsigned char*)cip-amx->code); + i=amx->debug(amx); + if (i!=AMX_ERR_NONE) { + if (i==AMX_ERR_SLEEP) { + amx->pri=pri; + amx->alt=alt; + amx->reset_stk=reset_stk; + amx->reset_hea=reset_hea; + return i; + } /* if */ + ABORT(amx,i); + } /* if */ + } /* if */ + break; +#if !defined AMX_DONT_RELOCATE + case OP_SYSREQ_D: /* see SYSREQ */ + GETPARAM(offs); + /* save a few registers */ + amx->cip=(cell)((unsigned char *)cip-amx->code); + amx->hea=hea; + amx->frm=frm; + amx->stk=stk; + pri=((AMX_NATIVE)(intptr_t)offs)(amx,(cell*)(data+(int)stk)); + if (amx->error!=AMX_ERR_NONE) { + if (amx->error==AMX_ERR_SLEEP) { + amx->pri=pri; + amx->alt=alt; + amx->reset_stk=reset_stk; + amx->reset_hea=reset_hea; + return AMX_ERR_SLEEP; + } /* if */ + ABORT(amx,amx->error); + } /* if */ + break; +#endif +#if !defined AMX_NO_MACRO_INSTR && !defined AMX_DONT_RELOCATE + case OP_SYSREQ_ND: /* see SYSREQ_N */ + GETPARAM(offs); + GETPARAM(val); + PUSH(val); + /* save a few registers */ + amx->cip=(cell)((unsigned char *)cip-amx->code); + amx->hea=hea; + amx->frm=frm; + amx->stk=stk; + pri=((AMX_NATIVE)(intptr_t)offs)(amx,(cell*)(data+(int)stk)); + stk+=val+4; + if (amx->error!=AMX_ERR_NONE) { + if (amx->error==AMX_ERR_SLEEP) { + amx->pri=pri; + amx->alt=alt; + amx->stk=stk; + amx->reset_stk=reset_stk; + amx->reset_hea=reset_hea; + return AMX_ERR_SLEEP; + } /* if */ + ABORT(amx,amx->error); + } /* if */ + break; +#endif + + /* overlay instructions */ +#if !defined AMX_NO_OVERLAY + case OP_CALL_OVL: + offs=(cell)((unsigned char *)cip-amx->code+sizeof(cell)); /* skip address */ + assert(offs>=0 && offs<(1L<<(sizeof(cell)*4))); + PUSH((offs<<(sizeof(cell)*4)) | amx->ovl_index); + amx->ovl_index=(int)*cip; + assert(amx->overlay!=NULL); + if ((i=amx->overlay(amx,amx->ovl_index))!=AMX_ERR_NONE) + ABORT(amx,i); + cip=(cell*)amx->code; + break; + case OP_RETN_OVL: + assert(amx->overlay!=NULL); + POP(frm); + POP(offs); + amx->ovl_index=offs & (((ucell)~0)>>4*sizeof(cell)); + offs=(ucell)offs >> (sizeof(cell)*4); + /* verify the index */ + stk+=_R(data,stk)+sizeof(cell); /* remove parameters from the stack */ + i=amx->overlay(amx,amx->ovl_index); /* reload overlay */ + if (i!=AMX_ERR_NONE || (long)offs>=amx->codesize) + ABORT(amx,AMX_ERR_MEMACCESS); + cip=(cell *)(amx->code+(int)offs); + break; + case OP_SWITCH_OVL: { + cell *cptr=JUMPREL(cip)+1; /* +1, to skip the "icasetbl" opcode */ + assert(*JUMPREL(cip)==OP_CASETBL_OVL); + amx->ovl_index=*(cptr+1); /* preset to "none-matched" case */ + i=(int)*cptr; /* number of records in the case table */ + for (cptr+=2; i>0 && *cptr!=pri; i--,cptr+=2) + /* nothing */; + if (i>0) + amx->ovl_index=*(cptr+1); /* case found */ + assert(amx->overlay!=NULL); + if ((i=amx->overlay(amx,amx->ovl_index))!=AMX_ERR_NONE) + ABORT(amx,i); + cip=(cell*)amx->code; + break; + } /* case */ +#endif + + /* supplemental and macro instructions */ +#if !defined AMX_NO_MACRO_INSTR + case OP_LIDX: + offs=pri*sizeof(cell)+alt; + /* verify address */ + if (offs>=hea && offs=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + pri=_R(data,offs); + break; + case OP_LIDX_B: + GETPARAM(offs); + offs=(pri << (int)offs)+alt; + /* verify address */ + if (offs>=hea && offs=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + pri=_R(data,offs); + break; + case OP_IDXADDR: + pri=pri*sizeof(cell)+alt; + break; + case OP_IDXADDR_B: + GETPARAM(offs); + pri=(pri << (int)offs)+alt; + break; + case OP_PUSH_C: + GETPARAM(offs); + PUSH(offs); + break; + case OP_PUSH: + GETPARAM(offs); + PUSH(_R(data,offs)); + break; + case OP_PUSH_S: + GETPARAM(offs); + PUSH(_R(data,frm+offs)); + break; + case OP_PUSH_ADR: + GETPARAM(offs); + PUSH(frm+offs); + break; + case OP_PUSHR_C: + GETPARAM(offs); + PUSH((intptr_t)data+offs); + break; + case OP_PUSHR_S: + GETPARAM(offs); + PUSH((intptr_t)data+_R(data,frm+offs)); + break; + case OP_PUSHR_ADR: + GETPARAM(offs); + PUSH((intptr_t)data+frm+offs); + break; + case OP_JEQ: + if (pri==alt) + cip=JUMPREL(cip); + else + SKIPPARAM(1); + break; + case OP_JNEQ: + if (pri!=alt) + cip=JUMPREL(cip); + else + SKIPPARAM(1); + break; + case OP_JSLESS: + if (prialt) + cip=JUMPREL(cip); + else + SKIPPARAM(1); + break; + case OP_JSGEQ: + if (pri>=alt) + cip=JUMPREL(cip); + else + SKIPPARAM(1); + break; + case OP_SDIV_INV: + if (alt==0) + ABORT(amx,AMX_ERR_DIVIDE); + /* use floored division and matching remainder */ + offs=alt; + #if defined TRUNC_SDIV + pri=pri/offs; + alt=pri%offs; + #else + val=pri; /* portable routine for truncated division */ + pri=IABS(pri)/IABS(offs); + if ((cell)(val ^ offs)<0) + pri=-pri; + alt=val-pri*offs; /* calculate the matching remainder */ + #endif + /* now "fiddle" with the values to get floored division */ + if (alt!=0 && (cell)(alt ^ offs)<0) { + pri--; + alt+=offs; + } /* if */ + break; + case OP_SUB_INV: + pri-=alt; + break; + case OP_ADD_C: + GETPARAM(offs); + pri+=offs; + break; + case OP_SMUL_C: + GETPARAM(offs); + pri*=offs; + break; + case OP_ZERO_PRI: + pri=0; + break; + case OP_ZERO_ALT: + alt=0; + break; + case OP_ZERO: + GETPARAM(offs); + _W(data,offs,0); + break; + case OP_ZERO_S: + GETPARAM(offs); + _W(data,frm+offs,0); + break; + case OP_EQ_C_PRI: + GETPARAM(offs); + pri= pri==offs ? 1 : 0; + break; + case OP_EQ_C_ALT: + GETPARAM(offs); + pri= alt==offs ? 1 : 0; + break; + case OP_INC: + GETPARAM(offs); + #if defined _R_DEFAULT + *(cell *)(data+(int)offs) += 1; + #else + val=_R(data,offs); + _W(data,offs,val+1); + #endif + break; + case OP_INC_S: + GETPARAM(offs); + #if defined _R_DEFAULT + *(cell *)(data+(int)(frm+offs)) += 1; + #else + val=_R(data,frm+offs); + _W(data,frm+offs,val+1); + #endif + break; + case OP_DEC: + GETPARAM(offs); + #if defined _R_DEFAULT + *(cell *)(data+(int)offs) -= 1; + #else + val=_R(data,offs); + _W(data,offs,val-1); + #endif + break; + case OP_DEC_S: + GETPARAM(offs); + #if defined _R_DEFAULT + *(cell *)(data+(int)(frm+offs)) -= 1; + #else + val=_R(data,frm+offs); + _W(data,frm+offs,val-1); + #endif + break; + case OP_SYSREQ_N: + GETPARAM(offs); + GETPARAM(val); + PUSH(val); + /* save a few registers */ + amx->cip=(cell)((unsigned char *)cip-amx->code); + amx->hea=hea; + amx->frm=frm; + amx->stk=stk; + i=amx->callback(amx,offs,&pri,(cell *)(data+(int)stk)); + stk+=val+4; + if (i!=AMX_ERR_NONE) { + if (i==AMX_ERR_SLEEP) { + amx->pri=pri; + amx->alt=alt; + amx->stk=stk; + amx->reset_stk=reset_stk; + amx->reset_hea=reset_hea; + return i; + } /* if */ + ABORT(amx,i); + } /* if */ + break; + case OP_PUSHM_C: + GETPARAM(val); + while (val--) { + GETPARAM(offs); + PUSH(offs); + } /* while */ + break; + case OP_PUSHM: + GETPARAM(val); + while (val--) { + GETPARAM(offs); + PUSH(_R(data,offs)); + } /* while */ + break; + case OP_PUSHM_S: + GETPARAM(val); + while (val--) { + GETPARAM(offs); + PUSH(_R(data,frm+offs)); + } /* while */ + break; + case OP_PUSHM_ADR: + GETPARAM(val); + while (val--) { + GETPARAM(offs); + PUSH(frm+offs); + } /* while */ + break; + case OP_PUSHRM_C: + GETPARAM(val); + while (val--) { + GETPARAM(offs); + PUSH((intptr_t)data+offs); + } /* while */ + break; + case OP_PUSHRM_S: + GETPARAM(val); + while (val--) { + GETPARAM(offs); + PUSH((intptr_t)data+_R(data,frm+offs)); + } /* while */ + break; + case OP_PUSHRM_ADR: + GETPARAM(val); + while (val--) { + GETPARAM(offs); + PUSH((intptr_t)data+frm+offs); + } /* while */ + break; + case OP_LOAD2: + GETPARAM(offs); + pri=_R(data,offs); + GETPARAM(offs); + alt=_R(data,offs); + break; + case OP_LOAD2_S: + GETPARAM(offs); + pri=_R(data,frm+offs); + GETPARAM(offs); + alt=_R(data,frm+offs); + break; + case OP_CONST: + GETPARAM(offs); + GETPARAM(val); + _W32(data,offs,val); + break; + case OP_CONST_S: + GETPARAM(offs); + GETPARAM(val); + _W32(data,frm+offs,val); + break; +#endif /* AMX_NO_MACRO_INSTR */ + +#if !defined AMX_NO_PACKED_OPC + case OP_LOAD_P_PRI: + GETPARAM_P(offs,op); + pri=_R(data,offs); + break; + case OP_LOAD_P_ALT: + GETPARAM_P(offs,op); + alt=_R(data,offs); + break; + case OP_LOAD_P_S_PRI: + GETPARAM_P(offs,op); + pri=_R(data,frm+offs); + break; + case OP_LOAD_P_S_ALT: + GETPARAM_P(offs,op); + alt=_R(data,frm+offs); + break; + case OP_LREF_P_S_PRI: + GETPARAM_P(offs,op); + offs=_R(data,frm+offs); + pri=_R(data,offs); + break; + case OP_LREF_P_S_ALT: + GETPARAM_P(offs,op); + offs=_R(data,frm+offs); + alt=_R(data,offs); + break; + case OP_LODB_P_I: + GETPARAM_P(offs,op); + goto __lodb_i; + case OP_CONST_P_PRI: + GETPARAM_P(pri,op); + break; + case OP_CONST_P_ALT: + GETPARAM_P(alt,op); + break; + case OP_ADDR_P_PRI: + GETPARAM_P(pri,op); + pri+=frm; + break; + case OP_ADDR_P_ALT: + GETPARAM_P(alt,op); + alt+=frm; + break; + case OP_STOR_P: + GETPARAM_P(offs,op); + _W(data,offs,pri); + break; + case OP_STOR_P_S: + GETPARAM_P(offs,op); + _W(data,frm+offs,pri); + break; + case OP_SREF_P_S: + GETPARAM_P(offs,op); + offs=_R(data,frm+offs); + _W(data,offs,pri); + break; + case OP_STRB_P_I: + GETPARAM_P(offs,op); + goto __strb_i; + case OP_LIDX_P_B: + GETPARAM_P(offs,op); + offs=(pri << (int)offs)+alt; + /* verify address */ + if (offs>=hea && offs=(ucell)amx->stp) + ABORT(amx,AMX_ERR_MEMACCESS); + pri=_R(data,offs); + break; + case OP_IDXADDR_P_B: + GETPARAM_P(offs,op); + pri=(pri << (int)offs)+alt; + break; + case OP_ALIGN_P_PRI: + GETPARAM_P(offs,op); + #if BYTE_ORDER==LITTLE_ENDIAN + if ((size_t)offs(ucell)offs) { + amx->cip=(cell)((unsigned char *)cip-amx->code); + ABORT(amx,AMX_ERR_BOUNDS); + } /* if */ + break; +#endif /* AMX_NO_PACKED_OPC */ + default: + assert(0); /* invalid instructions should already have been caught in VerifyPcode() */ + ABORT(amx,AMX_ERR_INVINSTR); + } /* switch */ + } /* for */ +#endif /* AMX_ALTCORE */ +} + +#endif /* AMX_EXEC */ + +#if defined AMX_SETCALLBACK +int AMXAPI amx_SetCallback(AMX *amx,AMX_CALLBACK callback) +{ + assert(amx!=NULL); + assert(callback!=NULL); + amx->callback=callback; + return AMX_ERR_NONE; +} +#endif /* AMX_SETCALLBACK */ + +#if defined AMX_SETDEBUGHOOK +int AMXAPI amx_SetDebugHook(AMX *amx,AMX_DEBUG debug) +{ + assert(amx!=NULL); + amx->debug=debug; + return AMX_ERR_NONE; +} +#endif /* AMX_SETDEBUGHOOK */ + +#if defined AMX_RAISEERROR +int AMXAPI amx_RaiseError(AMX *amx, int error) +{ + assert(error>0); + amx->error=error; + return AMX_ERR_NONE; +} +#endif /* AMX_RAISEERROR */ + +#if defined AMX_ALLOT || defined AMX_PUSHXXX +int AMXAPI amx_Allot(AMX *amx,int cells,cell **address) +{ + AMX_HEADER *hdr; + unsigned char *data; + + assert(amx!=NULL); + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + data=(amx->data!=NULL) ? amx->data : amx->base+(int)hdr->dat; + + if (amx->stk - amx->hea - cells*sizeof(cell) < STKMARGIN) + return AMX_ERR_MEMORY; + if (address!=NULL) + *address=(cell *)(data+(int)amx->hea); + amx->hea+=cells*sizeof(cell); + return AMX_ERR_NONE; +} + +int AMXAPI amx_Release(AMX *amx,cell *address) +{ + AMX_HEADER *hdr; + unsigned char *data; + cell amx_addr; + + assert(amx!=NULL); + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + data=(amx->data!=NULL) ? amx->data : amx->base+(int)hdr->dat; + amx_addr=(cell)((unsigned char*)address-data); + if (amx->hea>amx_addr) + amx->hea=amx_addr; + return AMX_ERR_NONE; +} +#endif /* AMX_ALLOT || AMX_PUSHXXX */ + +#if defined AMX_VERIFYADDR +int AMXAPI amx_VerifyAddress(AMX *amx,cell *address) +{ + AMX_HEADER *hdr; + unsigned char *data; + + assert(amx!=NULL); + hdr=(AMX_HEADER *)amx->base; + assert(hdr!=NULL); + assert(hdr->magic==AMX_MAGIC); + data=(amx->data!=NULL) ? amx->data : amx->base+(int)hdr->dat; + + return (unsigned char*)address>=data && (unsigned char*)addressstp; +} +#endif /* AMX_VERIFYADDR */ + +#if defined AMX_XXXSTRING || defined AMX_UTF8XXX + +#define CHARBITS (8*sizeof(char)) +#if PAWN_CELL_SIZE==16 + #define CHARMASK (0xffffu << 8*(2-sizeof(char))) +#elif PAWN_CELL_SIZE==32 + #define CHARMASK (0xffffffffuL << 8*(4-sizeof(char))) +#elif PAWN_CELL_SIZE==64 + #define CHARMASK (0xffffffffffffffffuLL << 8*(8-sizeof(char))) +#else + #error Unsupported cell size +#endif + +int AMXAPI amx_StrLen(const cell *cstr, int *length) +{ + int len; + #if BYTE_ORDER==LITTLE_ENDIAN + cell c; + #endif + + assert(length!=NULL); + if (cstr==NULL) { + *length=0; + return AMX_ERR_PARAMS; + } /* if */ + + if ((ucell)*cstr>UNPACKEDMAX) { + /* packed string */ + assert_static(sizeof(char)==1); + len=(int)strlen((char *)cstr); /* find '\0' */ + assert(check_endian()); + #if BYTE_ORDER==LITTLE_ENDIAN + /* on Little Endian machines, toggle the last bytes */ + c=cstr[len/sizeof(cell)]; /* get last cell */ + len=len - len % sizeof(cell); /* len = multiple of "cell" bytes */ + while ((c & CHARMASK)!=0) { + len++; + c=(ucell)c << 8*sizeof(char); + } /* if */ + #endif + } else { + for (len=0; cstr[len]!=0; len++) + /* nothing */; + } /* if */ + *length = len; + return AMX_ERR_NONE; +} +#endif + +#if defined AMX_XXXSTRING || defined AMX_PUSHXXX +int AMXAPI amx_SetString(cell *dest,const char *source,int pack,int use_wchar,size_t size) +{ /* the memory blocks should not overlap */ + int i; + size_t len; + + assert_static(UNLIMITED>0); + #if defined AMX_ANSIONLY + (void)use_wchar; + len=strlen(source); + #else + len= use_wchar ? wcslen((const wchar_t*)source) : strlen(source); + #endif + if (pack) { + /* create a packed string */ + if (size=size*sizeof(cell)) + len=size*sizeof(cell)-1; + dest[len/sizeof(cell)]=0; /* clear last bytes of last (semi-filled) cell*/ + #if defined AMX_ANSIONLY + memcpy(dest,source,len); + #else + if (use_wchar) { + for (i=0; i<(int)len; i++) + ((char*)dest)[i]=(char)(((wchar_t*)source)[i]); + } else { + memcpy(dest,source,len); + } /* if */ + #endif + /* On Big Endian machines, the characters are well aligned in the + * cells; on Little Endian machines, we must swap all cells. + */ + assert(check_endian()); + #if BYTE_ORDER==LITTLE_ENDIAN + for (i=(int)(len/sizeof(cell)); i>=0; i--) + amx_SwapCell((ucell *)&dest[i]); + #endif + } else { + /* create an unpacked string */ + if (size=size) + len=size-1; + #if defined AMX_ANSIONLY + for (i=0; i<(int)len; i++) + dest[i]=(cell)source[i]; + #else + if (use_wchar) { + for (i=0; i<(int)len; i++) + dest[i]=(cell)(((wchar_t*)source)[i]); + } else { + for (i=0; i<(int)len; i++) + dest[i]=(cell)source[i]; + } /* if */ + #endif + dest[len]=0; + } /* if */ + return AMX_ERR_NONE; +} +#endif + +#if defined AMX_XXXSTRING +int AMXAPI amx_GetString(char *dest,const cell *source,int use_wchar,size_t size) +{ + int len=0; + #if defined AMX_ANSIONLY + (void)use_wchar; /* unused parameter (if ANSI only) */ + #endif + if ((ucell)*source>UNPACKEDMAX) { + /* source string is packed */ + cell c=0; /* initialize to 0 to avoid a compiler warning */ + int i=sizeof(cell)-1; + char ch; + while ((size_t)len> i*CHARBITS); + if (ch=='\0') + break; /* terminating zero character found */ + #if defined AMX_ANSIONLY + dest[len++]=ch; + #else + if (use_wchar) + ((wchar_t*)dest)[len++]=ch; + else + dest[len++]=ch; + #endif + i=(i+sizeof(cell)-1) % sizeof(cell); + } /* while */ + } else { + /* source string is unpacked */ + #if defined AMX_ANSIONLY + while (*source!=0 && (size_t)len=size) + len=(int)size-1; + if (len>=0) { + #if defined AMX_ANSIONLY + dest[len]='\0'; + #else + if (use_wchar) + ((wchar_t*)dest)[len]=0; + else + dest[len]='\0'; + #endif + } /* IF */ + return AMX_ERR_NONE; +} +#endif /* AMX_XXXSTRING */ + +#if defined AMX_UTF8XXX + #if defined __BORLANDC__ + #pragma warn -amb -8000 /* ambiguous operators need parentheses */ + #endif +/* amx_UTF8Get() + * Extract a single UTF-8 encoded character from a string and return a pointer + * to the character just behind that UTF-8 character. The parameters "endptr" + * and "value" may be NULL. + * If the code is not valid UTF-8, "endptr" has the value of the input + * parameter "string" and "value" is zero. + */ +int AMXAPI amx_UTF8Get(const char *string, const char **endptr, cell *value) +{ +static const char utf8_count[16]={ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4 }; +static const long utf8_lowmark[5] = { 0x80, 0x800, 0x10000L, 0x200000L, 0x4000000L }; + unsigned char c; + cell result; + int followup; + + assert(string!=NULL); + if (value!=NULL) /* preset, in case of an error */ + *value=0; + if (endptr!=NULL) + *endptr=string; + + c = *(const unsigned char*)string++; + if (c<0x80) { + /* ASCII */ + result=c; + } else { + if (c<0xc0 || c>=0xfe) + return AMX_ERR_PARAMS; /* invalid or "follower" code, quit with error */ + /* At this point we know that the two top bits of c are ones. The two + * bottom bits are always part of the code. We only need to consider + * the 4 remaining bits; i.e., a 16-byte table. This is "utf8_count[]". + * (Actually the utf8_count[] table records the number of follow-up + * bytes minus 1. This is just for convenience.) + */ + assert((c & 0xc0)==0xc0); + followup=(int)utf8_count[(c >> 2) & 0x0f]; + /* The mask depends on the code length; this is just a very simple + * relation. + */ + #define utf8_mask (0x1f >> followup) + result= c & utf8_mask; + /* Collect the follow-up codes using a drop-through switch statement; + * this avoids a loop. In each case, verify the two leading bits. + */ + assert(followup>=0 && followup<=4); + switch (followup) { + case 4: + if (((c=*string++) & 0xc0) != 0x80) goto error; + result = (result << 6) | c & 0x3f; + case 3: + if (((c=*string++) & 0xc0) != 0x80) goto error; + result = (result << 6) | c & 0x3f; + case 2: + if (((c=*string++) & 0xc0) != 0x80) goto error; + result = (result << 6) | c & 0x3f; + case 1: + if (((c=*string++) & 0xc0) != 0x80) goto error; + result = (result << 6) | c & 0x3f; + case 0: + if (((c=*string++) & 0xc0) != 0x80) goto error; + result = (result << 6) | c & 0x3f; + } /* switch */ + /* Do additional checks: shortest encoding & reserved positions. The + * lowmark limits also depends on the code length; it can be read from + * a table with 5 elements. This is "utf8_lowmark[]". + */ + if (result=0xd800 && result<=0xdfff || result==0xfffe || result==0xffff) + goto error; + } /* if */ + + if (value!=NULL) + *value=result; + if (endptr!=NULL) + *endptr=string; + + return AMX_ERR_NONE; + +error: + return AMX_ERR_PARAMS; +} + +/* amx_UTF8Put() + * Encode a single character into a byte string. The character may result in + * a string of up to 6 bytes. The function returns an error code if "maxchars" + * is lower than the required number of characters; in this case nothing is + * stored. + * The function does not zero-terminate the string. + */ +int AMXAPI amx_UTF8Put(char *string, char **endptr, int maxchars, cell value) +{ + assert(string!=NULL); + if (endptr!=NULL) /* preset, in case of an error */ + *endptr=string; + + if (value<0x80) { + /* 0xxxxxxx */ + if (maxchars < 1) goto error; + *string++ = (char)value; + } else if (value<0x800) { + /* 110xxxxx 10xxxxxx */ + if (maxchars < 2) goto error; + *string++ = (char)((value>>6) & 0x1f | 0xc0); + *string++ = (char)(value & 0x3f | 0x80); + } else if (value<0x10000) { + /* 1110xxxx 10xxxxxx 10xxxxxx (16 bits, BMP plane) */ + if (maxchars < 3) goto error; + if (value>=0xd800 && value<=0xdfff || value==0xfffe || value==0xffff) + goto error; /* surrogate pairs and invalid characters */ + *string++ = (char)((value>>12) & 0x0f | 0xe0); + *string++ = (char)((value>>6) & 0x3f | 0x80); + *string++ = (char)(value & 0x3f | 0x80); + } else if (value<0x200000) { + /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (maxchars < 4) goto error; + *string++ = (char)((value>>18) & 0x07 | 0xf0); + *string++ = (char)((value>>12) & 0x3f | 0x80); + *string++ = (char)((value>>6) & 0x3f | 0x80); + *string++ = (char)(value & 0x3f | 0x80); + } else if (value<0x4000000) { + /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (maxchars < 5) goto error; + *string++ = (char)((value>>24) & 0x03 | 0xf8); + *string++ = (char)((value>>18) & 0x3f | 0x80); + *string++ = (char)((value>>12) & 0x3f | 0x80); + *string++ = (char)((value>>6) & 0x3f | 0x80); + *string++ = (char)(value & 0x3f | 0x80); + } else { + /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx (31 bits) */ + if (maxchars < 6) goto error; + *string++ = (char)((value>>30) & 0x01 | 0xfc); + *string++ = (char)((value>>24) & 0x3f | 0x80); + *string++ = (char)((value>>18) & 0x3f | 0x80); + *string++ = (char)((value>>12) & 0x3f | 0x80); + *string++ = (char)((value>>6) & 0x3f | 0x80); + *string++ = (char)(value & 0x3f | 0x80); + } /* if */ + + if (endptr!=NULL) + *endptr=string; + return AMX_ERR_NONE; + +error: + return AMX_ERR_PARAMS; +} + +/* amx_UTF8Check() + * Run through a zero-terminated string and check the validity of the UTF-8 + * encoding. The function returns an error code, it is AMX_ERR_NONE if the + * string is valid UTF-8 (or valid ASCII for that matter). + */ +int AMXAPI amx_UTF8Check(const char *string, int *length) +{ + int err=AMX_ERR_NONE; + int len=0; + while (err==AMX_ERR_NONE && *string!='\0') { + err=amx_UTF8Get(string,&string,NULL); + len++; + } /* while */ + if (length!=NULL) + *length=len; + return err; +} + +/* amx_UTF8Len() + * Run through a wide string and return how many 8-bit characters are needed to + * store the string in UTF-8 format. The returned cound excludes the terminating + * zero byte. The function returns an error code. + */ +int AMXAPI amx_UTF8Len(const cell *cstr, int *length) +{ + int err; + + assert(length!=NULL); + err=amx_StrLen(cstr, length); + if (err==AMX_ERR_NONE && (ucell)*cstr<=UNPACKEDMAX) { + char buffer[10]; /* maximum UTF-8 code is 6 characters */ + char *endptr; + int len=*length, count=0; + while (len-->0) { + amx_UTF8Put(buffer, &endptr, sizeof buffer, *cstr++); + count+=(int)(endptr-buffer); + } /* while */ + *length=count; + } /* while */ + return err; +} +#endif /* AMX_UTF8XXX */ diff --git a/src/pawn/amx.h b/src/pawn/amx.h new file mode 100644 index 0000000000..e4a633a873 --- /dev/null +++ b/src/pawn/amx.h @@ -0,0 +1,569 @@ +/* Pawn Abstract Machine (for the Pawn language) + * + * Copyright (c) CompuPhase, 1997-2023 + * + * 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. + * + * Version: $Id: amx.h 7491 2025-08-25 09:14:23Z thiadmer $ + */ + +#ifndef AMX_H_INCLUDED +#define AMX_H_INCLUDED + +#include /* for size_t */ +#include + +#if (defined __linux || defined __linux__) && !defined __LINUX__ + #define __LINUX__ +#endif +#if defined FREEBSD && !defined __FreeBSD__ + #define __FreeBSD__ +#endif +#if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + #include +#endif + +#if defined __GNUC__ + #define GCC_VERSION (__GNUC__ * 10000 \ + + __GNUC_MINOR__ * 100 \ + + __GNUC_PATCHLEVEL__) +#endif + +#if !defined HAVE_STDINT_H + #if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) \ + || defined __cplusplus \ + || defined __GNUC__ || defined __LCC__ || defined __DMC__ \ + || (defined __WATCOMC__ && __WATCOMC__ >= 1200) + #define HAVE_STDINT_H 1 + #endif +#endif +#if !defined HAVE_INTTYPES_H + #if defined __FreeBSD__ || defined __APPLE__ + #define HAVE_INTTYPES_H 1 + #endif +#endif +#if defined HAVE_STDINT_H + #include +#elif defined HAVE_INTTYPES_H + #include +#else + #if defined __MACH__ + #include + #endif + typedef short int int16_t; + typedef unsigned short int uint16_t; + #if defined SN_TARGET_PS2 + typedef int int32_t; + typedef unsigned int uint32_t; + #else + typedef long int int32_t; + typedef unsigned long int uint32_t; + #endif + #if defined __WIN32__ || defined _WIN32 || defined WIN32 + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; + #define HAVE_I64 + #endif + #if !defined _INTPTR_T_DEFINED + #if defined _LP64 || defined WIN64 || defined _WIN64 + typedef __int64 intptr_t; + #else + typedef int32_t intptr_t; + #endif + #endif +#endif +#if defined _LP64 || defined WIN64 || defined _WIN64 + #if !defined __64BIT__ + #define __64BIT__ + #endif +#endif +#if defined INTPTR_MAX + #if INTPTR_MAX == INT64_MAX + #if !defined __64BIT__ + #define __64BIT__ + #endif + #elif INTPTR_MAX == INT32_MAX + #if !defined __32BIT__ + #define __32BIT__ + #endif + #endif +#endif + +#if !defined HAVE_ALLOCA_H + #if defined __MINGW32__ + #define HAVE_ALLOCA_H 0 + #elif defined __FreeBSD__ + #undef HAVE_ALLOCA_H + #elif defined __GNUC__ || defined __LCC__ || defined __DMC__ || defined __ARMCC_VERSION + #define HAVE_ALLOCA_H 1 + #elif defined __WATCOMC__ && __WATCOMC__ >= 1200 + #define HAVE_ALLOCA_H 1 + #endif +#endif +#if defined HAVE_ALLOCA_H && HAVE_ALLOCA_H + #include +#elif defined __BORLANDC__ + #include +#endif +#if defined __WIN32__ || defined _WIN32 || defined WIN32 /* || defined __MSDOS__ */ + #if !defined alloca + #define alloca(n) _alloca(n) + #endif +#endif + +#if !defined assert_static + #if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 201112) || GCC_VERSION >= 40600 + #define assert_static(test) _Static_assert(test, "assert") + #else + /* see "Compile-Time Assertions" by Greg Miller, + * (with modifications to port it to C) + */ + #define _ASSERT_STATIC_SYMBOL_INNER(line) __ASSERT_STATIC_ ## line + #define _ASSERT_STATIC_SYMBOL(line) _ASSERT_STATIC_SYMBOL_INNER(line) + #define assert_static(test) \ + do { \ + typedef char _ASSERT_STATIC_SYMBOL(__LINE__)[ ((test) ? 1 : -1) ]; \ + } while (0) + #endif +#endif + +#if defined __cplusplus +extern "C" { +#endif + +#if defined PAWN_DLL + #if !defined AMX_NATIVE_CALL + #define AMX_NATIVE_CALL __stdcall + #endif + #if !defined AMXAPI + #define AMXAPI __stdcall + #endif + #if !defined AMXEXPORT + #define AMXEXPORT __declspec(dllexport) + #endif +#endif + +/* calling convention for native functions */ +#if !defined AMX_NATIVE_CALL + #define AMX_NATIVE_CALL +#endif +/* calling convention for all interface functions and callback functions */ +#if !defined AMXAPI + #if defined STDECL + #define AMXAPI __stdcall + #elif defined CDECL + #define AMXAPI __cdecl + #elif defined GCC_HASCLASSVISIBILITY && defined __cplusplus + #define AMXAPI __attribute__((visibility("default"))) + #else + #define AMXAPI + #endif +#endif +#if !defined AMXEXPORT + #define AMXEXPORT +#endif + +/* File format version (in CUR_FILE_VERSION) + * 0 original version + * 1 opcodes JUMP.pri, SWITCH and CASETBL + * 2 compressed files + * 3 public variables + * 4 opcodes SWAP.pri/alt and PUSHADDR + * 5 tagnames table + * 6 reformatted header + * 7 name table, opcodes SYMTAG & SYSREQ.D + * 8 opcode BREAK, renewed debug interface + * 9 macro opcodes + * 10 position-independent code, overlays, packed instructions + * 11 relocating instructions for the native interface, reorganized instruction set + * MIN_FILE_VERSION is the lowest file version number that the current AMX + * implementation supports. If the AMX file header gets new fields, this number + * often needs to be incremented. MIN_AMX_VERSION is the lowest AMX version that + * is needed to support the current file version. When there are new opcodes, + * this number needs to be incremented. + * The file version supported by the JIT may run behind MIN_AMX_VERSION. So + * there is an extra constant for it: MAX_FILE_VER_JIT. + */ +#define CUR_FILE_VERSION 11 /* current file version; also the current AMX version */ +#define MIN_FILE_VERSION 11 /* lowest supported file format version for the current AMX version */ +#define MIN_AMX_VERSION 11 /* minimum AMX version needed to support the current file format */ +#define MAX_FILE_VER_JIT 11 /* file version supported by the JIT */ +#define MIN_AMX_VER_JIT 11 /* AMX version supported by the JIT */ + +#if !defined PAWN_CELL_SIZE +# if __SIZEOF_POINTER__==8 + #define PAWN_CELL_SIZE 64 /* use 64-bit cells for 64-bit systems */ +# else + #define PAWN_CELL_SIZE 32 /* by default, use 32-bit cells */ +# endif +#endif +#if PAWN_CELL_SIZE==16 + typedef uint16_t ucell; + typedef int16_t cell; +#elif PAWN_CELL_SIZE==32 + typedef uint32_t ucell; + typedef int32_t cell; +#elif PAWN_CELL_SIZE==64 + typedef uint64_t ucell; + typedef int64_t cell; +#else + #error Unsupported cell size (PAWN_CELL_SIZE) +#endif + +#define UNPACKEDMAX (((cell)1 << (sizeof(cell)-1)*8) - 1) +#define UNLIMITED (~1u >> 1) + +struct tagAMX; +typedef cell (AMX_NATIVE_CALL *AMX_NATIVE)(struct tagAMX *amx, const cell *params); +typedef int (AMXAPI *AMX_CALLBACK)(struct tagAMX *amx, cell index, + cell *result, const cell *params); +typedef int (AMXAPI *AMX_DEBUG)(struct tagAMX *amx); +typedef int (AMXAPI *AMX_OVERLAY)(struct tagAMX *amx, int index); +typedef int (AMXAPI *AMX_IDLE)(struct tagAMX *amx, int AMXAPI Exec(struct tagAMX *, cell *, int)); +#if !defined _FAR + #define _FAR +#endif + +#if defined _MSC_VER + #pragma warning(disable:4100) /* "'%$S' : unreferenced formal parameter" */ + #pragma warning(disable:4103) /* disable warning message 4103 that complains about pragma pack in a header file */ + #pragma warning(disable:4127) /* "conditional expression is constant" (needed for static_assert) */ + #pragma warning(disable:4996) /* POSIX name is deprecated */ +#elif defined __GNUC__ + #pragma GCC diagnostic ignored "-Waddress-of-packed-member" +#elif defined __clang__ + #pragma GCC diagnostic ignored "-Wlogical-op-parentheses" + #pragma GCC diagnostic ignored "-Wbitwise-op-parentheses" + #pragma clang diagnostic ignored "-Wint-to-pointer-cast" +#endif + +/* Some compilers do not support the #pragma align, which should be fine. Some + * compilers give a warning on unknown #pragmas, which is not so fine... + */ +#if (defined SN_TARGET_PS2 || defined __GNUC__) && !defined AMX_NO_ALIGN + #define AMX_NO_ALIGN +#endif + +#if defined __GNUC__ + #define PACKED __attribute__((packed)) +#else + #define PACKED +#endif + +#if !defined AMX_NO_ALIGN + #if defined __LINUX__ || defined __FreeBSD__ || defined __APPLE__ + #pragma pack(1) /* structures must be packed (byte-aligned) */ + #elif defined MACOS && defined __MWERKS__ + #pragma options align=mac68k + #else + #pragma pack(push) + #pragma pack(1) /* structures must be packed (byte-aligned) */ + #if defined __BORLANDC__ + #pragma option -a- /* "pack" pragma for older Borland compilers */ + #endif + #endif +#endif + +typedef struct tagAMX_NATIVE_INFO { + const char _FAR *name; + AMX_NATIVE func; +} PACKED AMX_NATIVE_INFO; + +#if !defined AMX_USERNUM +#define AMX_USERNUM 4 +#endif +#define sEXPMAX 19 /* maximum name length for file version <= 6 */ +#define sNAMEMAX 31 /* maximum name length of symbol name */ + +typedef struct tagFUNCSTUB { + uint32_t address; + uint32_t nameofs; +} PACKED AMX_FUNCSTUB; + +typedef struct tagOVERLAYINFO { + int32_t offset; /* offset relative to the start of the code block */ + int32_t size; /* size in bytes */ +} PACKED AMX_OVERLAYINFO; + +/* The AMX structure is the internal structure for many functions. Not all + * fields are valid at all times; many fields are cached in local variables. + */ +typedef struct tagAMX { + unsigned char _FAR *base; /* points to the AMX header, perhaps followed by P-code and data */ + unsigned char _FAR *code; /* points to P-code block, possibly in ROM or in an overlay pool */ + unsigned char _FAR *data; /* points to separate data+stack+heap, may be NULL */ + AMX_CALLBACK callback; /* native function callback */ + AMX_DEBUG debug; /* debug callback */ + AMX_OVERLAY overlay; /* overlay reader callback */ + /* for external functions a few registers must be accessible from the outside */ + cell cip; /* instruction pointer: relative to base + amxhdr->cod */ + cell frm; /* stack frame base: relative to base + amxhdr->dat */ + cell hea; /* top of the heap: relative to base + amxhdr->dat */ + cell hlw; /* bottom of the heap: relative to base + amxhdr->dat */ + cell stk; /* stack pointer: relative to base + amxhdr->dat */ + cell stp; /* top of the stack: relative to base + amxhdr->dat */ + int flags; /* current status, see amx_Flags() */ + /* user data */ + #if AMX_USERNUM > 0 + long usertags[AMX_USERNUM]; + void _FAR *userdata[AMX_USERNUM]; + #endif + /* native functions can raise an error */ + int error; + /* passing parameters requires a "count" field */ + int paramcount; + /* the sleep opcode needs to store the full AMX status */ + cell pri; + cell alt; + cell reset_stk; + cell reset_hea; + /* extra fields for increased performance */ + cell sysreq_d; /* relocated address/value for the SYSREQ.D opcode */ + /* fields for overlay support and JIT support */ + int ovl_index; /* current overlay index */ + long codesize; /* size of the overlay, or estimated memory footprint of the native code */ + #if defined AMX_JIT + /* support variables for the JIT */ + int reloc_size; /* required temporary buffer for relocations */ + #endif +} PACKED AMX; + +/* The AMX_HEADER structure is both the memory format as the file format. The + * structure is used internaly. + */ +typedef struct tagAMX_HEADER { + int32_t size; /* size of the "file" */ + uint16_t magic; /* signature */ + char file_version; /* file format version */ + char amx_version; /* required version of the AMX */ + int16_t flags; + int16_t defsize; /* size of a definition record */ + int32_t cod; /* initial value of COD - code block */ + int32_t dat; /* initial value of DAT - data block */ + int32_t hea; /* initial value of HEA - start of the heap */ + int32_t stp; /* initial value of STP - stack top */ + int32_t cip; /* initial value of CIP - the instruction pointer */ + int32_t publics; /* offset to the "public functions" table */ + int32_t natives; /* offset to the "native functions" table */ + int32_t libraries; /* offset to the table of libraries */ + int32_t pubvars; /* offset to the "public variables" table */ + int32_t tags; /* offset to the "public tagnames" table */ + int32_t nametable; /* offset to the name table */ + int32_t overlays; /* offset to the overlay table */ +} PACKED AMX_HEADER; + +#define AMX_MAGIC_16 0xf1e2 +#define AMX_MAGIC_32 0xf1e0 +#define AMX_MAGIC_64 0xf1e1 +#if PAWN_CELL_SIZE==16 + #define AMX_MAGIC AMX_MAGIC_16 +#elif PAWN_CELL_SIZE==32 + #define AMX_MAGIC AMX_MAGIC_32 +#elif PAWN_CELL_SIZE==64 + #define AMX_MAGIC AMX_MAGIC_64 +#endif + +enum { + AMX_ERR_NONE, + /* reserve the first 15 error codes for exit codes of the abstract machine */ + AMX_ERR_EXIT, /* forced exit */ + AMX_ERR_ASSERT, /* assertion failed */ + AMX_ERR_STACKERR, /* stack/heap collision */ + AMX_ERR_BOUNDS, /* index out of bounds */ + AMX_ERR_MEMACCESS, /* invalid memory access */ + AMX_ERR_INVINSTR, /* invalid instruction */ + AMX_ERR_STACKLOW, /* stack underflow */ + AMX_ERR_HEAPLOW, /* heap underflow */ + AMX_ERR_CALLBACK, /* no callback, or invalid callback */ + AMX_ERR_NATIVE, /* native function failed */ + AMX_ERR_DIVIDE, /* divide by zero */ + AMX_ERR_SLEEP, /* go into sleepmode - code can be restarted */ + AMX_ERR_INVSTATE, /* no implementation for this state, no fall-back */ + + AMX_ERR_MEMORY = 16, /* out of memory */ + AMX_ERR_FORMAT, /* invalid file format */ + AMX_ERR_VERSION, /* file is for a newer version of the AMX */ + AMX_ERR_NOTFOUND, /* function not found */ + AMX_ERR_INDEX, /* invalid index parameter (bad entry point) */ + AMX_ERR_DEBUG, /* debugger cannot run */ + AMX_ERR_INIT, /* AMX not initialized (or doubly initialized) */ + AMX_ERR_USERDATA, /* unable to set user data field (table full) */ + AMX_ERR_INIT_JIT, /* cannot initialize the JIT */ + AMX_ERR_PARAMS, /* parameter error */ + AMX_ERR_DOMAIN, /* domain error, expression result does not fit in range */ + AMX_ERR_GENERAL, /* general error (unknown or unspecific error) */ + AMX_ERR_OVERLAY /* overlays are unsupported (JIT) or uninitialized */ +}; + +#define AMX_FLAG_OVERLAY 0x01 /* all function calls use overlays */ +#define AMX_FLAG_DEBUG 0x02 /* symbolic info. available */ +#define AMX_FLAG_NOCHECKS 0x04 /* no array bounds checking; no BREAK opcodes */ +#define AMX_FLAG_SLEEP 0x08 /* script uses the sleep instruction (possible re-entry or power-down mode) */ +#define AMX_FLAG_CRYPT 0x10 /* file is encrypted */ +#define AMX_FLAG_DSEG_INIT 0x20 /* data section is explicitly initialized */ +#define AMX_FLAG_SYSREQN 0x800 /* script uses new (optimized) version of SYSREQ opcode */ +#define AMX_FLAG_NTVREG 0x1000 /* all native functions are registered */ +#define AMX_FLAG_JITC 0x2000 /* abstract machine is JIT compiled */ +#define AMX_FLAG_VERIFY 0x4000 /* busy verifying P-code */ +#define AMX_FLAG_INIT 0x8000 /* AMX has been initialized */ + +#define AMX_EXEC_MAIN (-1) /* start at program entry point */ +#define AMX_EXEC_CONT (-2) /* continue from last address */ + +#define AMX_USERTAG(a,b,c,d) ((a) | ((b)<<8) | ((long)(c)<<16) | ((long)(d)<<24)) + +/* for native functions that use floating point parameters, the following + * two macros are convenient for casting a "cell" into a "float" type _without_ + * changing the bit pattern + */ +#if PAWN_CELL_SIZE==32 + #define amx_ftoc(f) ( * ((cell*)&f) ) /* float to cell */ + #define amx_ctof(c) ( * ((float*)&c) ) /* cell to float */ +#elif PAWN_CELL_SIZE==64 + #define amx_ftoc(f) ( * ((cell*)&f) ) /* float to cell */ + #define amx_ctof(c) ( * ((double*)&c) ) /* cell to float */ +#else + // amx_ftoc() and amx_ctof() cannot be used +#endif + +/* when a pointer cannot be stored in a cell, cells that hold relocated + * addresses need to be expanded + */ +#if defined __64BIT__ && PAWN_CELL_SIZE<64 + #define CELLMASK (((int64_t)1 << PAWN_CELL_SIZE) - 1) + #define amx_Address(amx,addr) \ + (cell*)(((int64_t)((amx)->data ? (amx)->data : (amx)->code) & ~CELLMASK) | ((int64_t)(addr) & CELLMASK)) +#elif defined __32BIT__ && PAWN_CELL_SIZE<32 + #define CELLMASK ((1L << PAWN_CELL_SIZE) - 1) + #define amx_Address(amx,addr) \ + (cell*)(((int32_t)((amx)->data ? (amx)->data : (amx)->code) & ~CELLMASK) | ((int32_t)(addr) & CELLMASK)) +#else + #define amx_Address(amx,addr) ((void)(amx),(cell*)(addr)) +#endif + +#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L + /* C99: use variable-length arrays */ + #define amx_StrParam_Type(amx,param,result,type) \ + int result##_length_; \ + amx_StrLen(amx_Address(amx,param),&result##_length_); \ + char result##_vla_[(result##_length_+1)*sizeof(*(result))]; \ + (result)=(type)result##_vla_; \ + amx_GetString((char*)(result),amx_Address(amx,param), \ + sizeof(*(result))>1,result##_length_+1) + #define amx_StrParam(amx,param,result) \ + amx_StrParam_Type(amx,param,result,void*) +#else + /* macro using alloca() */ + #define amx_StrParam_Type(amx,param,result,type) \ + do { \ + int result##_length_; \ + amx_StrLen(amx_Address(amx,param),&result##_length_); \ + if (result##_length_>0 && \ + ((result)=(type)alloca((result##_length_+1)*sizeof(*(result))))!=NULL) \ + amx_GetString((char*)(result),amx_Address(amx,param), \ + sizeof(*(result))>1,result##_length_+1); \ + else (result) = NULL; \ + } while (0) + #define amx_StrParam(amx,param,result) \ + amx_StrParam_Type(amx,param,result,void*) +#endif + +uint16_t * AMXAPI amx_Align16(uint16_t *v); +uint32_t * AMXAPI amx_Align32(uint32_t *v); +#if defined _I64_MAX || defined INT64_MAX || defined HAVE_I64 + uint64_t * AMXAPI amx_Align64(uint64_t *v); +#endif +int AMXAPI amx_Allot(AMX *amx, int cells, cell **address); +int AMXAPI amx_Callback(AMX *amx, cell index, cell *result, const cell *params); +int AMXAPI amx_Cleanup(AMX *amx); +int AMXAPI amx_Clone(AMX *amxClone, AMX *amxSource, void *data); +int AMXAPI amx_Exec(AMX *amx, cell *retval, int index); +int AMXAPI amx_FindNative(AMX *amx, const char *name, int *index); +int AMXAPI amx_FindPublic(AMX *amx, const char *name, int *index); +int AMXAPI amx_FindPubVar(AMX *amx, const char *name, cell **address); +int AMXAPI amx_FindTagId(AMX *amx, cell tag_id, char *tagname); +int AMXAPI amx_Flags(AMX *amx,uint16_t *flags); +int AMXAPI amx_GetNative(AMX *amx, int index, char *name); +int AMXAPI amx_GetPublic(AMX *amx, int index, char *name, ucell *address); +int AMXAPI amx_GetPubVar(AMX *amx, int index, char *name, cell **address); +int AMXAPI amx_GetString(char *dest,const cell *source, int use_wchar, size_t size); +int AMXAPI amx_GetTag(AMX *amx, int index, char *tagname, cell *tag_id); +int AMXAPI amx_GetUserData(AMX *amx, long tag, void **ptr); +int AMXAPI amx_Init(AMX *amx, void *program); +int AMXAPI amx_InitJIT(AMX *amx, void *reloc_table, void *native_code); +int AMXAPI amx_MemInfo(AMX *amx, long *codesize, long *datasize, long *stackheap); +int AMXAPI amx_NameLength(AMX *amx, int *length); +AMX_NATIVE_INFO * AMXAPI amx_NativeInfo(const char *name, AMX_NATIVE func); +int AMXAPI amx_NumNatives(AMX *amx, int *number); +int AMXAPI amx_NumPublics(AMX *amx, int *number); +int AMXAPI amx_NumPubVars(AMX *amx, int *number); +int AMXAPI amx_NumTags(AMX *amx, int *number); +int AMXAPI amx_Push(AMX *amx, cell value); +int AMXAPI amx_PushAddress(AMX *amx, cell *address); +int AMXAPI amx_PushArray(AMX *amx, cell **address, const cell array[], int numcells); +int AMXAPI amx_PushString(AMX *amx, cell **address, const char *string, int pack, int use_wchar); +int AMXAPI amx_RaiseError(AMX *amx, int error); +int AMXAPI amx_Register(AMX *amx, const AMX_NATIVE_INFO *nativelist, int number); +int AMXAPI amx_Release(AMX *amx, cell *address); +int AMXAPI amx_SetCallback(AMX *amx, AMX_CALLBACK callback); +int AMXAPI amx_SetDebugHook(AMX *amx, AMX_DEBUG debug); +int AMXAPI amx_SetString(cell *dest, const char *source, int pack, int use_wchar, size_t size); +int AMXAPI amx_SetUserData(AMX *amx, long tag, void *ptr); +int AMXAPI amx_StrLen(const cell *cstring, int *length); +int AMXAPI amx_UTF8Check(const char *string, int *length); +int AMXAPI amx_UTF8Get(const char *string, const char **endptr, cell *value); +int AMXAPI amx_UTF8Len(const cell *cstr, int *length); +int AMXAPI amx_UTF8Put(char *string, char **endptr, int maxchars, cell value); +int AMXAPI amx_VerifyAddress(AMX *amx, cell *address); + +#if PAWN_CELL_SIZE==16 + void amx_Swap16(uint16_t *v); +#endif +#if PAWN_CELL_SIZE==32 + void amx_Swap32(uint32_t *v); +#endif +#if PAWN_CELL_SIZE==64 && (defined _I64_MAX || defined INT64_MAX || defined HAVE_I64) + void amx_Swap64(uint64_t *v); +#endif + +#if PAWN_CELL_SIZE==16 + #define amx_AlignCell(v) amx_Align16((uint16_t*)(v)) + #define amx_SwapCell(v) amx_Swap16((uint16_t*)(v)) +#elif PAWN_CELL_SIZE==32 + #define amx_AlignCell(v) amx_Align32((uint32_t*)(v)) + #define amx_SwapCell(v) amx_Swap32((uint32_t*)(v)) +#elif PAWN_CELL_SIZE==64 && (defined _I64_MAX || defined INT64_MAX || defined HAVE_I64) + #define amx_AlignCell(v) amx_Align64((uint64_t*)(v)) + #define amx_SwapCell(v) amx_Swap64((uint64_t*)(v)) +#else + #error Unsupported cell size +#endif + +#define amx_RegisterFunc(amx, name, func) \ + amx_Register((amx), amx_NativeInfo((name),(func)), 1); + +#if !defined AMX_NO_ALIGN + #if defined __LINUX__ || defined __FreeBSD__ || defined __APPLE__ + #pragma pack() /* reset default packing */ + #elif defined MACOS && defined __MWERKS__ + #pragma options align=reset + #else + #pragma pack(pop) /* reset previous packing */ + #endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* AMX_H_INCLUDED */ diff --git a/src/pawn/amxpool.c b/src/pawn/amxpool.c new file mode 100644 index 0000000000..e710f34e0f --- /dev/null +++ b/src/pawn/amxpool.c @@ -0,0 +1,264 @@ +/* Simple allocation from a memory pool, with automatic release of + * least-recently used blocks (LRU blocks). + * + * These routines are as simple as possible, and they are neither re-entrant + * nor thread-safe. Their purpose is to have a standard implementation for + * systems where overlays are used and malloc() is not available. + * + * The algorithm uses a first-fit strategy. It keeps all blocks in a single + * list (both used blocks and free blocks are in the same list). Every memory + * block must have a unique number that identifies the block. This unique + * number allows to search for the presence of the block in the pool and for + * "conditional allocation". + * + * + * Copyright (c) CompuPhase, 2007-2020 + * + * 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. + * + * Version: $Id: amxpool.c 6131 2020-04-29 19:47:15Z thiadmer $ + */ +#include +#include "amx.h" +#include "amxpool.h" + +#if !defined NULL + #define NULL ((void*)0) +#endif + +#define MIN_BLOCKSIZE 32 +#define PROTECT_LRU 0xffff + +typedef struct tagARENA { + unsigned blocksize; + short index; /* overlay index, -1 if free */ + unsigned short lru; +} ARENA; + +static void touchblock(amxPool *pool, ARENA *hdr); +static ARENA *findblock(amxPool *pool, int index); + +/* amx_poolinit() initializes the memory pool for the allocated blocks. + * If parameter pool is NULL, the existing pool is cleared (without changing + * its position or size). + */ +void amx_poolinit(amxPool *pool, void *buffer, unsigned size) +{ + assert(buffer!=NULL || pool->base!=NULL); + if (buffer!=NULL) { + assert(size>sizeof(ARENA)); + /* save parameters in global variables, then "free" the entire pool */ + pool->base=buffer; + pool->size=size; + } /* if */ + pool->lru=0; + amx_poolfree(pool, NULL); +} + +/* amx_poolfree() releases a block allocated earlier. The parameter must have + * the same value as that returned by an earlier call to amx_poolalloc(). That + * is, the "block" parameter must point directly behind the arena header of the + * block. + * When parameter "block" is NULL, the pool is re-initialized (meaning that + * all blocks are freed). + */ +void amx_poolfree(amxPool *pool, void *block) +{ + ARENA *hdr,*hdr2; + unsigned sz; + + assert(pool->base!=NULL); + assert(pool->size>sizeof(ARENA)); + + /* special case: if "block" is NULL, create a single free space */ + if (block==NULL) { + /* store an arena header at the start of the pool */ + hdr=(ARENA*)pool->base; + hdr->blocksize=pool->size-sizeof(ARENA); + hdr->index=-1; + hdr->lru=0; + } else { + hdr=(ARENA*)((char*)block-sizeof(ARENA)); + assert((char*)hdr>=(char*)pool->base && (char*)hdr<(char*)pool->base+pool->size); + assert(hdr->blocksizesize); + + /* free this block */ + hdr->index=-1; + + /* try to coalesce with the next block */ + hdr2=(ARENA*)((char*)hdr+hdr->blocksize+sizeof(ARENA)); + if (hdr2->index==-1) + hdr->blocksize+=hdr2->blocksize+sizeof(ARENA); + + /* try to coalesce with the previous block */ + if ((void*)hdr!=pool->base) { + sz=pool->size; + hdr2=(ARENA*)pool->base; + while (sz>0 && (char*)hdr2+hdr2->blocksize+sizeof(ARENA)!=(char*)hdr) { + assert(sz<=pool->size); + sz-=hdr2->blocksize+sizeof(ARENA); + hdr2=(ARENA*)((char*)hdr2+hdr2->blocksize+sizeof(ARENA)); + } /* while */ + assert((char*)hdr2+hdr2->blocksize+sizeof(ARENA)==(char*)hdr); + if (hdr2->index==-1) + hdr2->blocksize+=hdr->blocksize+sizeof(ARENA); + } /* if */ + } /* if */ +} + +/* amx_poolalloc() allocates the requested number of bytes from the pool and + * returns a header to the start of it. Every block in the pool is prefixed + * with an "arena header"; the return value of this function points just + * behind this arena header. + * + * The block with the specified "index" should not already exist in the pool. + * In other words, parameter "index" should be unique for every of memory block, + * and the block should not change in size. Use amx_poolfind() to verify whether + * a block is already in the pool (and optionally amx_poolfree() to remove it). + * + * If no block of sufficient size is available, the routine frees blocks until + * the requested amount of memory can be allocated. There is no intelligent + * algorithm involved: the routine just frees the least-recently used block at + * every iteration (without considering the size of the block or whether that + * block is adjacent to a free block). + */ +void *amx_poolalloc(amxPool *pool, unsigned size,int index) +{ + ARENA *hdr,*hdrlru; + unsigned sz; + unsigned short minlru; + + assert(size>0); + assert(index>=0 && index<=SHRT_MAX); + assert(findblock(pool, index)==NULL); + + /* align the size to a cell boundary */ + if ((size % sizeof(cell))!=0) + size+=sizeof(cell)-(size % sizeof(cell)); + if (size+sizeof(ARENA)>pool->size) + return NULL; /* requested block does not fit in the pool */ + + /* find a block large enough to get the size plus an arena header; at + * the same time, detect the block with the lowest LRU + * if no block of sufficient size can be found, the routine then frees + * the block with the lowest LRU count and tries again + */ + do { + sz=pool->size; + hdr=(ARENA*)pool->base; + hdrlru=hdr; + minlru=USHRT_MAX; + while (sz>0) { + assert(sz<=pool->size); + assert((char*)hdr>=(char*)pool->base && (char*)hdr<(char*)pool->base+pool->size); + if (hdr->index==-1 && hdr->blocksize>=size) + break; + if (hdr->index!=-1 && hdr->lrulru; + hdrlru=hdr; + } /* if */ + sz-=hdr->blocksize+sizeof(ARENA); + hdr=(ARENA*)((char*)hdr+hdr->blocksize+sizeof(ARENA)); + } /* while */ + assert(sz<=pool->size); + if (sz==0) { + /* free up memory and try again */ + assert(hdrlru->index!=-1); + amx_poolfree(pool, (char*)hdrlru+sizeof(ARENA)); + } /* if */ + } while (sz==0); + + /* see whether to allocate the entire free block, or to cut it in two blocks */ + if (hdr->blocksize>size+MIN_BLOCKSIZE+sizeof(ARENA)) { + /* cut the block in two */ + ARENA *next=(ARENA*)((char*)hdr+size+sizeof(ARENA)); + next->blocksize=hdr->blocksize-size-sizeof(ARENA); + next->index=-1; + next->lru=0; + } else { + size=hdr->blocksize; + } /* if */ + hdr->blocksize=size; + hdr->index=(short)index; + touchblock(pool, hdr); /* set LRU field */ + + return (void*)((char*)hdr+sizeof(ARENA)); +} + +/* amx_poolfind() returns the address of the memory block with the given index, + * or NULL if no such block exists. Parameter "index" should not be -1, because + * -1 represents a free block (actually, only positive values are valid). + * When amx_poolfind() finds the block, it increments its LRU count. + */ +void *amx_poolfind(amxPool *pool, int index) +{ + ARENA *hdr=findblock(pool, index); + if (hdr==NULL) + return NULL; + touchblock(pool, hdr); + return (void*)((char*)hdr+sizeof(ARENA)); +} + +int amx_poolprotect(amxPool *pool, int index) +{ + ARENA *hdr=findblock(pool, index); + if (hdr==NULL) + return AMX_ERR_GENERAL; + hdr->lru=PROTECT_LRU; + return AMX_ERR_NONE; +} + +static ARENA *findblock(amxPool *pool, int index) +{ + ARENA *hdr; + unsigned sz; + + assert(index>=0); + sz=pool->size; + hdr=(ARENA*)pool->base; + while (sz>0 && hdr->index!=index) { + assert(sz<=pool->size); + assert((char*)hdr>=(char*)pool->base && (char*)hdr<(char*)pool->base+pool->size); + sz-=hdr->blocksize+sizeof(ARENA); + hdr=(ARENA*)((char*)hdr+hdr->blocksize+sizeof(ARENA)); + } /* while */ + assert(sz<=pool->size); + return (sz>0 && hdr->index==index) ? hdr : NULL; +} + +static void touchblock(amxPool *pool, ARENA *hdr) +{ + assert(hdr!=NULL); + if (++pool->lru >= PROTECT_LRU) + pool->lru=0; + hdr->lru=pool->lru; + + /* special case: if the overlay LRU count wrapped back to zero, set the + * LRU count of all blocks to zero, but set the count of the block just + * touched to 1 (skip blocks marked as protected, too) + */ + if (pool->lru==0) { + ARENA *hdr2; + unsigned sz=pool->size; + hdr2=(ARENA*)pool->base; + while (sz>0) { + assert(sz<=pool->size); + if (hdr2->lru!=PROTECT_LRU) + hdr2->lru=0; + sz-=hdr2->blocksize+sizeof(ARENA); + hdr2=(ARENA*)((char*)hdr2+hdr2->blocksize+sizeof(ARENA)); + } /* while */ + assert(sz==0); + hdr->lru=++pool->lru; + } /* if */ +} diff --git a/src/pawn/amxpool.h b/src/pawn/amxpool.h new file mode 100644 index 0000000000..eace865eba --- /dev/null +++ b/src/pawn/amxpool.h @@ -0,0 +1,43 @@ +/* Simple allocation from a memory pool, with automatic release of + * least-recently used blocks (LRU blocks). + * + * Copyright (c) CompuPhase, 2007-2020 + * + * 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. + * + * Version: $Id: amxpool.h 6131 2020-04-29 19:47:15Z thiadmer $ + */ +#ifndef AMXPOOL_H_INCLUDED +#define AMXPOOL_H_INCLUDED + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct amxPool { + void *base; + unsigned size; + unsigned short lru; +} amxPool; + +void amx_poolinit(amxPool *pool, void *buffer, unsigned size); +void *amx_poolalloc(amxPool *pool, unsigned size, int index); +void amx_poolfree(amxPool *pool, void *block); +void *amx_poolfind(amxPool *pool, int index); +int amx_poolprotect(amxPool *pool, int index); + +#if defined(__cplusplus) +} +#endif + +#endif /* AMXPOOL_H_INCLUDED */ diff --git a/src/pawn/heatshrink_common.h b/src/pawn/heatshrink_common.h new file mode 100644 index 0000000000..243f447029 --- /dev/null +++ b/src/pawn/heatshrink_common.h @@ -0,0 +1,20 @@ +#ifndef HEATSHRINK_H +#define HEATSHRINK_H + +#define HEATSHRINK_AUTHOR "Scott Vokes " +#define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink" + +/* Version 0.4.1 */ +#define HEATSHRINK_VERSION_MAJOR 0 +#define HEATSHRINK_VERSION_MINOR 4 +#define HEATSHRINK_VERSION_PATCH 1 + +#define HEATSHRINK_MIN_WINDOW_BITS 4 +#define HEATSHRINK_MAX_WINDOW_BITS 15 + +#define HEATSHRINK_MIN_LOOKAHEAD_BITS 3 + +#define HEATSHRINK_LITERAL_MARKER 0x01 +#define HEATSHRINK_BACKREF_MARKER 0x00 + +#endif diff --git a/src/pawn/heatshrink_config.h b/src/pawn/heatshrink_config.h new file mode 100644 index 0000000000..69ee3adcd1 --- /dev/null +++ b/src/pawn/heatshrink_config.h @@ -0,0 +1,26 @@ +#ifndef HEATSHRINK_CONFIG_H +#define HEATSHRINK_CONFIG_H + +/* Should functionality assuming dynamic allocation be used? */ +#ifndef HEATSHRINK_DYNAMIC_ALLOC +#define HEATSHRINK_DYNAMIC_ALLOC 0 +#endif + +#if HEATSHRINK_DYNAMIC_ALLOC + /* Optional replacement of malloc/free */ + #define HEATSHRINK_MALLOC(SZ) malloc(SZ) + #define HEATSHRINK_FREE(P, SZ) free(P) +#else + /* Required parameters for static configuration */ + #define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32 + #define HEATSHRINK_STATIC_WINDOW_BITS 8 + #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 +#endif + +/* Turn on logging for debugging. */ +#define HEATSHRINK_DEBUGGING_LOGS 0 + +/* Use indexing for faster compression. (This requires additional space.) */ +#define HEATSHRINK_USE_INDEX 1 + +#endif diff --git a/src/pawn/heatshrink_decoder.c b/src/pawn/heatshrink_decoder.c new file mode 100644 index 0000000000..0f118cf98a --- /dev/null +++ b/src/pawn/heatshrink_decoder.c @@ -0,0 +1,367 @@ +#include +#include +#include "heatshrink_decoder.h" + +/* States for the polling state machine. */ +typedef enum { + HSDS_TAG_BIT, /* tag bit */ + HSDS_YIELD_LITERAL, /* ready to yield literal byte */ + HSDS_BACKREF_INDEX_MSB, /* most significant byte of index */ + HSDS_BACKREF_INDEX_LSB, /* least significant byte of index */ + HSDS_BACKREF_COUNT_MSB, /* most significant byte of count */ + HSDS_BACKREF_COUNT_LSB, /* least significant byte of count */ + HSDS_YIELD_BACKREF, /* ready to yield back-reference */ +} HSD_state; + +#if HEATSHRINK_DEBUGGING_LOGS +#include +#include +#include +#define LOG(...) fprintf(stderr, __VA_ARGS__) +#define ASSERT(X) assert(X) +static const char *state_names[] = { + "tag_bit", + "yield_literal", + "backref_index_msb", + "backref_index_lsb", + "backref_count_msb", + "backref_count_lsb", + "yield_backref", +}; +#else +#define LOG(...) /* no-op */ +#define ASSERT(X) /* no-op */ +#endif + +typedef struct { + uint8_t *buf; /* output buffer */ + size_t buf_size; /* buffer size */ + size_t *output_size; /* bytes pushed to buffer, so far */ +} output_info; + +#define NO_BITS ((uint16_t)-1) + +/* Forward references. */ +static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count); +static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte); + +#if HEATSHRINK_DYNAMIC_ALLOC +heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size, + uint8_t window_sz2, + uint8_t lookahead_sz2) { + if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || + (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || + (input_buffer_size == 0) || + (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || + (lookahead_sz2 >= window_sz2)) { + return NULL; + } + size_t buffers_sz = (1 << window_sz2) + input_buffer_size; + size_t sz = sizeof(heatshrink_decoder) + buffers_sz; + heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz); + if (hsd == NULL) { return NULL; } + hsd->input_buffer_size = input_buffer_size; + hsd->window_sz2 = window_sz2; + hsd->lookahead_sz2 = lookahead_sz2; + heatshrink_decoder_reset(hsd); + LOG("-- allocated decoder with buffer size of %zu (%zu + %u + %u)\n", + sz, sizeof(heatshrink_decoder), (1 << window_sz2), input_buffer_size); + return hsd; +} + +void heatshrink_decoder_free(heatshrink_decoder *hsd) { + size_t buffers_sz = (1 << hsd->window_sz2) + hsd->input_buffer_size; + size_t sz = sizeof(heatshrink_decoder) + buffers_sz; + HEATSHRINK_FREE(hsd, sz); + (void)sz; /* may not be used by free */ +} +#endif + +void heatshrink_decoder_reset(heatshrink_decoder *hsd) { + size_t buf_sz = 1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd); + size_t input_sz = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd); + memset(hsd->buffers, 0, buf_sz + input_sz); + hsd->state = HSDS_TAG_BIT; + hsd->input_size = 0; + hsd->input_index = 0; + hsd->bit_index = 0x00; + hsd->current_byte = 0x00; + hsd->output_count = 0; + hsd->output_index = 0; + hsd->head_index = 0; +} + +/* Copy SIZE bytes into the decoder's input buffer, if it will fit. */ +HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, + uint8_t *in_buf, size_t size, size_t *input_size) { + if ((hsd == NULL) || (in_buf == NULL) || (input_size == NULL)) { + return HSDR_SINK_ERROR_NULL; + } + + size_t rem = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd) - hsd->input_size; + if (rem == 0) { + *input_size = 0; + return HSDR_SINK_FULL; + } + + size = rem < size ? rem : size; + LOG("-- sinking %zd bytes\n", size); + /* copy into input buffer (at head of buffers) */ + memcpy(&hsd->buffers[hsd->input_size], in_buf, size); + hsd->input_size += size; + *input_size = size; + return HSDR_SINK_OK; +} + + +/***************** + * Decompression * + *****************/ + +#define BACKREF_COUNT_BITS(HSD) (HEATSHRINK_DECODER_LOOKAHEAD_BITS(HSD)) +#define BACKREF_INDEX_BITS(HSD) (HEATSHRINK_DECODER_WINDOW_BITS(HSD)) + +// States +static HSD_state st_tag_bit(heatshrink_decoder *hsd); +static HSD_state st_yield_literal(heatshrink_decoder *hsd, + output_info *oi); +static HSD_state st_backref_index_msb(heatshrink_decoder *hsd); +static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd); +static HSD_state st_backref_count_msb(heatshrink_decoder *hsd); +static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd); +static HSD_state st_yield_backref(heatshrink_decoder *hsd, + output_info *oi); + +HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, + uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { + if ((hsd == NULL) || (out_buf == NULL) || (output_size == NULL)) { + return HSDR_POLL_ERROR_NULL; + } + *output_size = 0; + + output_info oi; + oi.buf = out_buf; + oi.buf_size = out_buf_size; + oi.output_size = output_size; + + while (1) { + LOG("-- poll, state is %d (%s), input_size %d\n", + hsd->state, state_names[hsd->state], hsd->input_size); + uint8_t in_state = hsd->state; + switch (in_state) { + case HSDS_TAG_BIT: + hsd->state = st_tag_bit(hsd); + break; + case HSDS_YIELD_LITERAL: + hsd->state = st_yield_literal(hsd, &oi); + break; + case HSDS_BACKREF_INDEX_MSB: + hsd->state = st_backref_index_msb(hsd); + break; + case HSDS_BACKREF_INDEX_LSB: + hsd->state = st_backref_index_lsb(hsd); + break; + case HSDS_BACKREF_COUNT_MSB: + hsd->state = st_backref_count_msb(hsd); + break; + case HSDS_BACKREF_COUNT_LSB: + hsd->state = st_backref_count_lsb(hsd); + break; + case HSDS_YIELD_BACKREF: + hsd->state = st_yield_backref(hsd, &oi); + break; + default: + return HSDR_POLL_ERROR_UNKNOWN; + } + + /* If the current state cannot advance, check if input or output + * buffer are exhausted. */ + if (hsd->state == in_state) { + if (*output_size == out_buf_size) { return HSDR_POLL_MORE; } + return HSDR_POLL_EMPTY; + } + } +} + +static HSD_state st_tag_bit(heatshrink_decoder *hsd) { + uint32_t bits = get_bits(hsd, 1); // get tag bit + if (bits == NO_BITS) { + return HSDS_TAG_BIT; + } else if (bits) { + return HSDS_YIELD_LITERAL; + } else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) { + return HSDS_BACKREF_INDEX_MSB; + } else { + hsd->output_index = 0; + return HSDS_BACKREF_INDEX_LSB; + } +} + +static HSD_state st_yield_literal(heatshrink_decoder *hsd, + output_info *oi) { + /* Emit a repeated section from the window buffer, and add it (again) + * to the window buffer. (Note that the repetition can include + * itself.)*/ + if (*oi->output_size < oi->buf_size) { + uint16_t byte = get_bits(hsd, 8); + if (byte == NO_BITS) { return HSDS_YIELD_LITERAL; } /* out of input */ + uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; + uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; + uint8_t c = byte & 0xFF; + LOG("-- emitting literal byte 0x%02x ('%c')\n", c, isprint(c) ? c : '.'); + buf[hsd->head_index++ & mask] = c; + push_byte(hsd, oi, c); + return HSDS_TAG_BIT; + } else { + return HSDS_YIELD_LITERAL; + } +} + +static HSD_state st_backref_index_msb(heatshrink_decoder *hsd) { + uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); + ASSERT(bit_ct > 8); + uint16_t bits = get_bits(hsd, bit_ct - 8); + LOG("-- backref index (msb), got 0x%04x (+1)\n", bits); + if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_MSB; } + hsd->output_index = bits << 8; + return HSDS_BACKREF_INDEX_LSB; +} + +static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd) { + uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); + uint16_t bits = get_bits(hsd, bit_ct < 8 ? bit_ct : 8); + LOG("-- backref index (lsb), got 0x%04x (+1)\n", bits); + if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_LSB; } + hsd->output_index |= bits; + hsd->output_index++; + uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); + hsd->output_count = 0; + return (br_bit_ct > 8) ? HSDS_BACKREF_COUNT_MSB : HSDS_BACKREF_COUNT_LSB; +} + +static HSD_state st_backref_count_msb(heatshrink_decoder *hsd) { + uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); + ASSERT(br_bit_ct > 8); + uint16_t bits = get_bits(hsd, br_bit_ct - 8); + LOG("-- backref count (msb), got 0x%04x (+1)\n", bits); + if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_MSB; } + hsd->output_count = bits << 8; + return HSDS_BACKREF_COUNT_LSB; +} + +static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd) { + uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); + uint16_t bits = get_bits(hsd, br_bit_ct < 8 ? br_bit_ct : 8); + LOG("-- backref count (lsb), got 0x%04x (+1)\n", bits); + if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_LSB; } + hsd->output_count |= bits; + hsd->output_count++; + return HSDS_YIELD_BACKREF; +} + +static HSD_state st_yield_backref(heatshrink_decoder *hsd, + output_info *oi) { + size_t count = oi->buf_size - *oi->output_size; + if (count > 0) { + size_t i = 0; + if (hsd->output_count < count) count = hsd->output_count; + uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; + uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; + uint16_t neg_offset = hsd->output_index; + LOG("-- emitting %zu bytes from -%u bytes back\n", count, neg_offset); + ASSERT(neg_offset <= mask + 1); + ASSERT(count <= (size_t)(1 << BACKREF_COUNT_BITS(hsd))); + + for (i=0; ihead_index - neg_offset) & mask]; + push_byte(hsd, oi, c); + buf[hsd->head_index & mask] = c; + hsd->head_index++; + LOG(" -- ++ 0x%02x\n", c); + } + hsd->output_count -= count; + if (hsd->output_count == 0) { return HSDS_TAG_BIT; } + } + return HSDS_YIELD_BACKREF; +} + +/* Get the next COUNT bits from the input buffer, saving incremental progress. + * Returns NO_BITS on end of input, or if more than 15 bits are requested. */ +static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count) { + uint16_t accumulator = 0; + int i = 0; + if (count > 15) { return NO_BITS; } + LOG("-- popping %u bit(s)\n", count); + + /* If we aren't able to get COUNT bits, suspend immediately, because we + * don't track how many bits of COUNT we've accumulated before suspend. */ + if (hsd->input_size == 0) { + if (hsd->bit_index < (1 << (count - 1))) { return NO_BITS; } + } + + for (i = 0; i < count; i++) { + if (hsd->bit_index == 0x00) { + if (hsd->input_size == 0) { + LOG(" -- out of bits, suspending w/ accumulator of %u (0x%02x)\n", + accumulator, accumulator); + return NO_BITS; + } + hsd->current_byte = hsd->buffers[hsd->input_index++]; + LOG(" -- pulled byte 0x%02x\n", hsd->current_byte); + if (hsd->input_index == hsd->input_size) { + hsd->input_index = 0; /* input is exhausted */ + hsd->input_size = 0; + } + hsd->bit_index = 0x80; + } + accumulator <<= 1; + if (hsd->current_byte & hsd->bit_index) { + accumulator |= 0x01; + if (0) { + LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n", + accumulator, hsd->bit_index); + } + } else { + if (0) { + LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n", + accumulator, hsd->bit_index); + } + } + hsd->bit_index >>= 1; + } + + if (count > 1) { LOG(" -- accumulated %08x\n", accumulator); } + return accumulator; +} + +HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) { + if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; } + switch (hsd->state) { + case HSDS_TAG_BIT: + return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; + + /* If we want to finish with no input, but are in these states, it's + * because the 0-bit padding to the last byte looks like a backref + * marker bit followed by all 0s for index and count bits. */ + case HSDS_BACKREF_INDEX_LSB: + case HSDS_BACKREF_INDEX_MSB: + case HSDS_BACKREF_COUNT_LSB: + case HSDS_BACKREF_COUNT_MSB: + return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; + + /* If the output stream is padded with 0xFFs (possibly due to being in + * flash memory), also explicitly check the input size rather than + * uselessly returning MORE but yielding 0 bytes when polling. */ + case HSDS_YIELD_LITERAL: + return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; + + default: + return HSDR_FINISH_MORE; + } +} + +static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte) { + LOG(" -- pushing byte: 0x%02x ('%c')\n", byte, isprint(byte) ? byte : '.'); + oi->buf[(*oi->output_size)++] = byte; + (void)hsd; +} diff --git a/src/pawn/heatshrink_decoder.h b/src/pawn/heatshrink_decoder.h new file mode 100644 index 0000000000..63211e417e --- /dev/null +++ b/src/pawn/heatshrink_decoder.h @@ -0,0 +1,108 @@ +#ifndef HEATSHRINK_DECODER_H +#define HEATSHRINK_DECODER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "heatshrink_common.h" +#include "heatshrink_config.h" + +typedef enum { + HSDR_SINK_OK, /* data sunk, ready to poll */ + HSDR_SINK_FULL, /* out of space in internal buffer */ + HSDR_SINK_ERROR_NULL=-1, /* NULL argument */ +} HSD_sink_res; + +typedef enum { + HSDR_POLL_EMPTY, /* input exhausted */ + HSDR_POLL_MORE, /* more data remaining, call again w/ fresh output buffer */ + HSDR_POLL_ERROR_NULL=-1, /* NULL arguments */ + HSDR_POLL_ERROR_UNKNOWN=-2, +} HSD_poll_res; + +typedef enum { + HSDR_FINISH_DONE, /* output is done */ + HSDR_FINISH_MORE, /* more output remains */ + HSDR_FINISH_ERROR_NULL=-1, /* NULL arguments */ +} HSD_finish_res; + +#if HEATSHRINK_DYNAMIC_ALLOC +#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(BUF) \ + ((BUF)->input_buffer_size) +#define HEATSHRINK_DECODER_WINDOW_BITS(BUF) \ + ((BUF)->window_sz2) +#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ + ((BUF)->lookahead_sz2) +#else +#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_) \ + HEATSHRINK_STATIC_INPUT_BUFFER_SIZE +#define HEATSHRINK_DECODER_WINDOW_BITS(_) \ + (HEATSHRINK_STATIC_WINDOW_BITS) +#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ + (HEATSHRINK_STATIC_LOOKAHEAD_BITS) +#endif + +typedef struct { + uint16_t input_size; /* bytes in input buffer */ + uint16_t input_index; /* offset to next unprocessed input byte */ + uint16_t output_count; /* how many bytes to output */ + uint16_t output_index; /* index for bytes to output */ + uint16_t head_index; /* head of window buffer */ + uint8_t state; /* current state machine node */ + uint8_t current_byte; /* current byte of input */ + uint8_t bit_index; /* current bit index */ + +#if HEATSHRINK_DYNAMIC_ALLOC + /* Fields that are only used if dynamically allocated. */ + uint8_t window_sz2; /* window buffer bits */ + uint8_t lookahead_sz2; /* lookahead bits */ + uint16_t input_buffer_size; /* input buffer size */ + + /* Input buffer, then expansion window buffer */ + uint8_t buffers[]; +#else + /* Input buffer, then expansion window buffer */ + uint8_t buffers[(1 << HEATSHRINK_DECODER_WINDOW_BITS(_)) + + HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_)]; +#endif +} heatshrink_decoder; + +#if HEATSHRINK_DYNAMIC_ALLOC +/* Allocate a decoder with an input buffer of INPUT_BUFFER_SIZE bytes, + * an expansion buffer size of 2^WINDOW_SZ2, and a lookahead + * size of 2^lookahead_sz2. (The window buffer and lookahead sizes + * must match the settings used when the data was compressed.) + * Returns NULL on error. */ +heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size, + uint8_t expansion_buffer_sz2, uint8_t lookahead_sz2); + +/* Free a decoder. */ +void heatshrink_decoder_free(heatshrink_decoder *hsd); +#endif + +/* Reset a decoder. */ +void heatshrink_decoder_reset(heatshrink_decoder *hsd); + +/* Sink at most SIZE bytes from IN_BUF into the decoder. *INPUT_SIZE is set to + * indicate how many bytes were actually sunk (in case a buffer was filled). */ +HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, + uint8_t *in_buf, size_t size, size_t *input_size); + +/* Poll for output from the decoder, copying at most OUT_BUF_SIZE bytes into + * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ +HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, + uint8_t *out_buf, size_t out_buf_size, size_t *output_size); + +/* Notify the dencoder that the input stream is finished. + * If the return value is HSDR_FINISH_MORE, there is still more output, so + * call heatshrink_decoder_poll and repeat. */ +HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/pawn/osdefs.h b/src/pawn/osdefs.h new file mode 100644 index 0000000000..72b4d89dc9 --- /dev/null +++ b/src/pawn/osdefs.h @@ -0,0 +1,159 @@ +/* + * Platform + * __MSDOS__ set when compiling for DOS (not Windows) + * _Windows set when compiling for any version of Microsoft Windows + * __WIN32__ set when compiling for Windows95 or WindowsNT (32 bit mode) + * __32BIT__ set when compiling in 32-bit "flat" mode (DOS, Windows, ARM) + * __64BIT__ set when compiling in 64-bit mode + * __ECOS__ set if Pawn was included with the eCos with configtool + * __LINUX__ set when compiling for Linux + * + * Copyright 1998-2020, CompuPhase, The Netherlands. + * No usage restrictions, no warranties. + */ + +#ifndef _OSDEFS_H +#define _OSDEFS_H + +/* Every compiler uses different "default" macros to indicate the mode + * it is in. Throughout the source, we use the Borland C++ macros, so + * the macros of Watcom C/C++ and Microsoft Visual C/C++ are mapped to + * those of Borland C++. + */ +#if defined(__WATCOMC__) + #if defined(__WINDOWS__) || defined(__NT__) + #define _Windows 1 + #endif + #if defined(__386__) || defined(__NT__) + #define __32BIT__ 1 + #endif + #if defined(_Windows) && defined(__32BIT__) + #define __WIN32__ 1 + #endif +#elif defined(_MSC_VER) + #if defined(_WINDOWS) || defined(_WIN32) + #define _Windows 1 + #endif + #if defined(_WIN32) + #define __WIN32__ 1 + #define __32BIT__ 1 + #endif +#elif defined __arm__ + #define __32BIT__ 1 +#elif defined __AVR__ + #define __16BIT__ 1 +#endif +#if !defined __16BIT__ && !defined __32BIT__ && !defined __64BIT__ + #define __32BIT__ 1 +#endif + + +#if (defined __linux || defined __linux__) && !defined __LINUX__ + #define __LINUX__ +#endif +/* To be able to eventually set __ECOS__, we have to find a symbol + * defined in a common place (so including the header file won't break + * anything for other platforms). includes + * and in this later file we can find CYGPKG_PAWN + * if the Pawn package was included with configtool and so we know + * that we are compiling for eCos. + */ +#if defined CCSINFO + #include +#endif +#if defined CYGPKG_PAWN + #define __ECOS__ 1 + #define HAVE_ALLOCA_H 0 +#endif + + +#if defined __FreeBSD__ + #include +#elif defined __LINUX__ + #include +#elif defined __ECOS__ + #include + #define BIG_ENDIAN 4321 + #define LITTLE_ENDIAN 1234 + #if (CYG_BYTEORDER == CYG_LSBFIRST) + #define BYTE_ORDER LITTLE_ENDIAN + #else + #define BYTE_ORDER BIG_ENDIAN + #endif + /* + * eCos option management. + */ + #include + #if defined CYGPKG_PAWN_AMX_ANSIONLY && CYGPKG_PAWN_AMX_ANSIONLY==1 + #define AMX_ANSIONLY + #endif + #define PAWN_CELL_SIZE CYGPKG_PAWN_AMX_CELLSIZE + #if defined CYGPKG_PAWN_CORE_RANDOM && CYGPKG_PAWN_CORE_RANDOM==0 + #define AMX_NORANDOM + #endif + #if defined CYGPKG_PAWN_CORE_PROPERTY && CYGPKG_PAWN_CORE_PROPERTY==0 + #define AMX_NOPROPLIST + #endif + #if defined CYGPKG_PAWN_AMX_CONS_FIXEDPOINT && CYGPKG_PAWN_AMX_CONS_FIXEDPOINT==1 + #define FIXEDPOINT + #endif + #if defined CYGPKG_PAWN_AMX_CONS_FLOATPOINT && CYGPKG_PAWN_AMX_CONS_FLOATPOINT==1 + #define FLOATPOINT + #endif +#endif + +/* Linux now has these */ +#if !defined BIG_ENDIAN + #define BIG_ENDIAN 4321 +#endif +#if !defined LITTLE_ENDIAN + #define LITTLE_ENDIAN 1234 +#endif + +/* educated guess, BYTE_ORDER is undefined, i386 is common => little endian */ +#if !defined BYTE_ORDER + #if defined UCLINUX + #define BYTE_ORDER BIG_ENDIAN + #else + #define BYTE_ORDER LITTLE_ENDIAN + #endif +#endif + +#if defined __MSDOS__ || defined __WIN32__ || defined _Windows + #define DIRSEP_CHAR '\\' +#elif defined macintosh /* only the original Macintosh uses ':', OSX uses the '/' */ + #define DIRSEP_CHAR ':' +#else + #define DIRSEP_CHAR '/' +#endif + +/* _MAX_PATH is sometimes called differently and it may be in limits.h or + * stdlib.h instead of stdio.h. + */ +#if !defined _MAX_PATH + /* not defined, perhaps stdio.h was not included */ + #if !defined PATH_MAX + #include + #endif + #if !defined _MAX_PATH && !defined PATH_MAX + /* no _MAX_PATH and no MAX_PATH, perhaps it is in limits.h */ + #include + #endif + #if !defined _MAX_PATH && !defined PATH_MAX + /* no _MAX_PATH and no MAX_PATH, perhaps it is in stdlib.h */ + #include + #endif + /* if _MAX_PATH is undefined, try common alternative names */ + #if !defined _MAX_PATH + #if defined MAX_PATH + #define _MAX_PATH MAX_PATH + #elif defined _POSIX_PATH_MAX + #define _MAX_PATH _POSIX_PATH_MAX + #else + /* everything failed, actually we have a problem here... */ + #define _MAX_PATH 1024 + #endif + #endif +#endif + +#endif /* _OSDEFS_H */ diff --git a/src/pawn/programs/include/infinitime.inc b/src/pawn/programs/include/infinitime.inc new file mode 100644 index 0000000000..9f7ec6f920 --- /dev/null +++ b/src/pawn/programs/include/infinitime.inc @@ -0,0 +1,66 @@ +#pragma library - + +#include "lvgl" + +#define DATETIME[.minute, .hour, .day, .year] + +native sprintf(out[], size=sizeof out, const fmt[], const ...) = -2000 + +#define jetbrains_mono_extrabold_compressed font_jmec // Name truncated because symbols have a max length of 31 characters +public stock const ptr:jetbrains_mono_extrabold_compressed + +const touch_event: { + TOUCH_None = 0, + TOUCH_TAP, + TOUCH_SWIPE_LEFT, + TOUCH_SWIPE_RIGHT, + TOUCH_SWIPE_UP, + TOUCH_SWIPE_DOWN, + TOUCH_LONG_TAP, + TOUCH_DOUBLE_TAP, +} + +native bool: read_datetime(out[DATETIME]) = -2001 +native read_datetime_short_str(day_of_week{4} = 0, month_of_year{4} = 0) = -2002 + +native status_icons_create() = -2003 +native status_icons_update() = -2004 +native bool: has_new_notifications() = -2005 + +const setting: { + SETTING_WATCHFACE = 0, + SETTING_CHIME_OPTION, + SETTING_PRIDE_FLAG, + SETTING_CLOCK_TYPE, + SETTING_WEATHER_FORMAT, + SETTING_NOTIFICATION_STATUS, + SETTING_STEPS_GOAL, +} +const { + CLOCK_TYPE_H24 = 0, + CLOCK_TYPE_H12 +} +native get_setting(setting: s) = -2006 + +const heartrate_state: { + HEARTRATE_STOPPED = 0, + HEARTRATE_NOT_ENOUGH_DATA + HEARTRATE_NO_TOUCH + HEARTRATE_RUNNING +} +native get_heartrate() = -2007 +native heartrate_state: get_heartrate_state() = -2008 + +native get_step_number() = -2009 + +native raise_error(err) = -2010 + +forward @refresh() +forward @touch(x, y) +forward @gesture(touch_event: ev) + +stock swap(&old_val, new_val) { + var changed = old_val != new_val + old_val = new_val + return changed +} diff --git a/src/pawn/programs/include/lvgl.inc b/src/pawn/programs/include/lvgl.inc new file mode 100644 index 0000000000..38018ba28c --- /dev/null +++ b/src/pawn/programs/include/lvgl.inc @@ -0,0 +1,197 @@ +// Make the lv_obj tag be a strong tag without making the code ugly +#define lv_obj: LV_OBJ: + +const lv_state: { + LV_STATE_DEFAULT = 0x00, + LV_STATE_CHECKED = 0x01, + LV_STATE_FOCUSED = 0x02, + LV_STATE_EDITED = 0x04, + LV_STATE_HOVERED = 0x08, + LV_STATE_PRESSED = 0x10, + LV_STATE_DISABLED = 0x20, +} + +const lv_event: { + LV_EVENT_PRESSED = 0, + LV_EVENT_PRESSING, + LV_EVENT_PRESS_LOST, + LV_EVENT_SHORT_CLICKED, + LV_EVENT_LONG_PRESSED, + LV_EVENT_LONG_PRESSED_REPEAT, + LV_EVENT_CLICKED, + LV_EVENT_RELEASED, + LV_EVENT_DRAG_BEGIN, + LV_EVENT_DRAG_END, + LV_EVENT_DRAG_THROW_BEGIN, + LV_EVENT_GESTURE, + LV_EVENT_KEY, + LV_EVENT_FOCUSED, + LV_EVENT_DEFOCUSED, + LV_EVENT_LEAVE, + LV_EVENT_VALUE_CHANGED, + LV_EVENT_INSERT, + LV_EVENT_REFRESH, + LV_EVENT_APPLY, + LV_EVENT_CANCEL, + LV_EVENT_DELETE, +} + +const lv_align: { + LV_ALIGN_CENTER = 0, + LV_ALIGN_IN_TOP_LEFT, + LV_ALIGN_IN_TOP_MID, + LV_ALIGN_IN_TOP_RIGHT, + LV_ALIGN_IN_BOTTOM_LEFT, + LV_ALIGN_IN_BOTTOM_MID, + LV_ALIGN_IN_BOTTOM_RIGHT, + LV_ALIGN_IN_LEFT_MID, + LV_ALIGN_IN_RIGHT_MID, + LV_ALIGN_OUT_TOP_LEFT, + LV_ALIGN_OUT_TOP_MID, + LV_ALIGN_OUT_TOP_RIGHT, + LV_ALIGN_OUT_BOTTOM_LEFT, + LV_ALIGN_OUT_BOTTOM_MID, + LV_ALIGN_OUT_BOTTOM_RIGHT, + LV_ALIGN_OUT_LEFT_TOP, + LV_ALIGN_OUT_LEFT_MID, + LV_ALIGN_OUT_LEFT_BOTTOM, + LV_ALIGN_OUT_RIGHT_TOP, + LV_ALIGN_OUT_RIGHT_MID, + LV_ALIGN_OUT_RIGHT_BOTTOM, +} + +#define LV_STYLE_PROP_INIT(%1,%2,%3,%4) %1 = (((%2 << 4) + %3) | ((%4) << 8)) + +#define LV_STYLE_ATTR_NONE 0 +#define LV_STYLE_ATTR_INHERIT (1 << 7) + +#define LV_STYLE_ID_VALUE 0x0 +#define LV_STYLE_ID_COLOR 0x9 +#define LV_STYLE_ID_OPA 0xC +#define LV_STYLE_ID_PTR 0xE + +const lv_style_prop: { + // These lines are copied verbatim from lv_style.h + + LV_STYLE_PROP_INIT(LV_STYLE_RADIUS, 0x0, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_CLIP_CORNER, 0x0, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SIZE, 0x0, LV_STYLE_ID_VALUE + 3, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSFORM_WIDTH, 0x0, LV_STYLE_ID_VALUE + 4, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSFORM_HEIGHT, 0x0, LV_STYLE_ID_VALUE + 5, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSFORM_ANGLE, 0x0, LV_STYLE_ID_VALUE + 6, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSFORM_ZOOM, 0x0, LV_STYLE_ID_VALUE + 7, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_OPA_SCALE, 0x0, LV_STYLE_ID_OPA + 0, LV_STYLE_ATTR_INHERIT), + + LV_STYLE_PROP_INIT(LV_STYLE_PAD_TOP, 0x1, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_PAD_BOTTOM, 0x1, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_PAD_LEFT, 0x1, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_PAD_RIGHT, 0x1, LV_STYLE_ID_VALUE + 3, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_PAD_INNER, 0x1, LV_STYLE_ID_VALUE + 4, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_MARGIN_TOP, 0x1, LV_STYLE_ID_VALUE + 5, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_MARGIN_BOTTOM, 0x1, LV_STYLE_ID_VALUE + 6, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_MARGIN_LEFT, 0x1, LV_STYLE_ID_VALUE + 7, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_MARGIN_RIGHT, 0x1, LV_STYLE_ID_VALUE + 8, LV_STYLE_ATTR_NONE), + + LV_STYLE_PROP_INIT(LV_STYLE_BG_BLEND_MODE, 0x2, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BG_MAIN_STOP, 0x2, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BG_GRAD_STOP, 0x2, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BG_GRAD_DIR, 0x2, LV_STYLE_ID_VALUE + 3, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BG_COLOR, 0x2, LV_STYLE_ID_COLOR + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BG_GRAD_COLOR, 0x2, LV_STYLE_ID_COLOR + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BG_OPA, 0x2, LV_STYLE_ID_OPA + 0, LV_STYLE_ATTR_NONE), + + LV_STYLE_PROP_INIT(LV_STYLE_BORDER_WIDTH, 0x3, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BORDER_SIDE, 0x3, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BORDER_BLEND_MODE, 0x3, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BORDER_POST, 0x3, LV_STYLE_ID_VALUE + 3, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BORDER_COLOR, 0x3, LV_STYLE_ID_COLOR + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_BORDER_OPA, 0x3, LV_STYLE_ID_OPA + 0, LV_STYLE_ATTR_NONE), + + LV_STYLE_PROP_INIT(LV_STYLE_OUTLINE_WIDTH, 0x4, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_OUTLINE_PAD, 0x4, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_OUTLINE_BLEND_MODE, 0x4, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_OUTLINE_COLOR, 0x4, LV_STYLE_ID_COLOR + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_OUTLINE_OPA, 0x4, LV_STYLE_ID_OPA + 0, LV_STYLE_ATTR_NONE), + + LV_STYLE_PROP_INIT(LV_STYLE_SHADOW_WIDTH, 0x5, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SHADOW_OFS_X, 0x5, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SHADOW_OFS_Y, 0x5, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SHADOW_SPREAD, 0x5, LV_STYLE_ID_VALUE + 3, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SHADOW_BLEND_MODE, 0x5, LV_STYLE_ID_VALUE + 4, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SHADOW_COLOR, 0x5, LV_STYLE_ID_COLOR + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SHADOW_OPA, 0x5, LV_STYLE_ID_OPA + 0, LV_STYLE_ATTR_NONE), + + LV_STYLE_PROP_INIT(LV_STYLE_PATTERN_BLEND_MODE, 0x6, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_PATTERN_REPEAT, 0x6, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_PATTERN_RECOLOR, 0x6, LV_STYLE_ID_COLOR + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_PATTERN_OPA, 0x6, LV_STYLE_ID_OPA + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_PATTERN_RECOLOR_OPA, 0x6, LV_STYLE_ID_OPA + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_PATTERN_IMAGE, 0x6, LV_STYLE_ID_PTR + 0, LV_STYLE_ATTR_NONE), + + LV_STYLE_PROP_INIT(LV_STYLE_VALUE_LETTER_SPACE, 0x7, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_VALUE_LINE_SPACE, 0x7, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_VALUE_BLEND_MODE, 0x7, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_VALUE_OFS_X, 0x7, LV_STYLE_ID_VALUE + 3, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_VALUE_OFS_Y, 0x7, LV_STYLE_ID_VALUE + 4, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_VALUE_ALIGN, 0x7, LV_STYLE_ID_VALUE + 5, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_VALUE_COLOR, 0x7, LV_STYLE_ID_COLOR + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_VALUE_OPA, 0x7, LV_STYLE_ID_OPA + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_VALUE_FONT, 0x7, LV_STYLE_ID_PTR + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_VALUE_STR, 0x7, LV_STYLE_ID_PTR + 1, LV_STYLE_ATTR_NONE), + + LV_STYLE_PROP_INIT(LV_STYLE_TEXT_LETTER_SPACE, 0x8, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_TEXT_LINE_SPACE, 0x8, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_TEXT_DECOR, 0x8, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_TEXT_BLEND_MODE, 0x8, LV_STYLE_ID_VALUE + 3, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_TEXT_COLOR, 0x8, LV_STYLE_ID_COLOR + 0, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_TEXT_SEL_COLOR, 0x8, LV_STYLE_ID_COLOR + 1, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_TEXT_SEL_BG_COLOR, 0x8, LV_STYLE_ID_COLOR + 2, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_TEXT_OPA, 0x8, LV_STYLE_ID_OPA + 0, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_TEXT_FONT, 0x8, LV_STYLE_ID_PTR + 0, LV_STYLE_ATTR_INHERIT), + + LV_STYLE_PROP_INIT(LV_STYLE_LINE_WIDTH, 0x9, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_LINE_BLEND_MODE, 0x9, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_LINE_DASH_WIDTH, 0x9, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_LINE_DASH_GAP, 0x9, LV_STYLE_ID_VALUE + 3, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_LINE_ROUNDED, 0x9, LV_STYLE_ID_VALUE + 4, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_LINE_COLOR, 0x9, LV_STYLE_ID_COLOR + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_LINE_OPA, 0x9, LV_STYLE_ID_OPA + 0, LV_STYLE_ATTR_NONE), + + LV_STYLE_PROP_INIT(LV_STYLE_IMAGE_BLEND_MODE, 0xA, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_IMAGE_RECOLOR, 0xA, LV_STYLE_ID_COLOR + 0, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_IMAGE_OPA, 0xA, LV_STYLE_ID_OPA + 0, LV_STYLE_ATTR_INHERIT), + LV_STYLE_PROP_INIT(LV_STYLE_IMAGE_RECOLOR_OPA, 0xA, LV_STYLE_ID_OPA + 1, LV_STYLE_ATTR_INHERIT), + + LV_STYLE_PROP_INIT(LV_STYLE_TRANSITION_TIME, 0xB, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSITION_DELAY, 0xB, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSITION_PROP_1, 0xB, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSITION_PROP_2, 0xB, LV_STYLE_ID_VALUE + 3, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSITION_PROP_3, 0xB, LV_STYLE_ID_VALUE + 4, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSITION_PROP_4, 0xB, LV_STYLE_ID_VALUE + 5, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSITION_PROP_5, 0xB, LV_STYLE_ID_VALUE + 6, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSITION_PROP_6, 0xB, LV_STYLE_ID_VALUE + 7, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_TRANSITION_PATH, 0xB, LV_STYLE_ID_PTR + 0, LV_STYLE_ATTR_NONE), + + LV_STYLE_PROP_INIT(LV_STYLE_SCALE_WIDTH, 0xC, LV_STYLE_ID_VALUE + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SCALE_BORDER_WIDTH, 0xC, LV_STYLE_ID_VALUE + 1, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SCALE_END_BORDER_WIDTH, 0xC, LV_STYLE_ID_VALUE + 2, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SCALE_END_LINE_WIDTH, 0xC, LV_STYLE_ID_VALUE + 3, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SCALE_GRAD_COLOR, 0xC, LV_STYLE_ID_COLOR + 0, LV_STYLE_ATTR_NONE), + LV_STYLE_PROP_INIT(LV_STYLE_SCALE_END_COLOR, 0xC, LV_STYLE_ID_COLOR + 1, LV_STYLE_ATTR_NONE), +}; + +public stock const lv_obj: screen + +native lv_obj: lv_label_create(lv_obj:par, lv_obj:copy = lv_obj:0, const text{} = 0) = -3000 +native lv_obj: lv_btn_create (lv_obj:par, lv_obj:copy = lv_obj:0) = -1001 +native lv_obj_set_pos(lv_obj:obj, x, y) = -1002 +native lv_obj_set_size(lv_obj:obj, w, h) = -1003 +native lv_obj_set_event_cb(lv_obj:obj, const cb_name[]) = -3001 +native lv_obj_align(lv_obj:obj, lv_obj:base = lv_obj:0, lv_align:align, x_ofs = 0, y_ofs = 0) = -1004 +native lv_obj_realign(lv_obj:obj) = -1005 +native lv_label_set_text(lv_obj:obj, const text[]) = -3002 + +native lv_obj_set_style_local_int (lv_obj:obj, lv_style_prop:prop, value, part = 0, lv_state:state_=LV_STATE_DEFAULT) = -3003 +native lv_obj_set_style_local_color(lv_obj:obj, lv_style_prop:prop, value, part = 0, lv_state:state_=LV_STATE_DEFAULT) = -3004 +native lv_obj_set_style_local_opa (lv_obj:obj, lv_style_prop:prop, value, part = 0, lv_state:state_=LV_STATE_DEFAULT) = -3005 +native lv_obj_set_style_local_ptr (lv_obj:obj, lv_style_prop:prop, ptr:value, part = 0, lv_state:state_=LV_STATE_DEFAULT) = -3006 diff --git a/src/pawn/programs/include/symbols.inc b/src/pawn/programs/include/symbols.inc new file mode 100644 index 0000000000..567228527e --- /dev/null +++ b/src/pawn/programs/include/symbols.inc @@ -0,0 +1,54 @@ +stock const symbol_batteryHalf{} = "\xEF\x89\x82" +stock const symbol_heartBeat{} = "\xEF\x88\x9E" +stock const symbol_bluetooth{} = "\xEF\x8A\x94" +stock const symbol_shieldAlt{} = "\xEF\x8F\xAD" +stock const symbol_plug{} = "\xEF\x87\xA6" +stock const symbol_shoe{} = "\xEF\x95\x8B" +stock const symbol_clock{} = "\xEF\x80\x97" +stock const symbol_bell{} = "\xEF\x83\xB3" +stock const symbol_info{} = "\xEF\x84\xA9" +stock const symbol_list{} = "\xEF\x80\xBA" +stock const symbol_sun{} = "\xEF\x86\x85" +stock const symbol_check{} = "\xEF\x95\xA0" +stock const symbol_music{} = "\xEF\x80\x81" +stock const symbol_tachometer{} = "\xEF\x8F\xBD" +stock const symbol_paintbrush{} = "\xEF\x87\xBC" +stock const symbol_paddle{} = "\xEF\x91\x9D" +stock const symbol_map{} = "\xEF\x96\xa0" +stock const symbol_phone{} = "\xEF\x82\x95" +stock const symbol_phoneSlash{} = "\xEF\x8F\x9D" +stock const symbol_volumMute{} = "\xEF\x9A\xA9" +stock const symbol_volumUp{} = "\xEF\x80\xA8" +stock const symbol_volumDown{} = "\xEF\x80\xA7" +stock const symbol_stepForward{} = "\xEF\x81\x91" +stock const symbol_stepBackward{} = "\xEF\x81\x88" +stock const symbol_play{} = "\xEF\x81\x8B" +stock const symbol_pause{} = "\xEF\x81\x8C" +stock const symbol_stop{} = "\xEF\x81\x8D" +stock const symbol_stopWatch{} = "\xEF\x8B\xB2" +stock const symbol_hourGlass{} = "\xEF\x89\x92" +stock const symbol_lapsFlag{} = "\xEF\x80\xA4" +stock const symbol_drum{} = "\xEF\x95\xA9" +stock const symbol_dice{} = "\xEF\x94\xA2" +stock const symbol_eye{} = "\xEF\x81\xAE" +stock const symbol_home{} = "\xEF\x80\x95" +stock const symbol_sleep{} = "\xEE\xBD\x84" +stock const symbol_calculator{} = "\xEF\x87\xAC" +stock const symbol_backspace{} = "\xEF\x95\x9A" +stock const symbol_cloudSun{} = "\xEF\x9B\x84" +stock const symbol_cloudSunRain{} = "\xEF\x9D\x83" +stock const symbol_cloudShowersHeavy{} = "\xEF\x9D\x80" +stock const symbol_smog{} = "\xEF\x9D\x9F" +stock const symbol_cloud{} = "\xEF\x83\x82" +stock const symbol_cloudMeatball{} = "\xEF\x9C\xBB" +stock const symbol_bolt{} = "\xEF\x83\xA7" +stock const symbol_snowflake{} = "\xEF\x8B\x9C" +stock const symbol_ban{} = "\xEF\x81\x9E" +stock const symbol_settings{} = "\xEE\xA2\xB8" +stock const symbol_brightnessLow{} = "\xEE\x8E\xAA" +stock const symbol_brightnessMedium{} = "\xEE\x8E\xAB" +stock const symbol_brightnessHigh{} = "\xEE\x8E\xAC" +stock const symbol_notificationsOff{} = "\xEE\x9F\xB6" +stock const symbol_notificationsOn{} = "\xEE\x9F\xB7" +stock const symbol_flashlight{} = "\xEF\x80\x8B" +stock const symbol_paintbrushLg{} = "\xEE\x90\x8A" diff --git a/src/pawn/programs/program.p b/src/pawn/programs/program.p new file mode 100644 index 0000000000..95a9382edd --- /dev/null +++ b/src/pawn/programs/program.p @@ -0,0 +1,7 @@ +#include "infinitime" + +@start() { + var lv_obj: label = lv_label_create() + lv_obj_set_pos(label, 20, 20) + lv_label_set_text(label, "Hello world") +} diff --git a/src/pawn/programs/watchface_digital.p b/src/pawn/programs/watchface_digital.p new file mode 100644 index 0000000000..caaa20a8bc --- /dev/null +++ b/src/pawn/programs/watchface_digital.p @@ -0,0 +1,118 @@ +#include "infinitime" +#include "symbols" + +var lv_obj: label_time +var lv_obj: label_time_ampm +var lv_obj: label_date +var lv_obj: notification_icon +var lv_obj: heartbeat_icon +var lv_obj: heartbeat_value +var lv_obj: step_icon +var lv_obj: step_value + +var clock_type + +var buffer{20} + +@start() { + status_icons_create(); + clock_type = get_setting(SETTING_CLOCK_TYPE) + + notification_icon = lv_label_create(screen) + lv_obj_set_style_local_color(notification_icon, LV_STYLE_TEXT_COLOR, 0x00FF00) + lv_obj_align(notification_icon, screen, LV_ALIGN_IN_TOP_LEFT); + + heartbeat_icon = lv_label_create(screen, .text = symbol_heartBeat) + lv_obj_set_style_local_color(heartbeat_icon, LV_STYLE_TEXT_COLOR, 0x1B1B1B) + lv_obj_align(heartbeat_icon, screen, LV_ALIGN_IN_BOTTOM_LEFT) + + heartbeat_value = lv_label_create(screen) + lv_obj_set_style_local_color(heartbeat_value, LV_STYLE_TEXT_COLOR, 0xCE1B1B) + lv_obj_align(heartbeat_value, heartbeat_icon, LV_ALIGN_OUT_RIGHT_MID, 5, 0) + + step_value = lv_label_create(screen, .text = "0") + lv_obj_set_style_local_color(step_value, LV_STYLE_TEXT_COLOR, 0x00FFE7) + lv_obj_align(step_value, screen, LV_ALIGN_IN_BOTTOM_RIGHT) + + step_icon = lv_label_create(screen, .text = symbol_shoe) + lv_obj_set_style_local_color(step_icon, LV_STYLE_TEXT_COLOR, 0x00FFE7) + lv_obj_align(step_icon, step_value, LV_ALIGN_OUT_LEFT_MID, -5, 0) + + label_date = lv_label_create(screen) + lv_obj_align(label_date, screen, LV_ALIGN_CENTER, 0, 60) + lv_obj_set_style_local_color(label_date, LV_STYLE_TEXT_COLOR, 0x999999) + + label_time = lv_label_create(screen) + lv_obj_set_style_local_ptr(label_time, LV_STYLE_TEXT_FONT, jetbrains_mono_extrabold_compressed) + lv_obj_align(label_time, screen, LV_ALIGN_IN_RIGHT_MID) + + label_time_ampm = lv_label_create(screen) + lv_obj_align(label_time_ampm, screen, LV_ALIGN_IN_RIGHT_MID, 0, -55) +} + +@refresh() { + status_icons_update(); + + static datetime[DATETIME] + + // Returns true if changed + if (read_datetime(datetime)) { + var hour = datetime.hour + + if (clock_type == CLOCK_TYPE_H12) { + lv_label_set_text(label_time_ampm, hour < 12 ? "AM" : "PM") + lv_obj_realign(label_time_ampm) + + if (hour == 0) hour = 12 + else if (hour > 12) hour -= 12 + } + sprintf(buffer, _, clock_type == CLOCK_TYPE_H12 ? "%2d:%02d" : "%02d:%02d", hour, datetime.minute) + lv_label_set_text(label_time, buffer) + lv_obj_realign(label_time) + + static day{4} + static month{4} + read_datetime_short_str(day, month) + + if (clock_type == CLOCK_TYPE_H12) + sprintf(buffer, _, "%s %s %d %d", day, month, datetime.year, datetime.day) + else + sprintf(buffer, _, "%s %d %s %d", day, datetime.day, month, datetime.year) + + lv_label_set_text(label_date, buffer) + lv_obj_realign(label_date) + } + + static new_notifs + if (swap(new_notifs, has_new_notifications())) { + lv_label_set_text(notification_icon, new_notifs ? symbol_info : "") + } + + static heartrate + static heartrate_state: heartrate_state + + var updated = swap(heartrate, get_heartrate()) + updated = swap(heartrate_state, get_heartrate_state()) || updated // Circumvent short-circuiting + + if (updated) { + if (heartrate_state == HEARTRATE_RUNNING) { + lv_obj_set_style_local_color(heartbeat_icon, LV_STYLE_TEXT_COLOR, 0xCE1B1B) + sprintf(buffer, _, "%d", heartrate) + lv_label_set_text(heartbeat_value, buffer) + } else { + lv_obj_set_style_local_color(heartbeat_icon, LV_STYLE_TEXT_COLOR, 0x1B1B1B) + lv_label_set_text(heartbeat_value, "") + } + + lv_obj_realign(heartbeat_icon) + lv_obj_realign(heartbeat_value) + } + + static steps + if (swap(steps, get_step_number())) { + sprintf(buffer, _, "%d", steps) + lv_label_set_text(step_value, buffer) + lv_obj_realign(step_value) + lv_obj_realign(step_icon) + } +}