├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── melonds-issue.md ├── changelog │ ├── gitHub.md │ └── playStore │ │ └── whatsnew-en-GB ├── images │ ├── screenshot_mobile0.png │ ├── screenshot_mobile1.png │ ├── screenshot_mobile2.png │ └── screenshot_mobile3.png └── workflows │ ├── main.yaml │ ├── release-playstore.yaml │ └── release.yaml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── app ├── .gitignore ├── CMakeLists.txt ├── build.gradle.kts ├── proguard-rules.pro ├── schemas │ └── me.magnum.melonds.database.MelonDatabase │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ └── 5.json └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── magnum │ │ └── melonds │ │ └── database │ │ └── migrations │ │ └── Migration4to5Test.kt │ ├── debug │ └── res │ │ └── values │ │ └── strings.xml │ ├── gitHub │ ├── AndroidManifest.xml │ ├── java │ │ └── me │ │ │ └── magnum │ │ │ └── melonds │ │ │ ├── common │ │ │ └── providers │ │ │ │ └── UpdateContentProvider.kt │ │ │ ├── di │ │ │ └── GitHubModule.kt │ │ │ └── github │ │ │ ├── GitHubApi.kt │ │ │ ├── GitHubConstants.kt │ │ │ ├── dtos │ │ │ ├── AssetDto.kt │ │ │ └── ReleaseDto.kt │ │ │ └── services │ │ │ └── GitHubUpdateInstallManager.kt │ └── res │ │ └── xml │ │ ├── pref_general_updates.xml │ │ └── provider_paths.xml │ ├── gitHubNightly │ └── java │ │ └── me │ │ └── magnum │ │ └── melonds │ │ ├── di │ │ └── GitHubNightlyModule.kt │ │ └── github │ │ └── repositories │ │ └── GitHubNightlyUpdatesRepository.kt │ ├── gitHubProd │ └── java │ │ └── me │ │ └── magnum │ │ └── melonds │ │ ├── di │ │ └── GitHubProdModule.kt │ │ └── github │ │ └── repositories │ │ └── GitHubProdUpdatesRepository.kt │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── AndroidFrameRenderedCallback.cpp │ │ ├── AndroidFrameRenderedCallback.h │ │ ├── AndroidRACallback.cpp │ │ ├── AndroidRACallback.h │ │ ├── JniEnvHandler.cpp │ │ ├── JniEnvHandler.h │ │ ├── MelonDSAndroidCameraHandler.cpp │ │ ├── MelonDSAndroidCameraHandler.h │ │ ├── MelonDSAndroidConfiguration.cpp │ │ ├── MelonDSAndroidConfiguration.h │ │ ├── MelonDSAndroidInterface.cpp │ │ ├── MelonDSAndroidInterface.h │ │ ├── MelonDSAndroidJNI.cpp │ │ ├── MelonDSNandJNI.cpp │ │ ├── RAAchievementMapper.cpp │ │ ├── RAAchievementMapper.h │ │ ├── UriFileHandler.cpp │ │ └── UriFileHandler.h │ ├── ic_launcher-web.png │ ├── java │ │ └── me │ │ │ └── magnum │ │ │ └── melonds │ │ │ ├── MelonDSAndroidInterface.kt │ │ │ ├── MelonDSApplication.kt │ │ │ ├── MelonDSiNand.kt │ │ │ ├── MelonEmulator.kt │ │ │ ├── common │ │ │ ├── Crc32.kt │ │ │ ├── Deletable.kt │ │ │ ├── DirectoryAccessValidator.kt │ │ │ ├── Permission.kt │ │ │ ├── PermissionHandler.kt │ │ │ ├── RetroAchievementsCallback.kt │ │ │ ├── UriFileHandler.kt │ │ │ ├── UriPermissionManager.kt │ │ │ ├── camera │ │ │ │ ├── BlackDSiCameraSource.kt │ │ │ │ └── DSiCameraSource.kt │ │ │ ├── cheats │ │ │ │ ├── CheatDatabaseParser.kt │ │ │ │ ├── CheatDatabaseParserListener.kt │ │ │ │ ├── ProgressTrackerInputStream.kt │ │ │ │ └── XmlCheatDatabaseParser.kt │ │ │ ├── contracts │ │ │ │ ├── CreateFileContract.kt │ │ │ │ ├── DirectoryPickerContract.kt │ │ │ │ └── FilePickerContract.kt │ │ │ ├── network │ │ │ │ └── MelonOkHttpInterceptor.kt │ │ │ ├── opengl │ │ │ │ ├── Shader.kt │ │ │ │ ├── ShaderFactory.kt │ │ │ │ └── ShaderProgramSource.kt │ │ │ ├── retroachievements │ │ │ │ ├── AndroidRAAchievementSignatureProvider.kt │ │ │ │ └── AndroidRAUserAuthStore.kt │ │ │ ├── romprocessors │ │ │ │ ├── CompressedRomFileProcessor.kt │ │ │ │ ├── NdsRomFileProcessor.kt │ │ │ │ ├── RomFileProcessor.kt │ │ │ │ ├── RomFileProcessorFactory.kt │ │ │ │ ├── SevenZRomFileProcessor.kt │ │ │ │ └── ZipRomFileProcessor.kt │ │ │ ├── runtime │ │ │ │ └── ScreenshotFrameBufferProvider.kt │ │ │ ├── uridelegates │ │ │ │ ├── CompositeUriHandler.kt │ │ │ │ ├── ContentUriHandler.kt │ │ │ │ ├── StandardFileUriHandler.kt │ │ │ │ └── UriHandler.kt │ │ │ ├── vibration │ │ │ │ ├── Api26VibratorDelegate.kt │ │ │ │ ├── OldVibratorDelegate.kt │ │ │ │ └── TouchVibrator.kt │ │ │ └── workers │ │ │ │ ├── CheatImportWorker.kt │ │ │ │ └── RetroAchievementsSubmissionWorker.kt │ │ │ ├── database │ │ │ ├── MelonDatabase.kt │ │ │ ├── callback │ │ │ │ └── CustomCheatCreationCallback.kt │ │ │ ├── converters │ │ │ │ └── InstantConverter.kt │ │ │ ├── daos │ │ │ │ ├── CheatDao.kt │ │ │ │ ├── CheatDatabaseDao.kt │ │ │ │ ├── CheatFolderDao.kt │ │ │ │ ├── GameDao.kt │ │ │ │ └── RAAchievementsDao.kt │ │ │ ├── entities │ │ │ │ ├── CheatDatabaseEntity.kt │ │ │ │ ├── CheatEntity.kt │ │ │ │ ├── CheatFolderEntity.kt │ │ │ │ ├── CheatFolderWithCheats.kt │ │ │ │ ├── CheatStatusUpdate.kt │ │ │ │ ├── GameEntity.kt │ │ │ │ ├── GameWithCheatCategories.kt │ │ │ │ └── retroachievements │ │ │ │ │ ├── RAAchievementEntity.kt │ │ │ │ │ ├── RAGameEntity.kt │ │ │ │ │ ├── RAGameHashEntity.kt │ │ │ │ │ ├── RAGameSetMetadata.kt │ │ │ │ │ ├── RAPendingAchievementSubmissionEntity.kt │ │ │ │ │ └── RAUserAchievementEntity.kt │ │ │ └── migrations │ │ │ │ ├── Migration1to2.kt │ │ │ │ └── Migration4to5.kt │ │ │ ├── di │ │ │ ├── AppModule.kt │ │ │ ├── CoilModule.kt │ │ │ ├── DatabaseModule.kt │ │ │ ├── EmulatorRuntimeModule.kt │ │ │ ├── MelonModule.kt │ │ │ ├── MigrationModule.kt │ │ │ ├── RAModule.kt │ │ │ └── entrypoint │ │ │ │ └── InitializerEntryPoint.kt │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── AudioBitrate.kt │ │ │ │ ├── AudioInterpolation.kt │ │ │ │ ├── AudioLatency.kt │ │ │ │ ├── Background.kt │ │ │ │ ├── Cheat.kt │ │ │ │ ├── CheatDatabase.kt │ │ │ │ ├── CheatFolder.kt │ │ │ │ ├── CheatImportProgress.kt │ │ │ │ ├── CheatInFolder.kt │ │ │ │ ├── ConfigurationDirResult.kt │ │ │ │ ├── ConsoleType.kt │ │ │ │ ├── ControllerConfiguration.kt │ │ │ │ ├── DSiWareTitle.kt │ │ │ │ ├── DownloadProgress.kt │ │ │ │ ├── EmulatorConfiguration.kt │ │ │ │ ├── FirmwareColour.kt │ │ │ │ ├── FirmwareConfiguration.kt │ │ │ │ ├── FpsCounterPosition.kt │ │ │ │ ├── Game.kt │ │ │ │ ├── Input.kt │ │ │ │ ├── InputConfig.kt │ │ │ │ ├── MacAddress.kt │ │ │ │ ├── MicSource.kt │ │ │ │ ├── Point.kt │ │ │ │ ├── Rect.kt │ │ │ │ ├── RendererConfiguration.kt │ │ │ │ ├── RomIconFiltering.kt │ │ │ │ ├── RomInfo.kt │ │ │ │ ├── RomMetadata.kt │ │ │ │ ├── RomScanningStatus.kt │ │ │ │ ├── RuntimeBackground.kt │ │ │ │ ├── SaveStateLocation.kt │ │ │ │ ├── SaveStateSlot.kt │ │ │ │ ├── SizeUnit.kt │ │ │ │ ├── SortingMode.kt │ │ │ │ ├── SortingOrder.kt │ │ │ │ ├── Version.kt │ │ │ │ ├── VideoFiltering.kt │ │ │ │ ├── VideoRenderer.kt │ │ │ │ ├── appupdate │ │ │ │ │ └── AppUpdate.kt │ │ │ │ ├── camera │ │ │ │ │ └── DSiCameraSourceType.kt │ │ │ │ ├── dsinand │ │ │ │ │ ├── DSiWareTitleFileType.kt │ │ │ │ │ ├── ImportDSiWareTitleResult.kt │ │ │ │ │ └── OpenDSiNandResult.kt │ │ │ │ ├── emulator │ │ │ │ │ ├── EmulatorSessionUpdateAction.kt │ │ │ │ │ ├── FirmwareLaunchResult.kt │ │ │ │ │ └── RomLaunchResult.kt │ │ │ │ ├── layout │ │ │ │ │ ├── BackgroundMode.kt │ │ │ │ │ ├── LayoutComponent.kt │ │ │ │ │ ├── LayoutConfiguration.kt │ │ │ │ │ ├── PositionedLayoutComponent.kt │ │ │ │ │ ├── ScreenFold.kt │ │ │ │ │ ├── UILayout.kt │ │ │ │ │ └── UILayoutVariant.kt │ │ │ │ ├── render │ │ │ │ │ └── FrameRenderEvent.kt │ │ │ │ ├── retroachievements │ │ │ │ │ ├── GameAchievementData.kt │ │ │ │ │ ├── RAEvent.kt │ │ │ │ │ ├── RAGameSummary.kt │ │ │ │ │ ├── RASimpleAchievement.kt │ │ │ │ │ ├── RAUserAchievement.kt │ │ │ │ │ └── exception │ │ │ │ │ │ └── RAGameNotExist.kt │ │ │ │ ├── rom │ │ │ │ │ ├── Rom.kt │ │ │ │ │ └── config │ │ │ │ │ │ ├── RomConfig.kt │ │ │ │ │ │ ├── RomGbaSlotConfig.kt │ │ │ │ │ │ ├── RuntimeConsoleType.kt │ │ │ │ │ │ ├── RuntimeEnum.kt │ │ │ │ │ │ └── RuntimeMicSource.kt │ │ │ │ └── ui │ │ │ │ │ └── Orientation.kt │ │ │ ├── repositories │ │ │ │ ├── BackgroundRepository.kt │ │ │ │ ├── CheatsRepository.kt │ │ │ │ ├── DSiWareMetadataRepository.kt │ │ │ │ ├── LayoutsRepository.kt │ │ │ │ ├── RetroAchievementsRepository.kt │ │ │ │ ├── RomsRepository.kt │ │ │ │ ├── SaveStatesRepository.kt │ │ │ │ ├── SettingsRepository.kt │ │ │ │ └── UpdatesRepository.kt │ │ │ └── services │ │ │ │ ├── ConfigurationDirectoryVerifier.kt │ │ │ │ ├── DSiNandManager.kt │ │ │ │ ├── EmulatorManager.kt │ │ │ │ └── UpdateInstallManager.kt │ │ │ ├── extensions │ │ │ ├── ActivityExtensions.kt │ │ │ ├── ContextExtensions.kt │ │ │ ├── DisposableExtensions.kt │ │ │ ├── DocumentFileExtensions.kt │ │ │ ├── FragmentViewBindingDelegate.kt │ │ │ ├── ListExtensions.kt │ │ │ ├── ParcelableExtensions.kt │ │ │ ├── PreferenceExtensions.kt │ │ │ ├── StringExtensions.kt │ │ │ ├── ViewExtensions.kt │ │ │ └── WindowExtensions.kt │ │ │ ├── impl │ │ │ ├── AndroidDSiNandManager.kt │ │ │ ├── AndroidRetroAchievementsRepository.kt │ │ │ ├── BackgroundThumbnailProvider.kt │ │ │ ├── DefaultLayoutProvider.kt │ │ │ ├── FileSystemConfigurationDirectoryVerifier.kt │ │ │ ├── FileSystemRomsRepository.kt │ │ │ ├── FileSystemSaveStatesRepository.kt │ │ │ ├── InternalBackgroundsRepository.kt │ │ │ ├── InternalLayoutsRepository.kt │ │ │ ├── NdsRomCache.kt │ │ │ ├── NusDSiWareMetadataRepository.kt │ │ │ ├── RomIconProvider.kt │ │ │ ├── RoomCheatsRepository.kt │ │ │ ├── SaveStateScreenshotProvider.kt │ │ │ ├── ScreenUnitsConverter.kt │ │ │ ├── SharedPreferencesSettingsRepository.kt │ │ │ ├── XmlCheatDatabaseSAXHandler.kt │ │ │ ├── camera │ │ │ │ ├── CameraBuffers.kt │ │ │ │ ├── DSiCameraSourceMultiplexer.kt │ │ │ │ ├── PhysicalDSiCameraSource.kt │ │ │ │ └── StaticImageDSiCameraSource.kt │ │ │ ├── dtos │ │ │ │ ├── input │ │ │ │ │ ├── ControllerConfigurationDto.kt │ │ │ │ │ └── InputConfigDto.kt │ │ │ │ ├── layout │ │ │ │ │ ├── LayoutConfigurationDto.kt │ │ │ │ │ ├── PointDto.kt │ │ │ │ │ ├── PositionedLayoutComponentDto.kt │ │ │ │ │ ├── RectDto.kt │ │ │ │ │ ├── ScreenFoldDto.kt │ │ │ │ │ ├── UILayoutDto.kt │ │ │ │ │ └── UILayoutVariantDto.kt │ │ │ │ └── rom │ │ │ │ │ ├── RomConfigDto.kt │ │ │ │ │ ├── RomDto.kt │ │ │ │ │ └── RomGbaSlotConfigDto.kt │ │ │ ├── emulator │ │ │ │ ├── AndroidEmulatorManager.kt │ │ │ │ ├── EmulatorSession.kt │ │ │ │ ├── LifecycleOwnerProvider.kt │ │ │ │ ├── SramLoadException.kt │ │ │ │ └── SramProvider.kt │ │ │ ├── image │ │ │ │ ├── BitmapFactoryBitmapLoader.kt │ │ │ │ ├── BitmapLoader.kt │ │ │ │ ├── CoilBackgroundThumbnailFetcher.kt │ │ │ │ └── ImageDecoderBitmapLoader.kt │ │ │ ├── layout │ │ │ │ └── UILayoutProvider.kt │ │ │ ├── mappers │ │ │ │ └── retroachievements │ │ │ │ │ └── RAAchievementMapper.kt │ │ │ ├── retroachievements │ │ │ │ └── NoCacheRAAchievementsDao.kt │ │ │ └── romprocessors │ │ │ │ ├── Api24RomFileProcessorFactory.kt │ │ │ │ └── BaseRomFileProcessorFactory.kt │ │ │ ├── initializer │ │ │ └── CoilInitializer.kt │ │ │ ├── migrations │ │ │ ├── Migration.kt │ │ │ ├── Migration14to15.kt │ │ │ ├── Migration16to17.kt │ │ │ ├── Migration20to21.kt │ │ │ ├── Migration21to22.kt │ │ │ ├── Migration24to25.kt │ │ │ ├── Migration25to26.kt │ │ │ ├── Migration30to31.kt │ │ │ ├── Migration31to32.kt │ │ │ ├── Migration33to34.kt │ │ │ ├── Migration6to7.kt │ │ │ ├── Migration7to8.kt │ │ │ ├── Migrator.kt │ │ │ ├── helper │ │ │ │ └── GenericJsonArrayMigrationHelper.kt │ │ │ └── legacy │ │ │ │ ├── Rom21.kt │ │ │ │ ├── Rom22.kt │ │ │ │ ├── RomConfig1.kt │ │ │ │ ├── RomConfigDto25.kt │ │ │ │ ├── RomConfigDto31.kt │ │ │ │ ├── RomDto25.kt │ │ │ │ ├── RomDto31.kt │ │ │ │ ├── RomGbaSlotConfigDto31.kt │ │ │ │ ├── input │ │ │ │ ├── ControllerConfigurationDto33.kt │ │ │ │ └── InputConfigDto33.kt │ │ │ │ └── layout │ │ │ │ ├── LayoutConfiguration25.kt │ │ │ │ ├── LayoutConfigurationDto31.kt │ │ │ │ ├── PositionedLayoutComponent25.kt │ │ │ │ ├── Rect25.kt │ │ │ │ └── UILayout25.kt │ │ │ ├── parcelables │ │ │ ├── BackgroundParcelable.kt │ │ │ ├── RomConfigParcelable.kt │ │ │ ├── RomGbaSlotConfigParcelable.kt │ │ │ ├── RomInfoParcelable.kt │ │ │ ├── RomParcelable.kt │ │ │ └── cheat │ │ │ │ ├── CheatFolderParcelable.kt │ │ │ │ ├── CheatParcelable.kt │ │ │ │ └── GameParcelable.kt │ │ │ ├── ui │ │ │ ├── Theme.kt │ │ │ ├── backgrounds │ │ │ │ ├── BackgroundsActivity.kt │ │ │ │ ├── BackgroundsNavigation.kt │ │ │ │ ├── BackgroundsViewModel.kt │ │ │ │ └── ui │ │ │ │ │ ├── BackgroundItem.kt │ │ │ │ │ ├── BackgroundListScreen.kt │ │ │ │ │ └── BackgroundPreviewScreen.kt │ │ │ ├── cheats │ │ │ │ ├── CheatsActivity.kt │ │ │ │ ├── CheatsNavigation.kt │ │ │ │ ├── CheatsViewModel.kt │ │ │ │ ├── model │ │ │ │ │ ├── CheatFormDialogState.kt │ │ │ │ │ ├── CheatSubmissionForm.kt │ │ │ │ │ ├── CheatsScreenUiState.kt │ │ │ │ │ ├── DeletedCheat.kt │ │ │ │ │ └── OpenScreenEvent.kt │ │ │ │ └── ui │ │ │ │ │ ├── CheatListScreen.kt │ │ │ │ │ ├── CheatsScreen.kt │ │ │ │ │ ├── EnabledCheatsListScreen.kt │ │ │ │ │ ├── FolderListScreen.kt │ │ │ │ │ ├── GameListScreen.kt │ │ │ │ │ ├── LoadingScreen.kt │ │ │ │ │ ├── cheatform │ │ │ │ │ ├── CheatForm.kt │ │ │ │ │ └── CheatFormState.kt │ │ │ │ │ └── item │ │ │ │ │ ├── CheatInFolderItem.kt │ │ │ │ │ ├── CheatItem.kt │ │ │ │ │ ├── FolderItem.kt │ │ │ │ │ └── GameItem.kt │ │ │ ├── common │ │ │ │ ├── Colors.kt │ │ │ │ ├── FullScreen.kt │ │ │ │ ├── LayoutComponentView.kt │ │ │ │ ├── LayoutComponentViewBuilder.kt │ │ │ │ ├── LayoutComponentViewBuilderFactory.kt │ │ │ │ ├── LayoutView.kt │ │ │ │ ├── LoadingDialog.kt │ │ │ │ ├── MelonPreviewSet.kt │ │ │ │ ├── ModifierExtensions.kt │ │ │ │ ├── MultiActionFloatingActionButton.kt │ │ │ │ ├── TextInputDialog.kt │ │ │ │ ├── component │ │ │ │ │ ├── dialog │ │ │ │ │ │ ├── BaseDialog.kt │ │ │ │ │ │ └── TextInputDialog.kt │ │ │ │ │ └── text │ │ │ │ │ │ ├── CaptionText.kt │ │ │ │ │ │ └── OutlinedTextFieldWithError.kt │ │ │ │ ├── componentbuilders │ │ │ │ │ ├── BottomScreenLayoutComponentViewBuilder.kt │ │ │ │ │ ├── ButtonsLayoutComponentViewBuilder.kt │ │ │ │ │ ├── DpadLayoutComponentViewBuilder.kt │ │ │ │ │ ├── EditorBackgroundLayoutComponentViewBuilder.kt │ │ │ │ │ ├── ScreenLayoutComponentViewBuilder.kt │ │ │ │ │ ├── SingleButtonLayoutComponentViewBuilder.kt │ │ │ │ │ └── TopScreenLayoutComponentViewBuilder.kt │ │ │ │ ├── preference │ │ │ │ │ ├── ActionLauncherPreference.kt │ │ │ │ │ ├── SingleChoicePreference.kt │ │ │ │ │ └── SwitchPreference.kt │ │ │ │ └── viewmodel │ │ │ │ │ └── RetroAchievementsViewModel.kt │ │ │ ├── dsiwaremanager │ │ │ │ ├── DSiWareManagerActivity.kt │ │ │ │ ├── DSiWareManagerViewModel.kt │ │ │ │ ├── DSiWareRomListViewModel.kt │ │ │ │ ├── model │ │ │ │ │ ├── DSiWareItemDropdownMenu.kt │ │ │ │ │ ├── DSiWareManagerUiState.kt │ │ │ │ │ ├── DSiWareMangerRomListUiState.kt │ │ │ │ │ └── ImportExportDSiWareTitleFileEvent.kt │ │ │ │ └── ui │ │ │ │ │ ├── DSiWareItem.kt │ │ │ │ │ ├── DSiWareManagerScreen.kt │ │ │ │ │ ├── DSiWareRomListDialog.kt │ │ │ │ │ ├── DSiWareTitleFileActions.kt │ │ │ │ │ └── RomItem.kt │ │ │ ├── emulator │ │ │ │ ├── DSRenderer.kt │ │ │ │ ├── EmulatorActivity.kt │ │ │ │ ├── EmulatorFrameRenderedListener.kt │ │ │ │ ├── EmulatorRetroAchievementsViewModel.kt │ │ │ │ ├── EmulatorViewModel.kt │ │ │ │ ├── PauseMenuOption.kt │ │ │ │ ├── RuntimeLayoutComponentViewBuilderFactory.kt │ │ │ │ ├── RuntimeLayoutView.kt │ │ │ │ ├── component │ │ │ │ │ └── EmulatorOverlayTracker.kt │ │ │ │ ├── exceptions │ │ │ │ │ ├── RomLoadException.kt │ │ │ │ │ └── SaveSlotLoadException.kt │ │ │ │ ├── firmware │ │ │ │ │ └── FirmwarePauseMenuOption.kt │ │ │ │ ├── input │ │ │ │ │ ├── BaseInputHandler.kt │ │ │ │ │ ├── ButtonsInputHandler.kt │ │ │ │ │ ├── DpadInputHandler.kt │ │ │ │ │ ├── FeedbackInputHandler.kt │ │ │ │ │ ├── FrontendInputHandler.kt │ │ │ │ │ ├── IInputListener.kt │ │ │ │ │ ├── INativeInputListener.kt │ │ │ │ │ ├── InputProcessor.kt │ │ │ │ │ ├── MelonTouchHandler.kt │ │ │ │ │ ├── MultiButtonInputHandler.kt │ │ │ │ │ ├── SingleButtonInputHandler.kt │ │ │ │ │ ├── TouchscreenInputHandler.kt │ │ │ │ │ ├── componentbuilder │ │ │ │ │ │ ├── RuntimeScreenLayoutComponentViewBuilder.kt │ │ │ │ │ │ └── ToggleableSingleButtonLayoutComponentViewBuilder.kt │ │ │ │ │ └── view │ │ │ │ │ │ └── ToggleableImageView.kt │ │ │ │ ├── model │ │ │ │ │ ├── EmulatorOverlay.kt │ │ │ │ │ ├── EmulatorState.kt │ │ │ │ │ ├── EmulatorUiEvent.kt │ │ │ │ │ ├── PauseMenu.kt │ │ │ │ │ ├── PopupEvent.kt │ │ │ │ │ ├── RAIntegrationEvent.kt │ │ │ │ │ ├── RuntimeInputLayoutConfiguration.kt │ │ │ │ │ ├── RuntimeRendererConfiguration.kt │ │ │ │ │ └── ToastEvent.kt │ │ │ │ ├── rewind │ │ │ │ │ ├── EdgeSpacingDecorator.kt │ │ │ │ │ ├── RewindSaveStateAdapter.kt │ │ │ │ │ └── model │ │ │ │ │ │ ├── RewindSaveState.kt │ │ │ │ │ │ └── RewindWindow.kt │ │ │ │ ├── rom │ │ │ │ │ ├── RomPauseMenuOption.kt │ │ │ │ │ └── SaveStateListAdapter.kt │ │ │ │ └── ui │ │ │ │ │ ├── AchievementList.kt │ │ │ │ │ ├── AchievementListDialog.kt │ │ │ │ │ ├── AchievementPopupUi.kt │ │ │ │ │ └── RAIntegrationEventUi.kt │ │ │ ├── inputsetup │ │ │ │ ├── InputSetupActivity.kt │ │ │ │ ├── InputSetupViewModel.kt │ │ │ │ └── ui │ │ │ │ │ └── InputSetupScreen.kt │ │ │ ├── layouteditor │ │ │ │ ├── EditorLayoutComponentViewBuilderFactory.kt │ │ │ │ ├── LayoutBackgroundDialog.kt │ │ │ │ ├── LayoutEditorActivity.kt │ │ │ │ ├── LayoutEditorView.kt │ │ │ │ ├── LayoutEditorViewModel.kt │ │ │ │ ├── LayoutPropertiesDialog.kt │ │ │ │ └── model │ │ │ │ │ ├── CurrentLayoutState.kt │ │ │ │ │ └── LayoutBackgroundProperties.kt │ │ │ ├── layouts │ │ │ │ ├── BaseLayoutsFragment.kt │ │ │ │ ├── BaseLayoutsViewModel.kt │ │ │ │ ├── LayoutListActivity.kt │ │ │ │ ├── LayoutSelectorActivity.kt │ │ │ │ ├── LayoutSelectorViewModel.kt │ │ │ │ ├── LayoutsViewModel.kt │ │ │ │ └── fragments │ │ │ │ │ ├── LayoutListFragment.kt │ │ │ │ │ └── LayoutSelectorFragment.kt │ │ │ ├── romdetails │ │ │ │ ├── RomDetailsActivity.kt │ │ │ │ ├── RomDetailsRetroAchievementsViewModel.kt │ │ │ │ ├── RomDetailsUiMapper.kt │ │ │ │ ├── RomDetailsViewModel.kt │ │ │ │ ├── model │ │ │ │ │ ├── RomAchievementsSummary.kt │ │ │ │ │ ├── RomConfigUiModel.kt │ │ │ │ │ ├── RomConfigUiState.kt │ │ │ │ │ ├── RomConfigUpdateEvent.kt │ │ │ │ │ ├── RomDetailsTab.kt │ │ │ │ │ ├── RomGbaSlotConfigUiModel.kt │ │ │ │ │ └── RomRetroAchievementsUiState.kt │ │ │ │ └── ui │ │ │ │ │ ├── RetroAchievementsLoginDialog.kt │ │ │ │ │ ├── RomAchievementUi.kt │ │ │ │ │ ├── RomConfigUi.kt │ │ │ │ │ ├── RomDetailsScreen.kt │ │ │ │ │ ├── RomHeaderUi.kt │ │ │ │ │ ├── RomRetroAchievementsUi.kt │ │ │ │ │ └── preview │ │ │ │ │ └── RAAchievementMock.kt │ │ │ ├── romlist │ │ │ │ ├── NoRomSearchDirectoriesFragment.kt │ │ │ │ ├── RomIcon.kt │ │ │ │ ├── RomListActivity.kt │ │ │ │ ├── RomListFragment.kt │ │ │ │ ├── RomListViewModel.kt │ │ │ │ └── UpdatesViewModel.kt │ │ │ ├── settings │ │ │ │ ├── CheatsImportProgressDialog.kt │ │ │ │ ├── FileStatusPopup.kt │ │ │ │ ├── PreferenceFragmentHelper.kt │ │ │ │ ├── PreferenceFragmentTitleProvider.kt │ │ │ │ ├── RetroAchievementsSettingsViewModel.kt │ │ │ │ ├── SettingsActivity.kt │ │ │ │ ├── SettingsViewModel.kt │ │ │ │ ├── fragments │ │ │ │ │ ├── AudioPreferencesFragment.kt │ │ │ │ │ ├── CheatsPreferencesFragment.kt │ │ │ │ │ ├── CustomFirmwarePreferencesFragment.kt │ │ │ │ │ ├── FirmwarePreferencesFragment.kt │ │ │ │ │ ├── GeneralPreferencesFragment.kt │ │ │ │ │ ├── InputPreferencesFragment.kt │ │ │ │ │ ├── MainPreferencesFragment.kt │ │ │ │ │ ├── RetroAchievementsPreferencesFragment.kt │ │ │ │ │ ├── RewindPreferencesFragment.kt │ │ │ │ │ ├── RomsPreferencesFragment.kt │ │ │ │ │ ├── SaveFilesPreferencesFragment.kt │ │ │ │ │ ├── SystemPreferencesFragment.kt │ │ │ │ │ └── VideoPreferencesFragment.kt │ │ │ │ ├── model │ │ │ │ │ └── RetroAchievementsAccountState.kt │ │ │ │ └── preferences │ │ │ │ │ ├── BiosDirectoryPickerPreference.kt │ │ │ │ │ ├── FirmwareBirthdayPreference.kt │ │ │ │ │ ├── FirmwareColourPickerPreference.kt │ │ │ │ │ ├── MacAddressPreference.kt │ │ │ │ │ └── StoragePickerPreference.kt │ │ │ ├── shortcutsetup │ │ │ │ ├── DSFirmwareShortcutSetupActivity.kt │ │ │ │ ├── DSiFirmwareShortcutSetupActivity.kt │ │ │ │ ├── FirmwareShortcutSetupActivity.kt │ │ │ │ └── ShortcutSetupActivity.kt │ │ │ └── theme │ │ │ │ ├── Colors.kt │ │ │ │ ├── MelonTheme.kt │ │ │ │ └── Typography.kt │ │ │ └── utils │ │ │ ├── BitmapRegionDecoderCompat.kt │ │ │ ├── BitmapUtils.kt │ │ │ ├── CompositeOnPreferenceChangeListener.kt │ │ │ ├── DsScreenshotConverter.kt │ │ │ ├── EnumUtils.kt │ │ │ ├── FileUtils.kt │ │ │ ├── InputUtils.kt │ │ │ ├── PackageManagerCompat.kt │ │ │ ├── RomProcessor.kt │ │ │ ├── SharedFlow.kt │ │ │ ├── SimpleDiffCallback.kt │ │ │ ├── SingleLiveEvent.kt │ │ │ ├── SizeUtils.kt │ │ │ ├── UriTypeHierarchyAdapter.kt │ │ │ └── WindowManagerCompat.kt │ └── res │ │ ├── anim │ │ ├── fragment_translate_enter_pop.xml │ │ ├── fragment_translate_enter_push.xml │ │ ├── fragment_translate_exit_pop.xml │ │ ├── fragment_translate_exit_push.xml │ │ └── rotate.xml │ │ ├── drawable-night │ │ └── logo_dsiware.png │ │ ├── drawable-xhdpi │ │ └── logo_splash.png │ │ ├── drawable │ │ ├── background_bottom_screen.xml │ │ ├── background_rewind_save_state_focused.xml │ │ ├── background_top_screen.xml │ │ ├── background_uiview.xml │ │ ├── button_fast_forward.png │ │ ├── button_fast_forward_disabled.png │ │ ├── button_l.png │ │ ├── button_microphone.png │ │ ├── button_microphone_disabled.png │ │ ├── button_pause.png │ │ ├── button_quick_load.png │ │ ├── button_quick_save.png │ │ ├── button_r.png │ │ ├── button_reset.png │ │ ├── button_rewind.png │ │ ├── button_select.png │ │ ├── button_start.png │ │ ├── button_swap_screens.png │ │ ├── button_toggle_lid.png │ │ ├── buttons.png │ │ ├── drawable_ripple.xml │ │ ├── drawable_ripple_small.xml │ │ ├── ic_add.xml │ │ ├── ic_audio.xml │ │ ├── ic_block.xml │ │ ├── ic_cheat.xml │ │ ├── ic_clear.xml │ │ ├── ic_clock.xml │ │ ├── ic_completed.png │ │ ├── ic_file.xml │ │ ├── ic_firmware.xml │ │ ├── ic_folder.xml │ │ ├── ic_gba_cart.xml │ │ ├── ic_input.xml │ │ ├── ic_link.xml │ │ ├── ic_melon_monochrome.xml │ │ ├── ic_melon_small.png │ │ ├── ic_menu.xml │ │ ├── ic_points.png │ │ ├── ic_refresh.xml │ │ ├── ic_search.xml │ │ ├── ic_settings.xml │ │ ├── ic_sort.xml │ │ ├── ic_sort_alpha.xml │ │ ├── ic_status_error.xml │ │ ├── ic_status_ok.xml │ │ ├── ic_status_warn.xml │ │ ├── ic_touch_disabled.png │ │ ├── ic_touch_enabled.png │ │ ├── ic_trophy.xml │ │ ├── ic_video.xml │ │ ├── keypad.png │ │ ├── logo_dsiware.png │ │ ├── selector_rewind_save_state.xml │ │ └── selector_rewind_save_state_time_text.xml │ │ ├── layout │ │ ├── activity_emulator.xml │ │ ├── activity_layout_editor.xml │ │ ├── activity_layouts.xml │ │ ├── activity_rom_list.xml │ │ ├── activity_settings.xml │ │ ├── activity_shortcut_setup.xml │ │ ├── dialog_cheats_import_progress.xml │ │ ├── dialog_config_files.xml │ │ ├── dialog_firmware_birthday.xml │ │ ├── dialog_firmware_colour_picker.xml │ │ ├── dialog_layout_backgrounds.xml │ │ ├── dialog_layout_properties.xml │ │ ├── dialog_layout_update_download_progress.xml │ │ ├── dialog_loading.xml │ │ ├── dialog_mac_address_editor.xml │ │ ├── dialog_retroachievements_login.xml │ │ ├── dialog_text_input.xml │ │ ├── fragment_layout_list.xml │ │ ├── fragment_no_directories.xml │ │ ├── item_file_status.xml │ │ ├── item_layout.xml │ │ ├── item_rewind_save_state.xml │ │ ├── item_rom_base.xml │ │ ├── item_rom_configurable.xml │ │ ├── item_rom_simple.xml │ │ ├── item_save_state_slot.xml │ │ ├── preference_directory_picker_status.xml │ │ ├── preference_firmware_colour_picker_colour.xml │ │ └── rom_list_fragment.xml │ │ ├── menu │ │ ├── layout_item_menu.xml │ │ ├── layouts_menu.xml │ │ └── rom_list_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ ├── ic_platform_ds.xml │ │ └── ic_platform_dsi.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_platform_ds_foreground.png │ │ └── ic_platform_dsi_foreground.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_platform_ds_foreground.png │ │ └── ic_platform_dsi_foreground.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_platform_ds_foreground.png │ │ └── ic_platform_dsi_foreground.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_platform_ds_foreground.png │ │ └── ic_platform_dsi_foreground.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_platform_ds_foreground.png │ │ └── ic_platform_dsi_foreground.png │ │ ├── values-b+es │ │ └── strings.xml │ │ ├── values-b+fr │ │ └── strings.xml │ │ ├── values-b+id │ │ └── strings.xml │ │ ├── values-b+it │ │ └── strings.xml │ │ ├── values-b+pt+BR │ │ └── strings.xml │ │ ├── values-b+ru │ │ └── strings.xml │ │ ├── values-night │ │ ├── colors.xml │ │ └── values.xml │ │ ├── values-v29 │ │ └── strings_theme.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── attrs_preferences.xml │ │ ├── colors.xml │ │ ├── dimens_input.xml │ │ ├── strings.xml │ │ ├── strings_theme.xml │ │ ├── styles.xml │ │ └── values.xml │ │ └── xml │ │ ├── game_mode_config.xml │ │ ├── network_security_config.xml │ │ ├── pref_audio.xml │ │ ├── pref_cheats.xml │ │ ├── pref_custom_firmware.xml │ │ ├── pref_general.xml │ │ ├── pref_general_updates.xml │ │ ├── pref_input.xml │ │ ├── pref_internal_firmware_settings.xml │ │ ├── pref_main.xml │ │ ├── pref_retroachievements.xml │ │ ├── pref_rewind.xml │ │ ├── pref_roms.xml │ │ ├── pref_save_files.xml │ │ ├── pref_system.xml │ │ ├── pref_video.xml │ │ └── scene_activity_emulator.xml │ ├── nightly │ └── res │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_foreground.png │ │ └── values │ │ └── strings.xml │ ├── playStore │ └── java │ │ └── me │ │ └── magnum │ │ └── melonds │ │ ├── di │ │ └── PlayStoreModule.kt │ │ ├── playstore │ │ └── PlayStoreUpdatesRepository.kt │ │ └── services │ │ └── PlayStoreUpdateInstallManager.kt │ └── test │ └── java │ └── me │ └── magnum │ └── melonds │ └── VersionTest.kt ├── build.gradle.kts ├── buildSrc ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── AppConfig.kt ├── common ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── me │ └── magnum │ └── melonds │ └── common │ └── SuspendRunCatching.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── masterswitch ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── smp │ │ └── masterswitchpreference │ │ ├── MasterSwitchExplanationText.kt │ │ ├── MasterSwitchPreference.kt │ │ ├── MasterSwitchPreferenceAttrs.kt │ │ ├── MasterSwitchPreferenceFragment.kt │ │ └── MasterSwitchSwitchPreference.kt │ └── res │ ├── values │ ├── attrs.xml │ └── colors.xml │ └── xml │ ├── blank_preference_screen.xml │ └── explanation_preference_screen.xml ├── rcheevos-api ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── me │ └── magnum │ └── rcheevosapi │ ├── RAAchievementSignatureProvider.kt │ ├── RAApi.kt │ ├── RAUserAuthStore.kt │ ├── dto │ ├── AchievementDto.kt │ ├── GameDto.kt │ ├── GamePatchDto.kt │ ├── HashLibraryDto.kt │ ├── UserLoginDto.kt │ ├── UserUnlocksDto.kt │ └── mapper │ │ ├── AchievementDtoMapper.kt │ │ └── GameDtoMapper.kt │ ├── exception │ ├── UnsuccessfulRequestException.kt │ └── UserNotAuthenticatedException.kt │ └── model │ ├── RAAchievement.kt │ ├── RAGame.kt │ ├── RAGameId.kt │ └── RAUserAuth.kt └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: rafaelvcaetano 4 | -------------------------------------------------------------------------------- /.github/changelog/gitHub.md: -------------------------------------------------------------------------------- 1 | **Changelog:** 2 | Beta 1.10.0 3 | * Add OpenGL renderer, with resolution scaling up to 8x native 4 | * Add support for manual cheat input 5 | * Add button to toggle microphone input (thanks @ricnava00) 6 | * Add visual indicators for toggle buttons 7 | * Add support for memory expansion GBA slot 8 | * Allow DSiWare title data to be imported/exported 9 | * Improve foldable support 10 | * Add Italian translation (thanks @alessandrosimonelli and @jincio92) 11 | * Fix crash when importing cheat databases 12 | * Minor fixes and improvements -------------------------------------------------------------------------------- /.github/changelog/playStore/whatsnew-en-GB: -------------------------------------------------------------------------------- 1 | Beta 1.10.0 2 | • Add OpenGL renderer, with resolution scaling up to 8x native 3 | • Add support for manual cheat input 4 | • Add button to toggle microphone input 5 | • Add visual indicators for toggle buttons 6 | • Add support for memory expansion GBA slot 7 | • Allow DSiWare title data to be imported/exported 8 | • Improve foldable support 9 | • Add Italian translation 10 | • Fix crash when importing cheat databases 11 | • Minor fixes and improvements -------------------------------------------------------------------------------- /.github/images/screenshot_mobile0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/.github/images/screenshot_mobile0.png -------------------------------------------------------------------------------- /.github/images/screenshot_mobile1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/.github/images/screenshot_mobile1.png -------------------------------------------------------------------------------- /.github/images/screenshot_mobile2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/.github/images/screenshot_mobile2.png -------------------------------------------------------------------------------- /.github/images/screenshot_mobile3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/.github/images/screenshot_mobile3.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .cxx 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .kotlin -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "melonDS-android-lib"] 2 | path = melonDS-android-lib 3 | url = https://github.com/rafaelvcaetano/melonDS-android-lib.git 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release -------------------------------------------------------------------------------- /app/src/debug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | melonDS Dev 4 | -------------------------------------------------------------------------------- /app/src/gitHub/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/gitHub/java/me/magnum/melonds/github/GitHubApi.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.github 2 | 3 | import me.magnum.melonds.github.dtos.ReleaseDto 4 | import retrofit2.http.GET 5 | 6 | interface GitHubApi { 7 | @GET("/repos/rafaelvcaetano/melonDS-android/releases/latest") 8 | suspend fun getLatestRelease(): ReleaseDto 9 | 10 | @GET("/repos/rafaelvcaetano/melonDS-android/releases/tags/nightly-release") 11 | suspend fun getLatestNightlyRelease(): ReleaseDto 12 | } -------------------------------------------------------------------------------- /app/src/gitHub/java/me/magnum/melonds/github/GitHubConstants.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.github 2 | 3 | const val APK_CONTENT_TYPE = "application/vnd.android.package-archive" 4 | 5 | const val PREF_KEY_GITHUB_CHECK_FOR_UPDATES = "github_check_for_updates" -------------------------------------------------------------------------------- /app/src/gitHub/java/me/magnum/melonds/github/dtos/AssetDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.github.dtos 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class AssetDto( 8 | @SerialName("id") val id: Long, 9 | @SerialName("browser_download_url") val url: String, 10 | @SerialName("name") val name: String, 11 | @SerialName("size") val size: Long, 12 | @SerialName("content_type") val contentType: String 13 | ) -------------------------------------------------------------------------------- /app/src/gitHub/java/me/magnum/melonds/github/dtos/ReleaseDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.github.dtos 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ReleaseDto( 8 | @SerialName("tag_name") val tagName: String, 9 | @SerialName("name") val name: String, 10 | @SerialName("body") val body: String, 11 | @SerialName("created_at") val createdAt: String, 12 | @SerialName("assets") val assets: List 13 | ) -------------------------------------------------------------------------------- /app/src/gitHub/res/xml/pref_general_updates.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /app/src/gitHub/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/gitHubNightly/java/me/magnum/melonds/di/GitHubNightlyModule.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.di 2 | 3 | import android.content.Context 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import dagger.hilt.components.SingletonComponent 9 | import me.magnum.melonds.domain.repositories.UpdatesRepository 10 | import me.magnum.melonds.github.GitHubApi 11 | import me.magnum.melonds.github.repositories.GitHubNightlyUpdatesRepository 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object GitHubNightlyModule { 17 | 18 | @Provides 19 | @Singleton 20 | fun provideUpdatesRepository(@ApplicationContext context: Context, gitHubApi: GitHubApi): UpdatesRepository { 21 | val gitHubPreferences = context.getSharedPreferences("preferences-github", Context.MODE_PRIVATE) 22 | return GitHubNightlyUpdatesRepository(gitHubApi, gitHubPreferences) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/gitHubProd/java/me/magnum/melonds/di/GitHubProdModule.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.di 2 | 3 | import android.content.Context 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import dagger.hilt.components.SingletonComponent 9 | import me.magnum.melonds.domain.repositories.UpdatesRepository 10 | import me.magnum.melonds.github.GitHubApi 11 | import me.magnum.melonds.github.repositories.GitHubProdUpdatesRepository 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object GitHubProdModule { 17 | 18 | @Provides 19 | @Singleton 20 | fun provideUpdatesRepository(@ApplicationContext context: Context, gitHubApi: GitHubApi): UpdatesRepository { 21 | val gitHubPreferences = context.getSharedPreferences("preferences-github", Context.MODE_PRIVATE) 22 | return GitHubProdUpdatesRepository(context, gitHubApi, gitHubPreferences) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/cpp/AndroidFrameRenderedCallback.cpp: -------------------------------------------------------------------------------- 1 | #include "AndroidFrameRenderedCallback.h" 2 | 3 | AndroidFrameRenderedCallback::AndroidFrameRenderedCallback(JniEnvHandler* jniEnvHandler, jobject androidFrameRenderedListener) 4 | { 5 | this->jniEnvHandler = jniEnvHandler; 6 | this->androidFrameRenderedListener = androidFrameRenderedListener; 7 | } 8 | 9 | void AndroidFrameRenderedCallback::onFrameRendered(int textureId) 10 | { 11 | JNIEnv* env = this->jniEnvHandler->getCurrentThreadEnv(); 12 | 13 | jclass listenerClass = env->GetObjectClass(this->androidFrameRenderedListener); 14 | jmethodID onFrameRenderedMethod = env->GetMethodID(listenerClass, "onFrameRendered", "(I)V"); 15 | env->CallVoidMethod(this->androidFrameRenderedListener, onFrameRenderedMethod, textureId); 16 | } -------------------------------------------------------------------------------- /app/src/main/cpp/AndroidFrameRenderedCallback.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROIDFRAMERENDEREDCALLBACK_H 2 | #define ANDROIDFRAMERENDEREDCALLBACK_H 3 | 4 | #include "JniEnvHandler.h" 5 | #include 6 | #include 7 | 8 | class AndroidFrameRenderedCallback : public FrameRenderedCallback 9 | { 10 | private: 11 | JniEnvHandler* jniEnvHandler; 12 | jobject androidFrameRenderedListener; 13 | 14 | public: 15 | AndroidFrameRenderedCallback(JniEnvHandler* jniEnvHandler, jobject androidFrameRenderedListener); 16 | void onFrameRendered(int textureId); 17 | }; 18 | 19 | 20 | #endif //ANDROIDFRAMERENDEREDCALLBACK_H 21 | -------------------------------------------------------------------------------- /app/src/main/cpp/AndroidRACallback.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROIDRACALLBACK_H 2 | #define ANDROIDRACALLBACK_H 3 | 4 | #include "JniEnvHandler.h" 5 | #include 6 | #include 7 | 8 | class AndroidRACallback : public RetroAchievements::RACallback 9 | { 10 | private: 11 | JniEnvHandler* jniEnvHandler; 12 | jobject callback; 13 | 14 | public: 15 | AndroidRACallback(JniEnvHandler* jniEnvHandler, jobject callback); 16 | void onAchievementPrimed(long achievementId); 17 | void onAchievementTriggered(long achievementId); 18 | void onAchievementUnprimed(long achievementId); 19 | }; 20 | 21 | #endif //ANDROIDRACALLBACK_H 22 | -------------------------------------------------------------------------------- /app/src/main/cpp/JniEnvHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef MELONDS_ANDROID_JNIENVHANDLER_H 2 | #define MELONDS_ANDROID_JNIENVHANDLER_H 3 | 4 | #include 5 | 6 | class JniEnvHandler { 7 | private: 8 | JavaVM* vm; 9 | 10 | public: 11 | JniEnvHandler(JavaVM* vm); 12 | JNIEnv* getCurrentThreadEnv(); 13 | }; 14 | 15 | 16 | #endif //MELONDS_ANDROID_JNIENVHANDLER_H 17 | -------------------------------------------------------------------------------- /app/src/main/cpp/MelonDSAndroidCameraHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef MELONDSANDROIDCAMERAHANDLER_H 2 | #define MELONDSANDROIDCAMERAHANDLER_H 3 | 4 | #include 5 | #include 6 | #include "JniEnvHandler.h" 7 | 8 | class MelonDSAndroidCameraHandler : public MelonDSAndroid::AndroidCameraHandler { 9 | private: 10 | const int BUFFER_SIZE = 640 * 480 * 2; 11 | 12 | JniEnvHandler* jniEnvHandler; 13 | jobject cameraManager; 14 | 15 | public: 16 | MelonDSAndroidCameraHandler(JniEnvHandler* jniEnvHandler, jobject cameraManager); 17 | void startCamera(int camera); 18 | void stopCamera(int camera); 19 | void captureFrame(int camera, u32* frameBuffer, int width, int height, bool isYuv); 20 | virtual ~MelonDSAndroidCameraHandler(); 21 | }; 22 | 23 | #endif //MELONDSANDROIDCAMERAHANDLER_H 24 | -------------------------------------------------------------------------------- /app/src/main/cpp/MelonDSAndroidConfiguration.h: -------------------------------------------------------------------------------- 1 | #ifndef MELONDSANDROIDCONFIGURATION_H 2 | #define MELONDSANDROIDCONFIGURATION_H 3 | 4 | #include "MelonDS.h" 5 | 6 | namespace MelonDSAndroidConfiguration { 7 | MelonDSAndroid::EmulatorConfiguration buildEmulatorConfiguration(JNIEnv* env, jobject emulatorConfiguration); 8 | MelonDSAndroid::FirmwareConfiguration buildFirmwareConfiguration(JNIEnv* env, jobject firmwareConfiguration); 9 | GPU::RenderSettings buildRenderSettings(JNIEnv* env, jobject renderSettings); 10 | } 11 | 12 | #endif //MELONDSANDROIDCONFIGURATION_H 13 | -------------------------------------------------------------------------------- /app/src/main/cpp/MelonDSAndroidInterface.h: -------------------------------------------------------------------------------- 1 | #ifndef MELONDSANDROIDINTERFACE_H 2 | #define MELONDSANDROIDINTERFACE_H 3 | 4 | #include "JniEnvHandler.h" 5 | 6 | extern JniEnvHandler* jniEnvHandler; 7 | 8 | #endif //MELONDSANDROIDINTERFACE_H 9 | -------------------------------------------------------------------------------- /app/src/main/cpp/RAAchievementMapper.h: -------------------------------------------------------------------------------- 1 | #ifndef RAACHIEVEMENTMAPPER_H 2 | #define RAACHIEVEMENTMAPPER_H 3 | 4 | #include 5 | #include 6 | #include "retroachievements/RAAchievement.h" 7 | 8 | void mapAchievementsFromJava(JNIEnv *env, jobjectArray javaAchievements, std::list &outputList); 9 | 10 | #endif //RAACHIEVEMENTMAPPER_H 11 | -------------------------------------------------------------------------------- /app/src/main/cpp/UriFileHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "UriFileHandler.h" 2 | 3 | UriFileHandler::UriFileHandler(JniEnvHandler* jniEnvHandler, jobject uriFileHandler) 4 | { 5 | this->jniEnvHandler = jniEnvHandler; 6 | this->uriFileHandler = uriFileHandler; 7 | } 8 | 9 | FILE* UriFileHandler::open(const char* path, const char* mode) 10 | { 11 | JNIEnv* env = this->jniEnvHandler->getCurrentThreadEnv(); 12 | 13 | jstring pathString = env->NewStringUTF(path); 14 | jstring modeString = env->NewStringUTF(mode); 15 | jclass handlerClass = env->GetObjectClass(this->uriFileHandler); 16 | jmethodID openMethod = env->GetMethodID(handlerClass, "open", "(Ljava/lang/String;Ljava/lang/String;)I"); 17 | jint fileDescriptor = env->CallIntMethod(this->uriFileHandler, openMethod, pathString, modeString); 18 | 19 | if (fileDescriptor == -1) { 20 | return nullptr; 21 | } else { 22 | return fdopen(fileDescriptor, mode); 23 | } 24 | } 25 | 26 | UriFileHandler::~UriFileHandler() 27 | { 28 | } -------------------------------------------------------------------------------- /app/src/main/cpp/UriFileHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef MELONDS_ANDROID_URIFILEHANDLER_H 2 | #define MELONDS_ANDROID_URIFILEHANDLER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "JniEnvHandler.h" 8 | 9 | class UriFileHandler : public MelonDSAndroid::AndroidFileHandler { 10 | private: 11 | JniEnvHandler* jniEnvHandler; 12 | jobject uriFileHandler; 13 | 14 | public: 15 | UriFileHandler(JniEnvHandler* jniEnvHandler, jobject uriFileHandler); 16 | FILE* open(const char* path, const char* mode); 17 | virtual ~UriFileHandler(); 18 | }; 19 | 20 | #endif //MELONDS_ANDROID_URIFILEHANDLER_H 21 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/MelonDSAndroidInterface.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds 2 | 3 | import me.magnum.melonds.common.UriFileHandler 4 | 5 | object MelonDSAndroidInterface { 6 | external fun setup(uriFileHandler: UriFileHandler) 7 | external fun cleanup() 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/MelonDSiNand.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds 2 | 3 | import me.magnum.melonds.domain.model.DSiWareTitle 4 | import me.magnum.melonds.domain.model.EmulatorConfiguration 5 | 6 | object MelonDSiNand { 7 | external fun openNand(emulatorConfiguration: EmulatorConfiguration): Int 8 | external fun listTitles(): ArrayList 9 | external fun importTitle(titleUri: String, tmdMetadata: ByteArray): Int 10 | external fun deleteTitle(titleId: Int) 11 | external fun importTitleFile(titleId: Int, fileType: Int, fileUri: String): Boolean 12 | external fun exportTitleFile(titleId: Int, fileType: Int, fileUri: String): Boolean 13 | external fun closeNand() 14 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/Deletable.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common 2 | 3 | /** 4 | * Represents an entity that can be deleted but may be kept in memory. This can be useful, for example, to support soft-deleting of entities. 5 | */ 6 | data class Deletable(val data: T, val isDeleted: Boolean) 7 | 8 | fun List>.filterNotDeleted(): List { 9 | return mapNotNull { 10 | if (it.isDeleted) { 11 | null 12 | } else { 13 | it.data 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/Permission.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common 2 | 3 | import android.content.Intent 4 | 5 | enum class Permission { 6 | READ, 7 | WRITE, 8 | READ_WRITE; 9 | 10 | fun toFlags(): Int { 11 | return when (this) { 12 | READ -> Intent.FLAG_GRANT_READ_URI_PERMISSION 13 | WRITE -> Intent.FLAG_GRANT_WRITE_URI_PERMISSION 14 | READ_WRITE -> Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/RetroAchievementsCallback.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common 2 | 3 | interface RetroAchievementsCallback { 4 | fun onAchievementPrimed(achievementId: Long) 5 | fun onAchievementTriggered(achievementId: Long) 6 | fun onAchievementUnprimed(achievementId: Long) 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/UriPermissionManager.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | 6 | class UriPermissionManager(private val context: Context) { 7 | 8 | fun persistDirectoryPermissions(directoryUri: Uri, permission: Permission) { 9 | val flags = permission.toFlags() 10 | context.contentResolver.takePersistableUriPermission(directoryUri, flags) 11 | } 12 | 13 | fun persistFilePermissions(fileUri: Uri, permission: Permission) { 14 | val flags = permission.toFlags() 15 | context.contentResolver.takePersistableUriPermission(fileUri, flags) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/camera/BlackDSiCameraSource.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.camera 2 | 3 | class BlackDSiCameraSource : DSiCameraSource { 4 | 5 | override fun startCamera(camera: CameraType) { 6 | } 7 | 8 | override fun stopCamera(camera: CameraType) { 9 | } 10 | 11 | override fun captureFrame(camera: CameraType, buffer: ByteArray, width: Int, height: Int, isYuv: Boolean) { 12 | for (i in buffer.indices step 2) { 13 | // Use 0 for luminance (Y) and 127 for color (U and V) 14 | buffer[i] = 0 15 | buffer[i + 1] = 127 16 | } 17 | } 18 | 19 | override fun dispose() { 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/camera/DSiCameraSource.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.camera 2 | 3 | typealias CameraType = Int 4 | 5 | interface DSiCameraSource { 6 | 7 | companion object { 8 | const val BackCamera: CameraType = 0 9 | const val FrontCamera: CameraType = 1 10 | } 11 | 12 | fun startCamera(camera: CameraType) 13 | fun stopCamera(camera: CameraType) 14 | fun captureFrame(camera: CameraType, buffer: ByteArray, width: Int, height: Int, isYuv: Boolean) 15 | fun dispose() 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/cheats/CheatDatabaseParser.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.cheats 2 | 3 | interface CheatDatabaseParser { 4 | fun parseCheatDatabase(databaseStream: ProgressTrackerInputStream, parseListener: CheatDatabaseParserListener) 5 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/cheats/CheatDatabaseParserListener.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.cheats 2 | 3 | import me.magnum.melonds.domain.model.CheatDatabase 4 | import me.magnum.melonds.domain.model.Game 5 | 6 | interface CheatDatabaseParserListener { 7 | fun onDatabaseParseStart(databaseName: String): CheatDatabase 8 | fun onGameParseStart(gameName: String) 9 | fun onGameParsed(game: Game) 10 | fun onParseComplete() 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/cheats/ProgressTrackerInputStream.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.cheats 2 | 3 | import java.io.FilterInputStream 4 | import java.io.InputStream 5 | 6 | class ProgressTrackerInputStream(inputStream: InputStream?) : FilterInputStream(inputStream) { 7 | var totalReadBytes = 0 8 | private set 9 | 10 | override fun read(b: ByteArray?, off: Int, len: Int): Int { 11 | val readBytes = super.read(b, off, len) 12 | totalReadBytes += readBytes 13 | 14 | return readBytes 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/contracts/CreateFileContract.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.contracts 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import androidx.activity.result.contract.ActivityResultContract 8 | 9 | class CreateFileContract : ActivityResultContract() { 10 | override fun createIntent(context: Context, input: String): Intent { 11 | return Intent(Intent.ACTION_CREATE_DOCUMENT) 12 | .putExtra(Intent.EXTRA_TITLE, input) 13 | .addCategory(Intent.CATEGORY_OPENABLE) 14 | .setType("application/octet-stream") 15 | } 16 | 17 | override fun parseResult(resultCode: Int, intent: Intent?): Uri? { 18 | return if (intent == null || resultCode != Activity.RESULT_OK) { 19 | null 20 | } else { 21 | intent.data 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/retroachievements/AndroidRAAchievementSignatureProvider.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.retroachievements 2 | 3 | import me.magnum.rcheevosapi.RAAchievementSignatureProvider 4 | import me.magnum.rcheevosapi.model.RAUserAuth 5 | import java.math.BigInteger 6 | import java.security.MessageDigest 7 | 8 | class AndroidRAAchievementSignatureProvider : RAAchievementSignatureProvider { 9 | 10 | override fun provideAchievementSignature(achievementId: Long, userAuth: RAUserAuth, forHardcoreMode: Boolean): String { 11 | val md5Digest = MessageDigest.getInstance("MD5") 12 | md5Digest.update(achievementId.toString().toByteArray()) 13 | md5Digest.update(userAuth.username.toByteArray()) 14 | md5Digest.update((if (forHardcoreMode) "1" else "0").toByteArray()) 15 | 16 | return BigInteger(1, md5Digest.digest()).toString(16).padStart(32, '0') 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/romprocessors/RomFileProcessor.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.romprocessors 2 | 3 | import android.graphics.Bitmap 4 | import android.net.Uri 5 | import io.reactivex.Single 6 | import me.magnum.melonds.domain.model.rom.Rom 7 | import me.magnum.melonds.domain.model.RomInfo 8 | 9 | interface RomFileProcessor { 10 | fun getRomFromUri(romUri: Uri, parentUri: Uri): Rom? 11 | fun getRomIcon(rom: Rom): Bitmap? 12 | fun getRomInfo(rom: Rom): RomInfo? 13 | fun getRealRomUri(rom: Rom): Single 14 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/romprocessors/RomFileProcessorFactory.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.romprocessors 2 | 3 | import android.net.Uri 4 | import androidx.documentfile.provider.DocumentFile 5 | 6 | interface RomFileProcessorFactory { 7 | fun getFileRomProcessorForDocument(romDocument: DocumentFile): RomFileProcessor? 8 | fun getFileRomProcessorForDocument(romUri: Uri): RomFileProcessor? 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/uridelegates/ContentUriHandler.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.uridelegates 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import androidx.documentfile.provider.DocumentFile 6 | 7 | class ContentUriHandler(private val context: Context) : UriHandler { 8 | override fun fileExists(uri: Uri): Boolean { 9 | return getUriDocument(uri)?.exists() == true 10 | } 11 | 12 | override fun createFileDocument(uri: Uri): DocumentFile? { 13 | // We can't reliably create a document from a target URI. We need the parent tree URI and document name at least. 14 | return null 15 | } 16 | 17 | override fun getUriDocument(uri: Uri): DocumentFile? { 18 | return DocumentFile.fromSingleUri(context, uri) 19 | } 20 | 21 | override fun getUriTreeDocument(uri: Uri): DocumentFile? { 22 | return DocumentFile.fromTreeUri(context, uri) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/uridelegates/UriHandler.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.uridelegates 2 | 3 | import android.net.Uri 4 | import androidx.documentfile.provider.DocumentFile 5 | 6 | interface UriHandler { 7 | fun fileExists(uri: Uri): Boolean 8 | fun createFileDocument(uri: Uri): DocumentFile? 9 | fun getUriDocument(uri: Uri): DocumentFile? 10 | fun getUriTreeDocument(uri: Uri): DocumentFile? 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/vibration/Api26VibratorDelegate.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.vibration 2 | 3 | import android.os.Build 4 | import android.os.VibrationEffect 5 | import android.os.Vibrator 6 | import androidx.annotation.RequiresApi 7 | 8 | @RequiresApi(Build.VERSION_CODES.O) 9 | class Api26VibratorDelegate(private val vibrator: Vibrator) : TouchVibrator.VibratorDelegate { 10 | override fun supportsVibration(): Boolean { 11 | return vibrator.hasVibrator() 12 | } 13 | 14 | override fun supportsVibrationAmplitude(): Boolean { 15 | return vibrator.hasAmplitudeControl() 16 | } 17 | 18 | override fun vibrate(duration: Int, amplitude: Int) { 19 | val effect = VibrationEffect.createOneShot(duration.toLong(), amplitude) 20 | vibrator.vibrate(effect) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/common/vibration/OldVibratorDelegate.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common.vibration 2 | 3 | import android.os.Vibrator 4 | 5 | @Suppress("DEPRECATION") 6 | class OldVibratorDelegate(private val vibrator: Vibrator) : TouchVibrator.VibratorDelegate { 7 | override fun supportsVibration(): Boolean { 8 | return vibrator.hasVibrator() 9 | } 10 | 11 | override fun supportsVibrationAmplitude(): Boolean { 12 | return false 13 | } 14 | 15 | override fun vibrate(duration: Int, amplitude: Int) { 16 | vibrator.vibrate(duration.toLong()) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/callback/CustomCheatCreationCallback.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.callback 2 | 3 | import android.content.ContentValues 4 | import android.database.sqlite.SQLiteDatabase 5 | import androidx.room.RoomDatabase 6 | import androidx.sqlite.db.SupportSQLiteDatabase 7 | import me.magnum.melonds.database.entities.CheatDatabaseEntity 8 | 9 | class CustomCheatCreationCallback : RoomDatabase.Callback() { 10 | 11 | override fun onCreate(db: SupportSQLiteDatabase) { 12 | val contentValues = ContentValues().apply { 13 | put("id", CheatDatabaseEntity.CUSTOM_CHEATS_DATABASE_ID) 14 | put("name", CheatDatabaseEntity.CUSTOM_CHEATS_DATABASE_NAME) 15 | } 16 | 17 | db.insert("cheat_database", SQLiteDatabase.CONFLICT_REPLACE, contentValues) 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/converters/InstantConverter.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.converters 2 | 3 | import androidx.room.TypeConverter 4 | import java.time.Instant 5 | 6 | class InstantConverter { 7 | 8 | @TypeConverter 9 | fun instantToTimestamp(instant: Instant?): Long? { 10 | return instant?.toEpochMilli() 11 | } 12 | 13 | @TypeConverter 14 | fun timestampToInstant(timestamp: Long?): Instant? { 15 | return timestamp?.let { 16 | Instant.ofEpochMilli(it) 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/daos/CheatDatabaseDao.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.daos 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import me.magnum.melonds.database.entities.CheatDatabaseEntity 7 | 8 | @Dao 9 | interface CheatDatabaseDao { 10 | @Insert 11 | suspend fun insertCheatDatabase(database: CheatDatabaseEntity): Long 12 | 13 | @Query("DELETE FROM cheat_database WHERE name = :databaseName") 14 | suspend fun deleteCheatDatabase(databaseName: String) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/daos/CheatFolderDao.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.daos 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import me.magnum.melonds.database.entities.CheatFolderEntity 7 | 8 | @Dao 9 | interface CheatFolderDao { 10 | @Insert 11 | suspend fun insertCheatFolder(cheatFolder: CheatFolderEntity): Long 12 | 13 | @Insert 14 | suspend fun insertCheatFolders(cheatFolders: List): List 15 | 16 | @Query("DELETE FROM cheat_folder WHERE id NOT IN (SELECT DISTINCT cheat_folder_id FROM cheat)") 17 | suspend fun deleteEmptyFolders() 18 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/CheatDatabaseEntity.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.Index 6 | import androidx.room.PrimaryKey 7 | 8 | @Entity( 9 | tableName = "cheat_database", 10 | indices = [ 11 | Index(value = ["name"], unique = true) 12 | ] 13 | ) 14 | data class CheatDatabaseEntity( 15 | @ColumnInfo(name = "id") @PrimaryKey(autoGenerate = true) val id: Long?, 16 | @ColumnInfo(name = "name") val name: String, 17 | ) { 18 | 19 | companion object { 20 | // The ID of the database that holds cheats manually created by the user. Its 0 because SQLite starts autoincrement keys from 1 21 | const val CUSTOM_CHEATS_DATABASE_ID = 0L 22 | const val CUSTOM_CHEATS_DATABASE_NAME = "__custom_cheat_database" 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/CheatFolderEntity.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import androidx.room.PrimaryKey 7 | 8 | @Entity( 9 | tableName = "cheat_folder", 10 | foreignKeys = [ 11 | ForeignKey( 12 | entity = GameEntity::class, 13 | parentColumns = ["id"], 14 | childColumns = ["game_id"], 15 | onDelete = ForeignKey.CASCADE 16 | ) 17 | ] 18 | ) 19 | data class CheatFolderEntity( 20 | @PrimaryKey(autoGenerate = true) val id: Long?, 21 | @ColumnInfo(name = "game_id", index = true) val gameId: Long, 22 | @ColumnInfo(name = "name") val name: String 23 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/CheatFolderWithCheats.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities 2 | 3 | import androidx.room.Embedded 4 | import androidx.room.Relation 5 | 6 | class CheatFolderWithCheats { 7 | @Embedded 8 | lateinit var cheatFolder: CheatFolderEntity 9 | 10 | @Relation( 11 | parentColumn = "id", 12 | entityColumn = "cheat_folder_id" 13 | ) 14 | lateinit var cheats: List 15 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/CheatStatusUpdate.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities 2 | 3 | data class CheatStatusUpdate( 4 | val id: Long, 5 | val enabled: Boolean 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/GameEntity.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.Index 6 | import androidx.room.PrimaryKey 7 | 8 | @Entity( 9 | tableName = "game", 10 | indices = [ 11 | Index( 12 | value = ["game_code", "game_checksum"], 13 | name = "game_code_checksum_index", 14 | unique = true, 15 | ), 16 | ] 17 | ) 18 | data class GameEntity( 19 | @PrimaryKey(autoGenerate = true) val id: Long?, 20 | @ColumnInfo(name = "name") val name: String, 21 | @ColumnInfo(name = "game_code") val gameCode: String, 22 | @ColumnInfo(name = "game_checksum") val gameChecksum: String, 23 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/GameWithCheatCategories.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities 2 | 3 | import androidx.room.Embedded 4 | import androidx.room.Relation 5 | 6 | class GameWithCheatCategories { 7 | @Embedded 8 | lateinit var game: GameEntity 9 | 10 | @Relation( 11 | entity = CheatFolderEntity::class, 12 | parentColumn = "id", 13 | entityColumn = "game_id" 14 | ) 15 | lateinit var cheatFolders: List 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/retroachievements/RAGameEntity.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities.retroachievements 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "ra_game") 8 | data class RAGameEntity( 9 | @PrimaryKey @ColumnInfo(name = "game_id") val gameId: Long, 10 | @ColumnInfo(name = "rich_presence_patch") val richPresencePatch: String?, 11 | @ColumnInfo(name = "icon") val icon: String, 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/retroachievements/RAGameHashEntity.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities.retroachievements 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "ra_game_hash_library") 8 | data class RAGameHashEntity( 9 | @PrimaryKey @ColumnInfo(name = "game_hash") val gameHash: String, 10 | @ColumnInfo(name = "game_id") val gameId: Long, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/retroachievements/RAGameSetMetadata.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities.retroachievements 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import java.time.Instant 7 | 8 | @Entity(tableName = "ra_game_set_metadata") 9 | data class RAGameSetMetadata( 10 | @PrimaryKey @ColumnInfo(name = "game_id") val gameId: Long, 11 | @ColumnInfo(name = "last_achievement_set_updated") val lastAchievementSetUpdated: Instant?, 12 | @ColumnInfo(name = "last_user_data_updated") val lastSoftcoreUserDataUpdated: Instant?, 13 | @ColumnInfo(name = "last_hardcore_user_data_updated") val lastHardcoreUserDataUpdated: Instant?, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/retroachievements/RAPendingAchievementSubmissionEntity.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities.retroachievements 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | 6 | @Entity( 7 | tableName = "ra_pending_achievement_award", 8 | primaryKeys = ["achievement_id", "for_hardcore_mode"], 9 | ) 10 | data class RAPendingAchievementSubmissionEntity( 11 | @ColumnInfo(name = "achievement_id") val achievementId: Long, 12 | @ColumnInfo(name = "game_id") val gameId: Long, 13 | @ColumnInfo(name = "for_hardcore_mode") val forHardcoreMode: Boolean, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/entities/retroachievements/RAUserAchievementEntity.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.entities.retroachievements 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | 6 | @Entity( 7 | tableName = "ra_user_achievement", 8 | primaryKeys = ["game_id", "achievement_id", "is_hardcore"], 9 | ) 10 | data class RAUserAchievementEntity( 11 | @ColumnInfo(name = "game_id") val gameId: Long, 12 | @ColumnInfo(name = "achievement_id") val achievementId: Long, 13 | @ColumnInfo(name = "is_unlocked") val isUnlocked: Boolean, 14 | @ColumnInfo(name = "is_hardcore") val isHardcore: Boolean, 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/database/migrations/Migration1to2.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.database.migrations 2 | 3 | import androidx.room.migration.Migration 4 | import androidx.sqlite.db.SupportSQLiteDatabase 5 | 6 | class Migration1to2 : Migration(1, 2) { 7 | override fun migrate(db: SupportSQLiteDatabase) { 8 | db.execSQL("ALTER TABLE game ADD COLUMN game_checksum TEXT") 9 | db.execSQL("CREATE INDEX index_game_game_checksum ON game(game_checksum)") 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/di/entrypoint/InitializerEntryPoint.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.di.entrypoint 2 | 3 | import android.content.Context 4 | import dagger.hilt.EntryPoint 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.android.EntryPointAccessors 7 | import dagger.hilt.components.SingletonComponent 8 | import me.magnum.melonds.initializer.CoilInitializer 9 | 10 | @EntryPoint 11 | @InstallIn(SingletonComponent::class) 12 | interface InitializerEntryPoint { 13 | 14 | fun inject(coilInitializer: CoilInitializer) 15 | 16 | companion object { 17 | fun resolve(context: Context): InitializerEntryPoint { 18 | val applicationContext = context.applicationContext ?: throw IllegalStateException() 19 | return EntryPointAccessors.fromApplication( 20 | context = applicationContext, 21 | entryPoint = InitializerEntryPoint::class.java 22 | ) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/AudioBitrate.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class AudioBitrate(val bitrateValue: Int) { 4 | AUTO(0), 5 | BIT10(1), 6 | BIT16(2) 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/AudioInterpolation.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class AudioInterpolation(val interpolationValue: Int) { 4 | NONE(0), 5 | LINEAR(1), 6 | COSINE(2), 7 | CUBIC(3) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/AudioLatency.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class AudioLatency(val latencyValue: Int) { 4 | LOW(0), 5 | MEDIUM(1), 6 | HIGH(2) 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/Background.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | import android.net.Uri 4 | import java.util.UUID 5 | 6 | data class Background(val id: UUID?, val name: String, val uri: Uri) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/Cheat.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class Cheat( 4 | val id: Long?, 5 | val cheatDatabaseId: Long, 6 | val name: String, 7 | val description: String?, 8 | val code: String, 9 | val enabled: Boolean, 10 | ) { 11 | 12 | fun isValid(): Boolean { 13 | // A cheat code can only have 128 parts (512 bytes). Since each part has 8 characters, we can add 1 to the length (to ensure that each part has a matching space 14 | // separator) and divide by 9 (part length + space separator) to calculate the total number of parts in the cheat 15 | return ((code.length + 1) / 9) <= 128 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/CheatDatabase.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class CheatDatabase( 4 | val id: Long?, 5 | val name: String, 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/CheatFolder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class CheatFolder(val id: Long?, val name: String, val cheats: List) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/CheatImportProgress.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class CheatImportProgress(val status: CheatImportStatus, val progress: Float, val ongoingItemName: String?) { 4 | enum class CheatImportStatus { 5 | NOT_IMPORTING, 6 | STARTING, 7 | ONGOING, 8 | FINISHED, 9 | FAILED 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/CheatInFolder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class CheatInFolder(val cheat: Cheat, val folderName: String) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/ConfigurationDirResult.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class ConfigurationDirResult( 4 | val consoleType: ConsoleType, 5 | val status: Status, 6 | val requiredFiles: Array, 7 | val fileResults: Array> 8 | ) { 9 | enum class Status { 10 | UNSET, INVALID, VALID 11 | } 12 | 13 | enum class FileStatus { 14 | PRESENT, MISSING, INVALID 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/ConsoleType.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class ConsoleType(val consoleType: Int) { 4 | DS(0), 5 | DSi(1) 6 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/DSiWareTitle.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | class DSiWareTitle( 4 | val name: String, 5 | val producer: String, 6 | val titleId: Long, 7 | val icon: ByteArray, 8 | val publicSavSize: Long, 9 | val privateSavSize: Long, 10 | val appFlags: Int, 11 | ) { 12 | 13 | fun hasPublicSavFile() = publicSavSize != 0L 14 | 15 | fun hasPrivateSavFile() = privateSavSize != 0L 16 | 17 | fun hasBannerSavFile() = (appFlags and (0x04)) != 0 18 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/DownloadProgress.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | sealed class DownloadProgress { 4 | class DownloadUpdate(val totalSize: Long, val downloadedBytes: Long) : DownloadProgress() 5 | object DownloadComplete : DownloadProgress() 6 | object DownloadFailed : DownloadProgress() 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/FirmwareColour.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class FirmwareColour { 4 | GRAY, 5 | BROWN, 6 | RED, 7 | PINK, 8 | ORANGE, 9 | YELLOW, 10 | LIME, 11 | GREEN, 12 | DARK_GREEN, 13 | TURQUOISE, 14 | LIGHT_BLUE, 15 | BLUE, 16 | DARK_BLUE, 17 | PURPLE, 18 | VIOLET, 19 | FUCHSIA 20 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/FirmwareConfiguration.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class FirmwareConfiguration( 4 | val nickname: String, 5 | val message: String, 6 | val language: Int, 7 | val favouriteColour: Int, 8 | val birthdayMonth: Int, 9 | val birthdayDay: Int, 10 | val randomizeMacAddress: Boolean, 11 | val internalMacAddress: String? 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/FpsCounterPosition.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class FpsCounterPosition { 4 | HIDDEN, 5 | TOP_LEFT, 6 | TOP_CENTER, 7 | TOP_RIGHT, 8 | BOTTOM_LEFT, 9 | BOTTOM_CENTER, 10 | BOTTOM_RIGHT 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/Game.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class Game(val id: Long?, val name: String, val gameCode: String, val gameChecksum: String, val cheats: List) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/InputConfig.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class InputConfig(val input: Input, val assignment: Assignment = Assignment.None) { 4 | 5 | sealed class Assignment(open val deviceId: Int?) { 6 | data object None : Assignment(null) 7 | data class Key(override val deviceId: Int?, val keyCode: Int) : Assignment(deviceId) 8 | data class Axis(override val deviceId: Int?, val axisCode: Int, val direction: Direction) : Assignment(deviceId) { 9 | enum class Direction { 10 | POSITIVE, 11 | NEGATIVE, 12 | } 13 | } 14 | } 15 | 16 | fun hasKeyAssigned(): Boolean { 17 | return assignment != Assignment.None 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/MicSource.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class MicSource(val sourceValue: Int) { 4 | NONE(0), 5 | BLOW(1), 6 | DEVICE(2) 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/Point.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class Point(var x: Int, var y: Int) { 4 | constructor() : this(0, 0) 5 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/Rect.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class Rect(val x: Int, val y: Int, val width: Int, val height: Int) { 4 | 5 | val bottom get() = y + height 6 | 7 | val right get() = x + width 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/RendererConfiguration.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class RendererConfiguration( 4 | val renderer: VideoRenderer, 5 | val videoFiltering: VideoFiltering, 6 | val threadedRendering: Boolean, 7 | private val internalResolutionScaling: Int, 8 | ) { 9 | 10 | val resolutionScaling get() = when (renderer) { 11 | VideoRenderer.SOFTWARE -> 1 12 | VideoRenderer.OPENGL -> internalResolutionScaling 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/RomIconFiltering.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class RomIconFiltering { 4 | NONE, 5 | LINEAR 6 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/RomInfo.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | /** 4 | * @param gameTitle The short ROM title as present in the ROM header 5 | * @param gameName The ROM name as displayed in the system 6 | */ 7 | data class RomInfo( 8 | val gameCode: String, 9 | val headerChecksum: UInt, 10 | val gameTitle: String, 11 | val gameName: String, 12 | ) { 13 | fun headerChecksumString(): String { 14 | return headerChecksum.toString(16).padStart(8, '0').uppercase() 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/RomMetadata.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | data class RomMetadata( 4 | val romTitle: String, 5 | val developerName: String, 6 | val isDSiWareTitle: Boolean, 7 | val retroAchievementsHash: String, 8 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/RomScanningStatus.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class RomScanningStatus { 4 | SCANNING, 5 | NOT_SCANNING 6 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/RuntimeBackground.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | import me.magnum.melonds.domain.model.layout.BackgroundMode 4 | 5 | data class RuntimeBackground(val background: Background?, val mode: BackgroundMode) { 6 | 7 | companion object { 8 | val None = RuntimeBackground(null, BackgroundMode.STRETCH) 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/SaveStateLocation.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class SaveStateLocation { 4 | SAVE_DIR, 5 | ROM_DIR, 6 | INTERNAL_DIR 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/SaveStateSlot.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | import android.net.Uri 4 | import java.util.* 5 | 6 | data class SaveStateSlot(val slot: Int, val exists: Boolean, val lastUsedDate: Date?, val screenshot: Uri?) { 7 | 8 | companion object { 9 | const val QUICK_SAVE_SLOT = 0 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/SortingMode.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class SortingMode(val defaultOrder: SortingOrder) { 4 | ALPHABETICALLY(SortingOrder.ASCENDING), 5 | RECENTLY_PLAYED(SortingOrder.DESCENDING) 6 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/SortingOrder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class SortingOrder { 4 | ASCENDING, 5 | DESCENDING 6 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/VideoFiltering.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class VideoFiltering { 4 | NONE, 5 | LINEAR, 6 | XBR2, 7 | HQ2X, 8 | HQ4X, 9 | QUILEZ, 10 | LCD, 11 | SCANLINES 12 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/VideoRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model 2 | 3 | enum class VideoRenderer(val renderer: Int) { 4 | SOFTWARE(0), 5 | OPENGL(1), 6 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/appupdate/AppUpdate.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.appupdate 2 | 3 | import android.net.Uri 4 | import me.magnum.melonds.domain.model.Version 5 | import java.time.Instant 6 | 7 | data class AppUpdate( 8 | val type: Type, 9 | val id: Long, 10 | val downloadUri: Uri, 11 | val newVersion: Version, 12 | val description: String, 13 | val binarySize: Long, 14 | val updateDate: Instant, 15 | ) { 16 | 17 | enum class Type { 18 | PRODUCTION, 19 | NIGHTLY, 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/camera/DSiCameraSourceType.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.camera 2 | 3 | enum class DSiCameraSourceType { 4 | BLACK_SCREEN, 5 | PHYSICAL_CAMERAS, 6 | STATIC_IMAGE, 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/dsinand/DSiWareTitleFileType.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.dsinand 2 | 3 | enum class DSiWareTitleFileType(val fileName: String) { 4 | PUBLIC_SAV("public.sav"), 5 | PRIVATE_SAV("private.sav"), 6 | BANNER_SAV("banner.sav"), 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/dsinand/ImportDSiWareTitleResult.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.dsinand 2 | 3 | enum class ImportDSiWareTitleResult { 4 | SUCCESS, 5 | NAND_NOT_OPEN, 6 | ERROR_OPENING_FILE, 7 | NOT_DSIWARE_TITLE, 8 | TITLE_ALREADY_IMPORTED, 9 | INSATLL_FAILED, 10 | METADATA_FETCH_FAILED, 11 | UNKNOWN, 12 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/dsinand/OpenDSiNandResult.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.dsinand 2 | 3 | enum class OpenDSiNandResult { 4 | SUCCESS, 5 | NAND_ALREADY_OPEN, 6 | BIOS7_NOT_FOUND, 7 | NAND_OPEN_FAILED, 8 | INVALID_DSI_SETUP, 9 | UNKNOWN, 10 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/emulator/EmulatorSessionUpdateAction.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.emulator 2 | 3 | sealed class EmulatorSessionUpdateAction { 4 | data object EnableRetroAchievements : EmulatorSessionUpdateAction() 5 | data object DisableRetroAchievements : EmulatorSessionUpdateAction() 6 | data object NotifyRetroAchievementsModeSwitch : EmulatorSessionUpdateAction() 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/emulator/FirmwareLaunchResult.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.emulator 2 | 3 | import me.magnum.melonds.MelonEmulator 4 | 5 | sealed class FirmwareLaunchResult { 6 | data class LaunchFailed(val reason: MelonEmulator.FirmwareLoadResult) : FirmwareLaunchResult() 7 | object LaunchSuccessful : FirmwareLaunchResult() 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/emulator/RomLaunchResult.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.emulator 2 | 3 | import me.magnum.melonds.MelonEmulator 4 | 5 | sealed class RomLaunchResult { 6 | data object LaunchFailedRomNotFound : RomLaunchResult() 7 | data class LaunchFailedSramProblem(val reason: Exception) : RomLaunchResult() 8 | data class LaunchFailed(val reason: MelonEmulator.LoadResult) : RomLaunchResult() 9 | data class LaunchSuccessful(val isGbaLoadSuccessful: Boolean) : RomLaunchResult() 10 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/layout/BackgroundMode.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.layout 2 | 3 | enum class BackgroundMode { 4 | STRETCH, 5 | FIT_CENTER, 6 | FIT_TOP, 7 | FIT_LEFT, 8 | FIT_BOTTOM, 9 | FIT_RIGHT; 10 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/layout/LayoutComponent.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.layout 2 | 3 | enum class LayoutComponent { 4 | TOP_SCREEN, 5 | BOTTOM_SCREEN, 6 | DPAD, 7 | BUTTONS, 8 | BUTTON_START, 9 | BUTTON_SELECT, 10 | BUTTON_L, 11 | BUTTON_R, 12 | BUTTON_HINGE, 13 | BUTTON_FAST_FORWARD_TOGGLE, 14 | BUTTON_TOGGLE_SOFT_INPUT, 15 | BUTTON_RESET, 16 | BUTTON_PAUSE, 17 | BUTTON_SWAP_SCREENS, 18 | BUTTON_QUICK_SAVE, 19 | BUTTON_QUICK_LOAD, 20 | BUTTON_REWIND, 21 | BUTTON_MICROPHONE_TOGGLE; 22 | 23 | fun isScreen(): Boolean { 24 | return this == TOP_SCREEN || this == BOTTOM_SCREEN 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/layout/PositionedLayoutComponent.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.layout 2 | 3 | import me.magnum.melonds.domain.model.Rect 4 | 5 | data class PositionedLayoutComponent(val rect: Rect, val component: LayoutComponent) { 6 | fun isScreen(): Boolean { 7 | return component.isScreen() 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/layout/ScreenFold.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.layout 2 | 3 | import me.magnum.melonds.domain.model.Rect 4 | import me.magnum.melonds.domain.model.ui.Orientation 5 | 6 | data class ScreenFold( 7 | val orientation: Orientation, 8 | val type: FoldType, 9 | val foldBounds: Rect, 10 | ) { 11 | 12 | enum class FoldType { 13 | SEAMLESS, 14 | GAP, 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/layout/UILayout.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.layout 2 | 3 | import java.util.UUID 4 | 5 | data class UILayout( 6 | val backgroundId: UUID?, 7 | val backgroundMode: BackgroundMode, 8 | val components: List?, 9 | ) { 10 | // Empty constructor allow parsing after new data is added to the class 11 | constructor() : this(null) 12 | 13 | constructor(components: List?): this(null, BackgroundMode.FIT_CENTER, components) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/layout/UILayoutVariant.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.layout 2 | 3 | import me.magnum.melonds.domain.model.Point 4 | import me.magnum.melonds.domain.model.ui.Orientation 5 | 6 | data class UILayoutVariant( 7 | val uiSize: Point, 8 | val orientation: Orientation, 9 | val folds: List, 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/render/FrameRenderEvent.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.render 2 | 3 | data class FrameRenderEvent( 4 | val textureId: Int, 5 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/retroachievements/RAEvent.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.retroachievements 2 | 3 | sealed class RAEvent { 4 | data class OnAchievementPrimed(val achievementId: Long) : RAEvent() 5 | data class OnAchievementUnPrimed(val achievementId: Long) : RAEvent() 6 | data class OnAchievementTriggered(val achievementId: Long) : RAEvent() 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/retroachievements/RAGameSummary.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.retroachievements 2 | 3 | import java.net.URL 4 | 5 | data class RAGameSummary( 6 | val icon: URL, 7 | val richPresencePatch: String?, 8 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/retroachievements/RASimpleAchievement.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.retroachievements 2 | 3 | data class RASimpleAchievement( 4 | val id: Long, 5 | val memoryAddress: String, 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/retroachievements/RAUserAchievement.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.retroachievements 2 | 3 | import me.magnum.rcheevosapi.model.RAAchievement 4 | 5 | data class RAUserAchievement( 6 | val achievement: RAAchievement, 7 | val isUnlocked: Boolean, 8 | val forHardcoreMode: Boolean, 9 | ) { 10 | 11 | /** 12 | * Returns the number of points that this achievement is worth for the user in the current state. This depends on the actual number of points that the achievements awards, 13 | * whether the user has unlocked it or not and if it was unlocked in hardcore or softcore mode. 14 | */ 15 | fun userPointsWorth(): Int { 16 | return if (isUnlocked) { 17 | achievement.points 18 | } else { 19 | 0 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/retroachievements/exception/RAGameNotExist.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.retroachievements.exception 2 | 3 | class RAGameNotExist(gameHash: String) : Exception("There is no game for hash $gameHash") -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/rom/Rom.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.rom 2 | 3 | import android.net.Uri 4 | import me.magnum.melonds.domain.model.rom.config.RomConfig 5 | import java.util.* 6 | 7 | data class Rom( 8 | val name: String, 9 | val developerName: String, 10 | val fileName: String, 11 | val uri: Uri, 12 | val parentTreeUri: Uri, 13 | var config: RomConfig, 14 | var lastPlayed: Date? = null, 15 | val isDsiWareTitle: Boolean, 16 | val retroAchievementsHash: String, 17 | ) { 18 | 19 | fun hasSameFileAsRom(other: Rom): Boolean { 20 | return uri == other.uri 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/rom/config/RomConfig.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.rom.config 2 | 3 | import java.util.* 4 | 5 | data class RomConfig( 6 | var runtimeConsoleType: RuntimeConsoleType = RuntimeConsoleType.DEFAULT, 7 | var runtimeMicSource: RuntimeMicSource = RuntimeMicSource.DEFAULT, 8 | var layoutId: UUID? = null, 9 | val gbaSlotConfig: RomGbaSlotConfig = RomGbaSlotConfig.None, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/rom/config/RomGbaSlotConfig.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.rom.config 2 | 3 | import android.net.Uri 4 | 5 | sealed class RomGbaSlotConfig { 6 | data object None : RomGbaSlotConfig() 7 | data class GbaRom(val romPath: Uri?, val savePath: Uri?) : RomGbaSlotConfig() 8 | data object MemoryExpansion : RomGbaSlotConfig() 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/rom/config/RuntimeConsoleType.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.rom.config 2 | 3 | import me.magnum.melonds.domain.model.ConsoleType 4 | 5 | enum class RuntimeConsoleType(val targetConsoleType: ConsoleType?) : RuntimeEnum { 6 | DEFAULT(null), 7 | DS(ConsoleType.DS), 8 | DSi(ConsoleType.DSi); 9 | 10 | override fun getDefault() = DEFAULT 11 | override fun getValue() = targetConsoleType!! 12 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/rom/config/RuntimeEnum.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.rom.config 2 | 3 | interface RuntimeEnum { 4 | fun getDefault(): T 5 | fun getValue(): U 6 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/rom/config/RuntimeMicSource.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.rom.config 2 | 3 | import me.magnum.melonds.domain.model.MicSource 4 | 5 | enum class RuntimeMicSource(val micSource: MicSource?) : RuntimeEnum { 6 | DEFAULT(null), 7 | NONE(MicSource.NONE), 8 | BLOW(MicSource.BLOW), 9 | DEVICE(MicSource.DEVICE); 10 | 11 | override fun getDefault() = DEFAULT 12 | override fun getValue() = micSource!! 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/model/ui/Orientation.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.model.ui 2 | 3 | enum class Orientation { 4 | PORTRAIT, 5 | LANDSCAPE 6 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/repositories/BackgroundRepository.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.repositories 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import me.magnum.melonds.domain.model.Background 5 | import java.util.UUID 6 | 7 | interface BackgroundRepository { 8 | fun getBackgrounds(): Flow> 9 | suspend fun getBackground(id: UUID): Background? 10 | suspend fun addBackground(background: Background) 11 | suspend fun deleteBackground(background: Background) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/repositories/DSiWareMetadataRepository.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.repositories 2 | 3 | interface DSiWareMetadataRepository { 4 | suspend fun getDSiWareTitleMetadata(categoryId: UInt, titleId: UInt): ByteArray 5 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/repositories/LayoutsRepository.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.repositories 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import me.magnum.melonds.domain.model.layout.LayoutConfiguration 5 | import java.util.UUID 6 | 7 | interface LayoutsRepository { 8 | fun getLayouts(): Flow> 9 | suspend fun getLayout(id: UUID): LayoutConfiguration? 10 | suspend fun deleteLayout(layout: LayoutConfiguration) 11 | fun getGlobalLayoutPlaceholder(): LayoutConfiguration 12 | fun observeLayout(id: UUID): Flow 13 | suspend fun saveLayout(layout: LayoutConfiguration) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/repositories/RomsRepository.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.repositories 2 | 3 | import android.net.Uri 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.StateFlow 6 | import me.magnum.melonds.domain.model.rom.Rom 7 | import me.magnum.melonds.domain.model.rom.config.RomConfig 8 | import me.magnum.melonds.domain.model.RomScanningStatus 9 | import java.util.* 10 | 11 | interface RomsRepository { 12 | fun getRoms(): Flow> 13 | fun getRomScanningStatus(): StateFlow 14 | suspend fun getRomAtPath(path: String): Rom? 15 | suspend fun getRomAtUri(uri: Uri): Rom? 16 | 17 | fun updateRomConfig(rom: Rom, romConfig: RomConfig) 18 | fun setRomLastPlayed(rom: Rom, lastPlayed: Date) 19 | fun rescanRoms() 20 | fun invalidateRoms() 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/repositories/SaveStatesRepository.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.repositories 2 | 3 | import android.graphics.Bitmap 4 | import android.net.Uri 5 | import me.magnum.melonds.domain.model.rom.Rom 6 | import me.magnum.melonds.domain.model.SaveStateSlot 7 | 8 | interface SaveStatesRepository { 9 | fun getRomSaveStates(rom: Rom): List 10 | fun getRomQuickSaveStateSlot(rom: Rom): SaveStateSlot 11 | fun getRomSaveStateUri(rom: Rom, saveState: SaveStateSlot): Uri 12 | fun setRomSaveStateScreenshot(rom: Rom, saveState: SaveStateSlot, screenshot: Bitmap) 13 | fun deleteRomSaveState(rom: Rom, saveState: SaveStateSlot) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/repositories/UpdatesRepository.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.repositories 2 | 3 | import me.magnum.melonds.domain.model.appupdate.AppUpdate 4 | 5 | interface UpdatesRepository { 6 | suspend fun checkNewUpdate(): Result 7 | fun skipUpdate(update: AppUpdate) 8 | fun notifyUpdateDownloaded(update: AppUpdate) 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/services/DSiNandManager.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.services 2 | 3 | import android.net.Uri 4 | import me.magnum.melonds.domain.model.DSiWareTitle 5 | import me.magnum.melonds.domain.model.dsinand.ImportDSiWareTitleResult 6 | import me.magnum.melonds.domain.model.dsinand.OpenDSiNandResult 7 | import me.magnum.melonds.domain.model.dsinand.DSiWareTitleFileType 8 | 9 | interface DSiNandManager { 10 | suspend fun openNand(): OpenDSiNandResult 11 | suspend fun listTitles(): List 12 | suspend fun importTitle(titleUri: Uri): ImportDSiWareTitleResult 13 | suspend fun deleteTitle(title: DSiWareTitle) 14 | suspend fun importTitleFile(title: DSiWareTitle, fileType: DSiWareTitleFileType, fileUri: Uri): Boolean 15 | suspend fun exportTitleFile(title: DSiWareTitle, fileType: DSiWareTitleFileType, fileUri: Uri): Boolean 16 | fun closeNand() 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/domain/services/UpdateInstallManager.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.domain.services 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import me.magnum.melonds.domain.model.DownloadProgress 5 | import me.magnum.melonds.domain.model.appupdate.AppUpdate 6 | 7 | interface UpdateInstallManager { 8 | fun downloadAndInstallUpdate(update: AppUpdate): Flow 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/extensions/ContextExtensions.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.extensions 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.os.PowerManager 7 | import androidx.annotation.RequiresApi 8 | import androidx.core.content.ContextCompat 9 | 10 | @RequiresApi(33) 11 | fun Context.isNotificationPermissionGranted(): Boolean { 12 | return ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED 13 | } 14 | 15 | fun Context.isMicrophonePermissionGranted(): Boolean { 16 | return ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED 17 | } 18 | 19 | fun Context.isSustainedPerformanceModeAvailable(): Boolean { 20 | val powerManager = this.getSystemService(Context.POWER_SERVICE) as PowerManager 21 | return powerManager.isSustainedPerformanceModeSupported 22 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/extensions/DisposableExtensions.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.extensions 2 | 3 | import io.reactivex.disposables.CompositeDisposable 4 | import io.reactivex.disposables.Disposable 5 | 6 | fun Disposable.addTo(compositeDisposable: CompositeDisposable) { 7 | compositeDisposable.add(this) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/extensions/DocumentFileExtensions.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.extensions 2 | 3 | import androidx.documentfile.provider.DocumentFile 4 | 5 | val DocumentFile.nameWithoutExtension get() = name?.substringBeforeLast('.') 6 | 7 | val DocumentFile.extension get() = name?.substringAfterLast('.') -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/extensions/ListExtensions.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.extensions 2 | 3 | fun MutableList.removeFirst(predicate: (T) -> Boolean): T? { 4 | val itemIndex = indexOfFirst(predicate) 5 | return if (itemIndex >= 0) { 6 | removeAt(itemIndex) 7 | } else { 8 | null 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/extensions/PreferenceExtensions.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.extensions 2 | 3 | import androidx.preference.Preference 4 | import me.magnum.melonds.utils.CompositeOnPreferenceChangeListener 5 | 6 | fun Preference.addOnPreferenceChangeListener(listener: Preference.OnPreferenceChangeListener) { 7 | val currentListener = onPreferenceChangeListener 8 | if (currentListener is CompositeOnPreferenceChangeListener) { 9 | currentListener.addOnPreferenceChangeListener(listener) 10 | } else { 11 | onPreferenceChangeListener = CompositeOnPreferenceChangeListener().apply { 12 | if (currentListener != null) { 13 | addOnPreferenceChangeListener(currentListener) 14 | } 15 | addOnPreferenceChangeListener(listener) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/extensions/StringExtensions.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.extensions 2 | 3 | fun String.isBlank(): Boolean { 4 | // Replace null terminators with another whitespace character 5 | val replacedString = this.replace('\u0000', '\u0009') 6 | return (replacedString as CharSequence).isBlank() 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/extensions/WindowExtensions.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.extensions 2 | 3 | import android.view.Window 4 | import androidx.core.view.WindowCompat 5 | import androidx.core.view.WindowInsetsControllerCompat 6 | 7 | inline val Window.insetsControllerCompat: WindowInsetsControllerCompat? 8 | get() = WindowCompat.getInsetsController(this, decorView) 9 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/impl/ScreenUnitsConverter.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.impl 2 | 3 | import android.content.Context 4 | import android.util.DisplayMetrics 5 | 6 | class ScreenUnitsConverter(private val context: Context) { 7 | fun pixelsToDp(pixels: Float): Float { 8 | return pixels / (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT) 9 | } 10 | 11 | fun dpToPixels(dp: Float): Float { 12 | return dp * (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/impl/camera/CameraBuffers.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.impl.camera 2 | 3 | class CameraBuffers { 4 | private val cameraBuffers = Array(2) { 5 | // YUV 422 format. Byte structure: YUYV YUYV (4 bytes per 2 pixels) 6 | ByteArray(640 * 480 * 2) 7 | } 8 | private var activeBufferIndex = 0 9 | 10 | fun getFrontBuffer(): ByteArray { 11 | return cameraBuffers[activeBufferIndex] 12 | } 13 | 14 | fun getBackBuffer(): ByteArray { 15 | return cameraBuffers[(activeBufferIndex + 1) % cameraBuffers.size] 16 | } 17 | 18 | fun swapBuffers() { 19 | activeBufferIndex = (activeBufferIndex + 1) % cameraBuffers.size 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/impl/dtos/input/ControllerConfigurationDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.impl.dtos.input 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | import me.magnum.melonds.domain.model.ControllerConfiguration 6 | 7 | @Serializable 8 | data class ControllerConfigurationDto( 9 | @SerialName("inputMapper") val inputMapper: List, 10 | ) { 11 | 12 | companion object { 13 | fun fromControllerConfiguration(controllerConfiguration: ControllerConfiguration): ControllerConfigurationDto { 14 | val inputMapping = controllerConfiguration.inputMapper.map { InputConfigDto.fromInputConfig(it) } 15 | return ControllerConfigurationDto(inputMapping) 16 | } 17 | } 18 | 19 | fun toControllerConfiguration(): ControllerConfiguration { 20 | return ControllerConfiguration(inputMapper.map { it.toInputConfig() }) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/impl/dtos/layout/PointDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.impl.dtos.layout 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.magnum.melonds.domain.model.Point 5 | 6 | data class PointDto( 7 | @SerializedName("x") 8 | val x: Int, 9 | @SerializedName("y") 10 | val y: Int, 11 | ) { 12 | 13 | fun toModel(): Point { 14 | return Point(x, y) 15 | } 16 | 17 | companion object { 18 | fun fromModel(point: Point): PointDto { 19 | return PointDto(point.x, point.y) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/impl/dtos/layout/RectDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.impl.dtos.layout 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.magnum.melonds.domain.model.Rect 5 | 6 | data class RectDto( 7 | @SerializedName("x") 8 | val x: Int, 9 | @SerializedName("y") 10 | val y: Int, 11 | @SerializedName("width") 12 | val width: Int, 13 | @SerializedName("height") 14 | val height: Int 15 | ) { 16 | 17 | companion object { 18 | fun fromModel(rect: Rect): RectDto { 19 | return RectDto(rect.x, rect.y, rect.width, rect.height) 20 | } 21 | } 22 | 23 | fun toModel(): Rect { 24 | return Rect(x, y, width, height) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/impl/dtos/layout/ScreenFoldDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.impl.dtos.layout 2 | 3 | import me.magnum.melonds.domain.model.layout.ScreenFold 4 | import me.magnum.melonds.utils.enumValueOfIgnoreCase 5 | 6 | data class ScreenFoldDto( 7 | val orientation: String, 8 | val type: String, 9 | val foldBounds: RectDto, 10 | ) { 11 | 12 | fun toModel(): ScreenFold { 13 | return ScreenFold( 14 | enumValueOfIgnoreCase(orientation), 15 | enumValueOfIgnoreCase(type), 16 | foldBounds.toModel(), 17 | ) 18 | } 19 | 20 | companion object { 21 | fun fromModel(fold: ScreenFold): ScreenFoldDto { 22 | return ScreenFoldDto( 23 | fold.orientation.name, 24 | fold.type.name, 25 | RectDto.fromModel(fold.foldBounds), 26 | ) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/impl/emulator/LifecycleOwnerProvider.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.impl.emulator 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleEventObserver 5 | import androidx.lifecycle.LifecycleOwner 6 | 7 | class LifecycleOwnerProvider { 8 | 9 | private var currentLifecycleOwner: LifecycleOwner? = null 10 | 11 | fun getCurrentLifecycleOwner(): LifecycleOwner? { 12 | return currentLifecycleOwner 13 | } 14 | 15 | fun setCurrentLifecycleOwner(lifecycleOwner: LifecycleOwner) { 16 | currentLifecycleOwner = lifecycleOwner 17 | lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver { 18 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { 19 | if (event == Lifecycle.Event.ON_DESTROY) { 20 | currentLifecycleOwner = null 21 | } 22 | } 23 | }) 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/impl/emulator/SramLoadException.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.impl.emulator 2 | 3 | class SramLoadException(message: String) : Exception(message) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/impl/image/BitmapLoader.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.impl.image 2 | 3 | import android.graphics.Bitmap 4 | import android.net.Uri 5 | 6 | interface BitmapLoader { 7 | fun loadAsBitmap(imageUri: Uri): Bitmap? 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/initializer/CoilInitializer.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.initializer 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import coil.Coil 6 | import coil.ImageLoader 7 | import me.magnum.melonds.di.entrypoint.InitializerEntryPoint 8 | import javax.inject.Inject 9 | 10 | class CoilInitializer : Initializer { 11 | 12 | @Inject 13 | lateinit var imageLoader: ImageLoader 14 | 15 | override fun create(context: Context) { 16 | InitializerEntryPoint.resolve(context).inject(this) 17 | Coil.setImageLoader(imageLoader) 18 | } 19 | 20 | override fun dependencies(): MutableList>> = mutableListOf() 21 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/Migration.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations 2 | 3 | interface Migration { 4 | val from: Int 5 | val to: Int 6 | 7 | fun migrate() 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/Migration14to15.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations 2 | 3 | import me.magnum.melonds.impl.RomIconProvider 4 | 5 | class Migration14to15(private val romIconProvider: RomIconProvider) : Migration { 6 | override val from = 14 7 | override val to = 15 8 | 9 | override fun migrate() { 10 | // Delete cached icons since the icon generation method has changed 11 | romIconProvider.clearIconCache() 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/Migration16to17.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations 2 | 3 | import me.magnum.melonds.domain.repositories.RomsRepository 4 | 5 | class Migration16to17(private val romsRepository: RomsRepository) : Migration { 6 | override val from = 16 7 | override val to = 17 8 | 9 | override fun migrate() { 10 | romsRepository.invalidateRoms() 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/Migration6to7.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations 2 | 3 | import android.content.SharedPreferences 4 | import androidx.core.content.edit 5 | 6 | class Migration6to7(private val sharedPreferences: SharedPreferences) : Migration { 7 | override val from: Int 8 | get() = 6 9 | override val to: Int 10 | get() = 7 11 | 12 | override fun migrate() { 13 | // Dir preferences now have a different format 14 | sharedPreferences.edit { 15 | putStringSet("bios_dir", emptySet()) 16 | putStringSet("rom_search_dirs", emptySet()) 17 | putStringSet("sram_dir", emptySet()) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/Migration7to8.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations 2 | 3 | import android.content.Context 4 | 5 | class Migration7to8(private val context: Context) : Migration { 6 | override val from: Int 7 | get() = 7 8 | override val to: Int 9 | get() = 8 10 | 11 | override fun migrate() { 12 | // Delete cached icons since the directory is now different 13 | context.externalCacheDir?.let { cacheDir -> 14 | cacheDir.listFiles()?.forEach { it.delete() } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/Rom21.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy 2 | 3 | import android.net.Uri 4 | import java.util.* 5 | 6 | /** 7 | * ROM model used until app version 21. 8 | */ 9 | data class Rom21( 10 | val name: String, 11 | val uri: Uri, 12 | val parentTreeUri: Uri, 13 | var config: RomConfig1, 14 | var lastPlayed: Date? = null, 15 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/Rom22.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy 2 | 3 | import android.net.Uri 4 | import com.google.gson.annotations.SerializedName 5 | import java.util.* 6 | 7 | /** 8 | * ROM model used from app version 22. 9 | */ 10 | data class Rom22( 11 | @SerializedName("a") 12 | val name: String, 13 | @SerializedName("b") 14 | val fileName: String, 15 | @SerializedName("c") 16 | val uri: Uri, 17 | @SerializedName("d") 18 | val parentTreeUri: Uri, 19 | @SerializedName("e") 20 | val config: RomConfig1, 21 | @SerializedName("f") 22 | val lastPlayed: Date? = null, 23 | @SerializedName("g") 24 | val isDsiWareTitle: Boolean, 25 | ) 26 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/RomConfig1.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy 2 | 3 | import android.net.Uri 4 | import com.google.gson.annotations.SerializedName 5 | import me.magnum.melonds.domain.model.rom.config.RuntimeConsoleType 6 | import me.magnum.melonds.domain.model.rom.config.RuntimeMicSource 7 | import java.util.* 8 | 9 | data class RomConfig1( 10 | @SerializedName("a") 11 | val runtimeConsoleType: RuntimeConsoleType, 12 | @SerializedName("b") 13 | val runtimeMicSource: RuntimeMicSource, 14 | @SerializedName("c") 15 | val layoutId: UUID?, 16 | @SerializedName("d") 17 | val loadGbaCart: Boolean, 18 | @SerializedName("e") 19 | val gbaCartPath: Uri?, 20 | @SerializedName("f") 21 | val gbaSavePath: Uri?, 22 | ) 23 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/RomConfigDto25.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.magnum.melonds.domain.model.rom.config.RuntimeConsoleType 5 | import me.magnum.melonds.domain.model.rom.config.RuntimeMicSource 6 | 7 | data class RomConfigDto25( 8 | @SerializedName("runtimeConsoleType") 9 | val runtimeConsoleType: RuntimeConsoleType, 10 | @SerializedName("runtimeMicSource") 11 | val runtimeMicSource: RuntimeMicSource, 12 | @SerializedName("layoutId") 13 | val layoutId: String?, 14 | @SerializedName("loadGbaCart") 15 | val loadGbaCart: Boolean, 16 | @SerializedName("gbaCartPath") 17 | val gbaCartPath: String?, 18 | @SerializedName("gbaSavePath") 19 | val gbaSavePath: String?, 20 | ) 21 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/RomConfigDto31.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.magnum.melonds.domain.model.rom.config.RuntimeConsoleType 5 | import me.magnum.melonds.domain.model.rom.config.RuntimeMicSource 6 | 7 | data class RomConfigDto31( 8 | @SerializedName("runtimeConsoleType") 9 | val runtimeConsoleType: RuntimeConsoleType, 10 | @SerializedName("runtimeMicSource") 11 | val runtimeMicSource: RuntimeMicSource, 12 | @SerializedName("layoutId") 13 | val layoutId: String?, 14 | @SerializedName("gbaSlotConfig") 15 | val gbaSlotConfig: RomGbaSlotConfigDto31, 16 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/RomDto25.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import java.util.* 5 | 6 | /** 7 | * ROM DTO used from app version 25. 8 | */ 9 | data class RomDto25( 10 | @SerializedName("name") 11 | val name: String, 12 | @SerializedName("developerName") 13 | val developerName: String, 14 | @SerializedName("fileName") 15 | val fileName: String, 16 | @SerializedName("uri") 17 | val uri: String, 18 | @SerializedName("parentTreeUri") 19 | val parentTreeUri: String, 20 | @SerializedName("config") 21 | var config: RomConfigDto25, 22 | @SerializedName("lastPlayed") 23 | var lastPlayed: Date? = null, 24 | @SerializedName("isDsiWareTitle") 25 | val isDsiWareTitle: Boolean, 26 | @SerializedName("retroAchievementsHash") 27 | val retroAchievementsHash: String, 28 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/RomDto31.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import java.util.Date 5 | 6 | /** 7 | * ROM DTO used from app version 27. 8 | */ 9 | data class RomDto31( 10 | @SerializedName("name") 11 | val name: String, 12 | @SerializedName("developerName") 13 | val developerName: String, 14 | @SerializedName("fileName") 15 | val fileName: String, 16 | @SerializedName("uri") 17 | val uri: String, 18 | @SerializedName("parentTreeUri") 19 | val parentTreeUri: String, 20 | @SerializedName("config") 21 | var config: RomConfigDto31, 22 | @SerializedName("lastPlayed") 23 | var lastPlayed: Date? = null, 24 | @SerializedName("isDsiWareTitle") 25 | val isDsiWareTitle: Boolean, 26 | @SerializedName("retroAchievementsHash") 27 | val retroAchievementsHash: String, 28 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/RomGbaSlotConfigDto31.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | class RomGbaSlotConfigDto31( 6 | @SerializedName("type") 7 | val type: Type, 8 | @SerializedName("gbaRomPath") 9 | val gbaRomPath: String?, 10 | @SerializedName("gbaSavePath") 11 | val gbaSavePath: String?, 12 | ) { 13 | 14 | enum class Type { 15 | None, GbaRom, MemoryExpansion 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/input/ControllerConfigurationDto33.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy.input 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ControllerConfigurationDto33( 8 | @SerialName("a") val inputMapper: List, 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/input/InputConfigDto33.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy.input 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | import me.magnum.melonds.domain.model.Input 6 | 7 | @Serializable 8 | data class InputConfigDto33( 9 | @SerialName("a") val input: Input, 10 | @SerialName("b") val key: Int, 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/layout/LayoutConfiguration25.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy.layout 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * LayoutConfiguration model used until app version 25. 7 | */ 8 | data class LayoutConfiguration25( 9 | @SerializedName("a") 10 | val id: String?, 11 | @SerializedName("b") 12 | val name: String?, 13 | @SerializedName("c") 14 | val type: String, 15 | @SerializedName("d") 16 | val orientation: String, 17 | @SerializedName("e") 18 | val useCustomOpacity: Boolean, 19 | @SerializedName("f") 20 | val opacity: Int, 21 | @SerializedName("g") 22 | val portraitLayout: UILayout25, 23 | @SerializedName("h") 24 | val landscapeLayout: UILayout25, 25 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/layout/LayoutConfigurationDto31.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy.layout 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.magnum.melonds.impl.dtos.layout.UILayoutDto 5 | 6 | data class LayoutConfigurationDto31( 7 | @SerializedName("id") 8 | val id: String?, 9 | @SerializedName("name") 10 | val name: String?, 11 | @SerializedName("type") 12 | val type: String, 13 | @SerializedName("orientation") 14 | val orientation: String, 15 | @SerializedName("useCustomOpacity") 16 | val useCustomOpacity: Boolean, 17 | @SerializedName("opacity") 18 | val opacity: Int, 19 | @SerializedName("portraitLayout") 20 | val portraitLayout: UILayoutDto, 21 | @SerializedName("landscapeLayout") 22 | val landscapeLayout: UILayoutDto 23 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/layout/PositionedLayoutComponent25.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy.layout 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * PositionedLayoutComponent model used until app version 25. 7 | */ 8 | data class PositionedLayoutComponent25( 9 | @SerializedName("a") 10 | val rect: Rect25, 11 | @SerializedName("b") 12 | val component: String, 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/layout/Rect25.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy.layout 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * Rect model used until app version 25. 7 | */ 8 | data class Rect25( 9 | @SerializedName("a") 10 | val x: Int, 11 | @SerializedName("b") 12 | val y: Int, 13 | @SerializedName("c") 14 | val width: Int, 15 | @SerializedName("d") 16 | val height: Int, 17 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/migrations/legacy/layout/UILayout25.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.migrations.legacy.layout 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * UILayout model used until app version 25. 7 | */ 8 | data class UILayout25( 9 | @SerializedName("a") 10 | val backgroundId: String?, 11 | @SerializedName("b") 12 | val backgroundMode: String, 13 | @SerializedName("c") 14 | val components: List, 15 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/parcelables/RomInfoParcelable.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.parcelables 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | import me.magnum.melonds.domain.model.RomInfo 6 | 7 | @Parcelize 8 | class RomInfoParcelable( 9 | private val gameCode: String, 10 | private val headerChecksum: Int, 11 | private val gameTitle: String, 12 | private val gameName: String, 13 | ) : Parcelable { 14 | companion object { 15 | fun fromRomInfo(romInfo: RomInfo): RomInfoParcelable { 16 | return RomInfoParcelable(romInfo.gameCode, romInfo.headerChecksum.toInt(), romInfo.gameTitle, romInfo.gameName) 17 | } 18 | } 19 | 20 | fun toRomInfo(): RomInfo { 21 | return RomInfo(gameCode, headerChecksum.toUInt(), gameTitle, gameName) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/parcelables/cheat/CheatFolderParcelable.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.parcelables.cheat 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | import me.magnum.melonds.domain.model.CheatFolder 6 | 7 | @Parcelize 8 | class CheatFolderParcelable( 9 | val id: Long?, 10 | val name: String, 11 | val cheats: List, 12 | ) : Parcelable { 13 | 14 | fun toCheatFolder(): CheatFolder { 15 | return CheatFolder( 16 | id = id, 17 | name = name, 18 | cheats = cheats.map { it.toCheat() } 19 | ) 20 | } 21 | 22 | companion object { 23 | fun fromCheatFolder(cheatFolder: CheatFolder): CheatFolderParcelable { 24 | return CheatFolderParcelable( 25 | id = cheatFolder.id, 26 | name = cheatFolder.name, 27 | cheats = cheatFolder.cheats.map { CheatParcelable.fromCheat(it) } 28 | ) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/Theme.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui 2 | 3 | import androidx.appcompat.app.AppCompatDelegate 4 | 5 | enum class Theme(val nightMode: Int) { 6 | LIGHT(AppCompatDelegate.MODE_NIGHT_NO), 7 | DARK(AppCompatDelegate.MODE_NIGHT_YES), 8 | SYSTEM(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/cheats/CheatsNavigation.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.cheats 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | sealed class CheatsNavigation { 6 | @Serializable 7 | data object Loading : CheatsNavigation() 8 | @Serializable 9 | data object GameList : CheatsNavigation() 10 | @Serializable 11 | data class GameFolders(val gameName: String?) : CheatsNavigation() 12 | @Serializable 13 | data class FolderCheats(val folderName: String?) : CheatsNavigation() 14 | @Serializable 15 | data object EnabledCheats : CheatsNavigation() 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/cheats/model/CheatSubmissionForm.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.cheats.model 2 | 3 | data class CheatSubmissionForm( 4 | val name: String, 5 | val description: String, 6 | val code: String, 7 | ) { 8 | 9 | fun isValid(): Boolean { 10 | return name.isNotBlank() && code.isNotBlank() 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/cheats/model/CheatsScreenUiState.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.cheats.model 2 | 3 | sealed class CheatsScreenUiState { 4 | class Loading : CheatsScreenUiState() 5 | data class Ready(val data: T) : CheatsScreenUiState() 6 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/cheats/model/DeletedCheat.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.cheats.model 2 | 3 | import me.magnum.melonds.domain.model.Cheat 4 | import me.magnum.melonds.domain.model.CheatFolder 5 | 6 | data class DeletedCheat(val cheat: Cheat, val folder: CheatFolder) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/cheats/model/OpenScreenEvent.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.cheats.model 2 | 3 | data class OpenScreenEvent(val newTitle: String?) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/cheats/ui/LoadingScreen.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.cheats.ui 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.material.CircularProgressIndicator 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Alignment 7 | import androidx.compose.ui.Modifier 8 | 9 | @Composable 10 | fun LoadingScreen(modifier: Modifier) { 11 | Box(modifier) { 12 | CircularProgressIndicator(Modifier.align(Alignment.Center)) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/common/LayoutComponentViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.common 2 | 3 | import android.content.Context 4 | import android.view.View 5 | 6 | abstract class LayoutComponentViewBuilder { 7 | abstract fun build(context: Context): View 8 | abstract fun getAspectRatio(): Float 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/common/LayoutComponentViewBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.common 2 | 3 | import me.magnum.melonds.domain.model.layout.LayoutComponent 4 | 5 | interface LayoutComponentViewBuilderFactory { 6 | fun getLayoutComponentViewBuilder(layoutComponent: LayoutComponent): LayoutComponentViewBuilder 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/common/LoadingDialog.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.common 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.graphics.Color 6 | import android.graphics.drawable.ColorDrawable 7 | import android.os.Bundle 8 | import me.magnum.melonds.R 9 | 10 | class LoadingDialog(context: Context) : Dialog(context) { 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.dialog_loading) 15 | setCancelable(false) 16 | setCanceledOnTouchOutside(false) 17 | window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/common/MelonPreviewSet.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.common 2 | 3 | import android.content.res.Configuration.UI_MODE_NIGHT_YES 4 | import androidx.compose.ui.tooling.preview.Preview 5 | 6 | @Preview(name = "Light", showBackground = true) 7 | @Preview(name = "Dark", showBackground = true, uiMode = UI_MODE_NIGHT_YES) 8 | annotation class MelonPreviewSet -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/common/componentbuilders/BottomScreenLayoutComponentViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.common.componentbuilders 2 | 3 | import android.content.Context 4 | import androidx.core.content.ContextCompat 5 | import me.magnum.melonds.R 6 | 7 | class BottomScreenLayoutComponentViewBuilder : ScreenLayoutComponentViewBuilder() { 8 | override fun getBackgroundDrawable(context: Context) = ContextCompat.getDrawable(context, R.drawable.background_bottom_screen) 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/common/componentbuilders/ButtonsLayoutComponentViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.common.componentbuilders 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.widget.ImageView 6 | import me.magnum.melonds.R 7 | import me.magnum.melonds.ui.common.LayoutComponentViewBuilder 8 | 9 | class ButtonsLayoutComponentViewBuilder : LayoutComponentViewBuilder() { 10 | override fun build(context: Context): View { 11 | return ImageView(context).apply { 12 | setImageResource(R.drawable.buttons) 13 | } 14 | } 15 | 16 | override fun getAspectRatio() = 1f 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/common/componentbuilders/DpadLayoutComponentViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.common.componentbuilders 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.widget.ImageView 6 | import me.magnum.melonds.R 7 | import me.magnum.melonds.ui.common.LayoutComponentViewBuilder 8 | 9 | class DpadLayoutComponentViewBuilder : LayoutComponentViewBuilder() { 10 | override fun build(context: Context): View { 11 | return ImageView(context).apply { 12 | setImageResource(R.drawable.keypad) 13 | } 14 | } 15 | 16 | override fun getAspectRatio() = 1f 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/common/componentbuilders/EditorBackgroundLayoutComponentViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.common.componentbuilders 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import androidx.core.content.ContextCompat 6 | import me.magnum.melonds.R 7 | import me.magnum.melonds.ui.common.LayoutComponentViewBuilder 8 | 9 | class EditorBackgroundLayoutComponentViewBuilder(private val baseBuilder: LayoutComponentViewBuilder) : LayoutComponentViewBuilder() { 10 | override fun build(context: Context): View { 11 | return baseBuilder.build(context).apply { 12 | background = ContextCompat.getDrawable(context, R.drawable.background_uiview) 13 | } 14 | } 15 | 16 | override fun getAspectRatio() = baseBuilder.getAspectRatio() 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/common/componentbuilders/ScreenLayoutComponentViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.common.componentbuilders 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import android.view.View 6 | import me.magnum.melonds.ui.common.LayoutComponentViewBuilder 7 | 8 | abstract class ScreenLayoutComponentViewBuilder : LayoutComponentViewBuilder() { 9 | override fun build(context: Context): View { 10 | return View(context).apply { 11 | background = getBackgroundDrawable(context) 12 | } 13 | } 14 | 15 | override fun getAspectRatio() = 256f / 192 16 | 17 | protected abstract fun getBackgroundDrawable(context: Context): Drawable? 18 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/common/componentbuilders/TopScreenLayoutComponentViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.common.componentbuilders 2 | 3 | import android.content.Context 4 | import androidx.core.content.ContextCompat 5 | import me.magnum.melonds.R 6 | 7 | class TopScreenLayoutComponentViewBuilder : ScreenLayoutComponentViewBuilder() { 8 | override fun getBackgroundDrawable(context: Context) = ContextCompat.getDrawable(context, R.drawable.background_top_screen) 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/dsiwaremanager/model/DSiWareItemDropdownMenu.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.dsiwaremanager.model 2 | 3 | enum class DSiWareItemDropdownMenu { 4 | NONE, 5 | MAIN, 6 | IMPORT, 7 | EXPORT, 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/dsiwaremanager/model/DSiWareManagerUiState.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.dsiwaremanager.model 2 | 3 | import me.magnum.melonds.domain.model.ConfigurationDirResult 4 | import me.magnum.melonds.domain.model.DSiWareTitle 5 | 6 | sealed class DSiWareManagerUiState { 7 | data class DSiSetupInvalid(val status: ConfigurationDirResult.Status) : DSiWareManagerUiState() 8 | object Loading : DSiWareManagerUiState() 9 | data class Ready(val titles: List) : DSiWareManagerUiState() 10 | object Error : DSiWareManagerUiState() 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/dsiwaremanager/model/DSiWareMangerRomListUiState.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.dsiwaremanager.model 2 | 3 | import me.magnum.melonds.domain.model.rom.Rom 4 | 5 | sealed class DSiWareMangerRomListUiState { 6 | object Loading : DSiWareMangerRomListUiState() 7 | class Loaded(val roms: List) : DSiWareMangerRomListUiState() 8 | object Empty : DSiWareMangerRomListUiState() 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/dsiwaremanager/model/ImportExportDSiWareTitleFileEvent.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.dsiwaremanager.model 2 | 3 | sealed class ImportExportDSiWareTitleFileEvent { 4 | data class ImportSuccess(val fileName: String) : ImportExportDSiWareTitleFileEvent() 5 | data object ImportError : ImportExportDSiWareTitleFileEvent() 6 | data class ExportSuccess(val fileName: String) : ImportExportDSiWareTitleFileEvent() 7 | data object ExportError : ImportExportDSiWareTitleFileEvent() 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/EmulatorFrameRenderedListener.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator 2 | 3 | fun interface EmulatorFrameRenderedListener { 4 | fun onFrameRendered(textureId: Int) 5 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/PauseMenuOption.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator 2 | 3 | interface PauseMenuOption { 4 | val textResource: Int 5 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/component/EmulatorOverlayTracker.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.component 2 | 3 | import me.magnum.melonds.ui.emulator.model.EmulatorOverlay 4 | 5 | class EmulatorOverlayTracker( 6 | private val onOverlaysCleared: () -> Unit, 7 | private val onOverlaysPresent: () -> Unit, 8 | ) { 9 | 10 | private val activeOverlays = mutableListOf() 11 | 12 | fun addActiveOverlay(overlay: EmulatorOverlay) { 13 | activeOverlays.add(overlay) 14 | if (activeOverlays.size == 1) { 15 | onOverlaysPresent() 16 | } 17 | } 18 | 19 | fun removeActiveOverlay(overlay: EmulatorOverlay) { 20 | activeOverlays.remove(overlay) 21 | if (activeOverlays.isEmpty()) { 22 | onOverlaysCleared() 23 | } 24 | } 25 | 26 | fun hasActiveOverlays(): Boolean { 27 | return activeOverlays.isNotEmpty() 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/exceptions/RomLoadException.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.exceptions 2 | 3 | class RomLoadException(message: String) : Exception(message) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/exceptions/SaveSlotLoadException.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.exceptions 2 | 3 | class SaveSlotLoadException(message: String) : Exception(message) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/firmware/FirmwarePauseMenuOption.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.firmware 2 | 3 | import me.magnum.melonds.R 4 | import me.magnum.melonds.ui.emulator.PauseMenuOption 5 | 6 | enum class FirmwarePauseMenuOption(override val textResource: Int) : PauseMenuOption { 7 | SETTINGS(R.string.settings), 8 | RESET(R.string.reset), 9 | EXIT(R.string.exit) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/input/BaseInputHandler.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.input 2 | 3 | import android.view.View.OnTouchListener 4 | 5 | abstract class BaseInputHandler(protected var inputListener: IInputListener) : OnTouchListener -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/input/ButtonsInputHandler.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.input 2 | 3 | import me.magnum.melonds.common.vibration.TouchVibrator 4 | import me.magnum.melonds.domain.model.Input 5 | 6 | class ButtonsInputHandler(inputListener: IInputListener, enableHapticFeedback: Boolean, touchVibrator: TouchVibrator) : MultiButtonInputHandler(inputListener, enableHapticFeedback, touchVibrator) { 7 | override fun getTopInput() = Input.X 8 | override fun getLeftInput() = Input.Y 9 | override fun getBottomInput() = Input.B 10 | override fun getRightInput() = Input.A 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/input/DpadInputHandler.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.input 2 | 3 | import me.magnum.melonds.common.vibration.TouchVibrator 4 | import me.magnum.melonds.domain.model.Input 5 | 6 | class DpadInputHandler(inputListener: IInputListener, enableHapticFeedback: Boolean, touchVibrator: TouchVibrator) : MultiButtonInputHandler(inputListener, enableHapticFeedback, touchVibrator) { 7 | override fun getTopInput() = Input.UP 8 | override fun getLeftInput() = Input.LEFT 9 | override fun getBottomInput() = Input.DOWN 10 | override fun getRightInput() = Input.RIGHT 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/input/IInputListener.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.input 2 | 3 | import me.magnum.melonds.domain.model.Input 4 | import me.magnum.melonds.domain.model.Point 5 | 6 | interface IInputListener { 7 | fun onKeyPress(key: Input) 8 | fun onKeyReleased(key: Input) 9 | fun onTouch(point: Point) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/input/INativeInputListener.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.input 2 | 3 | import android.view.KeyEvent 4 | import android.view.MotionEvent 5 | 6 | interface INativeInputListener { 7 | fun onKeyEvent(keyEvent: KeyEvent): Boolean 8 | fun onMotionEvent(motionEvent: MotionEvent): Boolean 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/input/componentbuilder/RuntimeScreenLayoutComponentViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.input.componentbuilder 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import me.magnum.melonds.ui.common.componentbuilders.ScreenLayoutComponentViewBuilder 6 | 7 | class RuntimeScreenLayoutComponentViewBuilder : ScreenLayoutComponentViewBuilder() { 8 | override fun getBackgroundDrawable(context: Context): Drawable? = null 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/input/view/ToggleableImageView.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.input.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import androidx.appcompat.widget.AppCompatImageView 6 | 7 | class ToggleableImageView @JvmOverloads constructor( 8 | context: Context, attrs: AttributeSet? = null 9 | ) : AppCompatImageView(context, attrs) { 10 | 11 | var enabledDrawable: Int? = null 12 | var disabledDrawable: Int? = null 13 | 14 | fun setToggleState(enabled: Boolean) { 15 | val drawable = if (enabled) enabledDrawable else disabledDrawable 16 | if (drawable != null) { 17 | setImageResource(drawable) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/model/EmulatorOverlay.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.model 2 | 3 | enum class EmulatorOverlay { 4 | PAUSE_MENU, 5 | REWIND_WINDOW, 6 | SAVE_STATES_DIALOG, 7 | ROM_LOAD_ERROR_DIALOG, 8 | FIRMWARE_LOAD_ERROR_DIALOG, 9 | ROM_NOT_FOUND_DIALOG, 10 | SWITCH_NEW_ROM_DIALOG, 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/model/EmulatorState.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.model 2 | 3 | import me.magnum.melonds.MelonEmulator 4 | import me.magnum.melonds.domain.model.ConsoleType 5 | import me.magnum.melonds.domain.model.rom.Rom 6 | 7 | sealed class EmulatorState { 8 | data object Uninitialized : EmulatorState() 9 | data object LoadingRom : EmulatorState() 10 | data object LoadingFirmware : EmulatorState() 11 | data class RunningRom(val rom: Rom) : EmulatorState() 12 | data class RunningFirmware(val console: ConsoleType) : EmulatorState() 13 | data object RomLoadError : EmulatorState() 14 | data class FirmwareLoadError(val reason: MelonEmulator.FirmwareLoadResult) : EmulatorState() 15 | data class RomNotFoundError(val romPath: String) : EmulatorState() 16 | 17 | fun isRunning() = this is RunningRom || this is RunningFirmware 18 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/model/PauseMenu.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.model 2 | 3 | import me.magnum.melonds.ui.emulator.PauseMenuOption 4 | 5 | data class PauseMenu(val options: List) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/model/PopupEvent.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.model 2 | 3 | import me.magnum.rcheevosapi.model.RAAchievement 4 | 5 | sealed class PopupEvent { 6 | data class AchievementUnlockPopup(val achievement: RAAchievement) : PopupEvent() 7 | data class RAIntegrationPopup(val event: RAIntegrationEvent) : PopupEvent() 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/model/RAIntegrationEvent.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.model 2 | 3 | import java.net.URL 4 | 5 | sealed class RAIntegrationEvent(open val icon: URL?) { 6 | data class Loaded(override val icon: URL?, val unlockedAchievements: Int, val totalAchievements: Int) : RAIntegrationEvent(icon) 7 | data class Failed(override val icon: URL?) : RAIntegrationEvent(icon) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/model/RuntimeInputLayoutConfiguration.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.model 2 | 3 | import me.magnum.melonds.domain.model.layout.LayoutConfiguration 4 | import me.magnum.melonds.domain.model.layout.UILayout 5 | 6 | data class RuntimeInputLayoutConfiguration( 7 | val showSoftInput: Boolean, 8 | val softInputOpacity: Int, 9 | val isHapticFeedbackEnabled: Boolean, 10 | val layoutOrientation: LayoutConfiguration.LayoutOrientation, 11 | val layout: UILayout, 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/model/RuntimeRendererConfiguration.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.model 2 | 3 | import me.magnum.melonds.domain.model.VideoFiltering 4 | 5 | data class RuntimeRendererConfiguration( 6 | val videoFiltering: VideoFiltering, 7 | val resolutionScaling: Int, 8 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/model/ToastEvent.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.model 2 | 3 | sealed class ToastEvent { 4 | data object GbaLoadFailed : ToastEvent() 5 | data object RewindNotEnabled : ToastEvent() 6 | data object RewindNotAvailableWhileRAHardcoreModeEnabled : ToastEvent() 7 | data object StateSaveFailed : ToastEvent() 8 | data object StateLoadFailed : ToastEvent() 9 | data object StateStateDoesNotExist : ToastEvent() 10 | data object QuickSaveSuccessful : ToastEvent() 11 | data object QuickLoadSuccessful : ToastEvent() 12 | data object CannotUseSaveStatesWhenRAHardcoreIsEnabled : ToastEvent() 13 | data object CannotSaveStateWhenRunningFirmware : ToastEvent() 14 | data object CannotLoadStateWhenRunningFirmware : ToastEvent() 15 | data object CannotSwitchRetroAchievementsMode : ToastEvent() 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/rewind/model/RewindSaveState.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.rewind.model 2 | 3 | import android.graphics.Bitmap 4 | import me.magnum.melonds.utils.DsScreenshotConverter 5 | import java.nio.ByteBuffer 6 | 7 | class RewindSaveState( 8 | val buffer: ByteBuffer, 9 | val screenshotBuffer: ByteBuffer, 10 | val frame: Int, 11 | ) { 12 | val screenshot: Bitmap get() = DsScreenshotConverter.fromByteBufferToBitmap(screenshotBuffer) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/rewind/model/RewindWindow.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.rewind.model 2 | 3 | import java.time.Duration 4 | import java.util.* 5 | 6 | class RewindWindow( 7 | val currentEmulationFrame: Int, 8 | val rewindStates: ArrayList 9 | ) { 10 | 11 | companion object { 12 | private const val FRAMES_PER_SECOND = 60 13 | } 14 | 15 | fun getDeltaFromEmulationTimeToRewindState(state: RewindSaveState): Duration { 16 | val elapsedFrames = currentEmulationFrame - state.frame 17 | val elapsedMillis = elapsedFrames.toFloat() / FRAMES_PER_SECOND * 1000 18 | return Duration.ofMillis(elapsedMillis.toLong()) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/emulator/rom/RomPauseMenuOption.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.emulator.rom 2 | 3 | import me.magnum.melonds.R 4 | import me.magnum.melonds.ui.emulator.PauseMenuOption 5 | 6 | enum class RomPauseMenuOption(override val textResource: Int) : PauseMenuOption { 7 | SETTINGS(R.string.settings), 8 | SAVE_STATE(R.string.save_state), 9 | LOAD_STATE(R.string.load_state), 10 | REWIND(R.string.rewind), 11 | CHEATS(R.string.cheats), 12 | VIEW_ACHIEVEMENTS(R.string.achievements), 13 | RESET(R.string.reset), 14 | EXIT(R.string.exit) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/layouteditor/model/CurrentLayoutState.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.layouteditor.model 2 | 3 | import me.magnum.melonds.domain.model.layout.LayoutConfiguration 4 | import me.magnum.melonds.domain.model.layout.UILayout 5 | 6 | data class CurrentLayoutState( 7 | val layout: UILayout, 8 | val orientation: LayoutConfiguration.LayoutOrientation, 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/layouteditor/model/LayoutBackgroundProperties.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.layouteditor.model 2 | 3 | import me.magnum.melonds.domain.model.layout.BackgroundMode 4 | import java.util.UUID 5 | 6 | data class LayoutBackgroundProperties( 7 | val backgroundId: UUID?, 8 | val backgroundMode: BackgroundMode, 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/layouts/fragments/LayoutListFragment.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.layouts.fragments 2 | 3 | import androidx.fragment.app.activityViewModels 4 | import dagger.hilt.android.AndroidEntryPoint 5 | import me.magnum.melonds.domain.model.layout.LayoutConfiguration 6 | import me.magnum.melonds.ui.layouts.BaseLayoutsFragment 7 | import me.magnum.melonds.ui.layouts.BaseLayoutsViewModel 8 | import me.magnum.melonds.ui.layouts.LayoutsViewModel 9 | import java.util.* 10 | 11 | @AndroidEntryPoint 12 | class LayoutListFragment : BaseLayoutsFragment() { 13 | override fun getFragmentViewModel(): BaseLayoutsViewModel { 14 | return activityViewModels().value 15 | } 16 | 17 | override fun getFallbackLayoutId(): UUID { 18 | return LayoutConfiguration.DEFAULT_ID 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/layouts/fragments/LayoutSelectorFragment.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.layouts.fragments 2 | 3 | import androidx.fragment.app.activityViewModels 4 | import dagger.hilt.android.AndroidEntryPoint 5 | import me.magnum.melonds.ui.layouts.BaseLayoutsFragment 6 | import me.magnum.melonds.ui.layouts.BaseLayoutsViewModel 7 | import me.magnum.melonds.ui.layouts.LayoutSelectorViewModel 8 | import java.util.* 9 | 10 | @AndroidEntryPoint 11 | class LayoutSelectorFragment : BaseLayoutsFragment() { 12 | override fun getFragmentViewModel(): BaseLayoutsViewModel { 13 | return activityViewModels().value 14 | } 15 | 16 | override fun getFallbackLayoutId(): UUID? { 17 | return null 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/romdetails/model/RomAchievementsSummary.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.romdetails.model 2 | 3 | data class RomAchievementsSummary( 4 | val forHardcoreMode: Boolean, 5 | val totalAchievements: Int, 6 | val completedAchievements: Int, 7 | val totalPoints: Int, 8 | ) { 9 | 10 | val completedPercentage get() = (completedAchievements / totalAchievements.toFloat() * 100).toInt() 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/romdetails/model/RomConfigUiModel.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.romdetails.model 2 | 3 | import me.magnum.melonds.domain.model.rom.config.RuntimeConsoleType 4 | import me.magnum.melonds.domain.model.rom.config.RuntimeMicSource 5 | import java.util.UUID 6 | 7 | data class RomConfigUiModel( 8 | val runtimeConsoleType: RuntimeConsoleType = RuntimeConsoleType.DEFAULT, 9 | val runtimeMicSource: RuntimeMicSource = RuntimeMicSource.DEFAULT, 10 | val layoutId: UUID? = null, 11 | val layoutName: String? = null, 12 | val gbaSlotConfig: RomGbaSlotConfigUiModel = RomGbaSlotConfigUiModel() 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/romdetails/model/RomConfigUiState.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.romdetails.model 2 | 3 | sealed class RomConfigUiState { 4 | object Loading : RomConfigUiState() 5 | data class Ready(val romConfigUiModel: RomConfigUiModel) : RomConfigUiState() 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/romdetails/model/RomConfigUpdateEvent.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.romdetails.model 2 | 3 | import android.net.Uri 4 | import me.magnum.melonds.domain.model.rom.config.RuntimeConsoleType 5 | import me.magnum.melonds.domain.model.rom.config.RuntimeMicSource 6 | import java.util.UUID 7 | 8 | sealed class RomConfigUpdateEvent { 9 | data class RuntimeConsoleUpdate(val newRuntimeConsole: RuntimeConsoleType) : RomConfigUpdateEvent() 10 | data class RuntimeMicSourceUpdate(val newRuntimeMicSource: RuntimeMicSource) : RomConfigUpdateEvent() 11 | data class LayoutUpdate(val newLayoutId: UUID?) : RomConfigUpdateEvent() 12 | data class GbaSlotTypeUpdated(val type: RomGbaSlotConfigUiModel.Type) : RomConfigUpdateEvent() 13 | data class GbaRomPathUpdate(val gbaRomPath: Uri?) : RomConfigUpdateEvent() 14 | data class GbaSavePathUpdate(val gbaSavePath: Uri?) : RomConfigUpdateEvent() 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/romdetails/model/RomDetailsTab.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.romdetails.model 2 | 3 | enum class RomDetailsTab(val tabIndex: Int) { 4 | CONFIG(0), 5 | RETRO_ACHIEVEMENTS(1), 6 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/romdetails/model/RomGbaSlotConfigUiModel.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.romdetails.model 2 | 3 | data class RomGbaSlotConfigUiModel( 4 | val type: Type = Type.None, 5 | val gbaRomPath: String? = null, 6 | val gbaSavePath: String? = null, 7 | ) { 8 | 9 | enum class Type { 10 | None, GbaRom, MemoryExpansion 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/romdetails/model/RomRetroAchievementsUiState.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.romdetails.model 2 | 3 | import me.magnum.melonds.domain.model.retroachievements.RAUserAchievement 4 | 5 | sealed class RomRetroAchievementsUiState { 6 | object LoggedOut : RomRetroAchievementsUiState() 7 | object Loading : RomRetroAchievementsUiState() 8 | data class Ready(val achievements: List, val summary: RomAchievementsSummary) : RomRetroAchievementsUiState() 9 | object LoginError : RomRetroAchievementsUiState() 10 | object AchievementLoadError : RomRetroAchievementsUiState() 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/romlist/RomIcon.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.romlist 2 | 3 | import android.graphics.Bitmap 4 | import me.magnum.melonds.domain.model.RomIconFiltering 5 | 6 | data class RomIcon(val bitmap: Bitmap?, val filtering: RomIconFiltering) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/settings/PreferenceFragmentTitleProvider.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.settings 2 | 3 | interface PreferenceFragmentTitleProvider { 4 | fun getTitle(): String 5 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/settings/fragments/MainPreferencesFragment.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.settings.fragments 2 | 3 | import android.os.Bundle 4 | import androidx.preference.PreferenceFragmentCompat 5 | import dagger.hilt.android.AndroidEntryPoint 6 | import me.magnum.melonds.R 7 | import me.magnum.melonds.ui.settings.PreferenceFragmentTitleProvider 8 | 9 | @AndroidEntryPoint 10 | class MainPreferencesFragment : PreferenceFragmentCompat(), PreferenceFragmentTitleProvider { 11 | 12 | override fun getTitle() = getString(R.string.settings) 13 | 14 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 15 | setPreferencesFromResource(R.xml.pref_main, rootKey) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/settings/model/RetroAchievementsAccountState.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.settings.model 2 | 3 | sealed class RetroAchievementsAccountState { 4 | object Unknown : RetroAchievementsAccountState() 5 | object LoggedOut : RetroAchievementsAccountState() 6 | data class LoggedIn(val accountName: String) : RetroAchievementsAccountState() 7 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/shortcutsetup/DSFirmwareShortcutSetupActivity.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.shortcutsetup 2 | 3 | import me.magnum.melonds.domain.model.ConsoleType 4 | 5 | class DSFirmwareShortcutSetupActivity : FirmwareShortcutSetupActivity() { 6 | override fun getConsoleType(): ConsoleType { 7 | return ConsoleType.DS 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/shortcutsetup/DSiFirmwareShortcutSetupActivity.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.shortcutsetup 2 | 3 | import me.magnum.melonds.domain.model.ConsoleType 4 | 5 | class DSiFirmwareShortcutSetupActivity : FirmwareShortcutSetupActivity() { 6 | override fun getConsoleType(): ConsoleType { 7 | return ConsoleType.DSi 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/theme/MelonTheme.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.runtime.Composable 6 | 7 | @Composable 8 | fun MelonTheme( 9 | isDarkTheme: Boolean = isSystemInDarkTheme(), 10 | content: @Composable () -> Unit 11 | ) { 12 | val colors = if (isDarkTheme) DarkMelonColors else LightMelonColors 13 | 14 | MaterialTheme( 15 | colors = colors, 16 | typography = MelonTypography, 17 | ) { 18 | content() 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/ui/theme/Typography.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.ui.theme 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.material.Typography 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.text.font.FontWeight 7 | 8 | val MelonTypography @Composable get() = Typography( 9 | button = MaterialTheme.typography.button.copy(fontWeight = FontWeight.Bold), 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/utils/BitmapRegionDecoderCompat.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.utils 2 | 3 | import android.graphics.BitmapRegionDecoder 4 | import android.os.Build 5 | import java.io.InputStream 6 | 7 | object BitmapRegionDecoderCompat { 8 | fun newInstance(inputStream: InputStream): BitmapRegionDecoder? { 9 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 10 | BitmapRegionDecoder.newInstance(inputStream) 11 | } else { 12 | BitmapRegionDecoder.newInstance(inputStream, true) 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/utils/CompositeOnPreferenceChangeListener.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.utils 2 | 3 | import androidx.preference.Preference 4 | 5 | class CompositeOnPreferenceChangeListener : Preference.OnPreferenceChangeListener { 6 | private val listeners = mutableListOf() 7 | 8 | override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { 9 | val allListenersReturnedTrue = listeners.all { it.onPreferenceChange(preference, newValue) } 10 | return allListenersReturnedTrue 11 | } 12 | 13 | fun addOnPreferenceChangeListener(listener: Preference.OnPreferenceChangeListener) { 14 | listeners.add(listener) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/utils/EnumUtils.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.utils 2 | 3 | fun > findEnumValueIgnoreCase(enumValues: Array, value: String): T { 4 | enumValues.forEach { 5 | if (it.name.equals(value, true)) 6 | return it 7 | } 8 | throw IllegalArgumentException("Value $value does not represent an enum entry") 9 | } 10 | 11 | inline fun enumValueOfIgnoreCase(value: String): T where T : Enum { 12 | return findEnumValueIgnoreCase(enumValues(), value) 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/utils/PackageManagerCompat.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.utils 2 | 3 | import android.content.pm.PackageInfo 4 | import android.content.pm.PackageManager 5 | import android.os.Build 6 | 7 | object PackageManagerCompat { 8 | 9 | fun getPackageInfo(packageManager: PackageManager, packageName: String, flags: Int): PackageInfo { 10 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 11 | packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flags.toLong())) 12 | } else { 13 | packageManager.getPackageInfo(packageName, flags) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/utils/SharedFlow.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.utils 2 | 3 | import kotlinx.coroutines.channels.BufferOverflow 4 | import kotlinx.coroutines.flow.MutableSharedFlow 5 | 6 | /** 7 | * Creates a [MutableSharedFlow] that holds a single value and has no initial value. 8 | */ 9 | @Suppress("FunctionName") 10 | fun SubjectSharedFlow() = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) 11 | 12 | /** 13 | * Creates a [MutableSharedFlow] that doesn't hold any value. Suitable to create flows used to fire events. 14 | */ 15 | @Suppress("FunctionName") 16 | fun EventSharedFlow() = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/utils/SimpleDiffCallback.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.utils 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | abstract class SimpleDiffCallback(private val oldList: List, private val newList: List) : DiffUtil.Callback() { 6 | override fun getOldListSize() = oldList.size 7 | 8 | override fun getNewListSize() = newList.size 9 | 10 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 11 | return areItemsTheSame(oldList[oldItemPosition], newList[newItemPosition]) 12 | } 13 | 14 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 15 | return oldList[oldItemPosition] == newList[newItemPosition] 16 | } 17 | 18 | abstract fun areItemsTheSame(old: T, new: T): Boolean 19 | } -------------------------------------------------------------------------------- /app/src/main/java/me/magnum/melonds/utils/UriTypeHierarchyAdapter.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.utils 2 | 3 | import android.net.Uri 4 | import androidx.core.net.toUri 5 | import com.google.gson.* 6 | import java.lang.reflect.Type 7 | import kotlin.jvm.Throws 8 | 9 | 10 | class UriTypeHierarchyAdapter : JsonDeserializer, JsonSerializer { 11 | @Throws(JsonParseException::class) 12 | override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): Uri { 13 | return json.asString.toUri() 14 | } 15 | 16 | override fun serialize(src: Uri?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { 17 | return JsonPrimitive(src.toString()) 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/fragment_translate_enter_pop.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fragment_translate_enter_push.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fragment_translate_exit_pop.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fragment_translate_exit_push.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/rotate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/logo_dsiware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable-night/logo_dsiware.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable-xhdpi/logo_splash.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_bottom_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_rewind_save_state_focused.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_top_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_uiview.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_fast_forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_fast_forward.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_fast_forward_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_fast_forward_disabled.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_l.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_microphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_microphone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_microphone_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_microphone_disabled.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_pause.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_quick_load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_quick_load.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_quick_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_quick_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_r.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_reset.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_rewind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_rewind.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_select.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_start.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_swap_screens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_swap_screens.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_toggle_lid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/button_toggle_lid.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/buttons.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/drawable_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/drawable_ripple_small.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_audio.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_block.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cheat.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clear.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clock.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_completed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/ic_completed.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_file.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_firmware.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_folder.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gba_cart.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_input.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_link.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_melon_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/ic_melon_small.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_points.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/ic_points.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sort.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sort_alpha.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_status_error.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_status_ok.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_status_warn.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_touch_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/ic_touch_disabled.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_touch_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/ic_touch_enabled.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_trophy.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_video.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/keypad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/keypad.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo_dsiware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/drawable/logo_dsiware.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_rewind_save_state.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_rewind_save_state_time_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_layouts.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_rom_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_shortcut_setup.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_text_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_layout_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_rom_simple.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preference_directory_picker_status.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preference_firmware_colour_picker_colour.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/menu/layout_item_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/layouts_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_platform_ds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_platform_dsi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_platform_ds_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-hdpi/ic_platform_ds_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_platform_dsi_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-hdpi/ic_platform_dsi_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_platform_ds_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-mdpi/ic_platform_ds_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_platform_dsi_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-mdpi/ic_platform_dsi_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_platform_ds_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xhdpi/ic_platform_ds_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_platform_dsi_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xhdpi/ic_platform_dsi_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_platform_ds_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xxhdpi/ic_platform_ds_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_platform_dsi_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xxhdpi/ic_platform_dsi_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_platform_ds_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xxxhdpi/ic_platform_ds_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_platform_dsi_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/main/res/mipmap-xxxhdpi/ic_platform_dsi_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-v29/strings_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @string/theme_option_light 5 | @string/theme_option_dark 6 | @string/theme_option_system_default 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 140dp 4 | 150dp 5 | 60dp 6 | 40dp 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @string/theme_option_light 5 | @string/theme_option_dark 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 250 5 | -------------------------------------------------------------------------------- /app/src/main/res/xml/game_mode_config.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | nus.cdn.t.shop.nintendowifi.net 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/xml/pref_cheats.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/xml/pref_general_updates.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/nightly/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/nightly/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/nightly/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/nightly/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/nightly/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/nightly/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/nightly/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/nightly/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/nightly/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/nightly/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/nightly/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/nightly/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/nightly/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/nightly/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/nightly/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/nightly/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | melonDS Nightly 4 | -------------------------------------------------------------------------------- /app/src/playStore/java/me/magnum/melonds/di/PlayStoreModule.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.di 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import me.magnum.melonds.domain.repositories.UpdatesRepository 8 | import me.magnum.melonds.domain.services.UpdateInstallManager 9 | import me.magnum.melonds.playstore.PlayStoreUpdatesRepository 10 | import me.magnum.melonds.services.PlayStoreUpdateInstallManager 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | object PlayStoreModule { 16 | @Provides 17 | @Singleton 18 | fun provideUpdatesRepository(): UpdatesRepository { 19 | return PlayStoreUpdatesRepository() 20 | } 21 | 22 | @Provides 23 | @Singleton 24 | fun provideUpdateInstallManager(): UpdateInstallManager { 25 | return PlayStoreUpdateInstallManager() 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/playStore/java/me/magnum/melonds/playstore/PlayStoreUpdatesRepository.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.playstore 2 | 3 | import me.magnum.melonds.domain.model.appupdate.AppUpdate 4 | import me.magnum.melonds.domain.repositories.UpdatesRepository 5 | 6 | class PlayStoreUpdatesRepository : UpdatesRepository { 7 | override suspend fun checkNewUpdate(): Result { 8 | return Result.success(null) 9 | } 10 | 11 | override fun skipUpdate(update: AppUpdate) { 12 | // Do nothing. Update checking not supported in the Play Store version 13 | } 14 | 15 | override fun notifyUpdateDownloaded(update: AppUpdate) { 16 | // Do nothing 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/playStore/java/me/magnum/melonds/services/PlayStoreUpdateInstallManager.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.services 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.emptyFlow 5 | import me.magnum.melonds.domain.model.DownloadProgress 6 | import me.magnum.melonds.domain.model.appupdate.AppUpdate 7 | import me.magnum.melonds.domain.services.UpdateInstallManager 8 | 9 | class PlayStoreUpdateInstallManager : UpdateInstallManager { 10 | override fun downloadAndInstallUpdate(update: AppUpdate): Flow { 11 | return emptyFlow() 12 | } 13 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) apply false 3 | alias(libs.plugins.android.library) apply false 4 | alias(libs.plugins.compose.compiler) apply false 5 | alias(libs.plugins.hilt.android) apply false 6 | alias(libs.plugins.kotlin.android) apply false 7 | alias(libs.plugins.kotlin.jvm) apply false 8 | alias(libs.plugins.kotlin.parcelize) apply false 9 | alias(libs.plugins.kotlin.serialization) apply false 10 | alias(libs.plugins.ksp) apply false 11 | } 12 | 13 | tasks.register("clean", Delete::class) { 14 | delete(rootProject.layout.buildDirectory) 15 | } 16 | -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | mavenCentral() 3 | } 4 | 5 | plugins { 6 | `kotlin-dsl` 7 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/AppConfig.kt: -------------------------------------------------------------------------------- 1 | object AppConfig { 2 | const val compileSdkVersion = 35 3 | const val targetSdkVersion = compileSdkVersion 4 | const val minSdkVersion = 24 5 | const val ndkVersion = "28.0.13004108" 6 | 7 | const val versionCode = 34 8 | const val versionName = "Beta 1.10.0" 9 | } -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.jvm) 3 | } 4 | 5 | kotlin { 6 | jvmToolchain(21) 7 | } 8 | 9 | dependencies { 10 | implementation(libs.kotlin.coroutines) 11 | } -------------------------------------------------------------------------------- /common/src/main/java/me/magnum/melonds/common/SuspendRunCatching.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.melonds.common 2 | 3 | import kotlinx.coroutines.ensureActive 4 | import kotlin.coroutines.coroutineContext 5 | 6 | suspend inline fun suspendRunCatching(block: () -> R): Result { 7 | return try { 8 | Result.success(block()) 9 | } catch (e: Throwable) { 10 | coroutineContext.ensureActive() 11 | Result.failure(e) 12 | } 13 | } 14 | 15 | suspend inline fun Result.suspendRecoverCatching(transform: (exception: Throwable) -> R): Result { 16 | return when (val exception = exceptionOrNull()) { 17 | null -> this 18 | else -> suspendRunCatching { transform(exception) } 19 | } 20 | } 21 | 22 | suspend inline fun Result.suspendMapCatching(transform: (value: T) -> R): Result { 23 | return when { 24 | isSuccess -> suspendRunCatching { transform(getOrThrow()) } 25 | else -> Result.failure(exceptionOrNull()!!) 26 | } 27 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | android.useAndroidX=true 16 | android.enableR8.fullMode=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelvcaetano/melonDS-android/7e31d08a3e0e3801c0aca393832238395ac63983/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Nov 02 16:55:59 GMT 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 7 | -------------------------------------------------------------------------------- /masterswitch/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.smp.masterswitchpreference" 8 | compileSdk = AppConfig.compileSdkVersion 9 | 10 | defaultConfig { 11 | minSdk = AppConfig.minSdkVersion 12 | } 13 | 14 | buildTypes { 15 | getByName("release") { 16 | isMinifyEnabled = true 17 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 18 | } 19 | } 20 | compileOptions { 21 | sourceCompatibility = JavaVersion.VERSION_21 22 | targetCompatibility = JavaVersion.VERSION_21 23 | 24 | kotlin { 25 | jvmToolchain(21) 26 | } 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation(libs.androidx.appcompat) 32 | implementation(libs.androidx.core) 33 | implementation(libs.androidx.preference) 34 | } 35 | -------------------------------------------------------------------------------- /masterswitch/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /masterswitch/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /masterswitch/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #fafafa 5 | #4dfafafa 6 | #00796b 7 | #757575 8 | -------------------------------------------------------------------------------- /masterswitch/src/main/res/xml/blank_preference_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /masterswitch/src/main/res/xml/explanation_preference_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /rcheevos-api/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /rcheevos-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.jvm) 3 | alias(libs.plugins.kotlin.serialization) 4 | } 5 | 6 | kotlin { 7 | jvmToolchain(21) 8 | } 9 | 10 | dependencies { 11 | implementation(projects.common) 12 | 13 | implementation(libs.kotlin.serialization) 14 | implementation(libs.okhttp) 15 | } -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/RAAchievementSignatureProvider.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi 2 | 3 | import me.magnum.rcheevosapi.model.RAUserAuth 4 | 5 | interface RAAchievementSignatureProvider { 6 | fun provideAchievementSignature(achievementId: Long, userAuth: RAUserAuth, forHardcoreMode: Boolean): String 7 | } -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/RAUserAuthStore.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi 2 | 3 | import me.magnum.rcheevosapi.model.RAUserAuth 4 | 5 | interface RAUserAuthStore { 6 | suspend fun storeUserAuth(userAuth: RAUserAuth) 7 | suspend fun getUserAuth(): RAUserAuth? 8 | suspend fun clearUserAuth() 9 | } -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/dto/AchievementDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.dto 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | internal data class AchievementDto( 8 | @SerialName("ID") 9 | val id: Long, 10 | @SerialName("NumAwarded") 11 | val numAwarded: Int?, 12 | @SerialName("NumAwardedHardcore") 13 | val numAwardedHardcore: Int?, 14 | @SerialName("Title") 15 | val title: String, 16 | @SerialName("Description") 17 | val description: String, 18 | @SerialName("Points") 19 | val points: Int, 20 | @SerialName("Flags") 21 | val flags: Int, 22 | @SerialName("BadgeURL") 23 | val badgeUrl: String, 24 | @SerialName("BadgeLockedURL") 25 | val badgeUrlLocked: String, 26 | @SerialName("DisplayOrder") 27 | val displayOrder: String?, 28 | @SerialName("MemAddr") 29 | val memoryAddress: String, 30 | ) 31 | -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/dto/GameDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.dto 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | internal data class GameDto( 8 | @SerialName("ID") 9 | val id: Long, 10 | @SerialName("Title") 11 | val title: String, 12 | @SerialName("ImageIconURL") 13 | val iconUrl: String, 14 | @SerialName("RichPresencePatch") 15 | val richPresencePatch: String?, 16 | @SerialName("Achievements") 17 | val achievements: List, 18 | ) 19 | -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/dto/GamePatchDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.dto 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | internal data class GamePatchDto( 8 | @SerialName("PatchData") 9 | val game: GameDto, 10 | ) 11 | -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/dto/HashLibraryDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.dto 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | internal data class HashLibraryDto( 8 | @SerialName("MD5List") 9 | val md5List: Map, 10 | ) 11 | -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/dto/UserLoginDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.dto 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | internal data class UserLoginDto( 8 | @SerialName("Token") 9 | val token: String, 10 | @SerialName("Score") 11 | val score: Long, 12 | @SerialName("SoftcoreScore") 13 | val softcoreScore: Long, 14 | ) 15 | -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/dto/UserUnlocksDto.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.dto 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | internal data class UserUnlocksDto( 8 | @SerialName("UserUnlocks") 9 | val userUnlocks: List, 10 | ) -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/dto/mapper/GameDtoMapper.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.dto.mapper 2 | 3 | import me.magnum.rcheevosapi.dto.GameDto 4 | import me.magnum.rcheevosapi.model.RAGame 5 | import me.magnum.rcheevosapi.model.RAGameId 6 | import java.net.URI 7 | 8 | internal fun GameDto.mapToModel(): RAGame { 9 | val gameId = RAGameId(id) 10 | return RAGame( 11 | id = gameId, 12 | title = title, 13 | icon = URI(iconUrl).toURL(), 14 | richPresencePatch = richPresencePatch, 15 | achievements = achievements.map { 16 | it.mapToModel(gameId) 17 | }, 18 | ) 19 | } -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/exception/UnsuccessfulRequestException.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.exception 2 | 3 | class UnsuccessfulRequestException(reason: String) : Exception(reason) -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/exception/UserNotAuthenticatedException.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.exception 2 | 3 | class UserNotAuthenticatedException : Exception("The user is not authenticated to RetroAchievements") -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/model/RAAchievement.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.model 2 | 3 | import java.net.URL 4 | 5 | data class RAAchievement( 6 | val id: Long, 7 | val gameId: RAGameId, 8 | val totalAwardsCasual: Int?, 9 | val totalAwardsHardcore: Int?, 10 | val title: String, 11 | val description: String, 12 | val points: Int, 13 | val displayOrder: Int, 14 | val badgeUrlUnlocked: URL, 15 | val badgeUrlLocked: URL, 16 | val memoryAddress: String, 17 | val type: Type, 18 | ) { 19 | 20 | enum class Type { 21 | CORE, 22 | UNOFFICIAL 23 | } 24 | 25 | fun getCleanTitle(): String { 26 | return title.removeSuffix("[m]").trim() 27 | } 28 | 29 | fun isMissable(): Boolean { 30 | return title.endsWith("[m]") 31 | } 32 | } -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/model/RAGame.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.model 2 | 3 | import java.net.URL 4 | 5 | data class RAGame( 6 | val id: RAGameId, 7 | val title: String, 8 | val icon: URL, 9 | val richPresencePatch: String?, 10 | val achievements: List, 11 | ) -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/model/RAGameId.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.model 2 | 3 | @JvmInline 4 | value class RAGameId(val id: Long) -------------------------------------------------------------------------------- /rcheevos-api/src/main/java/me/magnum/rcheevosapi/model/RAUserAuth.kt: -------------------------------------------------------------------------------- 1 | package me.magnum.rcheevosapi.model 2 | 3 | data class RAUserAuth( 4 | val username: String, 5 | val token: String, 6 | ) -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | } 16 | 17 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 18 | 19 | include(":app") 20 | include(":common") 21 | include(":masterswitch") 22 | include(":rcheevos-api") --------------------------------------------------------------------------------