From 3e9504856f9b4f07b2befa0845fe500fbdfba9f9 Mon Sep 17 00:00:00 2001 From: Thanasis Antoniou Date: Tue, 12 Nov 2019 20:01:43 +0200 Subject: ANDROID: Override UTF-8 compliant definition of vsn_printf This sets Android as a non-standard port in configure in order to override the definition for vsn_printf The vsn_printf implementation is taken from https://github.com/weiss/c99-snprintf --- backends/platform/android/android.cpp | 2 +- backends/platform/android/android.h | 2 + backends/platform/android/asset-archive.cpp | 2 +- backends/platform/android/events.cpp | 2 +- backends/platform/android/graphics.cpp | 2 +- backends/platform/android/jni-android.cpp | 753 +++++++++++++++ backends/platform/android/jni-android.h | 161 ++++ backends/platform/android/jni.cpp | 754 --------------- backends/platform/android/jni.h | 161 ---- backends/platform/android/module.mk | 5 +- backends/platform/android/portdefs.h | 48 + backends/platform/android/snprintf.cpp | 1323 +++++++++++++++++++++++++++ 12 files changed, 2294 insertions(+), 921 deletions(-) create mode 100644 backends/platform/android/jni-android.cpp create mode 100644 backends/platform/android/jni-android.h delete mode 100644 backends/platform/android/jni.cpp delete mode 100644 backends/platform/android/jni.h create mode 100644 backends/platform/android/portdefs.h create mode 100644 backends/platform/android/snprintf.cpp (limited to 'backends/platform/android') diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp index c1f8a6c493..e450c70e3a 100644 --- a/backends/platform/android/android.cpp +++ b/backends/platform/android/android.cpp @@ -61,7 +61,7 @@ #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" -#include "backends/platform/android/jni.h" +#include "backends/platform/android/jni-android.h" #include "backends/platform/android/android.h" #include "backends/platform/android/graphics.h" diff --git a/backends/platform/android/android.h b/backends/platform/android/android.h index bd6f015a7f..b5cb1d50fc 100644 --- a/backends/platform/android/android.h +++ b/backends/platform/android/android.h @@ -25,12 +25,14 @@ #if defined(__ANDROID__) +#include "backends/platform/android/portdefs.h" #include "common/fs.h" #include "common/archive.h" #include "audio/mixer_intern.h" #include "backends/modular-backend.h" #include "backends/plugins/posix/posix-provider.h" #include "backends/fs/posix/posix-fs-factory.h" +#include "backends/fs/posix/posix-fs-factory.h" #include diff --git a/backends/platform/android/asset-archive.cpp b/backends/platform/android/asset-archive.cpp index 0ee6efc111..44e5ed80f2 100644 --- a/backends/platform/android/asset-archive.cpp +++ b/backends/platform/android/asset-archive.cpp @@ -32,7 +32,7 @@ #include "common/debug.h" #include "common/textconsole.h" -#include "backends/platform/android/jni.h" +#include "backends/platform/android/jni-android.h" #include "backends/platform/android/asset-archive.h" #include diff --git a/backends/platform/android/events.cpp b/backends/platform/android/events.cpp index da35fb0dd4..2765b07045 100644 --- a/backends/platform/android/events.cpp +++ b/backends/platform/android/events.cpp @@ -42,7 +42,7 @@ #include "backends/platform/android/android.h" #include "backends/platform/android/graphics.h" #include "backends/platform/android/events.h" -#include "backends/platform/android/jni.h" +#include "backends/platform/android/jni-android.h" // floating point. use sparingly template diff --git a/backends/platform/android/graphics.cpp b/backends/platform/android/graphics.cpp index c359fcfba4..041293544d 100644 --- a/backends/platform/android/graphics.cpp +++ b/backends/platform/android/graphics.cpp @@ -39,7 +39,7 @@ #include "backends/platform/android/android.h" #include "backends/platform/android/graphics.h" -#include "backends/platform/android/jni.h" +#include "backends/platform/android/jni-android.h" // // AndroidGraphicsManager diff --git a/backends/platform/android/jni-android.cpp b/backends/platform/android/jni-android.cpp new file mode 100644 index 0000000000..b4a4dac6f6 --- /dev/null +++ b/backends/platform/android/jni-android.cpp @@ -0,0 +1,753 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#if defined(__ANDROID__) + +// Allow use of stuff in and abort() +#define FORBIDDEN_SYMBOL_EXCEPTION_time_h +#define FORBIDDEN_SYMBOL_EXCEPTION_abort + +// Disable printf override in common/forbidden.h to avoid +// clashes with log.h from the Android SDK. +// That header file uses +// __attribute__ ((format(printf, 3, 4))) +// which gets messed up by our override mechanism; this could +// be avoided by either changing the Android SDK to use the equally +// legal and valid +// __attribute__ ((format(printf, 3, 4))) +// or by refining our printf override to use a varadic macro +// (which then wouldn't be portable, though). +// Anyway, for now we just disable the printf override globally +// for the Android port +#define FORBIDDEN_SYMBOL_EXCEPTION_printf + +#include "base/main.h" +#include "base/version.h" +#include "common/config-manager.h" +#include "common/error.h" +#include "common/textconsole.h" +#include "common/translation.h" +#include "common/encoding.h" +#include "engines/engine.h" + +#include "backends/platform/android/android.h" +#include "backends/platform/android/asset-archive.h" +#include "backends/platform/android/jni-android.h" + +__attribute__ ((visibility("default"))) +jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { + return JNI::onLoad(vm); +} + +JavaVM *JNI::_vm = 0; +jobject JNI::_jobj = 0; +jobject JNI::_jobj_audio_track = 0; +jobject JNI::_jobj_egl = 0; +jobject JNI::_jobj_egl_display = 0; +jobject JNI::_jobj_egl_surface = 0; + +Common::Archive *JNI::_asset_archive = 0; +OSystem_Android *JNI::_system = 0; + +bool JNI::pause = false; +sem_t JNI::pause_sem = { 0 }; + +int JNI::surface_changeid = 0; +int JNI::egl_surface_width = 0; +int JNI::egl_surface_height = 0; +bool JNI::_ready_for_events = 0; + +jmethodID JNI::_MID_getDPI = 0; +jmethodID JNI::_MID_displayMessageOnOSD = 0; +jmethodID JNI::_MID_openUrl = 0; +jmethodID JNI::_MID_hasTextInClipboard = 0; +jmethodID JNI::_MID_getTextFromClipboard = 0; +jmethodID JNI::_MID_setTextInClipboard = 0; +jmethodID JNI::_MID_isConnectionLimited = 0; +jmethodID JNI::_MID_setWindowCaption = 0; +jmethodID JNI::_MID_showVirtualKeyboard = 0; +jmethodID JNI::_MID_showKeyboardControl = 0; +jmethodID JNI::_MID_getSysArchives = 0; +jmethodID JNI::_MID_getAllStorageLocations = 0; +jmethodID JNI::_MID_initSurface = 0; +jmethodID JNI::_MID_deinitSurface = 0; + +jmethodID JNI::_MID_EGL10_eglSwapBuffers = 0; + +jmethodID JNI::_MID_AudioTrack_flush = 0; +jmethodID JNI::_MID_AudioTrack_pause = 0; +jmethodID JNI::_MID_AudioTrack_play = 0; +jmethodID JNI::_MID_AudioTrack_stop = 0; +jmethodID JNI::_MID_AudioTrack_write = 0; + +const JNINativeMethod JNI::_natives[] = { + { "create", "(Landroid/content/res/AssetManager;" + "Ljavax/microedition/khronos/egl/EGL10;" + "Ljavax/microedition/khronos/egl/EGLDisplay;" + "Landroid/media/AudioTrack;II)V", + (void *)JNI::create }, + { "destroy", "()V", + (void *)JNI::destroy }, + { "setSurface", "(II)V", + (void *)JNI::setSurface }, + { "main", "([Ljava/lang/String;)I", + (void *)JNI::main }, + { "pushEvent", "(IIIIIII)V", + (void *)JNI::pushEvent }, + { "setPause", "(Z)V", + (void *)JNI::setPause }, + { "getCurrentCharset", "()Ljava/lang/String;", + (void *)JNI::getCurrentCharset } +}; + +JNI::JNI() { +} + +JNI::~JNI() { +} + +jint JNI::onLoad(JavaVM *vm) { + _vm = vm; + + JNIEnv *env; + + if (_vm->GetEnv((void **)&env, JNI_VERSION_1_2)) + return JNI_ERR; + + jclass cls = env->FindClass("org/scummvm/scummvm/ScummVM"); + if (cls == 0) + return JNI_ERR; + + if (env->RegisterNatives(cls, _natives, ARRAYSIZE(_natives)) < 0) + return JNI_ERR; + + return JNI_VERSION_1_2; +} + +JNIEnv *JNI::getEnv() { + JNIEnv *env = 0; + + jint res = _vm->GetEnv((void **)&env, JNI_VERSION_1_2); + + if (res != JNI_OK) { + LOGE("GetEnv() failed: %d", res); + abort(); + } + + return env; +} + +void JNI::attachThread() { + JNIEnv *env = 0; + + jint res = _vm->AttachCurrentThread(&env, 0); + + if (res != JNI_OK) { + LOGE("AttachCurrentThread() failed: %d", res); + abort(); + } +} + +void JNI::detachThread() { + jint res = _vm->DetachCurrentThread(); + + if (res != JNI_OK) { + LOGE("DetachCurrentThread() failed: %d", res); + abort(); + } +} + +void JNI::setReadyForEvents(bool ready) { + _ready_for_events = ready; +} + +void JNI::throwByName(JNIEnv *env, const char *name, const char *msg) { + jclass cls = env->FindClass(name); + + // if cls is 0, an exception has already been thrown + if (cls != 0) + env->ThrowNew(cls, msg); + + env->DeleteLocalRef(cls); +} + +void JNI::throwRuntimeException(JNIEnv *env, const char *msg) { + throwByName(env, "java/lang/RuntimeException", msg); +} + +// calls to the dark side + +void JNI::getDPI(float *values) { + values[0] = 0.0; + values[1] = 0.0; + + JNIEnv *env = JNI::getEnv(); + + jfloatArray array = env->NewFloatArray(2); + + env->CallVoidMethod(_jobj, _MID_getDPI, array); + + if (env->ExceptionCheck()) { + LOGE("Failed to get DPIs"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } else { + jfloat *res = env->GetFloatArrayElements(array, 0); + + if (res) { + values[0] = res[0]; + values[1] = res[1]; + + env->ReleaseFloatArrayElements(array, res, 0); + } + } + + env->DeleteLocalRef(array); +} + +void JNI::displayMessageOnOSD(const char *msg) { + // called from common/osd_message_queue, method: OSDMessageQueue::pollEvent() + JNIEnv *env = JNI::getEnv(); + Common::String fromEncoding = "ISO-8859-1"; +#ifdef USE_TRANSLATION + if (TransMan.getCurrentCharset() != "ASCII") { + fromEncoding = TransMan.getCurrentCharset(); + } +#endif + Common::Encoding converter("UTF-8", fromEncoding.c_str()); + + const char *utf8Msg = converter.convert(msg, converter.stringLength(msg, fromEncoding) ); + if (utf8Msg == nullptr) { + // Show a placeholder indicative of the translation error instead of silent failing + utf8Msg = "?"; + LOGE("Failed to convert message to UTF-8 for OSD!"); + } + jstring java_msg = env->NewStringUTF(utf8Msg); + + env->CallVoidMethod(_jobj, _MID_displayMessageOnOSD, java_msg); + + if (env->ExceptionCheck()) { + LOGE("Failed to display OSD message"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->DeleteLocalRef(java_msg); +} + +bool JNI::openUrl(const char *url) { + bool success = true; + JNIEnv *env = JNI::getEnv(); + jstring javaUrl = env->NewStringUTF(url); + + env->CallVoidMethod(_jobj, _MID_openUrl, javaUrl); + + if (env->ExceptionCheck()) { + LOGE("Failed to open URL"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + success = false; + } + + env->DeleteLocalRef(javaUrl); + return success; +} + +bool JNI::hasTextInClipboard() { + JNIEnv *env = JNI::getEnv(); + bool hasText = env->CallBooleanMethod(_jobj, _MID_hasTextInClipboard); + + if (env->ExceptionCheck()) { + LOGE("Failed to check the contents of the clipboard"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + hasText = true; + } + + return hasText; +} + +Common::String JNI::getTextFromClipboard() { + JNIEnv *env = JNI::getEnv(); + + jbyteArray javaText = (jbyteArray)env->CallObjectMethod(_jobj, _MID_getTextFromClipboard); + + if (env->ExceptionCheck()) { + LOGE("Failed to retrieve text from the clipboard"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + + return Common::String(); + } + + int len = env->GetArrayLength(javaText); + char* buf = new char[len]; + env->GetByteArrayRegion(javaText, 0, len, reinterpret_cast(buf)); + Common::String text(buf, len); + delete[] buf; + + return text; +} + +bool JNI::setTextInClipboard(const Common::String &text) { + JNIEnv *env = JNI::getEnv(); + + jbyteArray javaText = env->NewByteArray(text.size()); + env->SetByteArrayRegion(javaText, 0, text.size(), reinterpret_cast(text.c_str())); + + bool success = env->CallBooleanMethod(_jobj, _MID_setTextInClipboard, javaText); + + if (env->ExceptionCheck()) { + LOGE("Failed to add text to the clipboard"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + success = false; + } + + env->DeleteLocalRef(javaText); + return success; +} + +bool JNI::isConnectionLimited() { + JNIEnv *env = JNI::getEnv(); + bool limited = env->CallBooleanMethod(_jobj, _MID_isConnectionLimited); + + if (env->ExceptionCheck()) { + LOGE("Failed to check whether connection's limited"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + limited = true; + } + + return limited; +} + +void JNI::setWindowCaption(const char *caption) { + JNIEnv *env = JNI::getEnv(); + jstring java_caption = env->NewStringUTF(caption); + + env->CallVoidMethod(_jobj, _MID_setWindowCaption, java_caption); + + if (env->ExceptionCheck()) { + LOGE("Failed to set window caption"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->DeleteLocalRef(java_caption); +} + +void JNI::showVirtualKeyboard(bool enable) { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj, _MID_showVirtualKeyboard, enable); + + if (env->ExceptionCheck()) { + LOGE("Error trying to show virtual keyboard"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +void JNI::showKeyboardControl(bool enable) { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj, _MID_showKeyboardControl, enable); + + if (env->ExceptionCheck()) { + LOGE("Error trying to show virtual keyboard control"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { + JNIEnv *env = JNI::getEnv(); + + s.add("ASSET", _asset_archive, priority, false); + + jobjectArray array = + (jobjectArray)env->CallObjectMethod(_jobj, _MID_getSysArchives); + + if (env->ExceptionCheck()) { + LOGE("Error finding system archive path"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + + return; + } + + jsize size = env->GetArrayLength(array); + for (jsize i = 0; i < size; ++i) { + jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); + const char *path = env->GetStringUTFChars(path_obj, 0); + + if (path != 0) { + s.addDirectory(path, path, priority); + env->ReleaseStringUTFChars(path_obj, path); + } + + env->DeleteLocalRef(path_obj); + } +} + +bool JNI::initSurface() { + JNIEnv *env = JNI::getEnv(); + + jobject obj = env->CallObjectMethod(_jobj, _MID_initSurface); + + if (!obj || env->ExceptionCheck()) { + LOGE("initSurface failed"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + + return false; + } + + _jobj_egl_surface = env->NewGlobalRef(obj); + + return true; +} + +void JNI::deinitSurface() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj, _MID_deinitSurface); + + if (env->ExceptionCheck()) { + LOGE("deinitSurface failed"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->DeleteGlobalRef(_jobj_egl_surface); + _jobj_egl_surface = 0; +} + +void JNI::setAudioPause() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_flush); + + if (env->ExceptionCheck()) { + LOGE("Error flushing AudioTrack"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_pause); + + if (env->ExceptionCheck()) { + LOGE("Error setting AudioTrack: pause"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +void JNI::setAudioPlay() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_play); + + if (env->ExceptionCheck()) { + LOGE("Error setting AudioTrack: play"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +void JNI::setAudioStop() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_stop); + + if (env->ExceptionCheck()) { + LOGE("Error setting AudioTrack: stop"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +// natives for the dark side + +void JNI::create(JNIEnv *env, jobject self, jobject asset_manager, + jobject egl, jobject egl_display, + jobject at, jint audio_sample_rate, jint audio_buffer_size) { + LOGI("%s", gScummVMFullVersion); + + assert(!_system); + + pause = false; + // initial value of zero! + sem_init(&pause_sem, 0, 0); + + _asset_archive = new AndroidAssetArchive(asset_manager); + assert(_asset_archive); + + _system = new OSystem_Android(audio_sample_rate, audio_buffer_size); + assert(_system); + + // weak global ref to allow class to be unloaded + // ... except dalvik implements NewWeakGlobalRef only on froyo + //_jobj = env->NewWeakGlobalRef(self); + + _jobj = env->NewGlobalRef(self); + + jclass cls = env->GetObjectClass(_jobj); + +#define FIND_METHOD(prefix, name, signature) do { \ + _MID_ ## prefix ## name = env->GetMethodID(cls, #name, signature); \ + if (_MID_ ## prefix ## name == 0) \ + return; \ + } while (0) + + FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V"); + FIND_METHOD(, getDPI, "([F)V"); + FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V"); + FIND_METHOD(, openUrl, "(Ljava/lang/String;)V"); + FIND_METHOD(, hasTextInClipboard, "()Z"); + FIND_METHOD(, getTextFromClipboard, "()[B"); + FIND_METHOD(, setTextInClipboard, "([B)Z"); + FIND_METHOD(, isConnectionLimited, "()Z"); + FIND_METHOD(, showVirtualKeyboard, "(Z)V"); + FIND_METHOD(, showKeyboardControl, "(Z)V"); + FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;"); + FIND_METHOD(, getAllStorageLocations, "()[Ljava/lang/String;"); + FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;"); + FIND_METHOD(, deinitSurface, "()V"); + + _jobj_egl = env->NewGlobalRef(egl); + _jobj_egl_display = env->NewGlobalRef(egl_display); + + cls = env->GetObjectClass(_jobj_egl); + + FIND_METHOD(EGL10_, eglSwapBuffers, + "(Ljavax/microedition/khronos/egl/EGLDisplay;" + "Ljavax/microedition/khronos/egl/EGLSurface;)Z"); + + _jobj_audio_track = env->NewGlobalRef(at); + + cls = env->GetObjectClass(_jobj_audio_track); + + FIND_METHOD(AudioTrack_, flush, "()V"); + FIND_METHOD(AudioTrack_, pause, "()V"); + FIND_METHOD(AudioTrack_, play, "()V"); + FIND_METHOD(AudioTrack_, stop, "()V"); + FIND_METHOD(AudioTrack_, write, "([BII)I"); + +#undef FIND_METHOD + + g_system = _system; +} + +void JNI::destroy(JNIEnv *env, jobject self) { + delete _asset_archive; + _asset_archive = 0; + + // _system is a pointer of OSystem_Android <--- ModularBackend <--- BaseBacked <--- Common::OSystem + // It's better to call destroy() rather than just delete here + // to avoid mutex issues if a Common::String is used after this point + _system->destroy(); + + g_system = 0; + _system = 0; + + sem_destroy(&pause_sem); + + // see above + //JNI::getEnv()->DeleteWeakGlobalRef(_jobj); + + JNI::getEnv()->DeleteGlobalRef(_jobj_egl_display); + JNI::getEnv()->DeleteGlobalRef(_jobj_egl); + JNI::getEnv()->DeleteGlobalRef(_jobj_audio_track); + JNI::getEnv()->DeleteGlobalRef(_jobj); +} + +void JNI::setSurface(JNIEnv *env, jobject self, jint width, jint height) { + egl_surface_width = width; + egl_surface_height = height; + surface_changeid++; +} + +jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) { + assert(_system); + + const int MAX_NARGS = 32; + int res = -1; + + int argc = env->GetArrayLength(args); + if (argc > MAX_NARGS) { + throwByName(env, "java/lang/IllegalArgumentException", + "too many arguments"); + return 0; + } + + char *argv[MAX_NARGS]; + + // note use in cleanup loop below + int nargs; + + for (nargs = 0; nargs < argc; ++nargs) { + jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); + + if (arg == 0) { + argv[nargs] = 0; + } else { + const char *cstr = env->GetStringUTFChars(arg, 0); + + argv[nargs] = const_cast(cstr); + + // exception already thrown? + if (cstr == 0) + goto cleanup; + } + + env->DeleteLocalRef(arg); + } + + LOGI("Entering scummvm_main with %d args", argc); + + res = scummvm_main(argc, argv); + + LOGI("scummvm_main exited with code %d", res); + + _system->quit(); + +cleanup: + nargs--; + + for (int i = 0; i < nargs; ++i) { + if (argv[i] == 0) + continue; + + jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); + + // Exception already thrown? + if (arg == 0) + return res; + + env->ReleaseStringUTFChars(arg, argv[i]); + env->DeleteLocalRef(arg); + } + + return res; +} + +void JNI::pushEvent(JNIEnv *env, jobject self, int type, int arg1, int arg2, + int arg3, int arg4, int arg5, int arg6) { + // drop events until we're ready and after we quit + if (!_ready_for_events) { + LOGW("dropping event"); + return; + } + + assert(_system); + + _system->pushEvent(type, arg1, arg2, arg3, arg4, arg5, arg6); +} + +void JNI::setPause(JNIEnv *env, jobject self, jboolean value) { + if (!_system) + return; + + if (g_engine) { + LOGD("pauseEngine: %d", value); + + g_engine->pauseEngine(value); + + /*if (value && + g_engine->hasFeature(Engine::kSupportsSavingDuringRuntime) && + g_engine->canSaveGameStateCurrently()) + g_engine->saveGameState(0, "Android parachute");*/ + } + + pause = value; + + if (!pause) { + // wake up all threads + for (uint i = 0; i < 3; ++i) + sem_post(&pause_sem); + } +} + +jstring JNI::getCurrentCharset(JNIEnv *env, jobject self) { +#ifdef USE_TRANSLATION + if (TransMan.getCurrentCharset() != "ASCII") { +// LOGD("getCurrentCharset: %s", TransMan.getCurrentCharset().c_str()); + return env->NewStringUTF(TransMan.getCurrentCharset().c_str()); + } +#endif + return env->NewStringUTF("ISO-8859-1"); +} + +Common::Array JNI::getAllStorageLocations() { + Common::Array *res = new Common::Array(); + + JNIEnv *env = JNI::getEnv(); + + jobjectArray array = + (jobjectArray)env->CallObjectMethod(_jobj, _MID_getAllStorageLocations); + + if (env->ExceptionCheck()) { + LOGE("Error finding system archive path"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + + return *res; + } + + jsize size = env->GetArrayLength(array); + for (jsize i = 0; i < size; ++i) { + jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); + const char *path = env->GetStringUTFChars(path_obj, 0); + + if (path != 0) { + res->push_back(path); + env->ReleaseStringUTFChars(path_obj, path); + } + + env->DeleteLocalRef(path_obj); + } + + return *res; +} + + +#endif diff --git a/backends/platform/android/jni-android.h b/backends/platform/android/jni-android.h new file mode 100644 index 0000000000..fe4b948070 --- /dev/null +++ b/backends/platform/android/jni-android.h @@ -0,0 +1,161 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef _ANDROID_JNI_H_ +#define _ANDROID_JNI_H_ + +#if defined(__ANDROID__) + +#include +#include + +#include "common/fs.h" +#include "common/archive.h" +#include "common/array.h" + +class OSystem_Android; + +class JNI { +private: + JNI(); + virtual ~JNI(); + +public: + static bool pause; + static sem_t pause_sem; + + static int surface_changeid; + static int egl_surface_width; + static int egl_surface_height; + + static jint onLoad(JavaVM *vm); + + static JNIEnv *getEnv(); + + static void attachThread(); + static void detachThread(); + + static void setReadyForEvents(bool ready); + + static void setWindowCaption(const char *caption); + static void getDPI(float *values); + static void displayMessageOnOSD(const char *msg); + static bool openUrl(const char *url); + static bool hasTextInClipboard(); + static Common::String getTextFromClipboard(); + static bool setTextInClipboard(const Common::String &text); + static bool isConnectionLimited(); + static void showVirtualKeyboard(bool enable); + static void showKeyboardControl(bool enable); + static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority); + + static inline bool haveSurface(); + static inline bool swapBuffers(); + static bool initSurface(); + static void deinitSurface(); + + static void setAudioPause(); + static void setAudioPlay(); + static void setAudioStop(); + + static inline int writeAudio(JNIEnv *env, jbyteArray &data, int offset, + int size); + + static Common::Array getAllStorageLocations(); + +private: + static JavaVM *_vm; + // back pointer to (java) peer instance + static jobject _jobj; + static jobject _jobj_audio_track; + static jobject _jobj_egl; + static jobject _jobj_egl_display; + static jobject _jobj_egl_surface; + + static Common::Archive *_asset_archive; + static OSystem_Android *_system; + + static bool _ready_for_events; + + static jmethodID _MID_getDPI; + static jmethodID _MID_displayMessageOnOSD; + static jmethodID _MID_openUrl; + static jmethodID _MID_hasTextInClipboard; + static jmethodID _MID_getTextFromClipboard; + static jmethodID _MID_setTextInClipboard; + static jmethodID _MID_isConnectionLimited; + static jmethodID _MID_setWindowCaption; + static jmethodID _MID_showVirtualKeyboard; + static jmethodID _MID_showKeyboardControl; + static jmethodID _MID_getSysArchives; + static jmethodID _MID_getAllStorageLocations; + static jmethodID _MID_initSurface; + static jmethodID _MID_deinitSurface; + + static jmethodID _MID_EGL10_eglSwapBuffers; + + static jmethodID _MID_AudioTrack_flush; + static jmethodID _MID_AudioTrack_pause; + static jmethodID _MID_AudioTrack_play; + static jmethodID _MID_AudioTrack_stop; + static jmethodID _MID_AudioTrack_write; + + static const JNINativeMethod _natives[]; + + static void throwByName(JNIEnv *env, const char *name, const char *msg); + static void throwRuntimeException(JNIEnv *env, const char *msg); + + // natives for the dark side + static void create(JNIEnv *env, jobject self, jobject asset_manager, + jobject egl, jobject egl_display, + jobject at, jint audio_sample_rate, + jint audio_buffer_size); + static void destroy(JNIEnv *env, jobject self); + + static void setSurface(JNIEnv *env, jobject self, jint width, jint height); + static jint main(JNIEnv *env, jobject self, jobjectArray args); + + static void pushEvent(JNIEnv *env, jobject self, int type, int arg1, + int arg2, int arg3, int arg4, int arg5, int arg6); + static void setPause(JNIEnv *env, jobject self, jboolean value); + + static jstring getCurrentCharset(JNIEnv *env, jobject self); +}; + +inline bool JNI::haveSurface() { + return _jobj_egl_surface != 0; +} + +inline bool JNI::swapBuffers() { + JNIEnv *env = JNI::getEnv(); + + return env->CallBooleanMethod(_jobj_egl, _MID_EGL10_eglSwapBuffers, + _jobj_egl_display, _jobj_egl_surface); +} + +inline int JNI::writeAudio(JNIEnv *env, jbyteArray &data, int offset, int size) { + return env->CallIntMethod(_jobj_audio_track, _MID_AudioTrack_write, data, + offset, size); +} + +#endif +#endif diff --git a/backends/platform/android/jni.cpp b/backends/platform/android/jni.cpp deleted file mode 100644 index cedee38b7e..0000000000 --- a/backends/platform/android/jni.cpp +++ /dev/null @@ -1,754 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#if defined(__ANDROID__) - -// Allow use of stuff in and abort() -#define FORBIDDEN_SYMBOL_EXCEPTION_time_h -#define FORBIDDEN_SYMBOL_EXCEPTION_abort - -// Disable printf override in common/forbidden.h to avoid -// clashes with log.h from the Android SDK. -// That header file uses -// __attribute__ ((format(printf, 3, 4))) -// which gets messed up by our override mechanism; this could -// be avoided by either changing the Android SDK to use the equally -// legal and valid -// __attribute__ ((format(printf, 3, 4))) -// or by refining our printf override to use a varadic macro -// (which then wouldn't be portable, though). -// Anyway, for now we just disable the printf override globally -// for the Android port -#define FORBIDDEN_SYMBOL_EXCEPTION_printf - -#include "base/main.h" -#include "base/version.h" -#include "common/config-manager.h" -#include "common/error.h" -#include "common/textconsole.h" -#include "common/translation.h" -#include "common/encoding.h" -#include "engines/engine.h" - -#include "backends/platform/android/android.h" -#include "backends/platform/android/asset-archive.h" -#include "backends/platform/android/jni.h" - -__attribute__ ((visibility("default"))) -jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { - return JNI::onLoad(vm); -} - -JavaVM *JNI::_vm = 0; -jobject JNI::_jobj = 0; -jobject JNI::_jobj_audio_track = 0; -jobject JNI::_jobj_egl = 0; -jobject JNI::_jobj_egl_display = 0; -jobject JNI::_jobj_egl_surface = 0; - -Common::Archive *JNI::_asset_archive = 0; -OSystem_Android *JNI::_system = 0; - -bool JNI::pause = false; -sem_t JNI::pause_sem = { 0 }; - -int JNI::surface_changeid = 0; -int JNI::egl_surface_width = 0; -int JNI::egl_surface_height = 0; -bool JNI::_ready_for_events = 0; - -jmethodID JNI::_MID_getDPI = 0; -jmethodID JNI::_MID_displayMessageOnOSD = 0; -jmethodID JNI::_MID_openUrl = 0; -jmethodID JNI::_MID_hasTextInClipboard = 0; -jmethodID JNI::_MID_getTextFromClipboard = 0; -jmethodID JNI::_MID_setTextInClipboard = 0; -jmethodID JNI::_MID_isConnectionLimited = 0; -jmethodID JNI::_MID_setWindowCaption = 0; -jmethodID JNI::_MID_showVirtualKeyboard = 0; -jmethodID JNI::_MID_showKeyboardControl = 0; -jmethodID JNI::_MID_getSysArchives = 0; -jmethodID JNI::_MID_getAllStorageLocations = 0; -jmethodID JNI::_MID_initSurface = 0; -jmethodID JNI::_MID_deinitSurface = 0; - -jmethodID JNI::_MID_EGL10_eglSwapBuffers = 0; - -jmethodID JNI::_MID_AudioTrack_flush = 0; -jmethodID JNI::_MID_AudioTrack_pause = 0; -jmethodID JNI::_MID_AudioTrack_play = 0; -jmethodID JNI::_MID_AudioTrack_stop = 0; -jmethodID JNI::_MID_AudioTrack_write = 0; - -const JNINativeMethod JNI::_natives[] = { - { "create", "(Landroid/content/res/AssetManager;" - "Ljavax/microedition/khronos/egl/EGL10;" - "Ljavax/microedition/khronos/egl/EGLDisplay;" - "Landroid/media/AudioTrack;II)V", - (void *)JNI::create }, - { "destroy", "()V", - (void *)JNI::destroy }, - { "setSurface", "(II)V", - (void *)JNI::setSurface }, - { "main", "([Ljava/lang/String;)I", - (void *)JNI::main }, - { "pushEvent", "(IIIIIII)V", - (void *)JNI::pushEvent }, - { "setPause", "(Z)V", - (void *)JNI::setPause }, - { "getCurrentCharset", "()Ljava/lang/String;", - (void *)JNI::getCurrentCharset } -}; - -JNI::JNI() { -} - -JNI::~JNI() { -} - -jint JNI::onLoad(JavaVM *vm) { - _vm = vm; - - JNIEnv *env; - - if (_vm->GetEnv((void **)&env, JNI_VERSION_1_2)) - return JNI_ERR; - - jclass cls = env->FindClass("org/scummvm/scummvm/ScummVM"); - if (cls == 0) - return JNI_ERR; - - if (env->RegisterNatives(cls, _natives, ARRAYSIZE(_natives)) < 0) - return JNI_ERR; - - return JNI_VERSION_1_2; -} - -JNIEnv *JNI::getEnv() { - JNIEnv *env = 0; - - jint res = _vm->GetEnv((void **)&env, JNI_VERSION_1_2); - - if (res != JNI_OK) { - LOGE("GetEnv() failed: %d", res); - abort(); - } - - return env; -} - -void JNI::attachThread() { - JNIEnv *env = 0; - - jint res = _vm->AttachCurrentThread(&env, 0); - - if (res != JNI_OK) { - LOGE("AttachCurrentThread() failed: %d", res); - abort(); - } -} - -void JNI::detachThread() { - jint res = _vm->DetachCurrentThread(); - - if (res != JNI_OK) { - LOGE("DetachCurrentThread() failed: %d", res); - abort(); - } -} - -void JNI::setReadyForEvents(bool ready) { - _ready_for_events = ready; -} - -void JNI::throwByName(JNIEnv *env, const char *name, const char *msg) { - jclass cls = env->FindClass(name); - - // if cls is 0, an exception has already been thrown - if (cls != 0) - env->ThrowNew(cls, msg); - - env->DeleteLocalRef(cls); -} - -void JNI::throwRuntimeException(JNIEnv *env, const char *msg) { - throwByName(env, "java/lang/RuntimeException", msg); -} - -// calls to the dark side - -void JNI::getDPI(float *values) { - values[0] = 0.0; - values[1] = 0.0; - - JNIEnv *env = JNI::getEnv(); - - jfloatArray array = env->NewFloatArray(2); - - env->CallVoidMethod(_jobj, _MID_getDPI, array); - - if (env->ExceptionCheck()) { - LOGE("Failed to get DPIs"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } else { - jfloat *res = env->GetFloatArrayElements(array, 0); - - if (res) { - values[0] = res[0]; - values[1] = res[1]; - - env->ReleaseFloatArrayElements(array, res, 0); - } - } - - env->DeleteLocalRef(array); -} - -void JNI::displayMessageOnOSD(const char *msg) { - // called from common/osd_message_queue, method: OSDMessageQueue::pollEvent() - JNIEnv *env = JNI::getEnv(); -// LOGD("OSD orig MESSAGE: %s", msg); - Common::String fromEncoding = "ISO-8859-1"; -#ifdef USE_TRANSLATION - if (TransMan.getCurrentCharset() != "ASCII") { - fromEncoding = TransMan.getCurrentCharset(); - } -#endif - Common::Encoding converter("UTF-8", fromEncoding.c_str()); - - const char *utf8Msg = converter.convert(msg, converter.stringLength(msg, fromEncoding) ); - if (utf8Msg == nullptr) { - LOGE("Failed to convert message to UTF-8 for OSD!"); - return; - } -// LOGD("OSD target MESSAGE: %s", utf8Msg); - jstring java_msg = env->NewStringUTF(utf8Msg); - - env->CallVoidMethod(_jobj, _MID_displayMessageOnOSD, java_msg); - - if (env->ExceptionCheck()) { - LOGE("Failed to display OSD message"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } - - env->DeleteLocalRef(java_msg); -} - -bool JNI::openUrl(const char *url) { - bool success = true; - JNIEnv *env = JNI::getEnv(); - jstring javaUrl = env->NewStringUTF(url); - - env->CallVoidMethod(_jobj, _MID_openUrl, javaUrl); - - if (env->ExceptionCheck()) { - LOGE("Failed to open URL"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - success = false; - } - - env->DeleteLocalRef(javaUrl); - return success; -} - -bool JNI::hasTextInClipboard() { - JNIEnv *env = JNI::getEnv(); - bool hasText = env->CallBooleanMethod(_jobj, _MID_hasTextInClipboard); - - if (env->ExceptionCheck()) { - LOGE("Failed to check the contents of the clipboard"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - hasText = true; - } - - return hasText; -} - -Common::String JNI::getTextFromClipboard() { - JNIEnv *env = JNI::getEnv(); - - jbyteArray javaText = (jbyteArray)env->CallObjectMethod(_jobj, _MID_getTextFromClipboard); - - if (env->ExceptionCheck()) { - LOGE("Failed to retrieve text from the clipboard"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - return Common::String(); - } - - int len = env->GetArrayLength(javaText); - char* buf = new char[len]; - env->GetByteArrayRegion(javaText, 0, len, reinterpret_cast(buf)); - Common::String text(buf, len); - delete[] buf; - - return text; -} - -bool JNI::setTextInClipboard(const Common::String &text) { - JNIEnv *env = JNI::getEnv(); - - jbyteArray javaText = env->NewByteArray(text.size()); - env->SetByteArrayRegion(javaText, 0, text.size(), reinterpret_cast(text.c_str())); - - bool success = env->CallBooleanMethod(_jobj, _MID_setTextInClipboard, javaText); - - if (env->ExceptionCheck()) { - LOGE("Failed to add text to the clipboard"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - success = false; - } - - env->DeleteLocalRef(javaText); - return success; -} - -bool JNI::isConnectionLimited() { - JNIEnv *env = JNI::getEnv(); - bool limited = env->CallBooleanMethod(_jobj, _MID_isConnectionLimited); - - if (env->ExceptionCheck()) { - LOGE("Failed to check whether connection's limited"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - limited = true; - } - - return limited; -} - -void JNI::setWindowCaption(const char *caption) { - JNIEnv *env = JNI::getEnv(); - jstring java_caption = env->NewStringUTF(caption); - - env->CallVoidMethod(_jobj, _MID_setWindowCaption, java_caption); - - if (env->ExceptionCheck()) { - LOGE("Failed to set window caption"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } - - env->DeleteLocalRef(java_caption); -} - -void JNI::showVirtualKeyboard(bool enable) { - JNIEnv *env = JNI::getEnv(); - - env->CallVoidMethod(_jobj, _MID_showVirtualKeyboard, enable); - - if (env->ExceptionCheck()) { - LOGE("Error trying to show virtual keyboard"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } -} - -void JNI::showKeyboardControl(bool enable) { - JNIEnv *env = JNI::getEnv(); - - env->CallVoidMethod(_jobj, _MID_showKeyboardControl, enable); - - if (env->ExceptionCheck()) { - LOGE("Error trying to show virtual keyboard control"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } -} - -void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { - JNIEnv *env = JNI::getEnv(); - - s.add("ASSET", _asset_archive, priority, false); - - jobjectArray array = - (jobjectArray)env->CallObjectMethod(_jobj, _MID_getSysArchives); - - if (env->ExceptionCheck()) { - LOGE("Error finding system archive path"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - return; - } - - jsize size = env->GetArrayLength(array); - for (jsize i = 0; i < size; ++i) { - jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); - const char *path = env->GetStringUTFChars(path_obj, 0); - - if (path != 0) { - s.addDirectory(path, path, priority); - env->ReleaseStringUTFChars(path_obj, path); - } - - env->DeleteLocalRef(path_obj); - } -} - -bool JNI::initSurface() { - JNIEnv *env = JNI::getEnv(); - - jobject obj = env->CallObjectMethod(_jobj, _MID_initSurface); - - if (!obj || env->ExceptionCheck()) { - LOGE("initSurface failed"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - return false; - } - - _jobj_egl_surface = env->NewGlobalRef(obj); - - return true; -} - -void JNI::deinitSurface() { - JNIEnv *env = JNI::getEnv(); - - env->CallVoidMethod(_jobj, _MID_deinitSurface); - - if (env->ExceptionCheck()) { - LOGE("deinitSurface failed"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } - - env->DeleteGlobalRef(_jobj_egl_surface); - _jobj_egl_surface = 0; -} - -void JNI::setAudioPause() { - JNIEnv *env = JNI::getEnv(); - - env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_flush); - - if (env->ExceptionCheck()) { - LOGE("Error flushing AudioTrack"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } - - env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_pause); - - if (env->ExceptionCheck()) { - LOGE("Error setting AudioTrack: pause"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } -} - -void JNI::setAudioPlay() { - JNIEnv *env = JNI::getEnv(); - - env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_play); - - if (env->ExceptionCheck()) { - LOGE("Error setting AudioTrack: play"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } -} - -void JNI::setAudioStop() { - JNIEnv *env = JNI::getEnv(); - - env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_stop); - - if (env->ExceptionCheck()) { - LOGE("Error setting AudioTrack: stop"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } -} - -// natives for the dark side - -void JNI::create(JNIEnv *env, jobject self, jobject asset_manager, - jobject egl, jobject egl_display, - jobject at, jint audio_sample_rate, jint audio_buffer_size) { - LOGI("%s", gScummVMFullVersion); - - assert(!_system); - - pause = false; - // initial value of zero! - sem_init(&pause_sem, 0, 0); - - _asset_archive = new AndroidAssetArchive(asset_manager); - assert(_asset_archive); - - _system = new OSystem_Android(audio_sample_rate, audio_buffer_size); - assert(_system); - - // weak global ref to allow class to be unloaded - // ... except dalvik implements NewWeakGlobalRef only on froyo - //_jobj = env->NewWeakGlobalRef(self); - - _jobj = env->NewGlobalRef(self); - - jclass cls = env->GetObjectClass(_jobj); - -#define FIND_METHOD(prefix, name, signature) do { \ - _MID_ ## prefix ## name = env->GetMethodID(cls, #name, signature); \ - if (_MID_ ## prefix ## name == 0) \ - return; \ - } while (0) - - FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V"); - FIND_METHOD(, getDPI, "([F)V"); - FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V"); - FIND_METHOD(, openUrl, "(Ljava/lang/String;)V"); - FIND_METHOD(, hasTextInClipboard, "()Z"); - FIND_METHOD(, getTextFromClipboard, "()[B"); - FIND_METHOD(, setTextInClipboard, "([B)Z"); - FIND_METHOD(, isConnectionLimited, "()Z"); - FIND_METHOD(, showVirtualKeyboard, "(Z)V"); - FIND_METHOD(, showKeyboardControl, "(Z)V"); - FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;"); - FIND_METHOD(, getAllStorageLocations, "()[Ljava/lang/String;"); - FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;"); - FIND_METHOD(, deinitSurface, "()V"); - - _jobj_egl = env->NewGlobalRef(egl); - _jobj_egl_display = env->NewGlobalRef(egl_display); - - cls = env->GetObjectClass(_jobj_egl); - - FIND_METHOD(EGL10_, eglSwapBuffers, - "(Ljavax/microedition/khronos/egl/EGLDisplay;" - "Ljavax/microedition/khronos/egl/EGLSurface;)Z"); - - _jobj_audio_track = env->NewGlobalRef(at); - - cls = env->GetObjectClass(_jobj_audio_track); - - FIND_METHOD(AudioTrack_, flush, "()V"); - FIND_METHOD(AudioTrack_, pause, "()V"); - FIND_METHOD(AudioTrack_, play, "()V"); - FIND_METHOD(AudioTrack_, stop, "()V"); - FIND_METHOD(AudioTrack_, write, "([BII)I"); - -#undef FIND_METHOD - - g_system = _system; -} - -void JNI::destroy(JNIEnv *env, jobject self) { - delete _asset_archive; - _asset_archive = 0; - - // _system is a pointer of OSystem_Android <--- ModularBackend <--- BaseBacked <--- Common::OSystem - // It's better to call destroy() rather than just delete here - // to avoid mutex issues if a Common::String is used after this point - _system->destroy(); - - g_system = 0; - _system = 0; - - sem_destroy(&pause_sem); - - // see above - //JNI::getEnv()->DeleteWeakGlobalRef(_jobj); - - JNI::getEnv()->DeleteGlobalRef(_jobj_egl_display); - JNI::getEnv()->DeleteGlobalRef(_jobj_egl); - JNI::getEnv()->DeleteGlobalRef(_jobj_audio_track); - JNI::getEnv()->DeleteGlobalRef(_jobj); -} - -void JNI::setSurface(JNIEnv *env, jobject self, jint width, jint height) { - egl_surface_width = width; - egl_surface_height = height; - surface_changeid++; -} - -jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) { - assert(_system); - - const int MAX_NARGS = 32; - int res = -1; - - int argc = env->GetArrayLength(args); - if (argc > MAX_NARGS) { - throwByName(env, "java/lang/IllegalArgumentException", - "too many arguments"); - return 0; - } - - char *argv[MAX_NARGS]; - - // note use in cleanup loop below - int nargs; - - for (nargs = 0; nargs < argc; ++nargs) { - jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); - - if (arg == 0) { - argv[nargs] = 0; - } else { - const char *cstr = env->GetStringUTFChars(arg, 0); - - argv[nargs] = const_cast(cstr); - - // exception already thrown? - if (cstr == 0) - goto cleanup; - } - - env->DeleteLocalRef(arg); - } - - LOGI("Entering scummvm_main with %d args", argc); - - res = scummvm_main(argc, argv); - - LOGI("scummvm_main exited with code %d", res); - - _system->quit(); - -cleanup: - nargs--; - - for (int i = 0; i < nargs; ++i) { - if (argv[i] == 0) - continue; - - jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); - - // Exception already thrown? - if (arg == 0) - return res; - - env->ReleaseStringUTFChars(arg, argv[i]); - env->DeleteLocalRef(arg); - } - - return res; -} - -void JNI::pushEvent(JNIEnv *env, jobject self, int type, int arg1, int arg2, - int arg3, int arg4, int arg5, int arg6) { - // drop events until we're ready and after we quit - if (!_ready_for_events) { - LOGW("dropping event"); - return; - } - - assert(_system); - - _system->pushEvent(type, arg1, arg2, arg3, arg4, arg5, arg6); -} - -void JNI::setPause(JNIEnv *env, jobject self, jboolean value) { - if (!_system) - return; - - if (g_engine) { - LOGD("pauseEngine: %d", value); - - g_engine->pauseEngine(value); - - /*if (value && - g_engine->hasFeature(Engine::kSupportsSavingDuringRuntime) && - g_engine->canSaveGameStateCurrently()) - g_engine->saveGameState(0, "Android parachute");*/ - } - - pause = value; - - if (!pause) { - // wake up all threads - for (uint i = 0; i < 3; ++i) - sem_post(&pause_sem); - } -} - -jstring JNI::getCurrentCharset(JNIEnv *env, jobject self) { -#ifdef USE_TRANSLATION - if (TransMan.getCurrentCharset() != "ASCII") { -// LOGD("getCurrentCharset: %s", TransMan.getCurrentCharset().c_str()); - return env->NewStringUTF(TransMan.getCurrentCharset().c_str()); - } -#endif - return env->NewStringUTF("ISO-8859-1"); -} - -Common::Array JNI::getAllStorageLocations() { - Common::Array *res = new Common::Array(); - - JNIEnv *env = JNI::getEnv(); - - jobjectArray array = - (jobjectArray)env->CallObjectMethod(_jobj, _MID_getAllStorageLocations); - - if (env->ExceptionCheck()) { - LOGE("Error finding system archive path"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - return *res; - } - - jsize size = env->GetArrayLength(array); - for (jsize i = 0; i < size; ++i) { - jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); - const char *path = env->GetStringUTFChars(path_obj, 0); - - if (path != 0) { - res->push_back(path); - env->ReleaseStringUTFChars(path_obj, path); - } - - env->DeleteLocalRef(path_obj); - } - - return *res; -} - - -#endif diff --git a/backends/platform/android/jni.h b/backends/platform/android/jni.h deleted file mode 100644 index fe4b948070..0000000000 --- a/backends/platform/android/jni.h +++ /dev/null @@ -1,161 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef _ANDROID_JNI_H_ -#define _ANDROID_JNI_H_ - -#if defined(__ANDROID__) - -#include -#include - -#include "common/fs.h" -#include "common/archive.h" -#include "common/array.h" - -class OSystem_Android; - -class JNI { -private: - JNI(); - virtual ~JNI(); - -public: - static bool pause; - static sem_t pause_sem; - - static int surface_changeid; - static int egl_surface_width; - static int egl_surface_height; - - static jint onLoad(JavaVM *vm); - - static JNIEnv *getEnv(); - - static void attachThread(); - static void detachThread(); - - static void setReadyForEvents(bool ready); - - static void setWindowCaption(const char *caption); - static void getDPI(float *values); - static void displayMessageOnOSD(const char *msg); - static bool openUrl(const char *url); - static bool hasTextInClipboard(); - static Common::String getTextFromClipboard(); - static bool setTextInClipboard(const Common::String &text); - static bool isConnectionLimited(); - static void showVirtualKeyboard(bool enable); - static void showKeyboardControl(bool enable); - static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority); - - static inline bool haveSurface(); - static inline bool swapBuffers(); - static bool initSurface(); - static void deinitSurface(); - - static void setAudioPause(); - static void setAudioPlay(); - static void setAudioStop(); - - static inline int writeAudio(JNIEnv *env, jbyteArray &data, int offset, - int size); - - static Common::Array getAllStorageLocations(); - -private: - static JavaVM *_vm; - // back pointer to (java) peer instance - static jobject _jobj; - static jobject _jobj_audio_track; - static jobject _jobj_egl; - static jobject _jobj_egl_display; - static jobject _jobj_egl_surface; - - static Common::Archive *_asset_archive; - static OSystem_Android *_system; - - static bool _ready_for_events; - - static jmethodID _MID_getDPI; - static jmethodID _MID_displayMessageOnOSD; - static jmethodID _MID_openUrl; - static jmethodID _MID_hasTextInClipboard; - static jmethodID _MID_getTextFromClipboard; - static jmethodID _MID_setTextInClipboard; - static jmethodID _MID_isConnectionLimited; - static jmethodID _MID_setWindowCaption; - static jmethodID _MID_showVirtualKeyboard; - static jmethodID _MID_showKeyboardControl; - static jmethodID _MID_getSysArchives; - static jmethodID _MID_getAllStorageLocations; - static jmethodID _MID_initSurface; - static jmethodID _MID_deinitSurface; - - static jmethodID _MID_EGL10_eglSwapBuffers; - - static jmethodID _MID_AudioTrack_flush; - static jmethodID _MID_AudioTrack_pause; - static jmethodID _MID_AudioTrack_play; - static jmethodID _MID_AudioTrack_stop; - static jmethodID _MID_AudioTrack_write; - - static const JNINativeMethod _natives[]; - - static void throwByName(JNIEnv *env, const char *name, const char *msg); - static void throwRuntimeException(JNIEnv *env, const char *msg); - - // natives for the dark side - static void create(JNIEnv *env, jobject self, jobject asset_manager, - jobject egl, jobject egl_display, - jobject at, jint audio_sample_rate, - jint audio_buffer_size); - static void destroy(JNIEnv *env, jobject self); - - static void setSurface(JNIEnv *env, jobject self, jint width, jint height); - static jint main(JNIEnv *env, jobject self, jobjectArray args); - - static void pushEvent(JNIEnv *env, jobject self, int type, int arg1, - int arg2, int arg3, int arg4, int arg5, int arg6); - static void setPause(JNIEnv *env, jobject self, jboolean value); - - static jstring getCurrentCharset(JNIEnv *env, jobject self); -}; - -inline bool JNI::haveSurface() { - return _jobj_egl_surface != 0; -} - -inline bool JNI::swapBuffers() { - JNIEnv *env = JNI::getEnv(); - - return env->CallBooleanMethod(_jobj_egl, _MID_EGL10_eglSwapBuffers, - _jobj_egl_display, _jobj_egl_surface); -} - -inline int JNI::writeAudio(JNIEnv *env, jbyteArray &data, int offset, int size) { - return env->CallIntMethod(_jobj_audio_track, _MID_AudioTrack_write, data, - offset, size); -} - -#endif -#endif diff --git a/backends/platform/android/module.mk b/backends/platform/android/module.mk index 6a0721b4a9..d8cf64756c 100644 --- a/backends/platform/android/module.mk +++ b/backends/platform/android/module.mk @@ -1,11 +1,12 @@ MODULE := backends/platform/android MODULE_OBJS := \ - jni.o \ + jni-android.o \ asset-archive.o \ android.o \ graphics.o \ - events.o + events.o \ + snprintf.o # We don't use rules.mk but rather manually update OBJS and MODULE_DIRS. MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) diff --git a/backends/platform/android/portdefs.h b/backends/platform/android/portdefs.h new file mode 100644 index 0000000000..dda6a29c2d --- /dev/null +++ b/backends/platform/android/portdefs.h @@ -0,0 +1,48 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef _PORTDEFS_H_ +#define _PORTDEFS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#define _USE_MATH_DEFINES +#include + +// This is defined in snprintf.c +#ifdef __cplusplus +extern "C" { +#endif +int rpl_vsnprintf(char *text, size_t maxlen, const char *fmt, va_list ap); +#ifdef __cplusplus +} +#endif + +#define vsnprintf rpl_vsnprintf + +#endif // _PORTDEFS_H_ diff --git a/backends/platform/android/snprintf.cpp b/backends/platform/android/snprintf.cpp new file mode 100644 index 0000000000..c0b241c9d6 --- /dev/null +++ b/backends/platform/android/snprintf.cpp @@ -0,0 +1,1323 @@ +/* + * Copyright (c) 1995 Patrick Powell. + * + * This code is based on code written by Patrick Powell . + * It may be used for any purpose as long as this notice remains intact on all + * source code distributions. + */ + +/* + * Copyright (c) 2008 Holger Weiss. + * + * This version of the code is maintained by Holger Weiss . + * My changes to the code may freely be used, modified and/or redistributed for + * any purpose. It would be nice if additions and fixes to this file (including + * trivial code cleanups) would be sent back in order to let me include them in + * the version available at . + * However, this is not a requirement for using or redistributing (possibly + * modified) versions of this file, nor is leaving this notice intact mandatory. + */ + +/* + * History + * + * 2008-01-20 Holger Weiss for C99-snprintf 1.1: + * + * Fixed the detection of infinite floating point values on IRIX (and + * possibly other systems) and applied another few minor cleanups. + * + * 2008-01-06 Holger Weiss for C99-snprintf 1.0: + * + * Added a lot of new features, fixed many bugs, and incorporated various + * improvements done by Andrew Tridgell , Russ Allbery + * , Hrvoje Niksic , Damien Miller + * , and others for the Samba, INN, Wget, and OpenSSH + * projects. The additions include: support the "e", "E", "g", "G", and + * "F" conversion specifiers (and use conversion style "f" or "F" for the + * still unsupported "a" and "A" specifiers); support the "hh", "ll", "j", + * "t", and "z" length modifiers; support the "#" flag and the (non-C99) + * "'" flag; use localeconv(3) (if available) to get both the current + * locale's decimal point character and the separator between groups of + * digits; fix the handling of various corner cases of field width and + * precision specifications; fix various floating point conversion bugs; + * handle infinite and NaN floating point values; don't attempt to write to + * the output buffer (which may be NULL) if a size of zero was specified; + * check for integer overflow of the field width, precision, and return + * values and during the floating point conversion; use the OUTCHAR() macro + * instead of a function for better performance; provide asprintf(3) and + * vasprintf(3) functions; add new test cases. The replacement functions + * have been renamed to use an "rpl_" prefix, the function calls in the + * main project (and in this file) must be redefined accordingly for each + * replacement function which is needed (by using Autoconf or other means). + * Various other minor improvements have been applied and the coding style + * was cleaned up for consistency. + * + * 2007-07-23 Holger Weiss for Mutt 1.5.13: + * + * C99 compliant snprintf(3) and vsnprintf(3) functions return the number + * of characters that would have been written to a sufficiently sized + * buffer (excluding the '\0'). The original code simply returned the + * length of the resulting output string, so that's been fixed. + * + * 1998-03-05 Michael Elkins for Mutt 0.90.8: + * + * The original code assumed that both snprintf(3) and vsnprintf(3) were + * missing. Some systems only have snprintf(3) but not vsnprintf(3), so + * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. + * + * 1998-01-27 Thomas Roessler for Mutt 0.89i: + * + * The PGP code was using unsigned hexadecimal formats. Unfortunately, + * unsigned formats simply didn't work. + * + * 1997-10-22 Brandon Long for Mutt 0.87.1: + * + * Ok, added some minimal floating point support, which means this probably + * requires libm on most operating systems. Don't yet support the exponent + * (e,E) and sigfig (g,G). Also, fmtint() was pretty badly broken, it just + * wasn't being exercised in ways which showed it, so that's been fixed. + * Also, formatted the code to Mutt conventions, and removed dead code left + * over from the original. Also, there is now a builtin-test, run with: + * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm && ./snprintf + * + * 2996-09-15 Brandon Long for Mutt 0.43: + * + * This was ugly. It is still ugly. I opted out of floating point + * numbers, but the formatter understands just about everything from the + * normal C string format, at least as far as I can tell from the Solaris + * 2.5 printf(3S) man page. + */ + +/* + * ToDo + * + * - Add wide character support. + * - Add support for "%a" and "%A" conversions. + * - Create test routines which predefine the expected results. Our test cases + * usually expose bugs in system implementations rather than in ours :-) + */ + +/* + * Usage + * + * 1) The following preprocessor macros should be defined to 1 if the feature or + * file in question is available on the target system (by using Autoconf or + * other means), though basic functionality should be available as long as + * HAVE_STDARG_H and HAVE_STDLIB_H are defined correctly: + * + * HAVE_VSNPRINTF + * HAVE_SNPRINTF + * HAVE_VASPRINTF + * HAVE_ASPRINTF + * HAVE_STDARG_H + * HAVE_STDDEF_H + * HAVE_STDINT_H + * HAVE_STDLIB_H + * HAVE_FLOAT_H + * HAVE_INTTYPES_H + * HAVE_LOCALE_H + * HAVE_LOCALECONV + * HAVE_LCONV_DECIMAL_POINT + * HAVE_LCONV_THOUSANDS_SEP + * HAVE_LONG_DOUBLE + * HAVE_LONG_LONG_INT + * HAVE_UNSIGNED_LONG_LONG_INT + * HAVE_INTMAX_T + * HAVE_UINTMAX_T + * HAVE_UINTPTR_T + * HAVE_PTRDIFF_T + * HAVE_VA_COPY + * HAVE___VA_COPY + * + * 2) The calls to the functions which should be replaced must be redefined + * throughout the project files (by using Autoconf or other means): + * + * #define vsnprintf rpl_vsnprintf + * #define snprintf rpl_snprintf + * #define vasprintf rpl_vasprintf + * #define asprintf rpl_asprintf + * + * 3) The required replacement functions should be declared in some header file + * included throughout the project files: + * + * #if HAVE_CONFIG_H + * #include + * #endif + * #if HAVE_STDARG_H + * #include + * #if !HAVE_VSNPRINTF + * int rpl_vsnprintf(char *, size_t, const char *, va_list); + * #endif + * #if !HAVE_SNPRINTF + * int rpl_snprintf(char *, size_t, const char *, ...); + * #endif + * #if !HAVE_VASPRINTF + * int rpl_vasprintf(char **, const char *, va_list); + * #endif + * #if !HAVE_ASPRINTF + * int rpl_asprintf(char **, const char *, ...); + * #endif + * #endif + * + * Autoconf macros for handling step 1 and step 2 are available at + * . + */ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#if defined(__ANDROID__) + +// Allow use of stuff in +#define FORBIDDEN_SYMBOL_EXCEPTION_time_h + +// Disable printf override in common/forbidden.h to avoid +// clashes with log.h from the Android SDK. +// That header file uses +// __attribute__ ((format(printf, 3, 4))) +// which gets messed up by our override mechanism; this could +// be avoided by either changing the Android SDK to use the equally +// legal and valid +// __attribute__ ((format(printf, 3, 4))) +// or by refining our printf override to use a varadic macro +// (which then wouldn't be portable, though). +// Anyway, for now we just disable the printf override globally +// for the Android port +#define FORBIDDEN_SYMBOL_EXCEPTION_printf + +#include "backends/platform/android/portdefs.h" + +#include /* For ERANGE and errno. */ +#include /* For *_MAX. */ +#include /* For *DBL_{MIN,MAX}_10_EXP. */ + +#if HAVE_INTTYPES_H +#include /* For intmax_t (if not defined in ). */ +#endif /* HAVE_INTTYPES_H */ +#if HAVE_LOCALE_H +#include /* For localeconv(3). */ +#endif /* HAVE_LOCALE_H */ +#if HAVE_STDDEF_H +#include /* For ptrdiff_t. */ +#endif /* HAVE_STDDEF_H */ +#if HAVE_STDINT_H +#include /* For intmax_t. */ +#endif /* HAVE_STDINT_H */ + +/* Support for unsigned long long int. We may also need ULLONG_MAX. */ +#ifndef ULONG_MAX /* We may need ULONG_MAX as a fallback. */ +#ifdef UINT_MAX +#define ULONG_MAX UINT_MAX +#else +#define ULONG_MAX INT_MAX +#endif /* defined(UINT_MAX) */ +#endif /* !defined(ULONG_MAX) */ +#ifdef ULLONG +#undef ULLONG +#endif /* defined(ULLONG) */ +#if HAVE_UNSIGNED_LONG_LONG_INT +#define ULLONG unsigned long long int +#ifndef ULLONG_MAX +#define ULLONG_MAX ULONG_MAX +#endif /* !defined(ULLONG_MAX) */ +#else +#define ULLONG unsigned long int +#ifdef ULLONG_MAX +#undef ULLONG_MAX +#endif /* defined(ULLONG_MAX) */ +#define ULLONG_MAX ULONG_MAX +#endif /* HAVE_LONG_LONG_INT */ + +/* Support for uintmax_t. We also need UINTMAX_MAX. */ +#ifdef UINTMAX_T +#undef UINTMAX_T +#endif /* defined(UINTMAX_T) */ +#if HAVE_UINTMAX_T || defined(uintmax_t) +#define UINTMAX_T uintmax_t +#ifndef UINTMAX_MAX +#define UINTMAX_MAX ULLONG_MAX +#endif /* !defined(UINTMAX_MAX) */ +#else +#define UINTMAX_T ULLONG +#ifdef UINTMAX_MAX +#undef UINTMAX_MAX +#endif /* defined(UINTMAX_MAX) */ +#define UINTMAX_MAX ULLONG_MAX +#endif /* HAVE_UINTMAX_T || defined(uintmax_t) */ + +/* Support for long double. */ +#ifndef LDOUBLE +#if HAVE_LONG_DOUBLE +#define LDOUBLE long double +#define LDOUBLE_MIN_10_EXP LDBL_MIN_10_EXP +#define LDOUBLE_MAX_10_EXP LDBL_MAX_10_EXP +#else +#define LDOUBLE double +#define LDOUBLE_MIN_10_EXP DBL_MIN_10_EXP +#define LDOUBLE_MAX_10_EXP DBL_MAX_10_EXP +#endif /* HAVE_LONG_DOUBLE */ +#endif /* !defined(LDOUBLE) */ + +/* Support for long long int. */ +#ifndef LLONG +#if HAVE_LONG_LONG_INT +#define LLONG long long int +#else +#define LLONG long int +#endif /* HAVE_LONG_LONG_INT */ +#endif /* !defined(LLONG) */ + +/* Support for intmax_t. */ +#ifndef INTMAX_T +#if HAVE_INTMAX_T || defined(intmax_t) +#define INTMAX_T intmax_t +#else +#define INTMAX_T LLONG +#endif /* HAVE_INTMAX_T || defined(intmax_t) */ +#endif /* !defined(INTMAX_T) */ + +/* Support for uintptr_t. */ +#ifndef UINTPTR_T +#if HAVE_UINTPTR_T || defined(uintptr_t) +#define UINTPTR_T uintptr_t +#else +#define UINTPTR_T unsigned long int +#endif /* HAVE_UINTPTR_T || defined(uintptr_t) */ +#endif /* !defined(UINTPTR_T) */ + +/* Support for ptrdiff_t. */ +#ifndef PTRDIFF_T +#if HAVE_PTRDIFF_T || defined(ptrdiff_t) +#define PTRDIFF_T ptrdiff_t +#else +#define PTRDIFF_T long int +#endif /* HAVE_PTRDIFF_T || defined(ptrdiff_t) */ +#endif /* !defined(PTRDIFF_T) */ + +/* + * We need an unsigned integer type corresponding to ptrdiff_t (cf. C99: + * 7.19.6.1, 7). However, we'll simply use PTRDIFF_T and convert it to an + * unsigned type if necessary. This should work just fine in practice. + */ +#ifndef UPTRDIFF_T +#define UPTRDIFF_T PTRDIFF_T +#endif /* !defined(UPTRDIFF_T) */ + +/* + * We need a signed integer type corresponding to size_t (cf. C99: 7.19.6.1, 7). + * However, we'll simply use size_t and convert it to a signed type if + * necessary. This should work just fine in practice. + */ +#ifndef SSIZE_T +#define SSIZE_T size_t +#endif /* !defined(SSIZE_T) */ + + +/* + * Buffer size to hold the octal string representation of UINT128_MAX without + * nul-termination ("3777777777777777777777777777777777777777777"). + */ +#ifdef MAX_CONVERT_LENGTH +#undef MAX_CONVERT_LENGTH +#endif /* defined(MAX_CONVERT_LENGTH) */ +#define MAX_CONVERT_LENGTH 43 + +/* Format read states. */ +#define PRINT_S_DEFAULT 0 +#define PRINT_S_FLAGS 1 +#define PRINT_S_WIDTH 2 +#define PRINT_S_DOT 3 +#define PRINT_S_PRECISION 4 +#define PRINT_S_MOD 5 +#define PRINT_S_CONV 6 + +/* Format flags. */ +#define PRINT_F_MINUS (1 << 0) +#define PRINT_F_PLUS (1 << 1) +#define PRINT_F_SPACE (1 << 2) +#define PRINT_F_NUM (1 << 3) +#define PRINT_F_ZERO (1 << 4) +#define PRINT_F_QUOTE (1 << 5) +#define PRINT_F_UP (1 << 6) +#define PRINT_F_UNSIGNED (1 << 7) +#define PRINT_F_TYPE_G (1 << 8) +#define PRINT_F_TYPE_E (1 << 9) + +/* Conversion flags. */ +#define PRINT_C_CHAR 1 +#define PRINT_C_SHORT 2 +#define PRINT_C_LONG 3 +#define PRINT_C_LLONG 4 +#define PRINT_C_LDOUBLE 5 +#define PRINT_C_SIZE 6 +#define PRINT_C_PTRDIFF 7 +#define PRINT_C_INTMAX 8 + +#ifndef MAX +#define MAX(x, y) ((x >= y) ? x : y) +#endif /* !defined(MAX) */ +#ifndef CHARTOINT +#define CHARTOINT(ch) (ch - '0') +#endif /* !defined(CHARTOINT) */ +#ifndef ISDIGIT +#define ISDIGIT(ch) ('0' <= (unsigned char)ch && (unsigned char)ch <= '9') +#endif /* !defined(ISDIGIT) */ +#ifndef ISNAN +#define ISNAN(x) (x != x) +#endif /* !defined(ISNAN) */ +#ifndef ISINF +#define ISINF(x) ((x < -1 || x > 1) && x + x == x) +#endif /* !defined(ISINF) */ + +#ifdef OUTCHAR +#undef OUTCHAR +#endif /* defined(OUTCHAR) */ +#define OUTCHAR(str, len, size, ch) \ +do { \ + if (len + 1 < size) \ + str[len] = ch; \ + (len)++; \ +} while (/* CONSTCOND */ 0) + +static void fmtstr(char *, size_t *, size_t, const char *, int, int, int); +static void fmtint(char *, size_t *, size_t, INTMAX_T, int, int, int, int); +static void fmtflt(char *, size_t *, size_t, LDOUBLE, int, int, int, int *); +static void printsep(char *, size_t *, size_t); +static int getnumsep(int); +static int getexponent(LDOUBLE); +static int convert(UINTMAX_T, char *, size_t, int, int); +static UINTMAX_T cast(LDOUBLE); +static UINTMAX_T myround(LDOUBLE); +static LDOUBLE mypow10(int); + +//extern int errno; + +int rpl_vsnprintf(char *str, size_t size, const char *format, va_list args) { + LDOUBLE fvalue; + INTMAX_T value; + unsigned char cvalue; + const char *strvalue; + INTMAX_T *intmaxptr; + PTRDIFF_T *ptrdiffptr; + SSIZE_T *sizeptr; + LLONG *llongptr; + long int *longptr; + int *intptr; + short int *shortptr; + signed char *charptr; + size_t len = 0; + int overflow = 0; + int base = 0; + int cflags = 0; + int flags = 0; + int width = 0; + int precision = -1; + int state = PRINT_S_DEFAULT; + char ch = *format++; + + /* + * C99 says: "If `n' is zero, nothing is written, and `s' may be a null + * pointer." (7.19.6.5, 2) We're forgiving and allow a NULL pointer + * even if a size larger than zero was specified. At least NetBSD's + * snprintf(3) does the same, as well as other versions of this file. + * (Though some of these versions will write to a non-NULL buffer even + * if a size of zero was specified, which violates the standard.) + */ + if (str == NULL && size != 0) + size = 0; + + while (ch != '\0') + switch (state) { + case PRINT_S_DEFAULT: + if (ch == '%') + state = PRINT_S_FLAGS; + else + OUTCHAR(str, len, size, ch); + ch = *format++; + break; + case PRINT_S_FLAGS: + switch (ch) { + case '-': + flags |= PRINT_F_MINUS; + ch = *format++; + break; + case '+': + flags |= PRINT_F_PLUS; + ch = *format++; + break; + case ' ': + flags |= PRINT_F_SPACE; + ch = *format++; + break; + case '#': + flags |= PRINT_F_NUM; + ch = *format++; + break; + case '0': + flags |= PRINT_F_ZERO; + ch = *format++; + break; + case '\'': /* SUSv2 flag (not in C99). */ + flags |= PRINT_F_QUOTE; + ch = *format++; + break; + default: + state = PRINT_S_WIDTH; + break; + } + break; + case PRINT_S_WIDTH: + if (ISDIGIT(ch)) { + ch = CHARTOINT(ch); + if (width > (INT_MAX - ch) / 10) { + overflow = 1; + goto out; + } + width = 10 * width + ch; + ch = *format++; + } else if (ch == '*') { + /* + * C99 says: "A negative field width argument is + * taken as a `-' flag followed by a positive + * field width." (7.19.6.1, 5) + */ + if ((width = va_arg(args, int)) < 0) { + flags |= PRINT_F_MINUS; + width = -width; + } + ch = *format++; + state = PRINT_S_DOT; + } else + state = PRINT_S_DOT; + break; + case PRINT_S_DOT: + if (ch == '.') { + state = PRINT_S_PRECISION; + ch = *format++; + } else + state = PRINT_S_MOD; + break; + case PRINT_S_PRECISION: + if (precision == -1) + precision = 0; + if (ISDIGIT(ch)) { + ch = CHARTOINT(ch); + if (precision > (INT_MAX - ch) / 10) { + overflow = 1; + goto out; + } + precision = 10 * precision + ch; + ch = *format++; + } else if (ch == '*') { + /* + * C99 says: "A negative precision argument is + * taken as if the precision were omitted." + * (7.19.6.1, 5) + */ + if ((precision = va_arg(args, int)) < 0) + precision = -1; + ch = *format++; + state = PRINT_S_MOD; + } else + state = PRINT_S_MOD; + break; + case PRINT_S_MOD: + switch (ch) { + case 'h': + ch = *format++; + if (ch == 'h') { /* It's a char. */ + ch = *format++; + cflags = PRINT_C_CHAR; + } else + cflags = PRINT_C_SHORT; + break; + case 'l': + ch = *format++; + if (ch == 'l') { /* It's a long long. */ + ch = *format++; + cflags = PRINT_C_LLONG; + } else + cflags = PRINT_C_LONG; + break; + case 'L': + cflags = PRINT_C_LDOUBLE; + ch = *format++; + break; + case 'j': + cflags = PRINT_C_INTMAX; + ch = *format++; + break; + case 't': + cflags = PRINT_C_PTRDIFF; + ch = *format++; + break; + case 'z': + cflags = PRINT_C_SIZE; + ch = *format++; + break; + } + state = PRINT_S_CONV; + break; + case PRINT_S_CONV: + switch (ch) { + case 'd': + /* FALLTHROUGH */ + case 'i': + switch (cflags) { + case PRINT_C_CHAR: + value = (signed char)va_arg(args, int); + break; + case PRINT_C_SHORT: + value = (short int)va_arg(args, int); + break; + case PRINT_C_LONG: + value = va_arg(args, long int); + break; + case PRINT_C_LLONG: + value = va_arg(args, LLONG); + break; + case PRINT_C_SIZE: + value = va_arg(args, SSIZE_T); + break; + case PRINT_C_INTMAX: + value = va_arg(args, INTMAX_T); + break; + case PRINT_C_PTRDIFF: + value = va_arg(args, PTRDIFF_T); + break; + default: + value = va_arg(args, int); + break; + } + fmtint(str, &len, size, value, 10, width, + precision, flags); + break; + case 'X': + flags |= PRINT_F_UP; + /* FALLTHROUGH */ + case 'x': + base = 16; + /* FALLTHROUGH */ + case 'o': + if (base == 0) + base = 8; + /* FALLTHROUGH */ + case 'u': + if (base == 0) + base = 10; + flags |= PRINT_F_UNSIGNED; + switch (cflags) { + case PRINT_C_CHAR: + value = (unsigned char)va_arg(args, + unsigned int); + break; + case PRINT_C_SHORT: + value = (unsigned short int)va_arg(args, + unsigned int); + break; + case PRINT_C_LONG: + value = va_arg(args, unsigned long int); + break; + case PRINT_C_LLONG: + value = va_arg(args, ULLONG); + break; + case PRINT_C_SIZE: + value = va_arg(args, size_t); + break; + case PRINT_C_INTMAX: + value = va_arg(args, UINTMAX_T); + break; + case PRINT_C_PTRDIFF: + value = va_arg(args, UPTRDIFF_T); + break; + default: + value = va_arg(args, unsigned int); + break; + } + fmtint(str, &len, size, value, base, width, + precision, flags); + break; + case 'A': + /* Not yet supported, we'll use "%F". */ + /* FALLTHROUGH */ + case 'E': + if (ch == 'E') + flags |= PRINT_F_TYPE_E; + /* FALLTHROUGH */ + case 'G': + if (ch == 'G') + flags |= PRINT_F_TYPE_G; + /* FALLTHROUGH */ + case 'F': + flags |= PRINT_F_UP; + /* FALLTHROUGH */ + case 'a': + /* Not yet supported, we'll use "%f". */ + /* FALLTHROUGH */ + case 'e': + if (ch == 'e') + flags |= PRINT_F_TYPE_E; + /* FALLTHROUGH */ + case 'g': + if (ch == 'g') + flags |= PRINT_F_TYPE_G; + /* FALLTHROUGH */ + case 'f': + if (cflags == PRINT_C_LDOUBLE) + fvalue = va_arg(args, LDOUBLE); + else + fvalue = va_arg(args, double); + fmtflt(str, &len, size, fvalue, width, + precision, flags, &overflow); + if (overflow) + goto out; + break; + case 'c': + cvalue = va_arg(args, int); + OUTCHAR(str, len, size, cvalue); + break; + case 's': + strvalue = va_arg(args, char *); + fmtstr(str, &len, size, strvalue, width, + precision, flags); + break; + case 'p': + /* + * C99 says: "The value of the pointer is + * converted to a sequence of printing + * characters, in an implementation-defined + * manner." (C99: 7.19.6.1, 8) + */ + if ((strvalue = (const char *)va_arg(args, void *)) == NULL) + /* + * We use the glibc format. BSD prints + * "0x0", SysV "0". + */ + fmtstr(str, &len, size, "(nil)", width, + -1, flags); + else { + /* + * We use the BSD/glibc format. SysV + * omits the "0x" prefix (which we emit + * using the PRINT_F_NUM flag). + */ + flags |= PRINT_F_NUM; + flags |= PRINT_F_UNSIGNED; + fmtint(str, &len, size, + (UINTPTR_T)strvalue, 16, width, + precision, flags); + } + break; + case 'n': + switch (cflags) { + case PRINT_C_CHAR: + charptr = va_arg(args, signed char *); + *charptr = len; + break; + case PRINT_C_SHORT: + shortptr = va_arg(args, short int *); + *shortptr = len; + break; + case PRINT_C_LONG: + longptr = va_arg(args, long int *); + *longptr = len; + break; + case PRINT_C_LLONG: + llongptr = va_arg(args, LLONG *); + *llongptr = len; + break; + case PRINT_C_SIZE: + /* + * C99 says that with the "z" length + * modifier, "a following `n' conversion + * specifier applies to a pointer to a + * signed integer type corresponding to + * size_t argument." (7.19.6.1, 7) + */ + sizeptr = va_arg(args, SSIZE_T *); + *sizeptr = len; + break; + case PRINT_C_INTMAX: + intmaxptr = va_arg(args, INTMAX_T *); + *intmaxptr = len; + break; + case PRINT_C_PTRDIFF: + ptrdiffptr = va_arg(args, PTRDIFF_T *); + *ptrdiffptr = len; + break; + default: + intptr = va_arg(args, int *); + *intptr = len; + break; + } + break; + case '%': /* Print a "%" character verbatim. */ + OUTCHAR(str, len, size, ch); + break; + default: /* Skip other characters. */ + break; + } + ch = *format++; + state = PRINT_S_DEFAULT; + base = cflags = flags = width = 0; + precision = -1; + break; + } +out: + if (len < size) + str[len] = '\0'; + else if (size > 0) + str[size - 1] = '\0'; + + if (overflow || len > INT_MAX) { + errno = EOVERFLOW; + return -1; + } + return (int)len; +} + +static void fmtstr(char *str, size_t *len, size_t size, const char *value, int width, int precision, int flags) { + int padlen, strln; /* Amount to pad. */ + int noprecision = (precision == -1); + + if (value == NULL) /* We're forgiving. */ + value = "(null)"; + + /* If a precision was specified, don't read the string past it. */ + for (strln = 0; value[strln] != '\0' && + (noprecision || strln < precision); strln++) + continue; + + if ((padlen = width - strln) < 0) + padlen = 0; + if (flags & PRINT_F_MINUS) /* Left justify. */ + padlen = -padlen; + + while (padlen > 0) { /* Leading spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen--; + } + while (*value != '\0' && (noprecision || precision-- > 0)) { + OUTCHAR(str, *len, size, *value); + value++; + } + while (padlen < 0) { /* Trailing spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen++; + } +} + +static void fmtint(char *str, size_t *len, size_t size, INTMAX_T value, int base, int width, int precision, int flags) { + UINTMAX_T uvalue; + char iconvert[MAX_CONVERT_LENGTH]; + char sign = 0; + char hexprefix = 0; + int spadlen = 0; /* Amount to space pad. */ + int zpadlen = 0; /* Amount to zero pad. */ + int pos; + int separators = (flags & PRINT_F_QUOTE); + int noprecision = (precision == -1); + + if (flags & PRINT_F_UNSIGNED) + uvalue = value; + else { + uvalue = (value >= 0) ? value : -value; + if (value < 0) + sign = '-'; + else if (flags & PRINT_F_PLUS) /* Do a sign. */ + sign = '+'; + else if (flags & PRINT_F_SPACE) + sign = ' '; + } + + pos = convert(uvalue, iconvert, sizeof(iconvert), base, + flags & PRINT_F_UP); + + if (flags & PRINT_F_NUM && uvalue != 0) { + /* + * C99 says: "The result is converted to an `alternative form'. + * For `o' conversion, it increases the precision, if and only + * if necessary, to force the first digit of the result to be a + * zero (if the value and precision are both 0, a single 0 is + * printed). For `x' (or `X') conversion, a nonzero result has + * `0x' (or `0X') prefixed to it." (7.19.6.1, 6) + */ + switch (base) { + case 8: + if (precision <= pos) + precision = pos + 1; + break; + case 16: + hexprefix = (flags & PRINT_F_UP) ? 'X' : 'x'; + break; + } + } + + if (separators) /* Get the number of group separators we'll print. */ + separators = getnumsep(pos); + + zpadlen = precision - pos - separators; + spadlen = width /* Minimum field width. */ + - separators /* Number of separators. */ + - MAX(precision, pos) /* Number of integer digits. */ + - ((sign != 0) ? 1 : 0) /* Will we print a sign? */ + - ((hexprefix != 0) ? 2 : 0); /* Will we print a prefix? */ + + if (zpadlen < 0) + zpadlen = 0; + if (spadlen < 0) + spadlen = 0; + + /* + * C99 says: "If the `0' and `-' flags both appear, the `0' flag is + * ignored. For `d', `i', `o', `u', `x', and `X' conversions, if a + * precision is specified, the `0' flag is ignored." (7.19.6.1, 6) + */ + if (flags & PRINT_F_MINUS) /* Left justify. */ + spadlen = -spadlen; + else if (flags & PRINT_F_ZERO && noprecision) { + zpadlen += spadlen; + spadlen = 0; + } + while (spadlen > 0) { /* Leading spaces. */ + OUTCHAR(str, *len, size, ' '); + spadlen--; + } + if (sign != 0) /* Sign. */ + OUTCHAR(str, *len, size, sign); + if (hexprefix != 0) { /* A "0x" or "0X" prefix. */ + OUTCHAR(str, *len, size, '0'); + OUTCHAR(str, *len, size, hexprefix); + } + while (zpadlen > 0) { /* Leading zeros. */ + OUTCHAR(str, *len, size, '0'); + zpadlen--; + } + while (pos > 0) { /* The actual digits. */ + pos--; + OUTCHAR(str, *len, size, iconvert[pos]); + if (separators > 0 && pos > 0 && pos % 3 == 0) + printsep(str, len, size); + } + while (spadlen < 0) { /* Trailing spaces. */ + OUTCHAR(str, *len, size, ' '); + spadlen++; + } +} + +static void fmtflt(char *str, size_t *len, size_t size, LDOUBLE fvalue, int width, int precision, int flags, int *overflow) { + LDOUBLE ufvalue; + UINTMAX_T intpart; + UINTMAX_T fracpart; + UINTMAX_T mask; + const char *infnan = NULL; + char iconvert[MAX_CONVERT_LENGTH]; + char fconvert[MAX_CONVERT_LENGTH]; + char econvert[5]; /* "e-300" (without nul-termination). */ + char esign = 0; + char sign = 0; + int leadfraczeros = 0; + int exponent = 0; + int emitpoint = 0; + int omitzeros = 0; + int omitcount = 0; + int padlen = 0; + int epos = 0; + int fpos = 0; + int ipos = 0; + int separators = (flags & PRINT_F_QUOTE); + int estyle = (flags & PRINT_F_TYPE_E); +#if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT + struct lconv *lc = localeconv(); +#endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */ + + /* + * AIX' man page says the default is 0, but C99 and at least Solaris' + * and NetBSD's man pages say the default is 6, and sprintf(3) on AIX + * defaults to 6. + */ + if (precision == -1) + precision = 6; + + if (fvalue < 0.0) + sign = '-'; + else if (flags & PRINT_F_PLUS) /* Do a sign. */ + sign = '+'; + else if (flags & PRINT_F_SPACE) + sign = ' '; + + if (ISNAN(fvalue)) + infnan = (flags & PRINT_F_UP) ? "NAN" : "nan"; + else if (ISINF(fvalue)) + infnan = (flags & PRINT_F_UP) ? "INF" : "inf"; + + if (infnan != NULL) { + if (sign != 0) + iconvert[ipos++] = sign; + while (*infnan != '\0') + iconvert[ipos++] = *infnan++; + fmtstr(str, len, size, iconvert, width, ipos, flags); + return; + } + + /* "%e" (or "%E") or "%g" (or "%G") conversion. */ + if (flags & PRINT_F_TYPE_E || flags & PRINT_F_TYPE_G) { + if (flags & PRINT_F_TYPE_G) { + /* + * If the precision is zero, it is treated as one (cf. + * C99: 7.19.6.1, 8). + */ + if (precision == 0) + precision = 1; + /* + * For "%g" (and "%G") conversions, the precision + * specifies the number of significant digits, which + * includes the digits in the integer part. The + * conversion will or will not be using "e-style" (like + * "%e" or "%E" conversions) depending on the precision + * and on the exponent. However, the exponent can be + * affected by rounding the converted value, so we'll + * leave this decision for later. Until then, we'll + * assume that we're going to do an "e-style" conversion + * (in order to get the exponent calculated). For + * "e-style", the precision must be decremented by one. + */ + precision--; + /* + * For "%g" (and "%G") conversions, trailing zeros are + * removed from the fractional portion of the result + * unless the "#" flag was specified. + */ + if (!(flags & PRINT_F_NUM)) + omitzeros = 1; + } + exponent = getexponent(fvalue); + estyle = 1; + } + +again: + /* + * Sorry, we only support 9, 19, or 38 digits (that is, the number of + * digits of the 32-bit, the 64-bit, or the 128-bit UINTMAX_MAX value + * minus one) past the decimal point due to our conversion method. + */ + switch (sizeof(UINTMAX_T)) { + case 16: + if (precision > 38) + precision = 38; + break; + case 8: + if (precision > 19) + precision = 19; + break; + default: + if (precision > 9) + precision = 9; + break; + } + + ufvalue = (fvalue >= 0.0) ? fvalue : -fvalue; + if (estyle) /* We want exactly one integer digit. */ + ufvalue /= mypow10(exponent); + + if ((intpart = cast(ufvalue)) == UINTMAX_MAX) { + *overflow = 1; + return; + } + + /* + * Factor of ten with the number of digits needed for the fractional + * part. For example, if the precision is 3, the mask will be 1000. + */ + mask = mypow10(precision); + /* + * We "cheat" by converting the fractional part to integer by + * multiplying by a factor of ten. + */ + if ((fracpart = myround(mask * (ufvalue - intpart))) >= mask) { + /* + * For example, ufvalue = 2.99962, intpart = 2, and mask = 1000 + * (because precision = 3). Now, myround(1000 * 0.99962) will + * return 1000. So, the integer part must be incremented by one + * and the fractional part must be set to zero. + */ + intpart++; + fracpart = 0; + if (estyle && intpart == 10) { + /* + * The value was rounded up to ten, but we only want one + * integer digit if using "e-style". So, the integer + * part must be set to one and the exponent must be + * incremented by one. + */ + intpart = 1; + exponent++; + } + } + + /* + * Now that we know the real exponent, we can check whether or not to + * use "e-style" for "%g" (and "%G") conversions. If we don't need + * "e-style", the precision must be adjusted and the integer and + * fractional parts must be recalculated from the original value. + * + * C99 says: "Let P equal the precision if nonzero, 6 if the precision + * is omitted, or 1 if the precision is zero. Then, if a conversion + * with style `E' would have an exponent of X: + * + * - if P > X >= -4, the conversion is with style `f' (or `F') and + * precision P - (X + 1). + * + * - otherwise, the conversion is with style `e' (or `E') and precision + * P - 1." (7.19.6.1, 8) + * + * Note that we had decremented the precision by one. + */ + if (flags & PRINT_F_TYPE_G && estyle && + precision + 1 > exponent && exponent >= -4) { + precision -= exponent; + estyle = 0; + goto again; + } + + if (estyle) { + if (exponent < 0) { + exponent = -exponent; + esign = '-'; + } else + esign = '+'; + + /* + * Convert the exponent. The sizeof(econvert) is 5. So, the + * econvert buffer can hold e.g. "e+999" and "e-999". We don't + * support an exponent which contains more than three digits. + * Therefore, the following stores are safe. + */ + epos = convert(exponent, econvert, 3, 10, 0); + /* + * C99 says: "The exponent always contains at least two digits, + * and only as many more digits as necessary to represent the + * exponent." (7.19.6.1, 8) + */ + if (epos == 1) + econvert[epos++] = '0'; + econvert[epos++] = esign; + econvert[epos++] = (flags & PRINT_F_UP) ? 'E' : 'e'; + } + + /* Convert the integer part and the fractional part. */ + ipos = convert(intpart, iconvert, sizeof(iconvert), 10, 0); + if (fracpart != 0) /* convert() would return 1 if fracpart == 0. */ + fpos = convert(fracpart, fconvert, sizeof(fconvert), 10, 0); + + leadfraczeros = precision - fpos; + + if (omitzeros) { + if (fpos > 0) /* Omit trailing fractional part zeros. */ + while (omitcount < fpos && fconvert[omitcount] == '0') + omitcount++; + else { /* The fractional part is zero, omit it completely. */ + omitcount = precision; + leadfraczeros = 0; + } + precision -= omitcount; + } + + /* + * Print a decimal point if either the fractional part is non-zero + * and/or the "#" flag was specified. + */ + if (precision > 0 || flags & PRINT_F_NUM) + emitpoint = 1; + if (separators) /* Get the number of group separators we'll print. */ + separators = getnumsep(ipos); + + padlen = width /* Minimum field width. */ + - ipos /* Number of integer digits. */ + - epos /* Number of exponent characters. */ + - precision /* Number of fractional digits. */ + - separators /* Number of group separators. */ + - (emitpoint ? 1 : 0) /* Will we print a decimal point? */ + - ((sign != 0) ? 1 : 0); /* Will we print a sign character? */ + + if (padlen < 0) + padlen = 0; + + /* + * C99 says: "If the `0' and `-' flags both appear, the `0' flag is + * ignored." (7.19.6.1, 6) + */ + if (flags & PRINT_F_MINUS) /* Left justifty. */ + padlen = -padlen; + else if (flags & PRINT_F_ZERO && padlen > 0) { + if (sign != 0) { /* Sign. */ + OUTCHAR(str, *len, size, sign); + sign = 0; + } + while (padlen > 0) { /* Leading zeros. */ + OUTCHAR(str, *len, size, '0'); + padlen--; + } + } + while (padlen > 0) { /* Leading spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen--; + } + if (sign != 0) /* Sign. */ + OUTCHAR(str, *len, size, sign); + while (ipos > 0) { /* Integer part. */ + ipos--; + OUTCHAR(str, *len, size, iconvert[ipos]); + if (separators > 0 && ipos > 0 && ipos % 3 == 0) + printsep(str, len, size); + } + if (emitpoint) { /* Decimal point. */ +#if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT + if (lc->decimal_point != NULL && *lc->decimal_point != '\0') + OUTCHAR(str, *len, size, *lc->decimal_point); + else /* We'll always print some decimal point character. */ +#endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */ + OUTCHAR(str, *len, size, '.'); + } + while (leadfraczeros > 0) { /* Leading fractional part zeros. */ + OUTCHAR(str, *len, size, '0'); + leadfraczeros--; + } + while (fpos > omitcount) { /* The remaining fractional part. */ + fpos--; + OUTCHAR(str, *len, size, fconvert[fpos]); + } + while (epos > 0) { /* Exponent. */ + epos--; + OUTCHAR(str, *len, size, econvert[epos]); + } + while (padlen < 0) { /* Trailing spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen++; + } +} + +static void printsep(char *str, size_t *len, size_t size) { +#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP + struct lconv *lc = localeconv(); + int i; + + if (lc->thousands_sep != NULL) + for (i = 0; lc->thousands_sep[i] != '\0'; i++) + OUTCHAR(str, *len, size, lc->thousands_sep[i]); + else +#endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */ + OUTCHAR(str, *len, size, ','); +} + +static int +getnumsep(int digits) +{ + int separators = (digits - ((digits % 3 == 0) ? 1 : 0)) / 3; +#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP + int strln; + struct lconv *lc = localeconv(); + + /* We support an arbitrary separator length (including zero). */ + if (lc->thousands_sep != NULL) { + for (strln = 0; lc->thousands_sep[strln] != '\0'; strln++) + continue; + separators *= strln; + } +#endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */ + return separators; +} + +static int getexponent(LDOUBLE value) { + LDOUBLE tmp = (value >= 0.0) ? value : -value; + int exponent = 0; + + /* + * We check for LDOUBLE_MAX_10_EXP >= exponent >= LDOUBLE_MIN_10_EXP in + * order to work around possible endless loops which could happen (at + * least) in the second loop (at least) if we're called with an infinite + * value. However, we checked for infinity before calling this function + * using our ISINF() macro, so this might be somewhat paranoid. + */ + while (tmp < 1.0 && tmp > 0.0 && --exponent >= LDOUBLE_MIN_10_EXP) + tmp *= 10; + while (tmp >= 10.0 && ++exponent <= LDOUBLE_MAX_10_EXP) + tmp /= 10; + + return exponent; +} + +static int convert(UINTMAX_T value, char *buf, size_t size, int base, int caps) { + const char *digits = caps ? "0123456789ABCDEF" : "0123456789abcdef"; + size_t pos = 0; + + /* We return an unterminated buffer with the digits in reverse order. */ + do { + buf[pos++] = digits[value % base]; + value /= base; + } while (value != 0 && pos < size); + + return (int)pos; +} + +static UINTMAX_T cast(LDOUBLE value) { + UINTMAX_T result; + + /* + * We check for ">=" and not for ">" because if UINTMAX_MAX cannot be + * represented exactly as an LDOUBLE value (but is less than LDBL_MAX), + * it may be increased to the nearest higher representable value for the + * comparison (cf. C99: 6.3.1.4, 2). It might then equal the LDOUBLE + * value although converting the latter to UINTMAX_T would overflow. + */ + if (value >= UINTMAX_MAX) + return UINTMAX_MAX; + + result = value; + /* + * At least on NetBSD/sparc64 3.0.2 and 4.99.30, casting long double to + * an integer type converts e.g. 1.9 to 2 instead of 1 (which violates + * the standard). Sigh. + */ + return (result <= value) ? result : result - 1; +} + +static UINTMAX_T myround(LDOUBLE value) { + UINTMAX_T intpart = cast(value); + + return ((value -= intpart) < 0.5) ? intpart : intpart + 1; +} + +static LDOUBLE mypow10(int exponent) { + LDOUBLE result = 1; + + while (exponent > 0) { + result *= 10; + exponent--; + } + while (exponent < 0) { + result /= 10; + exponent++; + } + return result; +} +#endif // defined(__ANDROID__) +/* vim: set joinspaces noexpandtab textwidth=80 cinoptions=(4,u0: */ -- cgit v1.2.3