Merge branch 'master' into opengl_renderer

This commit is contained in:
Rafael Caetano 2023-11-03 15:56:59 +00:00
commit 4e668af070
51 changed files with 712 additions and 197 deletions

View File

@ -1,16 +1,11 @@
**IMPORTANT**: This update brings the changes from melonDS 0.9.5, which has incompatible saves-states with the previous version. If you rely on save-states to keep your
progress, be aware that you will lose your progress!
**Changelog:**
* Update to melonDS 0.9.5
* Implement the DSiWare Manager. This allows you to install DSiWare titles to the NAND directly from the emulator
* Allow DS and DSi firmwares to be launched from home screen shortcuts
* Improve search performance when there are a lot of ROMs
* Add option to quickly view enabled cheats
* Fix selected ROM icon filtering not being applied until the app was restarted
* Add monochrome icon
* Add Bahasa Indonesia translation (thanks @NTHGiT)
* Add French translation (thanks @SombrAbsol)
* Add Spanish translation (thanks @BackpackXl)
* Add Portuguese (Brazil) translation (thanks @Bardock88)
* Add support for RetroAchievements (leaderboards are not yet supported)
* Add support for DSi camera
* Add proper support for Wi-Fi connectivity (huge thanks to @JesseTG from the melonDS team!)
* Fix DS firmware not being able to boot under certain scenarios
* Fix some crashes when JIT was enabled
* Fix some issues on startup when save files could not be created
* Updated French translation (thanks @SombrAbsol)
* Updated Russian translation (thanks @6lackmag3)
* Updated Spanish translation (thanks @BackpackXl)
* Other minor fixes and improvements

View File

@ -1,9 +1,8 @@
<b>IMPORTANT</b>: This update brings the changes from melonDS 0.9.5, which has incompatible saves-states with the previous version. If you rely on save-states to keep your progress, be aware that you will lose it!
• Update to melonDS 0.9.5
• You can now install DSiWare titles to the NAND
• Add home shortcuts for DS and DSi firmwares
• Improve search performance
• Add option to quickly view enabled cheats
• Monochrome icon
• Add Indonesian, French, Spanish and Portuguese (Brazil) translations
• Add support for RetroAchievements (leaderboards are not yet supported)
• Add support for DSi camera
• Add proper support for Wi-Fi connectivity (huge thanks to Jesse from the melonDS team!)
• Fix DS firmware not being able to boot under certain scenarios
• Fix some crashes when JIT was enabled
• Fix some issues on startup when save files could not be created
• Updated French, Russian and Spanish translations
• Other minor fixes and improvements

View File

@ -51,7 +51,7 @@ jobs:
echo "MELONDS_KEY_ALIAS=melonds" >> local.properties
echo "MELONDS_KEY_PASSWORD=$MELONDS_KEYSTORE_PASSWORD" >> local.properties
chmod +x ./gradlew
./gradlew :app:assembleGitHubRelease
./gradlew :app:assembleGitHubNightlyRelease
git tag -m "Nightly Release" -f -a nightly-release
git push -f origin refs/tags/nightly-release
@ -64,12 +64,12 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
tag: nightly-release
name: 'Nightly Build'
body: 'The currently Nightly Build. Whenever new changes are pushed, you can find the latest build here.'
artifacts: app/build/outputs/apk/gitHub/release/app-gitHub-release.apk
body: 'The currently Nightly Build. Whenever new changes are pushed, you can find the latest build here. You can keep this installation alongside your main one.'
artifacts: app/build/outputs/apk/gitHubNightly/release/app-gitHub-nightly-release.apk
artifactContentType: application/vnd.android.package-archive
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: melonDS-android
path: app/build/outputs/apk/gitHub/release/app-gitHub-release.apk
path: app/build/outputs/apk/gitHubNightly/release/app-gitHub-nightly-release.apk

View File

@ -35,32 +35,31 @@ jobs:
echo "MELONDS_KEY_ALIAS=melonds-playstore" >> local.properties
echo "MELONDS_KEY_PASSWORD=$MELONDS_KEY_PASSWORD" >> local.properties
chmod +x ./gradlew
./gradlew :app:assemblePlayStoreRelease
./gradlew :app:assemblePlayStoreProdRelease
- name: Get Version
id: release_params
run: echo VERSION=$(grep -oP 'versionName = "\K(.*?)(?=")' buildSrc/src/main/kotlin/AppConfig.kt) >> $GITHUB_OUTPUT
- name: Create Play Store Release
uses: r0adkll/upload-google-play@v1.0.19
uses: r0adkll/upload-google-play@v1.1.2
with:
serviceAccountJsonPlainText: ${{ secrets.MELONDS_PLAYSTORE_ACCOUNT_JSON }}
packageName: me.magnum.melonds
releaseName: ${{ steps.release_params.outputs.VERSION }}
releaseFiles: app/build/outputs/apk/playStore/release/app-playStore-release.apk
releaseFiles: app/build/outputs/apk/playStoreProd/release/app-playStore-prod-release.apk
track: beta
inAppUpdatePriority: 2
status: draft
changesNotSentForReview: true
whatsNewDirectory: ./.github/changelog/playStore
mappingFile: app/build/outputs/mapping/playStoreRelease/mapping.txt
debugSymbols: app/build/outputs/native-debug-symbols/playStoreRelease/native-debug-symbols.zip
mappingFile: app/build/outputs/mapping/playStoreProdRelease/mapping.txt
debugSymbols: app/build/outputs/native-debug-symbols/playStoreProdRelease/native-debug-symbols.zip
- name: Upload APK, Mapping and Debug Symbols
uses: actions/upload-artifact@v3
with:
name: playstore-release
path: |
app/build/outputs/apk/playStore/release/app-playStore-release.apk
app/build/outputs/mapping/playStoreRelease/mapping.txt
app/build/outputs/native-debug-symbols/playStoreRelease/native-debug-symbols.zip
app/build/outputs/apk/playStoreProd/release/app-playStore-prod-release.apk
app/build/outputs/mapping/playStoreProdRelease/mapping.txt
app/build/outputs/native-debug-symbols/playStoreProdRelease/native-debug-symbols.zip

View File

@ -36,7 +36,7 @@ jobs:
echo "MELONDS_KEY_ALIAS=melonds" >> local.properties
echo "MELONDS_KEY_PASSWORD=$MELONDS_KEYSTORE_PASSWORD" >> local.properties
chmod +x ./gradlew
./gradlew :app:assembleGitHubRelease
./gradlew :app:assembleGitHubProdRelease
- name: Get Tag and Version
id: release_params
@ -52,7 +52,7 @@ jobs:
tag: ${{ steps.release_params.outputs.TAG }}
name: ${{ steps.release_params.outputs.VERSION }}
bodyFile: ./.github/changelog/gitHub.md
artifacts: app/build/outputs/apk/gitHub/release/app-gitHub-release.apk
artifacts: app/build/outputs/apk/gitHubProd/release/app-gitHub-prod-release.apk
artifactContentType: application/vnd.android.package-archive
- name: Upload APK, Mapping and Debug Symbols
@ -60,21 +60,21 @@ jobs:
with:
name: github-release
path: |
app/build/outputs/apk/gitHub/release/app-gitHub-release.apk
app/build/outputs/mapping/gitHubRelease/mapping.txt
app/build/outputs/native-debug-symbols/gitHubRelease/native-debug-symbols.zip
app/build/outputs/apk/gitHubProd/release/app-gitHub-prod-release.apk
app/build/outputs/mapping/gitHubProdRelease/mapping.txt
app/build/outputs/native-debug-symbols/gitHubProdRelease/native-debug-symbols.zip
playstore-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions/setup-java@v3
with:
java-version: '11'
java-version: '17'
distribution: zulu
- name: Setup Android SDK
@ -93,31 +93,31 @@ jobs:
echo "MELONDS_KEY_ALIAS=melonds-playstore" >> local.properties
echo "MELONDS_KEY_PASSWORD=$MELONDS_KEY_PASSWORD" >> local.properties
chmod +x ./gradlew
./gradlew :app:assemblePlayStoreRelease
./gradlew :app:assemblePlayStoreProdRelease
- name: Get Version
id: release_params
run: echo VERSION=$(grep -oP 'versionName = "\K(.*?)(?=")' buildSrc/src/main/kotlin/AppConfig.kt) >> $GITHUB_OUTPUT
- name: Create Play Store Release
uses: r0adkll/upload-google-play@v1.0.19
uses: r0adkll/upload-google-play@v1.1.2
with:
serviceAccountJsonPlainText: ${{ secrets.MELONDS_PLAYSTORE_ACCOUNT_JSON }}
packageName: me.magnum.melonds
releaseName: ${{ steps.release_params.outputs.VERSION }}
releaseFiles: app/build/outputs/apk/playStore/release/app-playStore-release.apk
releaseFiles: app/build/outputs/apk/playStoreProd/release/app-playStore-prod-release.apk
track: beta
inAppUpdatePriority: 2
status: draft
changesNotSentForReview: true
whatsNewDirectory: ./.github/changelog/playStore
mappingFile: app/build/outputs/mapping/playStoreRelease/mapping.txt
mappingFile: app/build/outputs/mapping/playStoreProdRelease/mapping.txt
debugSymbols: app/build/outputs/native-debug-symbols/playStoreProdRelease/native-debug-symbols.zip
- name: Upload APK, Mapping and Debug Symbols
uses: actions/upload-artifact@v3
with:
name: playstore-release
path: |
app/build/outputs/apk/playStore/release/app-playStore-release.apk
app/build/outputs/mapping/playStoreRelease/mapping.txt
app/build/outputs/native-debug-symbols/playStoreRelease/native-debug-symbols.zip
app/build/outputs/apk/playStoreProd/release/app-playStore-prod-release.apk
app/build/outputs/mapping/playStoreProdRelease/mapping.txt
app/build/outputs/native-debug-symbols/playStoreProdRelease/native-debug-symbols.zip

View File

@ -27,6 +27,12 @@ third-party frontend with the following configuration:
* `uri` (preferred) - a string with the [SAF](https://developer.android.com/guide/topics/providers/create-document-provider) URI of the NDS ROM (ZIP files are supported)
* `PATH` - a string with the absolute path to the NDS ROM (ZIP files are supported)
# Nightly Builds
To have access to the latest changes, you can install nightly builds that you can find [here](https://github.com/rafaelvcaetano/melonDS-android/releases/tag/nightly-release).
Be aware that these builds can contain more bugs than usual and you may need to clear your app data to get it to work properly.
# Building
To build the project you will need Android SDK, NDK and CMake.
@ -36,9 +42,9 @@ To build the project you will need Android SDK, NDK and CMake.
`git clone --recurse-submodules https://github.com/rafaelvcaetano/melonDS-android.git`
2. Install the Android SDK, NDK and CMake
3. Build with:
1. Unix: `./gradlew :app:assembleGitHubDebug`
2. Windows: `gradlew.bat :app:assembleGitHubDebug`
4. The generated APK can be found at `app/gitHub/debug`
1. Unix: `./gradlew :app:assembleGitHubProdDebug`
2. Windows: `gradlew.bat :app:assembleGitHubProdDebug`
4. The generated APK can be found at `app/gitHubProd/debug`
If you want to create a release build, you will need to modify your `local.properties` file to include the following fields:
* `MELONDS_KEYSTORE=<path_to_your_keystore>`

View File

@ -63,6 +63,7 @@ android {
}
flavorDimensions.add("version")
flavorDimensions.add("build")
productFlavors {
create("playStore") {
dimension = "version"
@ -70,8 +71,19 @@ android {
}
create("gitHub") {
dimension = "version"
isDefault = true
versionNameSuffix = " GH"
}
create("prod") {
dimension = "build"
isDefault = true
}
create("nightly") {
dimension = "build"
applicationIdSuffix = ".nightly"
versionNameSuffix = " (NIGHTLY)"
}
}
externalNativeBuild {
cmake {

View File

@ -7,10 +7,8 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.domain.services.UpdateInstallManager
import me.magnum.melonds.github.GitHubApi
import me.magnum.melonds.github.repositories.GitHubUpdatesRepository
import me.magnum.melonds.github.services.GitHubUpdateInstallManager
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
@ -32,13 +30,6 @@ object GitHubModule {
return retrofit.create(GitHubApi::class.java)
}
@Provides
@Singleton
fun provideUpdatesRepository(@ApplicationContext context: Context, gitHubApi: GitHubApi): UpdatesRepository {
val gitHubPreferences = context.getSharedPreferences("preferences-github", Context.MODE_PRIVATE)
return GitHubUpdatesRepository(context, gitHubApi, gitHubPreferences)
}
@Provides
@Singleton
fun provideUpdateInstallManager(@ApplicationContext context: Context): UpdateInstallManager {

View File

@ -7,4 +7,7 @@ import retrofit2.http.GET
interface GitHubApi {
@GET("/repos/rafaelvcaetano/melonDS-android/releases/latest")
fun getLatestRelease(): Single<ReleaseDto>
@GET("/repos/rafaelvcaetano/melonDS-android/releases/tags/nightly-release")
fun getLatestNightlyRelease(): Single<ReleaseDto>
}

View File

@ -0,0 +1,5 @@
package me.magnum.melonds.github
const val APK_CONTENT_TYPE = "application/vnd.android.package-archive"
const val PREF_KEY_GITHUB_CHECK_FOR_UPDATES = "github_check_for_updates"

View File

@ -6,5 +6,6 @@ data class ReleaseDto(
@SerializedName("tag_name") val tagName: String,
@SerializedName("name") val name: String,
@SerializedName("body") val body: String,
@SerializedName("created_at") val createdAt: String,
@SerializedName("assets") val assets: List<AssetDto>
)

View File

@ -10,7 +10,7 @@ import android.net.Uri
import androidx.core.content.getSystemService
import io.reactivex.Observable
import me.magnum.melonds.common.providers.UpdateContentProvider
import me.magnum.melonds.domain.model.AppUpdate
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.model.DownloadProgress
import me.magnum.melonds.domain.services.UpdateInstallManager
import java.io.File

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreference
android:key="github_check_for_updates"
android:title="@string/check_for_updates"
android:summary="@string/check_for_updates_summary"
app:iconSpaceReserved="false"
android:defaultValue="true" />
</PreferenceScreen>

View File

@ -0,0 +1,24 @@
package me.magnum.melonds.di
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.github.GitHubApi
import me.magnum.melonds.github.repositories.GitHubNightlyUpdatesRepository
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object GitHubNightlyModule {
@Provides
@Singleton
fun provideUpdatesRepository(@ApplicationContext context: Context, gitHubApi: GitHubApi): UpdatesRepository {
val gitHubPreferences = context.getSharedPreferences("preferences-github", Context.MODE_PRIVATE)
return GitHubNightlyUpdatesRepository(gitHubApi, gitHubPreferences)
}
}

View File

@ -0,0 +1,115 @@
package me.magnum.melonds.github.repositories
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.core.net.toUri
import io.reactivex.Maybe
import io.reactivex.Single
import me.magnum.melonds.domain.model.Version
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.github.APK_CONTENT_TYPE
import me.magnum.melonds.github.GitHubApi
import me.magnum.melonds.github.PREF_KEY_GITHUB_CHECK_FOR_UPDATES
import me.magnum.melonds.github.dtos.ReleaseDto
import java.time.Duration
import java.time.Instant
class GitHubNightlyUpdatesRepository(private val api: GitHubApi, private val preferences: SharedPreferences) : UpdatesRepository {
companion object {
private const val KEY_NEXT_CHECK_DATE = "github_updates_nightly_next_check_date"
private const val KEY_LAST_RELEASE_DATE = "github_updates_nightly_last_release_date"
}
override fun checkNewUpdate(): Maybe<AppUpdate> {
return shouldCheckUpdates()
.flatMapMaybe { checkUpdates ->
if (checkUpdates) {
api.getLatestNightlyRelease().flatMapMaybe { release ->
if (shouldUpdate(release)) {
val apkBinary = release.assets.firstOrNull { it.contentType == APK_CONTENT_TYPE }
if (apkBinary != null) {
val update = AppUpdate(
AppUpdate.Type.NIGHTLY,
apkBinary.id,
apkBinary.url.toUri(),
Version.fromString(release.tagName),
release.body,
apkBinary.size,
Instant.parse(release.createdAt),
)
Maybe.just(update)
} else {
Maybe.empty()
}
} else {
Maybe.empty()
}
}
} else {
Maybe.empty()
}
}
}
override fun skipUpdate(update: AppUpdate) {
scheduleNextUpdate()
}
override fun notifyUpdateDownloaded(update: AppUpdate) {
// This doesn't mean that the user has actually installed the update, but we have no way to determine that. As such, just assume that the update will be installed and
// store the date of the update
preferences.edit {
putLong(KEY_LAST_RELEASE_DATE, update.updateDate.toEpochMilli())
}
}
private fun shouldCheckUpdates(): Single<Boolean> {
return Single.create { emitter ->
val updateCheckEnabled = preferences.getBoolean(PREF_KEY_GITHUB_CHECK_FOR_UPDATES, true)
if (!updateCheckEnabled) {
emitter.onSuccess(false)
return@create
}
val nextUpdateCheckTime = preferences.getLong(KEY_NEXT_CHECK_DATE, -1)
if (nextUpdateCheckTime == (-1).toLong()) {
emitter.onSuccess(true)
return@create
}
val now = Instant.now()
val shouldCheckUpdates = now.toEpochMilli() > nextUpdateCheckTime
emitter.onSuccess(shouldCheckUpdates)
}
}
private fun scheduleNextUpdate() {
val now = Instant.now()
val nextUpdateDate = now + Duration.ofDays(1)
preferences.edit {
putLong(KEY_NEXT_CHECK_DATE, nextUpdateDate.toEpochMilli())
}
}
private fun shouldUpdate(releaseDto: ReleaseDto): Boolean {
val lastReleaseDate = preferences.getLong(KEY_LAST_RELEASE_DATE, -1)
if (lastReleaseDate == -1L) {
// If there is no last release date, then it's the first time the user is running the app and checking for updates. Ignore this release since we can't check if
// it's actually different from the one the user has installed, and save the release date in the preferences so that we can have a future reference
val releaseDate = Instant.parse(releaseDto.createdAt)
preferences.edit {
putLong(KEY_LAST_RELEASE_DATE, releaseDate.toEpochMilli())
}
scheduleNextUpdate()
return false
}
val thisReleaseDate = Instant.parse(releaseDto.createdAt)
return thisReleaseDate.toEpochMilli() > lastReleaseDate
}
}

View File

@ -0,0 +1,24 @@
package me.magnum.melonds.di
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.github.GitHubApi
import me.magnum.melonds.github.repositories.GitHubProdUpdatesRepository
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object GitHubProdModule {
@Provides
@Singleton
fun provideUpdatesRepository(@ApplicationContext context: Context, gitHubApi: GitHubApi): UpdatesRepository {
val gitHubPreferences = context.getSharedPreferences("preferences-github", Context.MODE_PRIVATE)
return GitHubProdUpdatesRepository(context, gitHubApi, gitHubPreferences)
}
}

View File

@ -6,17 +6,19 @@ import androidx.core.content.edit
import androidx.core.net.toUri
import io.reactivex.Maybe
import io.reactivex.Single
import me.magnum.melonds.domain.model.AppUpdate
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.model.Version
import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.github.GitHubApi
import me.magnum.melonds.github.PREF_KEY_GITHUB_CHECK_FOR_UPDATES
import me.magnum.melonds.github.dtos.ReleaseDto
import me.magnum.melonds.utils.PackageManagerCompat
import me.magnum.melonds.utils.enumValueOfIgnoreCase
import java.time.Instant
import java.util.*
import java.util.concurrent.TimeUnit
class GitHubUpdatesRepository(private val context: Context, private val api: GitHubApi, private val preferences: SharedPreferences) : UpdatesRepository {
class GitHubProdUpdatesRepository(private val context: Context, private val api: GitHubApi, private val preferences: SharedPreferences) : UpdatesRepository {
companion object {
private const val APK_CONTENT_TYPE = "application/vnd.android.package-archive"
private const val KEY_SKIP_VERSION = "github_updates_skip_version"
@ -35,11 +37,13 @@ class GitHubUpdatesRepository(private val context: Context, private val api: Git
val apkBinary = release.assets.firstOrNull { it.contentType == APK_CONTENT_TYPE }
if (apkBinary != null) {
val update = AppUpdate(
AppUpdate.Type.PRODUCTION,
apkBinary.id,
apkBinary.url.toUri(),
Version.fromString(release.tagName),
release.body,
apkBinary.size
apkBinary.size,
Instant.parse(release.createdAt),
)
Maybe.just(update)
} else {
@ -61,8 +65,18 @@ class GitHubUpdatesRepository(private val context: Context, private val api: Git
}
}
override fun notifyUpdateDownloaded(update: AppUpdate) {
// Do nothing
}
private fun shouldCheckUpdates(): Single<Boolean> {
return Single.create { emitter ->
val updateCheckEnabled = preferences.getBoolean(PREF_KEY_GITHUB_CHECK_FOR_UPDATES, true)
if (!updateCheckEnabled) {
emitter.onSuccess(false)
return@create
}
val lastCheckUpdateTimestamp = preferences.getLong(KEY_LAST_UPDATE_CHECK, -1)
if (lastCheckUpdateTimestamp == (-1).toLong()) {
emitter.onSuccess(true)

View File

@ -1,11 +0,0 @@
package me.magnum.melonds.domain.model
import android.net.Uri
data class AppUpdate(
val id: Long,
val downloadUri: Uri,
val newVersion: Version,
val description: String,
val binarySize: Long
)

View File

@ -0,0 +1,21 @@
package me.magnum.melonds.domain.model.appupdate
import android.net.Uri
import me.magnum.melonds.domain.model.Version
import java.time.Instant
data class AppUpdate(
val type: Type,
val id: Long,
val downloadUri: Uri,
val newVersion: Version,
val description: String,
val binarySize: Long,
val updateDate: Instant,
) {
enum class Type {
PRODUCTION,
NIGHTLY,
}
}

View File

@ -1,10 +1,10 @@
package me.magnum.melonds.domain.repositories
import io.reactivex.Maybe
import io.reactivex.Observable
import me.magnum.melonds.domain.model.AppUpdate
import me.magnum.melonds.domain.model.appupdate.AppUpdate
interface UpdatesRepository {
fun checkNewUpdate(): Maybe<AppUpdate>
fun skipUpdate(update: AppUpdate)
fun notifyUpdateDownloaded(update: AppUpdate)
}

View File

@ -1,7 +1,7 @@
package me.magnum.melonds.domain.services
import io.reactivex.Observable
import me.magnum.melonds.domain.model.AppUpdate
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.model.DownloadProgress
interface UpdateInstallManager {

View File

@ -561,7 +561,7 @@ class SharedPreferencesSettingsRepository(
return preferenceFlow.map { mapper() }
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
val subject = preferenceObservers[key]
subject?.onNext(Any())

View File

@ -56,8 +56,8 @@ class EmulatorSession {
return areRetroAchievementsEnabled && isRetroAchievementsIntegrationEnabled
}
fun areSaveStatesAllowed(): Boolean {
// Cannot use save-states when RA hardcore is enabled
fun areSaveStateLoadsAllowed(): Boolean {
// Cannot load save-states when RA hardcore is enabled
return !isRetroAchievementsHardcoreModeEnabled || !areRetroAchievementsEnabled
}

View File

@ -79,10 +79,12 @@ import me.magnum.melonds.impl.emulator.LifecycleOwnerProvider
import me.magnum.melonds.parcelables.RomInfoParcelable
import me.magnum.melonds.parcelables.RomParcelable
import me.magnum.melonds.ui.cheats.CheatsActivity
import me.magnum.melonds.ui.emulator.component.EmulatorOverlayTracker
import me.magnum.melonds.ui.emulator.input.FrontendInputHandler
import me.magnum.melonds.ui.emulator.input.INativeInputListener
import me.magnum.melonds.ui.emulator.input.InputProcessor
import me.magnum.melonds.ui.emulator.input.MelonTouchHandler
import me.magnum.melonds.ui.emulator.model.EmulatorOverlay
import me.magnum.melonds.ui.emulator.model.EmulatorState
import me.magnum.melonds.ui.emulator.model.EmulatorUiEvent
import me.magnum.melonds.ui.emulator.model.PauseMenu
@ -210,9 +212,17 @@ class EmulatorActivity : AppCompatActivity() {
closeRewindWindow()
}
private val showAchievementList = mutableStateOf(false)
private var resumeEmulatorOnActivityResume = true
private var emulatorReady = false
private val activeOverlays = EmulatorOverlayTracker(
onOverlaysCleared = {
disableScreenTimeOut()
},
onOverlaysPresent = {
enableScreenTimeOut()
}
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleOwnerProvider.setCurrentLifecycleOwner(this)
@ -512,24 +522,25 @@ class EmulatorActivity : AppCompatActivity() {
if (viewModel.emulatorState.value.isRunning()) {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
resumeEmulatorOnActivityResume = false
viewModel.pauseEmulator(false)
backPressedCallback.isEnabled = false
activeOverlays.addActiveOverlay(EmulatorOverlay.SWITCH_NEW_ROM_DIALOG)
AlertDialog.Builder(this)
.setTitle(getString(R.string.title_emulator_running))
.setMessage(getString(R.string.message_stop_emulation))
.setPositiveButton(R.string.ok) { _, _ ->
viewModel.stopEmulator()
resumeEmulatorOnActivityResume = true
setIntent(intent)
launchEmulator()
}
.setNegativeButton(R.string.no) { dialog, _ ->
dialog.cancel()
}
.setOnDismissListener {
activeOverlays.removeActiveOverlay(EmulatorOverlay.SWITCH_NEW_ROM_DIALOG)
}
.setOnCancelListener {
resumeEmulatorOnActivityResume = true
backPressedCallback.isEnabled = true
viewModel.resumeEmulator()
}
@ -541,7 +552,7 @@ class EmulatorActivity : AppCompatActivity() {
super.onResume()
binding.surfaceMain.onResume()
if (resumeEmulatorOnActivityResume) {
if (!activeOverlays.hasActiveOverlays()) {
disableScreenTimeOut()
viewModel.resumeEmulator()
}
@ -678,19 +689,18 @@ class EmulatorActivity : AppCompatActivity() {
getString(pauseMenu.options[it].textResource)
}
resumeEmulatorOnActivityResume = false
enableScreenTimeOut()
activeOverlays.addActiveOverlay(EmulatorOverlay.PAUSE_MENU)
AlertDialog.Builder(this)
.setTitle(R.string.pause)
.setItems(options) { _, which ->
val selectedOption = pauseMenu.options[which]
viewModel.onPauseMenuOptionSelected(selectedOption)
disableScreenTimeOut()
}
.setOnDismissListener {
activeOverlays.removeActiveOverlay(EmulatorOverlay.PAUSE_MENU)
}
.setOnCancelListener {
viewModel.resumeEmulator()
resumeEmulatorOnActivityResume = true
disableScreenTimeOut()
}
.show()
}
@ -721,7 +731,6 @@ class EmulatorActivity : AppCompatActivity() {
var adapter: SaveStateListAdapter? = null
adapter = SaveStateListAdapter(slots, picasso, dateFormatter, timeFormatter, {
disableScreenTimeOut()
dialog?.cancel()
onSlotPicked(it)
}) {
@ -730,7 +739,7 @@ class EmulatorActivity : AppCompatActivity() {
}
}
enableScreenTimeOut()
activeOverlays.addActiveOverlay(EmulatorOverlay.SAVE_STATES_DIALOG)
dialog = AlertDialog.Builder(this)
.setTitle(getString(R.string.save_slot))
.setAdapter(adapter) { _, _ ->
@ -738,15 +747,17 @@ class EmulatorActivity : AppCompatActivity() {
.setNegativeButton(R.string.cancel) { _dialog, _ ->
_dialog.cancel()
}
.setOnDismissListener {
activeOverlays.removeActiveOverlay(EmulatorOverlay.SAVE_STATES_DIALOG)
}
.setOnCancelListener {
viewModel.resumeEmulator()
disableScreenTimeOut()
}
.show()
}
private fun showRomLoadErrorDialog() {
enableScreenTimeOut()
activeOverlays.addActiveOverlay(EmulatorOverlay.ROM_LOAD_ERROR_DIALOG)
AlertDialog.Builder(this)
.setCancelable(false)
.setTitle(R.string.error_load_rom)
@ -759,7 +770,7 @@ class EmulatorActivity : AppCompatActivity() {
}
private fun showRomNotFoundDialog(romPath: String) {
enableScreenTimeOut()
activeOverlays.addActiveOverlay(EmulatorOverlay.ROM_NOT_FOUND_DIALOG)
AlertDialog.Builder(this)
.setTitle(R.string.error_rom_not_found)
.setMessage(getString(R.string.error_rom_not_found_info, romPath))
@ -773,7 +784,7 @@ class EmulatorActivity : AppCompatActivity() {
}
private fun showFirmwareLoadErrorDialog(error: EmulatorState.FirmwareLoadError) {
enableScreenTimeOut()
activeOverlays.addActiveOverlay(EmulatorOverlay.FIRMWARE_LOAD_ERROR_DIALOG)
AlertDialog.Builder(this)
.setCancelable(false)
.setTitle(R.string.error_load_firmware)
@ -786,13 +797,13 @@ class EmulatorActivity : AppCompatActivity() {
}
private fun showRewindWindow(rewindWindow: RewindWindow) {
enableScreenTimeOut()
activeOverlays.addActiveOverlay(EmulatorOverlay.REWIND_WINDOW)
binding.root.transitionToState(R.id.rewind_visible)
rewindSaveStateAdapter.setRewindWindow(rewindWindow)
}
private fun closeRewindWindow() {
disableScreenTimeOut()
activeOverlays.removeActiveOverlay(EmulatorOverlay.REWIND_WINDOW)
binding.root.transitionToState(R.id.rewind_hidden)
viewModel.resumeEmulator()
}

View File

@ -346,7 +346,7 @@ class EmulatorViewModel @Inject constructor(
return
}
if (!emulatorSession.areSaveStatesAllowed()) {
if (!emulatorSession.areSaveStateLoadsAllowed()) {
_toastEvent.tryEmit(ToastEvent.RewindNotAvailableWhileRAHardcoreModeEnabled)
return
}
@ -394,17 +394,13 @@ class EmulatorViewModel @Inject constructor(
val currentState = _emulatorState.value
when (currentState) {
is EmulatorState.RunningRom -> {
if (emulatorSession.areSaveStatesAllowed()) {
sessionCoroutineScope.launch {
emulatorManager.pauseEmulator()
val quickSlot = saveStatesRepository.getRomQuickSaveStateSlot(currentState.rom)
if (saveRomState(currentState.rom, quickSlot)) {
_toastEvent.emit(ToastEvent.QuickSaveSuccessful)
}
emulatorManager.resumeEmulator()
sessionCoroutineScope.launch {
emulatorManager.pauseEmulator()
val quickSlot = saveStatesRepository.getRomQuickSaveStateSlot(currentState.rom)
if (saveRomState(currentState.rom, quickSlot)) {
_toastEvent.emit(ToastEvent.QuickSaveSuccessful)
}
} else {
_toastEvent.tryEmit(ToastEvent.CannotUseSaveStatesWhenRAHardcoreIsEnabled)
emulatorManager.resumeEmulator()
}
}
is EmulatorState.RunningFirmware -> {
@ -420,7 +416,7 @@ class EmulatorViewModel @Inject constructor(
val currentState = _emulatorState.value
when (currentState) {
is EmulatorState.RunningRom -> {
if (emulatorSession.areSaveStatesAllowed()) {
if (emulatorSession.areSaveStateLoadsAllowed()) {
sessionCoroutineScope.launch {
emulatorManager.pauseEmulator()
val quickSlot = saveStatesRepository.getRomQuickSaveStateSlot(currentState.rom)
@ -733,9 +729,8 @@ class EmulatorViewModel @Inject constructor(
private fun filterRomPauseMenuOption(option: RomPauseMenuOption): Boolean {
return when (option) {
RomPauseMenuOption.REWIND -> settingsRepository.isRewindEnabled() && emulatorSession.areSaveStatesAllowed()
RomPauseMenuOption.SAVE_STATE -> emulatorSession.areSaveStatesAllowed()
RomPauseMenuOption.LOAD_STATE -> emulatorSession.areSaveStatesAllowed()
RomPauseMenuOption.REWIND -> settingsRepository.isRewindEnabled() && emulatorSession.areSaveStateLoadsAllowed()
RomPauseMenuOption.LOAD_STATE -> emulatorSession.areSaveStateLoadsAllowed()
RomPauseMenuOption.CHEATS -> emulatorSession.areCheatsEnabled()
RomPauseMenuOption.VIEW_ACHIEVEMENTS -> emulatorSession.areRetroAchievementsEnabled()
else -> true

View File

@ -0,0 +1,29 @@
package me.magnum.melonds.ui.emulator.component
import me.magnum.melonds.ui.emulator.model.EmulatorOverlay
class EmulatorOverlayTracker(
private val onOverlaysCleared: () -> Unit,
private val onOverlaysPresent: () -> Unit,
) {
private val activeOverlays = mutableListOf<EmulatorOverlay>()
fun addActiveOverlay(overlay: EmulatorOverlay) {
activeOverlays.add(overlay)
if (activeOverlays.size == 1) {
onOverlaysPresent()
}
}
fun removeActiveOverlay(overlay: EmulatorOverlay) {
activeOverlays.remove(overlay)
if (activeOverlays.isEmpty()) {
onOverlaysCleared()
}
}
fun hasActiveOverlays(): Boolean {
return activeOverlays.isNotEmpty()
}
}

View File

@ -0,0 +1,11 @@
package me.magnum.melonds.ui.emulator.model
enum class EmulatorOverlay {
PAUSE_MENU,
REWIND_WINDOW,
SAVE_STATES_DIALOG,
ROM_LOAD_ERROR_DIALOG,
FIRMWARE_LOAD_ERROR_DIALOG,
ROM_NOT_FOUND_DIALOG,
SWITCH_NEW_ROM_DIALOG,
}

View File

@ -27,6 +27,7 @@ import me.magnum.melonds.common.Permission
import me.magnum.melonds.common.contracts.DirectoryPickerContract
import me.magnum.melonds.databinding.ActivityRomListBinding
import me.magnum.melonds.domain.model.*
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.ui.dsiwaremanager.DSiWareManagerActivity
import me.magnum.melonds.ui.emulator.EmulatorActivity
import me.magnum.melonds.ui.settings.SettingsActivity
@ -98,7 +99,10 @@ class RomListActivity : AppCompatActivity() {
}
updatesViewModel.getAppUpdate().observe(this) {
showUpdateAvailableDialog(it)
when (it.type) {
AppUpdate.Type.PRODUCTION -> showProdUpdateAvailableDialog(it)
AppUpdate.Type.NIGHTLY -> showNightlyUpdateAvailableDialog(it)
}
}
updatesViewModel.getDownloadProgress().observe(this) {
onDownloadProgressUpdated(it)
@ -199,7 +203,7 @@ class RomListActivity : AppCompatActivity() {
romListFragment.setRomSelectedListener { rom -> loadRom(rom) }
}
private fun showUpdateAvailableDialog(update: AppUpdate) {
private fun showProdUpdateAvailableDialog(update: AppUpdate) {
val message = markwon.toMarkdown(update.description)
AlertDialog.Builder(this)
@ -215,6 +219,19 @@ class RomListActivity : AppCompatActivity() {
.show()
}
private fun showNightlyUpdateAvailableDialog(update: AppUpdate) {
AlertDialog.Builder(this)
.setTitle(getString(R.string.nightly_update_available))
.setMessage(getString(R.string.nightly_update_available_message))
.setPositiveButton(R.string.update) { _, _ ->
startUpdateDownload(update)
}
.setNegativeButton(R.string.remind_later_update) { _, _ ->
updatesViewModel.skipUpdate(update)
}
.show()
}
private fun startUpdateDownload(update: AppUpdate) {
downloadProgressDialog?.dismiss()
downloadProgressDialog = AlertDialog.Builder(this)

View File

@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.disposables.CompositeDisposable
import me.magnum.melonds.common.Schedulers
import me.magnum.melonds.domain.model.AppUpdate
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.model.DownloadProgress
import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.domain.services.UpdateInstallManager
@ -47,6 +47,9 @@ class UpdatesViewModel @Inject constructor(
.subscribeOn(schedulers.backgroundThreadScheduler)
.subscribe {
updateDownloadProgressLiveData.postValue(it)
if (it is DownloadProgress.DownloadComplete) {
updatesRepository.notifyUpdateDownloaded(update)
}
}
.addTo(disposables)
}

View File

@ -24,6 +24,7 @@ class GeneralPreferencesFragment : PreferenceFragmentCompat(), PreferenceFragmen
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_general, rootKey)
addPreferencesFromResource(R.xml.pref_general_updates)
rewindPreference = findPreference("enable_rewind")!!
val sustainedPerformancePreference = findPreference<SwitchPreference>("enable_sustained_performance")!!

View File

@ -37,6 +37,8 @@ object RomProcessor {
stream.read(header)
save(KEY_HEADER, header)
val gameCode = String(header, 0x0C, 4)
val arm9Offset = byteArrayToInt(header, 0x20)
val arm9Size = byteArrayToInt(header, 0x2C)
@ -78,22 +80,30 @@ object RomProcessor {
save(KEY_DEVELOPER_NAME, developer)
}
val cartCategory = gameCode[0]
if (cartCategory == 'H' || cartCategory == 'K') {
// This is probably a DSi Ware game. But confirm in a later value processor
register(
RomStreamDataProcessor.SectionProcessor.SectionValueProcessor(
streamOffset = 0x234
) { stream, save ->
val categoryData = ByteArray(4)
stream.read(categoryData)
val categoryId = byteArrayToInt(categoryData)
save(KEY_ROM_IS_DSIWARE_TITLE, categoryId.toUInt() == DSIWARE_CATEGORY)
}
)
} else {
save(KEY_ROM_IS_DSIWARE_TITLE, false)
}
register(arm9Processor)
register(arm7Processor)
register(bannerProcessor)
}
)
)
registerProcessor(
RomStreamDataProcessor.SectionProcessor.SectionValueProcessor(
streamOffset = 0x234
) { stream, save ->
val categoryData = ByteArray(4)
stream.read(categoryData)
val categoryId = byteArrayToInt(categoryData)
save(KEY_ROM_IS_DSIWARE_TITLE, categoryId.toUInt() == DSIWARE_CATEGORY)
}
)
process(inputStream)
}

View File

@ -16,6 +16,8 @@
<string name="undo">Deshacer</string>
<string name="update">Actualizar</string>
<string name="close">Cerrar</string>
<string name="play">Jugar</string>
<string name="ellipsis"></string>
<string name="version_alpha">Alfa</string>
<string name="version_beta">Beta</string>
@ -31,10 +33,13 @@
<string name="size_mb">MB</string>
<string name="size_gb">GB</string>
<string name="error_rom_not_found">No se pudo encontrar ROM</string>
<string name="error_rom_not_found_info">No se ha encontrado la ROM seleccionada. Asegúrese de que la ROM seleccionada se encuentre en el directorio de búsqueda de la ROM actual.\n%1$s</string>
<string name="error_rom_not_found">No se pudo encontrar la ROM</string>
<string name="error_rom_not_found_info">No se ha encontrado la ROM seleccionada. Asegúrate de que la ROM seleccionada se encuentre en el directorio de búsqueda de la ROM actual.\n%1$s</string>
<string name="error_load_rom">No se pudo cargar la ROM</string>
<string name="error_load_rom_report_issue">Se generó un registro de errores que puede ayudar a solucionar este problema. ¿Quieres abrir un problema en GitHub con este registro? Necesitarás una cuenta de GitHub.</string>
<string name="error_load_rom_message">Hubo un problema cargando la ROM. Asegúrate de que no esté dañada e inténtalo de nuevo.</string>
<string name="error_load_firmware">No se pudo cargar el firmware.</string>
<string name="error_load_firmware_message">Hubo un problema al cargar el firmware (%1$s). Asegúrate de que la BIOS y el archivo de firmware no estén dañados, de que el firmware sea arrancable e inténtalo de nuevo.</string>
<string name="error_load_rom_report_issue">Se generó un registro de errores que puede ayudar a solucionar este problema. ¿Deseas abrir un problema en GitHub con este registro? Necesitarás una cuenta de GitHub.</string>
<string name="title_emulator_running">Emulador ejecutándose</string>
<string name="message_stop_emulation">¿Detener la emulación y cargar una nueva ROM?</string>
<string name="error_load_gba_rom">No se pudo cargar la ROM de GBA</string>
@ -63,9 +68,10 @@
<string name="dsiware_manager_import_title_error_not_dsiware_title">No es un título de DSiWare</string>
<string name="dsiware_manager_import_title_error_title_already_imported">Título de DSiWare ya importado</string>
<string name="dsiware_manager_import_title_error_insatll_failed">Error al instalar el título</string>
<string name="dsiware_manager_import_title_error_metadat_fetch_failed">No se pudieron descargar los metadatos del título. Verifica tu conexión a Internet.</string>
<string name="dsiware_manager_import_title_error_unknown">Se ha producido un error desconocido</string>
<string name="dsiware_import_from_file">Desde el archivo</string>
<string name="dsiware_import_from_rom_list">De la lista de ROM</string>
<string name="dsiware_import_from_rom_list">Desde la lista de ROM</string>
<string name="select_dsiware_title">Seleccione el título de DSiWare</string>
<string name="no_dsiware_roms_found">Seleccione el título de DSiWare</string>
@ -76,11 +82,15 @@
<string name="failed_save_state">No se pudo guardar</string>
<string name="failed_load_state">No se pudo cargar</string>
<string name="cant_load_empty_slot">No se puede cargar un espacio vacío</string>
<string name="failed_reset_emulation">No se pudo restablecer</string>
<string name="failed_reset_emulation">No se pudo restablecer la emulación</string>
<string name="save_states_not_supported">Los estados de guardado no son compatibles mientras se ejecuta el firmware</string>
<string name="rewind_time_seconds">%1$ss</string> <!-- Ex: 43.56s -->
<string name="rewind_time_minutes_seconds">%1$dm%2$ss</string> <!-- Ex: 2m37.93s -->
<string name="save_states_unavailable_ra_hardcore_enabled">Guardados no disponibles en modo Hardcore</string>
<string name="rewind_time_seconds">%1$ss</string> <!-- Ej: 43,56s -->
<string name="rewind_time_minutes_seconds">%1$dmin%2$ss</string> <!-- Ej: 2min37,93s -->
<string name="rewind_not_enabled">El rebobinado no está habilitado</string>
<string name="rewind_unavailable_ra_hardcore_enabled">El rebobinado no disponible en modo Hardcore</string>
<string name="no_image_selected">Ninguna imagen seleccionada</string>
<string name="failed_to_load_image">Error al cargar la imagen</string>
<string name="action_sort_alphabetically">Alfabéticamente</string>
<string name="action_sort_recently_played">Recientemente</string>
@ -109,7 +119,8 @@
<string name="choose_component">Elegir componente</string>
<string name="pause">Pausar</string>
<string name="reset">Resetear</string>
<string name="achievements">Logros</string>
<string name="reset">Reestablecer</string>
<string name="exit">Salir</string>
<string name="settings">Configuración</string>
@ -163,9 +174,11 @@
<string name="threaded_rendering">Renderizar en subproceso</string>
<string name="fps_counter_position">Posición del contador de FPS</string>
<string name="threaded_rendering_summary">Mejora el rendimiento de los juegos en 3D cuando está habilitado, pero causará fallas gráficas.</string>
<string name="category_video">Video</string>
<string name="category_video_summary">Filtro, Renderizado de subprocesos, contador de FPS</string>
<string name="category_video">Vídeo</string>
<string name="category_video_summary">Filtro, renderizado de subprocesos, contador de FPS</string>
<string name="filter">Filtro</string>
<string name="dsi_camera_source">Fuente de cámara DSi</string>
<string name="dsi_camera_image">Imagen de cámara DSi</string>
<string name="category_audio">Audio</string>
<string name="category_audio_summary">Volumen, latencia, fuente de micrófono</string>
<string name="enable_sound">Activar sonido</string>
@ -189,13 +202,20 @@
<string name="vibrate_on_touch">Vibrar al tacto</string>
<string name="vibration_strength">Fuerza de vibración</string>
<string name="soft_input_opacity">Opacidad de entrada suave</string>
<string name="retroachievements">RetroAchievements</string> <!-- Mantén las palabras unidas porque este es el nombre oficial -->
<string name="retroachievements_summary">Inicio de sesión &amp; Gestionar la conexión de RetroAchievements</string> <!-- Mantén las palabras unidas porque este es el nombre oficial -->
<string name="enable_rich_presence">Activar la opción para mostrar lo que estás haciendo</string>
<string name="rich_presence_summary">Permite que otros usuarios de RetroAchievement vean lo que estás jugando</string>
<string name="hardcore_mode">Modo Hardcore</string>
<string name="hardcore_mode_summary">Gana el doble de puntos, pero no puedes usar estados de guardado, trucos ni cámara lenta mientras juegas</string>
<string name="cheats">Trucos</string>
<string name="cheats_summary">Habilitar e importar códigos de trucos</string>
<string name="cheats_summary">Habilitar e importar trucos de &amp; trucos</string>
<string name="enable_cheats">Habilitar trucos</string>
<string name="cheats_database">Base de datos de trucos (formato XML)</string>
<string name="import_cheats">Importar trucos</string>
<string name="import_cheats_summary">Importar trucos de archivos base disponibles. Por ahora, solo se admite XML. Se eliminará cualquier truco importado anteriormente</string>
<string name="rom_shortcut">Acceso directo de ROM</string>
<string name="import_cheats_summary">Importar trucos desde archivos de base de datos para tenerlos disponibles. Por ahora, solo se admiten bases de datos en formato XML. Cualquier truco previamente importado se eliminará</string>
<string name="rom_shortcut">Acceso directo de la ROM</string>
<string name="firmware_nickname">Apodo</string>
<string name="firmware_message">Mensaje</string>
@ -218,9 +238,11 @@
<string name="rom_settings">Configuración de Rom</string> <!-- Cadena de accesibilidad. No debe usar acrónimos -->
<string name="label_rom_config_console">Sistema de arranque</string>
<string name="label_rom_config_load_gba_rom">Cargar ROM GBA</string>
<string name="label_rom_config_gba_rom_path">Ruta de acceso ROM GBA</string>
<string name="label_rom_config_load_gba_rom">Cargar ROM de GBA</string>
<string name="label_rom_config_gba_rom_path">Ruta de acceso de la ROM de GBA</string>
<string name="label_rom_config_gba_save_path">Ruta de guardado de GBA</string>
<string name="rom_details_configuration_tab">Configuración</string>
<string name="retro_achievements_tab">Retro Achievements</string> <!-- Mantén las palabras separadas para ayudar con el ajuste de palabras -->
<string name="theme_option_light">Claro</string>
<string name="theme_option_dark">Oscuro</string>
@ -285,11 +307,42 @@
<string name="background_name">Nombre de fondo</string>
<string name="background_deleted">Fondo eliminado</string>
<string name="background_add_wrong_orientation">Se agregó fondo pero no es visible para la orientación seleccionada.</string>
<string name="background_add_processing_failed">Error al procesar la imagen de fondo seleccionada. Asegúrese de que no esté dañado.</string>
<string-array name="fast_forward_speed_multiplier_options">
<string name="background_add_processing_failed">Error al procesar la imagen de fondo seleccionada. Asegúrate de que no esté dañada.</string>
<string name="retro_achievements_login_description">¡Inicia sesión con RetroAchievements para ver los logros que puedes desbloquear mientras juegas este juego!</string>
<string name="login_with_retro_achievements">Inicia sesión con RetroAchievements</string>
<string name="login">Iniciar sesión</string>
<string name="retroachievements_login_summary">¡Inicia sesión con RetroAchievements para desbloquear logros mientras juegas!</string>
<string name="retroachievements_login_status">Sesión iniciada como %1$s</string>
<string name="retroachievements_logout">Cerrar sesión</string>
<string name="retroachievements_logout_confirmation">¿Estás seguro de que quieres cerrar sesión en RetroAchievements? No podrás desbloquear logros mientras juegas.</string>
<string name="retro_achievements_login_error">Hubo un problema al intentar iniciar sesión. Asegúrate de que tu nombre de usuario y contraseña sean correctos y de que estés conectado a Internet</string>
<string name="retro_achievements_login_error_short">Se produjo un problema al intentar iniciar sesión</string>
<string name="retro_achievements_load_error">Se produjo un problema al intentar cargar los logros de este juego. Asegúrate de estar conectado a Internet e intenta de nuevo más tarde</string>
<string name="retro_achievements_no_achievements">No se encontraron RetroAchievements para este juego</string>
<string name="retro_achievements_relaunch_to_apply_settings">Reinicia la ROM para aplicar la nueva configuración de RetroAchievements</string>
<string name="username">Nombre de usuario</string>
<string name="password">Contraseña</string>
<string name="retry">Reintentar</string>
<string name="points_abbreviated">PTS</string>
<string name="points">Puntos</string>
<string name="completed">Completado</string>
<string name="ra_mode_hardcore">Hardcore</string>
<string name="ra_mode_softcore">Softcore</string>
<string name="view_achievement">Ver logro</string>
<string name="completed_achievements"><b>%1$d</b> de <b>%2$d</b> (%3$d%%)</string>
<string name="submitting_achievements">Enviando RetroAchievements…</string>
<string name="achievements_loaded">Logros cargados</string>
<string name="achievements_unlocked_compact">Desbloqueados: %1$d/%2$d</string>
<string name="achievements_failed_load">¡Error al cargar los logros!</string>
<string name="achievements_failed_load_tip">Asegúrate de estar en línea</string>
<string name="achievement_unlocked">¡Logro desbloqueado!</string>
<string name="achievement_missable">Logro perdible</string>
<string name="achievement_missable_description">Este logro se puede perder durante una partida</string>
<string-array name="fast_forward_speed_multiplier_options">
<item>Ilimitado</item>
<item>1.5x</item>
<item>1,5x</item>
<item>2x</item>
<item>3x</item>
<item>4x</item>
@ -297,12 +350,12 @@
</string-array>
<string-array name="rom_icon_filtering_options">
<item>Ninguno (pixelado)</item>
<item>Lineal (suave)</item>
<item>Ninguno (Pixelado)</item>
<item>Lineal (Suave)</item>
</string-array>
<string-array name="game_runtime_console_type_options">
<item>Predeterminado</item>
<item>Por defecto</item>
<item>@string/console_ds</item>
<item>@string/console_dsi</item>
</string-array>
@ -317,7 +370,7 @@
</string-array>
<string-array name="game_runtime_mic_source_options">
<item>Predeterminado</item>
<item>Por defecto</item>
<item>Ninguno</item>
<item>Soplar</item>
<item>Micrófono del dispositivo</item>
@ -337,7 +390,7 @@
<item>Inferior-central</item>
<item>Abajo-derecha</item>
</string-array>
<string-array name="video_filtering_options">
<item>Ninguno</item>
<item>Lineal</item>
@ -348,6 +401,12 @@
<item>LCD</item>
</string-array>
<string-array name="dsi_camera_source_options">
<item>Pantalla negra</item>
<item>Cámaras físicas</item>
<item>Imagen estática</item>
</string-array>
<string-array name="audio_interpolation_options">
<item>Ninguno</item>
<item>Lineal</item>
@ -386,16 +445,16 @@
</string-array>
<string-array name="background_portrait_mode_options">
<item>Elasticidad</item>
<item>Ajuste al centro</item>
<item>Ajuste superior</item>
<item>Ajuste inferior</item>
<item>Estirar</item>
<item>Ajustar al centro</item>
<item>Ajustar a la parte superior</item>
<item>Ajustar a la parte inferior</item>
</string-array>
<string-array name="background_landscape_mode_options">
<item>Elasticidad</item>
<item>Centro de ajuste</item>
<item>Ajuste a la izquierda</item>
<item>Ajuste a la derecha</item>
<item>Estirar</item>
<item>Ajustar al centro</item>
<item>Ajustar a la izquierda</item>
<item>Ajustar a la derecha</item>
</string-array>
</resources>

View File

@ -17,6 +17,8 @@
<string name="undo">Annuler</string>
<string name="update">Mettre à jour</string>
<string name="close">Fermer</string>
<string name="play">Jouer</string>
<string name="ellipsis"></string>
<string name="version_alpha">Alpha</string>
<string name="version_beta">Bêta</string>
@ -35,6 +37,9 @@
<string name="error_rom_not_found">Impossible de trouver la ROM</string>
<string name="error_rom_not_found_info">La ROM que vous avez sélectionnée n\'a pas été trouvée. Assurez-vous que la ROM sélectionnée peut être trouvée dans le répertoire de recherche de ROMs actuel.\n%1$s</string>
<string name="error_load_rom">Impossible de charger la ROM</string>
<string name="error_load_rom_message">Un problème est survenu lors du chargement de la ROM. Veuillez vérifier qu\'elle n\'est pas corrompue et réessayez.</string>
<string name="error_load_firmware">Impossible de charger le firmware</string>
<string name="error_load_firmware_message">Un problème est survenu lors du chargement du firmware (%1$s). Veuillez vérifier que les dumps du BIOS et du firmware ne sont pas corrompus, que le firmare peut être démarré, et réessayez.</string>
<string name="error_load_rom_report_issue">Un journal d\'erreur qui peut aider à résoudre ce problème a été généré. Voulez-vous ouvrir un problème sur GitHub avec ce journal ? Vous aurez besoin d\'un compte GitHub.</string>
<string name="title_emulator_running">Émulateur en cours d\'exécution</string>
<string name="message_stop_emulation">Arrêter l\'émulation et charger une nouvelle ROM ?</string>
@ -58,12 +63,13 @@
<string name="import_dsiware_title">Importer le titre DSiWare</string>
<string name="dsiware_manager_setup">Configuration</string>
<string name="dsiware_manager_fix_setup">Réparer la configuration</string>
<string name="dsiware_manager_load_error">Il y a eu un problème avec la liste de vos titres DSiWare. Veuillez vérifier votre configuration DSi</string>
<string name="dsiware_manager_load_error">Un problème est survenu lors du listage de vos titres DSiWare. Veuillez vérifier votre configuration DSi</string>
<string name="dsiware_manager_import_title_error_open_nand_failed">La NAND n\'a pas pu être ouverte</string>
<string name="dsiware_manager_import_title_error_open_file_failed">Erreur lors de l\'ouverture du fichier sélectionné</string>
<string name="dsiware_manager_import_title_error_not_dsiware_title">Il ne s\'agit pas d\'un titre DSiWare</string>
<string name="dsiware_manager_import_title_error_title_already_imported">Ce titre DSiWare a déjà été importé</string>
<string name="dsiware_manager_import_title_error_insatll_failed">Impossible d\'installer le titre</string>
<string name="dsiware_manager_import_title_error_metadat_fetch_failed">Impossible de télécharger les métadonnées du titre. Veuillez vérifier votre connexion Internet</string>
<string name="dsiware_manager_import_title_error_unknown">Une erreur inconnue est survenue</string>
<string name="dsiware_import_from_file">Depuis le fichier</string>
<string name="dsiware_import_from_rom_list">Depuis la liste des ROMs</string>
@ -79,9 +85,13 @@
<string name="cant_load_empty_slot">Impossible de charger un emplacement de sauvegarde d\'état vide</string>
<string name="failed_reset_emulation">Impossible de réinitialiser l\'émulation</string>
<string name="save_states_not_supported">Les sauvegardes d\'état ne sont pas prises en charge pendant l\'exécution du firmware</string>
<string name="save_states_unavailable_ra_hardcore_enabled">Les sauvegardes d\'état sont indisponibles lorsque le mode Hardcore est activé</string>
<string name="rewind_time_seconds">%1$s s</string>
<string name="rewind_time_minutes_seconds">%1$d min%2$s s</string>
<string name="rewind_not_enabled">Le rembobinage n\'est pas activé</string>
<string name="rewind_unavailable_ra_hardcore_enabled">Le rembobinage n\'est pas disponible lorsque le mode Hardcore est activé</string>
<string name="no_image_selected">Aucune image sélectionnée</string>
<string name="failed_to_load_image">Impossible de charger l\'image</string>
<string name="action_sort_alphabetically">Ordre alphabétique</string>
<string name="action_sort_recently_played">Joué récemment</string>
@ -110,6 +120,7 @@
<string name="choose_component">Choisissez le composant</string>
<string name="pause">Pause</string>
<string name="achievements">Succès</string>
<string name="reset">Redémarrer</string>
<string name="exit">Quitter</string>
@ -167,6 +178,8 @@
<string name="category_video">Vidéo</string>
<string name="category_video_summary">Filtre, rendu fileté, compteur FPS</string>
<string name="filter">Filtre</string>
<string name="dsi_camera_source">Source appareil photo DSi</string>
<string name="dsi_camera_image">Image appareil photo DSi</string>
<string name="category_audio">Audio</string>
<string name="category_audio_summary">Volume, latence, source du microphone</string>
<string name="enable_sound">Activer le son</string>
@ -190,6 +203,13 @@
<string name="vibrate_on_touch">Vibrer au toucher</string>
<string name="vibration_strength">Force de vibration</string>
<string name="soft_input_opacity">Opacité des entrées logicielles</string>
<string name="retroachievements">RetroAchievements</string> <!-- Garder les deux mots collés car il s\'agit du nom officiel -->
<string name="retroachievements_summary">Se connecter et gérer l\'intégration de RetroAchievements</string> <!-- Garder les deux mots collés car il s\'agit du nom officiel -->
<string name="enable_rich_presence">Activer Rich Presence</string>
<string name="rich_presence_summary">Permet aux autres utilisateurs de RetroAchievement de voir ce à quoi vous jouez</string>
<string name="hardcore_mode">Mode Hardcore</string>
<string name="hardcore_mode_summary">Gagnez le double de points, mais vous ne pouvez pas utiliser les sauvegardes d\'état, ni les triches, ni le ralenti en jeu</string>
<string name="cheats">Triches</string>
<string name="cheats_summary">Activer et importer des codes de triche</string>
<string name="enable_cheats">Activer les triches</string>
@ -222,6 +242,8 @@
<string name="label_rom_config_load_gba_rom">Charger la ROM GBA</string>
<string name="label_rom_config_gba_rom_path">Chemin de la ROM GBA</string>
<string name="label_rom_config_gba_save_path">Chemin de la sauvegarde GBA</string>
<string name="rom_details_configuration_tab">Configuration</string>
<string name="retro_achievements_tab">Retro Achievements</string>
<string name="theme_option_light">Clair</string>
<string name="theme_option_dark">Sombre</string>
@ -286,7 +308,38 @@
<string name="background_name">Nom de l\'arrière-plan</string>
<string name="background_deleted">Arrière-plan supprimé</string>
<string name="background_add_wrong_orientation">L\'arrière-plan a été ajouté mais n\'est pas visible pour l\'orientation sélectionnée.</string>
<string name="background_add_processing_failed">Le traitement de l\'image d\'arrière-plan sélectionnée a échoué. Vérifiez qu\'elle n\'est pas corrompue.</string>
<string name="background_add_processing_failed">Le traitement de l\'image d\'arrière-plan sélectionnée a échoué. Veuillez vérifier qu\'elle n\'est pas corrompue.</string>
<string name="retro_achievements_login_description">Connectez-vous à RetroAchievements pour consulter les succès que vous pouvez déverouiller en jouant à ce jeu !</string>
<string name="login_with_retro_achievements">Connexion à RetroAchievements</string>
<string name="login">Connexion</string>
<string name="retroachievements_login_summary">Connectez-vous avec RetroAchievements pour débloquer des succès tout en jouant !</string>
<string name="retroachievements_login_status">Connecté en tant que %1$s</string>
<string name="retroachievements_logout">Déconnexion</string>
<string name="retroachievements_logout_confirmation">Are you sure you want to logout from RetroAchievements? You won\'t be able to unlock achievements while playing.</string>
<string name="retro_achievements_login_error">Un problème est survenu lors de la tentative de connexion. Assurez-vous que votre nom d\'utilisateur et votre mot de passe sont corrects et que vous êtes connecté à Internet</string>
<string name="retro_achievements_login_error_short">Un problème est survenu lors de la connexion</string>
<string name="retro_achievements_load_error">Un problème est survenu lors du chargement des succès de ce jeu. Assurez-vous que vous êtes connecté à Internet et réessayez plus tard</string>
<string name="retro_achievements_no_achievements">Aucun RetroAchievements n\'a été trouvé pour ce jeu</string>
<string name="retro_achievements_relaunch_to_apply_settings">Redémarrez la ROM pour appliquer les nouveaux paramètres de RetroAchievements</string>
<string name="username">Nom d\'utilisateur</string>
<string name="password">Mot de passe</string>
<string name="retry">Réessayer</string>
<string name="points_abbreviated">PTS</string>
<string name="points">Points</string>
<string name="completed">Terminé</string>
<string name="ra_mode_hardcore">Hardcore</string>
<string name="ra_mode_softcore">Softcore</string>
<string name="view_achievement">Consulter le succès</string>
<string name="completed_achievements"><b>%1$d</b> sur <b>%2$d</b> (%3$d %%)</string>
<string name="submitting_achievements">Envoi des RetroAchievements…</string>
<string name="achievements_loaded">Succès chargés</string>
<string name="achievements_unlocked_compact">Déverouillé : %1$d/%2$d</string>
<string name="achievements_failed_load">Impossible de charger les succès !</string>
<string name="achievements_failed_load_tip">Veuillez vérifier que vous êtes en ligne</string>
<string name="achievement_unlocked">Succès déverouillé !</string>
<string name="achievement_missable">Manquable</string>
<string name="achievement_missable_description">Ce succès peut être manqué pendant une session de jeu.</string>
<string-array name="fast_forward_speed_multiplier_options">
<item>Illimitée</item>
@ -349,6 +402,12 @@
<item>LCD</item>
</string-array>
<string-array name="dsi_camera_source_options">
<item>Écran noir</item>
<item>Caméras physiques</item>
<item>Image statique</item>
</string-array>
<string-array name="audio_interpolation_options">
<item>Aucune</item>
<item>Linéaire</item>

View File

@ -15,20 +15,34 @@
<string name="preview">Предпросмотр</string>
<string name="undo">Отмена</string>
<string name="update">Обновить</string>
<string name="close">Закрыть</string>
<string name="play">Запуск</string>
<string name="ellipsis"></string>
<string name="version_alpha">Альфа</string>
<string name="version_beta">Бета</string>
<string name="console_ds">DS</string>
<string name="console_dsi">DSi (экспериментально)</string>
<string name="console_ds_full">Nintendo DS</string>
<string name="console_dsi_full">Nintendo DSi</string>
<string name="size_bytes">Б</string>
<string name="size_kb">КБ</string>
<string name="size_mb">МБ</string>
<string name="size_gb">ГБ</string>
<string name="error_rom_not_found">Игра не найдена</string>
<string name="error_rom_not_found_info">Выбранный ROM не найден. Убедитесь, что файл присутствует в текущем каталоге поиска.\n%1$s</string>
<string name="error_rom_not_found_info">Выбранный образ игры не найден. Убедитесь, что файл присутствует в текущем каталоге поиска.\n%1$s</string>
<string name="error_load_rom">Не удалось загрузить игру</string>
<string name="error_load_rom_message">Ошибка при загрузке игры. Убедитесь, что образ не повреждён и повторите попытку.</string>
<string name="error_load_firmware">Не удалось загрузить прошивку</string>
<string name="error_load_firmware_message">Ошибка при загрузке прошивки (%1$s). Убедитесь, что образы прошивки и BIOS не повреждены, прошивка является загрузочной и повторите попытку.</string>
<string name="error_load_rom_report_issue">Сгенерирован лог об ошибке, который может помочь устранить проблему. Создать на GitHub обращение с содержимым лога? Требуется аккаунт GitHub.</string>
<string name="title_emulator_running">Эмулятор запущен</string>
<string name="message_stop_emulation">Остановить эмуляцию и загрузить новый файл?</string>
<string name="error_load_gba_rom">Не удалось загрузить файл GBA</string>
<string name="error_load_gba_rom">Не удалось загрузить образ GBA</string>
<string name="action_search_roms">Поиск игр</string>
<string name="action_sort_roms">Сортировка</string>
<string name="action_boot_firmware">Загрузить прошивку</string>
@ -41,6 +55,27 @@
<string name="download_progress_sizes">%1$.1fMB/%2$.1fMB</string>
<string name="update_download_failed">Не удалось загрузить обновление</string>
<string name="dsiware_manager">Менеджер DSiWare</string>
<string name="no_dsiware_titles_installed">Нет установленных игр DSiWare</string>
<string name="dsiware_manager_no_dsi_setup">Система DSi не настроена</string>
<string name="dsiware_manager_invalid_dsi_setup">Неправильные установки системы DSi</string>
<string name="import_dsiware_title">Импорт игры DSiWare</string>
<string name="dsiware_manager_setup">Настройка</string>
<string name="dsiware_manager_fix_setup">Исправить настройку</string>
<string name="dsiware_manager_load_error">Ошибка при создании списка игр DSiWare. Проверьте установки DSi</string>
<string name="dsiware_manager_import_title_error_open_nand_failed">Не удалось открыть NAND</string>
<string name="dsiware_manager_import_title_error_open_file_failed">Ошибка запуска выбранного файла</string>
<string name="dsiware_manager_import_title_error_not_dsiware_title">Не является игрой DSiWare</string>
<string name="dsiware_manager_import_title_error_title_already_imported">Данная игра DSiWare уже импортирована</string>
<string name="dsiware_manager_import_title_error_insatll_failed">Не удалось установить игру</string>
<string name="dsiware_manager_import_title_error_metadat_fetch_failed">Не удалось загрузить метаданные игры. Проверьте соединение с Интернет</string>
<string name="dsiware_manager_import_title_error_unknown">Произошла неизвестная ошибка</string>
<string name="dsiware_import_from_file">Из файла</string>
<string name="dsiware_import_from_rom_list">Из списка игр</string>
<string name="select_dsiware_title">Выберите игру DSiWare</string>
<string name="no_dsiware_roms_found">Не найдены игры DSiWare</string>
<string name="info_fps">FPS: %1$d</string>
<string name="info_loading">ЗАГРУЗКА…</string>
<string name="save_state_slot">%1$s.</string>
@ -49,9 +84,13 @@
<string name="cant_load_empty_slot">Невозможно загрузить пустой слот сохранения</string>
<string name="failed_reset_emulation">Не удалось перезапустить эмуляцию</string>
<string name="save_states_not_supported">Сохранения недоступны во время запуска прошивки</string>
<string name="save_states_unavailable_ra_hardcore_enabled">Сохранения недоступны пока включен режим хардкора</string>
<string name="rewind_time_seconds">%1$ss</string>
<string name="rewind_time_minutes_seconds">%1$dm%2$ss</string>
<string name="rewind_not_enabled">Перемотка назад не включена</string>
<string name="rewind_not_enabled">Обратная перемотка не включена</string>
<string name="rewind_unavailable_ra_hardcore_enabled">Обратная перемотка недоступна пока включен режим хардкора</string>
<string name="no_image_selected">Образ не выбран</string>
<string name="failed_to_load_image">Не удалось загрузить образ</string>
<string name="action_sort_alphabetically">По алфавиту</string>
<string name="action_sort_recently_played">Недавние</string>
@ -72,68 +111,74 @@
<string name="error_invalid_directory">Неправильный каталог</string>
<string name="error_invalid_directory_description">Невозможно произвести запись в выбранный каталог. Пожалуйста, укажите другой. Если выбран архив, попробуйте извлечь его содержимое.</string>
<string name="no_roms_found">Игры не найдены</string>
<string name="dsiware_title_cannot_be_launched_directly_title">Запуск игр DSiWare невозможен</string>
<string name="dsiware_title_cannot_be_launched_directly_message">Игры DSiWare нельзя запускать напрямую. Они должны быть установлены в NAND (с помощью менеджера DSiWare) и запускаться через программное обеспечение DSi.</string>
<string name="press_any_button">Нажмите любую кнопку…</string>
<string name="choose_component">Выберите компонент</string>
<string name="pause">Пауза</string>
<string name="achievements">Достижения</string>
<string name="reset">Сброс</string>
<string name="exit">Выход</string>
<string name="settings">Настройки</string>
<string name="save_state">Сохранение</string>
<string name="load_state">Загрузка</string>
<string name="rewind">Перемотка</string>
<string name="rewind">Обратная перемотка</string>
<string name="save_slot">Слот сохранения</string>
<string name="empty_slot">%1$s. &lt;Пусто&gt;</string>
<string name="quick_slot">Быстрый слот</string>
<string name="saved">Сохранено</string>
<string name="loaded">Загружено</string>
<string name="title_activity_settings">Настройки</string>
<string name="category_general">Основные</string>
<string name="category_general">Общие</string>
<string name="category_general_summary">Тема, общие настройки</string>
<string name="category_roms">Образы игр</string>
<string name="category_roms_summary">Каталог поиска, сглаживание иконок, настройки кэширования</string>
<string name="category_system">Система</string>
<string name="category_system_summary">Настройки прошивки, выбор BIOS, JIT</string>
<string name="theme">Тема</string>
<string name="fast_forward_max_speed">Макс. ускорение</string>
<string name="rewind_description">Включение активирует автоматическое сохранение через заданный период времени. При необходимости отмотать прогресс откройте меню перемотки и выберите сохранение, с которого хотите продолжить.\n\nУчитывайте, что включение данной функции может привести к появлению подтормаживаний, если ваше устройство недостаточно мощное. Также, потребуется значительный объём памяти в зависимости от длительности хранения и частоты создания сохранений.</string>
<string name="rewind_save_period">Частота сохранения</string>
<string name="fast_forward_max_speed">Максимальное ускорение</string>
<string name="rewind_description">При включении активирует автоматическое сохранение через заданный период времени. Для того, чтобы отмотать прогресс откройте меню обратной перемотки и выберите сохранение, с которого хотите продолжить.\n\nПри включении обратной перемотки возможно падение скорости, если устройство недостаточно мощное. В зависимости от выбранных параметров может потребоваться значительный объём свободной памяти.</string>
<string name="rewind_save_period">Частота сохранений</string>
<string name="rewind_length">Буфер перемотки</string>
<string name="rewind_max_memory_usage">Макс. объём памяти: %1$s</string>
<string name="rewind_max_memory_usage">Максимальный объём памяти: %1$s</string>
<string name="rewind_memory_usage_above_recommended_limit">Превышен рекомендуемый объём памяти</string>
<string name="sustained_performance_mode">Режим устойчивой производительности</string>
<string name="sustained_performance_mode_summary">При включении снижает пиковую производительность и уменьшает троттлинг.</string>
<string name="rom_icon_filtering">Сглаживание иконок</string>
<string name="max_rom_cache_size">Макс. размер кэша игр</string>
<string name="clear_extracted_rom_cache">Удалить кэш извлечённых игр</string>
<string name="max_rom_cache_size">Максимальный размер кэша игр</string>
<string name="clear_extracted_rom_cache">Очистить кэш извлечённых игр</string>
<string name="cache_size">Размер кэша: %s</string>
<string name="cache_size_calculating">Размер кэша: вычисление…</string>
<string name="rom_search_directory">Каталог поиска</string>
<string name="use_custom_bios">Исп. внешние BIOS и прошивку</string>
<string name="console_type">Система по умолчанию</string>
<string name="internal_firmware_settings">Настройки прошивки</string>
<string name="internal_firmware_settings_summary">Параметры встроенного программного обеспечения.</string>
<string name="custom_bios_firmware">Ручная установка BIOS и прошивки</string>
<string name="custom_bios_firmware_description">Использование внешних BIOS и прошивки позволяет запускать и настраивать прошивку DS без загрузки игры, как на реальной системе</string>
<string name="internal_firmware_settings_summary">Установки встроенного программного обеспечения.</string>
<string name="custom_bios_firmware">Ручной выбор BIOS и прошивки</string>
<string name="custom_bios_firmware_description">Использование внешних BIOS и прошивки позволяет запускать и настраивать программное обеспечение DS без запуска игр, как на реальной консоли.</string>
<string name="bios_directory">Каталог DS BIOS</string>
<string name="dsi_bios_directory">Каталог DSi BIOS</string>
<string name="show_boot_screen">Показывать экран загрузки</string>
<string name="mac_address">MAC-адрес</string>
<string name="randomize_mac_address">Случайный MAC-адрес</string>
<string name="randomize_mac_address_summary">Если включено, при каждой загрузке будет использоваться случайный MAC-адрес. Это может привести к тому, что некоторые игры будут определять, что они запущены на другой системе и к сбоям событий с привязкой ко времени.</string>
<string name="randomize_mac_address_summary">Если включено, при каждой загрузке будет сгенерирован случайный MAC-адрес. Это может привести к тому, что некоторые игры будут определять, что они запущены на другой системе и к сбоям событий с привязкой ко времени.</string>
<string name="generate_new_mac_address">Сгенерировать новый</string>
<string name="generate_new_mac_address_description">Создание нового MAC-адреса может привести к тому, что игры будут определять, что они запущены на другой системе.</string>
<string name="generate_new_mac_address_description">Создание нового MAC-адреса может привести к тому, что игры будут определять, что они запущены на другой системе.</string>
<string name="enable_jit">Включить JIT</string>
<string name="enable_jit_summary">Улучшает производительность, но может вызывать подвисания и вылеты.</string>
<string name="jit_not_supported">JIT доступен для 64-битных устройств.</string>
<string name="jit_not_supported">JIT доступен только для 64-битных устройств.</string>
<string name="threaded_rendering">Рендеринг в отдельном потоке</string>
<string name="fps_counter_position">Положение счётчика FPS</string>
<string name="threaded_rendering_summary">Повышает производительность в 3D-играх, но может приводить к ошибкам графики.</string>
<string name="fps_counter_position">Отображение счётчика FPS</string>
<string name="threaded_rendering_summary">Повышает производительность в 3D-играх, но может вызывать проблемы с графикой.</string>
<string name="category_video">Видео</string>
<string name="category_video_summary">Сглаживание, рендеринг в отдельном потоке, счётчик FPS</string>
<string name="filter">Сглаживание</string>
<string name="dsi_camera_source">Источник камеры DSi</string>
<string name="dsi_camera_image">Образ камеры DSi</string>
<string name="category_audio">Аудио</string>
<string name="category_audio_summary">Громкость, задержка, источник микрофона</string>
<string name="enable_sound">Включить звук</string>
@ -155,14 +200,21 @@
<string name="key_mapping_summary">Назначить кнопки контроллера</string>
<string name="show_soft_input">Экранные кнопки</string>
<string name="vibrate_on_touch">Вибрация при касании</string>
<string name="vibration_strength">Сила вибрации</string>
<string name="vibration_strength">Интенсивность вибрации</string>
<string name="soft_input_opacity">Видимость экранных кнопок</string>
<string name="retroachievements">RetroAchievements</string> <!-- Keep words together because this is the official name -->
<string name="retroachievements_summary">Вход и управление интеграцией с RetroAchievements</string> <!-- Keep words together because this is the official name -->
<string name="enable_rich_presence">Включить статус активности</string>
<string name="rich_presence_summary">Позволяет другим пользователям RetroAchievements видеть запущенную вами игру.</string>
<string name="hardcore_mode">Режим хардкора</string>
<string name="hardcore_mode_summary">Удваивает количество очков, но отключает быстрые сохранения, читы и замедление во время игры</string>
<string name="cheats">Чит-коды</string>
<string name="cheats_summary">Активация и импорт чит-кодов</string>
<string name="cheats_summary">Включение и импорт чит-кодов</string>
<string name="enable_cheats">Включить чит-коды</string>
<string name="cheats_database">База чит-кодов (в формате XML)</string>
<string name="import_cheats">Импорт чит-кодов</string>
<string name="import_cheats_summary">Для доступа к чит-кодам импортируйте их из внешней базы. Поддерживаются только файлы в формате XML. Все ранее добавленные коды будут удалены.</string>
<string name="import_cheats_summary">Для доступа к чит-кодам импортируйте их из внешней базы. Поддерживаются файлы в формате XML. Все ранее добавленные коды будут удалены.</string>
<string name="rom_shortcut">Ярлык для игры</string>
<string name="firmware_nickname">Никнейм</string>
@ -176,7 +228,9 @@
<string name="starting">Запуск…</string>
<string name="move_to_background">Переместить на задний план</string>
<string name="failed_save_cheat_changes">Не удалось сохранить изменения чит-кода</string>
<string name="no_cheats_found">Не найдены чит-коды для текущей игры. Попробуйте импортировать другую базу.</string>
<string name="no_cheats_found">Не найдены чит-коды для данной игры. Попробуйте импортировать другую базу.</string>
<string name="enabled_cheats">Активные чит-коды</string>
<string name="no_enabled_cheats_for_rom">Нет активных чит-кодов для данной игры</string>
<string name="day">День</string>
<string name="month">Месяц</string>
<string name="symbol_increase">+</string>
@ -184,9 +238,11 @@
<string name="rom_settings">Настройки игры</string> <!-- Accessibility string. Should not use acronyms -->
<string name="label_rom_config_console">Система загрузки</string>
<string name="label_rom_config_load_gba_rom">Загрузить файл GBA</string>
<string name="label_rom_config_gba_rom_path">Путь к файлу GBA</string>
<string name="label_rom_config_load_gba_rom">Загрузить образ GBA</string>
<string name="label_rom_config_gba_rom_path">Путь к образу GBA</string>
<string name="label_rom_config_gba_save_path">Путь к сохранению GBA</string>
<string name="rom_details_configuration_tab">Конфигурация</string>
<string name="retro_achievements_tab">Retro Achievements</string> <!-- Keep words separated to help with word wrapping -->
<string name="theme_option_light">Светлая</string>
<string name="theme_option_dark">Тёмная</string>
@ -215,7 +271,7 @@
<string name="input_reset">@string/reset</string>
<string name="input_dpad">DPAD</string>
<string name="input_abxy_buttons">Кнопки ABXY</string>
<string name="input_swap_screens">Поменять экраны</string>
<string name="input_swap_screens">Переключить экраны</string>
<string name="input_quick_save">Быстрое сохранение</string>
<string name="input_quick_load">Быстрая загрузка</string>
@ -252,6 +308,37 @@
<string name="background_deleted">Фон удалён</string>
<string name="background_add_wrong_orientation">Фон добавлен, но не отображается в выбранной ориентации.</string>
<string name="background_add_processing_failed">Не удалось обработать выбранное изображение. Убедитесь, что файл не повреждён.</string>
<string name="retro_achievements_login_description">Выполните вход в RetroAchievements для просмотра достижений, доступных для данной игры.</string>
<string name="login_with_retro_achievements">Войти в RetroAchievements</string>
<string name="login">Вход</string>
<string name="retroachievements_login_summary">Выполните вход в RetroAchievements для получения достижений во время игры!</string>
<string name="retroachievements_login_status">Выполнен вход для %1$s</string>
<string name="retroachievements_logout">Выход</string>
<string name="retroachievements_logout_confirmation">Вы подтверждаете выход из RetroAchievements? Получение достижений во время игры будет недоступно.</string>
<string name="retro_achievements_login_error">Ошибка при попытке входа. Проверьте соединение с Интернет и убедитесь, что имя пользователя и пароль введены верно.</string>
<string name="retro_achievements_login_error_short">Ошибка при попытке входа.</string>
<string name="retro_achievements_load_error">Ошибка при загрузке достижений для данной игры. Проверьте соединение с Интернет и повторите попытку.</string>
<string name="retro_achievements_no_achievements">Не найдены доступные достижения для данной игры.</string>
<string name="retro_achievements_relaunch_to_apply_settings">Перезапустите игру, чтобы применить новые настройки RetroAchievements</string>
<string name="username">Имя пользователя</string>
<string name="password">Пароль</string>
<string name="retry">Повторить</string>
<string name="points_abbreviated">ОЧК</string>
<string name="points">Очки</string>
<string name="completed">Завершено</string>
<string name="ra_mode_hardcore">Хардкор</string>
<string name="ra_mode_softcore">Софткор</string>
<string name="view_achievement">Просмотр достижения</string>
<string name="completed_achievements"><b>%1$d</b> из <b>%2$d</b> (%3$d%%)</string>
<string name="submitting_achievements">Отправка RetroAchievements…</string>
<string name="achievements_loaded">Достижения загружены</string>
<string name="achievements_unlocked_compact">Открыто: %1$d/%2$d</string>
<string name="achievements_failed_load">Не удалось загрузить достижения!</string>
<string name="achievements_failed_load_tip">Убедитесь, что вы онлайн</string>
<string name="achievement_unlocked">Открыто достижение!</string>
<string name="achievement_missable">Безвозвратное</string>
<string name="achievement_missable_description">Данное достижение может быть упущено при прохождении.</string>
<string-array name="fast_forward_speed_multiplier_options">
<item>Без ограничения</item>
@ -314,6 +401,12 @@
<item>LCD</item>
</string-array>
<string-array name="dsi_camera_source_options">
<item>Чёрный экран</item>
<item>Камеры устройства</item>
<item>Статичное изображение</item>
</string-array>
<string-array name="audio_interpolation_options">
<item>Нет</item>
<item>Линейная</item>

View File

@ -49,7 +49,10 @@
<string name="action_refresh_rom_list">Refresh ROM list</string>
<string name="hint_search_roms">Search ROMs…</string>
<string name="update_available">Update available: %1$s</string>
<string name="nightly_update_available">Nightly update available</string>
<string name="nightly_update_available_message">Do you want to download it now?</string>
<string name="skip_update">Skip</string>
<string name="remind_later_update">Later</string>
<string name="downloading_update">Downloading update…</string>
<string name="starting_download">Starting download…</string>
<string name="download_progress_sizes">%1$.1fMB/%2$.1fMB</string>
@ -148,6 +151,8 @@
<string name="rewind_memory_usage_above_recommended_limit">Memory usage above recommended limit</string>
<string name="sustained_performance_mode">Sustained performance mode</string>
<string name="sustained_performance_mode_summary">When enabled, peak performance will be lower but thermal throttling will be less pronounced.</string>
<string name="check_for_updates">Check for updates</string>
<string name="check_for_updates_summary">When enabled, the app will periodically check for available updates</string>
<string name="rom_icon_filtering">Icon filtering</string>
<string name="max_rom_cache_size">Max. ROM cache size</string>
<string name="clear_extracted_rom_cache">Clear extracted ROM cache</string>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen>
<!-- Empty on purpose since not all builds have update options -->
</PreferenceScreen>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">melonDS Nightly</string>
</resources>

View File

@ -1,7 +1,7 @@
package me.magnum.melonds.playstore
import io.reactivex.Maybe
import me.magnum.melonds.domain.model.AppUpdate
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.repositories.UpdatesRepository
class PlayStoreUpdatesRepository : UpdatesRepository {
@ -12,4 +12,8 @@ class PlayStoreUpdatesRepository : UpdatesRepository {
override fun skipUpdate(update: AppUpdate) {
// Do nothing. Update checking not supported in the Play Store version
}
override fun notifyUpdateDownloaded(update: AppUpdate) {
// Do nothing
}
}

View File

@ -1,8 +1,8 @@
package me.magnum.melonds.services
import io.reactivex.Observable
import me.magnum.melonds.domain.model.AppUpdate
import me.magnum.melonds.domain.model.DownloadProgress
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.services.UpdateInstallManager
class PlayStoreUpdateInstallManager : UpdateInstallManager {

View File

@ -1,9 +1,9 @@
object AppConfig {
const val compileSdkVersion = 33
const val compileSdkVersion = 34
const val targetSdkVersion = compileSdkVersion
const val minSdkVersion = 24
const val ndkVersion = "25.1.8937393"
const val versionCode = 26
const val versionName = "Beta 1.8.0"
const val versionCode = 27
const val versionName = "Beta 1.9.0"
}

View File

@ -3,11 +3,11 @@ apply plugin: 'kotlin-android'
android {
namespace "com.smp.masterswitchpreference"
compileSdk 33
compileSdk 34
defaultConfig {
minSdkVersion 19
targetSdkVersion 33
targetSdkVersion 34
versionCode 6
versionName "0.9.4"