14 Commits
v1.5.5 ... main

Author SHA1 Message Date
github-actions[bot]
85873379f3 chore: update package.nix for v1.5.6 2026-05-06 14:16:24 +00:00
Tickbase
293273af2d version bump 2026-05-06 16:14:37 +02:00
Tickbase
550d183ee2 changelog 2026-05-06 16:14:31 +02:00
Tickbase
756a6e3d53 fetch DLC ID's from steamcmd #108 2026-05-06 16:12:54 +02:00
Tickbase
cbed56ac33 Delete .github/workflows/update-npm-hash.yml 2026-05-05 17:12:28 +02:00
Tickbase
fbabff0a34 Update workflow to update npm hash automatically credit: @naguiagahnim
Automates computing the npm hash for nix users automatically on release. Credit to: @naguiagahnim
2026-05-05 17:12:12 +02:00
Tickbase
fb6203c4f7 Merge pull request #114 from naguiagahnim/main
Automate NPM hash fetch
2026-05-05 17:01:12 +02:00
Agahnim
9144e52db1 remove shell script
Will be integrated directly into CI/CD
2026-05-05 16:59:22 +02:00
Agahnim
e50f885ba2 add CI/CD to automate npm hash updating 2026-05-05 10:38:39 +02:00
Agahnim
433417ade2 automate npm dependencies hash fetch 2026-05-05 10:38:39 +02:00
Tickbase
6f5d750af9 Merge pull request #113 from naguiagahnim/main
Improve Nix packaging structure and update npm dependencies hash
2026-05-04 15:10:49 +02:00
Agahnim
ef7e183312 update Nix instructions in README to fit new packaging structure 2026-05-03 23:12:54 +02:00
Agahnim
3b4a17b615 improve nix packaging structure and update npm dependencies hash 2026-05-03 23:12:51 +02:00
Tickbase
6ff6c06bec Update README to include Epic Games support
Added Epic Games support for ScreamAPI in the application description and features.
2026-05-01 11:49:22 +02:00
11 changed files with 301 additions and 289 deletions

View File

@@ -53,6 +53,39 @@ jobs:
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: install nix
uses: DeterminateSystems/nix-installer-action@main
- name: update package.nix version, date, and npm hash
env:
VERSION: ${{ steps.get-version.outputs.version }}
run: |
# Get today's date in YYYY-MM-DD format
TODAY=$(date -u +%Y-%m-%d)
# Compute new npm deps hash from package-lock.json
HASH=$(nix-shell -p prefetch-npm-deps --run "prefetch-npm-deps package-lock.json" 2>/dev/null)
echo "New hash: $HASH"
# Update version string (e.g. 1.5.5-unstable-2026-05-03)
sed -i "s|version = \"[^\"]*\"|version = \"${VERSION}-unstable-${TODAY}\"|" package.nix
# Update npm deps hash
sed -i "s|hash = \"[^\"]*\"|hash = \"${HASH}\"|" package.nix
echo "Updated package.nix:"
grep -E 'version|hash' package.nix
- name: commit updated package.nix
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add package.nix
if [[ $(git status -s) ]]; then
git commit -m "chore: update package.nix for v${{ steps.get-version.outputs.version }}"
git push
fi
- name: create draft release
id: create-release
uses: actions/github-script@v6
@@ -88,6 +121,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: main
- name: Setup Node.js
uses: actions/setup-node@v4

View File

@@ -1,3 +1,8 @@
## [1.5.6] - 06-05-2026
### Added
- DLC fetching now uses steamcmd instead of the Steam store API, which was missing DLCs from many games
## [1.5.5] - 30-04-2026
### Added

View File

@@ -1,6 +1,6 @@
# CreamLinux
CreamLinux is a GUI application for Linux that simplifies the management of DLC IDs in Steam games. It provides a user-friendly interface to install and configure CreamAPI (for native Linux games) and SmokeAPI (for Windows games running through Proton).
CreamLinux is a GUI application for Linux that simplifies the management of DLC IDs in Steam games. It provides a user-friendly interface to install and configure CreamAPI (for native Linux games), SmokeAPI (for Windows games running through Proton) and ScreamAPI (Epic Games).
## Watch the demo here:
@@ -22,6 +22,7 @@ While the core functionality is working, please be aware that this is an early r
- **Auto-discovery**: Automatically finds Steam games installed on your system
- **Native support**: Installs CreamLinux for native Linux games
- **Proton support**: Installs SmokeAPI for Windows games running through Proton
- **Epic Games support**: Installs ScreamAPI for games running through Heroic/Legendary
- **DLC management**: Easily select which DLCs to enable
- **Modern UI**: Clean, responsive interface that's easy to use
@@ -47,32 +48,30 @@ While the core functionality is working, please be aware that this is an early r
```
### Nix
You can fetch this repository in your configuration using `pkgs.fetchFromGithub`:
You can add this package to your configuration using `pkgs.fetchFromGitHub`:
```nix
let
creamlinux = pkgs.callPackage (pkgs.fetchFromGitHub {
creamlinux = import (pkgs.fetchFromGitHub {
owner = "Novattz";
repo = "creamlinux-installer";
rev = "main";
hash = ""; # You can use nix-prefetch-url to determine which value to put here, or paste the value returned by the error your rebuild will output
}) {};
rev = "main"; # replace with a commit hash to pin the version
hash = ""; # paste the value returned by the error your rebuild will output
}) { inherit pkgs; };
in
{
environment.systemPackages = [ creamlinux ];
}
}
```
or, using `builtins.fetchTarball`:
```nix
let
creamlinux-src = builtins.fetchTarball {
creamlinux = import (builtins.fetchTarball {
url = "https://github.com/Novattz/creamlinux-installer/archive/main.tar.gz";
sha256 = ""; # See above
};
}) { inherit pkgs; };
in
{
environment.systemPackages = [
(pkgs.callPackage creamlinux-src {})
];
environment.systemPackages = [ creamlinux ];
}
```
alternatively and if you want to pin the package version, using [npins](https://github.com/andir/npins):
@@ -85,12 +84,11 @@ let
in
{
environment.systemPackages = [
(pkgs.callPackage "${sources.creamlinux-installer}/default.nix" {})
(import sources.creamlinux-installer { inherit pkgs; })
];
}
```
Those are the recommended methods to add creamlinux-installer to your environment. However, you could also add it as an input of your flake, like so:
```nix
{
inputs = {
@@ -107,7 +105,7 @@ Those are the recommended methods to add creamlinux-installer to your environmen
Then, in your configuration:
```nix
environment.systemPackages = [
(pkgs.callPackage inputs.creamlinux-installer {})
(import inputs.creamlinux-installer { inherit pkgs; })
];
```
Similarly to running the AppImage, you will need to set `WEBKIT_DISABLE_DMABUF_RENDERER=1` if your GPU is from Nvidia in order to run the package.

View File

@@ -1,57 +1,4 @@
{pkgs ? import <nixpkgs> {}}: let
cargoRoot = "src-tauri";
src = ./.;
patchSassEmbedded = pkgs.writeShellScriptBin "patch-sass-embedded" ''
NIX_LD="$(cat ${pkgs.stdenv.cc}/nix-support/dynamic-linker)"
for dart_bin in node_modules/sass-embedded-linux-*/dart-sass/src/dart; do
if [ -f "$dart_bin" ]; then
${pkgs.patchelf}/bin/patchelf --set-interpreter "$NIX_LD" "$dart_bin"
fi
done
'';
in
pkgs.rustPlatform.buildRustPackage {
pname = "creamlinux-installer";
version = "1.5.0-unstable-2026-04-23";
inherit src;
cargoLock.lockFile = ./src-tauri/Cargo.lock;
npmDeps = pkgs.fetchNpmDeps {
inherit src;
hash = "sha256-anYTERlnfOGDsGW0joy+h7wECJNDy6q+0a2to6t36pg=";
};
nativeBuildInputs =
[
pkgs.cargo-tauri.hook
pkgs.nodejs
pkgs.npmHooks.npmConfigHook
pkgs.pkg-config
]
++ pkgs.lib.optionals pkgs.stdenv.isLinux [
pkgs.wrapGAppsHook4
];
buildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [
pkgs.glib-networking
pkgs.openssl
pkgs.webkitgtk_4_1
];
inherit cargoRoot;
buildAndTestSubdir = cargoRoot;
postPatch = ''
substituteInPlace src-tauri/tauri.conf.json \
--replace-fail '"createUpdaterArtifacts": true' '"createUpdaterArtifacts": false'
'';
preBuild = ''
${patchSassEmbedded}/bin/patch-sass-embedded
'';
env.NO_STRIP = true;
}
{
pkgs ? import <nixpkgs> { },
}:
pkgs.callPackage ./package.nix { }

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "creamlinux",
"version": "1.5.5",
"version": "1.5.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "creamlinux",
"version": "1.5.5",
"version": "1.5.6",
"license": "MIT",
"dependencies": {
"@tauri-apps/api": "^2.5.0",

View File

@@ -1,7 +1,7 @@
{
"name": "creamlinux",
"private": true,
"version": "1.5.5",
"version": "1.5.6",
"type": "module",
"author": "Tickbase",
"repository": "https://github.com/Novattz/creamlinux-installer",

72
package.nix Normal file
View File

@@ -0,0 +1,72 @@
{
cargo-tauri,
writeShellScriptBin,
stdenv,
patchelf,
rustPlatform,
fetchNpmDeps,
nodejs,
npmHooks,
pkg-config,
lib,
wrapGAppsHook4,
glib-networking,
openssl,
webkitgtk_4_1,
}:
let
cargoRoot = "src-tauri";
src = ./.;
patchSassEmbedded = writeShellScriptBin "patch-sass-embedded" ''
NIX_LD="$(cat ${stdenv.cc}/nix-support/dynamic-linker)"
for dart_bin in node_modules/sass-embedded-linux-*/dart-sass/src/dart; do
if [ -f "$dart_bin" ]; then
${patchelf}/bin/patchelf --set-interpreter "$NIX_LD" "$dart_bin"
fi
done
'';
in
rustPlatform.buildRustPackage {
pname = "creamlinux-installer";
version = "1.5.6-unstable-2026-05-06";
inherit src;
cargoLock.lockFile = ./src-tauri/Cargo.lock;
npmDeps = fetchNpmDeps {
inherit src;
hash = "sha256-6sQul8tZaCk62JL9SfDKxVShNgYdoGYOS25asugirDo=";
};
nativeBuildInputs = [
cargo-tauri.hook
nodejs
npmHooks.npmConfigHook
pkg-config
]
++ lib.optionals stdenv.isLinux [
wrapGAppsHook4
];
buildInputs = lib.optionals stdenv.isLinux [
glib-networking
openssl
webkitgtk_4_1
];
inherit cargoRoot;
buildAndTestSubdir = cargoRoot;
postPatch = ''
substituteInPlace src-tauri/tauri.conf.json \
--replace-fail '"createUpdaterArtifacts": true' '"createUpdaterArtifacts": false'
'';
preBuild = ''
${patchSassEmbedded}/bin/patch-sass-embedded
'';
env.NO_STRIP = true;
}

2
src-tauri/Cargo.lock generated
View File

@@ -602,7 +602,7 @@ dependencies = [
[[package]]
name = "creamlinux-installer"
version = "1.5.5"
version = "1.5.6"
dependencies = [
"async-trait",
"log",

View File

@@ -1,6 +1,6 @@
[package]
name = "creamlinux-installer"
version = "1.5.5"
version = "1.5.6"
description = "DLC Manager for Steam games on Linux"
authors = ["tickbase"]
license = "MIT"

View File

@@ -650,106 +650,132 @@ pub async fn uninstall_koaloader(game: EpicGame, app_handle: AppHandle) -> Resul
Ok(())
}
// Fetch DLC details from Steam API (simple version without progress)
pub async fn fetch_dlc_details(app_id: &str) -> Result<Vec<DlcInfo>, String> {
let client = reqwest::Client::new();
let base_url = format!(
"https://store.steampowered.com/api/appdetails?appids={}",
app_id
);
// steamcmd helpers
let response = client
.get(&base_url)
.timeout(Duration::from_secs(10))
/// Calls `https://api.steamcmd.net/v1/info/{app_id}` and returns the per-app
/// JSON object (`data[app_id]`), or `None` on any failure.
async fn fetch_steamcmd_info(
client: &reqwest::Client,
app_id: &str,
) -> Option<serde_json::Value> {
let url = format!("https://api.steamcmd.net/v1/info/{}", app_id);
let resp = client
.get(&url)
.header("User-Agent", "CreamLinux-Installer")
.timeout(Duration::from_secs(15))
.send()
.await
.map_err(|e| format!("Failed to fetch game details: {}", e))?;
.ok()?;
if !response.status().is_success() {
return Err(format!(
"Failed to fetch game details: HTTP {}",
response.status()
));
let json: serde_json::Value = resp.json().await.ok()?;
if json.get("status").and_then(|s| s.as_str()) != Some("success") {
return None;
}
let data: serde_json::Value = response
.json()
.await
.map_err(|e| format!("Failed to parse response: {}", e))?;
json.get("data").and_then(|d| d.get(app_id)).cloned()
}
let dlc_ids = match data
.get(app_id)
.and_then(|app| app.get("data"))
.and_then(|data| data.get("dlc"))
/// Extracts DLC app-IDs from a game's steamcmd info object.
/// Merges two sources and deduplicates:
/// 1. `extended.listofdlc` - comma-separated string
/// 2. `depots[*].dlcappid` - per-depot numeric field
fn extract_dlc_ids(info: &serde_json::Value) -> Vec<String> {
let mut ids: std::collections::HashSet<String> = std::collections::HashSet::new();
// Source 1 - extended.listofdlc
if let Some(list) = info
.get("extended")
.and_then(|e| e.get("listofdlc"))
.and_then(|v| v.as_str())
{
Some(dlc_array) => match dlc_array.as_array() {
Some(array) => array
.iter()
.filter_map(|id| id.as_u64().map(|n| n.to_string()))
.collect::<Vec<String>>(),
_ => Vec::new(),
},
_ => Vec::new(),
};
info!("Found {} DLCs for game ID {}", dlc_ids.len(), app_id);
let mut dlc_details = Vec::new();
for dlc_id in dlc_ids {
let dlc_url = format!(
"https://store.steampowered.com/api/appdetails?appids={}",
dlc_id
);
// Add a small delay to avoid rate limiting
tokio::time::sleep(Duration::from_millis(300)).await;
let dlc_response = client
.get(&dlc_url)
.timeout(Duration::from_secs(10))
.send()
.await
.map_err(|e| format!("Failed to fetch DLC details: {}", e))?;
if dlc_response.status().is_success() {
let dlc_data: serde_json::Value = dlc_response
.json()
.await
.map_err(|e| format!("Failed to parse DLC response: {}", e))?;
let dlc_name = match dlc_data
.get(&dlc_id)
.and_then(|app| app.get("data"))
.and_then(|data| data.get("name"))
{
Some(name) => match name.as_str() {
Some(s) => s.to_string(),
_ => "Unknown DLC".to_string(),
},
_ => "Unknown DLC".to_string(),
};
info!("Found DLC: {} ({})", dlc_name, dlc_id);
dlc_details.push(DlcInfo {
appid: dlc_id,
name: dlc_name,
});
} else if dlc_response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
// If rate limited, wait longer
error!("Rate limited by Steam API, waiting 10 seconds");
tokio::time::sleep(Duration::from_secs(10)).await;
for raw in list.split(',') {
let id = raw.trim();
if !id.is_empty() {
ids.insert(id.to_string());
}
}
}
// Source 2 - depots[*].dlcappid
if let Some(depots) = info.get("depots").and_then(|d| d.as_object()) {
for (_key, depot) in depots {
if let Some(dlc_id) = depot.get("dlcappid").and_then(|v| {
v.as_u64()
.map(|n| n.to_string())
.or_else(|| v.as_str().map(|s| s.to_string()))
}) {
if !dlc_id.is_empty() {
ids.insert(dlc_id);
}
}
}
}
let mut sorted: Vec<String> = ids.into_iter().collect();
sorted.sort();
sorted
}
info!(
"Successfully retrieved details for {} DLCs",
dlc_details.len()
);
/// Fetches the display name for a single DLC from steamcmd.net.
/// Returns `None` if the call fails or the name is empty / "Unknown".
async fn fetch_dlc_name(
client: &reqwest::Client,
dlc_id: &str,
) -> Option<String> {
let info = fetch_steamcmd_info(client, dlc_id).await?;
let name = info
.get("common")
.and_then(|c| c.get("name"))
.and_then(|n| n.as_str())?;
if name.is_empty() || name.eq_ignore_ascii_case("unknown") {
return None;
}
Some(name.to_string())
}
// Fetch DLC details from steamcmd.net (simple version without progress)
pub async fn fetch_dlc_details(app_id: &str) -> Result<Vec<DlcInfo>, String> {
info!("Fetching DLC list via steamcmd.net for game ID: {}", app_id);
let client = reqwest::Client::new();
// Step 1: get game info → extract DLC IDs
let game_info = fetch_steamcmd_info(&client, app_id)
.await
.ok_or_else(|| format!("steamcmd.net returned no data for app {}", app_id))?;
let dlc_ids = extract_dlc_ids(&game_info);
info!("Found {} DLC IDs for game {}", dlc_ids.len(), app_id);
// Step 2: fetch name for each ID; skip any that resolve to Unknown
let mut dlc_details = Vec::new();
for dlc_id in &dlc_ids {
tokio::time::sleep(Duration::from_millis(100)).await;
match fetch_dlc_name(&client, dlc_id).await {
Some(name) => {
info!("Found DLC: {} ({})", name, dlc_id);
dlc_details.push(DlcInfo {
appid: dlc_id.clone(),
name,
});
}
None => {
info!("Skipping DLC {} - no name / unknown", dlc_id);
}
}
}
info!("Successfully retrieved {} named DLCs", dlc_details.len());
Ok(dlc_details)
}
// Fetch DLC details from Steam API with progress updates
// Fetch DLC details from steamcmd.net with progress updates
pub async fn fetch_dlc_details_with_progress(
app_id: &str,
app_handle: &tauri::AppHandle,
@@ -758,80 +784,46 @@ pub async fn fetch_dlc_details_with_progress(
"Starting DLC details fetch with progress for game ID: {}",
app_id
);
// Get a reference to a cancellation flag from app state
let state = app_handle.state::<AppState>();
let should_cancel = state.fetch_cancellation.clone();
let client = reqwest::Client::new();
let base_url = format!(
"https://store.steampowered.com/api/appdetails?appids={}",
app_id
);
// Emit initial progress
// Step 1: fetch game info to get DLC ID list
emit_dlc_progress(app_handle, "Looking up game details...", 5, None);
info!("Emitted initial DLC progress: 5%");
let response = client
.get(&base_url)
.timeout(Duration::from_secs(10))
.send()
let game_info = fetch_steamcmd_info(&client, app_id)
.await
.map_err(|e| format!("Failed to fetch game details: {}", e))?;
if !response.status().is_success() {
let error_msg = format!("Failed to fetch game details: HTTP {}", response.status());
error!("{}", error_msg);
return Err(error_msg);
}
let data: serde_json::Value = response
.json()
.await
.map_err(|e| format!("Failed to parse response: {}", e))?;
let dlc_ids = match data
.get(app_id)
.and_then(|app| app.get("data"))
.and_then(|data| data.get("dlc"))
{
Some(dlc_array) => match dlc_array.as_array() {
Some(array) => array
.iter()
.filter_map(|id| id.as_u64().map(|n| n.to_string()))
.collect::<Vec<String>>(),
_ => Vec::new(),
},
_ => Vec::new(),
};
info!("Found {} DLCs for game ID {}", dlc_ids.len(), app_id);
.ok_or_else(|| format!("steamcmd.net returned no data for app {}", app_id))?;
let dlc_ids = extract_dlc_ids(&game_info);
let total_dlcs = dlc_ids.len();
info!("Found {} DLC IDs for game {}", total_dlcs, app_id);
emit_dlc_progress(
app_handle,
&format!("Found {} DLCs. Fetching details...", dlc_ids.len()),
&format!("Found {} DLCs. Fetching details...", total_dlcs),
10,
None,
);
info!("Emitted DLC progress: 10%, found {} DLCs", dlc_ids.len());
// Step 2: fetch each DLC name, emit as we go, skip unknowns
let mut dlc_details = Vec::new();
let total_dlcs = dlc_ids.len();
for (index, dlc_id) in dlc_ids.iter().enumerate() {
// Check if cancellation was requested
// Check for cancellation
if should_cancel.load(Ordering::SeqCst) {
info!("DLC fetch cancelled for game {}", app_id);
return Err("Operation cancelled by user".to_string());
}
let progress_percent = 10.0 + (index as f32 / total_dlcs as f32) * 90.0;
let progress_rounded = progress_percent as u32;
let remaining_dlcs = total_dlcs - index;
// Estimate time remaining (rough calculation - 300ms per DLC)
let est_time_left = if remaining_dlcs > 0 {
let seconds = (remaining_dlcs as f32 * 0.3).ceil() as u32;
let progress_rounded = (10.0 + (index as f32 / total_dlcs as f32) * 90.0) as u32;
let remaining = total_dlcs - index;
let est_time_left = if remaining > 0 {
// steamcmd is ~100 ms/request, much faster than the old 300 ms Steam API
let seconds = (remaining as f32 * 0.15).ceil() as u32;
if seconds < 60 {
format!("~{} seconds", seconds)
} else {
@@ -840,12 +832,12 @@ pub async fn fetch_dlc_details_with_progress(
} else {
"almost done".to_string()
};
info!(
"Processing DLC {}/{} - Progress: {}%",
"Processing DLC {}/{} ({})",
index + 1,
total_dlcs,
progress_rounded
dlc_id
);
emit_dlc_progress(
app_handle,
@@ -853,73 +845,37 @@ pub async fn fetch_dlc_details_with_progress(
progress_rounded,
Some(&est_time_left),
);
let dlc_url = format!(
"https://store.steampowered.com/api/appdetails?appids={}",
dlc_id
);
// Add a small delay to avoid rate limiting
tokio::time::sleep(Duration::from_millis(300)).await;
let dlc_response = client
.get(&dlc_url)
.timeout(Duration::from_secs(10))
.send()
.await
.map_err(|e| format!("Failed to fetch DLC details: {}", e))?;
if dlc_response.status().is_success() {
let dlc_data: serde_json::Value = dlc_response
.json()
.await
.map_err(|e| format!("Failed to parse DLC response: {}", e))?;
let dlc_name = match dlc_data
.get(&dlc_id)
.and_then(|app| app.get("data"))
.and_then(|data| data.get("name"))
{
Some(name) => match name.as_str() {
Some(s) => s.to_string(),
_ => "Unknown DLC".to_string(),
},
_ => "Unknown DLC".to_string(),
};
info!("Found DLC: {} ({})", dlc_name, dlc_id);
let dlc_info = DlcInfo {
appid: dlc_id.clone(),
name: dlc_name,
};
// Emit each DLC as we find it
if let Ok(json) = serde_json::to_string(&dlc_info) {
if let Err(e) = app_handle.emit("dlc-found", json) {
warn!("Failed to emit dlc-found event: {}", e);
} else {
info!("Emitted dlc-found event for DLC: {}", dlc_id);
// Small delay to be polite to the API
tokio::time::sleep(Duration::from_millis(100)).await;
match fetch_dlc_name(&client, dlc_id).await {
Some(name) => {
info!("Found DLC: {} ({})", name, dlc_id);
let dlc_info = DlcInfo {
appid: dlc_id.clone(),
name,
};
// Emit immediately so the UI updates as DLCs arrive
if let Ok(json) = serde_json::to_string(&dlc_info) {
if let Err(e) = app_handle.emit("dlc-found", json) {
warn!("Failed to emit dlc-found event: {}", e);
}
}
dlc_details.push(dlc_info);
}
None => {
info!("Skipping DLC {} - no name / unknown", dlc_id);
}
dlc_details.push(dlc_info);
} else if dlc_response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
// If rate limited, wait longer
error!("Rate limited by Steam API, waiting 10 seconds");
emit_dlc_progress(
app_handle,
"Rate limited by Steam. Waiting...",
progress_rounded,
None,
);
tokio::time::sleep(Duration::from_secs(10)).await;
}
}
// Final progress update
info!(
"Completed DLC fetch. Found {} DLCs in total",
dlc_details.len()
"Completed DLC fetch. Found {} named DLCs out of {} IDs",
dlc_details.len(),
total_dlcs
);
emit_dlc_progress(
app_handle,
@@ -927,8 +883,7 @@ pub async fn fetch_dlc_details_with_progress(
100,
None,
);
info!("Emitted final DLC progress: 100%");
Ok(dlc_details)
}

View File

@@ -19,7 +19,7 @@
},
"productName": "Creamlinux",
"mainBinaryName": "creamlinux",
"version": "1.5.5",
"version": "1.5.6",
"identifier": "com.creamlinux.dev",
"app": {
"withGlobalTauri": false,