diff --git a/core/rend/TexCache.cpp b/core/rend/TexCache.cpp index 81d5d0549..4b0fb5dd7 100644 --- a/core/rend/TexCache.cpp +++ b/core/rend/TexCache.cpp @@ -14,6 +14,7 @@ #include #include "CustomTexture.h" #include +#include u8* vq_codebook; u32 palette_index; @@ -955,54 +956,22 @@ void rend_text_invl(vram_block* bl) libCore_vramlock_Unlock_block_wb(bl); } -static FILE* pngfile; - -void png_cstd_read(png_structp png_ptr, png_bytep data, png_size_t length) +static u8* loadPNGData(const u8 *header, png_voidp io_ptr, png_rw_ptr read_func, int &width, int &height) { - if (fread(data, 1, length, pngfile) != length) - png_error(png_ptr, "Truncated read error"); -} - -u8* loadPNGData(const string& fname, int &width, int &height) -{ - const char* filename=fname.c_str(); - FILE* file = fopen(filename, "rb"); - pngfile=file; - - if (!file) - { - EMUERROR("Error opening %s", filename); - return NULL; - } - - //header for testing if it is a png - png_byte header[8]; - - //read the header - if (fread(header, 1, 8, file) != 8) - { - fclose(file); - WARN_LOG(RENDERER, "Not a PNG file : %s", filename); - return NULL; - } - //test if png int is_png = !png_sig_cmp(header, 0, 8); if (!is_png) { - fclose(file); - WARN_LOG(RENDERER, "Not a PNG file : %s", filename); + WARN_LOG(RENDERER, "Passed data isn't a PNG file"); return NULL; } //create png struct - png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, - NULL, NULL); + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { - fclose(file); - WARN_LOG(RENDERER, "Unable to create PNG struct : %s", filename); - return (NULL); + WARN_LOG(RENDERER, "Unable to create PNG struct"); + return NULL; } //create png info struct @@ -1010,9 +979,8 @@ u8* loadPNGData(const string& fname, int &width, int &height) if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL); - WARN_LOG(RENDERER, "Unable to create PNG info : %s", filename); - fclose(file); - return (NULL); + WARN_LOG(RENDERER, "Unable to create PNG info"); + return NULL; } //create png info struct @@ -1020,23 +988,21 @@ u8* loadPNGData(const string& fname, int &width, int &height) if (!end_info) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); - WARN_LOG(RENDERER, "Unable to create PNG end info : %s", filename); - fclose(file); - return (NULL); + WARN_LOG(RENDERER, "Unable to create PNG end info"); + return NULL; } //png error stuff, not sure libpng man suggests this. if (setjmp(png_jmpbuf(png_ptr))) { - fclose(file); - WARN_LOG(RENDERER, "Error during setjmp : %s", filename); + WARN_LOG(RENDERER, "Error during setjmp"); png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - return (NULL); + return NULL; } //init png reading //png_init_io(png_ptr, fp); - png_set_read_fn(png_ptr, NULL, png_cstd_read); + png_set_read_fn(png_ptr, io_ptr, read_func); //let libpng know you already read the first 8 bytes png_set_sig_bytes(png_ptr, 8); @@ -1068,8 +1034,7 @@ u8* loadPNGData(const string& fname, int &width, int &height) { //clean up memory and close stuff png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - WARN_LOG(RENDERER, "Unable to allocate image_data while loading %s", filename); - fclose(file); + WARN_LOG(RENDERER, "Unable to allocate image_data"); return NULL; } @@ -1080,8 +1045,7 @@ u8* loadPNGData(const string& fname, int &width, int &height) //clean up memory and close stuff png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); delete[] image_data; - WARN_LOG(RENDERER, "Unable to allocate row_pointer while loading %s", filename); - fclose(file); + WARN_LOG(RENDERER, "Unable to allocate row_pointer"); return NULL; } @@ -1094,11 +1058,58 @@ u8* loadPNGData(const string& fname, int &width, int &height) png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); delete[] row_pointers; - fclose(file); return image_data; } +static size_t png_offset; + +static void png_read_vector(png_structp png_ptr, png_bytep data, png_size_t length) +{ + const std::vector *v = (const std::vector *)png_ptr->io_ptr; + memcpy(data, v->data() + png_offset, length); + png_offset += length; +} + +u8* loadPNGData(const std::vector& data, int &width, int &height) +{ + png_offset = 8; + return loadPNGData(data.data(), (void *)&data, png_read_vector, width, height); +} + +static void png_cstd_read(png_structp png_ptr, png_bytep data, png_size_t length) +{ + if (fread(data, 1, length, (FILE *)png_ptr->io_ptr) != length) + png_error(png_ptr, "Truncated read error"); +} + +u8* loadPNGData(const string& fname, int &width, int &height) +{ + const char* filename=fname.c_str(); + FILE* file = fopen(filename, "rb"); + + if (!file) + { + EMUERROR("Error opening %s", filename); + return NULL; + } + + //header for testing if it is a png + png_byte header[8]; + + //read the header + if (fread(header, 1, 8, file) != 8) + { + fclose(file); + WARN_LOG(RENDERER, "Not a PNG file : %s", filename); + return NULL; + } + u8 *data = loadPNGData(header, file, png_cstd_read, width, height); + fclose(file); + + return data; +} + #ifdef TEST_AUTOMATION void dump_screenshot(u8 *buffer, u32 width, u32 height, bool alpha, u32 rowPitch, bool invertY) { diff --git a/core/rend/TexCache.h b/core/rend/TexCache.h index a65525119..9384aded4 100644 --- a/core/rend/TexCache.h +++ b/core/rend/TexCache.h @@ -819,6 +819,7 @@ static inline void MakeFogTexture(u8 *tex_data) } } u8* loadPNGData(const string& fname, int &width, int &height); +u8* loadPNGData(const std::vector& data, int &width, int &height); void dump_screenshot(u8 *buffer, u32 width, u32 height, bool alpha = false, u32 rowPitch = 0, bool invertY = true); extern const std::array D_Adjust_LoD_Bias; diff --git a/core/rend/gl4/gles.cpp b/core/rend/gl4/gles.cpp index 89ec73fe9..4ffacee01 100644 --- a/core/rend/gl4/gles.cpp +++ b/core/rend/gl4/gles.cpp @@ -516,8 +516,6 @@ static bool gl_create_resources() create_modvol_shader(); - gl_load_osd_resources(); - // Create the buffer for Translucent poly params glGenBuffers(1, &gl4.vbo.tr_poly_params); // Bind it diff --git a/core/rend/gles/gles.cpp b/core/rend/gles/gles.cpp index 7949e92de..80d68efc2 100644 --- a/core/rend/gles/gles.cpp +++ b/core/rend/gles/gles.cpp @@ -756,9 +756,18 @@ void gl_load_osd_resources() glUniform1i(glGetUniformLocation(gl.OSD_SHADER.program, "tex"), 0); //bind osd texture to slot 0 #ifdef __ANDROID__ - int w, h; if (gl.OSD_SHADER.osd_tex == 0) - gl.OSD_SHADER.osd_tex = loadPNG(get_readonly_data_path(DATA_PATH "buttons.png"), w, h); + { + int width, height; + u8 *image_data = loadOSDButtons(width, height); + //Now generate the OpenGL texture object + gl.OSD_SHADER.osd_tex = glcache.GenTexture(); + glcache.BindTexture(GL_TEXTURE_2D, gl.OSD_SHADER.osd_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid *)image_data); + glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + delete[] image_data; + } #endif SetupOSDVBO(); } @@ -821,8 +830,6 @@ bool gl_create_resources() create_modvol_shader(); - gl_load_osd_resources(); - return true; } @@ -1283,24 +1290,4 @@ struct glesrend : Renderer } }; - -GLuint loadPNG(const string& fname, int &width, int &height) -{ - png_byte *image_data = loadPNGData(fname, width, height); - if (image_data == NULL) - return TEXTURE_LOAD_ERROR; - - //Now generate the OpenGL texture object - GLuint texture = glcache.GenTexture(); - glcache.BindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, - GL_UNSIGNED_BYTE, (GLvoid*) image_data); - glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - delete[] image_data; - - return texture; -} - - Renderer* rend_GLES2() { return new glesrend(); } diff --git a/core/rend/gles/gles.h b/core/rend/gles/gles.h index 012e8241f..9aea2049a 100755 --- a/core/rend/gles/gles.h +++ b/core/rend/gles/gles.h @@ -152,9 +152,6 @@ PipelineShader *GetProgram(u32 cp_AlphaTest, u32 pp_ClipTestMode, GLuint gl_CompileShader(const char* shader, GLuint type); GLuint gl_CompileAndLink(const char* VertexShader, const char* FragmentShader); bool CompilePipelineShader(PipelineShader* s); -#define TEXTURE_LOAD_ERROR 0 -u8* loadPNGData(const string& subpath, int &width, int &height); -GLuint loadPNG(const string& subpath, int &width, int &height); extern struct ShaderUniforms_t { diff --git a/core/rend/gles/gltex.cpp b/core/rend/gles/gltex.cpp index 6b01d173f..f1d531316 100644 --- a/core/rend/gles/gltex.cpp +++ b/core/rend/gles/gltex.cpp @@ -123,6 +123,9 @@ void TextureCacheData::UploadToGPU(int width, int height, u8 *temp_tex_buffer, b case TextureType::_8888: internalFormat = GL_RGBA8; break; + default: + die("Unsupported texture format"); + break; } if (Updates == 1) { diff --git a/core/rend/osd.cpp b/core/rend/osd.cpp index 53f66d2a1..c15f5a786 100644 --- a/core/rend/osd.cpp +++ b/core/rend/osd.cpp @@ -18,6 +18,7 @@ #include "types.h" #include "input/gamepad.h" #include "input/gamepad_device.h" +#include "TexCache.h" #if defined(__ANDROID__) extern float vjoy_pos[15][8]; @@ -53,6 +54,7 @@ static const float vjoy_sz[2][15] = { }; static std::vector osdVertices; +std::vector DefaultOSDButtons; void HideOSD() { @@ -142,3 +144,14 @@ static void setVjoyUV() static OnLoad setVjoyUVOnLoad(&setVjoyUV); +u8 *loadOSDButtons(int &width, int &height) +{ + u8 *image_data = loadPNGData(get_readonly_data_path(DATA_PATH "buttons.png"), width, height); + if (image_data == nullptr) + { + if (DefaultOSDButtons.empty()) + die("No default OSD buttons"); + image_data = loadPNGData(DefaultOSDButtons, width, height); + } + return image_data; +} diff --git a/core/rend/osd.h b/core/rend/osd.h index 1fd823eb2..ade574a36 100644 --- a/core/rend/osd.h +++ b/core/rend/osd.h @@ -29,3 +29,6 @@ struct OSDVertex }; const std::vector& GetOSDVertices(); + +extern std::vector DefaultOSDButtons; +u8 *loadOSDButtons(int &width, int &height); diff --git a/core/rend/vulkan/oit/oit_renderer.cpp b/core/rend/vulkan/oit/oit_renderer.cpp index 95c500326..d80dc13d5 100644 --- a/core/rend/vulkan/oit/oit_renderer.cpp +++ b/core/rend/vulkan/oit/oit_renderer.cpp @@ -50,27 +50,20 @@ public: if (!vjoyTexture) { int w, h; - u8 *image_data = loadPNGData(get_readonly_data_path(DATA_PATH "buttons.png"), w, h); - if (image_data == nullptr) - { - WARN_LOG(RENDERER, "Cannot load buttons.png image"); - } - else - { - texCommandPool.BeginFrame(); - vjoyTexture = std::unique_ptr(new Texture()); - vjoyTexture->tex_type = TextureType::_8888; - vjoyTexture->tcw.full = 0; - vjoyTexture->tsp.full = 0; - vjoyTexture->SetPhysicalDevice(GetContext()->GetPhysicalDevice()); - vjoyTexture->SetDevice(GetContext()->GetDevice()); - vjoyTexture->SetCommandBuffer(texCommandPool.Allocate()); - vjoyTexture->UploadToGPU(OSD_TEX_W, OSD_TEX_H, image_data, false); - vjoyTexture->SetCommandBuffer(nullptr); - texCommandPool.EndFrame(); - delete [] image_data; - osdPipeline.Init(&normalShaderManager, vjoyTexture->GetImageView(), GetContext()->GetRenderPass()); - } + u8 *image_data = loadOSDButtons(w, h); + texCommandPool.BeginFrame(); + vjoyTexture = std::unique_ptr(new Texture()); + vjoyTexture->tex_type = TextureType::_8888; + vjoyTexture->tcw.full = 0; + vjoyTexture->tsp.full = 0; + vjoyTexture->SetPhysicalDevice(GetContext()->GetPhysicalDevice()); + vjoyTexture->SetDevice(GetContext()->GetDevice()); + vjoyTexture->SetCommandBuffer(texCommandPool.Allocate()); + vjoyTexture->UploadToGPU(OSD_TEX_W, OSD_TEX_H, image_data, false); + vjoyTexture->SetCommandBuffer(nullptr); + texCommandPool.EndFrame(); + delete [] image_data; + osdPipeline.Init(&normalShaderManager, vjoyTexture->GetImageView(), GetContext()->GetRenderPass()); } if (!osdBuffer) { diff --git a/core/rend/vulkan/vulkan_renderer.cpp b/core/rend/vulkan/vulkan_renderer.cpp index 6cbfad324..08d3beb9b 100644 --- a/core/rend/vulkan/vulkan_renderer.cpp +++ b/core/rend/vulkan/vulkan_renderer.cpp @@ -46,27 +46,20 @@ public: if (!vjoyTexture) { int w, h; - u8 *image_data = loadPNGData(get_readonly_data_path(DATA_PATH "buttons.png"), w, h); - if (image_data == nullptr) - { - WARN_LOG(RENDERER, "Cannot load buttons.png image"); - } - else - { - texCommandPool.BeginFrame(); - vjoyTexture = std::unique_ptr(new Texture()); - vjoyTexture->tex_type = TextureType::_8888; - vjoyTexture->tcw.full = 0; - vjoyTexture->tsp.full = 0; - vjoyTexture->SetPhysicalDevice(GetContext()->GetPhysicalDevice()); - vjoyTexture->SetDevice(GetContext()->GetDevice()); - vjoyTexture->SetCommandBuffer(texCommandPool.Allocate()); - vjoyTexture->UploadToGPU(OSD_TEX_W, OSD_TEX_H, image_data, false); - vjoyTexture->SetCommandBuffer(nullptr); - texCommandPool.EndFrame(); - delete [] image_data; - osdPipeline.Init(&shaderManager, vjoyTexture->GetImageView(), GetContext()->GetRenderPass()); - } + u8 *image_data = loadOSDButtons(w, h); + texCommandPool.BeginFrame(); + vjoyTexture = std::unique_ptr(new Texture()); + vjoyTexture->tex_type = TextureType::_8888; + vjoyTexture->tcw.full = 0; + vjoyTexture->tsp.full = 0; + vjoyTexture->SetPhysicalDevice(GetContext()->GetPhysicalDevice()); + vjoyTexture->SetDevice(GetContext()->GetDevice()); + vjoyTexture->SetCommandBuffer(texCommandPool.Allocate()); + vjoyTexture->UploadToGPU(OSD_TEX_W, OSD_TEX_H, image_data, false); + vjoyTexture->SetCommandBuffer(nullptr); + texCommandPool.EndFrame(); + delete [] image_data; + osdPipeline.Init(&shaderManager, vjoyTexture->GetImageView(), GetContext()->GetRenderPass()); } if (!osdBuffer) { diff --git a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/BaseGLActivity.java b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/BaseGLActivity.java index cf3feb51f..b14eb9e95 100644 --- a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/BaseGLActivity.java +++ b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/BaseGLActivity.java @@ -30,6 +30,8 @@ import com.reicast.emulator.emu.JNIdc; import com.reicast.emulator.periph.InputDeviceManager; import com.reicast.emulator.periph.SipEmulator; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -84,6 +86,7 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat. return; } + installButtons(); setStorageDirectories(); @@ -127,7 +130,6 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat. pathList.addAll(FileBrowser.getExternalMounts()); Log.i("flycast", "External storage dirs: " + pathList); JNIdc.setExternalStorageDirectories(pathList.toArray()); - FileBrowser.installButtons(prefs); } @Override @@ -298,6 +300,21 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat. } + private void installButtons() { + try { + InputStream in = Emulator.getAppContext().getAssets().open("buttons.png"); + + if (in != null) { + byte[] buf = new byte[in.available()]; + in.read(buf); + in.close(); + JNIdc.setButtons(buf); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + // Called from native code protected void generateErrorLog() { try { diff --git a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/Emulator.java b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/Emulator.java index bd84024da..54961da0f 100644 --- a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/Emulator.java +++ b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/Emulator.java @@ -8,7 +8,6 @@ import android.support.v7.app.AppCompatDelegate; import android.util.Log; import com.reicast.emulator.config.Config; -import com.reicast.emulator.emu.AudioBackend; import com.reicast.emulator.emu.JNIdc; public class Emulator extends Application { @@ -57,7 +56,6 @@ public class Emulator extends Application { prefs.edit() .putString(Config.pref_home, homeDirectory).apply(); - FileBrowser.installButtons(prefs); if (micPluggedIn() && currentActivity instanceof BaseGLActivity) { ((BaseGLActivity)currentActivity).requestRecordAudioPermission(); } diff --git a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/FileBrowser.java b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/FileBrowser.java index 8c4be194a..b32e103d6 100644 --- a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/FileBrowser.java +++ b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/FileBrowser.java @@ -1,19 +1,8 @@ package com.reicast.emulator; -import android.content.SharedPreferences; -import android.os.Environment; - -import com.reicast.emulator.config.Config; - import org.apache.commons.lang3.StringUtils; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.util.HashSet; public class FileBrowser { @@ -56,40 +45,4 @@ public class FileBrowser { } return out; } - - public static void installButtons(SharedPreferences prefs) { - try { - File buttons = null; - // TODO button themes - //String theme = prefs.getString(Config.pref_theme, null); - //if (theme != null) { - // buttons = new File(theme); - //} - String home_directory = prefs.getString(Config.pref_home, Environment.getExternalStorageDirectory().getAbsolutePath()); - File file = new File(home_directory, "data/buttons.png"); - InputStream in = null; - if (buttons != null && buttons.exists()) { - in = new FileInputStream(buttons); - } else if (!file.exists() || file.length() == 0) { - in = Emulator.getAppContext().getAssets().open("buttons.png"); - } - if (in != null) { - OutputStream out = new FileOutputStream(file); - - // Transfer bytes from in to out - byte[] buf = new byte[4096]; - int len; - while ((len = in.read(buf)) != -1) { - out.write(buf, 0, len); - } - in.close(); - out.flush(); - out.close(); - } - } catch (FileNotFoundException fnf) { - fnf.printStackTrace(); - } catch (IOException ioe) { - ioe.printStackTrace(); - } - } } diff --git a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/emu/JNIdc.java b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/emu/JNIdc.java index 68baae0b9..6ed921f17 100644 --- a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/emu/JNIdc.java +++ b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/emu/JNIdc.java @@ -1,6 +1,5 @@ package com.reicast.emulator.emu; -import android.util.Log; import android.view.Surface; import com.reicast.emulator.Emulator; @@ -43,4 +42,6 @@ public final class JNIdc JNIdc.vjoy(14, 1, 0, 0, 0); } public static native void hideOsd(); + + public static native void setButtons(byte data[]); } diff --git a/shell/android-studio/reicast/src/main/jni/src/Android.cpp b/shell/android-studio/reicast/src/main/jni/src/Android.cpp index aa8e67f2f..ed2e89842 100644 --- a/shell/android-studio/reicast/src/main/jni/src/Android.cpp +++ b/shell/android-studio/reicast/src/main/jni/src/Android.cpp @@ -23,6 +23,7 @@ #include "oslib/audiostream.h" #include "imgread/common.h" #include "rend/gui.h" +#include "rend/osd.h" #include "cfg/cfg.h" #include "log/LogManager.h" #include "wsi/context.h" @@ -112,6 +113,7 @@ JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_screenDpi(JNIEnv *env JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_guiOpenSettings(JNIEnv *env,jobject obj) __attribute__((visibility("default"))); JNIEXPORT jboolean JNICALL Java_com_reicast_emulator_emu_JNIdc_guiIsOpen(JNIEnv *env,jobject obj) __attribute__((visibility("default"))); JNIEXPORT jboolean JNICALL Java_com_reicast_emulator_emu_JNIdc_guiIsContentBrowser(JNIEnv *env,jobject obj) __attribute__((visibility("default"))); +JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_setButtons(JNIEnv *env, jobject obj, jbyteArray data) __attribute__((visibility("default"))); JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_init(JNIEnv *env, jobject obj) __attribute__((visibility("default"))); JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_joystickAdded(JNIEnv *env, jobject obj, jint id, jstring name, jint maple_port, jstring junique_id) __attribute__((visibility("default"))); @@ -656,3 +658,13 @@ void android_send_logs() jmethodID generateErrorLogMID = env->GetMethodID(env->GetObjectClass(g_activity), "generateErrorLog", "()V"); env->CallVoidMethod(g_activity, generateErrorLogMID); } + +JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_setButtons(JNIEnv *env, jobject obj, jbyteArray data) +{ + u32 len = env->GetArrayLength(data); + DefaultOSDButtons.resize(len); + jboolean isCopy; + jbyte* b = env->GetByteArrayElements(data, &isCopy); + memcpy(DefaultOSDButtons.data(), b, len); + env->ReleaseByteArrayElements(data, b, JNI_ABORT); +} \ No newline at end of file