Compare commits

...

13 Commits

Author SHA1 Message Date
e43d38e1ce Error debugging 2026-02-17 15:50:43 -07:00
b94326a671 Move away from .unwrap() 2026-02-17 15:14:34 -07:00
5e94a8ac5f Switch to Vite for this, since it's one page and simple 2026-02-17 14:50:32 -07:00
4945b0513f Make it be 13.0 minimum version instead of 13.7.8 on macOS 2026-02-14 02:28:37 -07:00
78885c8cd0 1.0.4 + Update dependencies + Remove rightclick + Fix auto updating 2026-01-28 16:19:16 -07:00
c32a8c2cdd Remove appimage build 2026-01-21 12:26:04 -07:00
a90cd468e9 1.0.3 2026-01-07 19:56:13 -07:00
43c0159ad8 Fix memory not being free'd immediately for when a update downloads 2026-01-07 16:48:16 -07:00
e46c6e50dd Use a new folder layout for updates and improve a lot of code (read desc)
Switched from a layout like this:

downloads/
  1.4.0.zip (temp)
updates/
  1.4.0/
    lncvrt-games-launcher

to

.version
bin/
  lncvrt-games-launcher
2026-01-07 16:42:16 -07:00
15978c8d6b Make styles consistent with launcher 2026-01-07 16:40:01 -07:00
c21289ed2b Uopdate dependencies 2026-01-07 16:38:14 -07:00
0ad6d26c82 1.0.2 2025-12-20 17:05:45 -07:00
2d0cdd8074 Fix 2025-11-03 20:15:43 -07:00
26 changed files with 596 additions and 896 deletions

58
.gitignore vendored
View File

@@ -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?

780
bun.lock

File diff suppressed because it is too large Load Diff

23
eslint.config.js Normal file
View 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,
},
},
])

View File

@@ -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
View 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>

View File

@@ -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

View File

@@ -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"
} }
} }

View File

@@ -1,7 +0,0 @@
const config = {
plugins: {
'@tailwindcss/postcss': {}
}
}
export default config

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -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"

View File

@@ -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" }]
}
] ]
} }

View File

@@ -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");
} }

View File

@@ -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,

View File

@@ -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,

View File

@@ -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"]
} }

View File

@@ -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,

View File

@@ -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;
} }

View File

@@ -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>
</> </>

View File

@@ -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>
)
}

View File

@@ -1,6 +0,0 @@
export interface LauncherUpdate {
id: string
releaseDate: number
downloadUrl: string
sha512sum: string
}

9
src/main.tsx Normal file
View 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
View 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"]
}

View File

@@ -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
View 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
View 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(),
],
})