diff --git a/app/jni/src/ZAPDTR/ZAPD/CMakeLists.txt b/app/jni/src/ZAPDTR/ZAPD/CMakeLists.txt index c7009a4..6bdd032 100644 --- a/app/jni/src/ZAPDTR/ZAPD/CMakeLists.txt +++ b/app/jni/src/ZAPDTR/ZAPD/CMakeLists.txt @@ -337,7 +337,11 @@ target_include_directories(ZAPDLib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../../libpng ) -target_link_libraries(ZAPDLib PRIVATE png) +if (CMAKE_SYSTEM_NAME STREQUAL "Android") + target_link_libraries(ZAPDLib PRIVATE png log) +else() + target_link_libraries(ZAPDLib PRIVATE png) +endif() target_include_directories(${PROJECT_NAME} PRIVATE diff --git a/app/jni/src/ZAPDTR/ZAPD/Main.cpp b/app/jni/src/ZAPDTR/ZAPD/Main.cpp index a2030aa..fedee3c 100644 --- a/app/jni/src/ZAPDTR/ZAPD/Main.cpp +++ b/app/jni/src/ZAPDTR/ZAPD/Main.cpp @@ -4,6 +4,14 @@ #include "Utils/Path.h" #include "WarningHandler.h" +#ifdef __ANDROID__ +#include + +// Define a custom macro to replace printf with __android_log_print +#define printf(...) __android_log_print(ANDROID_LOG_INFO, "YourTag", __VA_ARGS__) +#endif + + #include "ZAnimation.h" ZNormalAnimation nAnim(nullptr); ZCurveAnimation cAnim(nullptr); @@ -306,6 +314,7 @@ extern "C" int zapd_main(int argc, char* argv[]) auto start = std::chrono::steady_clock::now(); int fileListSize = fileList.size(); Globals::Instance->singleThreaded = false; + Globals::Instance->verbosity = VerbosityLevel::VERBOSITY_INFO; for (int i = 0; i < fileListSize; i++) Globals::Instance->workerData[i] = new FileWorker(); @@ -327,16 +336,15 @@ extern "C" int zapd_main(int argc, char* argv[]) } } - if (!Globals::Instance->singleThreaded) - { - while (true) - { - if (numWorkersLeft <= 0) - break; + if (!Globals::Instance->singleThreaded) { + while (true) { + printf("%i", numWorkersLeft); + if (numWorkersLeft <= 0) + break; - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - } - } + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } + } auto end = std::chrono::steady_clock::now(); auto diff = @@ -422,9 +430,12 @@ int ExtractFunc(int workerID, int fileListSize, std::string fileListItem, ZFileM return 1; } + printf("%s 1",fileListItem.c_str()); // It's getting stuck here VV. Need to print in Parse? + parseSuccessful = Parse(fileListItem, Globals::Instance->baseRomPath, Globals::Instance->outputPath, fileMode, workerID); + printf("%s 2",fileListItem.c_str()); if (!parseSuccessful) return 1; @@ -443,6 +454,7 @@ int ExtractFunc(int workerID, int fileListSize, std::string fileListItem, ZFileM } else { + printf("%s 3",fileListItem.c_str()); for (int i = 0; i < Globals::Instance->workerData[workerID]->files.size(); i++) { delete Globals::Instance->workerData[workerID]->files[i]; @@ -464,9 +476,11 @@ int ExtractFunc(int workerID, int fileListSize, std::string fileListItem, ZFileM bool Parse(const fs::path& xmlFilePath, const fs::path& basePath, const fs::path& outPath, ZFileMode fileMode, int workerID) { + printf("%i parse 1",workerID); tinyxml2::XMLDocument doc; tinyxml2::XMLError eResult = doc.LoadFile(xmlFilePath.string().c_str()); + printf("%i parse 2",workerID); if (eResult != tinyxml2::XML_SUCCESS) { // TODO: use XMLDocument::ErrorIDToName to get more specific error messages here @@ -475,7 +489,9 @@ bool Parse(const fs::path& xmlFilePath, const fs::path& basePath, const fs::path return false; } + printf("%i parse 3",workerID); tinyxml2::XMLNode* root = doc.FirstChild(); + printf("%i parse 4",workerID); if (root == nullptr) { @@ -485,6 +501,7 @@ bool Parse(const fs::path& xmlFilePath, const fs::path& basePath, const fs::path return false; } + printf("%i parse 5",workerID); for (tinyxml2::XMLElement* child = root->FirstChildElement(); child != NULL; child = child->NextSiblingElement()) { @@ -538,6 +555,7 @@ bool Parse(const fs::path& xmlFilePath, const fs::path& basePath, const fs::path HANDLE_ERROR(WarningType::InvalidXML, errorHeader, errorBody); } } + printf("%i parse 6",workerID); if (fileMode != ZFileMode::ExternalFile) { @@ -564,6 +582,7 @@ bool Parse(const fs::path& xmlFilePath, const fs::path& basePath, const fs::path if (exporterSet != nullptr && exporterSet->endXMLFunc != nullptr) exporterSet->endXMLFunc(); } + printf("%i parse 7",workerID); return true; } diff --git a/app/jni/src/soh/soh/Extractor/Extract.cpp b/app/jni/src/soh/soh/Extractor/Extract.cpp index fcfdcf3..eaf3a41 100644 --- a/app/jni/src/soh/soh/Extractor/Extract.cpp +++ b/app/jni/src/soh/soh/Extractor/Extract.cpp @@ -37,6 +37,11 @@ #define UNREACHABLE __builtin_unreachable(); #endif +#ifdef __ANDROID__ +#include +#include +#endif + #include #include @@ -61,27 +66,27 @@ static constexpr uint32_t OOT_PAL_10 = 0xB044B569; static constexpr uint32_t OOT_PAL_11 = 0xB2055FBD; static const std::unordered_map verMap = { - { OOT_PAL_GC, "PAL Gamecube" }, - { OOT_PAL_MQ, "PAL MQ" }, - { OOT_PAL_GC_DBG1, "PAL Debug 1" }, - { OOT_PAL_GC_DBG2, "PAL Debug 2" }, - { OOT_PAL_GC_MQ_DBG, "PAL MQ Debug" }, - { OOT_PAL_10, "PAL N64 1.0" }, - { OOT_PAL_11, "PAL N64 1.1" }, + { OOT_PAL_GC, "PAL Gamecube" }, + { OOT_PAL_MQ, "PAL MQ" }, + { OOT_PAL_GC_DBG1, "PAL Debug 1" }, + { OOT_PAL_GC_DBG2, "PAL Debug 2" }, + { OOT_PAL_GC_MQ_DBG, "PAL MQ Debug" }, + { OOT_PAL_10, "PAL N64 1.0" }, + { OOT_PAL_11, "PAL N64 1.1" }, }; // TODO only check the first 54MB of the rom. static constexpr std::array goodCrcs = { - 0xfa8c0555, // MQ DBG 64MB (Original overdump) - 0x8652ac4c, // MQ DBG 64MB - 0x5B8A1EB7, // MQ DBG 64MB (Empty overdump) - 0x1f731ffe, // MQ DBG 54MB - 0x044b3982, // NMQ DBG 54MB - 0xEB15D7B9, // NMQ DBG 64MB - 0xDA8E61BF, // GC PAL - 0x7A2FAE68, // GC MQ PAL - 0xFD9913B1, // N64 PAL 1.0 - 0xE033FBBA, // N64 PAL 1.1 + 0xfa8c0555, // MQ DBG 64MB (Original overdump) + 0x8652ac4c, // MQ DBG 64MB + 0x5B8A1EB7, // MQ DBG 64MB (Empty overdump) + 0x1f731ffe, // MQ DBG 54MB + 0x044b3982, // NMQ DBG 54MB + 0xEB15D7B9, // NMQ DBG 64MB + 0xDA8E61BF, // GC PAL + 0x7A2FAE68, // GC MQ PAL + 0xFD9913B1, // N64 PAL 1.0 + 0xE033FBBA, // N64 PAL 1.1 }; enum class ButtonId : int { @@ -90,6 +95,27 @@ enum class ButtonId : int { FIND, }; +#ifdef __ANDROID__ + +const char* javaRomPath = NULL; +bool fileDialogOpen = false; + +//function to be called from C +void openFilePickerFromC(JNIEnv* env, jobject javaObject) { + fileDialogOpen = true; + jclass javaClass = env->GetObjectClass(javaObject); + jmethodID openFilePickerMethod = env->GetMethodID(javaClass, "openFilePicker", "()V"); + env->CallVoidMethod(javaObject, openFilePickerMethod); +} +// Define the native method to handle the selected file path +extern "C" void JNICALL Java_com_dishii_soh_MainActivity_nativeHandleSelectedFile(JNIEnv* env, jobject obj, jstring filePath) { + const char* filePathStr = env->GetStringUTFChars(filePath, 0); + javaRomPath = strdup(filePathStr); // save filepath to string + fileDialogOpen = false; + env->ReleaseStringUTFChars(filePath, filePathStr); +} + +#endif void Extractor::ShowErrorBox(const char* title, const char* text) { #ifdef _WIN32 @@ -324,16 +350,35 @@ bool Extractor::GetRomPathFromBox() { return false; } mCurrentRomPath = nameBuffer; - #else - auto selection = pfd::open_file("Select a file", ".", { "N64 Roms", "*.z64 *.n64 *.v64" }).result(); +#else +#ifndef __ANDROID__ + auto selection = pfd::open_file("Select a file", ".", { "N64 Roms", "*.z64 *.n64 *.v64" }).result(); +#else + JNIEnv* javaEnv = (JNIEnv*)SDL_AndroidGetJNIEnv(); + jobject javaObject = (jobject)SDL_AndroidGetActivity(); + std::vector selection; + openFilePickerFromC(javaEnv, javaObject); + while(fileDialogOpen){ + //Do nothing until it's chosen + SDL_Delay(250); + } + SDL_Log("%s",javaRomPath); + selection.push_back(javaRomPath); +#endif if (selection.empty()) { return false; } mCurrentRomPath = selection[0]; - #endif +#endif mCurRomSize = GetCurRomSize(); +#ifdef __ANDROID__ + if (javaRomPath) { + free((void*)javaRomPath); + javaRomPath = NULL; + } +#endif return true; } @@ -414,7 +459,7 @@ bool Extractor::ManuallySearchForRomMatchingType(RomSearchMode searchMode) { char msgBuf[150]; snprintf(msgBuf, 150, "The selected rom does not match the expected game type\nExpected type: %s.\n\nDo you want to search again?", - searchMode == RomSearchMode::MQ ? "Master Quest" : "Vanilla"); + searchMode == RomSearchMode::MQ ? "Master Quest" : "Vanilla"); while ((searchMode == RomSearchMode::Vanilla && IsMasterQuest()) || (searchMode == RomSearchMode::MQ && !IsMasterQuest())) { @@ -542,8 +587,12 @@ const char* Extractor::GetZapdVerStr() const { } std::string Extractor::Mkdtemp() { - std::string temp_dir = SDL_AndroidGetExternalStoragePath();//std::filesystem::temp_directory_path().string(); - +#ifndef __ANDROID__ + std::string temp_dir = std::filesystem::temp_directory_path().string(); +#else + std::string temp_dir = SDL_AndroidGetExternalStoragePath(); +#endif + // create 6 random alphanumeric characters static const char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; std::random_device rd; @@ -581,9 +630,12 @@ bool Extractor::CallZapd(std::string installPath, std::string exportdir) { std::filesystem::copy(installPath + "/assets", tempdir + "/assets", std::filesystem::copy_options::recursive | std::filesystem::copy_options::update_existing); #else - //std::filesystem::create_symlink(installPath + "/assets", tempdir + "/assets"); +#ifndef __ANDROID__ + std::filesystem::create_symlink(installPath + "/assets", tempdir + "/assets"); +#else std::filesystem::copy(installPath + "/assets", tempdir + "/assets", std::filesystem::copy_options::recursive | std::filesystem::copy_options::update_existing); +#endif #endif std::filesystem::current_path(tempdir); @@ -617,8 +669,26 @@ bool Extractor::CallZapd(std::string installPath, std::string exportdir) { SetWindowPos(cmdWindow, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); #endif +#ifdef __ANDROID__ + int ret; + SDL_MessageBoxData boxData = { 0 }; + SDL_MessageBoxButtonData buttons[2] = { { 0 } }; + + buttons[0].buttonid = IDYES; + buttons[0].text = "Ok"; + buttons[0].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT; + boxData.numbuttons = 1; + boxData.flags = SDL_MESSAGEBOX_INFORMATION; + boxData.message = "The screen will go black for at least 30 seconds, but it could take up to 3 minutes."; + boxData.title = "Starting Extraction"; + boxData.buttons = buttons; + SDL_ShowMessageBox(&boxData, &ret); +#endif + zapd_main(argc, (char**)argv.data()); + SDL_Log("zapd_main finished"); + #ifdef _WIN32 // Hide the command window again. ShowWindow(cmdWindow, SW_HIDE); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1ecab2a..4485456 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -49,8 +49,8 @@ - - + + @@ -80,7 +80,7 @@ android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation" android:preferMinimalPostProcessing="true" android:exported="true" - android:screenOrientation="landscape" + android:screenOrientation="userLandscape" > diff --git a/app/src/main/java/com/dishii/soh/MainActivity.java b/app/src/main/java/com/dishii/soh/MainActivity.java index d33bafd..2b66a60 100644 --- a/app/src/main/java/com/dishii/soh/MainActivity.java +++ b/app/src/main/java/com/dishii/soh/MainActivity.java @@ -18,14 +18,54 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.util.Log; -//This class is the main SDLActivity +//This class is the main SDLActivity and just sets up a bunch of default files public class MainActivity extends SDLActivity{ + private static final int STORAGE_PERMISSION_REQUEST_CODE = 1; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - //Check if folder/file exists and if it doesn't, create it + // Check if storage permissions are granted + if (hasStoragePermission()) { + setupFiles(); + } else { + requestStoragePermission(); + } + } + + + // Check if storage permission is granted + private boolean hasStoragePermission() { + return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED; + } + + // Request storage permission + private void requestStoragePermission() { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE}, + STORAGE_PERMISSION_REQUEST_CODE); + } + + // Handle permission request result + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == STORAGE_PERMISSION_REQUEST_CODE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + setupFiles(); + } else { + // Permission denied, handle accordingly (e.g., show a message) + } + } + } + + private void setupFiles(){ File externalAssetsDir = new File(getExternalFilesDir(null), "assets"); if (!externalAssetsDir.exists()) { try { @@ -57,4 +97,51 @@ public class MainActivity extends SDLActivity{ } + private native void nativeHandleSelectedFile(String filePath); + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == 0 && resultCode == RESULT_OK) { + Uri selectedFileUri = data.getData(); + String fileName = "ZELOOTD.z64"; + File destinationDirectory = getExternalFilesDir(null); // The second argument can specify a subdirectory, or you can pass null to use the root directory. + File destinationFile = new File(destinationDirectory, fileName); + + if (destinationDirectory != null) { + try { + InputStream in = getContentResolver().openInputStream(selectedFileUri); + OutputStream out = new FileOutputStream(destinationFile); + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + + in.close(); + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + nativeHandleSelectedFile(destinationFile.getPath()); + } + } + + public void openFilePicker() { + // Create an Intent to open the file picker dialog + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + + // Start the file picker dialog + startActivityForResult(intent, 0); + } + + // Check if external storage is available and writable + private boolean isExternalStorageWritable() { + String state = Environment.getExternalStorageState(); + return Environment.MEDIA_MOUNTED.equals(state); + } + }