Compare commits
13 Commits
893715af4c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
e43d38e1ce
|
|||
|
b94326a671
|
|||
|
5e94a8ac5f
|
|||
|
4945b0513f
|
|||
|
78885c8cd0
|
|||
|
c32a8c2cdd
|
|||
|
a90cd468e9
|
|||
|
43c0159ad8
|
|||
|
e46c6e50dd
|
|||
|
15978c8d6b
|
|||
|
c21289ed2b
|
|||
|
0ad6d26c82
|
|||
|
2d0cdd8074
|
58
.gitignore
vendored
58
.gitignore
vendored
@@ -1,44 +1,24 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# Logs
|
||||||
|
logs
|
||||||
# dependencies
|
*.log
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.*
|
|
||||||
.yarn/*
|
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/versions
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
.pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
node_modules
|
||||||
.env*
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
# vercel
|
# Editor directories and files
|
||||||
.vercel
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
# typescript
|
.idea
|
||||||
*.tsbuildinfo
|
.DS_Store
|
||||||
next-env.d.ts
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
# vscode project settings
|
*.njsproj
|
||||||
.vscode/settings.json
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|||||||
23
eslint.config.js
Normal file
23
eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
reactHooks.configs.flat.recommended,
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2024,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
||||||
import nextVitals from 'eslint-config-next/core-web-vitals'
|
|
||||||
import nextTs from 'eslint-config-next/typescript'
|
|
||||||
|
|
||||||
const eslintConfig = defineConfig([
|
|
||||||
...nextVitals,
|
|
||||||
...nextTs,
|
|
||||||
// Override default ignores of eslint-config-next.
|
|
||||||
globalIgnores([
|
|
||||||
// Default ignores of eslint-config-next:
|
|
||||||
'.next/**',
|
|
||||||
'out/**',
|
|
||||||
'build/**',
|
|
||||||
'next-env.d.ts'
|
|
||||||
])
|
|
||||||
])
|
|
||||||
|
|
||||||
export default eslintConfig
|
|
||||||
11
index.html
Normal file
11
index.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import type { NextConfig } from 'next'
|
|
||||||
|
|
||||||
const isProd = process.env.NODE_ENV === 'production'
|
|
||||||
const internalHost = process.env.TAURI_DEV_HOST || 'localhost'
|
|
||||||
const nextConfig: NextConfig = {
|
|
||||||
output: 'export',
|
|
||||||
images: {
|
|
||||||
unoptimized: true
|
|
||||||
},
|
|
||||||
assetPrefix: isProd ? undefined : `http://${internalHost}:3000`,
|
|
||||||
devIndicators: false
|
|
||||||
}
|
|
||||||
|
|
||||||
export default nextConfig
|
|
||||||
49
package.json
49
package.json
@@ -1,36 +1,41 @@
|
|||||||
{
|
{
|
||||||
"name": "lncvrt-games-launcher-loader",
|
"name": "lncvrt-games-launcher-loader",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.1",
|
"version": "1.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "vite",
|
||||||
"build": "next build",
|
"build": "tsc -b && vite build",
|
||||||
"start": "next start",
|
"lint": "eslint .",
|
||||||
"lint": "next lint",
|
"preview": "vite preview",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "2.9.0",
|
"@tailwindcss/vite": "4.1.18",
|
||||||
"@tauri-apps/plugin-dialog": "2.4.2",
|
"@tauri-apps/api": "2.10.1",
|
||||||
"@tauri-apps/plugin-fs": "2.4.4",
|
"@tauri-apps/plugin-dialog": "2.6.0",
|
||||||
"@tauri-apps/plugin-opener": "2.5.2",
|
"@tauri-apps/plugin-fs": "2.4.5",
|
||||||
|
"@tauri-apps/plugin-opener": "2.5.3",
|
||||||
"@tauri-apps/plugin-os": "2.3.2",
|
"@tauri-apps/plugin-os": "2.3.2",
|
||||||
"axios": "1.13.1",
|
"axios": "1.13.5",
|
||||||
"next": "16.0.1",
|
"react": "19.2.4",
|
||||||
"react": "19.2.0",
|
"react-dom": "19.2.4",
|
||||||
"react-dom": "19.2.0"
|
"tailwindcss": "4.1.18"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "2.9.2",
|
"@eslint/js": "10.0.1",
|
||||||
|
"@tauri-apps/cli": "2.10.0",
|
||||||
|
"@types/node": "25.2.3",
|
||||||
|
"@types/react": "19.2.14",
|
||||||
|
"@types/react-dom": "19.2.3",
|
||||||
|
"@vitejs/plugin-react": "5.1.4",
|
||||||
|
"babel-plugin-react-compiler": "1.0.0",
|
||||||
|
"eslint": "10.0.0",
|
||||||
|
"eslint-plugin-react-hooks": "7.0.1",
|
||||||
|
"eslint-plugin-react-refresh": "0.5.0",
|
||||||
|
"globals": "17.3.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"@types/node": "24.10.0",
|
"typescript-eslint": "8.56.0",
|
||||||
"@types/react": "19.2.2",
|
"vite": "7.3.1"
|
||||||
"@types/react-dom": "19.2.2",
|
|
||||||
"@tailwindcss/postcss": "4.1.16",
|
|
||||||
"tailwindcss": "4.1.16",
|
|
||||||
"eslint": "9.39.1",
|
|
||||||
"eslint-config-next": "16.0.1",
|
|
||||||
"@eslint/eslintrc": "3.3.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
const config = {
|
|
||||||
plugins: {
|
|
||||||
'@tailwindcss/postcss': {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lncvrt-games-launcher-loader"
|
name = "lncvrt-games-launcher-loader"
|
||||||
version = "1.0.1"
|
version = "1.0.4"
|
||||||
authors = ["Lncvrt"]
|
authors = ["Lncvrt"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
@@ -9,21 +9,21 @@ name = "lncvrt_games_launcher_loader_lib"
|
|||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.5.1", features = [] }
|
tauri-build = { version = "2.5.5", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2.9.2", features = [] }
|
tauri = { version = "2.10.2", features = [] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.149"
|
||||||
tauri-plugin-fs = "2.4.4"
|
tauri-plugin-fs = "2.4.5"
|
||||||
tokio = "1.48.0"
|
tokio = "1.49.0"
|
||||||
zip = "6.0.0"
|
zip = "8.1.0"
|
||||||
tauri-plugin-os = "2.3.2"
|
tauri-plugin-os = "2.3.2"
|
||||||
reqwest = { version = "0.12.24", default-features = false, features = ["stream", "rustls-tls"] }
|
reqwest = { version = "0.13.2", default-features = false, features = ["stream", "rustls"] }
|
||||||
tauri-plugin-opener = "2.5.2"
|
tauri-plugin-opener = "2.5.3"
|
||||||
tauri-plugin-dialog = "2.4.2"
|
tauri-plugin-dialog = "2.6.0"
|
||||||
sha2 = "0.10.9"
|
sha2 = "0.10.9"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-single-instance = "2.3.6"
|
tauri-plugin-single-instance = "2.4.0"
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,14 @@
|
|||||||
"core:default",
|
"core:default",
|
||||||
"core:window:allow-start-dragging",
|
"core:window:allow-start-dragging",
|
||||||
"fs:default",
|
"fs:default",
|
||||||
"fs:allow-applocaldata-read",
|
|
||||||
"fs:allow-applocaldata-write",
|
|
||||||
"fs:allow-exists",
|
|
||||||
"os:default",
|
"os:default",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
"dialog:default"
|
"dialog:default",
|
||||||
|
"fs:allow-write-text-file",
|
||||||
|
"fs:allow-create",
|
||||||
|
{
|
||||||
|
"identifier": "fs:scope",
|
||||||
|
"allow": [{ "path": "$APPLOCALDATA/.version" }]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,38 +1,69 @@
|
|||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
|
use std::fs::{remove_dir_all};
|
||||||
|
use std::io::Cursor;
|
||||||
use std::{
|
use std::{
|
||||||
fs::{File, create_dir_all},
|
fs::{File, create_dir_all},
|
||||||
io::{BufReader, copy},
|
io::{copy},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::Command,
|
process::Command
|
||||||
};
|
};
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::{AppHandle, Manager};
|
||||||
use tauri_plugin_os::platform;
|
use tauri_plugin_os::platform;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(unix)]
|
||||||
use std::{fs, os::unix::fs::PermissionsExt};
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
async fn unzip_to_dir(zip_path: PathBuf, out_dir: PathBuf) -> String {
|
fn should_skip(name: &str) -> bool {
|
||||||
let res = tauri::async_runtime::spawn_blocking(move || {
|
name.starts_with("__MACOSX/")
|
||||||
let file = File::open(zip_path)?;
|
|| name == "__MACOSX"
|
||||||
let mut archive = ZipArchive::new(BufReader::new(file))?;
|
|| name.ends_with("/.DS_Store")
|
||||||
|
|| name.ends_with(".DS_Store")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn unzip_to_dir(bytes: &[u8], target: &PathBuf) -> String {
|
||||||
|
let bytes = bytes.to_vec();
|
||||||
|
let target = target.clone();
|
||||||
|
|
||||||
|
let res = tauri::async_runtime::spawn_blocking(move || -> Result<(), String> {
|
||||||
|
let reader = Cursor::new(bytes);
|
||||||
|
let mut archive = ZipArchive::new(reader).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
for i in 0..archive.len() {
|
for i in 0..archive.len() {
|
||||||
let mut file = archive.by_index(i)?;
|
let mut entry = archive.by_index(i).map_err(|e| e.to_string())?;
|
||||||
let outpath = out_dir.join(file.name());
|
let name = entry.name();
|
||||||
|
|
||||||
if file.is_dir() {
|
if should_skip(name) {
|
||||||
create_dir_all(&outpath)?;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let outpath = target.join(name);
|
||||||
|
|
||||||
|
if entry.is_dir() {
|
||||||
|
create_dir_all(&outpath).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
if let Some(mode) = entry.unix_mode() {
|
||||||
|
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(parent) = outpath.parent() {
|
if let Some(parent) = outpath.parent() {
|
||||||
create_dir_all(parent)?;
|
create_dir_all(parent).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut outfile = File::create(&outpath).map_err(|e| e.to_string())?;
|
||||||
|
copy(&mut entry, &mut outfile).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
if let Some(mode) = entry.unix_mode() {
|
||||||
|
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
let mut outfile = File::create(&outpath)?;
|
|
||||||
copy(&mut file, &mut outfile)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<(), zip::result::ZipError>(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -50,20 +81,7 @@ fn get_sha512_hash(data: &[u8]) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn check_latest_ver(app: AppHandle, version: String) -> String {
|
async fn download(app: AppHandle, url: String, hash: String) -> String {
|
||||||
let updates_path = app.path().app_local_data_dir().unwrap().join("updates");
|
|
||||||
if updates_path.exists()
|
|
||||||
&& updates_path.is_dir()
|
|
||||||
&& updates_path.join(&version).exists()
|
|
||||||
&& updates_path.join(&version).is_dir()
|
|
||||||
{
|
|
||||||
return "1".to_string();
|
|
||||||
}
|
|
||||||
return "-1".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn download(app: AppHandle, url: String, name: String, hash: String) -> String {
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let resp = match client.get(&url).send().await {
|
let resp = match client.get(&url).send().await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
@@ -79,108 +97,84 @@ async fn download(app: AppHandle, url: String, name: String, hash: String) -> St
|
|||||||
return "-2".to_string();
|
return "-2".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let downloads_path = app.path().app_local_data_dir().unwrap().join("downloads");
|
let bin_path = match app.path().app_local_data_dir() {
|
||||||
let updates_path = app.path().app_local_data_dir().unwrap().join("updates");
|
Ok(p) => p.join("bin"),
|
||||||
|
Err(_) => return "-1".to_string(),
|
||||||
let download_part_path = downloads_path.join(format!("{}.part", name));
|
};
|
||||||
let download_zip_path = downloads_path.join(format!("{}.zip", name));
|
let _ = tokio::fs::create_dir_all(&bin_path).await;
|
||||||
|
let unzip_res = unzip_to_dir(&bytes, &bin_path).await;
|
||||||
let _ = tokio::fs::create_dir_all(&downloads_path).await;
|
|
||||||
if let Ok(true) = tokio::fs::try_exists(&updates_path.join(name.clone())).await {
|
|
||||||
let _ = tokio::fs::remove_dir_all(&updates_path.join(name.clone())).await;
|
|
||||||
}
|
|
||||||
if updates_path.exists() {
|
|
||||||
if let Ok(mut entries) = tokio::fs::read_dir(&updates_path).await {
|
|
||||||
while let Ok(Some(entry)) = entries.next_entry().await {
|
|
||||||
let _ = tokio::fs::remove_dir_all(entry.path()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = tokio::fs::create_dir_all(updates_path.join(&name)).await;
|
|
||||||
}
|
|
||||||
if download_part_path.exists() {
|
|
||||||
let _ = tokio::fs::remove_file(&download_part_path).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokio::fs::write(&download_part_path, bytes).await.is_err() {
|
|
||||||
return "-1".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokio::fs::rename(&download_part_path, &download_zip_path)
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return "-1".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
let unzip_res = unzip_to_dir(download_zip_path.clone(), updates_path.join(&name)).await;
|
|
||||||
tokio::fs::remove_file(download_zip_path.clone())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
if unzip_res == "-1" {
|
if unzip_res == "-1" {
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
drop(bytes);
|
||||||
{
|
|
||||||
let executable_path = updates_path.join(&name).join("lncvrt-games-launcher");
|
|
||||||
let mut perms = fs::metadata(&executable_path).unwrap().permissions();
|
|
||||||
perms.set_mode(0o755);
|
|
||||||
fs::set_permissions(executable_path, perms).unwrap();
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
use std::fs;
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
|
|
||||||
let macos_app_path = updates_path
|
|
||||||
.join(&name)
|
|
||||||
.join("Lncvrt Games Launcher.app")
|
|
||||||
.join("Contents")
|
|
||||||
.join("MacOS")
|
|
||||||
.join("lncvrt-games-launcher");
|
|
||||||
|
|
||||||
let mut perms = fs::metadata(&macos_app_path).unwrap().permissions();
|
|
||||||
perms.set_mode(0o755);
|
|
||||||
fs::set_permissions(&macos_app_path, perms).unwrap();
|
|
||||||
}
|
|
||||||
return "1".to_string();
|
return "1".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn load(app: AppHandle, name: String) {
|
fn load(app: AppHandle) {
|
||||||
let update_path = app
|
let bin_path = match app.path().app_local_data_dir() {
|
||||||
.path()
|
Ok(p) => p.join("bin"),
|
||||||
.app_local_data_dir()
|
Err(_) => return,
|
||||||
.unwrap()
|
};
|
||||||
.join("updates")
|
if !bin_path.exists() {
|
||||||
.join(&name);
|
|
||||||
if !update_path.exists() {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if platform() == "macos" {
|
if platform() == "macos" {
|
||||||
Command::new("open")
|
if let Err(_) = Command::new("open")
|
||||||
.arg("Lncvrt Games Launcher.app")
|
.arg("Lncvrt Games Launcher.app")
|
||||||
.current_dir(&update_path)
|
.current_dir(&bin_path)
|
||||||
.spawn()
|
.spawn() {
|
||||||
.unwrap();
|
eprintln!("Failed to launch game on macOS");
|
||||||
|
}
|
||||||
} else if platform() == "linux" {
|
} else if platform() == "linux" {
|
||||||
Command::new("./lncvrt-games-launcher")
|
if let Err(_) = Command::new("./lncvrt-games-launcher")
|
||||||
.current_dir(&update_path)
|
.current_dir(&bin_path)
|
||||||
.spawn()
|
.spawn() {
|
||||||
.unwrap();
|
eprintln!("Failed to launch game on macOS");
|
||||||
|
}
|
||||||
} else if platform() == "windows" {
|
} else if platform() == "windows" {
|
||||||
Command::new(&update_path.join("lncvrt-games-launcher.exe"))
|
if let Err(_) = Command::new(&bin_path.join("lncvrt-games-launcher.exe"))
|
||||||
.current_dir(&update_path)
|
.current_dir(&bin_path)
|
||||||
.spawn()
|
.spawn() {
|
||||||
.unwrap();
|
eprintln!("Failed to launch game on macOS");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.setup(|app| {
|
||||||
|
let app_local_data_dir = match app.path().app_local_data_dir() {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(_) => return Ok(()),
|
||||||
|
};
|
||||||
|
let downloads_dir = app_local_data_dir.join("downloads");
|
||||||
|
let updates_dir = app_local_data_dir.join("updates");
|
||||||
|
let bin_dir = app_local_data_dir.join("bin");
|
||||||
|
let version_file = app_local_data_dir.join(".version");
|
||||||
|
|
||||||
|
if downloads_dir.exists() {
|
||||||
|
let _ = remove_dir_all(downloads_dir);
|
||||||
|
}
|
||||||
|
if updates_dir.exists() {
|
||||||
|
let _ = remove_dir_all(&updates_dir);
|
||||||
|
}
|
||||||
|
if bin_dir.exists() && bin_dir.is_file() {
|
||||||
|
let _ = remove_dir_all(&bin_dir);
|
||||||
|
}
|
||||||
|
if version_file.exists() && !bin_dir.is_file() {
|
||||||
|
let _ = remove_dir_all(&version_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
||||||
let _ = app
|
let _ = app
|
||||||
.get_webview_window("main")
|
.get_webview_window("main")
|
||||||
@@ -191,7 +185,7 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.invoke_handler(tauri::generate_handler![check_latest_ver, download, load])
|
.invoke_handler(tauri::generate_handler![download, load])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Lncvrt Games Launcher",
|
"productName": "Lncvrt Games Launcher",
|
||||||
"version": "1.0.1",
|
"version": "1.0.4",
|
||||||
"identifier": "xyz.lncvrt.lncvrt-games-launcher-loader",
|
"identifier": "xyz.lncvrt.lncvrt-games-launcher-loader",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
"devUrl": "http://localhost:3000",
|
"devUrl": "http://localhost:5173",
|
||||||
"beforeBuildCommand": "next build",
|
"beforeBuildCommand": "bun run build",
|
||||||
"frontendDist": "../out"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Lncvrt Games Launcher",
|
"productName": "Lncvrt Games Launcher",
|
||||||
"version": "1.0.1",
|
"version": "1.0.4",
|
||||||
"identifier": "xyz.lncvrt.lncvrt-games-launcher-loader",
|
"identifier": "xyz.lncvrt.lncvrt-games-launcher-loader",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
"devUrl": "http://localhost:3000",
|
"devUrl": "http://localhost:5173",
|
||||||
"beforeBuildCommand": "next build",
|
"beforeBuildCommand": "bun run build",
|
||||||
"frontendDist": "../out"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Lncvrt Games Launcher",
|
"productName": "Lncvrt Games Launcher",
|
||||||
"version": "1.0.1",
|
"version": "1.0.4",
|
||||||
"identifier": "xyz.lncvrt.lncvrt-games-launcher-loader",
|
"identifier": "xyz.lncvrt.lncvrt-games-launcher-loader",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
"devUrl": "http://localhost:3000",
|
"devUrl": "http://localhost:5173",
|
||||||
"beforeBuildCommand": "next build",
|
"beforeBuildCommand": "bun run build",
|
||||||
"frontendDist": "../out"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"targets": ["dmg"],
|
"targets": ["dmg"],
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"minimumSystemVersion": "13.7.8"
|
"minimumSystemVersion": "13.0"
|
||||||
},
|
},
|
||||||
"icon": ["icons/icon.icns"]
|
"icon": ["icons/icon.icns"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Lncvrt Games Launcher",
|
"productName": "Lncvrt Games Launcher",
|
||||||
"version": "1.0.1",
|
"version": "1.0.4",
|
||||||
"identifier": "xyz.lncvrt.lncvrt-games-launcher-loader",
|
"identifier": "xyz.lncvrt.lncvrt-games-launcher-loader",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
"devUrl": "http://localhost:3000",
|
"devUrl": "http://localhost:5173",
|
||||||
"beforeBuildCommand": "next build",
|
"beforeBuildCommand": "bun run build",
|
||||||
"frontendDist": "../out"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@apply bg-[rgb(48,48,48)] hover:bg-[rgb(56,56,56)] border border-[rgb(72,72,72)] hover:border-[rgb(80,80,80)] transition-colors px-2 py-1 rounded-md cursor-pointer;
|
@apply bg-[rgb(48,48,48)] hover:bg-[rgb(72,72,72)] border border-[rgb(72,72,72)] hover:border-[rgb(96,96,96)] transition-colors px-2 py-1 rounded-md cursor-pointer;
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,31 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import Image from 'next/image'
|
|
||||||
import Icon from './assets/Icon.png'
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { app } from '@tauri-apps/api'
|
import { app } from '@tauri-apps/api'
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import { LauncherUpdate } from './types/LauncherUpdate'
|
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
|
import { arch, platform } from '@tauri-apps/plugin-os'
|
||||||
|
import {
|
||||||
|
BaseDirectory,
|
||||||
|
create,
|
||||||
|
exists,
|
||||||
|
readTextFile,
|
||||||
|
writeTextFile
|
||||||
|
} from '@tauri-apps/plugin-fs'
|
||||||
|
import './App.css'
|
||||||
|
import { message } from '@tauri-apps/plugin-dialog'
|
||||||
|
|
||||||
export default function Home () {
|
interface LauncherUpdate {
|
||||||
|
id: string
|
||||||
|
releaseDate: number
|
||||||
|
downloadUrl: string
|
||||||
|
sha512sum: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App () {
|
||||||
const [state, setState] = useState<string>('Loading...')
|
const [state, setState] = useState<string>('Loading...')
|
||||||
|
const [error, setError] = useState<string>('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
@@ -25,10 +40,11 @@ export default function Home () {
|
|||||||
'https://games.lncvrt.xyz/api/launcher/loader/latest'
|
'https://games.lncvrt.xyz/api/launcher/loader/latest'
|
||||||
)
|
)
|
||||||
launcherLatestRequest = await axios.get(
|
launcherLatestRequest = await axios.get(
|
||||||
'https://games.lncvrt.xyz/api/launcher/latest'
|
'https://gamdes.lncvrt.xyz/api/launcher/latest'
|
||||||
)
|
)
|
||||||
} catch {
|
} catch (e: unknown) {
|
||||||
setState('Failed. Check internet connection.')
|
setState('Failed. Check internet connection.')
|
||||||
|
setError('e0001 / ' + String(e))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +53,7 @@ export default function Home () {
|
|||||||
launcherLatestRequest.status !== 200
|
launcherLatestRequest.status !== 200
|
||||||
) {
|
) {
|
||||||
setState('Failed. Try again later.')
|
setState('Failed. Try again later.')
|
||||||
|
setError('e0002')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,51 +63,64 @@ export default function Home () {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLatest = await invoke('check_latest_ver', {
|
const options = {
|
||||||
version: launcherLatestRequest.data
|
baseDir: BaseDirectory.AppLocalData
|
||||||
})
|
}
|
||||||
if (isLatest == '1') {
|
|
||||||
setState('Starting...')
|
let latest = false
|
||||||
} else {
|
if (await exists('.version', options))
|
||||||
|
latest =
|
||||||
|
(await readTextFile('.version', options)) ==
|
||||||
|
launcherLatestRequest.data
|
||||||
|
|
||||||
|
if (!latest) {
|
||||||
setState('Downloading new update...')
|
setState('Downloading new update...')
|
||||||
try {
|
try {
|
||||||
const launcherUpdateRequest = await axios.get(
|
const launcherUpdateRequest = await axios.get(
|
||||||
'https://games.lncvrt.xyz/api/launcher/loader/update-data'
|
`https://games.lncvrt.xyz/api/launcher/loader/update-data?platform=${platform()}&arch=${arch()}`
|
||||||
)
|
)
|
||||||
launcherUpdateData = launcherUpdateRequest.data
|
launcherUpdateData = launcherUpdateRequest.data
|
||||||
} catch {
|
} catch (e: unknown) {
|
||||||
setState('Failed. Check internet connection.')
|
setState('Failed. Check internet connection.')
|
||||||
|
setError('e0003 / ' + String(e))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!launcherUpdateData) {
|
||||||
|
setState('Failed. Check internet connection.')
|
||||||
|
setError('e0004')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!launcherUpdateData) return
|
|
||||||
const downloadResult = await invoke('download', {
|
const downloadResult = await invoke('download', {
|
||||||
url: launcherUpdateData.downloadUrl,
|
url: launcherUpdateData.downloadUrl,
|
||||||
name: launcherLatestRequest.data,
|
|
||||||
hash: launcherUpdateData.sha512sum
|
hash: launcherUpdateData.sha512sum
|
||||||
})
|
})
|
||||||
if (downloadResult == '-1') {
|
if (downloadResult == '-1') {
|
||||||
setState('Failed. Check internet connection.')
|
setState('Failed. Check internet connection.')
|
||||||
|
setError('e0005 / ' + downloadResult)
|
||||||
return
|
return
|
||||||
} else if (downloadResult == '-2') {
|
} else if (downloadResult == '-2') {
|
||||||
setState('File integrity check failed.')
|
setState('File integrity check failed.')
|
||||||
return
|
return
|
||||||
|
} else if (downloadResult == '-3') {
|
||||||
|
setState('Failed to unzip update.')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
setState('Starting...')
|
if (await exists('.version', options)) await create('.version', options)
|
||||||
|
await writeTextFile('.version', launcherLatestRequest.data, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke('load', {
|
setState('Starting...')
|
||||||
name: launcherLatestRequest.data
|
invoke('load')
|
||||||
})
|
|
||||||
})()
|
})()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='absolute left-1/2 top-[20%] -translate-x-1/2 flex flex-col items-center'>
|
<div className='absolute left-1/2 top-[20%] -translate-x-1/2 flex flex-col items-center'>
|
||||||
<Image src={Icon} width={128} height={128} alt='' draggable={false} />
|
<img src='/Icon.png' width={128} height={128} alt='' draggable={false} />
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
state !== 'Loader update required' ? 'mt-10' : 'mt-4'
|
state == 'Loader update required' || error != '' ? 'mt-4' : 'mt-10'
|
||||||
} text-center`}
|
} text-center`}
|
||||||
>
|
>
|
||||||
<p className='whitespace-nowrap'>{state}</p>
|
<p className='whitespace-nowrap'>{state}</p>
|
||||||
@@ -103,6 +133,13 @@ export default function Home () {
|
|||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
hidden={error == ''}
|
||||||
|
className='mt-4'
|
||||||
|
onClick={async () => await message(error, { title: 'Error', kind: 'error' })}
|
||||||
|
>
|
||||||
|
Show error
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { Lexend } from 'next/font/google'
|
|
||||||
import './globals.css'
|
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
const lexend = Lexend({
|
|
||||||
subsets: ['latin']
|
|
||||||
})
|
|
||||||
|
|
||||||
export default function RootLayout ({
|
|
||||||
children
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode
|
|
||||||
}>) {
|
|
||||||
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
||||||
if ((e.target as HTMLElement).closest('button')) return
|
|
||||||
getCurrentWindow().startDragging()
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.body.addEventListener('mousedown', handleMouseDown as any)
|
|
||||||
return () =>
|
|
||||||
document.body.removeEventListener('mousedown', handleMouseDown as any)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<html lang='en'>
|
|
||||||
<body className={lexend.className}>
|
|
||||||
<div className='w-max h-screen'>{children}</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export interface LauncherUpdate {
|
|
||||||
id: string
|
|
||||||
releaseDate: number
|
|
||||||
downloadUrl: string
|
|
||||||
sha512sum: string
|
|
||||||
}
|
|
||||||
9
src/main.tsx
Normal file
9
src/main.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import App from './App.tsx'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
28
tsconfig.app.json
Normal file
28
tsconfig.app.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -1,34 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"files": [],
|
||||||
"target": "ES2017",
|
"references": [
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
{ "path": "./tsconfig.app.json" },
|
||||||
"allowJs": true,
|
{ "path": "./tsconfig.node.json" }
|
||||||
"skipLibCheck": true,
|
]
|
||||||
"strict": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"incremental": true,
|
|
||||||
"plugins": [
|
|
||||||
{
|
|
||||||
"name": "next"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"next-env.d.ts",
|
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx",
|
|
||||||
".next/types/**/*.ts",
|
|
||||||
".next/dev/types/**/*.ts",
|
|
||||||
"**/*.mts"
|
|
||||||
],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
}
|
||||||
26
tsconfig.node.json
Normal file
26
tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
15
vite.config.ts
Normal file
15
vite.config.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
react({
|
||||||
|
babel: {
|
||||||
|
plugins: [['babel-plugin-react-compiler']],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
tailwindcss(),
|
||||||
|
],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user