diff --git a/README.md b/README.md index 7e10d3b..a725049 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Optionally bootloader can be used by providing "DMG_ROM.bin" in working director * Ubuntu 14.04 LTS * Windows 10 (MinGW) -(* Android but example not yet(?) published) +* Android ## Thanks to ## diff --git a/android/ChesterApp/.gitignore b/android/ChesterApp/.gitignore new file mode 100644 index 0000000..59e1263 --- /dev/null +++ b/android/ChesterApp/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/.name +/.idea/compiler.xml +/.idea/copyright/ +/.idea/vcs.xml +/.idea/caches/* +/.idea/misc.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/android/ChesterApp/.idea/codeStyles/Project.xml b/android/ChesterApp/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..30aa626 --- /dev/null +++ b/android/ChesterApp/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/ChesterApp/.idea/gradle.xml b/android/ChesterApp/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/android/ChesterApp/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/android/ChesterApp/.idea/runConfigurations.xml b/android/ChesterApp/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/android/ChesterApp/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/android/ChesterApp/app/.gitignore b/android/ChesterApp/app/.gitignore new file mode 100644 index 0000000..956c004 --- /dev/null +++ b/android/ChesterApp/app/.gitignore @@ -0,0 +1,2 @@ +/build +/release \ No newline at end of file diff --git a/android/ChesterApp/app/CMakeLists.txt b/android/ChesterApp/app/CMakeLists.txt new file mode 100644 index 0000000..349204e --- /dev/null +++ b/android/ChesterApp/app/CMakeLists.txt @@ -0,0 +1,50 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +file(GLOB ChesterSrcs ../../../src/lib/*.c) + +add_library( # Sets the name of the library. + native-lib + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + ${ChesterSrcs} + src/main/cpp/native-lib.cpp ) + +# Include paths +include_directories(../../../src/lib) + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +find_library( # Sets the name of the path variable. + log-lib + + # Specifies the name of the NDK library that + # you want CMake to locate. + log ) + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +target_link_libraries( # Specifies the target library. + native-lib + + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) \ No newline at end of file diff --git a/android/ChesterApp/app/build.gradle b/android/ChesterApp/app/build.gradle new file mode 100644 index 0000000..343693b --- /dev/null +++ b/android/ChesterApp/app/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + defaultConfig { + applicationId "com.chester.chesterapp" + minSdkVersion 23 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags += "-I" + file("../../../src/lib").absolutePath + cFlags += "-I" + file("../../../src/lib").absolutePath + } + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:26.1.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' +} diff --git a/android/ChesterApp/app/proguard-rules.pro b/android/ChesterApp/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/android/ChesterApp/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/android/ChesterApp/app/src/main/AndroidManifest.xml b/android/ChesterApp/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9606ea9 --- /dev/null +++ b/android/ChesterApp/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/ChesterApp/app/src/main/cpp/native-lib.cpp b/android/ChesterApp/app/src/main/cpp/native-lib.cpp new file mode 100644 index 0000000..8463d10 --- /dev/null +++ b/android/ChesterApp/app/src/main/cpp/native-lib.cpp @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include + +#define LOG_E(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, "Chester", fmt, ##__VA_ARGS__) +#define LOG_D(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, "Chester", fmt, ##__VA_ARGS__) +#define LOG_V(fmt, ...) __android_log_print(ANDROID_LOG_VERBOSE, "Chester", fmt, ##__VA_ARGS__) +static JavaVM *jvm; + +extern "C" +{ +#include "chester.h" + +chester gChester; + +// Button states +bool button_a; +bool button_b; +bool button_start; +bool button_select; +bool button_up; +bool button_down; +bool button_left; +bool button_right; + +static int keysCb(keys* k) +{ + k->a = button_a; + k->b = button_b; + k->start = button_start; + k->select = button_select; + k->up = button_up; + k->down = button_down; + k->left = button_left; + k->right = button_right; + + return button_a || button_b || + button_start || button_select || + button_up || button_down || button_left || button_right; +} + +static int32_t ticksCb() +{ + struct timespec res; + clock_gettime(CLOCK_MONOTONIC, &res); + return static_cast(1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6); +} + +static void delayCb(uint32_t ms) +{ + usleep(ms * 1000); +} + +static bool initGpuCb(gpu* g) +{ + g->locked_pixel_data = malloc(256*160*4); + return true; +} + +static void uninitGpuCb(gpu* g) +{ + if (g->locked_pixel_data) { + free(g->locked_pixel_data); + } +} + +static bool lockTextureCb(gpu* g) +{ + return true; +} + +static void renderCb(gpu* g) +{ + JNIEnv *env; + jint rs = jvm->AttachCurrentThread(&env, NULL); + + jclass cls = env->FindClass("com/chester/chesterapp/MainActivity"); + jmethodID methodid = env->GetStaticMethodID(cls, "renderCallback", "(Ljava/nio/ByteBuffer;)V"); + jobject directByteBuffer = env->NewDirectByteBuffer(g->locked_pixel_data, 256*160*4); + env->CallStaticVoidMethod(cls, methodid, directByteBuffer); +} + +JNIEXPORT jboolean JNICALL +Java_com_chester_chesterapp_MainActivity_initChester( + JNIEnv *env, + jobject, /* this */ + jstring romPath, + jstring savePath) { + jint rs = env->GetJavaVM(&jvm); + assert (rs == JNI_OK); + + register_delay_callback(&gChester, &delayCb); + register_keys_callback(&gChester, &keysCb); + register_get_ticks_callback(&gChester, &ticksCb); + register_delay_callback(&gChester, &delayCb); + register_gpu_init_callback(&gChester, &initGpuCb); + register_gpu_uninit_callback(&gChester, &uninitGpuCb); + register_gpu_lock_texture_callback(&gChester, &lockTextureCb); + register_gpu_render_callback(&gChester, &renderCb); + + button_a = false; + button_b = false; + button_start = false; + button_select = false; + button_up = false; + button_down = false; + button_left = false; + button_right = false; + + const char *nativeRomPath = env->GetStringUTFChars(romPath, 0); + const char *nativeSavePath = env->GetStringUTFChars(savePath, 0); + + const bool ret = init(&gChester, nativeRomPath, nativeSavePath, NULL); + + env->ReleaseStringUTFChars(romPath, nativeRomPath); + env->ReleaseStringUTFChars(romPath, nativeSavePath); + + return static_cast(ret); +} + +JNIEXPORT void JNICALL +Java_com_chester_chesterapp_MainActivity_uninitChester( + JNIEnv *env, + jobject /* this */) { + uninit(&gChester); +} + +JNIEXPORT void JNICALL +Java_com_chester_chesterapp_MainActivity_saveChester( + JNIEnv *env, + jobject /* this */) { + save_if_needed(&gChester); +} + +JNIEXPORT jint JNICALL +Java_com_chester_chesterapp_MainActivity_runChester( + JNIEnv *env, + jobject /* this */) { + return run(&gChester); +} + +JNIEXPORT void JNICALL +Java_com_chester_chesterapp_MainActivity_setKeyA( + JNIEnv *env, + jobject /* this */, + jboolean pressed +) { + button_a = pressed; +} + +JNIEXPORT void JNICALL +Java_com_chester_chesterapp_MainActivity_setKeyB( + JNIEnv *env, + jobject /* this */, + jboolean pressed +) { + button_b = pressed; +} + +JNIEXPORT void JNICALL +Java_com_chester_chesterapp_MainActivity_setKeyStart( + JNIEnv *env, + jobject /* this */, + jboolean pressed +) { + + button_start = pressed; +} + +JNIEXPORT void JNICALL +Java_com_chester_chesterapp_MainActivity_setKeySelect( + JNIEnv *env, + jobject /* this */, + jboolean pressed +) { + button_select = pressed; +} + +JNIEXPORT void JNICALL +Java_com_chester_chesterapp_MainActivity_setKeyUp( + JNIEnv *env, + jobject /* this */, + jboolean pressed +) { + button_up = pressed; +} + +JNIEXPORT void JNICALL +Java_com_chester_chesterapp_MainActivity_setKeyDown( + JNIEnv *env, + jobject /* this */, + jboolean pressed +) { + button_down = pressed; +} + +JNIEXPORT void JNICALL +Java_com_chester_chesterapp_MainActivity_setKeyLeft( + JNIEnv *env, + jobject /* this */, + jboolean pressed +) { + button_left = pressed; +} + +JNIEXPORT void JNICALL +Java_com_chester_chesterapp_MainActivity_setKeyRight( + JNIEnv *env, + jobject /* this */, + jboolean pressed +) { + button_right = pressed; +} +} \ No newline at end of file diff --git a/android/ChesterApp/app/src/main/java/com/chester/chesterapp/ChesterView.java b/android/ChesterApp/app/src/main/java/com/chester/chesterapp/ChesterView.java new file mode 100644 index 0000000..b3a32aa --- /dev/null +++ b/android/ChesterApp/app/src/main/java/com/chester/chesterapp/ChesterView.java @@ -0,0 +1,65 @@ +package com.chester.chesterapp; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +public class ChesterView extends View { + + public ChesterView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public ChesterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public ChesterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + final static String TAG = "ChesterView"; + + final static Object imageLock = new Object(); + private Bitmap image; + private int width; + + public void setScreenWidth(int width) { + this.width = width; + } + + @Override + protected void onDraw(Canvas canvas) { + synchronized (imageLock) { + if (image != null) { + canvas.save(); + float scale = (float)width / 160; + canvas.scale(scale, scale); + canvas.drawBitmap(image, 0, 0, null); + canvas.restore(); + } else { + Log.i(TAG, "onDraw null"); + } + } + + super.onDraw(canvas); + } + + public void update(Bitmap bm) { + synchronized (imageLock) { + if (image != null) { + image.recycle(); + } + image = bm; + } + postInvalidate(); + } + + public Object getImageLock() { + return imageLock; + } +} diff --git a/android/ChesterApp/app/src/main/java/com/chester/chesterapp/FileDialog.java b/android/ChesterApp/app/src/main/java/com/chester/chesterapp/FileDialog.java new file mode 100644 index 0000000..1fe404c --- /dev/null +++ b/android/ChesterApp/app/src/main/java/com/chester/chesterapp/FileDialog.java @@ -0,0 +1,125 @@ +package com.chester.chesterapp; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Environment; +import android.support.v7.app.AlertDialog; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +// Lots of the credit for this code goes to schwiz and others who +// contributed to this helper class on Stack Overflow, see +// https://stackoverflow.com/questions/3592717/choose-file-dialog + +public class FileDialog { + private static final String PARENT_DIR = ".."; + private String[] mFileList; + private File mCurrentPath; + public interface FileSelectedListener { + void fileSelected(File file); + } + private ListenerList mFileListenerList = new ListenerList<>(); + private final Context mContext; + private String mFileEnd; + + FileDialog(Context context, File initialPath, String fileEndsWith) { + this.mContext = context; + setFileEndsWith(fileEndsWith); + if (!initialPath.exists()) initialPath = Environment.getExternalStorageDirectory(); + loadFileList(initialPath); + } + + private Dialog createFileDialog() { + Dialog dialog; + AlertDialog.Builder builder = new AlertDialog.Builder(mContext); + builder.setTitle(mCurrentPath.getPath()); + builder.setItems(mFileList, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String fileChosen = mFileList[which]; + File chosenFile = getChosenFile(fileChosen); + if (chosenFile.isDirectory()) { + loadFileList(chosenFile); + dialog.cancel(); + dialog.dismiss(); + showDialog(); + } else { + fileSelected(chosenFile); + } + } + }); + + dialog = builder.show(); + dialog.setCancelable(false); + return dialog; + } + + public void addFileListener(FileSelectedListener listener) { + mFileListenerList.add(listener); + } + + public void showDialog() { + createFileDialog().show(); + } + + private void fileSelected(final File file) { + mFileListenerList.fireEvent(new ListenerList.FireHandler() { + public void fireEvent(FileSelectedListener listener) { + listener.fileSelected(file); + } + }); + } + + private void loadFileList(File path) { + this.mCurrentPath = path; + List r = new ArrayList<>(); + if (path.exists()) { + if (path.getParentFile() != null) r.add(PARENT_DIR); + FilenameFilter filter = new FilenameFilter() { + public boolean accept(File dir, String filename) { + File sel = new File(dir, filename); + if (!sel.canRead()) return false; + else { + boolean endsWith = mFileEnd == null || filename.toLowerCase().endsWith(mFileEnd); + return endsWith || sel.isDirectory(); + } + } + }; + String[] fileList1 = path.list(filter); + Collections.addAll(r, fileList1); + } + mFileList = r.toArray(new String[]{}); + } + + private File getChosenFile(String fileChosen) { + if (fileChosen.equals(PARENT_DIR)) return mCurrentPath.getParentFile(); + else return new File(mCurrentPath, fileChosen); + } + + private void setFileEndsWith(String fileEndsWith) { + this.mFileEnd = fileEndsWith != null ? fileEndsWith.toLowerCase() : null; + } +} + +class ListenerList { + private List listenerList = new ArrayList(); + + public interface FireHandler { + void fireEvent(L listener); + } + + public void add(L listener) { + listenerList.add(listener); + } + + public void fireEvent(FireHandler fireHandler) { + List copy = new ArrayList(listenerList); + for (L l : copy) { + fireHandler.fireEvent(l); + } + } +} \ No newline at end of file diff --git a/android/ChesterApp/app/src/main/java/com/chester/chesterapp/MainActivity.java b/android/ChesterApp/app/src/main/java/com/chester/chesterapp/MainActivity.java new file mode 100644 index 0000000..7dc529f --- /dev/null +++ b/android/ChesterApp/app/src/main/java/com/chester/chesterapp/MainActivity.java @@ -0,0 +1,303 @@ +package com.chester.chesterapp; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.Display; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; + +import java.io.File; +import java.nio.ByteBuffer; + +public class MainActivity extends AppCompatActivity { + + static { + System.loadLibrary("native-lib"); + } + + final int code = 123; + final int readRequestCode = 124; + final static String TAG = "Chester"; + static java.util.concurrent.atomic.AtomicBoolean paused; + + static ChesterView chesterView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + int result = this.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE); + if (result != PackageManager.PERMISSION_GRANTED) { + try { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + readRequestCode); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + else + { + openGameSelector(this); + } + } + else + { + openGameSelector(this); + } + + paused = new java.util.concurrent.atomic.AtomicBoolean(true); + + Display display = getWindowManager().getDefaultDisplay(); + Point size = new Point(); + display.getSize(size); + + chesterView = findViewById(R.id.chesterView); + chesterView.setScreenWidth(size.x); + + buttonCallbacks(); + } + + @Override + protected void onResume() { + super.onResume(); + paused.set(false); + + View decorView = getWindow().getDecorView(); + int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN; + decorView.setSystemUiVisibility(uiOptions); + + ActionBar bar = getSupportActionBar(); + if (bar != null) { + bar.hide(); + } + } + + @Override + protected void onPause() { + paused.set(true); + super.onPause(); + } + + private void openGameSelector(final Context context) + { + File mPath = new File(Environment.getExternalStorageDirectory() + "//Download//"); + FileDialog fileDialog = new FileDialog(this, mPath, ".gb"); + fileDialog.addFileListener(new FileDialog.FileSelectedListener() { + public void fileSelected(File file) { + Log.d(getClass().getName(), "selected file " + file.toString()); + if (initChester(file.getAbsolutePath(), getFilesDir().getAbsolutePath() + "/")) + { + new Thread(){ + public void run(){ + Log.i(TAG, "Initialized Chester"); + int ret = 0; + do { + if (paused.get()) { + saveChester(); + try{ Thread.sleep(350); }catch(InterruptedException e){ } + } else { + ret = runChester(); + } + } while(ret == 0); + } + }.start(); + } + else + { + CharSequence text = "Couldn't init Chester"; + int duration = Toast.LENGTH_SHORT; + + Toast toast = Toast.makeText(context, text, duration); + toast.show(); + } + } + }); + fileDialog.showDialog(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, + @NonNull String permissions[], + @NonNull int[] grantResults) { + switch (requestCode) { + case readRequestCode: { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + + openGameSelector(this); + + } else { + // permission denied + finish(); + } + break; + } + } + } + + public static void renderCallback(ByteBuffer buffer){ + int bytes = buffer.remaining(); + byte[] imageBytes = new byte[bytes]; + buffer.get(imageBytes); + synchronized (chesterView.getImageLock()) { + Bitmap bm = Bitmap.createBitmap(256, 160, Bitmap.Config.ARGB_8888); + bm.copyPixelsFromBuffer(ByteBuffer.wrap(imageBytes)); + chesterView.update(bm); + } + } + + public void buttonCallbacks() { + Button a = findViewById(R.id.a); + a.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if(event.getAction() == MotionEvent.ACTION_DOWN) { + setKeyA(true); + return true; + } else if (event.getAction() == MotionEvent.ACTION_UP) { + setKeyA(false); + return true; + } + return false; + } + }); + + Button b = findViewById(R.id.b); + b.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if(event.getAction() == MotionEvent.ACTION_DOWN) { + setKeyB(true); + return true; + } else if (event.getAction() == MotionEvent.ACTION_UP) { + setKeyB(false); + return true; + } + return false; + } + }); + + Button start = findViewById(R.id.start); + start.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if(event.getAction() == MotionEvent.ACTION_DOWN) { + setKeyStart(true); + return true; + } else if (event.getAction() == MotionEvent.ACTION_UP) { + setKeyStart(false); + return true; + } + return false; + } + }); + + Button select = findViewById(R.id.select); + select.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if(event.getAction() == MotionEvent.ACTION_DOWN) { + setKeySelect(true); + return true; + } else if (event.getAction() == MotionEvent.ACTION_UP) { + setKeySelect(false); + return true; + } + return false; + } + }); + + Button up = findViewById(R.id.up); + up.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if(event.getAction() == MotionEvent.ACTION_DOWN) { + setKeyUp(true); + return true; + } else if (event.getAction() == MotionEvent.ACTION_UP) { + setKeyUp(false); + return true; + } + return false; + } + }); + + Button down = findViewById(R.id.down); + down.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if(event.getAction() == MotionEvent.ACTION_DOWN) { + setKeyDown(true); + return true; + } else if (event.getAction() == MotionEvent.ACTION_UP) { + setKeyDown(false); + return true; + } + return false; + } + }); + + Button left = findViewById(R.id.left); + left.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if(event.getAction() == MotionEvent.ACTION_DOWN) { + setKeyLeft(true); + return true; + } else if (event.getAction() == MotionEvent.ACTION_UP) { + setKeyLeft(false); + return true; + } + return false; + } + }); + + Button right = findViewById(R.id.right); + right.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if(event.getAction() == MotionEvent.ACTION_DOWN) { + setKeyRight(true); + return true; + } else if (event.getAction() == MotionEvent.ACTION_UP) { + setKeyRight(false); + return true; + } + return false; + } + }); + } + + public native boolean initChester(String rom, String savePath); + + public native int runChester(); + + public native void saveChester(); + + public native void setKeyA(boolean pressed); + public native void setKeyB(boolean pressed); + public native void setKeyStart(boolean pressed); + public native void setKeySelect(boolean pressed); + public native void setKeyUp(boolean pressed); + public native void setKeyDown(boolean pressed); + public native void setKeyLeft(boolean pressed); + public native void setKeyRight(boolean pressed); + +} diff --git a/android/ChesterApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/ChesterApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/android/ChesterApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/android/ChesterApp/app/src/main/res/drawable/ic_launcher_background.xml b/android/ChesterApp/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/android/ChesterApp/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/ChesterApp/app/src/main/res/layout/activity_main.xml b/android/ChesterApp/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..26138b5 --- /dev/null +++ b/android/ChesterApp/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + +