Compare commits

..

50 commits

Author SHA1 Message Date
Nikita Podvirnyi
125787e793
1.7.1 2024-08-03 10:54:02 +02:00
Observer KRypt0n_
83b9f887a6
Merge pull request #169 from an-anime-team/next
Release 1.7.1
2024-08-03 10:52:53 +02:00
Nikita Podvirnyi
a92f3599e2
feat: removed p7zip dependency 2024-08-03 10:49:47 +02:00
Nikita Podvirnyi
57dde8615a
docs: updated changelog 2024-08-02 14:58:55 +02:00
Nikita Podvirnyi
9d11ce1c95
1.7.0 2024-08-02 14:50:02 +02:00
Observer KRypt0n_
e19cbf4c31
Merge pull request #168 from an-anime-team/next
Release 1.7.0
2024-08-02 14:49:05 +02:00
Nikita Podvirnyi
51c4f66ba8
feat: updated changelog 2024-08-02 14:48:50 +02:00
Nikita Podvirnyi
acc6e77c61
feat: added 2.4.0 voiceovers sizes 2024-08-02 14:42:52 +02:00
Nikita Podvirnyi
008b8ce1b5
fix: fixed merging issues 2024-08-02 11:56:19 +02:00
Nikita Podvirnyi
898388e1b9
Merge branch 'next' of https://github.com/an-anime-team/an-anime-game-launcher into next 2024-08-02 11:45:27 +02:00
Nikita Podvirnyi
6fc8547e90
3.11.0 2024-08-02 11:24:17 +02:00
Observer KRypt0n_
75f09cb73e
Merge pull request #412 from an-anime-team/next
Release 3.11.0
2024-08-02 11:21:11 +02:00
Nikita Podvirnyi
5a23eef691
feat: updated changelog 2024-08-02 11:18:42 +02:00
Nikita Podvirnyi
0e692df475
feat: clarify GPL version in about dialog
Before it stated "or later", though I expected it to be 3.0 only
2024-08-02 11:06:14 +02:00
Nikita Podvirnyi
e2823aafa5
feat: removed migrate installation feature 2024-08-02 10:54:25 +02:00
Nikita Podvirnyi
32ef2a02d6
build: updated SDK version 2024-08-02 10:45:39 +02:00
Nikita Podvirnyi
935390ab7f
3.10.3 2024-07-21 09:36:31 +02:00
Observer KRypt0n_
2c3559c0c5
Merge pull request #404 from an-anime-team/next
Release 3.10.3
2024-07-21 09:33:16 +02:00
Nikita Podvirnyi
287e79e79a
feat: updated changelog 2024-07-21 09:32:56 +02:00
Nikita Podvirnyi
de780bd7f9
build: updated launcher SDK 2024-07-21 09:02:52 +02:00
Nikita Podvirnyi
52c8e943df
build: updated launcher SDK 2024-07-21 06:57:07 +02:00
Nikita Podvirnyi
209ca477f8
fix: potentially fixed game.log file issues 2024-07-21 06:13:16 +02:00
Observer KRypt0n_
db46f254bc
Update README.md 2024-07-20 06:37:54 +02:00
Nikita Podvirnyi
c97259c063
docs: updated readme 2024-07-19 16:18:24 +02:00
Nikita Podvirnyi
4c95c145d2
3.10.2 2024-07-19 16:08:50 +02:00
Observer KRypt0n_
ad2879283c
Merge pull request #401 from an-anime-team/next
Release 3.10.2
2024-07-19 16:05:09 +02:00
Nikita Podvirnyi
290adcee22
builds: updated SDK version 2024-07-19 14:41:50 +02:00
Nikita Podvirnyi
05c55ba7d9
feat: added missing changelog entry 2024-07-19 13:25:44 +02:00
Nikita Podvirnyi
f32aa2465e
feat: updated changelog 2024-07-19 13:22:39 +02:00
Nikita Podvirnyi
8f4f1eaa39
build: updated SDK version 2024-07-19 13:16:32 +02:00
Nikita Podvirnyi
7726773105
feat(ui): list "Indonesia" wine language option 2024-07-19 13:16:23 +02:00
Nikita Podvirnyi
7f5ebc173b
build: delete unneeded package from the flake.nix 2024-07-19 13:09:33 +02:00
Nikita Podvirnyi
b7ca18495e
feat: changed background images processing logic 2024-07-19 12:39:27 +02:00
Nikita Podvirnyi
39527c18d7
feat: updated libraries versions 2024-07-19 12:28:41 +02:00
Nikita Podvirnyi
203f7e5f98
build: updated Cargo.lock 2024-07-19 11:19:13 +02:00
Nikita Podvirnyi
42085c2b61
build: updated nix flake 2024-07-19 11:19:03 +02:00
Observer KRypt0n_
d326649897
Merge pull request #399 from ezKEa/next
build: replace shell.nix with flake.nix for dev shell
2024-07-19 11:12:17 +02:00
ezKEa
14c71beb09 build: replace shell.nix with flake.nix for dev shell 2024-07-17 07:14:55 -04:00
Observer KRypt0n_
d83b74bb48
Merge pull request #398 from ThunderGemios10/main
Add third-party DEB package link
2024-07-12 19:03:44 +02:00
ThunderGemios10
b24625b773
Place DEB format above Pacstall
Also added common Ubuntu-based distributions.
2024-07-12 05:28:37 +08:00
ThunderGemios10
1eeaaf333e
Add third-party DEB package link 2024-07-11 19:02:02 +08:00
Nikita Podvirnyi
3eff1e2a6d
docs: little text clarifications 2024-07-11 05:49:00 +02:00
Nikita Podvirnyi
424da3418d
build: updated cargo lock 2024-07-11 05:34:27 +02:00
Nikita Podvirnyi
eeae5bd970
docs: added AUR source link 2024-07-11 05:34:14 +02:00
Nikita Podvirnyi
5d1cfb4f0b
docs: updated readme 2024-07-11 05:31:27 +02:00
Observer KRypt0n_
20ff347190
Merge pull request #395 from Luk-ESC/next
refactor: remove unsafe in i18n
2024-07-08 21:58:20 +02:00
Luk-ESC
462ada831c refactor: remove unsafe in i18n 2024-07-08 20:45:40 +02:00
Nikita Podvirnyi
b7bfda5a85
feat: clarified runtime dependencies
Removed xdelta3, updated dwebp package name for fedora
2024-07-05 21:47:22 +02:00
Nikita Podvirnyi
0c50bd7051
build: updated dependencies 2024-07-05 21:46:56 +02:00
Nikita Podvirnyi
bacc930c5a
docs: updated changelog 2024-07-02 10:12:00 +02:00
27 changed files with 920 additions and 851 deletions

2
.envrc
View file

@ -1 +1 @@
use nix use flake

View file

@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.7.1] - 03.08.2024
### Removed
- Removed `p7zip` dependency
## [1.7.0] - 02.08.2024
### Added
- Added "Indonesia" wine language option
- Added writing of the game's output to the `game.log` file in the launcher's folder.
Size of this file is controlled by the `LAUNCHER_GAME_LOG_FILE_LIMIT` environment variable.
- Respect root `.version` file for game version parsing
- Added 2.4.0 voiceovers sizes
### Fixed
- Fixed `dwebp` package name for fedora during initial setup
- Fixed Discord RPC updates
### Changed
- Changed background images processing logic
- Prioritize parsed game version over the API response
### Removed
- Removed `xdelta3` dependency
- Removed migrate installation feature
## [1.6.1] - 02.07.2024 ## [1.6.1] - 02.07.2024
### Added ### Added
@ -306,7 +337,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<br> <br>
[unreleased]: https://github.com/an-anime-team/the-honkers-railway-launcher/compare/1.6.1...next [unreleased]: https://github.com/an-anime-team/the-honkers-railway-launcher/compare/1.7.1...next
[1.7.1]: https://github.com/an-anime-team/the-honkers-railway-launcher/compare/1.7.0...1.7.1
[1.7.0]: https://github.com/an-anime-team/the-honkers-railway-launcher/compare/1.6.1...1.7.0
[1.6.1]: https://github.com/an-anime-team/the-honkers-railway-launcher/compare/1.6.0...1.6.1 [1.6.1]: https://github.com/an-anime-team/the-honkers-railway-launcher/compare/1.6.0...1.6.1
[1.6.0]: https://github.com/an-anime-team/the-honkers-railway-launcher/compare/1.5.5...1.6.0 [1.6.0]: https://github.com/an-anime-team/the-honkers-railway-launcher/compare/1.5.5...1.6.0
[1.5.5]: https://github.com/an-anime-team/the-honkers-railway-launcher/compare/1.5.4...1.5.5 [1.5.5]: https://github.com/an-anime-team/the-honkers-railway-launcher/compare/1.5.4...1.5.5

534
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "honkers-railway-launcher" name = "honkers-railway-launcher"
version = "1.6.1" version = "1.7.1"
description = "The Honkers Railway launcher" description = "The Honkers Railway launcher"
authors = ["Nikita Podvirnyi <krypt0nn@vk.com>"] authors = ["Nikita Podvirnyi <krypt0nn@vk.com>"]
homepage = "https://github.com/an-anime-team/the-honkers-railway-launcher" homepage = "https://github.com/an-anime-team/the-honkers-railway-launcher"
@ -15,28 +15,28 @@ lto = true
opt-level = "s" opt-level = "s"
[build-dependencies] [build-dependencies]
glib-build-tools = "0.19" glib-build-tools = "0.20"
[dependencies.anime-launcher-sdk] [dependencies.anime-launcher-sdk]
git = "https://github.com/an-anime-team/anime-launcher-sdk" git = "https://github.com/an-anime-team/anime-launcher-sdk"
tag = "1.15.4" tag = "1.17.3"
features = ["all", "star-rail", "star-rail-patch"] features = ["all", "star-rail", "star-rail-patch"]
# path = "../anime-launcher-sdk" # ! for dev purposes only # path = "../anime-launcher-sdk" # ! for dev purposes only
[dependencies] [dependencies]
relm4 = { version = "0.8.1", features = ["macros", "libadwaita"] } relm4 = { version = "0.9.0", features = ["macros", "libadwaita"] }
gtk = { package = "gtk4", version = "0.8.1", features = ["v4_12"] } gtk = { package = "gtk4", version = "0.9.0", features = ["v4_12"] }
adw = { package = "libadwaita", version = "0.6.0", features = ["v1_4"] } adw = { package = "libadwaita", version = "0.7.0", features = ["v1_4"] }
rfd = { version = "0.14.1", features = ["xdg-portal", "tokio"], default-features = false } rfd = { version = "0.14.1", features = ["xdg-portal", "tokio"], default-features = false }
open = "5.0.0" open = "5.3.0"
whatadistro = "0.1.0" whatadistro = "0.1.0"
serde_json = "1.0" serde_json = "1.0"
anyhow = "1.0" anyhow = "1.0"
lazy_static = "1.4.0" lazy_static = "1.5.0"
cached = { version = "0.51", features = ["proc_macro"] } cached = { version = "0.53", features = ["proc_macro"] }
md-5 = { version = "0.10", features = ["asm"] } md-5 = { version = "0.10", features = ["asm"] }
enum-ordinalize = "4.3" enum-ordinalize = "4.3"
@ -46,4 +46,4 @@ tracing-subscriber = "0.3"
fluent-templates = "0.9" fluent-templates = "0.9"
unic-langid = "0.9" unic-langid = "0.9"
human-panic = "2.0.0" human-panic = "2.0.1"

View file

@ -5,7 +5,6 @@
<p align="center"> <p align="center">
<a href="https://discord.gg/ck37X6UWBp">Discord</a> · <a href="https://discord.gg/ck37X6UWBp">Discord</a> ·
<a href="https://matrix.to/#/#an-anime-game:envs.net">Matrix</a> ·
<a href="https://github.com/an-anime-team/the-honkers-railway-launcher/wiki">Wiki</a> <a href="https://github.com/an-anime-team/the-honkers-railway-launcher/wiki">Wiki</a>
</p> </p>
@ -29,6 +28,12 @@ instead of the actual name of the game, to avoid search engine parsing.
<br> <br>
# 🚧 Project status
Due to lack of interest from my side the project stays in a legacy, maintaining-only state for a long period of time. This project will not receive huge updates unless really necessary. I still keep it up to date with latest changes in the game and work with community to solve the issues, but old-known unessential bugs will not be fixed, and new features will not be added. Instead, I'm working on other projects, and the future is in uniting all the launchers in one single [universal launcher](https://github.com/an-anime-team/anime-games-launcher). This project stays in "proof of concept" stage right now and requires major changes, which, again, require interest from my side. Keep your eye on our discord server for more details.
<br>
# ♥️ Useful links and thanks # ♥️ Useful links and thanks
* Original patch project without which this project wouldn't be possible. Link is omitted for "privacy" purposes * Original patch project without which this project wouldn't be possible. Link is omitted for "privacy" purposes
@ -37,57 +42,62 @@ instead of the actual name of the game, to avoid search engine parsing.
* [Releases page](https://github.com/an-anime-team/the-honkers-railway-launcher/releases) where you can find latest available version * [Releases page](https://github.com/an-anime-team/the-honkers-railway-launcher/releases) where you can find latest available version
* [Changelog](CHANGELOG.md) with chronology of the project * [Changelog](CHANGELOG.md) with chronology of the project
All the project's life happen in our discord server. If you have any questions or want to report an issue - please contact the dev directly there.
<br> <br>
# ⬇️ Download # ⬇️ Download
| Distribution | Format | Wiki | Source | Launcher developer does not provide any packages for this programm. Instead, we almost fully rely on other people to maintain them.
| - | - | - | - |
| Any | Flatpak | [link](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation#-any-distribution-flatpak) | - |
| Arch Linux, Manjaro | AUR | [link](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation#-arch-linux-aur) | [the-honkers-railway-launcher-bin](https://aur.archlinux.org/packages/the-honkers-railway-launcher-bin) |
| Fedora, OpenSUSE | RPM | [link](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation#-fedora-rpm) | [HRL](https://build.opensuse.org/repositories/home:Maroxy:AAT-Apps/HRL) |
| Gentoo | ebuild | [link](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation#-gentoo-linux-ebuild) | [gentoo-ebuilds](https://github.com/an-anime-team/gentoo-ebuilds) |
| NixOS | nixpkg | [link](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation#-nixos-nixpkg) | [aagl-gtk-on-nix](https://github.com/ezKEa/aagl-gtk-on-nix) |
To see the installation guides, please visit the wiki page [here](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation) To see the installation guides, please visit [this wiki page](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation).
Lutris integration described [here](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation#-lutris) Instructions may be outdated due to lack of interest in maintaining them. You can help the project by keeping documentation up to date if you're interested in it.
## 😀 Official support
These packages are officially supported by the An Anime Team, and we try to ensure that they work for everyone.
| Format | Wiki | Source | Distributions | Maintainer |
| - | - | - | - | - |
| Flatpak | [wiki](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation#-any-distribution-flatpak) | [flatpak-builds](https://github.com/an-anime-team/flatpak-builds) | Any (Fedora, Pop!_OS, SteamOS / Steam Deck, etc.) | Luna (available in discord) |
| RPM | [wiki](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation#-fedora-rpm) | [THRL](https://build.opensuse.org/repositories/home:Maroxy:AAT-Apps/AAGL) * | Fedora, OpenSUSE | Maroxy (second discord admin) |
> [!NOTE]
> RPM packages are often really outdated. It's not recommended to use them.
## 🙂 Community support
These packages are supported by active members of our community. They're widely used and we keep some level of interactions with their maintainers.
| Format | Wiki | Source | Distributions | Maintainer |
| - | - | - | - | - |
| AUR | [wiki](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation#-arch-linux-aur) | [the-honkers-railway-launcher-bin](https://aur.archlinux.org/packages/the-honkers-railway-launcher-bin) | Arch Linux, Manjaro, EndeavourOS | xstra * |
| NixOS module | [wiki](https://github.com/an-anime-team/the-honkers-railway-launcher/wiki/Installation#-nixos-nixpkg) | [aagl-gtk-on-nix](https://github.com/ezKEa/aagl-gtk-on-nix) | NixOS | Luxxy * |
> [!NOTE]
> Honorary members of our discord server. We have direct contact with them.
## 😑 Third party support
These packages are supported by third party distributors. They either did not contact us, or contact exceptionally rarely. We do not verify state of these packages, and we are not related to their state at all.
| Format | Source | Distributions |
| - | - | - |
| DEB | [the-honkers-railway-launcher](https://launchpad.net/~thundergemios10/+archive/ubuntu/the-honkers-railway-launcher) | Ubuntu, Linux Mint, Pop!_OS |
| Pacstall | [the-honkers-railway-launcher-bin](https://pacstall.dev/packages/the-honkers-railway-launcher-bin) | Ubuntu |
| Ebuild | [aagl-ebuilds](https://github.com/an-anime-team/gentoo-ebuilds) * | Gentoo |
| Lutris | `lutris.net/games/hon...-star-ra...` (stripping the link) | Any |
> [!NOTE]
> Although it's hosted in our official repo we didn't contact with its maintainer for some time already, and recent updates were made via merge requests by the community.
## Chinese version support ## Chinese version support
This should be automatically enabled if you're using zh_cn (Chinese) as your system language. If you're not using it - you can change the game edition in the launcher settings This should be automatically enabled if you're using `zh_cn` (Chinese) as your system language. If you're not using it - you can change the game edition in the launcher settings.
<br> The main problem, though, is that github is blocked in China, and it's used in other parts of the launcher - not just in game edition. Notably, you can't use the same components index as other people do.
# 💻 Development To fix this, you have to make your own copy of the [components](https://github.com/an-anime-team/components) repository and change all the links there from github releases to some mirror. Later you can update the components index repo link in your launcher's `config.json` file.
| Folder | Description | If you have any questions - feel free to contact the dev in our discord server (or if you have no way to use discord - try sending me an email, but it's unlikely to be received).
| - | - |
| src | Rust source code |
| assets | App assets folder |
| assets/locales | App localizations |
| target/release | Release build of the app |
## Clone repo
```sh
git clone --recursive https://github.com/an-anime-team/the-honkers-railway-launcher
```
## Run app
```sh
cargo run
```
## Build app
```sh
cargo build --release
```
## Updates strategy
Starting from 3.2.1 ([fcab428](https://github.com/an-anime-team/the-honkers-railway-launcher/commit/fcab428cb40b1457f41e0856f9d1e1473acbe653)) we have 2 branches: stable ([main](https://github.com/an-anime-team/the-honkers-railway-launcher/tree/main)) and dev ([next](https://github.com/an-anime-team/the-honkers-railway-launcher/tree/next)). Code changes will be pushed into dev branch and merged into stable once they're ready for new version release
<img src="repository/branches.png" />

44
flake.lock Normal file
View file

@ -0,0 +1,44 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1721226092,
"narHash": "sha256-UBvzVpo5sXSi2S/Av+t+Q+C2mhMIw/LBEZR+d6NMjws=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "c716603a63aca44f39bef1986c13402167450e0a",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1721138476,
"narHash": "sha256-+W5eZOhhemLQxelojLxETfbFbc19NWawsXBlapYpqIA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ad0b5eed1b6031efaed382844806550c3dcb4206",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"nixpkgs-unstable": "nixpkgs-unstable"
}
}
},
"root": "root",
"version": 7
}

37
flake.nix Normal file
View file

@ -0,0 +1,37 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, nixpkgs-unstable }:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
pkgs-unstable = nixpkgs-unstable.legacyPackages.x86_64-linux;
in {
devShells.x86_64-linux.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
pkgs-unstable.rustup
pkgs-unstable.rustfmt
pkgs-unstable.clippy
gcc
cmake
pkg-config
p7zip
libwebp
];
buildInputs = with pkgs; [
gtk4
glib
gdk-pixbuf
gobject-introspection
libadwaita
];
};
};
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View file

@ -1,39 +0,0 @@
let
nixpkgs = builtins.fetchGit {
name = "nixos-24.05";
url = "https://github.com/nixos/nixpkgs";
ref = "refs/heads/nixos-24.05";
};
nixpkgs-unstable = builtins.fetchGit {
name = "nixos-unstable";
url = "https://github.com/nixos/nixpkgs";
ref = "refs/heads/nixos-unstable";
};
pkgs = import nixpkgs {};
pkgs-unstable = import nixpkgs-unstable {};
in pkgs.mkShell {
nativeBuildInputs = with pkgs; [
pkgs-unstable.rustup
pkgs-unstable.rustfmt
pkgs-unstable.clippy
gcc
cmake
pkg-config
xdelta
libwebp
];
buildInputs = with pkgs; [
gtk4
glib
gdk-pixbuf
gobject-introspection
libadwaita
];
}

View file

@ -21,7 +21,7 @@ pub fn get_uri() -> String {
else { else {
let uri = concat!("https://sg-hyp-api.", "ho", "yo", "verse", ".com/hyp/hyp-connect/api/getAllGameBasicInfo?launcher_id=VYTpXlbWo8&language="); let uri = concat!("https://sg-hyp-api.", "ho", "yo", "verse", ".com/hyp/hyp-connect/api/getAllGameBasicInfo?launcher_id=VYTpXlbWo8&language=");
uri.to_owned() + &crate::i18n::format_lang(&lang) uri.to_owned() + &crate::i18n::format_lang(lang)
} }
} }
@ -73,12 +73,6 @@ pub fn download_background() -> anyhow::Result<()> {
tracing::debug!("Background picture is already downloaded. Skipping"); tracing::debug!("Background picture is already downloaded. Skipping");
download_image = false; download_image = false;
if crate::BACKGROUND_PRIMARY_FILE.exists() {
tracing::debug!("Background picture is already patched. Skipping");
return Ok(());
}
} }
} }
@ -97,20 +91,20 @@ pub fn download_background() -> anyhow::Result<()> {
Command::new("dwebp") Command::new("dwebp")
.arg(crate::BACKGROUND_FILE.as_path()) .arg(crate::BACKGROUND_FILE.as_path())
.arg("-o") .arg("-o")
.arg(crate::BACKGROUND_PRIMARY_FILE.as_path()) .arg(crate::PROCESSED_BACKGROUND_FILE.as_path())
.spawn()? .spawn()?
.wait()?; .wait()?;
// If it failed to re-code the file - just copy it // If it failed to re-code the file - just copy it
// Will happen with HSR because devs apparently named // Will happen with HSR because devs apparently named
// their background image ".webp" while it's JPEG // their background image ".webp" while it's JPEG
if !crate::BACKGROUND_PRIMARY_FILE.exists() { if !crate::PROCESSED_BACKGROUND_FILE.exists() {
std::fs::copy(crate::BACKGROUND_FILE.as_path(), crate::BACKGROUND_PRIMARY_FILE.as_path())?; std::fs::copy(crate::BACKGROUND_FILE.as_path(), crate::PROCESSED_BACKGROUND_FILE.as_path())?;
} }
} }
else { else {
std::fs::copy(crate::BACKGROUND_FILE.as_path(), crate::BACKGROUND_PRIMARY_FILE.as_path())?; std::fs::copy(crate::BACKGROUND_FILE.as_path(), crate::PROCESSED_BACKGROUND_FILE.as_path())?;
} }
Ok(()) Ok(())

View file

@ -1,3 +1,4 @@
use std::sync::OnceLock;
use unic_langid::{langid, LanguageIdentifier}; use unic_langid::{langid, LanguageIdentifier};
fluent_templates::static_loader! { fluent_templates::static_loader! {
@ -32,14 +33,15 @@ pub const SUPPORTED_LANGUAGES: &[LanguageIdentifier] = &[
langid!("cs-cz") langid!("cs-cz")
]; ];
pub static mut LANG: LanguageIdentifier = langid!("en-us"); /// Fallback used if the system language is not supported
static FALLBACK: LanguageIdentifier = langid!("en-us");
pub static LANG: OnceLock<LanguageIdentifier> = OnceLock::new();
/// Set launcher language /// Set launcher language
pub fn set_lang(lang: LanguageIdentifier) -> anyhow::Result<()> { pub fn set_lang(lang: LanguageIdentifier) -> anyhow::Result<()> {
if SUPPORTED_LANGUAGES.iter().any(|item| item.language == lang.language) { if SUPPORTED_LANGUAGES.iter().any(|item| item.language == lang.language) {
unsafe { LANG.set(lang).expect("Can't overwrite language!");
LANG = lang
}
Ok(()) Ok(())
} }
@ -50,8 +52,8 @@ pub fn set_lang(lang: LanguageIdentifier) -> anyhow::Result<()> {
} }
/// Get launcher language /// Get launcher language
pub fn get_lang() -> LanguageIdentifier { pub fn get_lang() -> &'static LanguageIdentifier {
unsafe { LANG.clone() } LANG.get().expect("Language hasn't been initialized!")
} }
/// Get system language or default language if system one is not supported /// Get system language or default language if system one is not supported
@ -60,7 +62,7 @@ pub fn get_lang() -> LanguageIdentifier {
/// - `LC_ALL` /// - `LC_ALL`
/// - `LC_MESSAGES` /// - `LC_MESSAGES`
/// - `LANG` /// - `LANG`
pub fn get_default_lang() -> LanguageIdentifier { pub fn get_default_lang() -> &'static LanguageIdentifier {
let current = std::env::var("LC_ALL") let current = std::env::var("LC_ALL")
.unwrap_or_else(|_| std::env::var("LC_MESSAGES") .unwrap_or_else(|_| std::env::var("LC_MESSAGES")
.unwrap_or_else(|_| std::env::var("LANG") .unwrap_or_else(|_| std::env::var("LANG")
@ -69,11 +71,11 @@ pub fn get_default_lang() -> LanguageIdentifier {
for lang in SUPPORTED_LANGUAGES { for lang in SUPPORTED_LANGUAGES {
if current.starts_with(lang.language.as_str()) { if current.starts_with(lang.language.as_str()) {
return lang.clone(); return lang;
} }
} }
get_lang() &FALLBACK
} }
pub fn format_lang(lang: &LanguageIdentifier) -> String { pub fn format_lang(lang: &LanguageIdentifier) -> String {
@ -106,8 +108,7 @@ macro_rules! tr {
{ {
use fluent_templates::Loader; use fluent_templates::Loader;
#[allow(unused_unsafe)] $crate::i18n::LOCALES.lookup($crate::i18n::get_lang(), $id)
$crate::i18n::LOCALES.lookup(unsafe { $crate::i18n::LANG.as_ref() }, $id)
} }
}; };
@ -124,8 +125,7 @@ macro_rules! tr {
args.insert($key, FluentValue::from($value)); args.insert($key, FluentValue::from($value));
)* )*
#[allow(unused_unsafe)] $crate::i18n::LOCALES.lookup_complete($crate::i18n::get_lang(), $id, Some(&args))
$crate::i18n::LOCALES.lookup_complete(unsafe { $crate::i18n::LANG.as_ref() }, $id, Some(&args))
} }
}; };
} }

View file

@ -61,8 +61,8 @@ lazy_static::lazy_static! {
/// Path to `background` file. Standard is `$HOME/.local/share/honkers-railway-launcher/background` /// Path to `background` file. Standard is `$HOME/.local/share/honkers-railway-launcher/background`
pub static ref BACKGROUND_FILE: PathBuf = LAUNCHER_FOLDER.join("background"); pub static ref BACKGROUND_FILE: PathBuf = LAUNCHER_FOLDER.join("background");
/// Path to `background-primary` file. Standard is `$HOME/.local/share/anime-game-launcher/background-primary` /// Path to the processed `background` file. Standard is `$HOME/.cache/anime-game-launcher/background`
pub static ref BACKGROUND_PRIMARY_FILE: PathBuf = LAUNCHER_FOLDER.join("background-primary"); pub static ref PROCESSED_BACKGROUND_FILE: PathBuf = CACHE_FOLDER.join("background");
/// Path to `.keep-background` file. Used to mark launcher that it shouldn't update background picture /// Path to `.keep-background` file. Used to mark launcher that it shouldn't update background picture
/// ///
@ -102,7 +102,7 @@ lazy_static::lazy_static! {
.round-bin {{ .round-bin {{
border-radius: 24px; border-radius: 24px;
}} }}
", BACKGROUND_PRIMARY_FILE.to_string_lossy()); ", PROCESSED_BACKGROUND_FILE.to_string_lossy());
} }
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
@ -120,7 +120,7 @@ fn main() -> anyhow::Result<()> {
// CONFIG is initialized lazily so it will contain following changes as well // CONFIG is initialized lazily so it will contain following changes as well
let mut config = Config::get().expect("Failed to get config"); let mut config = Config::get().expect("Failed to get config");
config.launcher.language = i18n::format_lang(&i18n::get_default_lang()); config.launcher.language = i18n::format_lang(i18n::get_default_lang());
Config::update_raw(config).expect("Failed to update config"); Config::update_raw(config).expect("Failed to update config");
} }
@ -201,6 +201,9 @@ fn main() -> anyhow::Result<()> {
gtk::IconTheme::for_display(&gtk::gdk::Display::default().unwrap()) gtk::IconTheme::for_display(&gtk::gdk::Display::default().unwrap())
.add_resource_path(&format!("{APP_RESOURCE_PATH}/icons")); .add_resource_path(&format!("{APP_RESOURCE_PATH}/icons"));
// Set global css
relm4::set_global_css(&GLOBAL_CSS);
// Set application's title // Set application's title
gtk::glib::set_application_name("The Honkers Railway Launcher"); gtk::glib::set_application_name("The Honkers Railway Launcher");
gtk::glib::set_program_name(Some("The Honkers Railway Launcher")); gtk::glib::set_program_name(Some("The Honkers Railway Launcher"));
@ -218,9 +221,6 @@ fn main() -> anyhow::Result<()> {
let app = RelmApp::new(APP_ID) let app = RelmApp::new(APP_ID)
.with_args(gtk_args); .with_args(gtk_args);
// Set global css
app.set_global_css(&GLOBAL_CSS);
// Show first run window // Show first run window
app.run::<FirstRunApp>(()); app.run::<FirstRunApp>(());
} }
@ -297,9 +297,6 @@ fn main() -> anyhow::Result<()> {
let app = RelmApp::new(APP_ID) let app = RelmApp::new(APP_ID)
.with_args(gtk_args); .with_args(gtk_args);
// Set global css
app.set_global_css(&GLOBAL_CSS);
// Show main window // Show main window
app.run::<App>(()); app.run::<App>(());
} }

View file

@ -39,7 +39,7 @@ impl SimpleComponent for AboutDialog {
set_website: "https://github.com/an-anime-team/the-honkers-railway-launcher", set_website: "https://github.com/an-anime-team/the-honkers-railway-launcher",
set_issue_url: "https://github.com/an-anime-team/the-honkers-railway-launcher/issues", set_issue_url: "https://github.com/an-anime-team/the-honkers-railway-launcher/issues",
set_license_type: gtk::License::Gpl30, set_license_type: gtk::License::Gpl30Only,
set_version: &APP_VERSION, set_version: &APP_VERSION,
set_developers: &[ set_developers: &[
@ -100,17 +100,10 @@ impl SimpleComponent for AboutDialog {
set_release_notes_version: &APP_VERSION, set_release_notes_version: &APP_VERSION,
set_release_notes: &[ set_release_notes: &[
"<p>Added</p>", "<p>Removed</p>",
"<ul>", "<ul>",
"<li>Handle dwebp re-coding errors</li>", "<li>Removed \"p7zip\" dependency</li>",
"<li>Added 2.3.0 voiceovers sizes</li>",
"</ul>",
"<p>Fixed</p>",
"<ul>",
"<li>Added workaround for wrong pre-downloads API format</li>",
"</ul>" "</ul>"
].join("\n"), ].join("\n"),

View file

@ -162,33 +162,38 @@ impl SimpleAsyncComponent for ComponentVersion {
let progress_bar_sender = self.progress_bar.sender().clone(); let progress_bar_sender = self.progress_bar.sender().clone();
#[allow(unused_must_use)] #[allow(unused_must_use)]
std::thread::spawn(clone!(@strong self.download_folder as download_folder => move || { std::thread::spawn(clone!(
progress_bar_sender.send(ProgressBarMsg::Reset); #[strong(rename_to = download_folder)]
progress_bar_sender.send(ProgressBarMsg::SetVisible(true)); self.download_folder,
installer.install(download_folder, move |state| { move || {
match &state { progress_bar_sender.send(ProgressBarMsg::Reset);
InstallerUpdate::UnpackingFinished | progress_bar_sender.send(ProgressBarMsg::SetVisible(true));
InstallerUpdate::DownloadingError(_) |
InstallerUpdate::UnpackingError(_) => {
progress_bar_sender.send(ProgressBarMsg::SetVisible(false));
if let InstallerUpdate::UnpackingFinished = &state { installer.install(download_folder, move |state| {
sender.input(ComponentVersionMsg::SetState(VersionState::Downloaded)); match &state {
sender.output(ComponentGroupMsg::CallOnDownloaded); InstallerUpdate::UnpackingFinished |
} InstallerUpdate::DownloadingError(_) |
InstallerUpdate::UnpackingError(_) => {
progress_bar_sender.send(ProgressBarMsg::SetVisible(false));
else { if let InstallerUpdate::UnpackingFinished = &state {
sender.input(ComponentVersionMsg::SetState(VersionState::NotDownloaded)); sender.input(ComponentVersionMsg::SetState(VersionState::Downloaded));
} sender.output(ComponentGroupMsg::CallOnDownloaded);
}, }
_ => () else {
} sender.input(ComponentVersionMsg::SetState(VersionState::NotDownloaded));
}
},
progress_bar_sender.send(ProgressBarMsg::UpdateFromState(DiffUpdate::InstallerUpdate(state))); _ => ()
}); }
}));
progress_bar_sender.send(ProgressBarMsg::UpdateFromState(DiffUpdate::InstallerUpdate(state)));
});
}
));
} }
} }

View file

@ -12,7 +12,6 @@ pub struct DefaultPathsApp {
progress_bar: AsyncController<ProgressBar>, progress_bar: AsyncController<ProgressBar>,
show_additional: bool, show_additional: bool,
migrate_installation: bool,
show_progress: bool, show_progress: bool,
launcher: PathBuf, launcher: PathBuf,
@ -49,8 +48,7 @@ pub enum DefaultPathsAppMsg {
#[relm4::component(async, pub)] #[relm4::component(async, pub)]
impl SimpleAsyncComponent for DefaultPathsApp { impl SimpleAsyncComponent for DefaultPathsApp {
/// If `true`, then use migrate installation mode type Init = ();
type Init = bool;
type Input = DefaultPathsAppMsg; type Input = DefaultPathsAppMsg;
type Output = FirstRunAppMsg; type Output = FirstRunAppMsg;
@ -242,11 +240,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
set_spacing: 8, set_spacing: 8,
gtk::Button { gtk::Button {
set_label: &if model.migrate_installation { set_label: &tr!("continue"),
tr!("migrate")
} else {
tr!("continue")
},
set_css_classes: &["suggested-action", "pill"], set_css_classes: &["suggested-action", "pill"],
@ -254,17 +248,10 @@ impl SimpleAsyncComponent for DefaultPathsApp {
}, },
gtk::Button { gtk::Button {
set_label: &if model.migrate_installation { set_label: &tr!("exit"),
tr!("close", { "form" = "noun" })
} else {
tr!("exit")
},
add_css_class: "pill", add_css_class: "pill",
#[watch]
set_visible: !model.migrate_installation,
connect_clicked => DefaultPathsAppMsg::Exit connect_clicked => DefaultPathsAppMsg::Exit
} }
} }
@ -287,7 +274,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
} }
} }
async fn init(init: Self::Init, root: Self::Root, _sender: AsyncComponentSender<Self>) -> AsyncComponentParts<Self> { async fn init(_init: Self::Init, root: Self::Root, _sender: AsyncComponentSender<Self>) -> AsyncComponentParts<Self> {
let model = Self { let model = Self {
progress_bar: ProgressBar::builder() progress_bar: ProgressBar::builder()
.launch(ProgressBarInit { .launch(ProgressBarInit {
@ -299,7 +286,6 @@ impl SimpleAsyncComponent for DefaultPathsApp {
.detach(), .detach(),
show_additional: false, show_additional: false,
migrate_installation: init,
show_progress: false, show_progress: false,
launcher: LAUNCHER_FOLDER.to_path_buf(), launcher: LAUNCHER_FOLDER.to_path_buf(),
@ -311,12 +297,13 @@ impl SimpleAsyncComponent for DefaultPathsApp {
components: CONFIG.components.path.clone(), components: CONFIG.components.path.clone(),
patch: CONFIG.patch.path.clone(), patch: CONFIG.patch.path.clone(),
#[allow(clippy::or_fun_call)] temp: CONFIG.launcher.temp.clone()
temp: CONFIG.launcher.temp.clone().unwrap_or(std::env::temp_dir()) .unwrap_or_else(std::env::temp_dir)
}; };
// Set progress bar width // Set progress bar width
model.progress_bar.widget().set_width_request(400); model.progress_bar.widget()
.set_width_request(400);
let widgets = view_output!(); let widgets = view_output!();
@ -364,48 +351,9 @@ impl SimpleAsyncComponent for DefaultPathsApp {
#[allow(unused_must_use)] #[allow(unused_must_use)]
DefaultPathsAppMsg::Continue => { DefaultPathsAppMsg::Continue => {
let old_config = Config::get().unwrap_or_else(|_| CONFIG.clone());
match self.update_config() { match self.update_config() {
Ok(_) => { Ok(_) => {
if self.migrate_installation { sender.output(Self::Output::ScrollToDownloadComponents);
self.progress_bar.sender().send(ProgressBarMsg::SetVisible(true));
self.show_progress = true;
let folders = [
(old_config.game.wine.builds, &self.runners),
(old_config.game.dxvk.builds, &self.dxvks),
(old_config.game.wine.prefix, &self.prefix),
(old_config.game.path.global, &self.game_global),
(old_config.game.path.china, &self.game_china),
(old_config.components.path, &self.components),
(old_config.patch.path, &self.patch)
];
#[allow(clippy::expect_fun_call)]
for (i, (from, to)) in folders.iter().enumerate() {
self.progress_bar.sender().send(ProgressBarMsg::UpdateCaption(Some(
from.to_str().map(|str| str.to_string()).unwrap_or_else(|| format!("{:?}", from))
)));
if &from != to && from.exists() {
move_files::move_files(from, to).expect(&format!("Failed to move folder: {:?} -> {:?}", from, to));
}
self.progress_bar.sender().send(ProgressBarMsg::UpdateProgress(i as u64 + 1, folders.len() as u64));
}
// Restart the app
std::process::Command::new(std::env::current_exe().unwrap()).spawn().unwrap();
relm4::main_application().quit();
}
else {
sender.output(Self::Output::ScrollToDownloadComponents);
}
} }
Err(err) => { Err(err) => {
@ -418,14 +366,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
} }
DefaultPathsAppMsg::Exit => { DefaultPathsAppMsg::Exit => {
if self.migrate_installation { relm4::main_application().quit();
// TODO: this shit should return message to general preferences component somehow to close MigrateInstallation window
todo!();
}
else {
relm4::main_application().quit();
}
} }
} }
} }

View file

@ -68,7 +68,7 @@ impl SimpleAsyncComponent for DependenciesApp {
}, },
gtk::Entry { gtk::Entry {
set_text: "sudo pacman -Syu git xdelta3 p7zip libwebp", set_text: "sudo pacman -Syu git libwebp",
set_editable: false set_editable: false
} }
}, },
@ -85,7 +85,7 @@ impl SimpleAsyncComponent for DependenciesApp {
}, },
gtk::Entry { gtk::Entry {
set_text: "sudo apt install git xdelta3 p7zip-full webp", set_text: "sudo apt install git webp",
set_editable: false set_editable: false
} }
}, },
@ -102,7 +102,7 @@ impl SimpleAsyncComponent for DependenciesApp {
}, },
gtk::Entry { gtk::Entry {
set_text: "sudo dnf install git xdelta p7zip libwebp", set_text: "sudo dnf install git libwebp-tools",
set_editable: false set_editable: false
} }
}, },
@ -119,14 +119,6 @@ impl SimpleAsyncComponent for DependenciesApp {
set_title: "git" set_title: "git"
}, },
adw::ActionRow {
set_title: "xdelta3"
},
adw::ActionRow {
set_title: "p7zip"
},
adw::ActionRow { adw::ActionRow {
set_title: "libwebp" set_title: "libwebp"
} }
@ -195,7 +187,7 @@ impl SimpleAsyncComponent for DependenciesApp {
match msg { match msg {
#[allow(unused_must_use)] #[allow(unused_must_use)]
DependenciesAppMsg::Continue => { DependenciesAppMsg::Continue => {
let packages = ["git", "xdelta3", "dwebp"]; let packages = ["git", "dwebp"];
for package in packages { for package in packages {
if !is_available(package) { if !is_available(package) {
@ -210,18 +202,6 @@ impl SimpleAsyncComponent for DependenciesApp {
} }
} }
// 7z sometimes has different binaries
if !is_available("7z") && !is_available("7za") {
sender.output(Self::Output::Toast {
title: tr!("package-not-available", {
"package" = "7z"
}),
description: None
});
return;
}
sender.output(Self::Output::ScrollToDefaultPaths); sender.output(Self::Output::ScrollToDefaultPaths);
} }

View file

@ -135,7 +135,7 @@ impl SimpleComponent for FirstRunApp {
.forward(sender.input_sender(), std::convert::identity), .forward(sender.input_sender(), std::convert::identity),
default_paths: DefaultPathsApp::builder() default_paths: DefaultPathsApp::builder()
.launch(false) .launch(())
.forward(sender.input_sender(), std::convert::identity), .forward(sender.input_sender(), std::convert::identity),
download_components: DownloadComponentsApp::builder() download_components: DownloadComponentsApp::builder()

View file

@ -103,7 +103,11 @@ impl SimpleAsyncComponent for TosWarningApp {
"exit" => relm4::main_application().quit(), "exit" => relm4::main_application().quit(),
"continue" => { "continue" => {
if is_available("git") && is_available("xdelta3") { let installed =
is_available("git") &&
is_available("dwebp");
if installed {
sender.output(Self::Output::ScrollToDefaultPaths); sender.output(Self::Output::ScrollToDefaultPaths);
} else { } else {
sender.output(Self::Output::ScrollToDependencies); sender.output(Self::Output::ScrollToDependencies);

View file

@ -21,33 +21,38 @@ pub fn download_diff(sender: ComponentSender<App>, progress_bar_input: Sender<Pr
diff = diff.with_temp_folder(temp); diff = diff.with_temp_folder(temp);
} }
let result = diff.install_to(game_path, clone!(@strong sender => move |state| { let result = diff.install_to(game_path, clone!(
match &state { #[strong]
DiffUpdate::InstallerUpdate(InstallerUpdate::DownloadingError(err)) => { sender,
tracing::error!("Downloading failed: {err}");
sender.input(AppMsg::Toast { move |state| {
title: tr!("downloading-failed"), match &state {
description: Some(err.to_string()) DiffUpdate::InstallerUpdate(InstallerUpdate::DownloadingError(err)) => {
}); tracing::error!("Downloading failed: {err}");
sender.input(AppMsg::Toast {
title: tr!("downloading-failed"),
description: Some(err.to_string())
});
}
DiffUpdate::InstallerUpdate(InstallerUpdate::UnpackingError(err)) => {
tracing::error!("Unpacking failed: {err}");
sender.input(AppMsg::Toast {
title: tr!("unpacking-failed"),
description: Some(err.clone())
});
}
_ => ()
} }
DiffUpdate::InstallerUpdate(InstallerUpdate::UnpackingError(err)) => { #[allow(unused_must_use)] {
tracing::error!("Unpacking failed: {err}"); progress_bar_input.send(ProgressBarMsg::UpdateFromState(state));
sender.input(AppMsg::Toast {
title: tr!("unpacking-failed"),
description: Some(err.clone())
});
} }
_ => ()
} }
));
#[allow(unused_must_use)] {
progress_bar_input.send(ProgressBarMsg::UpdateFromState(state));
}
}));
let mut perform_on_download_needed = true; let mut perform_on_download_needed = true;

View file

@ -52,45 +52,55 @@ pub fn download_wine(sender: ComponentSender<App>, progress_bar_input: Sender<Pr
sender.input(AppMsg::SetDownloading(true)); sender.input(AppMsg::SetDownloading(true));
std::thread::spawn(clone!(@strong sender => move || { std::thread::spawn(clone!(
installer.install(&config.game.wine.builds, clone!(@strong sender => move |state| { #[strong]
match &state { sender,
InstallerUpdate::DownloadingError(err) => {
tracing::error!("Downloading failed: {err}");
sender.input(AppMsg::Toast { move || {
title: tr!("downloading-failed"), installer.install(&config.game.wine.builds, clone!(
description: Some(err.to_string()) #[strong]
}); sender,
move |state| {
match &state {
InstallerUpdate::DownloadingError(err) => {
tracing::error!("Downloading failed: {err}");
sender.input(AppMsg::Toast {
title: tr!("downloading-failed"),
description: Some(err.to_string())
});
}
InstallerUpdate::UnpackingError(err) => {
tracing::error!("Unpacking failed: {err}");
sender.input(AppMsg::Toast {
title: tr!("unpacking-failed"),
description: Some(err.clone())
});
}
_ => ()
}
#[allow(unused_must_use)] {
progress_bar_input.send(ProgressBarMsg::UpdateFromState(DiffUpdate::InstallerUpdate(state)));
}
} }
));
InstallerUpdate::UnpackingError(err) => { config.game.wine.selected = Some(wine.name.clone());
tracing::error!("Unpacking failed: {err}");
sender.input(AppMsg::Toast { Config::update(config);
title: tr!("unpacking-failed"),
description: Some(err.clone())
});
}
_ => () sender.input(AppMsg::SetDownloading(false));
} sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed: false,
#[allow(unused_must_use)] { show_status_page: true
progress_bar_input.send(ProgressBarMsg::UpdateFromState(DiffUpdate::InstallerUpdate(state))); });
} }
})); ));
config.game.wine.selected = Some(wine.name.clone());
Config::update(config);
sender.input(AppMsg::SetDownloading(false));
sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed: false,
show_status_page: true
});
}));
} }
Err(err) => sender.input(AppMsg::Toast { Err(err) => sender.input(AppMsg::Toast {

View file

@ -573,11 +573,16 @@ impl SimpleComponent for App {
connect_clicked[sender] => move |_| { connect_clicked[sender] => move |_| {
sender.input(AppMsg::DisableKillGameButton(true)); sender.input(AppMsg::DisableKillGameButton(true));
std::thread::spawn(clone!(@strong sender => move || { std::thread::spawn(clone!(
std::thread::sleep(std::time::Duration::from_secs(3)); #[strong]
sender,
sender.input(AppMsg::DisableKillGameButton(false)); move || {
})); std::thread::sleep(std::time::Duration::from_secs(3));
sender.input(AppMsg::DisableKillGameButton(false));
}
));
let result = std::process::Command::new("pkill") let result = std::process::Command::new("pkill")
.arg("-f") // full text search .arg("-f") // full text search
@ -733,134 +738,164 @@ impl SimpleComponent for App {
// TODO: reduce code somehow // TODO: reduce code somehow
group.add_action::<LauncherFolder>(RelmAction::new_stateless(clone!(@strong sender => move |_| { group.add_action::<LauncherFolder>(RelmAction::new_stateless(clone!(
if let Err(err) = open::that(LAUNCHER_FOLDER.as_path()) { #[strong]
sender.input(AppMsg::Toast { sender,
title: tr!("launcher-folder-opening-error"),
description: Some(err.to_string())
});
tracing::error!("Failed to open launcher folder: {err}"); move |_| {
} if let Err(err) = open::that(LAUNCHER_FOLDER.as_path()) {
})));
group.add_action::<GameFolder>(RelmAction::new_stateless(clone!(@strong sender => move |_| {
let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(),
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf()
};
if let Err(err) = open::that(path) {
sender.input(AppMsg::Toast {
title: tr!("game-folder-opening-error"),
description: Some(err.to_string())
});
tracing::error!("Failed to open game folder: {err}");
}
})));
group.add_action::<ConfigFile>(RelmAction::new_stateless(clone!(@strong sender => move |_| {
if let Ok(file) = config_file() {
if let Err(err) = open::that(file) {
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {
title: tr!("config-file-opening-error"), title: tr!("launcher-folder-opening-error"),
description: Some(err.to_string()) description: Some(err.to_string())
}); });
tracing::error!("Failed to open config file: {err}"); tracing::error!("Failed to open launcher folder: {err}");
} }
} }
}))); )));
group.add_action::<DebugFile>(RelmAction::new_stateless(clone!(@strong sender => move |_| { group.add_action::<GameFolder>(RelmAction::new_stateless(clone!(
if let Err(err) = open::that(crate::DEBUG_FILE.as_os_str()) { #[strong]
sender.input(AppMsg::Toast { sender,
title: tr!("debug-file-opening-error"),
description: Some(err.to_string())
});
tracing::error!("Failed to open debug file: {err}"); move |_| {
let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(),
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(),
};
if let Err(err) = open::that(path) {
sender.input(AppMsg::Toast {
title: tr!("game-folder-opening-error"),
description: Some(err.to_string())
});
tracing::error!("Failed to open game folder: {err}");
}
} }
}))); )));
group.add_action::<WishUrl>(RelmAction::new_stateless(clone!(@strong sender => move |_| { group.add_action::<ConfigFile>(RelmAction::new_stateless(clone!(
std::thread::spawn(clone!(@strong sender => move || { #[strong]
let config = Config::get().unwrap_or_else(|_| CONFIG.clone()); sender,
let web_cache = config.game.path.for_edition(config.launcher.edition) move |_| {
.join(config.launcher.edition.data_folder()) if let Ok(file) = config_file() {
.join("webCaches"); if let Err(err) = open::that(file) {
sender.input(AppMsg::Toast {
title: tr!("config-file-opening-error"),
description: Some(err.to_string())
});
// Find newest cache folder tracing::error!("Failed to open config file: {err}");
let mut web_cache_id = None;
if let Ok(entries) = web_cache.read_dir() {
for entry in entries.flatten() {
if entry.path().is_dir() &&
entry.file_name().to_string_lossy().trim_matches(|c| "0123456789.".contains(c)).is_empty() &&
Some(entry.file_name()) > web_cache_id
{
web_cache_id = Some(entry.file_name());
}
} }
} }
}
)));
if let Some(web_cache_id) = web_cache_id { group.add_action::<DebugFile>(RelmAction::new_stateless(clone!(
let web_cache = web_cache #[strong]
.join(web_cache_id) sender,
.join("Cache/Cache_Data/data_2");
match std::fs::read(web_cache) { move |_| {
Ok(web_cache) => { if let Err(err) = open::that(crate::DEBUG_FILE.as_os_str()) {
let web_cache = String::from_utf8_lossy(&web_cache); sender.input(AppMsg::Toast {
title: tr!("debug-file-opening-error"),
description: Some(err.to_string())
});
// https://webstatic-sea.[ho-yo-ver-se].com/hkrpg/event/e20211215gacha-v3/index.html?...... tracing::error!("Failed to open debug file: {err}");
if let Some(url) = web_cache.lines().rev().find(|line| line.contains("gacha-v2/index.html") || line.contains("gacha-v3/index.html")) { }
let url_begin_pos = url.find("https://").unwrap(); }
let url_end_pos = url_begin_pos + url[url_begin_pos..].find("\0\0\0\0").unwrap(); )));
if let Err(err) = open::that(format!("{}#/log", &url[url_begin_pos..url_end_pos])) { group.add_action::<WishUrl>(RelmAction::new_stateless(clone!(
tracing::error!("Failed to open wishes URL: {err}"); #[strong]
sender,
move |_| {
std::thread::spawn(clone!(
#[strong]
sender,
move || {
let config = Config::get().unwrap_or_else(|_| CONFIG.clone());
let web_cache = config.game.path.for_edition(config.launcher.edition)
.join(config.launcher.edition.data_folder())
.join("webCaches");
// Find newest cache folder
let mut web_cache_id = None;
if let Ok(entries) = web_cache.read_dir() {
for entry in entries.flatten() {
if entry.path().is_dir() &&
entry.file_name().to_string_lossy().trim_matches(|c| "0123456789.".contains(c)).is_empty() &&
Some(entry.file_name()) > web_cache_id
{
web_cache_id = Some(entry.file_name());
}
}
}
if let Some(web_cache_id) = web_cache_id {
let web_cache = web_cache
.join(web_cache_id)
.join("Cache/Cache_Data/data_2");
match std::fs::read(web_cache) {
Ok(web_cache) => {
let web_cache = String::from_utf8_lossy(&web_cache);
// https://webstatic-sea.[ho-yo-ver-se].com/[ge-nsh-in]/event/e20190909gacha-v2/index.html?......
if let Some(url) = web_cache.lines().rev().find(|line| line.contains("gacha-v3/index.html")) {
let url_begin_pos = url.find("https://").unwrap();
let url_end_pos = url_begin_pos + url[url_begin_pos..].find("\0\0\0\0").unwrap();
if let Err(err) = open::that(format!("{}#/log", &url[url_begin_pos..url_end_pos])) {
tracing::error!("Failed to open wishes URL: {err}");
sender.input(AppMsg::Toast {
title: tr!("wish-url-opening-error"),
description: Some(err.to_string())
});
}
}
else {
tracing::error!("Couldn't find wishes URL: no url found");
sender.input(AppMsg::Toast {
title: tr!("wish-url-search-failed"),
description: None
});
}
}
Err(err) => {
tracing::error!("Couldn't find wishes URL: failed to open cache file: {err}");
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {
title: tr!("wish-url-opening-error"), title: tr!("wish-url-search-failed"),
description: Some(err.to_string()) description: Some(err.to_string())
}); });
} }
} }
else {
tracing::error!("Couldn't find wishes URL: no url found");
sender.input(AppMsg::Toast {
title: tr!("wish-url-search-failed"),
description: None
});
}
} }
Err(err) => { else {
tracing::error!("Couldn't find wishes URL: failed to open cache file: {err}"); tracing::error!("Couldn't find wishes URL: cache file doesn't exist");
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {
title: tr!("wish-url-search-failed"), title: tr!("wish-url-search-failed"),
description: Some(err.to_string()) description: None
}); });
} }
} }
} ));
}
else { )));
tracing::error!("Couldn't find wishes URL: cache file doesn't exist");
sender.input(AppMsg::Toast {
title: tr!("wish-url-search-failed"),
description: None
});
}
}));
})));
group.add_action::<About>(RelmAction::new_stateless(move |_| { group.add_action::<About>(RelmAction::new_stateless(move |_| {
about_dialog_broker.send(AboutDialogMsg::Show); about_dialog_broker.send(AboutDialogMsg::Show);
@ -881,115 +916,135 @@ impl SimpleComponent for App {
// Download background picture if needed // Download background picture if needed
if download_picture { if download_picture {
tasks.push(std::thread::spawn(clone!(@strong sender => move || { tasks.push(std::thread::spawn(clone!(
if let Err(err) = crate::background::download_background() { #[strong]
tracing::error!("Failed to download background picture: {err}"); sender,
sender.input(AppMsg::Toast { move || {
title: tr!("background-downloading-failed"), if let Err(err) = crate::background::download_background() {
description: Some(err.to_string()) tracing::error!("Failed to download background picture: {err}");
});
sender.input(AppMsg::Toast {
title: tr!("background-downloading-failed"),
description: Some(err.to_string())
});
}
} }
}))); )));
} }
// Update components index // Update components index
tasks.push(std::thread::spawn(clone!(@strong sender => move || { tasks.push(std::thread::spawn(clone!(
let components = ComponentsLoader::new(&CONFIG.components.path); #[strong]
sender,
match components.is_sync(&CONFIG.components.servers) { move || {
Ok(Some(_)) => (), let components = ComponentsLoader::new(&CONFIG.components.path);
Ok(None) => { match components.is_sync(&CONFIG.components.servers) {
for host in &CONFIG.components.servers { Ok(Some(_)) => (),
match components.sync(host) {
Ok(changes) => {
sender.input(AppMsg::Toast {
title: tr!("components-index-updated"),
description: if changes.is_empty() {
None
} else {
Some(changes.into_iter()
.map(|line| format!("- {line}"))
.collect::<Vec<_>>()
.join("\n"))
}
});
break; Ok(None) => {
} for host in &CONFIG.components.servers {
match components.sync(host) {
Ok(changes) => {
sender.input(AppMsg::Toast {
title: tr!("components-index-updated"),
description: if changes.is_empty() {
None
} else {
Some(changes.into_iter()
.map(|line| format!("- {line}"))
.collect::<Vec<_>>()
.join("\n"))
}
});
Err(err) => { break;
tracing::error!("Failed to sync components index"); }
sender.input(AppMsg::Toast { Err(err) => {
title: tr!("components-index-sync-failed"), tracing::error!("Failed to sync components index");
description: Some(err.to_string())
}); sender.input(AppMsg::Toast {
title: tr!("components-index-sync-failed"),
description: Some(err.to_string())
});
}
} }
} }
} }
}
Err(err) => { Err(err) => {
tracing::error!("Failed to verify that components index synced"); tracing::error!("Failed to verify that components index synced");
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {
title: tr!("components-index-verify-failed"), title: tr!("components-index-verify-failed"),
description: Some(err.to_string()) description: Some(err.to_string())
}); });
}
} }
} }
}))); )));
// Update initial patch status // Update initial patch status
tasks.push(std::thread::spawn(clone!(@strong sender => move || { tasks.push(std::thread::spawn(clone!(
// Get main patch status #[strong]
sender.input(AppMsg::SetMainPatch(match jadeite::get_metadata() { sender,
Ok(metadata) => {
let status = GAME.get_version()
.map(|version| metadata.games.hsr.global.get_status(version))
.unwrap_or(metadata.games.hsr.global.status);
Some((metadata.jadeite.version, status)) move || {
} // Get main patch status
sender.input(AppMsg::SetMainPatch(match jadeite::get_metadata() {
Ok(metadata) => {
let status = GAME.get_version()
.map(|version| metadata.games.hsr.global.get_status(version))
.unwrap_or(metadata.games.hsr.global.status);
Err(err) => { Some((metadata.jadeite.version, status))
tracing::error!("Failed to fetch patch metadata: {err}"); }
sender.input(AppMsg::Toast { Err(err) => {
title: tr!("patch-info-fetching-error"), tracing::error!("Failed to fetch patch metadata: {err}");
description: Some(err.to_string())
});
None sender.input(AppMsg::Toast {
} title: tr!("patch-info-fetching-error"),
})); description: Some(err.to_string())
});
tracing::info!("Updated patch status"); None
}))); }
}));
tracing::info!("Updated patch status");
}
)));
// Update initial game version status // Update initial game version status
tasks.push(std::thread::spawn(clone!(@strong sender => move || { tasks.push(std::thread::spawn(clone!(
sender.input(AppMsg::SetGameDiff(match GAME.try_get_diff() { #[strong]
Ok(diff) => Some(diff), sender,
Err(err) => {
tracing::error!("Failed to find game diff: {err}");
sender.input(AppMsg::Toast { move || {
title: tr!("game-diff-finding-error"), sender.input(AppMsg::SetGameDiff(match GAME.try_get_diff() {
description: Some(err.to_string()) Ok(diff) => Some(diff),
}); Err(err) => {
tracing::error!("Failed to find game diff: {err}");
None sender.input(AppMsg::Toast {
} title: tr!("game-diff-finding-error"),
})); description: Some(err.to_string())
});
tracing::info!("Updated game version status"); None
}))); }
}));
tracing::info!("Updated game version status");
}
)));
// Await for tasks to finish execution // Await for tasks to finish execution
for task in tasks { for task in tasks {
@ -1023,25 +1078,30 @@ impl SimpleComponent for App {
self.disabled_buttons = true; self.disabled_buttons = true;
} }
let updater = clone!(@strong sender => move |state| { let updater = clone!(
if show_status_page { #[strong]
match state { sender,
StateUpdating::Game => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr!("loading-launcher-state--game")))));
}
StateUpdating::Voice(locale) => { move |state| {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr!("loading-launcher-state--voice", { if show_status_page {
"locale" = locale.to_name() match state {
}))))); StateUpdating::Game => {
} sender.input(AppMsg::SetLoadingStatus(Some(Some(tr!("loading-launcher-state--game")))));
}
StateUpdating::Patch => { StateUpdating::Voice(locale) => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr!("loading-launcher-state--patch"))))); sender.input(AppMsg::SetLoadingStatus(Some(Some(tr!("loading-launcher-state--voice", {
"locale" = locale.to_name()
})))));
}
StateUpdating::Patch => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr!("loading-launcher-state--patch")))));
}
} }
} }
} }
}); );
let state = match LauncherState::get_from_config(updater) { let state = match LauncherState::get_from_config(updater) {
Ok(state) => Some(state), Ok(state) => Some(state),
@ -1137,9 +1197,14 @@ impl SimpleComponent for App {
std::thread::spawn(move || { std::thread::spawn(move || {
for mut diff in diffs { for mut diff in diffs {
let result = diff.download_to(&tmp, clone!(@strong progress_bar_input => move |curr, total| { let result = diff.download_to(&tmp, clone!(
progress_bar_input.send(ProgressBarMsg::UpdateProgress(curr, total)); #[strong]
})); progress_bar_input,
move |curr, total| {
progress_bar_input.send(ProgressBarMsg::UpdateProgress(curr, total));
}
));
if let Err(err) = result { if let Err(err) = result {
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {

View file

@ -62,17 +62,22 @@ pub fn repair_game(sender: ComponentSender<App>, progress_bar_input: Sender<Prog
let thread_sender = verify_sender.clone(); let thread_sender = verify_sender.clone();
std::thread::spawn(clone!(@strong game_path => move || { std::thread::spawn(clone!(
for file in thread_files { #[strong]
let status = if config.launcher.repairer.fast { game_path,
file.fast_verify(&game_path)
} else {
file.verify(&game_path)
};
thread_sender.send((file, status)).unwrap(); move || {
for file in thread_files {
let status = if config.launcher.repairer.fast {
file.fast_verify(&game_path)
} else {
file.verify(&game_path)
};
thread_sender.send((file, status)).unwrap();
}
} }
})); ));
} }
// We have [config.launcher.repairer.threads] copies of this sender + the original one // We have [config.launcher.repairer.threads] copies of this sender + the original one

View file

@ -17,33 +17,38 @@ pub fn update_patch(sender: ComponentSender<App>, progress_bar_input: Sender<Pro
std::thread::spawn(move || { std::thread::spawn(move || {
let result = jadeite::get_latest() let result = jadeite::get_latest()
.and_then(|patch| patch.install(config.patch.path, clone!(@strong sender => move |state| { .and_then(|patch| patch.install(config.patch.path, clone!(
match &state { #[strong]
InstallerUpdate::DownloadingError(err) => { sender,
tracing::error!("Downloading failed: {err}");
sender.input(AppMsg::Toast { move |state| {
title: tr!("downloading-failed"), match &state {
description: Some(err.to_string()) InstallerUpdate::DownloadingError(err) => {
}); tracing::error!("Downloading failed: {err}");
sender.input(AppMsg::Toast {
title: tr!("downloading-failed"),
description: Some(err.to_string())
});
}
InstallerUpdate::UnpackingError(err) => {
tracing::error!("Unpacking failed: {err}");
sender.input(AppMsg::Toast {
title: tr!("unpacking-failed"),
description: Some(err.clone())
});
}
_ => ()
} }
InstallerUpdate::UnpackingError(err) => { #[allow(unused_must_use)] {
tracing::error!("Unpacking failed: {err}"); progress_bar_input.send(ProgressBarMsg::UpdateFromState(state.into()));
sender.input(AppMsg::Toast {
title: tr!("unpacking-failed"),
description: Some(err.clone())
});
} }
_ => ()
} }
)));
#[allow(unused_must_use)] {
progress_bar_input.send(ProgressBarMsg::UpdateFromState(state.into()));
}
})));
if let Err(err) = result { if let Err(err) = result {
tracing::error!("Failed to download latest patch version"); tracing::error!("Failed to download latest patch version");

View file

@ -1,52 +0,0 @@
use relm4::prelude::*;
use gtk::prelude::*;
use crate::tr;
use super::first_run::default_paths::DefaultPathsApp;
pub struct MigrateInstallationApp {
default_paths: AsyncController<DefaultPathsApp>,
}
#[relm4::component(pub)]
impl SimpleComponent for MigrateInstallationApp {
type Init = ();
type Input = ();
type Output = ();
view! {
adw::Window {
set_default_size: (780, 560),
set_modal: true,
set_hide_on_close: true,
#[watch]
set_title: Some(&tr!("migrate-installation")),
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
adw::HeaderBar {
add_css_class: "flat"
},
append = model.default_paths.widget(),
}
}
}
fn init(_init: Self::Init, root: Self::Root, _sender: ComponentSender<Self>) -> ComponentParts<Self> {
tracing::info!("Initializing migration window");
let model = Self {
default_paths: DefaultPathsApp::builder()
.launch(true)
.detach()
};
let widgets = view_output!();
ComponentParts { model, widgets }
}
}

View file

@ -3,4 +3,3 @@ pub mod about;
pub mod preferences; pub mod preferences;
pub mod components; pub mod components;
pub mod first_run; pub mod first_run;
pub mod migrate_installation;

View file

@ -212,7 +212,8 @@ impl SimpleAsyncComponent for EnhancementsApp {
"Español", "Español",
"中国", "中国",
"日本語", "日本語",
"한국어" "한국어",
"Indonesia"
]), ]),
set_selected: CONFIG.game.wine.language.ordinal() as u32, set_selected: CONFIG.game.wine.language.ordinal() as u32,

View file

@ -22,7 +22,6 @@ pub mod components;
use components::*; use components::*;
use crate::ui::migrate_installation::MigrateInstallationApp;
use crate::ui::preferences::main::PreferencesAppMsg; use crate::ui::preferences::main::PreferencesAppMsg;
use crate::i18n::*; use crate::i18n::*;
@ -103,7 +102,6 @@ impl AsyncFactoryComponent for VoicePackageComponent {
pub struct GeneralApp { pub struct GeneralApp {
voice_packages: AsyncFactoryVecDeque<VoicePackageComponent>, voice_packages: AsyncFactoryVecDeque<VoicePackageComponent>,
migrate_installation: Controller<MigrateInstallationApp>,
components_page: AsyncController<ComponentsPage>, components_page: AsyncController<ComponentsPage>,
game_diff: Option<VersionDiff>, game_diff: Option<VersionDiff>,
@ -133,7 +131,6 @@ pub enum GeneralAppMsg {
UpdateDownloadedWine, UpdateDownloadedWine,
UpdateDownloadedDxvk, UpdateDownloadedDxvk,
OpenMigrateInstallation,
RepairGame, RepairGame,
OpenMainPage, OpenMainPage,
@ -315,13 +312,6 @@ impl SimpleAsyncComponent for GeneralApp {
set_spacing: 8, set_spacing: 8,
set_margin_top: 16, set_margin_top: 16,
gtk::Button {
set_label: &tr!("migrate-installation"),
set_tooltip_text: Some(&tr!("migrate-installation-description")),
connect_clicked => GeneralAppMsg::OpenMigrateInstallation
},
gtk::Button { gtk::Button {
set_label: &tr!("repair-game"), set_label: &tr!("repair-game"),
@ -557,10 +547,6 @@ impl SimpleAsyncComponent for GeneralApp {
.launch_default() .launch_default()
.forward(sender.input_sender(), std::convert::identity), .forward(sender.input_sender(), std::convert::identity),
migrate_installation: MigrateInstallationApp::builder()
.launch(())
.detach(),
components_page: ComponentsPage::builder() components_page: ComponentsPage::builder()
.launch(()) .launch(())
.forward(sender.input_sender(), std::convert::identity), .forward(sender.input_sender(), std::convert::identity),
@ -668,14 +654,6 @@ impl SimpleAsyncComponent for GeneralApp {
.unwrap(); .unwrap();
} }
GeneralAppMsg::OpenMigrateInstallation => unsafe {
if let Some(window) = crate::ui::main::PREFERENCES_WINDOW.as_ref() {
self.migrate_installation.widget().set_transient_for(Some(window.widget()));
}
self.migrate_installation.widget().present();
}
GeneralAppMsg::RepairGame => { GeneralAppMsg::RepairGame => {
sender.output(Self::Output::RepairGame).unwrap(); sender.output(Self::Output::RepairGame).unwrap();
} }