Skip to content

Added language support for Epic (also shrinks game sizes)#754

Merged
utkarshdalal merged 1 commit intoutkarshdalal:masterfrom
unbelievableflavour:epic-multi-lang-support
Mar 9, 2026
Merged

Added language support for Epic (also shrinks game sizes)#754
utkarshdalal merged 1 commit intoutkarshdalal:masterfrom
unbelievableflavour:epic-multi-lang-support

Conversation

@unbelievableflavour
Copy link
Contributor

@unbelievableflavour unbelievableflavour commented Mar 7, 2026

Currently it downloads all files with all tags.. Only files with "no tags" are required. And language specific files would be located under a "language" tag. So implemented that.


Summary by cubic

Adds language-aware downloads for Epic. We now install required files plus the selected container language to cut download/install sizes and match the GOG/Steam language setting.

  • New Features
    • Map container languages to Epic install tags (e.g., german → ["German", "de-DE", "de"]; includes schinese/tchinese/latam, etc.).
    • Download only required files + selected language tags; clear errors if none found.
    • Fallbacks: unknown language → English tags; if no tag matches → required-only.
    • UI passes container language to the service; service and download manager apply it.
    • Manifest utils add helpers to pick files/chunks and compute sizes for selected tags.
    • Game details show sizes for required-only content.

Written for commit 9805f4c. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Language-to-install-tag mapping and a fallback language so installs can pick tag-specific content.
    • Tag-aware manifest selection to compute download and install sizes for selected language tags.
  • Improvements

    • UI now passes container language through the download flow so installs respect container language.
    • Size calculations unified using tag-aware logic.
  • Bug Fixes

    • Better validation and clearer error messages when manifest chunks or files are missing for selected tags.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 670da620-1f55-4727-aa11-33239d77cd74

📥 Commits

Reviewing files that changed from the base of the PR and between 1384194 and 9805f4c.

📒 Files selected for processing (6)
  • app/src/main/java/app/gamenative/service/epic/EpicConstants.kt
  • app/src/main/java/app/gamenative/service/epic/EpicDownloadManager.kt
  • app/src/main/java/app/gamenative/service/epic/EpicManager.kt
  • app/src/main/java/app/gamenative/service/epic/EpicService.kt
  • app/src/main/java/app/gamenative/service/epic/manifest/ManifestUtils.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/EpicAppScreen.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/main/java/app/gamenative/service/epic/EpicManager.kt
  • app/src/main/java/app/gamenative/service/epic/EpicConstants.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/EpicAppScreen.kt

📝 Walkthrough

Walkthrough

Adds container-language-aware install-tag mapping and selection for Epic downloads, updates download APIs to accept a containerLanguage, and extends ManifestUtils to compute files/chunks/sizes based on selected install tags with fallback to English.

Changes

Cohort / File(s) Summary
Constants & Mappings
app/src/main/java/app/gamenative/service/epic/EpicConstants.kt
Add EPIC_FALLBACK_CONTAINER_LANGUAGE, CONTAINER_LANGUAGE_TO_EPIC_INSTALL_TAGS map, and containerLanguageToEpicInstallTags() helper.
Manifest Utilities
app/src/main/java/app/gamenative/service/epic/manifest/ManifestUtils.kt
Add tag-aware functions: getRequiredInstallFiles(), getRequiredChunksForFileList(), getFilesForSelectedInstallTags(), getSizesForSelectedInstallTags(); refactor getRequiredChunks() to delegate.
Download Flow
app/src/main/java/app/gamenative/service/epic/EpicDownloadManager.kt, app/src/main/java/app/gamenative/service/epic/EpicService.kt
Change public API to accept containerLanguage; compute install tags via constants, derive files/chunks via ManifestUtils, validate and fail early when chunks/files missing, and use derived sizes for download logic.
Size Calculation
app/src/main/java/app/gamenative/service/epic/EpicManager.kt
Replace separate install/download size computations with single call to ManifestUtils.getSizesForSelectedInstallTags() and unpack returned pair.
UI Integration
app/src/main/java/app/gamenative/ui/screen/library/appscreen/EpicAppScreen.kt
Load container data in performDownload and pass containerData.language into EpicService.downloadGame().

Sequence Diagram

sequenceDiagram
    actor User
    participant EpicAppScreen
    participant EpicService
    participant EpicDownloadManager
    participant EpicConstants
    participant ManifestUtils

    User->>EpicAppScreen: initiate download
    EpicAppScreen->>EpicAppScreen: loadContainerData()
    EpicAppScreen->>EpicService: downloadGame(..., containerLanguage)
    EpicService->>EpicDownloadManager: downloadGame(..., containerLanguage)
    EpicDownloadManager->>EpicConstants: containerLanguageToEpicInstallTags(containerLanguage)
    EpicConstants-->>EpicDownloadManager: selectedTags
    EpicDownloadManager->>ManifestUtils: getFilesForSelectedInstallTags(manifest, selectedTags)
    ManifestUtils-->>EpicDownloadManager: files
    EpicDownloadManager->>ManifestUtils: getRequiredChunksForFileList(manifest, files)
    ManifestUtils-->>EpicDownloadManager: chunks
    rect rgba(200,100,100,0.5)
    alt chunks exist AND files non-empty
        EpicDownloadManager->>ManifestUtils: getSizesForSelectedInstallTags(manifest, selectedTags)
        ManifestUtils-->>EpicDownloadManager: (downloadSize, installSize)
        EpicDownloadManager->>EpicDownloadManager: proceed with download using sizes
    else missing chunks OR no matching files
        EpicDownloadManager-->>EpicDownloadManager: fail with descriptive error
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through tags and language maps,
I sniff out chunks in hidden gaps,
With fallback English in my paws,
I stitch installs without a pause,
Hooray — each game finds just the right chaps! 🎮

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Added language support for Epic (also shrinks game sizes)' directly and clearly describes the main changes: introducing container language support for Epic downloads with the secondary benefit of reducing game sizes.
Docstring Coverage ✅ Passed Docstring coverage is 88.24% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/main/java/app/gamenative/service/epic/EpicService.kt">

<violation number="1" location="app/src/main/java/app/gamenative/service/epic/EpicService.kt:349">
P2: Download became language-dependent, but manifest size calculation/precheck still ignores language tags, causing potentially incorrect install/download size estimates and disk-space gating.</violation>
</file>

<file name="app/src/main/java/app/gamenative/service/epic/EpicManager.kt">

<violation number="1" location="app/src/main/java/app/gamenative/service/epic/EpicManager.kt:955">
P2: `fetchManifestSizes` now returns required-only sizes via `emptyList()` tags, which can underestimate install/download requirements in callers that use it for install space checks.</violation>
</file>

<file name="app/src/main/java/app/gamenative/service/epic/EpicDownloadManager.kt">

<violation number="1" location="app/src/main/java/app/gamenative/service/epic/EpicDownloadManager.kt:136">
P2: DLC download path bypasses new install-tag language filtering and still downloads unfiltered manifest content.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

}

fun downloadGame(context: Context, appId: Int, dlcGameIds: List<Int>, installPath: String): Result<DownloadInfo> {
fun downloadGame(context: Context, appId: Int, dlcGameIds: List<Int>, installPath: String, containerLanguage: String): Result<DownloadInfo> {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Download became language-dependent, but manifest size calculation/precheck still ignores language tags, causing potentially incorrect install/download size estimates and disk-space gating.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/service/epic/EpicService.kt, line 349:

<comment>Download became language-dependent, but manifest size calculation/precheck still ignores language tags, causing potentially incorrect install/download size estimates and disk-space gating.</comment>

<file context>
@@ -346,7 +346,7 @@ class EpicService : Service() {
         }
 
-        fun downloadGame(context: Context, appId: Int, dlcGameIds: List<Int>, installPath: String): Result<DownloadInfo> {
+        fun downloadGame(context: Context, appId: Int, dlcGameIds: List<Int>, installPath: String, containerLanguage: String): Result<DownloadInfo> {
             val instance = getInstance() ?: return Result.failure(Exception("Service not available"))
 
</file context>
Fix with Cubic

val installSize = manifest.fileManifestList?.elements?.sumOf { it.fileSize } ?: 0L
val downloadSize = app.gamenative.service.epic.manifest.ManifestUtils.getTotalDownloadSize(manifest)
// Required-only sizes for detail page display (download uses container language via getSizesForSelectedInstallTags elsewhere).
val (downloadSize, installSize) = app.gamenative.service.epic.manifest.ManifestUtils.getSizesForSelectedInstallTags(manifest, emptyList())
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: fetchManifestSizes now returns required-only sizes via emptyList() tags, which can underestimate install/download requirements in callers that use it for install space checks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/service/epic/EpicManager.kt, line 955:

<comment>`fetchManifestSizes` now returns required-only sizes via `emptyList()` tags, which can underestimate install/download requirements in callers that use it for install space checks.</comment>

<file context>
@@ -951,9 +951,8 @@ class EpicManager @Inject constructor(
-            val installSize = manifest.fileManifestList?.elements?.sumOf { it.fileSize } ?: 0L
-            val downloadSize = app.gamenative.service.epic.manifest.ManifestUtils.getTotalDownloadSize(manifest)
+            // Required-only sizes for detail page display (download uses container language via getSizesForSelectedInstallTags elsewhere).
+            val (downloadSize, installSize) = app.gamenative.service.epic.manifest.ManifestUtils.getSizesForSelectedInstallTags(manifest, emptyList())
             Timber.tag("Epic").d(
                 "Manifest stats for $appName: version=${manifest.version}, featureLevel=${manifest.meta?.featureLevel}, " +
</file context>
Fix with Cubic

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/service/epic/EpicDownloadManager.kt`:
- Around line 134-137: The current code computes totalInstalledSize from unique
chunk windowSize (via chunks) which measures payload, not the actual selected
files; replace that calculation by using
ManifestUtils.getSizesForSelectedInstallTags(selectedTags, manifest) (or the
provided getSizesForSelectedInstallTags function that accepts the manifest and
selected tags) to compute installSize based on file.fileSize for the files
variable, then assign that value to updatedGame.installSize before calling
epicManager.updateGame(updatedGame); update references to
totalInstalledSize/installSize in the block that follows the
selectedTags/files/chunks creation so the stored install size reflects selected
files rather than chunk window sizes.
- Around line 75-76: The DLC download flow currently only applies install tags
(including containerLanguage) to the base manifest; update the logic so that for
each dlcId you fetch its manifest, call the same tag application routine
(preserving containerLanguage) before the size prepass and before invoking
downloadGameWithManifest(), and ensure the size prepass uses these per-DLC
tagged manifests rather than the full untagged manifests; modify the loop
handling dlcIds and the calls to downloadGameWithManifest() and the size-prepass
helper so they accept/consume the tagged DLC manifest objects.

In `@app/src/main/java/app/gamenative/service/epic/EpicManager.kt`:
- Around line 954-955: fetchManifestSizes() currently calls
ManifestUtils.getSizesForSelectedInstallTags(manifest, emptyList()) which
hardcodes required-only sizes; update the call to pass the same install-tag
language used at download time so displayed sizes match actual download sizes
(e.g., pass the container/install tags derived from containerData.language or
the selected install tags stored on the manifest/epic app state). Locate the
call to getSizesForSelectedInstallTags in EpicManager.fetchManifestSizes() and
replace emptyList() with the appropriate install-tag list (the same source used
by EpicAppScreen.performDownload()), ensuring the function receives the
language/tag selection used when performing downloads.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83bb8fc1-7e95-4e01-9ca9-c278981286bb

📥 Commits

Reviewing files that changed from the base of the PR and between c37289a and 1384194.

📒 Files selected for processing (6)
  • app/src/main/java/app/gamenative/service/epic/EpicConstants.kt
  • app/src/main/java/app/gamenative/service/epic/EpicDownloadManager.kt
  • app/src/main/java/app/gamenative/service/epic/EpicManager.kt
  • app/src/main/java/app/gamenative/service/epic/EpicService.kt
  • app/src/main/java/app/gamenative/service/epic/manifest/ManifestUtils.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/EpicAppScreen.kt

Comment on lines +75 to 76
containerLanguage: String = EpicConstants.EPIC_FALLBACK_CONTAINER_LANGUAGE,
dlcIds: List<Int>,
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

DLC downloads still ignore containerLanguage.

Lines 135-137 apply install tags to the base manifest only. The DLC size prepass and the later downloadGameWithManifest() path still use full manifests, so selecting DLCs falls back to downloading every tagged language package for those manifests.

Suggested direction
-    private suspend fun downloadGameWithManifest(
+    private suspend fun downloadGameWithManifest(
         context: Context,
         game: EpicGame,
         manifestData: EpicManager.ManifestResult,
         installPath: String,
         downloadInfo: DownloadInfo,
+        selectedTags: List<String>,
     ): Result<Unit> = withContext(Dispatchers.IO) {
@@
-            val chunks = chunkDataList.elements
-            val files = fileManifestList.elements
+            val files = ManifestUtils.getFilesForSelectedInstallTags(manifest, selectedTags)
+            val chunks = ManifestUtils.getRequiredChunksForFileList(manifest, files)

Also applies to: 134-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/service/epic/EpicDownloadManager.kt` around
lines 75 - 76, The DLC download flow currently only applies install tags
(including containerLanguage) to the base manifest; update the logic so that for
each dlcId you fetch its manifest, call the same tag application routine
(preserving containerLanguage) before the size prepass and before invoking
downloadGameWithManifest(), and ensure the size prepass uses these per-DLC
tagged manifests rather than the full untagged manifests; modify the loop
handling dlcIds and the calls to downloadGameWithManifest() and the size-prepass
helper so they accept/consume the tagged DLC manifest objects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still an issue?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phobos665 Yes, the issue is still present in the current code. Here's a quick summary of what's unfixed:

  1. DLC size prepass — the loop that fetches DLC manifests and sums sizes uses dlcParsed.chunkDataList?.elements directly (the full, unfiltered chunk list), ignoring the selectedTags derived from containerLanguage.

  2. downloadGameWithManifest() — the private helper used for each DLC still reads the raw chunkDataList.elements and fileManifestList.elements without any tag filtering:

    val chunks = chunkDataList.elements
    val files = fileManifestList.elements
  3. Call sitedownloadGameWithManifest is called without forwarding selectedTags.

The fix direction from the original review is still valid — downloadGameWithManifest needs a selectedTags: List<String> parameter, and both the size prepass and the assembly path need to use ManifestUtils.getFilesForSelectedInstallTags / ManifestUtils.getRequiredChunksForFileList for DLC manifests, just as the base game already does.


🧠 Learnings used
Learnt from: phobos665
Repo: utkarshdalal/GameNative PR: 418
File: app/src/main/java/app/gamenative/service/gog/GOGManager.kt:270-282
Timestamp: 2026-01-16T17:32:12.974Z
Learning: In the GOG implementation for GameNative, DLC items should be excluded from the library view using the `isDlc` flag in the exclude condition. The library should only display base games. DLC download decisions are handled separately via the `withDlcs` parameter in GOGDownloadManager and related filtering logic, not through library visibility.

Learnt from: utkarshdalal
Repo: utkarshdalal/GameNative PR: 159
File: app/src/main/java/app/gamenative/service/DownloadService.kt:6-6
Timestamp: 2025-09-19T17:07:27.941Z
Learning: In app/src/main/java/app/gamenative/service/DownloadService.kt, the getSizeFromStoreDisplay() function uses runBlocking to call SteamService.getDownloadableDepots() because the latter was converted from synchronous to suspend in this PR. The user confirmed the previous synchronous implementation worked fine, and if performance is acceptable, keeping runBlocking is a valid approach.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 581
File: app/src/main/java/app/gamenative/ui/PluviaMain.kt:1154-1176
Timestamp: 2026-02-21T22:26:41.871Z
Learning: In Kotlin coroutines, suspend functions automatically suspend and resume within a coroutine scope. Only Deferred objects produced by async { ... } require .await() to obtain the result. Do not call .await() on direct suspend function calls or on non-Deferred results; use await() only when you have a Deferred that represents a value you need.

Comment on lines +134 to +137
// Use container language (same as GOG) to select install tags: required + optional language files.
val selectedTags = EpicConstants.containerLanguageToEpicInstallTags(containerLanguage)
val files = ManifestUtils.getFilesForSelectedInstallTags(manifest, selectedTags)
val chunks = ManifestUtils.getRequiredChunksForFileList(manifest, files)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use selected file sizes for installSize.

Line 159 still derives totalInstalledSize from unique chunk windowSizes. That measures chunk payload, not the selected files that land on disk, so the value later written via epicManager.updateGame(updatedGame) can drift from the real install size. ManifestUtils.getSizesForSelectedInstallTags() already calculates this from file.fileSize; reuse it here.

Suggested fix
             val selectedTags = EpicConstants.containerLanguageToEpicInstallTags(containerLanguage)
             val files = ManifestUtils.getFilesForSelectedInstallTags(manifest, selectedTags)
             val chunks = ManifestUtils.getRequiredChunksForFileList(manifest, files)
@@
-            var totalDownloadSize = chunks.sumOf { it.fileSize }
-            var totalInstalledSize = chunks.sumOf { it.windowSize.toLong() }
+            val (baseDownloadSize, baseInstalledSize) =
+                ManifestUtils.getSizesForSelectedInstallTags(manifest, selectedTags)
+            var totalDownloadSize = baseDownloadSize
+            var totalInstalledSize = baseInstalledSize

Also applies to: 157-160

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/service/epic/EpicDownloadManager.kt` around
lines 134 - 137, The current code computes totalInstalledSize from unique chunk
windowSize (via chunks) which measures payload, not the actual selected files;
replace that calculation by using
ManifestUtils.getSizesForSelectedInstallTags(selectedTags, manifest) (or the
provided getSizesForSelectedInstallTags function that accepts the manifest and
selected tags) to compute installSize based on file.fileSize for the files
variable, then assign that value to updatedGame.installSize before calling
epicManager.updateGame(updatedGame); update references to
totalInstalledSize/installSize in the block that follows the
selectedTags/files/chunks creation so the stored install size reflects selected
files rather than chunk window sizes.

Comment on lines +954 to +955
// Required-only sizes for detail page display (download uses container language via getSizesForSelectedInstallTags elsewhere).
val (downloadSize, installSize) = app.gamenative.service.epic.manifest.ManifestUtils.getSizesForSelectedInstallTags(manifest, emptyList())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

fetchManifestSizes() is now hardcoded to required-only.

Line 955 passes emptyList() into getSizesForSelectedInstallTags(), so the library page only stores untagged-file sizes. EpicAppScreen.performDownload() later passes containerData.language into the real download path, which means non-English installs can be larger than the size we show before install.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/service/epic/EpicManager.kt` around lines
954 - 955, fetchManifestSizes() currently calls
ManifestUtils.getSizesForSelectedInstallTags(manifest, emptyList()) which
hardcodes required-only sizes; update the call to pass the same install-tag
language used at download time so displayed sizes match actual download sizes
(e.g., pass the container/install tags derived from containerData.language or
the selected install tags stored on the manifest/epic app state). Locate the
call to getSizesForSelectedInstallTags in EpicManager.fetchManifestSizes() and
replace emptyList() with the appropriate install-tag list (the same source used
by EpicAppScreen.performDownload()), ensuring the function receives the
language/tag selection used when performing downloads.

@utkarshdalal utkarshdalal merged commit c83c7e2 into utkarshdalal:master Mar 9, 2026
2 checks passed
@unbelievableflavour unbelievableflavour deleted the epic-multi-lang-support branch March 9, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants