10 Commits
1.9.0 ... 2.3.1

Author SHA1 Message Date
e08b265ae8 FIX: Fail to start game 2025-11-05 23:33:34 +07:00
52134c2200 UPDATE: handler before start game 2025-11-05 20:44:41 +07:00
892ea44c17 UPDATE: Fix ui and add License 2025-10-20 18:11:37 +07:00
ca612797ee FIX: Drag drop, ui/ux 2025-10-20 17:20:16 +07:00
787962c6d0 UPDATE: Backgroud setting, Remake ui/ux 2025-10-19 23:37:04 +07:00
09434fcc5b UPDATE: System tray 2025-10-13 09:09:55 +07:00
edbe04b9fc UPDATE: System tray setting 2025-10-07 13:14:18 +07:00
a6b49bef24 UPDATE: Change app description 2025-10-07 11:23:39 +07:00
dcdb3863ca FIX: Fix appicon resolution 2025-10-07 11:14:33 +07:00
d5710c9102 FIX: Fix appicon resolution 2025-10-07 11:12:53 +07:00
76 changed files with 3975 additions and 2274 deletions

4
.gitignore vendored
View File

@@ -1,10 +1,10 @@
.task
bin
bin/
tests/
server/
proxy/
proto/
temp/
.history/
frontend/dist
frontend/node_modules
build/linux/appimage/build

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Firefly Shelter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,7 +1,39 @@
PROTO_SRC=./proto
GEN_DEST=./pkg
build_proto:
proto:
@echo Compiling Protobuf files...
protoc --go_out=${GEN_DEST} --go-grpc_out=${GEN_DEST} ${PROTO_SRC}/*.proto
@echo Done!
icon:
@echo Updating icons...
magick build/appicon.png -define icon:auto-resize=16,24,32,48,64,128,256 build/windows/icon.ico
magick build/appicon.png -define icon:auto-resize=16,24,32,48,64,128,256,512 build/windows/icons.ico
@echo Done!
dev:
@echo Starting development server...
wails3 dev
@echo Done!
build:
@echo Building application...
wails3 build
@echo Done!
generate:
@echo Generating bindings...
wails3 generate bindings -ts
@echo Done!
release:
@echo Building release application...
wails3 package
@echo Done!
update:
@echo Updating build assets...
wails3 update build-assets -name "firefly-launcher" -binaryname "firefly-launcher" -config build/config.yml
@echo Done!

BIN
assets/appicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -22,15 +22,19 @@ tasks:
- npm install
build:frontend:
label: build:frontend (PRODUCTION={{.PRODUCTION}})
summary: Build the frontend project
dir: frontend
sources:
- "**/*"
generates:
- dist/*
- dist/**/*
deps:
- task: install:frontend:deps
- task: generate:bindings
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
cmds:
- npm run {{.BUILD_COMMAND}} -q
env:
@@ -40,15 +44,19 @@ tasks:
generate:bindings:
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
summary: Generates bindings for the frontend
deps:
- task: go:mod:tidy
sources:
- "**/*.[jt]s"
- exclude: frontend/**/*
- frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output
- "**/*.go"
- go.mod
- go.sum
generates:
- "frontend/bindings/**/*"
- frontend/bindings/**/*
cmds:
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true
@@ -58,10 +66,10 @@ tasks:
sources:
- "appicon.png"
generates:
- "icons.icns"
- "icons.ico"
- "darwin/icons.icns"
- "windows/icon.ico"
cmds:
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icons.ico
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico
dev:frontend:
summary: Runs the frontend in development mode

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 304 KiB

View File

@@ -8,10 +8,10 @@ info:
companyName: "Firefly Shelter" # The name of the company
productName: "Firefly Launcher" # The name of the application
productIdentifier: "com.fireflyshelter.fireflylauncher" # The unique product identifier
description: "Custom game launcher built for convenience and quick access" # The application description
description: "Firefly Launcher" # The application description
copyright: "@ 2025, Firefly Shelter" # Copyright text
comments: "Custom game launcher built for convenience and quick access" # Comments
version: "0.1.0" # The application version
comments: "Firefly Launcher" # Comments
version: "1.0.0" # The application version
# Dev mode configuration
dev_mode:
@@ -44,9 +44,19 @@ dev_mode:
type: primary
# File Associations
# More information at: https://v3alpha.wails.io/noit/done/yet
# More information at: https://v3.wails.io/noit/done/yet
fileAssociations:
# - ext: wails
# name: Wails
# description: Wails Application File
# iconName: wailsFileIcon
# role: Editor
# - ext: jpg
# name: JPEG
# description: Image File
# iconName: jpegFileIcon
# role: Editor
# mimeType: image/jpeg # (optional)
# Other data
other:

View File

@@ -2,27 +2,27 @@
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<string>APPL</string>
<key>CFBundleName</key>
<string>Firefly Launcher</string>
<string>Firefly Launcher</string>
<key>CFBundleExecutable</key>
<string>firefly-launcher</string>
<string>firefly-launcher</string>
<key>CFBundleIdentifier</key>
<string>com.fireflyshelter.fireflylauncher</string>
<string>com.fireflyshelter.fireflylauncher</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<string>1.0.0</string>
<key>CFBundleGetInfoString</key>
<string>Custom game launcher built for convenience and quick access</string>
<string>Firefly Launcher</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>1.0.0</string>
<key>CFBundleIconFile</key>
<string>icons</string>
<string>icons</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<string>10.15.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>@ 2025, Firefly Shelter</string>
<string>@ 2025, Firefly Shelter</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>

View File

@@ -2,26 +2,26 @@
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<string>APPL</string>
<key>CFBundleName</key>
<string>Firefly Launcher</string>
<string>Firefly Launcher</string>
<key>CFBundleExecutable</key>
<string>firefly-launcher</string>
<string>firefly-launcher</string>
<key>CFBundleIdentifier</key>
<string>com.fireflyshelter.fireflylauncher</string>
<string>com.fireflyshelter.fireflylauncher</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<string>1.0.0</string>
<key>CFBundleGetInfoString</key>
<string>Custom game launcher built for convenience and quick access</string>
<string>Firefly Launcher</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>1.0.0</string>
<key>CFBundleIconFile</key>
<string>icons</string>
<string>icons</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<string>10.15.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>@ 2025, Firefly Shelter</string>
<string>@ 2025, Firefly Shelter</string>
</dict>
</plist>

View File

@@ -9,6 +9,11 @@ tasks:
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
@@ -64,7 +69,13 @@ tasks:
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS
- cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents
- codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app
run:
cmds:
- '{{.BIN_DIR}}/{{.APP_NAME}}'
- mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources}
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS
- cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist
- codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app
- '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}'

Binary file not shown.

View File

@@ -9,6 +9,11 @@ tasks:
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}
@@ -105,7 +110,7 @@ tasks:
vars:
APP_NAME: '{{.APP_NAME}}'
EXEC: '{{.APP_NAME}}'
ICON: 'appicon'
ICON: '{{.APP_NAME}}'
CATEGORIES: 'Development;'
OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop'

View File

@@ -14,12 +14,21 @@ cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/"
cp "${ICON_PATH}" "${APP_DIR}/"
cp "${DESKTOP_FILE}" "${APP_DIR}/"
# Download linuxdeploy and make it executable
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
if [[ $(uname -m) == *x86_64* ]]; then
# Download linuxdeploy and make it executable
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
# Run linuxdeploy to bundle the application
./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage
# Run linuxdeploy to bundle the application
./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage
else
# Download linuxdeploy and make it executable (arm64)
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage
chmod +x linuxdeploy-aarch64.AppImage
# Run linuxdeploy to bundle the application (arm64)
./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage
fi
# Rename the generated AppImage
mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage"

13
build/linux/desktop Normal file
View File

@@ -0,0 +1,13 @@
[Desktop Entry]
Version=1.0
Name=Firefly Launcher
Comment=Firefly Launcher
# The Exec line includes %u to pass the URL to the application
Exec=/usr/local/bin/firefly-launcher %u
Terminal=false
Type=Application
Icon=firefly-launcher
Categories=Utility;
StartupWMClass=firefly-launcher

View File

@@ -6,11 +6,11 @@
name: "firefly-launcher"
arch: ${GOARCH}
platform: "linux"
version: "0.1.0"
version: "1.0.0"
section: "default"
priority: "extra"
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
description: "Custom game launcher built for convenience and quick access"
description: "Firefly Launcher"
vendor: "Firefly Shelter"
homepage: "https://wails.io"
license: "MIT"
@@ -24,9 +24,31 @@ contents:
- src: "./build/linux/firefly-launcher.desktop"
dst: "/usr/share/applications/firefly-launcher.desktop"
# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1
depends:
- gtk3
- libwebkit2gtk
- libgtk-3-0
- libwebkit2gtk-4.1-0
# Distribution-specific overrides for different package formats and WebKit versions
overrides:
# RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0)
rpm:
depends:
- gtk3
- webkit2gtk4.1
# Arch Linux packages (WebKit 4.1)
archlinux:
depends:
- gtk3
- webkit2gtk-4.1
# scripts section to ensure desktop database is updated after install
scripts:
postinstall: "./build/linux/nfpm/scripts/postinstall.sh"
# You can also add preremove, postremove if needed
# preremove: "./build/linux/nfpm/scripts/preremove.sh"
# postremove: "./build/linux/nfpm/scripts/postremove.sh"
# replaces:
# - foobar
@@ -43,8 +65,3 @@ depends:
# - not-foo
# - not-bar
# changelog: "changelog.yaml"
# scripts:
# preinstall: ./build/linux/nfpm/scripts/preinstall.sh
# postinstall: ./build/linux/nfpm/scripts/postinstall.sh
# preremove: ./build/linux/nfpm/scripts/preremove.sh
# postremove: ./build/linux/nfpm/scripts/postremove.sh

View File

@@ -1 +1,21 @@
#!/bin/bash
#!/bin/sh
# Update desktop database for .desktop file changes
# This makes the application appear in application menus and registers its capabilities.
if command -v update-desktop-database >/dev/null 2>&1; then
echo "Updating desktop database..."
update-desktop-database -q /usr/share/applications
else
echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2
fi
# Update MIME database for custom URL schemes (x-scheme-handler)
# This ensures the system knows how to handle your custom protocols.
if command -v update-mime-database >/dev/null 2>&1; then
echo "Updating MIME database..."
update-mime-database -n /usr/share/mime
else
echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2
fi
exit 0

View File

@@ -10,7 +10,10 @@ tasks:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
PRODUCTION: '{{.PRODUCTION}}'
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- task: generate:syso
@@ -28,9 +31,11 @@ tasks:
PRODUCTION: '{{.PRODUCTION | default "false"}}'
package:
summary: Packages a production build of the application into a `.exe` bundle
summary: Packages a production build of the application
vars:
FORMAT: '{{.FORMAT | default "nsis"}}'
cmds:
- task: create:nsis:installer
- task: '{{if eq .FORMAT "msix"}}create:msix:package{{else}}create:nsis:installer{{end}}'
generate:syso:
summary: Generates Windows `.syso` file
@@ -49,12 +54,42 @@ tasks:
PRODUCTION: "true"
cmds:
# Create the Microsoft WebView2 bootstrapper if it doesn't exist
- wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}\build\windows\nsis"
- makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}\{{.BIN_DIR}}\{{.APP_NAME}}.exe" project.nsi
- wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis"
- cmd: >
powershell -NoProfile -Command "& makensis ('-DARG_WAILS_{{.ARG_FLAG}}_BINARY=' + (\"{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe\" -replace '/', '\\')) 'project.nsi'"
platforms: [windows]
vars:
ARCH: '{{.ARCH | default ARCH}}'
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
create:msix:package:
summary: Creates an MSIX package
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- |-
wails3 tool msix \
--config "{{.ROOT_DIR}}/wails.json" \
--name "{{.APP_NAME}}" \
--executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \
--arch "{{.ARCH}}" \
--out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \
{{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \
{{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \
{{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}}
vars:
ARCH: '{{.ARCH | default ARCH}}'
CERT_PATH: '{{.CERT_PATH | default ""}}'
PUBLISHER: '{{.PUBLISHER | default ""}}'
USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}'
install:msix:tools:
summary: Installs tools required for MSIX packaging
cmds:
- wails3 tool msix-install-tools
run:
cmds:
- '{{.BIN_DIR}}\\{{.APP_NAME}}.exe'
- '{{.BIN_DIR}}/{{.APP_NAME}}.exe'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 592 KiB

View File

@@ -1,15 +1,15 @@
{
"fixed": {
"file_version": "0.1.0"
"file_version": "1.0.0"
},
"info": {
"0000": {
"ProductVersion": "0.1.0",
"ProductVersion": "1.0.0",
"CompanyName": "Firefly Shelter",
"FileDescription": "Custom game launcher built for convenience and quick access",
"FileDescription": "Firefly Launcher",
"LegalCopyright": "@ 2025, Firefly Shelter",
"ProductName": "Firefly Launcher",
"Comments": "Custom game launcher built for convenience and quick access"
"Comments": "Firefly Launcher"
}
}
}

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
<Identity
Name="com.wails.firefly-launcher"
Publisher="CN=My Company"
Version="0.1.0.0"
ProcessorArchitecture="x64" />
<Properties>
<DisplayName>My Product</DisplayName>
<PublisherDisplayName>My Company</PublisherDisplayName>
<Description>My Product Description</Description>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="en-us" />
</Resources>
<Applications>
<Application Id="com.wails.firefly-launcher" Executable="firefly-launcher.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="My Product"
Description="My Product Description"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<desktop:Extension Category="windows.fullTrustProcess" Executable="firefly-launcher.exe" />
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<MsixPackagingToolTemplate
xmlns="http://schemas.microsoft.com/msix/packaging/msixpackagingtool/template/2022">
<Settings
AllowTelemetry="false"
ApplyACLsToPackageFiles="true"
GenerateCommandLineFile="true"
AllowPromptForPassword="false">
</Settings>
<Installer
Path="firefly-launcher.exe"
Arguments=""
InstallLocation="C:\Program Files\My Company\My Product">
</Installer>
<PackageInformation
PackageName="My Product"
PackageDisplayName="My Product"
PublisherName="CN=My Company"
PublisherDisplayName="My Company"
Version="0.1.0.0"
PackageDescription="My Product Description">
<Capabilities>
<Capability Name="runFullTrust" />
</Capabilities>
<Applications>
<Application
Id="com.wails.firefly-launcher"
Description="My Product Description"
DisplayName="My Product"
ExecutableName="firefly-launcher.exe"
EntryPoint="Windows.FullTrustApplication">
</Application>
</Applications>
<Resources>
<Resource Language="en-us" />
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Properties>
<Framework>false</Framework>
<DisplayName>My Product</DisplayName>
<PublisherDisplayName>My Company</PublisherDisplayName>
<Description>My Product Description</Description>
<Logo>Assets\AppIcon.png</Logo>
</Properties>
</PackageInformation>
<SaveLocation PackagePath="firefly-launcher.msix" />
<PackageIntegrity>
<CertificatePath></CertificatePath>
</PackageIntegrity>
</MsixPackagingToolTemplate>

View File

@@ -20,10 +20,10 @@ Unicode true
## The following information is taken from the wails_tools.nsh file, but they can be overwritten here.
####
## !define INFO_PROJECTNAME "my-project" # Default "firefly-launcher"
## !define INFO_COMPANYNAME "My Company" # Default "FireflyShelter"
## !define INFO_PRODUCTNAME "My Product Name" # Default "Firefly Launcher"
## !define INFO_COMPANYNAME "My Company" # Default "My Company"
## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product"
## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0"
## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "@ 2025, FireflyShelter"
## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© now, My Company"
###
## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
@@ -92,6 +92,7 @@ Section
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
!insertmacro wails.associateFiles
!insertmacro wails.associateCustomProtocols
!insertmacro wails.writeUninstaller
SectionEnd
@@ -107,6 +108,7 @@ Section "uninstall"
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
!insertmacro wails.unassociateFiles
!insertmacro wails.unassociateCustomProtocols
!insertmacro wails.deleteUninstaller
SectionEnd

View File

@@ -14,7 +14,7 @@
!define INFO_PRODUCTNAME "Firefly Launcher"
!endif
!ifndef INFO_PRODUCTVERSION
!define INFO_PRODUCTVERSION "0.1.0"
!define INFO_PRODUCTVERSION "1.0.0"
!endif
!ifndef INFO_COPYRIGHT
!define INFO_COPYRIGHT "@ 2025, Firefly Shelter"
@@ -158,7 +158,7 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
${If} ${REQUEST_EXECUTION_LEVEL} == "user"
# If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed
ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
${If} $0 != ""
Goto ok
${EndIf}
@@ -210,3 +210,27 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
; Delete app associations
!macroend
!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND
DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}"
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" ""
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}"
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" ""
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" ""
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}"
!macroend
!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL
DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
!macroend
!macro wails.associateCustomProtocols
; Create custom protocols associations
!macroend
!macro wails.unassociateCustomProtocols
; Delete app custom protocol associations
!macroend

View File

@@ -1,13 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity type="win32" name="com.fireflyshelter.fireflylauncher" version="0.1.0" processorArchitecture="*"/>
<assemblyIdentity type="win32" name="com.fireflyshelter.fireflylauncher" version="1.0.0" processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<!-- Yêu cầu quyền admin -->
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
</asmv3:windowsSettings>
</asmv3:application>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
@@ -15,11 +19,4 @@
</requestedPrivileges>
</security>
</trustInfo>
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

View File

@@ -6,6 +6,13 @@
// @ts-ignore: Unused imports
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
/**
* @returns {$CancellablePromise<[boolean, string]>}
*/
export function CloseApp() {
return $Call.ByID(3422460836);
}
/**
* @param {number} timeout
* @returns {$CancellablePromise<[boolean, string]>}
@@ -20,3 +27,31 @@ export function CloseAppAfterTimeout(timeout) {
export function GetCurrentLauncherVersion() {
return $Call.ByID(3575133982);
}
/**
* @returns {$CancellablePromise<[boolean, string]>}
*/
export function HideApp() {
return $Call.ByID(88003266);
}
/**
* @returns {$CancellablePromise<[boolean, string]>}
*/
export function MaximizeApp() {
return $Call.ByID(1257306588);
}
/**
* @returns {$CancellablePromise<[boolean, string]>}
*/
export function MinimizeApp() {
return $Call.ByID(3434614194);
}
/**
* @returns {$CancellablePromise<[boolean, string]>}
*/
export function RestoreApp() {
return $Call.ByID(3115625834);
}

View File

@@ -31,6 +31,22 @@ export function FileExistsInZip(archivePath, fileInside) {
return $Call.ByID(2509699047, archivePath, fileInside);
}
/**
* @param {string} path
* @returns {$CancellablePromise<string>}
*/
export function GetDir(path) {
return $Call.ByID(1744445742, path);
}
/**
* @param {string[]} paths
* @returns {$CancellablePromise<string>}
*/
export function Join(...paths) {
return $Call.ByID(2460588289, paths);
}
/**
* @param {string} path
* @returns {$CancellablePromise<[boolean, string]>}
@@ -56,7 +72,15 @@ export function PickFolder() {
/**
* @param {string} path
* @returns {$CancellablePromise<boolean>}
* @returns {$CancellablePromise<void>}
*/
export function RemoveFile(path) {
return $Call.ByID(3206735043, path);
}
/**
* @param {string} path
* @returns {$CancellablePromise<[boolean, string]>}
*/
export function StartApp(path) {
return $Call.ByID(1267568402, path);
@@ -64,7 +88,7 @@ export function StartApp(path) {
/**
* @param {string} path
* @returns {$CancellablePromise<boolean>}
* @returns {$CancellablePromise<[boolean, string]>}
*/
export function StartWithConsole(path) {
return $Call.ByID(3249271428, path);

View File

@@ -1,13 +1,16 @@
<!DOCTYPE html>
<html data-theme="cupcake" lang="en">
<head>
<html lang="en" data-theme="dracula">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/wails.png" />
<link rel="icon" type="image/svg+xml" href="/appicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wails + React + TS</title>
</head>
<body>
<title>Firefly Launcher</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.12",
"@tailwindcss/vite": "^4.1.16",
"@tanstack/react-router": "^1.131.27",
"@tanstack/react-router-devtools": "^1.131.27",
"lucide-react": "^0.541.0",
@@ -19,8 +19,9 @@
"path-browserify": "^1.0.1",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-easy-crop": "^5.5.3",
"react-toastify": "^11.0.5",
"tailwindcss": "^4.1.12",
"tailwindcss": "^4.1.16",
"zustand": "^5.0.8"
},
"devDependencies": {
@@ -33,7 +34,7 @@
"@typescript-eslint/parser": "^8.40.0",
"@vitejs/plugin-react": "^5.0.1",
"@wailsio/runtime": "^3.0.0-alpha.66",
"daisyui": "^5.0.50",
"daisyui": "^5.4.4",
"eslint": "^9.34.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",

BIN
frontend/public/appicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

BIN
frontend/public/bg-1.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

BIN
frontend/public/bg-10.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
frontend/public/bg-11.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

BIN
frontend/public/bg-12.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

BIN
frontend/public/bg-13.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
frontend/public/bg-16.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

BIN
frontend/public/bg-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
frontend/public/bg-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
frontend/public/bg-5.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

BIN
frontend/public/bg-6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
frontend/public/bg-7.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

BIN
frontend/public/bg-8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
frontend/public/bg-9.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,180 @@
'use client'
import { useState, useRef } from 'react'
import { X, Image as ImageIcon, Plus, Upload, Check } from 'lucide-react'
import useSettingStore from '@/stores/settingStore'
import Cropper from 'react-easy-crop'
import getCroppedImg from '@/utils/cropImage'
const initialImages = {
"bg-1": "bg-1.jpeg",
"bg-2": "bg-2.png",
"bg-3": "bg-3.png",
"bg-6": "bg-6.png",
"bg-7": "bg-7.jpeg",
"bg-8": "bg-8.png",
"bg-9": "bg-9.jpeg",
"bg-10": "bg-10.jpg",
"bg-11": "bg-11.jpeg",
"bg-12": "bg-12.jpg",
"bg-13": "bg-13.jpg",
"bg-16": "bg-16.jpg",
}
export const BackgroundSelector = () => {
const [isOpen, setIsOpen] = useState(false)
const [newUrl, setNewUrl] = useState('')
const [croppingImage, setCroppingImage] = useState<string | null>(null)
const [crop, setCrop] = useState({ x: 0, y: 0 })
const [zoom, setZoom] = useState(1)
const [croppedAreaPixels, setCroppedAreaPixels] = useState<any>(null)
const { background, setBackground, extraBackgrounds, setExtraBackgrounds } = useSettingStore()
const fileInputRef = useRef<HTMLInputElement>(null)
const handleSelect = (img: string) => {
setIsOpen(false)
setBackground(img)
}
const handleAddUrl = () => {
if (!newUrl.trim()) return setCroppingImage(newUrl)
setNewUrl('')
}
const handleRemoveExtra = (url: string) => {
setExtraBackgrounds(extraBackgrounds.filter(bg => bg !== url))
}
const handleUploadFile = (file: File) => {
const reader = new FileReader()
reader.onload = () => setCroppingImage(reader.result as string)
reader.readAsDataURL(file)
}
const handleCropComplete = async () => {
if (!croppingImage || !croppedAreaPixels) return
const croppedBase64 = await getCroppedImg(croppingImage, croppedAreaPixels)
setExtraBackgrounds([croppedBase64, ...extraBackgrounds])
setCroppingImage(null)
}
const allBackgrounds = [...extraBackgrounds, ...Object.values(initialImages)]
return (
<div className="flex flex-col items-center justify-center gap-4">
<div className="tooltip tooltip-right" data-tip="Select Background">
<button
className="group btn btn-primary btn-circle flex items-center justify-center shadow-md transition-all duration-300 hover:scale-110 hover:shadow-lg hover:bg-primary/80"
onClick={() => setIsOpen(true)}
>
<ImageIcon size={22} className="text-white transition-all duration-300 group-hover:rotate-6 group-hover:scale-110 group-hover:text-yellow-300" />
</button>
</div>
{isOpen && (
<div className="fixed inset-0 z-40 flex items-center justify-center bg-base-200/60 pt-10">
<div className="bg-base-200 text-white rounded-xl shadow-xl p-6 w-[90%] max-w-2xl relative">
<button className="btn btn-ghost btn-circle absolute top-3 right-3" onClick={() => setIsOpen(false)}>
<X size={20} />
</button>
<h2 className="text-lg font-semibold mb-4">Choose Background</h2>
{/* Add via URL */}
<div className="flex gap-2 mb-4">
<input
type="text"
placeholder="Paste image URL (https://...)"
className="input input-bordered w-full text-info"
value={newUrl}
onChange={(e) => setNewUrl(e.target.value)}
/>
<button className="btn btn-success flex items-center gap-1" onClick={handleAddUrl}>
<Plus size={16} /> Add
</button>
</div>
{/* Upload from computer */}
<div className="flex mb-4">
<button
className="btn btn-warning flex items-center gap-1"
onClick={() => fileInputRef.current?.click()}
>
<Upload size={16} /> Upload from computer
</button>
<input
type="file"
accept="image/*"
className="hidden"
ref={fileInputRef}
onChange={(e) => {
const file = e.target.files?.[0]
if (file) handleUploadFile(file)
e.target.value = ''
}}
/>
</div>
{/* Crop Modal */}
{croppingImage && (
<div className="fixed inset-0 z-60 flex flex-col items-center justify-center bg-black/70 p-4">
<div className="relative w-full max-w-3xl h-[400px] bg-gray-800 rounded-lg">
<Cropper
image={croppingImage}
crop={crop}
zoom={zoom}
aspect={16 / 9}
onCropChange={setCrop}
onZoomChange={setZoom}
onCropComplete={(_, croppedAreaPixels) => setCroppedAreaPixels(croppedAreaPixels)}
/>
<button
className="absolute bottom-4 left-1/2 -translate-x-1/2 btn btn-success"
onClick={handleCropComplete}
>
<Check size={20} /> Done
</button>
<button
className="absolute top-2 right-2 btn btn-ghost btn-circle"
onClick={() => setCroppingImage(null)}
>
<X size={20} />
</button>
</div>
</div>
)}
<div className="grid grid-cols-3 gap-4 max-h-[60vh] overflow-y-auto">
{allBackgrounds.map((value, i) => {
const isExtra = i < extraBackgrounds.length
return (
<div
key={i}
className={`relative rounded-lg overflow-hidden cursor-pointer border-2 transition-all duration-200 ${
value === background ? 'border-blue-500' : 'border-transparent hover:border-gray-500'
}`}
onClick={() => handleSelect(value)}
>
<img src={value} alt={`bg-${i}`} loading="lazy" className="w-full h-28 object-cover" />
{isExtra && (
<button
className="absolute top-1 right-1 bg-black/50 hover:bg-black/70 text-white rounded-full p-1"
onClick={(e) => {
e.stopPropagation()
handleRemoveExtra(value)
}}
>
<X size={14} />
</button>
)}
</div>
)
})}
</div>
</div>
</div>
)}
</div>
)
}

View File

@@ -0,0 +1,83 @@
import { motion } from "motion/react"
import { AppService } from "@bindings/firefly-launcher/internal/app-service"
import { toast } from "react-toastify"
import useSettingStore from "@/stores/settingStore"
export default function CloseModal({
isOpen,
onClose
}: {
isOpen: boolean
onClose: () => void
}) {
if (!isOpen) return null
const { closingOption, setClosingOption } = useSettingStore()
return (
<div className="fixed inset-0 z-50 h-full flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="relative w-[90%] max-w-2xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="border-b border-purple-500/30 px-6 py-4 mb-4 flex justify-between items-center">
<h3 className="font-bold text-xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-600">
Confirm Action
</h3>
<motion.button
whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }}
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
onClick={onClose}
>
</motion.button>
</div>
<div className="px-6 pt-2 pb-6">
<p className="mb-4 text-lg">
Do you want to minimize the application to the system tray or close the application?
</p>
<div className="flex items-center mb-4">
<input
id="dontAskAgain"
type="checkbox"
className="checkbox checkbox-sm mr-2"
checked={!closingOption.isAsk}
onChange={(e) => setClosingOption({ isMinimize: closingOption.isMinimize, isAsk: !e.target.checked })}
/>
<label htmlFor="dontAskAgain" className="text-sm font-semibold text-accent">
Do not ask me again
</label>
</div>
<div className="grid grid-cols-2 justify-end gap-3">
<button
className="btn btn-warning"
onClick={async () => {
onClose()
const [success, message] = await AppService.HideApp()
if (!success) toast.error(message)
if (!closingOption.isAsk) {
setClosingOption({ isMinimize: true, isAsk: false })
}
}}
>
Minimize
</button>
<button
className="btn btn-error btn-outline"
onClick={async () => {
onClose()
const [success, message] = await AppService.CloseApp()
if (!success) toast.error(message)
if (!closingOption.isAsk) {
setClosingOption({ isMinimize: false, isAsk: false })
}
}}
>
Close
</button>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,163 @@
import { Link } from "@tanstack/react-router";
import useModalStore from "@/stores/modalStore";
import { Blend, BookOpen, Diff, Home, Info, Languages, Minus, Puzzle, Settings, TrendingUpDown, Wrench, X } from "lucide-react";
import { AppService } from "@bindings/firefly-launcher/internal/app-service";
export default function Header() {
const { setIsOpenSettingModal } = useModalStore()
const controlButtons = [
{
icon: <Settings className="w-5 h-5" />,
action: () => setIsOpenSettingModal(true),
tip: "Settings",
},
{
icon: <Minus className="w-5 h-5" />,
action: () => AppService.MinimizeApp(),
tip: "Minimize",
},
{
icon: <X className="w-5 h-5" />,
action: () => AppService.CloseApp(),
tip: "Close",
},
]
return (
<div className="navbar sticky top-0 z-50 px-3" style={{ '--wails-draggable': 'drag' } as any}>
<div className="navbar-start">
<div className="dropdown">
<div tabIndex={0} role="button" className="btn btn-ghost md:hidden">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h8m-8 6h16" /> </svg>
</div>
<ul
tabIndex={0}
className="menu menu-sm dropdown-content bg-black/50 backdrop-blur-md rounded-box z-1 mt-3 w-52 p-2 shadow">
<li><Link to="/">Home</Link></li>
<li>
<a>Tools</a>
<ul className="p-2">
<li><Link to="/language">Language</Link></li>
<li><Link to="/diff">Diff</Link></li>
</ul>
</li>
<li>
<a>Plugins</a>
<ul className="p-2">
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
<li><Link to="/srtools">SrTools</Link></li>
</ul>
</li>
<li><Link to="/howto">How to?</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
</div>
<Link to="/" className="grid grid-cols-1 items-start text-left gap-0">
<div className="flex items-center justify-center">
<img src="/appicon.png" alt="Logo" className='w-13 h-13 rounded-lg mx-2' />
<div className="flex flex-col justify-center items-start">
<h1 className="text-xl font-bold">
<span className="text-emerald-500"
style={{
textShadow: '0 1px 2px rgba(255, 255, 255, 0.2)',
}}
>Firefly </span>
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500"
style={{
textShadow: '0 1px 2px rgba(255, 255, 255, 0.2)',
}}
>
Launcher
</span>
</h1>
<p
className="text-white text-sm"
style={{
textShadow: '0 1px 2px rgba(0, 0, 0, 0.8)',
}}
>
By Kain
</p>
</div>
</div>
</Link>
</div>
<div className="navbar-center hidden md:flex bg-black/40 backdrop-blur-sm rounded-lg shadow-lg">
<ul className="menu menu-horizontal px-1 gap-4 text-white">
<li>
<Link to="/" className="flex items-center gap-2 hover:text-cyan-300 transition-colors">
<Home size={18} /> Home
</Link>
</li>
<li>
<details>
<summary className="flex items-center gap-2 cursor-pointer hover:text-cyan-300 transition-colors">
<Wrench size={18} /> Tools
</summary>
<ul className="p-2 bg-black/75 text-white rounded-lg">
<li>
<Link to="/language" className="flex items-center gap-2 hover:text-cyan-300">
<Languages size={18} /> Language
</Link>
</li>
<li>
<Link to="/diff" className="flex items-center gap-2 hover:text-cyan-300">
<Diff size={18} /> Client update
</Link>
</li>
</ul>
</details>
</li>
<li>
<details>
<summary className="flex items-center gap-2 cursor-pointer hover:text-cyan-300 transition-colors">
<Puzzle size={18} /> Plugins
</summary>
<ul className="p-2 bg-black/75 text-white rounded-lg">
<li>
<Link to="/analysis" className="flex items-center gap-2 hover:text-cyan-300">
<TrendingUpDown size={18} /> Analysis (Veritas)
</Link>
</li>
<li>
<Link to="/srtools" className="flex items-center gap-2 hover:text-cyan-300">
<Blend size={18} /> Firefly Tools
</Link>
</li>
</ul>
</details>
</li>
<li>
<Link to="/howto" className="flex items-center gap-2 hover:text-cyan-300 transition-colors">
<BookOpen size={18} /> How to?
</Link>
</li>
<li>
<Link to="/about" className="flex items-center gap-2 hover:text-cyan-300 transition-colors">
<Info size={18} /> About
</Link>
</li>
</ul>
</div>
<div className="navbar-end flex gap-2 z-52">
<div className="flex items-center gap-2 bg-black/40 backdrop-blur-sm rounded-lg">
{controlButtons.map((btn, i) => (
<div key={i} className="tooltip tooltip-bottom" data-tip={btn.tip}>
<button
onClick={btn.action}
className="btn btn-ghost btn-circle bg-transparent border-none flex items-center justify-center"
>
<div className="hover:text-cyan-300 transition-colors">
{btn.icon}
</div>
</button>
</div>
))}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,117 @@
import { CheckUpdateLauncher } from "@/helper"
import useModalStore from "@/stores/modalStore"
import useSettingStore from "@/stores/settingStore"
import useLauncherStore from "@/stores/launcherStore"
import { toast } from "react-toastify"
export default function SettingModal({
isOpen,
onClose
}: {
isOpen: boolean
onClose: () => void
}) {
if (!isOpen) return null
const { setIsOpenSelfUpdateModal } = useModalStore()
const { closingOption, setClosingOption, serverVersion,
proxyVersion, } = useSettingStore()
const { setUpdateData, updateData, launcherVersion } = useLauncherStore()
const CheckUpdate = async () => {
const launcherData = await CheckUpdateLauncher()
if (!launcherData.isUpdate) {
toast.success("Launcher is already up to date")
return
}
setUpdateData({
server: updateData.server,
proxy: updateData.proxy,
launcher: launcherData
})
setIsOpenSelfUpdateModal(true)
}
return (
<div className="fixed inset-0 z-10 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="relative w-[90%] max-w-md bg-base-100 text-base-content rounded-2xl border border-purple-500/30 shadow-2xl shadow-purple-500/30 p-6">
{/* Header */}
<div className="flex justify-between items-center mb-6">
<h3 className="font-extrabold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-500">
Settings
</h3>
<button
className="btn btn-circle btn-sm bg-red-600 hover:bg-red-700 text-white border-none shadow-lg"
onClick={onClose}
>
</button>
</div>
{/* Content */}
<div className="flex flex-col gap-6">
{/* Section 1: Launcher Update */}
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
<h4 className="font-bold text-lg mb-2">Launcher Update</h4>
<p className="text-sm text-info mb-3">
Check if your launcher is up to date.
</p>
<button
className="btn btn-primary bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-400 hover:to-red-500 text-white shadow-md hover:shadow-lg transition-all duration-200"
onClick={CheckUpdate}
>
Check for Launcher Updates
</button>
</div>
{/* Section 2: Closing Option */}
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
<h4 className="font-bold text-lg mb-2">Closing Options</h4>
<label className="flex items-start gap-3 cursor-pointer select-none">
<input
type="checkbox"
className="checkbox checkbox-primary w-5 h-5 mt-1"
checked={!closingOption.isAsk}
onChange={(e) => {
setClosingOption({
isMinimize: closingOption.isMinimize,
isAsk: !e.target.checked
})
}}
/>
<div className="flex flex-col">
<span className="text-base font-medium text-info">
Set do not ask again
</span>
<span className="text-sm text-accent">
Next time you close the app, it will automatically{" "}
{closingOption.isMinimize ? "minimize to system tray" : "quit the app"}{" "}
without asking.
</span>
</div>
</label>
</div>
{/* Section 3: Launcher Version */}
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
<h4 className="font-bold text-lg mb-2">Version</h4>
<div className="flex flex-wrap gap-2">
<p className="text-base text-info">
Server: {serverVersion}
</p>
<p className="text-base text-info">
Proxy: {proxyVersion}
</p>
<p className="text-base text-info">
Launcher: {launcherVersion}
</p>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -1,29 +0,0 @@
import { useEffect, useState } from "react";
export default function ThemeController() {
const [theme, setTheme] = useState(localStorage.getItem("theme") ?? "light");
const handleToggle = (e: any) => {
if (e.target.checked) {
setTheme("cupcake");
} else {
setTheme("night");
}
};
useEffect(() => {
localStorage.setItem('theme', theme!)
const localTheme = localStorage.getItem('theme')
document.querySelector('html')?.setAttribute('data-theme', localTheme!)
}, [theme]);
return (
<label className="toggle text-base-content">
<input type="checkbox" onChange={handleToggle} className="theme-controller" />
<svg aria-label="moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g strokeLinejoin="round" strokeLinecap="round" strokeWidth="2" fill="none" stroke="currentColor"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path></g></svg>
<svg aria-label="sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g strokeLinejoin="round" strokeLinecap="round" strokeWidth="2" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2"></path><path d="M12 20v2"></path><path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path><path d="M2 12h2"></path><path d="M20 12h2"></path><path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path></g></svg>
</label>
)
}

View File

@@ -0,0 +1,64 @@
import { motion } from "framer-motion"
interface UpdateModalProps {
isOpen: boolean
title: string
message: string
buttons: {
text: string
onClick: () => Promise<void> | void
variant?: "primary" | "error" | "outline"
}[]
onClose: () => void
}
export default function UpdateModal({ isOpen, title, message, buttons, onClose }: UpdateModalProps) {
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
<motion.button
whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }}
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
onClick={onClose}
>
</motion.button>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
{title}
</h3>
</div>
<div className="px-6 pb-6">
<div className="mb-6">
<p className="text-accent text-lg">{message}</p>
</div>
<div className="flex justify-end gap-3">
{buttons.map((btn, idx) => (
<motion.button
key={idx}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className={`btn ${
btn.variant === "primary"
? "btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
: btn.variant === "error"
? "btn-error"
: "btn-outline btn-error"
}`}
onClick={btn.onClick}
>
{btn.text}
</motion.button>
))}
</div>
</div>
</div>
</div>
)
}

View File

@@ -2,29 +2,17 @@
import { useEffect } from "react";
import { Events } from "@wailsio/runtime";
import { toast } from "react-toastify";
import useSettingStore from "@/stores/settingStore";
import { AppService } from "@bindings/firefly-launcher/internal/app-service";
import useModalStore from "@/stores/modalStore";
import useDiffStore from "@/stores/diffStore";
import useLauncherStore from "@/stores/launcherStore";
export function useGlobalEvents({
setGameRunning,
setServerRunning,
setProxyRunning,
setProgressUpdate,
setMaxProgressUpdate,
setProgressDownload,
setDownloadSpeed,
setMessageUpdate,
setStageType,
export function useGlobalEvents() {
const { setIsOpenCloseModal } = useModalStore()
const { setGameRunning, setServerRunning, setProxyRunning, setProgressDownload, setDownloadSpeed } = useLauncherStore()
const { setProgressUpdate, setMaxProgressUpdate, setMessageUpdate, setStageType } = useDiffStore()
}: {
setGameRunning: (v: boolean) => void;
setServerRunning: (v: boolean) => void;
setProxyRunning: (v: boolean) => void;
setProgressUpdate: (v: number) => void;
setMaxProgressUpdate: (v: number) => void;
setProgressDownload: (v: number) => void;
setDownloadSpeed: (v: string) => void;
setMessageUpdate: (v: string) => void;
setStageType: (v: string) => void,
}) {
useEffect(() => {
const onGameExit = () => setGameRunning(false);
const onServerExit = () => setServerRunning(false);
@@ -64,6 +52,20 @@ export function useGlobalEvents({
const { message } = event.data[0];
toast.error(message);
});
Events.On("window:close", async () => {
const option = useSettingStore.getState().closingOption
if (option.isAsk) {
setIsOpenCloseModal(true);
return
}
if (option.isMinimize) {
const [success, message] = await AppService.MinimizeApp()
if (!success) toast.error(message)
} else {
const [success, message] = await AppService.CloseApp()
if (!success) toast.error(message)
}
});
return () => {
Events.Off("download:server");
@@ -75,6 +77,7 @@ export function useGlobalEvents({
Events.Off("diff:message");
Events.Off("diff:stage");
Events.Off("version:check");
Events.Off("window:close");
};
}, []);
}

View File

@@ -14,7 +14,7 @@ export default function AboutPage() {
I created a lightweight and modern <span className="font-semibold text-success">Game Launcher</span> to help users easily launch and manage their games with better performance and simplicity.
</p>
<p className="text-lg leading-relaxed">
The launcher is built using <span className="font-mono text-info">Go + Wails3</span>, with a clean and responsive interface styled with <span className="text-warning">Tailwind CSS</span> and <span className="text-warning">DaisyUI</span>.
The launcher is built using <span className="font-mono text-info">Go + Wails3</span>, with a clean and responsive interface styled with <span className="text-accent">Tailwind CSS</span> and <span className="text-accent">DaisyUI</span>.
</p>
<p className="text-lg leading-relaxed">
My goal is to make tools that are fast, efficient, and enjoyable to use and this launcher is just the beginning.

View File

@@ -64,7 +64,7 @@ export default function AnalysisPage() {
</div>
<a
href="https://sranalysis.kain.id.vn/"
className="link link-warning font-mono text-sm break-all"
className="link link-accent font-mono text-sm break-all"
target="_blank"
rel="noopener noreferrer"
>
@@ -79,7 +79,7 @@ export default function AnalysisPage() {
</div>
<a
href="https://firefly-sranalysis.vercel.app/"
className="link link-warning font-mono text-sm break-all"
className="link link-accent font-mono text-sm break-all"
target="_blank"
rel="noopener noreferrer"
>

View File

@@ -309,7 +309,7 @@ export default function DiffPage() {
<div className="w-full p-4">
<div className="space-y-3">
<div className="flex justify-center items-center text-sm text-white/80">
<span className="font-bold text-lg text-warning">{stageType}:</span>
<span className="font-bold text-lg text-accent">{stageType}:</span>
<div className="flex items-center gap-4 ml-2">
{stageType !== 'Cut Data' && <span className="text-white font-bold">{progressUpdate.toFixed(0)} / {maxProgressUpdate.toFixed(0)}</span>}
{stageType === 'Cut Data' && <span className="text-white font-bold truncate max-w-full overflow-hidden whitespace-nowrap">{messageUpdate}</span>}

View File

@@ -1,28 +1,28 @@
import { Link } from "@tanstack/react-router";
export default function FireflyToolsPage() {
return (
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
<div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
<h1 className="text-4xl font-bold text-primary text-center">Firefly Tools</h1>
return (
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
<div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
<h1 className="text-4xl font-bold text-primary text-center">Firefly Tools</h1>
{/* Section 1: About SR Tools */}
<div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4">
<span></span>
<span>About Firefly Tools</span>
</h2>
<div className="space-y-3 text-blue-700">
<div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">🏠</div>
<p>
This site is a another version of {" "}
<span className="font-semibold text-success">Firefly Tools {" "}</span>
developed by {" "}
<span className="font-semibold text-warning">Me {"(Kain)"}</span>
</p>
</div>
<div className="grid md:grid-cols-2 gap-4">
{/* Section 1: About SR Tools */}
<div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4">
<span></span>
<span>About Firefly Tools</span>
</h2>
<div className="space-y-3 text-blue-700">
<div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">🏠</div>
<p>
This site is a another version of {" "}
<span className="font-semibold text-success">Firefly Tools {" "}</span>
developed by {" "}
<span className="font-semibold text-accent">Me {"(Kain)"}</span>
</p>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div className="bg-white border border-blue-200 rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<span className="text-blue-600 text-lg">🏆</span>
@@ -30,7 +30,7 @@ export default function FireflyToolsPage() {
</div>
<a
href="https://srtools.kain.id.vn/"
className="link link-warning font-mono text-sm break-all"
className="link link-accent font-mono text-sm break-all"
target="_blank"
rel="noopener noreferrer"
>
@@ -45,7 +45,7 @@ export default function FireflyToolsPage() {
</div>
<a
href="https://firefly-srtools.vercel.app/"
className="link link-warning font-mono text-sm break-all"
className="link link-accent font-mono text-sm break-all"
target="_blank"
rel="noopener noreferrer"
>
@@ -53,106 +53,106 @@ export default function FireflyToolsPage() {
</a>
</div>
</div>
<div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">👨💻</div>
<p>The original tool was created by a third-party developer named <span className="font-semibold text-warning">Amazing</span>. This version is directly based on that work, without modification to core logic.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">🔗</div>
<p>There is also a more modern version by the same author available at{" "}
<a
href="https://srtools.neonteam.dev/"
className="link link-warning"
target="_blank"
rel="noopener noreferrer"
>
srtools.neonteam.dev
</a>
{" "}and the original version at{" "}
<a
href="https://srtools.pages.dev/"
className="link link-warning"
target="_blank"
rel="noopener noreferrer"
>
srtools.pages.dev
</a>
</p>
</div>
</div>
</div>
<div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">👨💻</div>
<p>The original tool was created by a third-party developer named <span className="font-semibold text-accent">Amazing</span>. This version is directly based on that work, without modification to core logic.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">🔗</div>
<p>There is also a more modern version by the same author available at{" "}
<a
href="https://srtools.neonteam.dev/"
className="link link-accent"
target="_blank"
rel="noopener noreferrer"
>
srtools.neonteam.dev
</a>
{" "}and the original version at{" "}
<a
href="https://srtools.pages.dev/"
className="link link-accent"
target="_blank"
rel="noopener noreferrer"
>
srtools.pages.dev
</a>
</p>
</div>
</div>
</div>
{/* Section 2: Main Features */}
<div className="bg-green-50 border-l-4 border-green-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-green-800 flex items-center gap-2 mb-4">
<span>🔧</span>
<span>Main Features</span>
</h2>
<div className="space-y-3 text-green-700">
<div className="flex items-start gap-3">
<div className="text-green-600 text-lg"></div>
<p>Configure characters, light cones, relics, traces, and eidolons easily in your browser.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-green-600 text-lg">🔌</div>
<p>Instantly apply setups to <span className="font-semibold text-warning">Firefly GO Server</span> using <span className="font-semibold">Connect PS</span> no manual file uploads required.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-green-600 text-2xl"></div>
<div>
<h4 className="font-semibold text-green-800 text-lg">Extra Settings</h4>
<p className="text-green-700 mt-1">
Enhance your <span className="font-semibold text-warning">Firefly GO Server</span> experience with extra features:
</p>
<ul className="list-disc list-inside mt-2 space-y-1 text-green-700">
<li>🎭 <span className="font-medium">Hidden Game UI</span> remove the entire game interface.</li>
<li>🚫 <span className="font-medium">Disable Censorship</span> get rid of Lens Flare censor 💀.</li>
<li>🧪 <span className="font-medium">Theorycraft Mode</span> configure HP, cycles, and more via the web.</li>
</ul>
</div>
</div>
{/* Section 2: Main Features */}
<div className="bg-green-50 border-l-4 border-green-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-green-800 flex items-center gap-2 mb-4">
<span>🔧</span>
<span>Main Features</span>
</h2>
<div className="space-y-3 text-green-700">
<div className="flex items-start gap-3">
<div className="text-green-600 text-lg"></div>
<p>Configure characters, light cones, relics, traces, and eidolons easily in your browser.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-green-600 text-lg">🔌</div>
<p>Instantly apply setups to <span className="font-semibold text-accent">Firefly GO Server</span> using <span className="font-semibold">Connect PS</span> no manual file uploads required.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-green-600 text-2xl"></div>
<div>
<h4 className="font-semibold text-green-800 text-lg">Extra Settings</h4>
<p className="text-green-700 mt-1">
Enhance your <span className="font-semibold text-accent">Firefly GO Server</span> experience with extra features:
</p>
<ul className="list-disc list-inside mt-2 space-y-1 text-green-700">
<li>🎭 <span className="font-medium">Hidden Game UI</span> remove the entire game interface.</li>
<li>🚫 <span className="font-medium">Disable Censorship</span> get rid of Lens Flare censor 💀.</li>
<li>🧪 <span className="font-medium">Theorycraft Mode</span> configure HP, cycles, and more via the web.</li>
</ul>
</div>
</div>
<div className="flex items-start gap-3">
<div className="text-green-600 text-lg">📂</div>
<p>Export and import full builds using <code className="bg-gray-200 px-2 py-1 rounded text-sm">freesr-data.json</code>.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-green-600 text-lg"></div>
<p>Fast testing workflow no sync cooldowns, instant in-game updates.</p>
</div>
</div>
</div>
<div className="flex items-start gap-3">
<div className="text-green-600 text-lg">📂</div>
<p>Export and import full builds using <code className="bg-gray-200 px-2 py-1 rounded text-sm">freesr-data.json</code>.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-green-600 text-lg"></div>
<p>Fast testing workflow no sync cooldowns, instant in-game updates.</p>
</div>
</div>
</div>
{/* Section 3: Getting Started */}
<div className="bg-purple-50 border-l-4 border-purple-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-purple-800 flex items-center gap-2 mb-4">
<span>🚀</span>
<span>Getting Started</span>
</h2>
<div className="space-y-3 text-purple-700">
<div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">1</div>
<p>Access the tool through your browser at the self-hosted instance.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">2</div>
<p>Configure your character builds with the intuitive web interface.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">3</div>
<p>Use <span className="font-semibold">Connect PS</span> feature to instantly sync with your private server.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">4</div>
<p>Test your builds in-game with real-time updates and modifications.</p>
</div>
</div>
</div>
{/* Section 3: Getting Started */}
<div className="bg-purple-50 border-l-4 border-purple-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-purple-800 flex items-center gap-2 mb-4">
<span>🚀</span>
<span>Getting Started</span>
</h2>
<div className="space-y-3 text-purple-700">
<div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">1</div>
<p>Access the tool through your browser at the self-hosted instance.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">2</div>
<p>Configure your character builds with the intuitive web interface.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">3</div>
<p>Use <span className="font-semibold">Connect PS</span> feature to instantly sync with your private server.</p>
</div>
<div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">4</div>
<p>Test your builds in-game with real-time updates and modifications.</p>
</div>
</div>
</div>
<div className="text-center pt-4">
<Link to="/" className="btn btn-primary btn-wide">Back to Home</Link>
</div>
</div>
</div>
);
<div className="text-center pt-4">
<Link to="/" className="btn btn-primary btn-wide">Back to Home</Link>
</div>
</div>
</div>
);
}

View File

@@ -56,7 +56,7 @@ export default function HowToPage() {
<p className="text-blue-700 mb-4">
Below are in-game chat commands you can use. Some commands require you to enable{" "}
<span className="font-semibold text-warning">Theorycraft Mode</span>.
<span className="font-semibold text-accent">Theorycraft Mode</span>.
</p>
{/* Theorycraft Mode Warning */}

View File

@@ -208,10 +208,10 @@ export default function LanguagePage() {
<div className="bg-warning/5 rounded-lg p-2 border border-warning/30">
<div className="flex items-center gap-2 mb-1">
<Mic size={20} className="text-warning" />
<span className="font-bold text-warning">Voice Language</span>
<Mic size={20} className="text-accent" />
<span className="font-bold text-accent">Voice Language</span>
</div>
<p className="text-2xl font-bold text-warning">
<p className="text-2xl font-bold text-accent">
{getLanguageLabel(voiceLang)}
</p>
</div>
@@ -250,7 +250,7 @@ export default function LanguagePage() {
{/* Voice Language */}
<div className="space-y-3">
<label className="flex text-sm font-medium text-warning items-center gap-2">
<label className="flex text-sm font-medium text-accent items-center gap-2">
<Mic size={16} />
Voice Language
</label>

View File

@@ -10,9 +10,13 @@ import useLauncherStore from '@/stores/launcherStore';
import { motion } from 'motion/react';
import { Link } from '@tanstack/react-router';
import { CheckUpdateLauncher, CheckUpdateProxy, CheckUpdateServer, sleep, UpdateLauncher, UpdateProxy, UpdateServer } from '@/helper';
import UpdateModal from '@/components/updateModal';
import { BackgroundSelector } from '@/components/backgroudModal';
export default function LauncherPage() {
const { gamePath,
const {
gamePath,
setGamePath,
setGameDir,
serverPath,
@@ -20,7 +24,9 @@ export default function LauncherPage() {
gameDir,
serverVersion,
proxyVersion,
background
} = useSettingStore()
const {
isOpenDownloadDataModal,
isOpenUpdateDataModal,
@@ -29,6 +35,7 @@ export default function LauncherPage() {
setIsOpenUpdateDataModal,
setIsOpenSelfUpdateModal
} = useModalStore()
const {
isLoading,
downloadType,
@@ -41,7 +48,7 @@ export default function LauncherPage() {
progressDownload,
downloadSpeed,
updateData,
launcherVersion,
// launcherVersion,
setLauncherVersion,
setIsLoading,
setDownloadType,
@@ -54,6 +61,33 @@ export default function LauncherPage() {
setUpdateData,
} = useLauncherStore()
const widgetLinks = [
{
tooltip: "Firefly SRAnalysis",
href: "https://sranalysis.kain.id.vn/",
img: "https://sranalysis.kain.id.vn/ff-sranalysis.png",
btnClass: "btn-primary"
},
{
tooltip: "Firefly SRTools",
href: "https://srtools.kain.id.vn/",
img: "https://srtools.kain.id.vn/ff-srtool.png",
btnClass: "btn-secondary"
},
{
tooltip: "Amazing's SRTools (Original UI)",
href: "https://srtools.pages.dev/",
img: "https://icons.duckduckgo.com/ip3/srtools.pages.dev.ico",
btnClass: "btn-primary"
},
{
tooltip: "Amazing's SRTools (Modern UI)",
href: "https://srtools.neonteam.dev/",
img: "https://icons.duckduckgo.com/ip3/srtools.neonteam.dev.ico",
btnClass: "btn-secondary"
}
]
useEffect(() => {
const check = async () => {
if (!serverVersion || !proxyVersion) {
@@ -156,39 +190,46 @@ export default function LauncherPage() {
try {
setIsLoading(true)
if (!proxyRunning && !gamePath.endsWith("launcher.exe")) {
const resultProxy = await FSService.StartWithConsole(proxyPath)
const [resultProxy, error] = await FSService.StartWithConsole(proxyPath)
if (!resultProxy) {
toast.error('Failed to start proxy')
toast.error('Failed to start proxy: ' + error)
return
}
setProxyRunning(true)
}
await sleep(500)
if (!serverRunning) {
const resultServer = await FSService.StartWithConsole(serverPath)
const [resultServer, error] = await FSService.StartWithConsole(serverPath)
if (!resultServer) {
toast.error('Failed to start server')
toast.error('Failed to start server: ' + error)
return
}
setServerRunning(true)
}
await sleep(2000)
await sleep(1000)
const gameFolder = await FSService.GetDir(gamePath)
const fileNeedRemove = await FSService.Join(gameFolder, "StarRail_Data", "Plugins", "x86_64", "AccountPlatNative.dll")
const fileExists = await FSService.FileExists(fileNeedRemove)
if (fileExists) {
await FSService.RemoveFile(fileNeedRemove)
}
if (gamePath.endsWith("launcher.exe")) {
const resultGame = await FSService.StartWithConsole(gamePath)
const [resultGame, error] = await FSService.StartWithConsole(gamePath)
if (!resultGame) {
toast.error('Failed to start game')
toast.error('Failed to start game: ' + error)
return
}
} else {
const resultGame = await FSService.StartApp(gamePath)
const [resultGame, error] = await FSService.StartApp(gamePath)
if (!resultGame) {
toast.error('Failed to start game')
toast.error('Failed to start game: ' + error)
return
}
}
setGameRunning(true)
} catch (err: any) {
console.log(err)
toast.error('StartGame error:', err)
} finally {
setIsLoading(false)
@@ -200,18 +241,18 @@ export default function LauncherPage() {
setIsDownloading(true)
if (updateData.launcher.isUpdate) {
await UpdateLauncher(updateData.launcher.version)
setUpdateData({...updateData, launcher: { isUpdate: false, isExists: true, version: updateData.launcher.version }})
setUpdateData({ ...updateData, launcher: { isUpdate: false, isExists: true, version: updateData.launcher.version } })
setIsOpenSelfUpdateModal(true)
}
if (updateData.server.isUpdate || !updateData.server.isExists) {
await UpdateServer(updateData.server.version)
setServerReady(true)
setUpdateData({...updateData, server: { isUpdate: false, isExists: true, version: updateData.server.version }})
setUpdateData({ ...updateData, server: { isUpdate: false, isExists: true, version: updateData.server.version } })
}
if (updateData.proxy.isUpdate || !updateData.proxy.isExists) {
await UpdateProxy(updateData.proxy.version)
setProxyReady(true)
setUpdateData({...updateData, proxy: { isUpdate: false, isExists: true, version: updateData.proxy.version }})
setUpdateData({ ...updateData, proxy: { isUpdate: false, isExists: true, version: updateData.proxy.version } })
}
setDownloadType("")
@@ -235,78 +276,39 @@ export default function LauncherPage() {
return (
<div className="relative min-h-fit overflow-hidden">
<div
className="fixed inset-0 z-0 w-full h-full"
style={{
backgroundImage: "url('/bg.jpg')",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat"
<img
src={background}
alt="background"
className="fixed inset-0 w-full h-full object-cover z-0"
onError={(e) => {
const target = e.currentTarget as HTMLImageElement
target.src = "bg-12.jpg"
}}
></div>
/>
{/* Header */}
<header className="hidden sm:flex fixed z-10 items-center justify-between p-6">
<div className="text-2xl font-bold text-white">Firefly GO</div>
<header className="hidden sm:flex fixed z-10 items-center justify-between py-6 px-4 ">
<div className="text-2xl font-bold text-white bg-gray-500/5 rounded-full p-1">Firefly GO</div>
</header>
<div className="hidden sm:flex fixed top-1/4 left-4 z-10 flex-col space-y-3 bg-black/30 backdrop-blur-md rounded-xl p-3 shadow-lg">
<div className="hidden sm:flex fixed top-1/4 right-4 z-10 flex-col space-y-3 bg-white/5 rounded-xl p-3 shadow-lg">
{widgetLinks.map((link, idx) => (
<div key={idx} className="tooltip tooltip-left" data-tip={link.tooltip}>
<a
className={`btn btn-circle ${link.btnClass}`}
target="_blank"
href={link.href}
>
<img
src={link.img}
alt={link.tooltip}
className="w-8 h-8 rounded-full"
/>
</a>
</div>
))}
<div className="tooltip tooltip-right" data-tip="Firefly SRAnalysis">
<a
className="btn btn-circle btn-primary"
target="_blank"
href="https://sranalysis.kain.id.vn/"
>
<img
src="https://sranalysis.kain.id.vn/ff-sranalysis.png"
alt="SRAnalysis Logo"
className="w-8 h-8 rounded-full"
/>
</a>
</div>
<div className="tooltip tooltip-right" data-tip="Firefly SRTools">
<a
className="btn btn-circle btn-secondary"
target="_blank"
href="https://srtools.kain.id.vn/"
>
<img
src="https://srtools.kain.id.vn/ff-srtool.png"
alt="SRTools Logo"
className="w-8 h-8 rounded-full"
/>
</a>
</div>
<div className="tooltip tooltip-right" data-tip="Amazing's SRTools (Original UI)">
<a
className="btn btn-circle btn-primary"
target="_blank"
href="https://srtools.pages.dev/"
>
<img
src="https://icons.duckduckgo.com/ip3/srtools.pages.dev.ico"
alt="SRTools Logo"
className="w-8 h-8 rounded-full"
/>
</a>
</div>
<div className="tooltip tooltip-right" data-tip="Amazing's SRTools (Modern UI)">
<a
className="btn btn-circle btn-secondary"
target="_blank"
href="https://srtools.neonteam.dev/"
>
<img
src="https://icons.duckduckgo.com/ip3/srtools.neonteam.dev.ico"
alt="SRTools Logo"
className="w-8 h-8 rounded-full"
/>
</a>
</div>
<div className="tooltip tooltip-right" data-tip="How to use all tools & commands">
<div className="tooltip tooltip-left" data-tip="How to use all tools & commands">
<Link
to="/howto"
className="btn btn-warning btn-circle"
@@ -316,6 +318,9 @@ export default function LauncherPage() {
</div>
</div>
<div className="hidden sm:flex fixed top-1/2 left-5 z-10 ">
<BackgroundSelector />
</div>
{/* Bottom Panel */}
{serverReady && proxyReady && !isDownloading && (
@@ -345,32 +350,33 @@ export default function LauncherPage() {
</div>
<ul tabIndex={0} className="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm">
<li><button onClick={handlePickFile}>Change Game Path</button></li>
<li><button
onClick={async () => {
const serverData = await CheckUpdateServer(serverPath, serverVersion)
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
const launcherData = await CheckUpdateLauncher()
setUpdateData({
server: serverData,
proxy: proxyData,
launcher: launcherData
})
if (launcherData.isUpdate) {
setIsOpenSelfUpdateModal(true)
return
}
if (!serverData.isExists || !proxyData.isExists) {
setIsOpenDownloadDataModal(true)
return
}
if (serverData.isUpdate || proxyData.isUpdate) {
setIsOpenUpdateDataModal(true)
return
}
toast.success("No updates available")
}}>
Check for Updates
</button></li>
<li>
<button
onClick={async () => {
const serverData = await CheckUpdateServer(serverPath, serverVersion)
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
setUpdateData({
server: serverData,
proxy: proxyData,
launcher: updateData.launcher
})
if (!serverData.isExists || !proxyData.isExists) {
setIsOpenDownloadDataModal(true)
return
}
if (serverData.isUpdate || proxyData.isUpdate) {
setIsOpenUpdateDataModal(true)
return
}
toast.success("No updates available")
}}>
Check for Updates Server & Proxy
</button>
</li>
<li>
</li>
<li><button disabled={!serverPath} onClick={() => {
if (serverPath) {
FSService.OpenFolder("./server")
@@ -400,29 +406,30 @@ export default function LauncherPage() {
|| !updateData.proxy.isExists
|| !updateData.server.isExists
) && (
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-10 w-[60vw] bg-black/20 backdrop-blur-sm rounded-lg p-4 shadow-lg">
<div className="space-y-3">
<div className="flex justify-center items-center text-sm text-white/80">
<span>{downloadType}</span>
<div className="flex items-center gap-4 ml-4">
<span className="text-cyan-400 font-semibold">{downloadSpeed}</span>
<span className="text-white font-bold">{progressDownload.toFixed(1)}%</span>
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-10 w-[60vw] bg-black/20 backdrop-blur-sm rounded-lg p-4 shadow-lg">
<div className="space-y-3">
<div className="flex justify-center items-center text-sm text-white/80">
<span>{downloadType}</span>
<div className="flex items-center gap-4 ml-4">
<span className="text-cyan-400 font-semibold">{downloadSpeed}</span>
<span className="text-white font-bold">{progressDownload.toFixed(1)}%</span>
</div>
</div>
<div className="w-full bg-white/20 rounded-full h-2 overflow-hidden">
<motion.div
className="h-full bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full"
initial={{ width: 0 }}
animate={{ width: `${progressDownload}%` }}
transition={{ type: "tween", ease: "linear", duration: 0.03 }}
/>
</div>
<div className="text-center text-xs text-white/60">
{progressDownload < 100 ? 'Please wait...' : 'Complete!'}
</div>
</div>
<div className="w-full bg-white/20 rounded-full h-2 overflow-hidden">
<motion.div
className="h-full bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full"
initial={{ width: 0 }}
animate={{ width: `${progressDownload}%` }}
transition={{ type: "tween", ease: "linear", duration: 0.03 }}
/>
</div>
<div className="text-center text-xs text-white/60">
{progressDownload < 100 ? 'Please wait...' : 'Complete!'}
</div>
</div>
</div>
)}
)}
{isDownloading && updateData.launcher.isUpdate && (
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-10 w-[60vw] bg-black/20 backdrop-blur-sm rounded-lg p-4 shadow-lg">
<div className="space-y-3 text-sm text-white/80 text-center">
@@ -430,10 +437,10 @@ export default function LauncherPage() {
<div className="flex justify-center items-center gap-4 ml-4">
<span
className={`font-bold ${downloadType === "update:launcher:downloading"
? "text-yellow-200 text-2xl"
: downloadType === "update:launcher:success"
? "text-emerald-200 text-xl"
: "text-red-200 text-xl"
? "text-yellow-200 text-2xl"
: downloadType === "update:launcher:success"
? "text-emerald-200 text-xl"
: "text-red-200 text-xl"
}`}
>
{downloadType === "update:launcher:downloading" && "Updating launcher"}
@@ -466,151 +473,44 @@ export default function LauncherPage() {
</div>
)}
{/* Version Info */}
{serverReady && proxyReady && !isDownloading && (
<div className="hidden md:block fixed bottom-4 left-4 z-10 text-sm font-bold bg-black/20 backdrop-blur-sm rounded-lg p-2 shadow">
<p className="text-primary">Version server: {serverVersion}</p>
<p className="mt-2 text-secondary">Version proxy: {proxyVersion}</p>
<p className="mt-2 text-success">Version launcher: {launcherVersion}</p>
</div>
)}
<div className="hidden md:block fixed bottom-4 left-5 z-10">
<img src="/heart-hsr.gif" alt="firefly animation" className="rounded-lg w-24 h-24" />
</div>
{/* Modal */}
{isOpenUpdateDataModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
<motion.button
whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }}
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
onClick={() => setIsOpenUpdateDataModal(false)}
>
</motion.button>
<UpdateModal
isOpen={isOpenUpdateDataModal}
onClose={() => setIsOpenUpdateDataModal(false)}
title="Update Data"
message="Do you want to update data server and proxy?"
buttons={[
{ text: "No", onClick: () => setIsOpenUpdateDataModal(false), variant: "outline" },
{ text: "Yes", onClick: async () => { setIsOpenUpdateDataModal(false); await handlerUpdateData() }, variant: "primary" }
]}
/>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
Update Data
</h3>
</div>
<UpdateModal
isOpen={isOpenDownloadDataModal}
onClose={() => setIsOpenDownloadDataModal(false)}
title="Download Data"
message="Data server and proxy download required"
buttons={[
{ text: "Download", onClick: async () => { setIsOpenDownloadDataModal(false); await handlerUpdateData() }, variant: "primary" }
]}
/>
<div className="px-6 pb-6">
<div className="mb-6">
<p className="text-warning text-lg">
Do you want to update data server and proxy?
</p>
</div>
<UpdateModal
isOpen={isOpenSelfUpdateModal}
onClose={() => setIsOpenSelfUpdateModal(false)}
title="Update Launcher"
message="Do you want to update launcher?"
buttons={[
{ text: "No", onClick: () => setIsOpenSelfUpdateModal(false), variant: "outline" },
{ text: "Yes", onClick: async () => { setIsOpenSelfUpdateModal(false); await handlerUpdateData() }, variant: "primary" }
]}
/>
<div className="flex justify-end gap-3">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="btn btn-outline btn-error"
onClick={() => setIsOpenUpdateDataModal(false)}
>
No
</motion.button>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
onClick={async () => {
setIsOpenUpdateDataModal(false)
await handlerUpdateData()
}}
>
Yes
</motion.button>
</div>
</div>
</div>
</div>
)}
{isOpenDownloadDataModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
Download Data
</h3>
</div>
<div className="px-6 pb-6">
<div className="mb-6">
<p className="text-warning text-lg">
Data server and proxy download required
</p>
</div>
<div className="flex justify-end gap-3">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
onClick={async () => {
setIsOpenDownloadDataModal(false)
await handlerUpdateData()
}}
>
Download
</motion.button>
</div>
</div>
</div>
</div>
)}
{isOpenSelfUpdateModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
<motion.button
whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }}
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
onClick={() => setIsOpenSelfUpdateModal(false)}
>
</motion.button>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
Update Launcher
</h3>
</div>
<div className="px-6 pb-6">
<div className="mb-6">
<p className="text-warning text-lg">
Do you want to update launcher?
</p>
</div>
<div className="flex justify-end gap-3">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="btn btn-outline btn-error"
onClick={() => setIsOpenSelfUpdateModal(false)}
>
No
</motion.button>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
onClick={async () => {
setIsOpenSelfUpdateModal(false)
await handlerUpdateData()
}}
>
Yes
</motion.button>
</div>
</div>
</div>
</div>
)}
</div>
)
}

View File

@@ -1,107 +1,31 @@
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import ThemeController from '../components/themeController'
import { createRootRoute, Outlet } from '@tanstack/react-router'
import { ToastContainer } from 'react-toastify'
import { useGlobalEvents } from '@/hooks';
import useLauncherStore from '@/stores/launcherStore';
import useDiffStore from '@/stores/diffStore';
import useModalStore from '@/stores/modalStore';;
import SettingModal from '@/components/settingModal';
import CloseModal from '@/components/closeModal';
import Header from '@/components/header';
export const Route = createRootRoute({
component: RootLayout
})
function RootLayout() {
const { setGameRunning, setServerRunning, setProxyRunning, setProgressDownload, setDownloadSpeed } = useLauncherStore()
const { setProgressUpdate, setMaxProgressUpdate, setMessageUpdate, setStageType } = useDiffStore()
useGlobalEvents({
setGameRunning,
setServerRunning,
setProxyRunning,
setProgressUpdate,
setMaxProgressUpdate,
setProgressDownload,
setDownloadSpeed,
setMessageUpdate,
setStageType
});
const { setIsOpenCloseModal, isOpenCloseModal, isOpenSettingModal, setIsOpenSettingModal } = useModalStore()
useGlobalEvents();
return (
<>
<div className="navbar bg-base-100 shadow-sm sticky top-0 z-50 px-3">
<div className="navbar-start">
<div className="dropdown">
<div tabIndex={0} role="button" className="btn btn-ghost md:hidden">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h8m-8 6h16" /> </svg>
</div>
<ul
tabIndex={0}
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow">
<li><Link to="/">Home</Link></li>
<Header />
<li>
<a>Tools</a>
<ul className="p-2">
<li><Link to="/language">Language</Link></li>
<li><Link to="/diff">Diff</Link></li>
</ul>
</li>
<li>
<a>Plugins</a>
<ul className="p-2">
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
<li><Link to="/srtools">SrTools</Link></li>
</ul>
</li>
<li><Link to="/howto">How to?</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
</div>
<Link to="/" className="grid grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
<div className="flex items-center justify-center">
<img src="/ff-launcher.png" alt="Logo" className='w-13 h-13' />
<div className="flex flex-col justify-center items-start">
<h1 className="text-xl font-bold">
<span className="text-emerald-500">Firefly </span>
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500">
Launcher
</span>
</h1>
<p className="text-sm text-gray-500">By Kain</p>
</div>
</div>
</Link>
</div>
<div className="navbar-center hidden md:flex">
<ul className="menu menu-horizontal px-1 gap-4">
<li><Link to="/">Home</Link></li>
<li>
<details>
<summary>Tools</summary>
<ul className="p-2">
<li><Link to="/language">Language</Link></li>
<li><Link to="/diff">Diff</Link></li>
</ul>
</details>
</li>
<li>
<details>
<summary>Plugins</summary>
<ul className="p-2">
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
<li><Link to="/srtools">Firefly Tools</Link></li>
</ul>
</details>
</li>
<li><Link to="/howto">How to?</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
</div>
<div className="navbar-end">
<ThemeController />
</div>
</div>
<div className="min-h-[78vh]">
<Outlet />
</div>
<CloseModal isOpen={isOpenCloseModal} onClose={() => setIsOpenCloseModal(false)} />
<SettingModal isOpen={isOpenSettingModal} onClose={() => setIsOpenSettingModal(false)} />
<ToastContainer />
</>
)

View File

@@ -5,18 +5,26 @@ interface ModalState {
isOpenDownloadDataModal: boolean;
isOpenUpdateDataModal: boolean;
isOpenSelfUpdateModal: boolean;
isOpenCloseModal: boolean;
isOpenSettingModal: boolean;
setIsOpenDownloadDataModal: (modal: boolean) => void;
setIsOpenUpdateDataModal: (modal: boolean) => void;
setIsOpenSelfUpdateModal: (modal: boolean) => void;
setIsOpenCloseModal: (modal: boolean) => void;
setIsOpenSettingModal: (modal: boolean) => void;
}
const useModalStore = create<ModalState>((set, get) => ({
isOpenDownloadDataModal: false,
isOpenUpdateDataModal: false,
isOpenSelfUpdateModal: false,
isOpenCloseModal: false,
isOpenSettingModal: false,
setIsOpenDownloadDataModal: (modal: boolean) => set({ isOpenDownloadDataModal: modal }),
setIsOpenUpdateDataModal: (modal: boolean) => set({ isOpenUpdateDataModal: modal }),
setIsOpenSelfUpdateModal: (modal: boolean) => set({ isOpenSelfUpdateModal: modal }),
setIsOpenCloseModal: (modal: boolean) => set({ isOpenCloseModal: modal }),
setIsOpenSettingModal: (modal: boolean) => set({ isOpenSettingModal: modal }),
}));
export default useModalStore;

View File

@@ -10,6 +10,15 @@ interface SettingState {
proxyPath: string;
serverVersion: string;
proxyVersion: string;
closingOption: {
isMinimize: boolean;
isAsk: boolean;
}
background: string;
extraBackgrounds: string[];
setExtraBackgrounds: (newExtraBackgrounds: string[]) => void;
setBackground: (newBackground: string) => void;
setClosingOption: (newClosingOption: { isMinimize: boolean; isAsk: boolean }) => void;
setLocale: (newLocale: string) => void;
setGamePath: (newGamePath: string) => void;
setGameDir: (newGameDir: string) => void;
@@ -29,6 +38,15 @@ const useSettingStore = create<SettingState>()(
proxyPath: "",
serverVersion: "",
proxyVersion: "",
closingOption: {
isMinimize: false,
isAsk: true,
},
background: "bg-12.jpg",
extraBackgrounds: [],
setExtraBackgrounds: (newExtraBackgrounds: string[]) => set({ extraBackgrounds: newExtraBackgrounds }),
setBackground: (newBackground: string) => set({ background: newBackground }),
setClosingOption: (newClosingOption: { isMinimize: boolean; isAsk: boolean }) => set({ closingOption: newClosingOption }),
setLocale: (newLocale: string) => set({ locale: newLocale }),
setGamePath: (newGamePath: string) => set({ gamePath: newGamePath }),
setGameDir: (newGameDir: string) => set({ gameDir: newGameDir }),

View File

@@ -1,5 +1,4 @@
@import "tailwindcss";
@plugin "daisyui";
@plugin "daisyui" {
themes: night --default, night --prefersdark, cupcake;
@plugin "daisyui"{
themes: dracula --default;
}

View File

@@ -0,0 +1,25 @@
export default function getCroppedImg(imageSrc: string, crop: any): Promise<string> {
const image = new Image()
image.src = imageSrc
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')!
canvas.width = crop.width
canvas.height = crop.height
return new Promise((resolve) => {
image.onload = () => {
ctx.drawImage(
image,
crop.x,
crop.y,
crop.width,
crop.height,
0,
0,
crop.width,
crop.height
)
resolve(canvas.toDataURL('image/jpeg'))
}
})
}

View File

@@ -8,6 +8,7 @@ export default defineConfig({
base: '/',
build: {
outDir: 'dist',
chunkSizeWarningLimit: 2000,
},
plugins: [
tanstackRouter({

47
go.mod
View File

@@ -3,56 +3,53 @@ module firefly-launcher
go 1.25
require (
github.com/wailsapp/wails/v3 v3.0.0-alpha.26
golang.org/x/sys v0.35.0
github.com/klauspost/compress v1.18.0
github.com/minio/selfupdate v0.6.0
github.com/wailsapp/wails/v3 v3.0.0-alpha.34
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
golang.org/x/sys v0.31.0
google.golang.org/protobuf v1.33.0
)
require (
aead.dev/minisign v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/text v0.28.0 // indirect
)
require (
dario.cat/mergo v1.0.2 // indirect
aead.dev/minisign v0.2.0 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/adrg/xdg v0.5.3 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cloudflare/circl v1.6.0 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.2 // indirect
github.com/go-git/go-git/v5 v5.13.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/compress v1.18.0
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/lmittmann/tint v1.1.2 // indirect
github.com/lmittmann/tint v1.0.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/selfupdate v0.6.0
github.com/pjbgf/sha1cd v0.4.0 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/samber/lo v1.51.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/samber/lo v1.49.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/wailsapp/go-webview2 v1.0.21 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
golang.org/x/net v0.43.0 // indirect
google.golang.org/protobuf v1.36.8
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/text v0.23.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

87
go.sum
View File

@@ -1,13 +1,12 @@
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
aead.dev/minisign v0.3.0 h1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=
aead.dev/minisign v0.3.0/go.mod h1:NLvG3Uoq3skkRMDuc3YHpWUTMTrSExqm+Ij73W13F6Y=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
@@ -16,17 +15,17 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=
github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
@@ -37,8 +36,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
@@ -51,10 +50,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -68,8 +67,8 @@ github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y=
github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
@@ -81,8 +80,8 @@ github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDw
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -92,12 +91,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
@@ -110,23 +109,23 @@ github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2
github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v3 v3.0.0-alpha.26 h1:co3eifrIcG/J7sQ1oMPmLfvtAE4bezdhOtabI4Ua5fg=
github.com/wailsapp/wails/v3 v3.0.0-alpha.26/go.mod h1:UZpnhYuju4saspCJrIHAvC0H5XjtKnqd26FRxJLrQ0M=
github.com/wailsapp/wails/v3 v3.0.0-alpha.34 h1:t6NwHOLJzXuESb3YSXarSd1C/U1h2CpXF+BLr0ekj2g=
github.com/wailsapp/wails/v3 v3.0.0-alpha.34/go.mod h1:UZpnhYuju4saspCJrIHAvC0H5XjtKnqd26FRxJLrQ0M=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -138,19 +137,19 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View File

@@ -20,3 +20,44 @@ func (a *AppService) CloseAppAfterTimeout(timeout int) (bool, string) {
}()
return true, ""
}
func (a *AppService) CloseApp() (bool, string) {
application.Get().Quit()
return true, ""
}
func (a *AppService) HideApp() (bool, string) {
window := application.Get().Window.Current()
if window == nil {
return false, "not found window"
}
window.Hide()
return true, ""
}
func (a *AppService) MinimizeApp() (bool, string) {
window := application.Get().Window.Current()
if window == nil {
return false, "not found window"
}
window.Minimise()
return true, ""
}
func (a *AppService) MaximizeApp() (bool, string) {
window := application.Get().Window.Current()
if window == nil {
return false, "not found window"
}
window.Maximise()
return true, ""
}
func (a *AppService) RestoreApp() (bool, string) {
window := application.Get().Window.Current()
if window == nil {
return false, "not found window"
}
window.Restore()
return true, ""
}

View File

@@ -2,12 +2,12 @@ package fsService
import (
"firefly-launcher/pkg/sevenzip"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/wailsapp/wails/v3/pkg/application"
"golang.org/x/sys/windows"
)
@@ -63,12 +63,25 @@ func (f *FSService) FileExists(path string) bool {
return false
}
func (f *FSService) StartApp(path string) (bool, error) {
func (f *FSService) GetDir(path string) string {
return filepath.Dir(path)
}
func (f *FSService) Join(paths ...string) string {
return filepath.Join(paths...)
}
func (f *FSService) RemoveFile(path string) error {
return os.Remove(path)
}
func (f *FSService) StartApp(path string) (bool, string) {
cmd := exec.Command(path)
err := cmd.Start()
if err != nil {
return false, err
return false, err.Error()
}
if strings.HasSuffix(path, "StarRail.exe") {
go func() {
_ = cmd.Wait()
@@ -76,17 +89,17 @@ func (f *FSService) StartApp(path string) (bool, error) {
}()
}
return true, nil
return true, ""
}
func (f *FSService) StartWithConsole(path string) (bool, error) {
func (f *FSService) StartWithConsole(path string) (bool, string) {
absPath, err := filepath.Abs(path)
if err != nil {
return false, err
return false, err.Error()
}
if _, err := os.Stat(absPath); os.IsNotExist(err) {
return false, fmt.Errorf("file not found: %s", absPath)
return false, "file not found: " + absPath
}
cmd := exec.Command(absPath)
cmd.Dir = filepath.Dir(absPath)
@@ -103,7 +116,7 @@ func (f *FSService) StartWithConsole(path string) (bool, error) {
err = cmd.Start()
if err != nil {
return false, err
return false, err.Error()
}
go func() {
@@ -116,7 +129,7 @@ func (f *FSService) StartWithConsole(path string) (bool, error) {
application.Get().Event.Emit("proxy:exit")
}
}()
return true, nil
return true, ""
}
func (f *FSService) OpenFolder(path string) (bool, string) {
@@ -142,4 +155,3 @@ func (f *FSService) FileExistsInZip(archivePath, fileInside string) (bool, strin
}
return exists, ""
}

32
main.go
View File

@@ -17,6 +17,7 @@ import (
"path/filepath"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
//go:embed all:frontend/dist
@@ -84,7 +85,7 @@ func main() {
})
// Create window
app.Window.NewWithOptions(application.WebviewWindowOptions{
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Firefly Launcher - Kain",
Mac: application.MacWindow{
InvisibleTitleBarHeight: 50,
@@ -92,10 +93,35 @@ func main() {
TitleBar: application.MacTitleBarHiddenInset,
},
BackgroundColour: application.NewRGB(27, 38, 54),
Width: 1200,
Height: 600,
Width: 1280,
Height: 720,
URL: "/",
DevToolsEnabled: true,
Frameless: true,
DisableResize: true,
})
iconBytes, _ := tools.ReadFile("assets/appicon.png")
systemTray := app.SystemTray.New()
systemTray.SetIcon(iconBytes)
systemTray.SetTooltip("Firefly Launcher")
// Attach the window to the system tray
menu := application.NewMenu()
menu.Add("Open").OnClick(func(ctx *application.Context) {
window.Show()
})
menu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
systemTray.SetMenu(menu)
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
app.Event.Emit("window:close")
e.Cancel()
})
err = app.Run()

View File

@@ -10,7 +10,7 @@ const ProxyZipFile = "64bit.zip"
const LauncherFile = "firefly-launcher.exe"
const TempUrl = "./temp"
const CurrentLauncherVersion = "1.9.0"
const CurrentLauncherVersion = "2.3.1"
type ToolFile string

55
proto/firefly.proto Normal file
View File

@@ -0,0 +1,55 @@
syntax = "proto3";
package firefly;
option go_package = "./firefly/pb";
message ChunkProto {
repeated AssetChunkProperty assets = 1;
}
message AssetChunkProperty {
string asset_name = 1;
repeated AssetChunk asset_chunks = 2;
int32 asset_type = 3;
int64 asset_size = 4;
string asset_hash_md5 = 5;
}
message AssetChunk {
string chunk_name = 1;
string chunk_decompressed_hash_md5 = 2;
int64 chunk_on_file_offset = 3;
int64 chunk_size = 4;
int64 chunk_size_decompressed = 5;
}
message ManifestProto {
repeated AssetManifestProperty assets = 1;
}
message AssetManifestProperty {
string asset_name = 1;
int64 asset_size = 2;
string asset_hash_md5 = 3;
AssetManifestChunk asset_data = 4;
}
message AssetManifestChunk {
string latest_asset_version = 1;
repeated AssetManifest assets = 2;
}
message AssetManifest {
string chunk_file_name = 1;
string chunk_file_version = 2;
string chunk_file_node = 3;
int64 chunk_file_size = 4;
string chunk_file_md5 = 5;
int64 hdiff_file_in_chunk_offset = 6;
int64 hdiff_file_size = 7;
string original_file_path = 8;
int64 original_file_size = 9;
string original_file_md5 = 10;
}