Initial commit
This commit is contained in:
commit
cbda62e750
24 changed files with 6227 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
11
.idea/luncher.iml
generated
Normal file
11
.idea/luncher.iml
generated
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="EMPTY_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/luncher.iml" filepath="$PROJECT_DIR$/.idea/luncher.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
5128
Cargo.lock
generated
Normal file
5128
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
32
Cargo.toml
Normal file
32
Cargo.toml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
[package]
|
||||||
|
name = "luncher"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
egui = { version = "0.30.0", features = ["deadlock_detection", "persistence"] }
|
||||||
|
eframe = { version = "0.30.0", features = ["persistence"] }
|
||||||
|
ehttp = { version = "0.5.0", features = ["streaming"] }
|
||||||
|
|
||||||
|
egui_extras = { version = "0.30.0", features = ["image"] }
|
||||||
|
egui-phosphor = { version = "0.8.0", features = ["fill"] }
|
||||||
|
egui_flex = "0.2.0"
|
||||||
|
|
||||||
|
dark-light = "2.0.0"
|
||||||
|
rfd = "0.15.2"
|
||||||
|
|
||||||
|
toml = "0.8.12"
|
||||||
|
serde = "1.0.201"
|
||||||
|
serde_derive = "1.0.201"
|
||||||
|
serde_json = "1.0.135"
|
||||||
|
|
||||||
|
keyring = { version = "3.6.1", features = ["apple-native", "windows-native", "sync-secret-service"] }
|
||||||
|
|
||||||
|
directories = "6.0.0"
|
||||||
|
|
||||||
|
blake3 = "1.5.5"
|
||||||
|
|
||||||
|
image = { version = "0.25.5", features = ["png"] }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
strip="symbols"
|
176
LICENSE
Normal file
176
LICENSE
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# osrs luncher
|
BIN
assets/Cooking_icon.png
Normal file
BIN
assets/Cooking_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
BIN
assets/Steam_client_logo.png
Normal file
BIN
assets/Steam_client_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 175 KiB |
76
src/backend/config.rs
Normal file
76
src/backend/config.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use directories::ProjectDirs;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub static CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||||
|
pub static CONFIG_FILE: OnceLock<PathBuf> = OnceLock::new();
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Paths {
|
||||||
|
pub runelite_path: String,
|
||||||
|
pub java_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Versions {
|
||||||
|
pub runelite_version: String,
|
||||||
|
pub runelite_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub paths: Paths,
|
||||||
|
pub versions: Versions,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new() -> Config {
|
||||||
|
let mut runelite_path = CONFIG_DIR.get().unwrap().clone();
|
||||||
|
runelite_path.push("runelite.jar");
|
||||||
|
|
||||||
|
let new_config = Config {
|
||||||
|
paths: Paths { runelite_path: runelite_path.into_os_string().into_string().unwrap(), java_path: "java".to_string() },
|
||||||
|
versions: Versions { runelite_version: "".to_string(), runelite_hash: "".to_string() },
|
||||||
|
};
|
||||||
|
|
||||||
|
new_config.save_config();
|
||||||
|
|
||||||
|
new_config
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config() -> Config {
|
||||||
|
let binding = ProjectDirs::from("sex", "gaycatgirl", "osrs-luncher").expect("Could not generate config path");
|
||||||
|
let expected_config_dir = binding.config_dir();
|
||||||
|
CONFIG_DIR.set(expected_config_dir.to_path_buf()).expect("Failed to set config dir");
|
||||||
|
|
||||||
|
let expected_config_file = expected_config_dir.join("config.toml");
|
||||||
|
CONFIG_FILE.set(expected_config_file.to_path_buf()).expect("Failed to set config file");
|
||||||
|
|
||||||
|
// check to see if a config already exists at the expected path
|
||||||
|
match fs::read_to_string(&expected_config_file) {
|
||||||
|
Ok(config_contents) => {
|
||||||
|
toml::from_str::<Config>(&config_contents).unwrap_or_else(|_| Config::new())
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
match error.kind() {
|
||||||
|
ErrorKind::NotFound => {
|
||||||
|
// config doesn't exist, create
|
||||||
|
Config::new()
|
||||||
|
}
|
||||||
|
_ => panic!("Could not read config file: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_config(&self) {
|
||||||
|
let file_path = CONFIG_FILE.get().unwrap().clone();
|
||||||
|
|
||||||
|
fs::create_dir_all(file_path.parent().unwrap()).expect("Could not create config directory");
|
||||||
|
fs::write(file_path, toml::to_string_pretty(self).expect("Failed to generate config string")).expect("Could not write config file");
|
||||||
|
}
|
||||||
|
}
|
0
src/backend/hash.rs
Normal file
0
src/backend/hash.rs
Normal file
85
src/backend/http.rs
Normal file
85
src/backend/http.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
use ehttp::Response;
|
||||||
|
use ehttp::streaming::Part;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct HttpState {
|
||||||
|
pub channel: Option<std::sync::mpsc::Receiver<HttpChannelChunk>>,
|
||||||
|
pub request_in_progress: bool,
|
||||||
|
pub total_size: f32,
|
||||||
|
pub current_size: f32,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HttpChannelChunk {
|
||||||
|
Start(f32),
|
||||||
|
Data(Vec<u8>),
|
||||||
|
End,
|
||||||
|
Oneshot(Response)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl HttpState {
|
||||||
|
pub fn new() -> HttpState {
|
||||||
|
HttpState {
|
||||||
|
channel: None,
|
||||||
|
request_in_progress: false,
|
||||||
|
total_size: 0.0,
|
||||||
|
current_size: 0.0,
|
||||||
|
data: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn do_request(&mut self, req: ehttp::Request) {
|
||||||
|
if !self.request_in_progress {
|
||||||
|
self.request_in_progress = true;
|
||||||
|
|
||||||
|
let (sender, receiver) = channel();
|
||||||
|
self.channel = Some(receiver);
|
||||||
|
|
||||||
|
ehttp::streaming::fetch(req, move |res| {
|
||||||
|
let part = match res {
|
||||||
|
Ok(part) => part,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("waaa waaa waaa {err}");
|
||||||
|
return std::ops::ControlFlow::Break(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match part {
|
||||||
|
Part::Response(response) => {
|
||||||
|
if response.ok {
|
||||||
|
let total_size = response.headers.get("Content-Length").expect("Missing Content-Length").parse().expect("Content-Length wasn't a number");
|
||||||
|
|
||||||
|
sender.send(HttpChannelChunk::Start(total_size)).expect("Unable to send start chunk");
|
||||||
|
|
||||||
|
std::ops::ControlFlow::Continue(())
|
||||||
|
} else {
|
||||||
|
std::ops::ControlFlow::Break(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Part::Chunk(chunk) => {
|
||||||
|
if chunk.is_empty() {
|
||||||
|
sender.send(HttpChannelChunk::End).expect("Unable to send end chunk");
|
||||||
|
} else {
|
||||||
|
sender.send(HttpChannelChunk::Data(chunk)).expect("Unable to send data chunk");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ops::ControlFlow::Continue(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn do_oneshot_request(&mut self, req: ehttp::Request) {
|
||||||
|
self.request_in_progress = true;
|
||||||
|
|
||||||
|
let (sender, receiver) = channel();
|
||||||
|
self.channel = Some(receiver);
|
||||||
|
|
||||||
|
ehttp::fetch(req, move |res| {
|
||||||
|
sender.send(HttpChannelChunk::Oneshot(res.expect("Invalid response"))).expect("Failed to send oneshot response")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
0
src/backend/messages.rs
Normal file
0
src/backend/messages.rs
Normal file
5
src/backend/mod.rs
Normal file
5
src/backend/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod secrets;
|
||||||
|
pub mod messages;
|
||||||
|
pub mod http;
|
||||||
|
pub mod config;
|
||||||
|
mod hash;
|
48
src/backend/secrets.rs
Normal file
48
src/backend/secrets.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use keyring::Entry;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Account {
|
||||||
|
pub session_id: String,
|
||||||
|
pub character_id: String,
|
||||||
|
pub character_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Account {
|
||||||
|
pub fn get_secrets() -> Option<Self> {
|
||||||
|
let session_id = Entry::new("osrs-luncher", "session-id").expect("Unable to get session_id entry");
|
||||||
|
|
||||||
|
if session_id.get_password().is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let character_id = Entry::new("osrs-luncher", "character-id").expect("Unable to get character_id entry");
|
||||||
|
let character_name = Entry::new("osrs-luncher", "character-name").expect("Unable to get character_name entry");
|
||||||
|
|
||||||
|
Some(Account {
|
||||||
|
session_id: session_id.get_password().expect("Unable to get session_id entry content"),
|
||||||
|
character_id: character_id.get_password().expect("Unable to get character_id entry content"),
|
||||||
|
character_name: character_name.get_password().expect("Unable to get character_name entry content"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_secrets(&self) -> bool {
|
||||||
|
let session_id = Entry::new("osrs-luncher", "session-id").expect("Unable to get session_id entry");
|
||||||
|
let character_id = Entry::new("osrs-luncher", "character-id").expect("Unable to get character_id entry");
|
||||||
|
let character_name = Entry::new("osrs-luncher", "character-name").expect("Unable to get character_name entry");
|
||||||
|
|
||||||
|
session_id.set_password(self.session_id.as_str()).expect("Unable to set session_id");
|
||||||
|
character_id.set_password(self.character_id.as_str()).expect("Unable to set character_id");
|
||||||
|
character_name.set_password(self.character_name.as_str()).expect("Unable to set character_name");
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_secrets(&self) -> bool {
|
||||||
|
let session_id = Entry::new("osrs-luncher", "session-id").expect("Unable to get session_id entry").delete_credential().is_ok();
|
||||||
|
let character_id = Entry::new("osrs-luncher", "character-id").expect("Unable to get character_id entry").delete_credential().is_ok();
|
||||||
|
let character_name = Entry::new("osrs-luncher", "character-name").expect("Unable to get character_name entry").delete_credential().is_ok();
|
||||||
|
|
||||||
|
session_id | character_id | character_name
|
||||||
|
}
|
||||||
|
}
|
161
src/main.rs
Normal file
161
src/main.rs
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
use std::process::Child;
|
||||||
|
use dark_light::Mode;
|
||||||
|
use egui::{IconData, Theme, ViewportBuilder};
|
||||||
|
use egui_extras::install_image_loaders;
|
||||||
|
use egui_flex::{item, Flex, FlexAlign, FlexAlignContent};
|
||||||
|
use image::GenericImageView;
|
||||||
|
use backend::config::{Config, Paths, Versions};
|
||||||
|
use crate::backend::http::HttpState;
|
||||||
|
use crate::backend::secrets::Account;
|
||||||
|
use crate::ui::landing::Landing;
|
||||||
|
use crate::ui::{game_open, Pages};
|
||||||
|
use crate::ui::launcher::Launcher;
|
||||||
|
|
||||||
|
mod ui;
|
||||||
|
mod backend;
|
||||||
|
|
||||||
|
fn main() -> Result<(), eframe::Error> {
|
||||||
|
let default_theme: Theme = match dark_light::detect() {
|
||||||
|
Ok(Mode::Dark) => Theme::Dark,
|
||||||
|
_ => Theme::Light
|
||||||
|
};
|
||||||
|
|
||||||
|
let icon = image::load_from_memory(include_bytes!("../assets/Cooking_icon.png")).expect("Failed to load Cooking_icon").to_rgba8();
|
||||||
|
|
||||||
|
let viewport = egui::ViewportBuilder::default()
|
||||||
|
.with_title("OSRS Luncher")
|
||||||
|
.with_app_id("sex.gaycatgirl.luncher")
|
||||||
|
.with_icon(IconData {
|
||||||
|
rgba: icon.clone().into_raw(),
|
||||||
|
width: icon.width(),
|
||||||
|
height: icon.height(),
|
||||||
|
})
|
||||||
|
.with_fullscreen(true);
|
||||||
|
|
||||||
|
let mut fonts = egui::FontDefinitions::default();
|
||||||
|
egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Regular);
|
||||||
|
egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Fill);
|
||||||
|
|
||||||
|
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
viewport,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
eframe::run_native(
|
||||||
|
"osrs luncher",
|
||||||
|
options,
|
||||||
|
Box::new(|ctx| {
|
||||||
|
ctx.egui_ctx.set_fonts(fonts);
|
||||||
|
ctx.egui_ctx.set_theme(default_theme);
|
||||||
|
|
||||||
|
install_image_loaders(&ctx.egui_ctx);
|
||||||
|
|
||||||
|
Ok(Box::<App>::default())
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
child: Option<Child>,
|
||||||
|
game_open: bool,
|
||||||
|
current_page: Pages,
|
||||||
|
next_page: Option<Pages>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
config: Config,
|
||||||
|
account: Option<Account>,
|
||||||
|
state: State,
|
||||||
|
http_state: HttpState,
|
||||||
|
working: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for App {
|
||||||
|
fn default() -> Self {
|
||||||
|
App {
|
||||||
|
config: Config { paths: Paths { runelite_path: "".to_string(), java_path: "".to_string() }, versions: Versions { runelite_version: "".to_string(), runelite_hash: "".to_string() } },
|
||||||
|
account: None,
|
||||||
|
state: State { child: None, game_open: false, current_page: Pages::Landing(None), next_page: None },
|
||||||
|
http_state: HttpState::new(),
|
||||||
|
working: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for App {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
// If we've requested a page change, change to it
|
||||||
|
if let Some(next_page) = self.state.next_page.take() {
|
||||||
|
self.state.current_page = next_page;
|
||||||
|
|
||||||
|
self.state.next_page = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look to see if the child process has exited yet and reset to the launcher if it has
|
||||||
|
if let Some(child) = self.state.child.as_mut() {
|
||||||
|
if let Ok(Some(_)) = child.try_wait() {
|
||||||
|
self.state.game_open = false;
|
||||||
|
self.state.child = None;
|
||||||
|
|
||||||
|
self.state.current_page = Pages::Launcher(Launcher::init());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure to show the game open page when its open
|
||||||
|
if self.state.game_open {
|
||||||
|
self.state.current_page = Pages::GameOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
egui::TopBottomPanel::bottom("footer").show(ctx, |ui| {
|
||||||
|
Flex::new().w_full().show(ui, |flex| {
|
||||||
|
flex.add_ui(item(), |ui| {
|
||||||
|
ui.label(format!("osrs luncher v{}", env!("CARGO_PKG_VERSION")));
|
||||||
|
ui.separator();
|
||||||
|
ui.hyperlink_to("see the source", "https://github.com/");
|
||||||
|
});
|
||||||
|
|
||||||
|
flex.grow();
|
||||||
|
|
||||||
|
flex.add_ui(item(), |ui| {
|
||||||
|
if self.working {
|
||||||
|
ui.spinner();
|
||||||
|
ui.label("working...");
|
||||||
|
|
||||||
|
self.working = false;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, move |ui| {
|
||||||
|
ui.heading("osrs luncher");
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
match &mut self.state.current_page {
|
||||||
|
Pages::Landing(landing) => {
|
||||||
|
if let Some(landing) = landing.as_mut() {
|
||||||
|
landing.update(&mut self.state.next_page, &mut self.http_state, &mut self.account, &mut self.config, &mut self.working, ctx, ui)
|
||||||
|
} else {
|
||||||
|
*landing = Some(Landing::init())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Pages::Login(login) => {
|
||||||
|
login.update(&mut self.state.next_page, &mut self.account, &mut self.working, ctx, ui)
|
||||||
|
}
|
||||||
|
Pages::Settings(settings) => {
|
||||||
|
settings.update(&mut self.state.next_page, &mut self.config, ctx, ui)
|
||||||
|
}
|
||||||
|
Pages::Launcher(launcher) => {
|
||||||
|
launcher.update(&mut self.state.next_page, &mut self.account, &mut self.config, &mut self.state.child, &mut self.state.game_open, ctx, ui);
|
||||||
|
}
|
||||||
|
Pages::GameOpen => {
|
||||||
|
game_open::update(&mut self.state.child, ctx, ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
10
src/ui/game_open.rs
Normal file
10
src/ui/game_open.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use std::process::Child;
|
||||||
|
use egui::{Context, Ui};
|
||||||
|
|
||||||
|
pub fn update(child: &mut Option<Child>, ctx: &Context, ui: &mut Ui) {
|
||||||
|
ui.heading("Close OSRS to continue!");
|
||||||
|
|
||||||
|
if ui.button("Close").clicked() {
|
||||||
|
child.as_mut().unwrap().kill().expect("Failed to kill");
|
||||||
|
}
|
||||||
|
}
|
199
src/ui/landing.rs
Normal file
199
src/ui/landing.rs
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use egui::{Context, ProgressBar, Ui};
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
use crate::backend::config::{Config, CONFIG_DIR};
|
||||||
|
use crate::backend::http::HttpChannelChunk;
|
||||||
|
use crate::backend::secrets::Account;
|
||||||
|
use crate::HttpState;
|
||||||
|
use crate::ui::launcher::Launcher;
|
||||||
|
use crate::ui::login::{Login, LoginType};
|
||||||
|
use crate::ui::Pages;
|
||||||
|
|
||||||
|
pub struct Landing {
|
||||||
|
stage: LandingStage,
|
||||||
|
server_version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum LandingStage {
|
||||||
|
Config,
|
||||||
|
CheckUpdate,
|
||||||
|
Verify,
|
||||||
|
Download,
|
||||||
|
Account,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Landing {
|
||||||
|
pub fn init() -> Self {
|
||||||
|
Landing {
|
||||||
|
stage: LandingStage::Config,
|
||||||
|
server_version: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self,
|
||||||
|
next_page: &mut Option<Pages>,
|
||||||
|
http_state: &mut HttpState,
|
||||||
|
account: &mut Option<Account>,
|
||||||
|
config: &mut Config,
|
||||||
|
working: &mut bool,
|
||||||
|
ctx: &Context,
|
||||||
|
ui: &mut Ui
|
||||||
|
) {
|
||||||
|
match self.stage {
|
||||||
|
LandingStage::Config => {
|
||||||
|
ui.label("loading config");
|
||||||
|
*working = true;
|
||||||
|
|
||||||
|
*config = Config::get_config();
|
||||||
|
|
||||||
|
self.stage = LandingStage::CheckUpdate;
|
||||||
|
}
|
||||||
|
LandingStage::CheckUpdate => {
|
||||||
|
ui.label("checking for updates");
|
||||||
|
*working = true;
|
||||||
|
|
||||||
|
// Only do these checks if we're managing the runelite jar
|
||||||
|
if Path::new(&config.paths.runelite_path).to_path_buf().parent().unwrap().eq(CONFIG_DIR.get().unwrap()) {
|
||||||
|
if http_state.request_in_progress {
|
||||||
|
if let Some(channel) = &http_state.channel {
|
||||||
|
if let Ok(res) = channel.try_recv() {
|
||||||
|
match res {
|
||||||
|
HttpChannelChunk::Oneshot(res) => {
|
||||||
|
let res_json: Map<String, Value> = serde_json::from_slice(&res.bytes).expect("Invalid json from server");
|
||||||
|
|
||||||
|
let server_version = res_json.get("tag_name").expect("Missing tag_name in server response").as_str().unwrap().to_string();
|
||||||
|
|
||||||
|
println!("Server version: {}", server_version);
|
||||||
|
|
||||||
|
self.server_version = Some(server_version.clone());
|
||||||
|
|
||||||
|
if config.versions.runelite_version.ne(&server_version) {
|
||||||
|
self.stage = LandingStage::Download;
|
||||||
|
} else {
|
||||||
|
self.stage = LandingStage::Verify;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're done, destroy channel and reset fields
|
||||||
|
http_state.channel = None;
|
||||||
|
http_state.data = vec![];
|
||||||
|
http_state.total_size = 0.0;
|
||||||
|
http_state.current_size = 0.0;
|
||||||
|
http_state.request_in_progress = false;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Invalid!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http_state.do_oneshot_request(ehttp::Request::get("https://api.github.com/repos/runelite/launcher/releases/latest"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LandingStage::Verify => {
|
||||||
|
ui.label("verifying runelite");
|
||||||
|
*working = true;
|
||||||
|
|
||||||
|
self.stage = LandingStage::Account;
|
||||||
|
|
||||||
|
if !Path::new(&config.paths.runelite_path).exists() {
|
||||||
|
self.stage = LandingStage::Download;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only do these checks if we're managing the runelite jar
|
||||||
|
if Path::new(&config.paths.runelite_path).to_path_buf().parent().unwrap().eq(CONFIG_DIR.get().unwrap()) {
|
||||||
|
if config.versions.runelite_version.is_empty() || config.versions.runelite_hash.is_empty() {
|
||||||
|
self.stage = LandingStage::Download;
|
||||||
|
}
|
||||||
|
|
||||||
|
let jar = fs::read(&config.paths.runelite_path);
|
||||||
|
match jar {
|
||||||
|
Ok(jar) => {
|
||||||
|
if blake3::hash(&jar).to_hex().to_string() != config.versions.runelite_hash {
|
||||||
|
self.stage = LandingStage::Download;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_=> {
|
||||||
|
self.stage = LandingStage::Download;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LandingStage::Download => {
|
||||||
|
ui.label("downloading runelite");
|
||||||
|
*working = true;
|
||||||
|
|
||||||
|
let update_to = self.server_version.clone().expect("Asked to download without version!");
|
||||||
|
|
||||||
|
if http_state.request_in_progress {
|
||||||
|
let progress = http_state.current_size / http_state.total_size;
|
||||||
|
|
||||||
|
ui.add(ProgressBar::new(progress));
|
||||||
|
ui.label(format!("{:.1}%", (progress * 100.0).max(0.0)));
|
||||||
|
|
||||||
|
if let Some(channel) = &http_state.channel {
|
||||||
|
while let Ok(res) = channel.try_recv(){
|
||||||
|
match res {
|
||||||
|
HttpChannelChunk::Start(total) => {
|
||||||
|
http_state.total_size = total;
|
||||||
|
}
|
||||||
|
HttpChannelChunk::Data(data) => {
|
||||||
|
http_state.current_size += data.len() as f32;
|
||||||
|
http_state.data.extend(data);
|
||||||
|
}
|
||||||
|
HttpChannelChunk::End => {
|
||||||
|
// We're done, destroy channel, write buffer, and reset fields
|
||||||
|
http_state.channel = None;
|
||||||
|
|
||||||
|
// this isn't the best behaviour (we're writing a good amount of data sync'ed) but its easy and fast so who cares
|
||||||
|
fs::write(&config.paths.runelite_path, &http_state.data).expect("Failed to write runelite jar");
|
||||||
|
|
||||||
|
// same bad behaviour as above
|
||||||
|
config.versions.runelite_hash = blake3::hash(&http_state.data).to_hex().to_string();
|
||||||
|
config.versions.runelite_version = update_to;
|
||||||
|
config.save_config();
|
||||||
|
|
||||||
|
http_state.data = vec![];
|
||||||
|
|
||||||
|
http_state.total_size = 0.0;
|
||||||
|
http_state.current_size = 0.0;
|
||||||
|
|
||||||
|
http_state.request_in_progress = false;
|
||||||
|
|
||||||
|
// TODO: verify or smth
|
||||||
|
|
||||||
|
self.stage = LandingStage::Account;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Unexpected http channel chunk");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
http_state.do_request(ehttp::Request::get(format!("https://github.com/runelite/launcher/releases/download/{}/RuneLite.jar", update_to)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LandingStage::Account => {
|
||||||
|
ui.label("loading account");
|
||||||
|
*working = true;
|
||||||
|
|
||||||
|
*account = Account::get_secrets();
|
||||||
|
|
||||||
|
if account.is_none() {
|
||||||
|
*next_page = Some(Pages::Login(Login::init(LoginType::NoAccount)));
|
||||||
|
} else {
|
||||||
|
// TODO: verification that confirms the creds are valid
|
||||||
|
*next_page = Some(Pages::Launcher(Launcher::init()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
85
src/ui/launcher.rs
Normal file
85
src/ui/launcher.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
use std::process::{Child, Command};
|
||||||
|
use egui::{Color32, Context, RichText, Ui};
|
||||||
|
use egui_flex::{item, Flex, FlexAlign, FlexDirection};
|
||||||
|
use egui_phosphor::fill;
|
||||||
|
use crate::backend::config::Config;
|
||||||
|
use crate::backend::secrets::Account;
|
||||||
|
use crate::ui::login::{Login, LoginType};
|
||||||
|
use crate::ui::Pages;
|
||||||
|
use crate::ui::settings::Settings;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Launcher {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Launcher {
|
||||||
|
pub fn init() -> Launcher {
|
||||||
|
Launcher {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self,
|
||||||
|
next_page: &mut Option<Pages>,
|
||||||
|
account: &mut Option<Account>,
|
||||||
|
config: &mut Config,
|
||||||
|
child: &mut Option<Child>,
|
||||||
|
game_open: &mut bool,
|
||||||
|
_ctx: &Context,
|
||||||
|
ui: &mut Ui
|
||||||
|
) {
|
||||||
|
let checked_account = if let Some(account) = account {
|
||||||
|
account.clone()
|
||||||
|
} else {
|
||||||
|
*next_page = Some(Pages::Login(Login::init(LoginType::NoAccount)));
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
Flex::new().w_full().h_full().direction(FlexDirection::Vertical).show(ui, |flex| {
|
||||||
|
flex.add_flex(item(), Flex::horizontal().w_full(), |flex| {
|
||||||
|
flex.add_ui(item(), |ui| {
|
||||||
|
ui.heading(format!("Hello, {}!", checked_account.character_name));
|
||||||
|
});
|
||||||
|
|
||||||
|
flex.grow();
|
||||||
|
|
||||||
|
flex.add_ui(item(), |ui| {
|
||||||
|
if ui.button(RichText::new(fill::GEAR).size(20.0)).clicked() {
|
||||||
|
*next_page = Some(Pages::Settings(Settings::init()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button(RichText::new(fill::SIGN_OUT).color(Color32::RED).size(20.0)).clicked() {
|
||||||
|
checked_account.delete_secrets();
|
||||||
|
|
||||||
|
*account = None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
flex.grow();
|
||||||
|
|
||||||
|
flex.add_ui(item().align_self(FlexAlign::Center), |ui| {
|
||||||
|
ui.add(egui::Image::new(egui::include_image!("../../assets/Steam_client_logo.png")).max_height(250f32));
|
||||||
|
});
|
||||||
|
|
||||||
|
flex.add_ui(item().align_self(FlexAlign::Center), |ui| {
|
||||||
|
if ui.button(RichText::new("Play").size(30.0)).clicked() {
|
||||||
|
*child = Some(Command::new(config.paths.java_path.clone())
|
||||||
|
.arg("-jar")
|
||||||
|
.arg(config.paths.runelite_path.clone())
|
||||||
|
.arg("--launch-mode")
|
||||||
|
.arg("REFLECT")
|
||||||
|
.env("JX_SESSION_ID", checked_account.session_id.clone())
|
||||||
|
.env("JX_CHARACTER_ID", checked_account.character_id.clone())
|
||||||
|
.env("JX_DISPLAY_NAME", checked_account.character_name.clone())
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to start RuneLite!"));
|
||||||
|
|
||||||
|
*game_open = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
flex.grow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
114
src/ui/login.rs
Normal file
114
src/ui/login.rs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
use std::cmp::PartialEq;
|
||||||
|
use egui::{Context, Ui};
|
||||||
|
use crate::backend::secrets::Account;
|
||||||
|
use crate::ui::launcher::Launcher;
|
||||||
|
use crate::ui::Pages;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Login {
|
||||||
|
login_type: LoginType,
|
||||||
|
login_error_type: Option<LoginErrorType>,
|
||||||
|
session_id: String,
|
||||||
|
character_name: String,
|
||||||
|
character_id: String,
|
||||||
|
save_credentials: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum LoginType {
|
||||||
|
NoAccount,
|
||||||
|
ExpiredCredentials,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum LoginErrorType {
|
||||||
|
LoggingIn,
|
||||||
|
MissingFields,
|
||||||
|
FailedToSave
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Login {
|
||||||
|
pub fn init(login_type: LoginType) -> Login {
|
||||||
|
Login { login_type, login_error_type: None, session_id: "".to_string(), character_name: "".to_string(), character_id: "".to_string(), save_credentials: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self,
|
||||||
|
next_page: &mut Option<Pages>,
|
||||||
|
account: &mut Option<Account>,
|
||||||
|
working: &mut bool,
|
||||||
|
_ctx: &Context,
|
||||||
|
ui: &mut Ui
|
||||||
|
) {
|
||||||
|
ui.heading("Login");
|
||||||
|
|
||||||
|
match self.login_type {
|
||||||
|
LoginType::NoAccount => {
|
||||||
|
ui.label("No account");
|
||||||
|
}
|
||||||
|
LoginType::ExpiredCredentials => {
|
||||||
|
ui.label("Expired credentials");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Character Name: ");
|
||||||
|
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut self.character_name));
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Character ID: ");
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut self.character_id).password(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Session ID: ");
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut self.session_id).password(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Login").clicked() {
|
||||||
|
self.login_error_type = None;
|
||||||
|
|
||||||
|
if self.character_name.is_empty() | self.character_id.is_empty() | self.session_id.is_empty() {
|
||||||
|
self.login_error_type = Some(LoginErrorType::MissingFields);
|
||||||
|
} else {
|
||||||
|
self.login_error_type = Some(LoginErrorType::LoggingIn);
|
||||||
|
|
||||||
|
let new_account = Account {
|
||||||
|
session_id: self.session_id.clone(),
|
||||||
|
character_id: self.character_id.clone(),
|
||||||
|
character_name: self.character_name.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
*account = Some(new_account.clone());
|
||||||
|
|
||||||
|
if self.save_credentials && !new_account.insert_secrets() {
|
||||||
|
self.login_error_type = Some(LoginErrorType::FailedToSave);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.login_error_type.clone().is_some_and(|x| x.eq(&LoginErrorType::LoggingIn)) {
|
||||||
|
*next_page = Some(Pages::Launcher(Launcher::init()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(error_type) = self.login_error_type.clone() {
|
||||||
|
match error_type {
|
||||||
|
LoginErrorType::MissingFields => {
|
||||||
|
ui.label("Missing fields");
|
||||||
|
}
|
||||||
|
LoginErrorType::LoggingIn => {
|
||||||
|
ui.label("Logging in...");
|
||||||
|
*working = true;
|
||||||
|
}
|
||||||
|
LoginErrorType::FailedToSave => {
|
||||||
|
ui.label("Failed to save credentials.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.checkbox(&mut self.save_credentials, "Save credentials?");
|
||||||
|
}
|
||||||
|
}
|
18
src/ui/mod.rs
Normal file
18
src/ui/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use crate::ui::landing::Landing;
|
||||||
|
use crate::ui::launcher::Launcher;
|
||||||
|
use crate::ui::login::Login;
|
||||||
|
use crate::ui::settings::Settings;
|
||||||
|
|
||||||
|
pub mod landing;
|
||||||
|
pub mod game_open;
|
||||||
|
pub mod login;
|
||||||
|
pub mod launcher;
|
||||||
|
pub mod settings;
|
||||||
|
|
||||||
|
pub enum Pages {
|
||||||
|
Landing(Option<Landing>),
|
||||||
|
Login(Login),
|
||||||
|
Settings(Settings),
|
||||||
|
Launcher(Launcher),
|
||||||
|
GameOpen,
|
||||||
|
}
|
55
src/ui/settings.rs
Normal file
55
src/ui/settings.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use egui::{Context, Ui};
|
||||||
|
use egui_phosphor::fill;
|
||||||
|
use rfd::FileDialog;
|
||||||
|
use crate::backend::config::Config;
|
||||||
|
use crate::ui::launcher::Launcher;
|
||||||
|
use crate::ui::Pages;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Settings {
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub fn init() -> Settings {
|
||||||
|
Settings {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self,
|
||||||
|
next_page: &mut Option<Pages>,
|
||||||
|
config: &mut Config,
|
||||||
|
_ctx: &Context,
|
||||||
|
ui: &mut Ui
|
||||||
|
) {
|
||||||
|
ui.heading("Settings");
|
||||||
|
|
||||||
|
if ui.button(format!("{} Open Data Folder", fill::FOLDER)).clicked() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("RuneLite path: ");
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut config.paths.runelite_path));
|
||||||
|
|
||||||
|
if ui.button(format!("{} Select", fill::FOLDER)).clicked() {
|
||||||
|
// TODO: switch this to use a channel and another thread
|
||||||
|
config.paths.runelite_path = FileDialog::new().pick_file().unwrap_or_default().into_os_string().into_string().unwrap_or_default();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Java path: ");
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut config.paths.java_path));
|
||||||
|
|
||||||
|
if ui.button(format!("{} Select", fill::FOLDER)).clicked() {
|
||||||
|
// TODO: switch this to use a channel and another thread
|
||||||
|
config.paths.java_path = FileDialog::new().pick_file().unwrap_or_default().into_os_string().into_string().unwrap_or_default();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ui.button("Save and Go Back").clicked() {
|
||||||
|
config.save_config();
|
||||||
|
|
||||||
|
*next_page = Some(Pages::Launcher(Launcher::init()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue