diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 5d7e6da..a8e5f1f 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -17,7 +17,7 @@ tauri-plugin-opener = "2.4.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.141"
tauri-plugin-os = "2.3.0"
-reqwest = { version = "0.12.22", features = ["stream"] }
+reqwest = { version = "0.12.22", default-features = false, features = ["stream", "rustls-tls"] }
tokio = "1.46.1"
futures-util = { version = "0.3.31", features = ["io"] }
tauri-plugin-decorum = "1.1.1"
diff --git a/src/componets/Setting.css b/src/componets/Setting.css
new file mode 100644
index 0000000..943d096
--- /dev/null
+++ b/src/componets/Setting.css
@@ -0,0 +1,17 @@
+@import 'tailwindcss';
+
+.setting-checkbox-wrapper {
+ @apply relative w-5 h-5;
+}
+
+.setting-checkbox {
+ @apply appearance-none w-full h-full border-2 border-[#484848] rounded-md bg-[#242424] transition-colors duration-200 cursor-pointer;
+}
+
+.setting-checkbox:checked {
+ @apply bg-blue-500 border-blue-600;
+}
+
+.fa-check-icon {
+ @apply absolute top-1/2 left-1/2 text-white text-[11px] pointer-events-none -translate-x-2/4 -translate-y-2/4;
+}
diff --git a/src/componets/Setting.tsx b/src/componets/Setting.tsx
new file mode 100644
index 0000000..3168cc5
--- /dev/null
+++ b/src/componets/Setting.tsx
@@ -0,0 +1,21 @@
+import { SettingProps } from '../types/SettingProps'
+import './Setting.css'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faCheck } from '@fortawesome/free-solid-svg-icons'
+
+export function Setting ({ label, value, onChange, className }: SettingProps) {
+ return (
+
+
+
+ onChange(!value)}
+ />
+ {value && }
+
+
+ )
+}
diff --git a/src/enums/Keys.ts b/src/enums/Keys.ts
index 4718f11..2918760 100644
--- a/src/enums/Keys.ts
+++ b/src/enums/Keys.ts
@@ -1,5 +1,6 @@
export enum Keys {
SERVER_RECEIVE_TRANSFER_KEY = '',
SERVER_SEND_TRANSFER_KEY = '',
- CONFIG_ENCRYPTION_KEY = ''
+ CONFIG_ENCRYPTION_KEY = '',
+ VERSIONS_ENCRYPTION_KEY = ''
}
diff --git a/src/main.tsx b/src/main.tsx
index 8d2c9ec..cbf78aa 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react'
+import { useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom/client'
import Installs from './routes/Installs'
import Settings from './routes/Settings'
@@ -14,6 +14,7 @@ import { faAdd, faRemove, faX } from '@fortawesome/free-solid-svg-icons'
import '@fontsource/roboto'
import Leaderboards from './routes/Leaderboards'
import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/plugin-notification'
+import { readNormalConfig, readVersionsConfig, writeVersionsConfig } from './util/BazookaManager'
function App () {
const [hash, setHash] = useState(window.location.hash || '#installs')
@@ -23,13 +24,13 @@ function App () {
const [showPopup, setShowPopup] = useState(false)
const [popupMode, setPopupMode] = useState(null)
const [fadeOut, setFadeOut] = useState(false)
- let activeDownloads = 0
- const queue: (() => void)[] = []
+ const activeDownloads = useRef(0)
+ const queue = useRef<(() => void)[]>([])
function runNext() {
- if (activeDownloads >= 3 || queue.length === 0) return
- activeDownloads++
- const next = queue.shift()
+ if (activeDownloads.current >= 3 || queue.current.length === 0) return
+ activeDownloads.current++
+ const next = queue.current.shift()
next?.()
}
@@ -49,8 +50,17 @@ function App () {
const unlistenDone = listen('download-done', async (event) => {
const versionName = event.payload
- setDownloadProgress(prev => prev.filter(d => d.version.version !== versionName))
- activeDownloads--
+ setDownloadProgress(prev => {
+ const downloaded = prev.find(d => d.version.version === versionName)
+ if (downloaded) {
+ readVersionsConfig().then(cfg => {
+ cfg.list.push(downloaded.version)
+ writeVersionsConfig(cfg)
+ })
+ }
+ return prev.filter(d => d.version.version !== versionName)
+ })
+ activeDownloads.current--
runNext()
if (downloadProgress.length === 0) {
await notifyUser('Downloads Complete', 'All downloads have completed.')
@@ -64,7 +74,7 @@ function App () {
d.version.version === versionName ? { ...d, failed: true } : d
)
)
- activeDownloads--
+ activeDownloads.current--
runNext()
await notifyUser('Download Failed', `The download for version ${versionName} has failed.`)
})
@@ -76,11 +86,33 @@ function App () {
}
}, [])
- function downloadVersions(versions: LauncherVersion[]) {
+ async function downloadVersions(versions: LauncherVersion[]) {
+ const config = await readNormalConfig()
+ const useWine = config.settings.useWineOnUnixWhenNeeded
+ const p = platform()
const newDownloads = versions.map(v => new DownloadProgress(v, 0, false, true))
setDownloadProgress(prev => [...prev, ...newDownloads])
newDownloads.forEach(download => {
+ let plat = p
+ if ((p === 'macos' || p === 'linux') && useWine) {
+ if (!download.version.platforms.includes(p) && download.version.platforms.includes('windows')) {
+ plat = 'windows'
+ }
+ }
+ const idx = download.version.platforms.indexOf(plat)
+ const url = download.version.downloadUrls[idx]
+ const exe = download.version.executables[idx]
+
+ if (!url) {
+ setDownloadProgress(prev =>
+ prev.map(d =>
+ d.version.version === download.version.version ? { ...d, failed: true } : d
+ )
+ )
+ return
+ }
+
const task = () => {
setDownloadProgress(prev => {
const i = prev.findIndex(d => d.version.version === download.version.version)
@@ -91,13 +123,13 @@ function App () {
})
invoke('download', {
- url: download.version.downloadUrls[download.version.platforms.indexOf(platform())],
+ url,
name: download.version.version,
- executable: download.version.executables[download.version.platforms.indexOf(platform())]
+ executable: exe
})
}
- queue.push(task)
+ queue.current.push(task)
runNext()
})
}
@@ -110,6 +142,8 @@ function App () {
}
async function notifyUser(title: string, body: string) {
+ const config = await readNormalConfig()
+ if (!config.settings.allowNotifications) return
let permissionGranted = await isPermissionGranted();
if (!permissionGranted) {
const permission = await requestPermission();
diff --git a/src/routes/Installs.tsx b/src/routes/Installs.tsx
index b45c3d2..de0a7f6 100644
--- a/src/routes/Installs.tsx
+++ b/src/routes/Installs.tsx
@@ -2,15 +2,31 @@ import { useEffect } from 'react'
import axios from 'axios'
import { InstallsProps } from '../types/InstallsProps'
import { platform } from '@tauri-apps/plugin-os'
+import { readNormalConfig } from '../util/BazookaManager'
export default function Installs({ downloadProgress, showPopup, setShowPopup, setPopupMode, setFadeOut, setSelectedVersionList, setVersionList }: InstallsProps) {
useEffect(() => {
if (!showPopup) return
- setSelectedVersionList([]);
+ setSelectedVersionList([])
setVersionList(null)
- axios.get('https://berrydash.lncvrt.xyz/database/launcher/versions.php')
- .then(res => setVersionList(res.data.filter((d: { platforms: string[] }) => d.platforms.includes(platform()))))
- .catch(() => setVersionList([]))
+ ;(async () => {
+ try {
+ const config = await readNormalConfig()
+ const useWine = config.settings.useWineOnUnixWhenNeeded
+ const res = await axios.get('https://berrydash.lncvrt.xyz/database/launcher/versions.php')
+ const p = platform()
+ const filtered = res.data.filter((d: { platforms: string[] }) =>
+ p === 'macos' || p === 'linux'
+ ? useWine
+ ? d.platforms.includes('windows') || d.platforms.includes(p)
+ : d.platforms.includes(p)
+ : d.platforms.includes(p)
+ )
+ setVersionList(filtered)
+ } catch {
+ setVersionList([])
+ }
+ })()
}, [showPopup])
return (
diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx
index 4583d67..13ffa03 100644
--- a/src/routes/Settings.tsx
+++ b/src/routes/Settings.tsx
@@ -1,7 +1,59 @@
+import { useEffect, useState } from 'react'
+import { Setting } from '../componets/Setting'
+import { readNormalConfig, writeNormalConfig } from '../util/BazookaManager'
+import { platform } from '@tauri-apps/plugin-os'
+
export default function Settings () {
+ const [checkForNewVersionOnLoad, setCheckForNewVersionOnLoad] =
+ useState(false)
+ const [useWineOnUnixWhenNeeded, setUseWineOnUnixWhenNeeded] = useState(false)
+ const [allowNotifications, setAllowNotifications] = useState(false)
+
+ useEffect(() => {
+ ;(async () => {
+ const config = await readNormalConfig()
+ setCheckForNewVersionOnLoad(config.settings.checkForNewVersionOnLoad)
+ setUseWineOnUnixWhenNeeded(config.settings.useWineOnUnixWhenNeeded)
+ setAllowNotifications(config.settings.allowNotifications)
+ })()
+ }, [])
+
return (
<>
Settings
+
+ {
+ setCheckForNewVersionOnLoad(!checkForNewVersionOnLoad)
+ const config = await readNormalConfig()
+ config.settings.checkForNewVersionOnLoad = !checkForNewVersionOnLoad
+ await writeNormalConfig(config)
+ }}
+ />
+ {
+ setAllowNotifications(!allowNotifications)
+ const config = await readNormalConfig()
+ config.settings.allowNotifications = !allowNotifications
+ await writeNormalConfig(config)
+ }}
+ />
+ {
+ setUseWineOnUnixWhenNeeded(!useWineOnUnixWhenNeeded)
+ const config = await readNormalConfig()
+ config.settings.useWineOnUnixWhenNeeded = !useWineOnUnixWhenNeeded
+ await writeNormalConfig(config)
+ }}
+ className={platform() == 'linux' || platform() == 'macos' ? '' : 'hidden'}
+ />
+
>
)
}
diff --git a/src/types/SettingProps.ts b/src/types/SettingProps.ts
new file mode 100644
index 0000000..b663229
--- /dev/null
+++ b/src/types/SettingProps.ts
@@ -0,0 +1,6 @@
+export type SettingProps = {
+ label: string
+ value: boolean
+ onChange: (val: boolean) => void,
+ className?: string
+}
diff --git a/src/types/SettingsType.ts b/src/types/SettingsType.ts
index b115637..714cabf 100644
--- a/src/types/SettingsType.ts
+++ b/src/types/SettingsType.ts
@@ -1,6 +1,7 @@
export class SettingsType {
constructor (
public checkForNewVersionOnLoad: boolean = true,
- public useWineOnUnixWhenNeeded: boolean = false
+ public allowNotifications: boolean = true,
+ public useWineOnUnixWhenNeeded: boolean = false,
) {}
}
diff --git a/src/types/VersionsConfig.ts b/src/types/VersionsConfig.ts
new file mode 100644
index 0000000..982118f
--- /dev/null
+++ b/src/types/VersionsConfig.ts
@@ -0,0 +1,14 @@
+import { LauncherVersion } from './LauncherVersion'
+
+export class VersionsConfig {
+ constructor (
+ public version: string,
+ public list: LauncherVersion[] = []
+ ) {}
+
+ static import (data: any) {
+ const cfg = new VersionsConfig(data.version)
+ Object.assign(cfg.list, data.list)
+ return cfg
+ }
+}
diff --git a/src/util/BazookaManager.ts b/src/util/BazookaManager.ts
index 9287081..2a42b21 100644
--- a/src/util/BazookaManager.ts
+++ b/src/util/BazookaManager.ts
@@ -10,8 +10,9 @@ import {
} from '@tauri-apps/plugin-fs'
import { decrypt, encrypt } from './Encryption'
import { Keys } from '../enums/Keys'
+import { VersionsConfig } from '../types/VersionsConfig'
-export async function readConfig (): Promise {
+export async function readNormalConfig (): Promise {
const version = await app.getVersion()
const options = {
baseDir: BaseDirectory.AppLocalData
@@ -40,7 +41,7 @@ export async function readConfig (): Promise {
)
}
-export async function writeConfig (data: NormalConfig) {
+export async function writeNormalConfig (data: NormalConfig) {
const options = {
baseDir: BaseDirectory.AppLocalData
}
@@ -67,3 +68,60 @@ export async function writeConfig (data: NormalConfig) {
)
}
}
+
+export async function readVersionsConfig (): Promise {
+ const version = await app.getVersion()
+ const options = {
+ baseDir: BaseDirectory.AppLocalData
+ }
+ const doesFolderExist = await exists('', options)
+ const doesConfigExist = await exists('versions.dat', options)
+ if (!doesFolderExist || !doesConfigExist) {
+ if (!doesFolderExist) {
+ await mkdir('', options)
+ }
+ const file = await create('versions.dat', options)
+ await file.write(
+ new TextEncoder().encode(
+ encrypt(
+ JSON.stringify(new VersionsConfig(version)),
+ Keys.VERSIONS_ENCRYPTION_KEY
+ )
+ )
+ )
+ await file.close()
+ return new VersionsConfig(version)
+ }
+ const config = await readTextFile('versions.dat', options)
+ return VersionsConfig.import(
+ JSON.parse(decrypt(config, Keys.VERSIONS_ENCRYPTION_KEY))
+ )
+}
+
+export async function writeVersionsConfig (data: VersionsConfig) {
+ const options = {
+ baseDir: BaseDirectory.AppLocalData
+ }
+ const doesFolderExist = await exists('', options)
+ const doesConfigExist = await exists('versions.dat', options)
+ if (!doesFolderExist || !doesConfigExist) {
+ if (!doesFolderExist) {
+ await mkdir('', options)
+ }
+ const file = await create('versions.dat', options)
+ await file.write(
+ new TextEncoder().encode(
+ encrypt(JSON.stringify(data), Keys.VERSIONS_ENCRYPTION_KEY)
+ )
+ )
+ await file.close()
+ } else {
+ await writeFile(
+ 'versions.dat',
+ new TextEncoder().encode(
+ encrypt(JSON.stringify(data), Keys.VERSIONS_ENCRYPTION_KEY)
+ ),
+ options
+ )
+ }
+}