diff --git a/Common/Data/Format/JSONReader.h b/Common/Data/Format/JSONReader.h index 0f5804ecaa..44b33d25ee 100644 --- a/Common/Data/Format/JSONReader.h +++ b/Common/Data/Format/JSONReader.h @@ -10,8 +10,7 @@ namespace json { struct JsonGet { - JsonGet(const JsonValue &value) : value_(value) { - } + JsonGet(const JsonValue &value) : value_(value) {} int numChildren() const; const JsonNode *get(const char *child_name) const; @@ -47,7 +46,8 @@ struct JsonGet { class JsonReader { public: JsonReader(const std::string &filename); - JsonReader(const void *data, size_t size) { + // Makes a copy, after this returns you can free the input buffer. + JsonReader(const char *data, size_t size) { buffer_ = (char *)malloc(size + 1); if (buffer_) { memcpy(buffer_, data, size); diff --git a/Common/System/Request.h b/Common/System/Request.h index a53e904637..314dd7f88a 100644 --- a/Common/System/Request.h +++ b/Common/System/Request.h @@ -91,6 +91,7 @@ enum class BrowseFileType { INI, DB, SOUND_EFFECT, + ZIP, ANY, }; diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index b406305a6b..b66fd389d4 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -318,6 +318,9 @@ bool MainUI::HandleCustomEvent(QEvent *e) { case BrowseFileType::SOUND_EFFECT: filter = "WAVE files (*.wav)"; break; + case BrowseFileType::ZIP: + filter = "ZIP files (*.zip)"; + break; case BrowseFileType::ANY: break; } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index daefeb9c19..d6e2a27bf0 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1268,59 +1268,75 @@ UI::EventReturn DeveloperToolsScreen::OnCustomDriverChange(UI::EventParams &e) { UI::EventReturn DeveloperToolsScreen::OnCustomDriverInstall(UI::EventParams &e) { auto gr = GetI18NCategory(I18NCat::GRAPHICS); - System_BrowseForFile(gr->T("Install Custom Driver..."), BrowseFileType::ANY, [this](const std::string &value, int) { - const Path driverPath = g_Config.internalDataDirectory / "drivers"; - - if (!value.empty()) { - Path zipPath = Path(value); - - bool success = false; - - if (zipPath.GetFileExtension() == ".zip") { - ZipFileReader *zipFileReader = ZipFileReader::Create(zipPath, ""); - - size_t metaDataSize; - uint8_t *metaData = zipFileReader->ReadFile("meta.json", &metaDataSize); - - Path tempMeta = Path(g_Config.internalDataDirectory / "meta.json"); - - File::CreateEmptyFile(tempMeta); - File::WriteDataToFile(false, metaData, metaDataSize, tempMeta); - - delete[] metaData; - - json::JsonReader meta = json::JsonReader((g_Config.internalDataDirectory / "meta.json").c_str()); - if (meta.ok()) { - std::string driverName = meta.root().get("name")->value.toString(); - - Path newCustomDriver = driverPath / driverName; - File::CreateFullPath(newCustomDriver); - - std::vector zipListing; - zipFileReader->GetFileListing("", &zipListing, nullptr); - - for (auto file : zipListing) { - File::CreateEmptyFile(newCustomDriver / file.name); - - size_t size; - uint8_t *data = zipFileReader->ReadFile(file.name.c_str(), &size); - File::WriteDataToFile(false, data, size, newCustomDriver / file.name); - - delete[] data; - } - - File::Delete(tempMeta); - - success = true; - - RecreateViews(); - } - } - if (!success) { - auto gr = GetI18NCategory(I18NCat::GRAPHICS); - g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The file is not a ZIP file containing a compatible driver.")); - } + System_BrowseForFile(gr->T("Install Custom Driver..."), BrowseFileType::ZIP, [this](const std::string &value, int) { + if (value.empty()) { + return; } + + auto gr = GetI18NCategory(I18NCat::GRAPHICS); + + Path zipPath = Path(value); + + // Don't bother checking the file extension. Can't always do that with files from Download (they have paths like content://com.android.providers.downloads.documents/document/msf%3A1000001095). + // Though, it may be possible to get it in other ways. + + std::unique_ptr zipFileReader = std::unique_ptr(ZipFileReader::Create(zipPath, "", true)); + if (!zipFileReader) { + g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen file is not a valid ZIP file.")); + ERROR_LOG(SYSTEM, "Failed to open file '%s' as zip", zipPath.c_str()); + return; + } + + size_t metaDataSize; + uint8_t *metaData = zipFileReader->ReadFile("meta.json", &metaDataSize); + if (!metaData) { + g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), "meta.json missing"); + return; + } + + // Validate the json file. TODO: Be a bit more detailed. + json::JsonReader meta = json::JsonReader((const char *)metaData, metaDataSize); + delete[] metaData; + if (!meta.ok()) { + g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), "meta.json not valid json"); + return; + } + + const JsonNode *nameNode = meta.root().get("name"); + if (!nameNode) { + g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), "missing driver name in json"); + return; + } + + std::string driverName = nameNode->value.toString(); + if (driverName.empty()) { + g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), "driver name empty"); + return; + } + + const Path newCustomDriver = g_Config.internalDataDirectory / "drivers" / driverName; + NOTICE_LOG(G3D, "Installing driver into '%s'", newCustomDriver.c_str()); + File::CreateFullPath(newCustomDriver); + + std::vector zipListing; + zipFileReader->GetFileListing("", &zipListing, nullptr); + + for (auto file : zipListing) { + File::CreateEmptyFile(newCustomDriver / file.name); + + size_t size; + uint8_t *data = zipFileReader->ReadFile(file.name.c_str(), &size); + if (!data) { + g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), file.name.c_str()); + return; + } + File::WriteDataToFile(false, data, size, newCustomDriver / file.name); + delete[] data; + } + + auto iz = GetI18NCategory(I18NCat::INSTALLZIP); + g_OSD.Show(OSDType::MESSAGE_SUCCESS, iz->T("Installed!")); + RecreateViews(); }); return UI::EVENT_DONE; } diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 00321de052..5f7f9d6e9f 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -507,6 +507,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string case BrowseFileType::INI: supportedExtensions = { ".ini" }; break; + case BrowseFileType::ZIP: + supportedExtensions = { ".zip" }; + break; case BrowseFileType::DB: supportedExtensions = { ".db" }; break; diff --git a/Windows/main.cpp b/Windows/main.cpp index 05dd0b6018..22cc681f90 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -569,6 +569,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string case BrowseFileType::INI: filter = MakeFilter(L"Ini files (*.ini)|*.ini|All files (*.*)|*.*||"); break; + case BrowseFileType::ZIP: + filter = MakeFilter(L"ZIP files (*.zip)|*.zip|All files (*.*)|*.*||"); + break; case BrowseFileType::DB: filter = MakeFilter(L"Cheat db files (*.db)|*.db|All files (*.*)|*.*||"); break; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 0cd5ec1b48..08858ca83d 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -1108,12 +1108,16 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string case SystemRequestType::BROWSE_FOR_FILE: { BrowseFileType fileType = (BrowseFileType)param3; + std::string params = StringFromFormat("%d", requestId); switch (fileType) { case BrowseFileType::SOUND_EFFECT: - PushCommand("browse_file_audio", StringFromFormat("%d", requestId)); + PushCommand("browse_file_audio", params); + break; + case BrowseFileType::ZIP: + PushCommand("browse_file_zip", params); break; default: - PushCommand("browse_file", StringFromFormat("%d", requestId)); + PushCommand("browse_file", params); break; } return true; diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 17e7cac416..2d50642cc3 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -1409,7 +1409,7 @@ public abstract class NativeActivity extends Activity { Log.e(TAG, e.toString()); return false; } - } else if (command.equals("browse_file") || command.equals("browse_file_audio")) { + } else if (command.equals("browse_file") || command.equals("browse_file_audio") || command.equals("browse_file_zip")) { try { int requestId = Integer.parseInt(params); int packedResultCode = packResultCode(RESULT_OPEN_DOCUMENT, requestId); @@ -1418,6 +1418,8 @@ public abstract class NativeActivity extends Activity { intent.addCategory(Intent.CATEGORY_OPENABLE); if (command.equals("browse_file_audio")) { intent.setType("audio/x-wav"); + } else if (command.equals("browse_file_zip")) { + intent.setType("application/zip"); } else { intent.setType("*/*"); }