125 Commits
gtk-dev ... dev

Author SHA1 Message Date
0f09d23e67 Setup NSIS installer 2025-05-16 13:33:43 -06:00
b858d50746 replaced list with buttons 2025-05-07 07:23:00 -06:00
4ecdd3be6a upload app.svelte 2025-05-06 22:14:23 -06:00
2825b461af working on modpack ui 2025-05-06 22:13:34 -06:00
0f14bc2de7 Admin Page Login 2025-05-06 19:14:16 -06:00
008761e94a starting work on admin backend 2025-05-05 13:36:29 -06:00
2be1341404 maybe fixed auth issue? 2025-05-04 13:10:10 -06:00
3df2030f1e version bump 2025-05-03 19:32:58 -06:00
9a7a317a11 Pointed at gitea.piwalker.net 2025-05-03 19:29:36 -06:00
1d7df8854f New Instance Page Layout 2025-03-14 14:44:38 +11:00
2b21faf626 Merge branch 'golang-dev' of https://gitea.piwalker.net/piwalker/FCLauncher into golang-dev 2025-03-13 20:21:22 -06:00
1acf7db41d updated gitignore 2025-03-13 20:20:41 -06:00
e45d32a4ad wording change 2025-03-12 13:01:06 -06:00
11bda753c6 version 0.0.4 2025-03-11 15:57:54 -06:00
5a4307dc94 added login prompt parameter to oauth login 2025-03-11 15:34:23 -06:00
231b789545 Finished oauth2 2025-03-11 10:20:43 -06:00
a4316fa8de Working on OAuth2 authentication. 2025-03-11 08:28:29 -06:00
ce24c0a55d Created delete instance button, and made minor ui modifications 2025-03-10 11:38:27 -06:00
3b6aa14082 updated gitignore 2024-11-30 08:32:11 -07:00
=
ee19529a42 version 0.0.3 2024-11-26 21:42:06 -07:00
ca4713eb51 Weird build changes 2024-11-27 15:41:43 +11:00
=
9d61ea470a implemented open instance folder button 2024-11-26 21:37:26 -07:00
=
fab46be020 added test link 2024-11-26 21:34:38 -07:00
e8c24ee540 Instances UI 2024-11-27 15:34:12 +11:00
=
86cc464f45 version 0.0.2 2024-11-26 21:02:56 -07:00
ab6ad50983 Merge branch 'golang-dev' of https://gitea.piwalker.net/piwalker/FCLauncher into golang-dev 2024-11-27 14:56:43 +11:00
ec0fdb87c6 Theme Buttons 2024-11-27 14:56:39 +11:00
=
035d59b0f6 Implemented auto updating 2024-11-26 20:54:30 -07:00
a412cbf9d6 Theming works 2024-11-27 14:20:21 +11:00
cf31acdbc0 Settings page baybee 2024-11-27 13:55:43 +11:00
45efbffe7c Fixed nav hover 2024-11-27 13:41:30 +11:00
=
29c2351b2f use unix path for https requests 2024-11-26 16:49:32 -07:00
=
c34652bc4a replace path separators again 2024-11-26 16:45:08 -07:00
=
d5f8881d7e more path issues 2024-11-26 16:29:58 -07:00
=
0c111235a6 all unix baby! 2024-11-26 16:23:54 -07:00
=
377e42a24c linux separator? 2024-11-26 16:21:26 -07:00
=
eef50d5f2c more path debugging 2024-11-26 16:19:46 -07:00
=
3389119d03 fixed path madness 2024-11-26 16:16:13 -07:00
=
cf24d351e3 more debug code 2024-11-26 16:14:02 -07:00
=
49147a4edd debugging lib paths 2024-11-26 16:12:17 -07:00
=
20b8433d34 path separator weridness 2024-11-26 16:09:54 -07:00
=
6b07b95146 debugging maven paths 2024-11-26 16:03:28 -07:00
=
cddb8bb478 fix path splitting issue 2024-11-26 16:00:05 -07:00
40311e1b12 Fix weird prompt to commit 2024-11-27 09:58:20 +11:00
=
aed69860de remove auth file before re-writting 2024-11-26 15:52:29 -07:00
=
dadf5f641b https request fix path separator 2024-11-26 15:49:44 -07:00
=
4a35078f90 more error checking on modpack downloads 2024-11-26 15:37:35 -07:00
=
e7f8de116e Added error checking 2024-11-26 15:31:01 -07:00
=
22c18915f5 Changed url to point to new server 2024-11-26 12:37:30 -07:00
=
69791e5188 open browser for auth 2024-11-26 11:50:33 -07:00
=
d4f9711699 some random ui polish changes 2024-11-26 10:22:37 -07:00
=
ad736787e9 Changed default RAM limit 2024-11-25 19:21:20 -07:00
=
8208434b8f fixed overrides extraction 2024-11-25 14:55:04 -07:00
=
2b372423eb Finalized Modpack installation. 2024-11-25 14:28:32 -07:00
=
f905d617a8 Able to install correct version of minecraft + loader from mrpack 2024-11-25 13:23:06 -07:00
=
ad84711646 Working on modpack installation page 2024-11-25 12:36:29 -07:00
24f32cdba5 Fixed navbar flicker on windows 2024-11-05 11:56:10 +11:00
3e667f004d package json 2024-11-05 11:11:40 +11:00
f3e6f5d925 Fixed icons growing on windows 2024-11-05 11:09:46 +11:00
7e5b9596a4 global theming works 2024-11-04 13:23:47 +11:00
d15158f79c Changed color handling, in testing right now 2024-11-03 14:12:07 +11:00
d9dbd2d29b Replacing old ui, list population needs moved to app 2024-11-02 19:06:56 +11:00
3fe2b3df6d Moving page elements out of the way when the navbar opens 2024-11-02 18:32:24 +11:00
6f6a00cb45 Merge branch 'golang-dev' of https://gitea.piwalker.net/piwalker/FCLauncher into golang-dev 2024-11-02 12:14:08 +11:00
0923913801 Trying to make pages adjust dynamically for navbar 2024-11-02 12:14:04 +11:00
b9d8b763a7 working on forge integration 2024-11-01 18:08:29 -06:00
4946b7e595 Merge branch 'golang-dev' of https://gitea.piwalker.net/piwalker/FCLauncher into golang-dev 2024-11-01 08:05:14 -06:00
620636fb36 fixed reauth 2024-11-01 08:05:12 -06:00
ca9e41e99e Merge branch 'golang-dev' of https://gitea.piwalker.net/piwalker/FCLauncher into golang-dev 2024-11-02 01:04:56 +11:00
129b1d1a19 Added launch text to launch button 2024-11-02 01:04:52 +11:00
59b836b40a change import extension 2024-11-01 07:51:38 -06:00
af6ff50cfd Instance switching in good ui 2024-11-02 00:44:39 +11:00
bd787bdfa2 More ui work 2024-11-02 00:12:40 +11:00
2d8ce26af9 Cool select UI 2024-11-01 23:41:52 +11:00
27c97010ae Made instances list global 2024-11-01 05:55:41 -06:00
9d23d503e2 array stuff in testpage 2024-11-01 22:43:18 +11:00
479ee2e6e0 removed logo because it was annoying to work around 2024-11-01 21:55:39 +11:00
7e290472fb Added a test page for constructing new ui 2024-11-01 21:50:35 +11:00
e5cf9f532e Pull forge versions 2024-10-31 21:20:20 -06:00
a614f71aa1 quilt implemented 2024-10-31 18:57:54 -06:00
46bfc92370 Merge branch 'golang-dev' of https://gitea.piwalker.net/piwalker/FCLauncher into golang-dev 2024-10-31 18:21:11 -06:00
f6b68b0b43 working on quilt 2024-10-31 18:21:09 -06:00
6709726709 Merge branch 'golang-dev' of https://gitea.piwalker.net/piwalker/FCLauncher into golang-dev 2024-11-01 11:20:10 +11:00
5745670c0a navbar controls things 2024-11-01 11:20:06 +11:00
cce23ec175 fixed work directory 2024-10-31 17:50:45 -06:00
d7cfdaf6f2 merge 2024-10-31 17:48:17 -06:00
bf4d4ac583 fixed fabric 2024-10-31 17:47:50 -06:00
525af0db42 background color 2024-11-01 10:38:55 +11:00
c1bab40aab Minor layout change 2024-11-01 10:03:42 +11:00
7e728ddff8 working on fabric issue 2024-10-31 16:46:12 -06:00
0f265ced92 Replaced stand-in items in navbar 2024-11-01 09:44:41 +11:00
859bf812cd fabric installing, sorta... 2024-10-31 16:23:38 -06:00
d0384b1778 merge 2024-10-31 14:57:12 -06:00
3951af01c9 fabric version selection 2024-10-31 14:55:58 -06:00
6179e669df logo color 2024-11-01 01:20:11 +11:00
a96dfa032d fixed text wrapping 2024-11-01 01:09:38 +11:00
58a7a472fc Change navbar text 2024-11-01 01:05:55 +11:00
189e3e438f implemented navbar 2024-11-01 01:00:46 +11:00
ca21acf6b1 Started Navbar 2024-10-31 13:38:56 +11:00
25426e749b updated gitignor 2024-10-30 19:35:29 -06:00
e36eabe5df merge 2024-10-30 19:34:21 -06:00
e71593bf3a updated gitignore 2024-10-30 19:33:50 -06:00
10d373fd4b Merge branch 'golang-dev' of https://gitea.piwalker.net/piwalker/FCLauncher into golang-dev 2024-10-31 12:32:31 +11:00
dec56ffcb2 added Navbar svelte 2024-10-31 12:29:26 +11:00
774b623f1d random tweaks 2024-10-30 19:11:24 -06:00
7c169ce4a7 finished auth 2024-10-30 17:16:10 -06:00
806f6e2dbf initial microsoft auth 2024-10-30 16:41:03 -06:00
3720f1e524 added logging config 2024-10-30 15:58:05 -06:00
2bbb4b8cf0 installation and launching of vanilla minecraft working 2024-10-30 15:39:11 -06:00
ab0bfebe87 Started work on downloading minecraft 2024-10-29 21:48:59 -06:00
98e03a3e60 added basic slide transition 2024-10-26 23:39:21 -06:00
4e6ea6f22b fixed windows issues 2024-10-26 22:33:33 -06:00
99a5934575 working on windows version 2024-10-26 19:27:39 -06:00
b6af1549a3 fixed application not closing 2024-10-26 19:10:26 -06:00
bce2b04d3e almost have launching working 2024-10-26 18:14:12 -06:00
4bcc2703d6 Working on division between modpack and instance 2024-10-26 14:00:53 -06:00
db9ba30442 Fixed output name of modpack files 2024-10-25 22:58:52 -06:00
3c8fb9a89f added basic support for downloading (not installing) modpack files 2024-10-25 22:56:22 -06:00
29bf715025 removed some frontend testing code 2024-10-25 21:43:19 -06:00
8efd3a5631 Added support for installing java versions 2024-10-25 20:46:06 -06:00
bca9bec6d6 install prism, and display status in ui 2024-10-24 19:48:19 -06:00
7ad769ff96 Started https download code 2024-10-24 17:05:47 -06:00
0a6b37ccab fixed formatting 2024-10-24 15:49:14 -06:00
3b89a26265 got svelte working 2024-10-24 15:46:17 -06:00
c646c36a06 built wails project 2024-10-24 13:31:01 -06:00
82 changed files with 7442 additions and 69 deletions

8
.gitignore vendored
View File

@ -3,6 +3,8 @@
# will have compiled files and executables # will have compiled files and executables
debug/ debug/
target/ target/
logs/
**/logs/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
@ -10,4 +12,10 @@ Cargo.lock
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
**/*.log
FCLauncher/build/bin
FCLauncher/node_modules
FCLauncher/frontend/dist
FCLauncher/frontend/wailsjs/go/

View File

@ -1,60 +0,0 @@
{
"build": {
"devPath": "../src",
"distDir": "../src",
"withGlobalTauri": true
},
"package": {
"productName": "FCLauncher",
"version": "1.0.5"
},
"tauri": {
"updater": {
"active": true,
"endpoints": [
"https://gitea.piwalker.net/fclauncher/app.json"
],
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDNDOEUwMjYxRUU2NEI5RgpSV1NmUytZZUp1RElBN3dEaGhpWG9JZVNQcFlnNFFzaXN0UnBsVmxNeVdWWnJoQmh4cGJRbjN3Ygo="
},
"allowlist": {
"all": false,
"shell": {
"all": false,
"open": true
},
"dialog": {
"all": false,
"ask": true,
"open": true,
"message": true
},
"process": {
"all": true
}
},
"windows": [
{
"title": "FCLauncher",
"width": 800,
"height": 600,
"resizable": false
}
],
"security": {
"csp": null
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "net.piwalker",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
}

4
fclauncher/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
build/bin
node_modules
frontend/dist
frontend/wailsjs/go/

34
fclauncher/2 Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"context"
"os"
"path/filepath"
)
type Instance struct {
InstanceName string
ModpackId string
ModpackVersion string
}
type InstanceManager struct {
instances []Instance
PrismLauncher Prism
ctx context.Context
}
func (i *InstanceManager)SearchInstances() {
i.instances = []Instance{}
dir := i.PrismLauncher.GetInstanceDir()
subdirs, _ := os.ReadDir(dir)
for _, d := range subdirs {
if !d.IsDir() {
continue
}
if _, err := os.Stat(filepath.Join(dir, d.Name(), "instance.json")); err != nil {
continue
}
f, _ = os.OpenFile()
}
}

44
fclauncher/Admin.go Normal file
View File

@ -0,0 +1,44 @@
package main
import (
"fmt"
"golang.org/x/crypto/ssh"
)
type Admin struct {
conf *ssh.ClientConfig
}
func sftpConnect(conf *ssh.ClientConfig) (ssh.Conn, error) {
return ssh.Dial("tcp", "gitea-svr.piwalker.net:22", conf)
}
func sftpAuth(username string, password string) (*ssh.ClientConfig, error) {
key, _, _, _, err := ssh.ParseAuthorizedKey([]byte("gitea-svr.piwalker.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILkyh7MDvubWw4OzTFbvsUz7gOmOzBq77i5Q86STKqja"));
config := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
HostKeyCallback: ssh.FixedHostKey(key),
}
conn, err := sftpConnect(config)
if err != nil {
return nil, err
}
conn.Close()
return config, nil
}
func (a *Admin) AdminAuth(username string, password string) bool {
conf, err := sftpAuth(username, password)
if err != nil {
fmt.Println("SFTP error: ", err)
a.conf = nil
return false
}
a.conf = conf
return true
}

View File

@ -1,6 +0,0 @@
[package]
name = "fclauncher"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@ -0,0 +1,624 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
"github.com/zhyee/zipstream"
)
type MrData struct {
FormatVersion int
Game string
VersionId string
Name string
Summary string
Files []MrFile
Dependencies map[string]string
}
type MrFile struct {
Path string
Hashes map[string]string
Env map[string]string
Downloads []string
FileSize int
}
type Instance struct {
InstanceName string
ModpackId string
ModpackVersion string
MinecraftVersion string
ForgeVersion string
NeoForgeVersion string
FabricVersion string
QuiltVersion string
JavaVersion int
Libraries []string
MainClass string
}
type InstanceManager struct {
instances []Instance
app *App
}
type mmcpack struct {
Components []component
}
type component struct {
Uid string
Version string
}
func (i *InstanceManager) SearchInstances() {
i.instances = []Instance{}
dir, _ := os.UserConfigDir()
dir = filepath.Join(dir, "FCLauncher", "instances")
if _, err := os.Stat(dir); err != nil {
return
}
subdirs, _ := os.ReadDir(dir)
for _, d := range subdirs {
if !d.IsDir() {
continue
}
if _, err := os.Stat(filepath.Join(dir, d.Name(), "instance.json")); err != nil {
continue
}
f, _ := os.OpenFile(filepath.Join(dir, d.Name(), "instance.json"), os.O_RDONLY, 0755)
defer f.Close()
buff := new(bytes.Buffer)
io.Copy(buff, f)
instance := Instance{}
json.Unmarshal(buff.Bytes(), &instance)
i.instances = append(i.instances, instance)
}
}
func (i *InstanceManager) checkJavaVersion(instance Instance) {
infoPath := filepath.Join(i.app.PrismLauncher.GetInstanceDir(), instance.InstanceName, "mmc-pack.json")
f, _ := os.OpenFile(infoPath, os.O_RDONLY, 0755)
defer f.Close()
dataStr, _ := io.ReadAll(f)
var data mmcpack
json.Unmarshal(dataStr, &data)
mc_version := "0.0"
for _, comp := range data.Components {
if comp.Uid == "net.minecraft" {
mc_version = comp.Version
break
}
}
fmt.Printf("MC Version: %s", mc_version)
tokensStr := strings.Split(mc_version, ".")
tokens := []int{0, 0, 0}
tokens[0], _ = strconv.Atoi(tokensStr[0])
tokens[1], _ = strconv.Atoi(tokensStr[1])
if len(tokensStr) > 2 {
tokens[2], _ = strconv.Atoi(tokensStr[2])
}
javaVer := 8
if tokens[1] == 17 {
javaVer = 17
} else if tokens[1] == 18 || tokens[1] == 19 {
javaVer = 17
} else if tokens[1] > 19 {
if tokens[1] == 20 && tokens[2] < 5 {
javaVer = 17
} else {
javaVer = 21
}
}
fmt.Printf("Req Java Version: %d", javaVer)
if !i.app.Java.CheckJavaVer(javaVer) {
i.app.Java.InstallJavaVer(javaVer)
}
confPath := filepath.Join(i.app.PrismLauncher.GetInstanceDir(), instance.InstanceName, "instance.cfg")
f, _ = os.OpenFile(confPath, os.O_RDONLY, 0755)
defer f.Close()
buff := new(bytes.Buffer)
io.Copy(buff, f)
sc := bufio.NewScanner(buff)
f, _ = os.OpenFile(confPath, os.O_CREATE|os.O_RDWR, 0755)
plat := "lin"
exe := "java"
if runtime.GOOS == "windows" {
plat = "win"
exe = "Java.exe"
}
confDir, _ := os.UserConfigDir()
found := false
for sc.Scan() {
line := sc.Text()
if strings.HasPrefix(line, "JavaPath=") {
line = fmt.Sprintf("JavaPath=%s/FCLauncher/java/java-%d-%s/bin/%s", strings.ReplaceAll(confDir, "\\", "/"), javaVer, plat, exe)
found = true
}
f.WriteString(line + "\n")
}
if !found {
line := fmt.Sprintf("JavaPath=%s/FCLauncher/java/java-%d-%s/bin/%s", strings.ReplaceAll(confDir, "\\", "/"), javaVer, plat, exe)
f.WriteString(line + "\n")
f.WriteString("OverrideJavaLocation=true\nOverrideJava=true\n")
}
f.Close()
}
func (i *InstanceManager) InstallModpack(modpack Modpack, instanceName string) {
i.app.Status(fmt.Sprintf("Installing %s", modpack.Name))
version := modpack.Versions[len(modpack.Versions)-1]
dname, _ := os.MkdirTemp("", "fclauncher-*")
f, _ := os.OpenFile(filepath.Join(dname, instanceName+".mrpack"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
HttpDownload(modpack.Id+"/"+version.File, f, i.app.Ctx)
i.app.PrismLauncher.ImportModpack(f.Name())
instance := Instance{InstanceName: instanceName, ModpackVersion: version.Version, ModpackId: modpack.Id}
i.instances = append(i.instances, instance)
f, _ = os.OpenFile(filepath.Join(i.app.PrismLauncher.GetInstanceDir(), instanceName, "instance.json"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
data, _ := json.Marshal(instance)
f.Write(data)
i.checkJavaVersion(instance)
i.SearchInstances()
}
func (i *InstanceManager) InstallVanilla(version string, instanceName string) {
dir, _ := os.UserConfigDir()
err := DownloadAssets(version, filepath.Join(dir, "FCLauncher", "assets"), *i.app)
if err != nil {
fmt.Printf("Unable to download assets: %s\n", err)
} else {
fmt.Printf("Assets Downloaded")
}
err = DownloadLibraries(version, filepath.Join(dir, "FCLauncher", "lib"), *i.app)
if err != nil {
fmt.Printf("Unable to download libs: %s\n", err)
} else {
fmt.Printf("Libs Downloaded")
}
InstallNatives(version, filepath.Join(dir, "FCLauncher", "instances", instanceName, "minecraft", "natives"))
DownloadLoggingConfig(version, filepath.Join(dir, "FCLauncher", "instances", instanceName, "minecraft"))
err = DownloadExecutable(version, filepath.Join(dir, "FCLauncher", "bin"), *i.app)
if err != nil {
fmt.Printf("Unable to download binaries: %s\n", err)
} else {
fmt.Printf("Binaries Downloaded")
}
metadata, err := GetVersionMetadata(version)
if err != nil {
fmt.Printf("unable to pull metadata: %s\n", err)
}
err = os.MkdirAll(filepath.Join(dir, "FCLauncher", "instances", instanceName, "minecraft"), 0755)
if err != nil {
fmt.Printf("unable to create directory: %s\n", err)
}
instance := Instance{InstanceName: instanceName, MinecraftVersion: version, JavaVersion: metadata.JavaVersion.MajorVersion, MainClass: metadata.MainClass}
for _, lib := range metadata.Libraries {
instance.Libraries = append(instance.Libraries, lib.Downloads.Artifact.Path)
}
data, err := json.Marshal(instance)
if err != nil {
fmt.Printf("unable to marshal json data: %s\n", err)
}
f, err := os.OpenFile(filepath.Join(dir, "FCLauncher", "instances", instanceName, "instance.json"), os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
fmt.Printf("unable to open file: %s\n", err)
}
defer f.Close()
_, err = f.Write(data)
if err != nil {
fmt.Printf("unable to write data: %s\n", err)
}
i.instances = append(i.instances, instance)
if !i.app.Java.CheckJavaVer(instance.JavaVersion) {
i.app.Status(fmt.Sprintf("Installing Java Version %d", instance.JavaVersion))
i.app.Java.InstallJavaVer(instance.JavaVersion)
}
i.SearchInstances()
}
func (i *InstanceManager) GetInstances() []string {
names := []string{}
for _, inst := range i.instances {
names = append(names, inst.InstanceName)
}
return names
}
func (i *InstanceManager) CheckUpdate(instance Instance) {
return
i.app.Status("Checking for Updates")
i.app.Modpacks.QuerryModpacks()
pack := i.app.Modpacks.GetModpack(instance.ModpackId)
if pack.Versions[len(pack.Versions)-1].Version == instance.ModpackVersion {
return
}
i.app.Status(fmt.Sprintf("Updating %s", instance.InstanceName))
version := pack.Versions[len(pack.Versions)-1]
dname, _ := os.MkdirTemp("", "fclauncher-*")
f, _ := os.OpenFile(filepath.Join(dname, instance.InstanceName+".mrpack"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
HttpDownload(pack.Id+"/"+version.File, f, i.app.Ctx)
i.app.PrismLauncher.ImportModpack(f.Name())
instance.ModpackVersion = version.Version
f, _ = os.OpenFile(filepath.Join(i.app.PrismLauncher.GetInstanceDir(), instance.InstanceName, "instance.json"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
data, _ := json.Marshal(instance)
f.Write(data)
i.checkJavaVersion(instance)
i.SearchInstances()
}
func (i *InstanceManager) GetInstance(instance string) (Instance, error) {
instanceObject := Instance{}
found := false
for _, inst := range i.instances {
if inst.InstanceName == instance {
instanceObject = inst
found = true
break
}
}
if !found {
return Instance{}, fmt.Errorf("unable to find instance %s\n", instance)
}
return instanceObject, nil
}
func (i *InstanceManager) LaunchInstance(instance string) {
i.app.Status(fmt.Sprintf("Launching %s", instance))
dir, err := os.UserConfigDir()
if err != nil {
fmt.Printf("unable to get config directory\n")
}
instanceObject, err := i.GetInstance(instance)
if err != nil {
fmt.Printf("Unable to find instance\n")
}
execName := "java"
suffix := "lin"
if runtime.GOOS == "windows" {
execName = "Javaw.exe"
suffix = "win"
}
dir = filepath.Join(dir, "FCLauncher")
auth, err := MicrosoftAuth(i.app.Auth)
if err != nil {
fmt.Printf("unable to authenticate: %s\n", err)
return
}
args, err := GetOnlineLaunchArgs(instanceObject.MinecraftVersion, instanceObject, filepath.Join(dir, "lib"), filepath.Join(dir, "bin"), filepath.Join(dir, "assets"), filepath.Join(dir, "instances", instance, "minecraft"), auth)
if err != nil {
fmt.Printf("unable to get launch args: %s\n", err)
}
if instanceObject.ForgeVersion != "" {
args = append(args, "--launchTarget")
args = append(args, "forge_client")
}
fmt.Printf("Args: %+v", args)
child := exec.Command(filepath.Join(dir, "java", fmt.Sprintf("java-%d-%s", instanceObject.JavaVersion, suffix), "bin", execName), args...)
child.Dir = filepath.Join(dir, "instances", instance, "minecraft")
wruntime.WindowHide(i.app.Ctx)
data, err := child.CombinedOutput()
wruntime.WindowShow(i.app.Ctx)
fmt.Printf("Command Output: %s\n", data)
}
func (i *InstanceManager) InstallFabric(instance string, fabricVersion string) {
i.app.Status("Installing Fabric")
instanceObject, err := i.GetInstance(instance)
if err != nil {
fmt.Printf("Instance does not exist\n")
}
metadata, err := GetFabricMetadata(instanceObject.MinecraftVersion, fabricVersion)
if err != nil {
fmt.Printf("unable to get version metadata\n")
}
client:
for _, lib := range metadata.LauncherMeta.Libraries.Client {
tokens := strings.Split(ProcessMavenPath(lib.Name), string(os.PathSeparator))
pkg := tokens[len(tokens)-2]
instanceObject.Libraries = append(instanceObject.Libraries, filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name)))
for ind, path := range instanceObject.Libraries {
path = strings.ReplaceAll(path, "/", string(os.PathSeparator))
tokens := strings.Split(path, string(os.PathSeparator))
if pkg == tokens[len(tokens)-3] {
instanceObject.Libraries[ind] = filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name))
fmt.Printf("duplicate library %s\n", pkg)
continue client
}
}
}
common:
for _, lib := range metadata.LauncherMeta.Libraries.Common {
tokens := strings.Split(ProcessMavenPath(lib.Name), string(os.PathSeparator))
pkg := tokens[len(tokens)-2]
instanceObject.Libraries = append(instanceObject.Libraries, filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name)))
for ind, path := range instanceObject.Libraries {
path = strings.ReplaceAll(path, "/", string(os.PathSeparator))
tokens := strings.Split(path, string(os.PathSeparator))
fmt.Printf("Inspecing path %s with %d tokens\n", path, len(tokens))
if pkg == tokens[len(tokens)-3] {
instanceObject.Libraries[ind] = filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name))
fmt.Printf("duplicate library %s\n", pkg)
continue common
}
}
}
instanceObject.Libraries = append(instanceObject.Libraries, filepath.Join(ProcessMavenPath(metadata.Loader.Maven), ProcessMavenFilename(metadata.Loader.Maven)))
instanceObject.Libraries = append(instanceObject.Libraries, filepath.Join(ProcessMavenPath(metadata.Intermediary.Maven), ProcessMavenFilename(metadata.Intermediary.Maven)))
instanceObject.MainClass = metadata.LauncherMeta.MainClass["client"]
instanceObject.FabricVersion = fabricVersion
dir, _ := os.UserConfigDir()
InstallFabricLibs(instanceObject.MinecraftVersion, fabricVersion, filepath.Join(dir, "FCLauncher", "lib"), i.app)
f, _ := os.OpenFile(filepath.Join(dir, "FCLauncher", "instances", instance, "instance.json"), os.O_CREATE|os.O_RDWR, 0755)
data, _ := json.Marshal(instanceObject)
defer f.Close()
f.Write(data)
for ind, inst := range i.instances {
if inst.InstanceName == instance {
i.instances[ind] = instanceObject
break
}
}
}
func (i *InstanceManager) InstallQuilt(instance string, quiltVersion string) {
i.app.Status("Installing Quilt")
instanceObject, err := i.GetInstance(instance)
if err != nil {
fmt.Printf("Instance does not exist\n")
}
metadata, err := GetQuiltMetadata(instanceObject.MinecraftVersion, quiltVersion)
if err != nil {
fmt.Printf("unable to get version metadata\n")
}
client:
for _, lib := range metadata.LauncherMeta.Libraries.Client {
tokens := strings.Split(ProcessMavenPath(lib.Name), string(os.PathSeparator))
pkg := tokens[len(tokens)-2]
instanceObject.Libraries = append(instanceObject.Libraries, filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name)))
for ind, path := range instanceObject.Libraries {
path = strings.ReplaceAll(path, "/", string(os.PathSeparator))
tokens := strings.Split(path, string(os.PathSeparator))
if pkg == tokens[len(tokens)-3] {
instanceObject.Libraries[ind] = filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name))
fmt.Printf("duplicate library %s\n", pkg)
continue client
}
}
}
common:
for _, lib := range metadata.LauncherMeta.Libraries.Common {
tokens := strings.Split(ProcessMavenPath(lib.Name), string(os.PathSeparator))
pkg := tokens[len(tokens)-2]
instanceObject.Libraries = append(instanceObject.Libraries, filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name)))
for ind, path := range instanceObject.Libraries {
path = strings.ReplaceAll(path, "/", string(os.PathSeparator))
tokens := strings.Split(path, string(os.PathSeparator))
if pkg == tokens[len(tokens)-3] {
instanceObject.Libraries[ind] = filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name))
fmt.Printf("duplicate library %s\n", pkg)
continue common
}
}
}
instanceObject.Libraries = append(instanceObject.Libraries, filepath.Join(ProcessMavenPath(metadata.Loader.Maven), ProcessMavenFilename(metadata.Loader.Maven)))
instanceObject.Libraries = append(instanceObject.Libraries, filepath.Join(ProcessMavenPath(metadata.Intermediary.Maven), ProcessMavenFilename(metadata.Intermediary.Maven)))
instanceObject.Libraries = append(instanceObject.Libraries, filepath.Join(ProcessMavenPath(metadata.Hashed.Maven), ProcessMavenFilename(metadata.Hashed.Maven)))
instanceObject.MainClass = metadata.LauncherMeta.MainClass["client"]
instanceObject.QuiltVersion = quiltVersion
dir, _ := os.UserConfigDir()
InstallQuiltLibs(instanceObject.MinecraftVersion, quiltVersion, filepath.Join(dir, "FCLauncher", "lib"), i.app)
f, _ := os.OpenFile(filepath.Join(dir, "FCLauncher", "instances", instance, "instance.json"), os.O_CREATE|os.O_RDWR, 0755)
data, _ := json.Marshal(instanceObject)
defer f.Close()
f.Write(data)
for ind, inst := range i.instances {
if inst.InstanceName == instance {
i.instances[ind] = instanceObject
break
}
}
}
func (i *InstanceManager) InstallForge(instance string, forgeVersion string) {
instanceObject, err := i.GetInstance(instance)
if err != nil {
fmt.Printf("Unable to find instance: %s\n", err)
}
installData, err := GetForgeInstallData(instanceObject.MinecraftVersion, forgeVersion)
if err != nil {
fmt.Printf("Unable to get install data: %s\n", err)
}
dir, _ := os.UserConfigDir()
InstallForgeLibs(instanceObject.MinecraftVersion, forgeVersion, filepath.Join(dir, "FCLauncher", "lib"))
instanceObject.ForgeVersion = forgeVersion
outer:
for _, lib := range installData.Libraries {
tokens := strings.Split(lib.Downloads.Artifact.Path, string(os.PathSeparator))
pkg := tokens[len(tokens)-2]
instanceObject.Libraries = append(instanceObject.Libraries, lib.Downloads.Artifact.Path)
for ind, path := range instanceObject.Libraries {
tokens := strings.Split(path, string(os.PathSeparator))
if pkg == tokens[len(tokens)-3] {
instanceObject.Libraries[ind] = filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name))
fmt.Printf("duplicate library %s\n", pkg)
continue outer
}
}
}
instanceObject.MainClass = installData.MainClass
f, _ := os.OpenFile(filepath.Join(dir, "FCLauncher", "instances", instance, "instance.json"), os.O_CREATE|os.O_RDWR, 0755)
data, _ := json.Marshal(instanceObject)
defer f.Close()
f.Write(data)
for ind, inst := range i.instances {
if inst.InstanceName == instance {
i.instances[ind] = instanceObject
break
}
}
}
func (i *InstanceManager) ImportModpack(modpack Modpack, name string) {
i.app.Status(fmt.Sprintf("Downloading %s", modpack.Name))
buff := new(bytes.Buffer)
err := HttpDownload(filepath.Join(modpack.Id, modpack.Versions[len(modpack.Versions)-1].File), buff, i.app.Ctx)
if err != nil {
fmt.Printf("Unable to download modpack file: %s\n", err)
return
}
i.ImportMrpack(buff, name)
}
func (i *InstanceManager) ImportMrpack(data io.Reader, name string) {
dir, _ := os.UserConfigDir()
InstancePath := filepath.Join(dir, "FCLauncher", "instances", name, "minecraft")
zr := zipstream.NewReader(data)
mrdata := MrData{}
i.app.Status("Unpacking modpack File")
for {
entry, err := zr.GetNextEntry()
if err == io.EOF {
break
}
if err != nil {
fmt.Printf("Error unpacking modpack file: %s\n", err)
break
}
if entry.Name == "modrinth.index.json" {
i.app.Status("Loading metadata")
file, _ := entry.Open()
data, _ := io.ReadAll(file)
json.Unmarshal(data, &mrdata)
} else {
i.app.Status(fmt.Sprintf("Unpacking %s", entry.Name))
prefix := strings.Split(entry.Name, "/")[0]
if prefix == "overrides" || prefix == "client-overrides" {
path := strings.SplitN(entry.Name, "/", 2)[1]
if entry.IsDir() {
fmt.Printf("creating directory %s\n", filepath.Join(InstancePath, path))
if _, err := os.Stat(filepath.Join(InstancePath, path)); err != nil {
os.MkdirAll(filepath.Join(InstancePath, path), 0755)
}
} else {
zf, _ := entry.Open()
defer zf.Close()
fileDir := ""
tokens := strings.Split(path, "/")
for ind, token := range tokens {
if ind != len(tokens)-1 {
fileDir = filepath.Join(fileDir, token)
}
}
fmt.Printf("creating directory %s\n", filepath.Join(InstancePath, fileDir))
if _, err := os.Stat(filepath.Join(InstancePath, fileDir)); err != nil {
os.MkdirAll(filepath.Join(InstancePath, fileDir), 0755)
}
file, _ := os.OpenFile(filepath.Join(InstancePath, path), os.O_CREATE|os.O_RDWR, 0755)
defer file.Close()
io.Copy(file, zf)
}
}
}
}
i.InstallVanilla(mrdata.Dependencies["minecraft"], name)
if mrdata.Dependencies["forge"] != "" {
fmt.Printf("Forge not implemented!")
//implement forge
} else if mrdata.Dependencies["neoforge"] != "" {
fmt.Printf("Neoforge not implemented!")
//implement neoforge
} else if mrdata.Dependencies["fabric-loader"] != "" {
i.InstallFabric(name, mrdata.Dependencies["fabric-loader"])
} else if mrdata.Dependencies["quilt-loader"] != "" {
i.InstallQuilt(name, mrdata.Dependencies["quilt-loader"])
}
i.app.Status("Downloading Mods")
for _, f := range mrdata.Files {
fmt.Printf("Downloading %s\n", f.Path)
i.app.Status(fmt.Sprintf("Downloading %s", f.Path))
resp, err := http.Get(f.Downloads[0])
if err != nil {
fmt.Printf("Unable to download file %s\n", err)
continue
}
defer resp.Body.Close()
buff := new(bytes.Buffer)
downloaded := 0
for {
count, err := io.CopyN(buff, resp.Body, BlockSize)
if err == io.EOF {
downloaded += int(count)
break
}
if err != nil {
fmt.Printf("Error Downloading libs: %e\n", err)
return
}
downloaded += int(count)
wruntime.EventsEmit(i.app.Ctx, "download", downloaded, f.FileSize)
}
fileDir := ""
tokens := strings.Split(f.Path, "/")
for ind, token := range tokens {
if ind != len(tokens)-1 {
fileDir = filepath.Join(fileDir, token)
}
}
if _, err := os.Stat(filepath.Join(InstancePath, fileDir)); err != nil {
os.MkdirAll(filepath.Join(InstancePath, fileDir), 0755)
}
file, _ := os.OpenFile(filepath.Join(InstancePath, f.Path), os.O_CREATE|os.O_RDWR, 0755)
defer file.Close()
io.Copy(file, buff)
wruntime.EventsEmit(i.app.Ctx, "download_complete")
}
}
func (i *InstanceManager) OpenInstanceFolder(instance string) {
_, err := i.GetInstance(instance)
if err != nil {
fmt.Printf("Instance does not exist\n")
}
dir, _ := os.UserConfigDir()
openbrowser(filepath.Join(dir, "FCLauncher", "instances", instance, "minecraft"))
}
func (i *InstanceManager) DeleteInstance(instance string) {
_, err := i.GetInstance(instance)
if err != nil {
fmt.Printf("Instance does not exist\n")
return
}
dir, _ := os.UserConfigDir()
os.RemoveAll(filepath.Join(dir, "FCLauncher", "instances", instance))
i.SearchInstances()
}

120
fclauncher/Java.go Normal file
View File

@ -0,0 +1,120 @@
package main
import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/zhyee/zipstream"
)
type JavaManager struct {
app *App
}
func (JavaManager)CheckJavaVer(version int) bool{
suffix := "lin"
if runtime.GOOS == "windows" {
suffix = "win"
}
path, _ := os.UserConfigDir()
path = filepath.Join(path, "FCLauncher", "java", fmt.Sprintf("java-%d-%s", version, suffix))
_, err := os.Stat(path)
if err == nil {
return true
}
return false
}
func (j *JavaManager)InstallJavaVer(version int) {
suffix := "lin.tar.gz"
if runtime.GOOS == "windows" {
suffix = "win.zip"
}
buff := new(bytes.Buffer)
HttpDownload("java/"+fmt.Sprintf("java-%d-%s", version, suffix), buff, j.app.Ctx)
path, _ := os.UserConfigDir()
suffix = "lin"
if runtime.GOOS == "windows" {
suffix = "win"
}
path = filepath.Join(path, "FCLauncher", "java", fmt.Sprintf("java-%d-%s", version, suffix))
os.MkdirAll(path, 0755)
if runtime.GOOS == "windows" {
zr := zipstream.NewReader(buff)
for {
entry, err := zr.GetNextEntry()
if err == io.EOF {
break
}
if err != nil {
return
}
target := filepath.Join(path, strings.SplitN(entry.Name, "/", 2)[1])
if !entry.IsDir() {
rc, err := entry.Open()
if err != nil {
return
}
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, entry.FileInfo().Mode())
if err != nil {
return
}
if _, err := io.Copy(f, rc); err != nil {
return
}
f.Close()
rc.Close()
} else {
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0755); err != nil {
return
}
}
}
}
} else {
gzip, _ := gzip.NewReader(buff)
defer gzip.Close()
tr := tar.NewReader(gzip)
out:
for {
header, err := tr.Next()
switch {
case err == io.EOF:
break out
case err != nil:
return
case header == nil:
continue
}
target := filepath.Join(path, strings.SplitN(header.Name, string(os.PathSeparator), 2)[1])
switch header.Typeflag {
case tar.TypeDir:
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0755); err != nil {
return
}
}
case tar.TypeReg:
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return
}
if _, err := io.Copy(f, tr); err != nil {
return
}
f.Close()
}
}
}
}

63
fclauncher/Modpack.go Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"time"
)
type Modpack struct {
Name string
Id string
Last_updated string
Versions []Version
}
type Version struct {
Version string
Data time.Time
File string
}
type ModpackManager struct {
app *App
Modpacks []Modpack
}
func (m *ModpackManager) QuerryModpacks() {
m.Modpacks = []Modpack{}
buff := new(bytes.Buffer)
err := HttpDownload("modpacks.json", buff, nil)
if err != nil {
fmt.Printf("HTTP error: %s\n", err)
return
}
err = json.Unmarshal(buff.Bytes(), &m.Modpacks)
if err != nil {
return
}
for ind, pack := range m.Modpacks {
buff = new(bytes.Buffer)
err = HttpDownload(pack.Id+"/versions.json", buff, nil)
if err != nil {
continue
}
json.Unmarshal(buff.Bytes(), &pack.Versions)
m.Modpacks[ind] = pack
}
}
func (m *ModpackManager) GetModpacks() []Modpack {
return m.Modpacks
}
func (m *ModpackManager) GetModpack(id string) Modpack {
for _, pack := range m.Modpacks {
if pack.Id == id {
return pack
}
}
return Modpack{}
}

170
fclauncher/Prism.go Normal file
View File

@ -0,0 +1,170 @@
package main
import (
"archive/tar"
"bufio"
"bytes"
"compress/gzip"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/zhyee/zipstream"
)
type Prism struct {
app *App
}
func (Prism) CheckInstalled() bool {
path, _ := os.UserConfigDir()
_, err := os.Stat(filepath.Join(path, "FCLauncher", "prism"))
if err == nil {
return true
} else {
return false
}
}
func (p *Prism) Install() {
suffix := "lin.tar.gz"
shortSuffix := "lin"
if runtime.GOOS == "windows" {
suffix = "win.zip"
shortSuffix = "win"
}
buff := new(bytes.Buffer)
HttpDownload("prism/prism-"+suffix, buff, p.app.Ctx)
path, _ := os.UserConfigDir()
os.MkdirAll(filepath.Join(path, "FCLauncher", "prism"), 0755)
if runtime.GOOS == "windows" {
zr := zipstream.NewReader(buff)
for {
entry, err := zr.GetNextEntry()
if err == io.EOF {
break
}
if err != nil {
return
}
target := filepath.Join(path, "FCLauncher", "prism", entry.Name)
if !entry.IsDir() {
rc, err := entry.Open()
if err != nil {
return
}
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, entry.FileInfo().Mode())
if err != nil {
return
}
if _, err := io.Copy(f, rc); err != nil {
return
}
f.Close()
rc.Close()
} else {
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0755); err != nil {
return
}
}
}
}
} else {
gzip, _ := gzip.NewReader(buff)
defer gzip.Close()
tr := tar.NewReader(gzip)
out:
for {
header, err := tr.Next()
switch {
case err == io.EOF:
break out
case err != nil:
return
case header == nil:
continue
}
target := filepath.Join(path, "FCLauncher", "prism", header.Name)
switch header.Typeflag {
case tar.TypeDir:
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0755); err != nil {
return
}
}
case tar.TypeReg:
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return
}
if _, err := io.Copy(f, tr); err != nil {
return
}
f.Close()
}
}
}
dir, _ := os.UserConfigDir()
buff = new(bytes.Buffer)
HttpDownload("prism/prismlauncher.cfg", buff, nil)
scanner := bufio.NewScanner(buff)
f, _ := os.OpenFile(filepath.Join(dir, "FCLauncher", "prism", "prismlauncher.cfg"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "JavaPath") {
line = fmt.Sprintf("JavaPath=%s/FCLauncher/java/java-21-%s", strings.ReplaceAll(dir, "\\", "/"), shortSuffix)
}
if strings.HasPrefix(line, "LastHostname") {
host, _ := os.Hostname()
line = fmt.Sprintf("LastHostname=%s", host)
}
f.WriteString(line + "\n")
}
}
func (Prism)GetInstanceDir() string{
dir, _ := os.UserConfigDir()
return filepath.Join(dir, "FCLauncher", "prism", "instances")
}
func (Prism)getExecutableName() string {
execName := "PrismLauncher"
if runtime.GOOS == "windows" {
execName = "prismlauncher.exe"
}
return execName
}
func (p *Prism)ImportModpack(path string) {
dir, _ := os.UserConfigDir()
child := exec.Command(filepath.Join(dir, "FCLauncher", "prism", p.getExecutableName()), "-I", path)
child.Start()
tokens := strings.Split(path, string(os.PathSeparator))
versionPath := filepath.Join(dir, "FCLauncher", "prism", "instances",strings.Split(tokens[len(tokens)-1], ".")[0], ".minecraft", "version.txt")
for {
time.Sleep(time.Second * 3)
if _, err := os.Stat(versionPath); err == nil {
break
}
}
child.Process.Kill()
}
func (p *Prism)LaunchInstance(instance Instance) {
p.app.Status(fmt.Sprintf("Launching %s", instance.InstanceName))
dir, _ := os.UserConfigDir()
child := exec.Command(filepath.Join(dir, "FCLauncher", "prism", p.getExecutableName()), "-l", instance.InstanceName)
child.Start()
}

16
fclauncher/README.md Normal file
View File

@ -0,0 +1,16 @@
# README
## About
This is the official Wails Svelte-TS template.
## Live Development
To run in live development mode, run `wails dev` in the project directory. This will run a Vite development
server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser
and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect
to this in your browser, and you can call your Go code from devtools.
## Building
To build a redistributable, production mode package, use `wails build`.

158
fclauncher/app.go Normal file
View File

@ -0,0 +1,158 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/inconshreveable/go-update"
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)
const client_id string = "9305aeb8-5ecb-4e7a-b28f-c33aefcfbd8d"
const client_version string = "0.0.7"
type LauncherMetadata struct {
Schema_Version string
Version string
Desc string
Downloads map[string]string
}
// App struct
type App struct {
Ctx context.Context
PrismLauncher Prism
Java JavaManager
Instance InstanceManager
Modpacks ModpackManager
Auth authenticationResp
}
// NewApp creates a new App application struct
func NewApp() *App {
a := &App{}
a.Java = JavaManager{app: a}
a.Instance = InstanceManager{app: a}
a.Modpacks = ModpackManager{app: a}
return a
}
// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
a.Ctx = ctx
}
// Greet returns a greeting for the given name
func openbrowser(url string) {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("unsupported platform")
}
if err != nil {
log.Fatal(err)
}
}
func (a *App) CheckPrerequisites() {
buff := new(bytes.Buffer)
err := HttpDownload("launcher.json", buff, nil)
fmt.Printf("Starting\n")
if err == nil {
data, _ := io.ReadAll(buff)
meta := LauncherMetadata{}
json.Unmarshal(data, &meta)
if client_version != meta.Version {
//Update available!
val, _ := wruntime.MessageDialog(a.Ctx, wruntime.MessageDialogOptions{Type: wruntime.QuestionDialog, Title: "Update!", Message: fmt.Sprintf("There is an update available:\n\n%s -> %s\n\nUpdate Description:\n\n%s\n\nWould you like to update?\n", client_version, meta.Version, meta.Desc)})
if val == "Yes" {
//run the update
fmt.Printf("Updating\n")
buff := new(bytes.Buffer)
HttpDownload(meta.Downloads[runtime.GOOS], buff, nil)
executable, _ := os.Executable()
err := update.Apply(buff, update.Options{})
if err != nil {
fmt.Printf("Error!")
}
child := exec.Command(executable)
err = child.Start()
if err != nil {
fmt.Printf("Unable to launch: %s\n", err)
}
wruntime.Quit(a.Ctx)
}
}
}
a.Status("Querrying Existing Instances")
a.Instance.SearchInstances()
a.Status("Pulling Modpacks")
a.Modpacks.QuerryModpacks()
a.Status("Logging in with Microsoft")
dir, _ := os.UserConfigDir()
authenticated := false
if _, err := os.Stat(filepath.Join(dir, "FCLauncher", "authentication.json")); err == nil {
f, _ := os.OpenFile(filepath.Join(dir, "FCLauncher", "authentication.json"), os.O_RDONLY, 0755)
defer f.Close()
data, _ := io.ReadAll(f)
json.Unmarshal(data, &a.Auth)
a.Auth, err = TokenRefresh(*a, a.Auth)
if err == nil {
authenticated = true
} else {
fmt.Printf("token reauth failed, requesting device code: %s\n", err)
}
}
if !authenticated {
var err error
//a.Auth, err = AuthCode(*a)
a.Auth, err = OAuth2(*a)
if err != nil {
fmt.Printf("Authentication Error: %s\n", err)
return
}
}
os.MkdirAll(filepath.Join(dir, "FCLauncher"), 0755)
f, _ := os.OpenFile(filepath.Join(dir, "FCLauncher", "authentication.json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0755)
defer f.Close()
data, _ := json.Marshal(a.Auth)
f.Write(data)
}
func (App) GetVersions() ([]string, error) {
manifest, err := GetVersionManifest()
if err != nil {
fmt.Printf("Manifest Error: %s\n", err)
return []string{}, err
}
versions := []string{}
for _, version := range manifest.Versions {
versions = append(versions, version.Id)
}
return versions, nil
}
func (a *App) Status(status string) {
fmt.Printf("LOG: %s\n", status)
wruntime.EventsEmit(a.Ctx, "status", status)
}

268
fclauncher/auth.go Normal file
View File

@ -0,0 +1,268 @@
package main
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)
type LauncherAuth struct {
Id string
Name string
Token string
}
type McProfile struct {
Id string
Name string
}
type devcodeResp struct {
User_code string
Device_code string
Verification_uri string
Expires_in string
Interval int
Message string
}
type authenticationResp struct {
Access_token string
Token_type string
Refresh_token string
Expires_in string
Error string
Error_description string
}
type xboxAuthProperties struct {
AuthMethod string
SiteName string
RpsTicket string
}
type xboxAuthRequest struct {
Properties xboxAuthProperties
RelyingParty string
TokenType string
}
type xboxDisplayClaim struct {
Uhs string
Gtg string
Xid string
Agg string
Usr string
Utr string
Prv string
}
type xboxDisplayClaims struct {
Xui []xboxDisplayClaim
}
type xboxAuthResponse struct {
IssueInstant time.Time
NotAfter time.Time
Token string
DisplayClaims xboxDisplayClaims
}
type XSTSProperties struct {
SandboxId string
UserTokens []string
}
type XSTSRequest struct {
Properties XSTSProperties
RelyingParty string
TokenType string
}
type McAuthRequest struct {
Xtoken string `json:"xtoken"`
Platform string `json:"platform"`
}
type McAuthResponse struct {
Username string
Access_token string
Expires_in string
Token_type string
}
func getHTTPRedirect(w http.ResponseWriter, r *http.Request, srv *http.Server, code *string) {
r.ParseForm()
fmt.Printf("Response Code: %s\n", r.Form.Get("code"))
if r.Form.Get("code") != "" {
*code = r.Form.Get("code")
io.WriteString(w, "You can now close this window and return to the application.")
} else {
srv.Shutdown(r.Context())
}
}
func AuthCode(a App) (authenticationResp, error) {
authentication := authenticationResp{}
resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", url.Values{
"client_id": {client_id},
"scope": {"XboxLive.SignIn XboxLive.offline_access"},
})
if err != nil {
return authentication, fmt.Errorf("Unable to request device code: %e\n", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return authentication, fmt.Errorf("Unable to request device code: %s\n", resp.Status)
}
data, _ := io.ReadAll(resp.Body)
codeResp := devcodeResp{}
json.Unmarshal(data, &codeResp)
//display message
fmt.Printf("resp: %s\n", data)
openbrowser(codeResp.Verification_uri)
wruntime.MessageDialog(a.Ctx, wruntime.MessageDialogOptions{Type: wruntime.InfoDialog, Title: "Authentication", Message: codeResp.Message + ". The code has been automatically coppied to the clipboard."})
wruntime.ClipboardSetText(a.Ctx, codeResp.Device_code)
ticker := time.NewTicker(time.Second * time.Duration(codeResp.Interval))
for range ticker.C {
resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", url.Values{
"client_id": {client_id},
"grant_type": {"urn:ietf:params:oauth:grant-type:device_code"},
"device_code": {codeResp.Device_code},
})
if err != nil {
return authentication, fmt.Errorf("Authentication request error %e\n", err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
json.Unmarshal(data, &authentication)
if authentication.Error == "" {
return authentication, nil
}
}
return authentication, fmt.Errorf("Unknown error")
}
func OAuth2(a App) (authenticationResp, error) {
code := "code"
srv := http.Server{Addr: ":5000"}
authentication := authenticationResp{}
verifier := make([]byte, 128)
rand.Read(verifier)
verifier_string := base64.RawURLEncoding.EncodeToString(verifier)
challenge := sha256.Sum256([]byte(verifier_string))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { getHTTPRedirect(w, r, &srv, &code) })
openbrowser("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=" + client_id + "&response_type=code&redirect_uri=http%3A%2F%2F127.0.0.1%3A5000&response_mode=query&scope=XboxLive.signin&state=12345&code_challenge=" + base64.RawURLEncoding.EncodeToString(challenge[:]) + "&code_challenge_method=S256&prompt=login")
srv.ListenAndServe()
fmt.Printf("continuing auth\n")
resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", url.Values{
"grant_type": {"authorization_code"},
"code": {code},
"redirect_uri": {"http://127.0.0.1:5000"},
"code_verifier": {verifier_string},
"client_id": {client_id},
})
if err != nil {
return authenticationResp{}, fmt.Errorf("unable to request token: %e", err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
json.Unmarshal(data, &authentication)
//fmt.Printf("auth data: %s\n", data)
return authentication, nil
}
func TokenRefresh(app App, auth authenticationResp) (authenticationResp, error) {
resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", url.Values{
"client_id": {client_id},
"grant_type": {"refresh_token"},
"refresh_token": {auth.Refresh_token},
"scope": {"XboxLive.SignIn XboxLive.offline_access"},
})
if err != nil {
return authenticationResp{}, fmt.Errorf("unable to refresh token: %e\n", err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
authResp := authenticationResp{}
json.Unmarshal(data, &authResp)
if authResp.Error != "" {
return authResp, fmt.Errorf("unable to request new token: %s", authResp.Error_description)
}
return authResp, nil
}
func MicrosoftAuth(auth authenticationResp) (LauncherAuth, error) {
//Xbox Live Auth
req, _ := json.Marshal(xboxAuthRequest{Properties: xboxAuthProperties{AuthMethod: "RPS", SiteName: "user.auth.xboxlive.com", RpsTicket: "d=" + auth.Access_token}, RelyingParty: "http://auth.xboxlive.com", TokenType: "JWT"})
client := http.Client{}
httpreq, _ := http.NewRequest("POST", "https://user.auth.xboxlive.com/user/authenticate", bytes.NewBuffer(req))
httpreq.Header.Add("x-xbl-contract-version", "1")
httpreq.Header.Add("Content-Type", "application/json")
httpreq.Header.Add("Accept", "application/json")
httpResp, err := client.Do(httpreq)
if err != nil {
return LauncherAuth{}, fmt.Errorf("unable to obtain xbox live token: %e\n", err)
}
defer httpResp.Body.Close()
if httpResp.StatusCode != 200 {
return LauncherAuth{}, fmt.Errorf("unable to obtain xbox live token: %s\n", httpResp.Status)
}
d, _ := io.ReadAll(httpResp.Body)
xblAuth := xboxAuthResponse{}
json.Unmarshal(d, &xblAuth)
xstsData, _ := json.Marshal(XSTSRequest{Properties: XSTSProperties{SandboxId: "RETAIL", UserTokens: []string{xblAuth.Token}}, RelyingParty: "rp://api.minecraftservices.com/", TokenType: "JWT"})
httpXstsReq, _ := http.NewRequest("POST", "https://xsts.auth.xboxlive.com/xsts/authorize", bytes.NewBuffer(xstsData))
httpXstsReq.Header.Add("Content-Type", "application/json")
httpResp, err = client.Do(httpXstsReq)
if err != nil {
return LauncherAuth{}, fmt.Errorf("unable to obtain minecraft sts token: %e\n", err)
}
defer httpResp.Body.Close()
if httpResp.StatusCode != 200 {
return LauncherAuth{}, fmt.Errorf("unable to obtain minecraft sts token: %s\n", httpResp.Status)
}
d, _ = io.ReadAll(httpResp.Body)
mcApi := xboxAuthResponse{}
json.Unmarshal(d, &mcApi)
mcAuthData, _ := json.Marshal(McAuthRequest{Xtoken: "XBL 3.0 x=" + mcApi.DisplayClaims.Xui[0].Uhs + ";" + mcApi.Token, Platform: "PC_LAUNCHER"})
httpReqMC, _ := http.NewRequest("POST", "https://api.minecraftservices.com/launcher/login", bytes.NewBuffer(mcAuthData))
httpReqMC.Header.Add("Content-Type", "application/json")
httpReqMC.Header.Add("Accept", "application/json")
resp, err := client.Do(httpReqMC)
if err != nil {
return LauncherAuth{}, fmt.Errorf("unable to obtain mojang auth token: %e\n", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return LauncherAuth{}, fmt.Errorf("unable to obtain mojang auth token: %s\n", resp.Status)
}
d, _ = io.ReadAll(resp.Body)
mcAuth := McAuthResponse{}
json.Unmarshal(d, &mcAuth)
httpreq, err = http.NewRequest("GET", "https://api.minecraftservices.com/minecraft/profile", new(bytes.Buffer))
httpreq.Header.Add("Content-Type", "application/json")
httpreq.Header.Add("Accept", "application/json")
httpreq.Header.Add("Authorization", "Bearer "+mcAuth.Access_token)
resp, _ = client.Do(httpreq)
if err != nil {
return LauncherAuth{}, fmt.Errorf("unable to get profile data: %e\n", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return LauncherAuth{}, fmt.Errorf("unable to get profile data: %s\n", resp.Status)
}
data, _ := io.ReadAll(resp.Body)
profile := McProfile{}
json.Unmarshal(data, &profile)
return LauncherAuth{Id: profile.Id, Name: profile.Name, Token: mcAuth.Access_token}, nil
}

View File

@ -0,0 +1,35 @@
# Build Directory
The build directory is used to house all the build files and assets for your application.
The structure is:
* bin - Output directory
* darwin - macOS specific files
* windows - Windows specific files
## Mac
The `darwin` directory holds files specific to Mac builds.
These may be customised and used as part of the build. To return these files to the default state, simply delete them
and
build with `wails build`.
The directory contains the following files:
- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`.
- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`.
## Windows
The `windows` directory contains the manifest and rc files used when building with `wails build`.
These may be customised for your application. To return these files to the default state, simply delete them and
build with `wails build`.
- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to
use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file
will be created using the `appicon.png` file in the build directory.
- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer,
as well as the application itself (right click the exe -> properties -> details)
- `wails.exe.manifest` - The main application manifest file.

View File

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

View File

@ -0,0 +1,68 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>{{.Info.ProductName}}</string>
<key>CFBundleExecutable</key>
<string>{{.Name}}</string>
<key>CFBundleIdentifier</key>
<string>com.wails.{{.Name}}</string>
<key>CFBundleVersion</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleGetInfoString</key>
<string>{{.Info.Comments}}</string>
<key>CFBundleShortVersionString</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleIconFile</key>
<string>iconfile</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>{{.Info.Copyright}}</string>
{{if .Info.FileAssociations}}
<key>CFBundleDocumentTypes</key>
<array>
{{range .Info.FileAssociations}}
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>{{.Ext}}</string>
</array>
<key>CFBundleTypeName</key>
<string>{{.Name}}</string>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
<key>CFBundleTypeIconFile</key>
<string>{{.IconName}}</string>
</dict>
{{end}}
</array>
{{end}}
{{if .Info.Protocols}}
<key>CFBundleURLTypes</key>
<array>
{{range .Info.Protocols}}
<dict>
<key>CFBundleURLName</key>
<string>com.wails.{{.Scheme}}</string>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.Scheme}}</string>
</array>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
</dict>
{{end}}
</array>
{{end}}
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,63 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>{{.Info.ProductName}}</string>
<key>CFBundleExecutable</key>
<string>{{.Name}}</string>
<key>CFBundleIdentifier</key>
<string>com.wails.{{.Name}}</string>
<key>CFBundleVersion</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleGetInfoString</key>
<string>{{.Info.Comments}}</string>
<key>CFBundleShortVersionString</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleIconFile</key>
<string>iconfile</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>{{.Info.Copyright}}</string>
{{if .Info.FileAssociations}}
<key>CFBundleDocumentTypes</key>
<array>
{{range .Info.FileAssociations}}
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>{{.Ext}}</string>
</array>
<key>CFBundleTypeName</key>
<string>{{.Name}}</string>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
<key>CFBundleTypeIconFile</key>
<string>{{.IconName}}</string>
</dict>
{{end}}
</array>
{{end}}
{{if .Info.Protocols}}
<key>CFBundleURLTypes</key>
<array>
{{range .Info.Protocols}}
<dict>
<key>CFBundleURLName</key>
<string>com.wails.{{.Scheme}}</string>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.Scheme}}</string>
</array>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
</dict>
{{end}}
</array>
{{end}}
</dict>
</plist>

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,15 @@
{
"fixed": {
"file_version": "{{.Info.ProductVersion}}"
},
"info": {
"0000": {
"ProductVersion": "{{.Info.ProductVersion}}",
"CompanyName": "{{.Info.CompanyName}}",
"FileDescription": "{{.Info.ProductName}}",
"LegalCopyright": "{{.Info.Copyright}}",
"ProductName": "{{.Info.ProductName}}",
"Comments": "{{.Info.Comments}}"
}
}
}

View File

@ -0,0 +1,115 @@
Unicode true
####
## Please note: Template replacements don't work in this file. They are provided with default defines like
## mentioned underneath.
## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo.
## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually
## from outside of Wails for debugging and development of the installer.
##
## For development first make a wails nsis build to populate the "wails_tools.nsh":
## > wails build --target windows/amd64 --nsis
## Then you can call makensis on this file with specifying the path to your binary:
## For a AMD64 only installer:
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe
## For a ARM64 only installer:
## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe
## For a installer with both architectures:
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
####
## The following information is taken from the ProjectInfo file, but they can be overwritten here.
####
## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}"
## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}"
## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}"
## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}"
## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}"
###
## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
####
## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html
####
## Include the wails tools
####
!include "wails_tools.nsh"
# The version information for this two must consist of 4 parts
VIProductVersion "${INFO_PRODUCTVERSION}.0"
VIFileVersion "${INFO_PRODUCTVERSION}.0"
VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}"
VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer"
VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}"
VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}"
VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}"
VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}"
# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware
ManifestDPIAware true
!include "MUI.nsh"
!define MUI_ICON "..\icon.ico"
!define MUI_UNICON "..\icon.ico"
# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
!insertmacro MUI_PAGE_INSTFILES # Installing page.
!insertmacro MUI_PAGE_FINISH # Finished installation page.
!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page
!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
#!uninstfinalize 'signtool --file "%1"'
#!finalize 'signtool --file "%1"'
Name "${INFO_PRODUCTNAME}"
OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder).
ShowInstDetails show # This will always show the installation details.
Function .onInit
!insertmacro wails.checkArchitecture
FunctionEnd
Section
!insertmacro wails.setShellContext
!insertmacro wails.webview2runtime
SetOutPath $INSTDIR
!insertmacro wails.files
CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
!insertmacro wails.associateFiles
!insertmacro wails.associateCustomProtocols
!insertmacro wails.writeUninstaller
AccessControl::GrantOnFile "$INSTDIR" "(BU)" "FullAccess"
SectionEnd
Section "uninstall"
!insertmacro wails.setShellContext
RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
RMDir /r $INSTDIR
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
!insertmacro wails.unassociateFiles
!insertmacro wails.unassociateCustomProtocols
!insertmacro wails.deleteUninstaller
SectionEnd

View File

@ -0,0 +1,236 @@
# DO NOT EDIT - Generated automatically by `wails build`
!include "x64.nsh"
!include "WinVer.nsh"
!include "FileFunc.nsh"
!ifndef INFO_PROJECTNAME
!define INFO_PROJECTNAME "fclauncher"
!endif
!ifndef INFO_COMPANYNAME
!define INFO_COMPANYNAME "fclauncher"
!endif
!ifndef INFO_PRODUCTNAME
!define INFO_PRODUCTNAME "fclauncher"
!endif
!ifndef INFO_PRODUCTVERSION
!define INFO_PRODUCTVERSION "1.0.0"
!endif
!ifndef INFO_COPYRIGHT
!define INFO_COPYRIGHT "Copyright........."
!endif
!ifndef PRODUCT_EXECUTABLE
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
!endif
!ifndef UNINST_KEY_NAME
!define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
!endif
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
!ifndef REQUEST_EXECUTION_LEVEL
!define REQUEST_EXECUTION_LEVEL "admin"
!endif
RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
!ifdef ARG_WAILS_AMD64_BINARY
!define SUPPORTS_AMD64
!endif
!ifdef ARG_WAILS_ARM64_BINARY
!define SUPPORTS_ARM64
!endif
!ifdef SUPPORTS_AMD64
!ifdef SUPPORTS_ARM64
!define ARCH "amd64_arm64"
!else
!define ARCH "amd64"
!endif
!else
!ifdef SUPPORTS_ARM64
!define ARCH "arm64"
!else
!error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY"
!endif
!endif
!macro wails.checkArchitecture
!ifndef WAILS_WIN10_REQUIRED
!define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later."
!endif
!ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED
!define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}"
!endif
${If} ${AtLeastWin10}
!ifdef SUPPORTS_AMD64
${if} ${IsNativeAMD64}
Goto ok
${EndIf}
!endif
!ifdef SUPPORTS_ARM64
${if} ${IsNativeARM64}
Goto ok
${EndIf}
!endif
IfSilent silentArch notSilentArch
silentArch:
SetErrorLevel 65
Abort
notSilentArch:
MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}"
Quit
${else}
IfSilent silentWin notSilentWin
silentWin:
SetErrorLevel 64
Abort
notSilentWin:
MessageBox MB_OK "${WAILS_WIN10_REQUIRED}"
Quit
${EndIf}
ok:
!macroend
!macro wails.files
!ifdef SUPPORTS_AMD64
${if} ${IsNativeAMD64}
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}"
${EndIf}
!endif
!ifdef SUPPORTS_ARM64
${if} ${IsNativeARM64}
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}"
${EndIf}
!endif
!macroend
!macro wails.writeUninstaller
WriteUninstaller "$INSTDIR\uninstall.exe"
SetRegView 64
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
!macroend
!macro wails.deleteUninstaller
Delete "$INSTDIR\uninstall.exe"
SetRegView 64
DeleteRegKey HKLM "${UNINST_KEY}"
!macroend
!macro wails.setShellContext
${If} ${REQUEST_EXECUTION_LEVEL} == "admin"
SetShellVarContext all
${else}
SetShellVarContext current
${EndIf}
!macroend
# Install webview2 by launching the bootstrapper
# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment
!macro wails.webview2runtime
!ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT
!define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime"
!endif
SetRegView 64
# If the admin key exists and is not empty then webview2 is already installed
ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
${If} $0 != ""
Goto ok
${EndIf}
${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"
${If} $0 != ""
Goto ok
${EndIf}
${EndIf}
SetDetailsPrint both
DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
SetDetailsPrint listonly
InitPluginsDir
CreateDirectory "$pluginsdir\webview2bootstrapper"
SetOutPath "$pluginsdir\webview2bootstrapper"
File "tmp\MicrosoftEdgeWebview2Setup.exe"
ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
SetDetailsPrint both
ok:
!macroend
# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b
!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND
; Backup the previously associated file class
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open"
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}`
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}`
!macroend
!macro APP_UNASSOCIATE EXT FILECLASS
; Backup the previously associated file class
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup`
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0"
DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}`
!macroend
!macro wails.associateFiles
; Create file associations
!macroend
!macro wails.unassociateFiles
; 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

@ -0,0 +1,15 @@
<?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.wails.{{.Name}}" version="{{.Info.ProductVersion}}.0" processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<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>
</assembly>

145
fclauncher/fabric.go Normal file
View File

@ -0,0 +1,145 @@
package main
import (
"bytes"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)
type Fabric struct {
}
type FabricDefinition struct {
Separator string
Build int
Maven string
Version string
Stable bool
}
type FabricLibrary struct {
Name string
Url string
Sha1 string
}
type FabricLibraries struct {
Client []FabricLibrary
Common []FabricLibrary
Server []FabricLibrary
}
type FabricMeta struct {
Version int
Libraries FabricLibraries
MainClass map[string]string
}
type FabricVersion struct {
Loader FabricDefinition
Intermediary FabricDefinition
LauncherMeta FabricMeta
}
func (Fabric) GetFabricVersions(mcVersion string) ([]FabricVersion, error) {
resp, err := http.Get("https://meta.fabricmc.net/v2/versions/loader/" + mcVersion)
if err != nil {
return []FabricVersion{}, fmt.Errorf("Unable to pull fabric version manifest: %s\n", err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
versions := []FabricVersion{}
json.Unmarshal(data, &versions)
return versions, nil
}
func GetFabricMetadata(mcVersion string, fabricVersion string) (FabricVersion, error) {
versions, err := Fabric{}.GetFabricVersions(mcVersion)
if err != nil {
return FabricVersion{}, fmt.Errorf("unable to download versions manifest: %e\n", err)
}
for _, version := range versions {
if version.Loader.Version == fabricVersion {
return version, nil
}
}
return FabricVersion{}, fmt.Errorf("Unable to find requested version.\n")
}
func InstallLib(lib FabricLibrary, libDir string, a *App) {
a.Status(fmt.Sprintf("Checking %s\n", lib.Name))
path := filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name))
if _, err := os.Stat(filepath.Join(libDir, path)); err == nil {
f, _ := os.OpenFile(filepath.Join(libDir, path), os.O_RDONLY, 0755)
defer f.Close()
data, _ := io.ReadAll(f)
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == lib.Sha1 {
return
}
}
a.Status(fmt.Sprintf("Downloading %s\n", lib.Name))
unixPath := strings.ReplaceAll(path, "\\", "/")
resp, err := http.Get(lib.Url + unixPath)
if err != nil {
return
}
defer resp.Body.Close()
buff := new(bytes.Buffer)
downloaded := 0
for {
count, err := io.CopyN(buff, resp.Body, BlockSize)
if err == io.EOF {
downloaded += int(count)
break
}
if err != nil {
fmt.Printf("Error Downloading libs: %e\n", err)
return
}
downloaded += int(count)
wruntime.EventsEmit(a.Ctx, "download", downloaded, resp.ContentLength)
}
os.MkdirAll(filepath.Join(libDir, ProcessMavenPath(lib.Name)), 0755)
f, _ := os.OpenFile(filepath.Join(libDir, path), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
io.Copy(f, buff)
wruntime.EventsEmit(a.Ctx, "download_complete")
}
func InstallFabricLibs(mcVersion string, fabricVersion string, libDir string, a *App) {
metadata, _ := GetFabricMetadata(mcVersion, fabricVersion)
for _, lib := range metadata.LauncherMeta.Libraries.Client {
InstallLib(lib, libDir, a)
}
for _, lib := range metadata.LauncherMeta.Libraries.Common {
InstallLib(lib, libDir, a)
}
InstallLib(FabricLibrary{Name: metadata.Loader.Maven, Sha1: "", Url: "https://maven.fabricmc.net/"}, libDir, a)
InstallLib(FabricLibrary{Name: metadata.Intermediary.Maven, Sha1: "", Url: "https://maven.fabricmc.net/"}, libDir, a)
}
func ProcessMavenPath(maven string) string {
tokens := strings.Split(maven, ":")
path := filepath.Join(strings.Split(tokens[0], ".")...)
pack := tokens[1]
version := tokens[2]
path = filepath.Join(path, pack, version)
return path
}
func ProcessMavenFilename(maven string) string {
tokens := strings.Split(maven, ":")
pack := tokens[1]
version := tokens[2]
return pack + "-" + version + ".jar"
}

185
fclauncher/forge.go Normal file
View File

@ -0,0 +1,185 @@
package main
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/zhyee/zipstream"
)
type Forge struct{}
type ForgeVersion struct {
Version string
Time string
Url string
}
type ForgeLibraryArtifact struct {
Path string
Url string
Sha1 string
}
type ForgeLibraryDownload struct {
Artifact ForgeLibraryArtifact
}
type ForgeLibrary struct {
Name string
Downloads ForgeLibraryDownload
}
type ForgeInstallData struct {
Id string
Time time.Time
ReleaseTime time.Time
InheritsFrom string
Type string
MainClass string
Libraries []ForgeLibrary
}
func parseForgeVersions(html string) []ForgeVersion {
lines := strings.Split(html, "\n")
parsing := false
foundTR := false
buff := ""
versions := []ForgeVersion{}
for _, line := range lines {
if strings.Contains(line, "<tbody>") {
parsing = true
} else if strings.Contains(line, "</tbody>") {
parsing = false
} else if parsing {
if strings.Contains(line, "<tr>") {
buff = ""
foundTR = true
} else if strings.Contains(line, "</tr>") {
foundTR = false
versions = append(versions, parseForgeVersion(buff))
} else if foundTR {
buff += line + "\n"
}
}
}
return versions
}
func parseForgeVersion(html string) ForgeVersion {
lines := strings.Split(html, "\n")
version := ForgeVersion{}
for ind, line := range lines {
if strings.Contains(line, "<td class=\"download-version\">") {
version.Version = strings.TrimSpace(lines[ind+1])
} else if strings.Contains(line, "<td class=\"download-time\"") {
version.Time = strings.Split(strings.Split(line, "<td class=\"download-time\" title=\"")[1], "\">")[0]
} else if strings.Contains(line, "https://adfoc.us") && strings.Contains(line, "installer.jar") {
version.Url = strings.Split(strings.Split(line, "&url=")[1], "\">")[0]
}
}
return version
}
func (Forge) GetForgeVersions(mcVersion string) ([]ForgeVersion, error) {
resp, err := http.Get(fmt.Sprintf("https://files.minecraftforge.net/net/minecraftforge/forge/index_%s.html", mcVersion))
if err != nil {
return []ForgeVersion{}, fmt.Errorf("unable to access minecraft forge index: %e", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return []ForgeVersion{}, fmt.Errorf("unable to access minecraft forge index: %s", err)
}
data, _ := io.ReadAll(resp.Body)
return parseForgeVersions(string(data)), nil
}
func GetForgeInstallDataFromVersion(version ForgeVersion) (ForgeInstallData, error) {
resp, err := http.Get(version.Url)
if err != nil {
return ForgeInstallData{}, fmt.Errorf("unable to pull jar file: %e", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return ForgeInstallData{}, fmt.Errorf("unable to pull jar file: %s", resp.Status)
}
zs := zipstream.NewReader(resp.Body)
for {
entry, err := zs.GetNextEntry()
if err == io.EOF {
break
}
if entry.Name != "version.json" {
continue
}
f, _ := entry.Open()
defer f.Close()
data, _ := io.ReadAll(f)
installData := ForgeInstallData{}
json.Unmarshal(data, &installData)
return installData, nil
}
return ForgeInstallData{}, fmt.Errorf("unable to find version.json")
}
func GetForgeInstallData(mcVersion string, forgeVersion string) (ForgeInstallData, error) {
versions, err := Forge{}.GetForgeVersions(mcVersion)
if err != nil {
return ForgeInstallData{}, fmt.Errorf("failed to pull forge versions: %e", err)
}
for _, version := range versions {
if version.Version == forgeVersion {
return GetForgeInstallDataFromVersion(version)
}
}
return ForgeInstallData{}, fmt.Errorf("unable to find the requested version")
}
func InstallForgeLibs(mcVersion string, forgeVersion string, libDir string) {
installData, err := GetForgeInstallData(mcVersion, forgeVersion)
if err != nil {
fmt.Printf("Unable to get install data: %s\n", err)
}
for _, lib := range installData.Libraries {
if _, err := os.Stat(filepath.Join(libDir, lib.Downloads.Artifact.Path)); err == nil {
if f, err := os.OpenFile(filepath.Join(libDir, lib.Downloads.Artifact.Path), os.O_RDONLY, 0755); err == nil {
defer f.Close()
data, _ := io.ReadAll(f)
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == lib.Downloads.Artifact.Sha1 {
continue
}
}
}
resp, err := http.Get(lib.Downloads.Artifact.Url)
if err != nil {
fmt.Printf("Unable to download library %s: %s\n", lib.Name, err)
continue
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
fmt.Printf("Unable to download library %s: %s\n", lib.Name, resp.Status)
continue
}
tokens := strings.Split(lib.Downloads.Artifact.Path, "/")
path := ""
for ind, token := range tokens {
if ind == len(tokens)-1 {
break
}
path = filepath.Join(path, token)
}
os.MkdirAll(filepath.Join(libDir, path), 0755)
f, _ := os.OpenFile(filepath.Join(libDir, lib.Downloads.Artifact.Path), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
io.Copy(f, resp.Body)
}
}

View File

@ -0,0 +1,5 @@
{
"recommendations": [
"svelte.svelte-vscode"
]
}

View File

@ -0,0 +1,65 @@
# Svelte + TS + Vite
This template should help get you started developing with Svelte and TypeScript in Vite.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/)
+ [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
## Need an official Svelte framework?
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its
serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less,
and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
## Technical considerations
**Why use this over SvelteKit?**
- It brings its own routing solution which might not be preferable for some users.
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
`vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account
the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the
other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte
project.
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been
structured similarly to SvelteKit so that it is easy to migrate.
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash
references keeps the default TypeScript setting of accepting type information from the entire workspace, while also
adding `svelte` and `vite/client` type information.
**Why include `.vscode/extensions.json`?**
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to
install the recommended extension upon opening the project.
**Why enable `allowJs` in the TS template?**
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of
JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds:
not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing
JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
**Why is HMR not preserving my local component state?**
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr`
and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the
details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
If you have state that's important to retain within a component, consider creating an external store which would not be
replaced by HMR.
```ts
// store.ts
// An extremely simple external store
import { writable } from 'svelte/store'
export default writable(0)
```

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>fclauncher</title>
</head>
<body>
<div id="app"></div>
<script src="./src/main.ts" type="module"></script>
</body>
</html>

1613
fclauncher/frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"@tsconfig/svelte": "^3.0.0",
"svelte": "^3.49.0",
"svelte-check": "^2.8.0",
"svelte-preprocess": "^4.10.7",
"tslib": "^2.4.0",
"typescript": "^4.6.4",
"vite": "^3.0.7"
}
}

View File

@ -0,0 +1 @@
48cb20b8d107dab0a7876a449352234a

View File

@ -0,0 +1,51 @@
<script lang="ts">
import { AdminAuth } from '../wailsjs/go/main/Admin.js'
import { adminLogin } from './global';
var username = "", password = ""
var loginFail = false
function login() {
AdminAuth(username, password).then((result) => {
if(result) {
$adminLogin = true
} else {
loginFail = true
}
})
}
</script>
<main>
<div class="container">
<input type="text" placeholder="Username" bind:value={username} />
<input type="password" placeholder="Password" bind:value={password} />
</div>
{#if loginFail}
<p>Login Failed!</p>
{/if}
<button on:click={login}>Login</button>
</main>
<style>
* {
box-sizing: border-box;
font-family: sans-serif;
--text-primary: #b6b6b6;
--text-secondary: #ececec;
--bg-primary: #23232e;
--bg-secondary: #141418;
}
p {
color: red;
font-weight: bold;
}
.container{
display: flex;
justify-content: center;
margin: 3rem;
}
</style>

View File

@ -0,0 +1,33 @@
<script lang="ts">
import AdminSettings from "./AdminSettings.svelte";
import AdminLogin from "./AdminLogin.svelte";
import { adminLogin } from "./global";
</script>
<main>
<h1>Admin</h1>
<div class="container">
{#if $adminLogin}
<AdminSettings />
{:else}
<AdminLogin />
{/if}
</div>
</main>
<style>
* {
box-sizing: border-box;
font-family: sans-serif;
--text-primary: #b6b6b6;
--text-secondary: #ececec;
--bg-primary: #23232e;
--bg-secondary: #141418;
}
.container{
display: flex;
justify-content: center;
margin: 3rem;
}
</style>

View File

@ -0,0 +1,87 @@
<script lang="ts">
import {onMount} from 'svelte'
import { GetModpacks, QuerryModpacks } from '../wailsjs/go/main/ModpackManager';
var modpacks = []
var selectedPack = []
onMount(() => {
QuerryModpacks().then(() => {
GetModpacks().then((result) => {
modpacks = result
selectedPack = modpacks[0]
})
})
})
function select(modpack) {
selectedPack = modpack
}
</script>
<main>
<p>Selectec pack: {selectedPack.Name}</p>
<div class="container">
<div class="modpackList">
{#each modpacks as pack}
{#if pack == selectedPack}
<button on:click={select(pack)} class="modpackElementSelected">{pack.Name}</button>
{:else}
<button on:click={select(pack)} class="modpackElement">{pack.Name}</button>
{/if}
{/each}
</div>
<div class="modpackOptions">
</div>
</div>
</main>
<style>
* {
display: box;
box-sizing: border-box;
font-family: sans-serif;
--text-primary: #b6b6b6;
--text-secondary: #ececec;
--bg-primary: #23232e;
--bg-secondary: #141418;
width: 100%;
}
.container{
display: flex;
justify-content: left;
margin: 3rem;
}
.modpackList {
display: flex;
flex-direction: column;
background-color: #141418;
float: left;
width: 60%;
height: 20em;
align-items: center;
gap: 2px;
}
.modpackList button {
border: none;
color: inherit;
}
.modpackElement {
width: 100%;
background-color: #23232e;
}
.modpackElement:hover {
background-color: #303030;
}
.modpackElementSelected {
width: 100%;
background-color: #707070;
}
.modpackElementSelected:hover {
background-color: #303030;
}
</style>

View File

@ -0,0 +1,139 @@
<script lang="ts">
import logo from './assets/images/fc-logo.png'
import Instances from './Instances.svelte'
import Loading from './Loading.svelte'
import Modpacks from './Modpacks.svelte'
import {CheckPrerequisites} from '../wailsjs/go/main/App.js'
import { onMount } from 'svelte'
import { loading, currentPage, instances, themecolor } from './global'
import { slide } from 'svelte/transition'
import Navbar from './Navbar.svelte'
import Instancepage from './Instancepage.svelte'
import { set_attributes, set_style } from 'svelte/internal';
import {GetInstances} from '../wailsjs/go/main/InstanceManager.js'
import Settingspage from './Settingspage.svelte';
import AdminPage from './AdminPage.svelte';
let width: number = 10
let navMargin = document.getElementById("body") as HTMLElement;
let r
function UpdateInstances() {
$loading = true
GetInstances().then((result) => {
$instances = result
$loading = false
})
}
onMount(() => {
CheckPrerequisites().then(() => {
UpdateInstances()
})
r = document.getElementById('wrapper');
})
function setMargin(){
r.style.setProperty('--navMargin', '17rem');
}
function unsetMargin(){
r.style.setProperty('--navMargin', '5rem');
}
function initialColor() {
r.style.setProperty('--accent-color', 'purple');
}
function setcolor() {
console.log("changing theme");
r.style.setProperty('--accent-color', $themecolor);
}
window.document.onload = function() {setcolor()};
</script>
<main>
<div id = "wrapper">
<div class="navbar" on:mouseover={setMargin} on:focus={setMargin} on:mouseleave={unsetMargin} >
<Navbar />
</div>
<body class="body" id="body">
<!--<img alt="Wails logo" id="logo" src="{logo}">-->
{#if $loading}
<div transition:slide="{{duration:100}}" class="central">
<Loading />
</div>
{:else if $currentPage == 1}
<div transition:slide="{{duration:100}}" class="central">
<Instancepage on:change-theme = {setcolor} />
</div>
{:else if $currentPage == 2}
<div transition:slide="{{duration:100}}" class="central">
<Instances UpdateInstances={UpdateInstances} />
</div>
{:else if $currentPage == 3}
<div transition:slide="{{duration:100}}" class="central">
<Modpacks UpdateInstances={UpdateInstances} />
</div>
{:else if $currentPage == 4}
<div transition:slide="{{duration:100}}" class="central">
<Settingspage on:change-theme = {setcolor} on:change-theme-back = {initialColor} />
</div>
{:else if $currentPage == 5}
<div transition:slide="{{duration:100}}" class="central">
<AdminPage />
</div>
{/if}
</body>
</div>
</main>
<style>
:root{
font-size: 16px;
--text-primary: #b6b6b6;
--text-secondary: #ececec;
--bg-secondary: #2c2c33;
--bg-primary: #141418;
background-color: var(--bg-secondary);
}
#wrapper{
--accent-color: purple;
--navMargin: 5rem;
}
.navbar{
z-index: 5;
}
#logo {
display: flex;
width: 70%;
height: 70%;
margin: auto;
padding: 10% 0 0;
background-position: center;
background-repeat: no-repeat;
background-size: 100% 100%;
background-origin: content-box;
}
body{
display: flex;
flex-direction: column;
margin-left: var(--navMargin);
transition: 200ms;
}
main{
background-color: var(--bg-secondary);
}
.central{
background-color: var(--bg-secondary);
}
</style>

View File

@ -0,0 +1,194 @@
<script lang="ts">
import {instances, loading, navMargin} from './global'
import {OpenInstanceFolder, InstallVanilla, LaunchInstance, GetInstances, InstallForge, InstallQuilt, InstallFabric, CheckUpdate, DeleteInstance} from '../wailsjs/go/main/InstanceManager.js'
import {GetVersions} from '../wailsjs/go/main/App.js'
import {onMount, createEventDispatcher} from 'svelte'
var testArray = ["test","test2","test3"];
export let UpdateInstances
let pack: string = "";
let instance: string
let radio: string = "";
let marginScale: string= $navMargin + "rem";
var r = document.querySelector('main');
const dispatch = createEventDispatcher()
function changetheme(){
dispatch('change-theme');
}
//function initialColor() {
// r.style.setProperty('--accent-color', 'purple');
//}
//window.document.onload = function() {initialColor()};
function launchclick(event) {
$loading = true
LaunchInstance(radio).then(() => {
$loading = false
})
}
function deleteclick() {
$loading = true
DeleteInstance(radio).then(() => {
GetInstances().then((result) => {
$instances = result
$loading = false
})
})
}
</script>
<main>
<div class="instance-header">Instances</div>
<div class="header">
<div class="container">
<div class="tile-group">
{#each $instances as instance}
<div class="input-container" id=input-container>
<input id={instance} bind:group={radio} type="radio" name="radio" value={instance}>
<div class="radio-tile">
<!--icon goes here later-->
<label for={instance}>{instance}</label>
</div>
</div>
{/each}
</div>
</div>
<div class="options-container">
<button class="instance-button" disabled='{radio == ""}' on:click={launchclick}>Launch {radio}</button>
<button class="instance-button" disabled='{radio == ""}' on:click={() => {OpenInstanceFolder(radio)}}>Open Instance Folder</button>
<button class="instance-button" disabled='{radio == ""}' on:click={deleteclick}>Delete Instance</button>
</div>
</div>
</main>
<style>
* {
box-sizing: border-box;
font-family: sans-serif;
--text-primary: #b6b6b6;
--text-secondary: #ececec;
--bg-primary: #23232e;
--bg-secondary: #141418;
}
body{
background-color: var(--bg-secondary);
}
main{
display: flex;
flex-direction: column;
}
.header{
display: flex;
flex: 3;
flex-direction: row;
}
.instance-header{
display: inline-block;
font-weight: bold;
text-transform: uppercase;
margin-bottom: 1rem;
text-align: center;
font-size: 1.5rem;
color: var(--text-primary);
}
.options-container{
display: flex;
height: 100%;
justify-self: right;
justify-content: center;
align-items: center;
width: 15rem;
flex-direction: column;
position: relative;
margin-left: auto;
margin-top: 15rem;
gap: 10px;
}
.instance-button{
min-height: 3rem;
border:solid;
border-radius: 4px;
background-color: var(--bg-secondary);
border-color: var(--bg-secondary);
color: var(--text-primary);
font-size: large;
max-width: 10rem;
max-height: fit-content;
}
.instance-button:hover:enabled{
color: var(--text-secondary);
}
.instance-button:active:enabled{
opacity: 0.6;
}
.instance-button:disabled{
opacity: 0.6;
}
.container{
display: flex;
min-height: fit-content;
}
.tile-group{
display: flex;
flex-wrap: wrap;
flex: 1;
flex-grow:1 ;
}
.input-container{
position: relative;
height: 7rem;
width: 7rem;
margin: 0.5rem;
}
.input-container input{
position: absolute;
height: 100%;
width: 100%;
margin-left: -3.5rem;
z-index: 0;
opacity: 0;
cursor: pointer;
}
.input-container .radio-tile{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
border: 2px solid var(--accent-color);
border-radius: 8px;
transition: all 300ms ease;
}
.input-container label{
color: var(--accent-color);
font-size: 1.2rem;
font-weight: 600;
}
input:checked + .radio-tile{
background: var(--accent-color);
box-shadow: 0 0 12px var(--accent-color);
}
input:hover + .radio-tile{
box-shadow: 0 0 12px var(--accent-color);
}
input:checked + .radio-tile label{
color: var(--text-primary);
}
</style>

View File

@ -0,0 +1,198 @@
<script lang="ts">
import {InstallVanilla, LaunchInstance, GetInstances, InstallForge, InstallQuilt, InstallFabric, CheckUpdate} from '../wailsjs/go/main/InstanceManager.js'
import {GetVersions} from '../wailsjs/go/main/App.js'
import {GetFabricVersions} from '../wailsjs/go/main/Fabric.js'
import {GetQuiltVersions} from '../wailsjs/go/main/Quilt.js'
import {GetForgeVersions} from '../wailsjs/go/main/Forge.js'
import {onMount} from 'svelte'
import {loading, addingInstance} from './global'
import {slide} from 'svelte/transition'
let modpacks: string[] = []
let pack: string
//let instances: Instance[] = []
export let UpdateInstances
let name: string = "New Modpack"
let loader: string = "none"
let fabric_ver: string = ""
let fab_versions: string[] = []
let quilt_ver: string = ""
let quilt_versions: string[] = []
let forge_ver: string = ""
let forge_versions: string[] = []
function updateLists(){
GetVersions().then((result) => {
modpacks = result
pack = modpacks[0]
name = modpacks[0]
updateLoaders()
})
UpdateInstances()
}
function updateLoaders(){
GetFabricVersions(pack).then((result) => {
fab_versions = []
result.forEach((ver) => {
fab_versions.push(ver.Loader.Version)
})
fabric_ver = fab_versions[0]
})
GetQuiltVersions(pack).then((result) => {
quilt_versions = []
result.forEach((ver) => {
quilt_versions.push(ver.Loader.Version)
})
quilt_ver = quilt_versions[0]
})
GetForgeVersions(pack).then((result) => {
forge_versions = []
result.forEach((ver) => {
forge_versions.push(ver.Version)
})
forge_ver = forge_versions[0]
}).catch(() => { forge_versions = []; forge_ver = "" })
}
onMount(() => {
updateLists()
})
function install(){
$loading = true
InstallVanilla(pack, name).then(() => {
switch (loader){
case "none":
$addingInstance = false
$loading = false
updateLists()
break
case "fabric":
InstallFabric(name, fabric_ver).then(() => {
$addingInstance = false
$loading = false
updateLists()
})
break
case "quilt":
InstallQuilt(name, quilt_ver).then(() => {
$addingInstance = false
$loading = false
updateLists()
})
case "forge":
InstallForge(name, forge_ver).then(() => {
$addingInstance = false
$loading = false
updateLists()
})
break
}
})
}
function onchange(event){
name = event.target.value
pack = event.target.value
updateLoaders()
}
</script>
<main>
<div class=header>New Instance</div>
<br/>
<div class=container>
<div class=version-container>
{#each modpacks as modpack}
<div class="input-container" id=input-container>
<input id={modpack} type="radio" bind:group={pack} on:change={onchange} name="radio" value={modpack}>
<div class="radio-tile">
<!--icon goes here later-->
<label for={modpack}>{modpack}</label>
</div>
</div>
{/each}
</div>
<div class=loader-options>
<input type="radio" bind:group={loader} checked id="noLoader" name="Loader" value="none" />
<label for="noLoader">None</label>
<input type="radio" bind:group={loader} id="fabric" name="Loader" value="fabric" />
<label for="fabric">Fabric</label>
<input type="radio" bind:group={loader} id="forge" name="Loader" value="forge" />
<label for="forge">Forge</label>
<input type="radio" bind:group={loader} id="neoforge" name="Loader" value="neoforge" />
<label for="neoforge">NeoForge</label>
<input type="radio" bind:group={loader} id="quilt" name="Loader" value="quilt" />
<label for="quilt">Quilt</label>
<br/>
<input bind:value={name} />
{#if loader == "fabric"}
<select id="fabric_ver" bind:value={fabric_ver} name="fabric_ver">Select Fabric Version:
{#each fab_versions as ver}
<option value={ver}>{ver}</option>
{/each}
</select>
{:else if loader == "quilt"}
<select id="quilt_ver" bind:value={quilt_ver} name="quilt_ver">Select Quilt Version:
{#each quilt_versions as ver}
<option value={ver}>{ver}</option>
{/each}
</select>
{:else if loader == "forge"}
<select id="forge_ver" bind:value={forge_ver} name="forge_ver">Select Forge Version:
{#each forge_versions as ver}
<option value={ver}>{ver}</option>
{/each}
</select>
{/if}
</div>
</div>
<div transition:slide="{{duration:300}}">
<br/>
<button on:click={install}>Install</button>
<button on:click={() => {$addingInstance = false}}>Cancel</button>
</div>
</main>
<style>
main{
margin-top: 1rem;
margin-left: 3rem;
display: flex;
flex-direction: column;
--text-primary: #b6b6b6;
--text-secondary: #ececec;
--bg-secondary: #2c2c33;
--bg-primary: #141418;
}
.container{
display: flex;
flex-direction: row;
justify-content: center;
max-height: 100%;
}
.version-container{
display: flex;
flex-direction: column;
max-height: 30rem;
flex-shrink: 0;
min-height: 0;
max-width: 20rem;
overflow-y: scroll;
scrollbar-color: var(--text-secondary) var(--bg-secondary);
overflow-x: hidden;
}
.input-container{
display: flex;
}
#pack{
display: flex;
max-width: 10rem;
}
</style>

View File

@ -0,0 +1,29 @@
<script lang="ts">
import {EventsOn} from '../wailsjs/runtime/runtime'
var stat: string = ""
var completed: number = 0
var total: number = 0
var downloading: boolean = false
EventsOn("status", (status) => {
stat = status;
})
EventsOn("download", (Completed, Total) => {
completed = (Completed / (1024*1024)).toFixed(2)
total = (Total / (1024*1024)).toFixed(2)
downloading = true
})
EventsOn("download_complete", () => {
downloading = false
})
</script>
<main>
<p id="status">{stat}</p>
{#if downloading}
<p id="download_status">{completed}MB / {total}MB</p>
{/if}
</main>

View File

@ -0,0 +1,41 @@
<script lang="ts">
import {onMount} from 'svelte'
import {GetModpacks} from '../wailsjs/go/main/ModpackManager.js'
import {ImportModpack} from '../wailsjs/go/main/InstanceManager.js'
import { main } from '../wailsjs/go/models';
import {loading} from './global.js'
let modpacks: main.Modpack[] = []
let pack: main.Modpack
export let UpdateInstances
let name: string = "New Modpack"
onMount(() => {
GetModpacks().then((result) => {
modpacks = result
pack = result[0]
name = pack.Name
})
})
function AddModpack(){
$loading = true
ImportModpack(pack, name).then(() => {
UpdateInstances()
})
}
function onchange(event){
name = pack.Name
}
</script>
<main>
<select id="pack" bind:value={pack} on:change={onchange} name="pack">Select a Modpack:
{#each modpacks as pack}
<option value={pack}>{pack.Name}</option>
{/each}
</select>
<input bind:value={name} />
<button on:click={AddModpack}>Add Modpack</button>
</main>

View File

@ -0,0 +1,172 @@
<script lang="ts" src="https://kit.fontawesome.com/172593a6a5.js" crossorigin="anonymous">
import { main } from '../wailsjs/go/models';
import {addingInstance, navMargin, currentPage} from './global'
function extend(){
$navMargin = 17;
}
function unextend(){
$navMargin = 6;
}
</script>
<main>
<nav class="navbar" id="navbar">
<ul class="navbar-nav">
<li class="logo">
<a href="#" class="nav-link">
<span class="link-text">FCLauncher</span>
<svg xmlns="http://www.w3.org/2000/svg" class="nav-icon" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" on:click={() => {$currentPage = 1}}>
<svg xmlns="http://www.w3.org/2000/svg" class="nav-icon" viewBox="0 0 576 512"><path d="M287.9 0c9.2 0 17.6 5.2 21.6 13.5l68.6 141.3 153.2 22.6c9 1.3 16.5 7.6 19.3 16.3s.5 18.1-5.9 24.5L433.6 328.4l26.2 155.6c1.5 9-2.2 18.1-9.7 23.5s-17.3 6-25.3 1.7l-137-73.2L151 509.1c-8.1 4.3-17.9 3.7-25.3-1.7s-11.2-14.5-9.7-23.5l26.2-155.6L31.1 218.2c-6.5-6.4-8.7-15.9-5.9-24.5s10.3-14.9 19.3-16.3l153.2-22.6L266.3 13.5C270.4 5.2 278.7 0 287.9 0zm0 79L235.4 187.2c-3.5 7.1-10.2 12.1-18.1 13.3L99 217.9 184.9 303c5.5 5.5 8.1 13.3 6.8 21L171.4 443.7l105.2-56.2c7.1-3.8 15.6-3.8 22.6 0l105.2 56.2L384.2 324.1c-1.3-7.7 1.2-15.5 6.8-21l85.9-85.1L358.6 200.5c-7.8-1.2-14.6-6.1-18.1-13.3L287.9 79z"/></svg>
<span class="link-text">Instances</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" on:click={() => {$currentPage = 3}}>
<svg xmlns="http://www.w3.org/2000/svg" class="nav-icon" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>
<span class="link-text">Browse Packs</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" on:click={() => {$currentPage = 2}}>
<svg xmlns="http://www.w3.org/2000/svg" class="nav-icon" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 144L48 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l144 0 0 144c0 17.7 14.3 32 32 32s32-14.3 32-32l0-144 144 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-144 0 0-144z"/></svg>
<span class="link-text">New Instance</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" on:click={() => {$currentPage = 3}}>
<svg xmlns="http://www.w3.org/2000/svg" class="nav-icon" viewBox="0 0 640 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M175 389.4c-9.8 16-15 34.3-15 53.1c-10 3.5-20.8 5.5-32 5.5c-53 0-96-43-96-96L32 64C14.3 64 0 49.7 0 32S14.3 0 32 0L96 0l64 0 64 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l0 245.9-49 79.6zM96 64l0 96 64 0 0-96L96 64zM352 0L480 0l32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l0 150.9L629.7 406.2c6.7 10.9 10.3 23.5 10.3 36.4c0 38.3-31.1 69.4-69.4 69.4l-309.2 0c-38.3 0-69.4-31.1-69.4-69.4c0-12.8 3.6-25.4 10.3-36.4L320 214.9 320 64c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0zm32 64l0 160c0 5.9-1.6 11.7-4.7 16.8L330.5 320l171 0-48.8-79.2c-3.1-5-4.7-10.8-4.7-16.8l0-160-64 0z"/></svg>
<span class="link-text">Testing Page</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" on:click={() => {$currentPage = 4}}>
<svg xmlns="http://www.w3.org/2000/svg" class="nav-icon" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"/></svg>
<span class="link-text">Settings</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" on:click={() => {$currentPage = 5}}>
<svg xmlns="http://www.w3.org/2000/svg" class="nav-icon" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"/></svg>
<span class="link-text">Admin</span>
</a>
</li>
</ul>
</nav>
</main>
<style>
:root{
font-size: 16px;
--text-primary: #b6b6b6;
--text-secondary: #ececec;
--bg-secondary: #23232e;
--bg-primary: #141418;
}
.navbar {
width: 5rem;
height: 100vh;
position: fixed;
background-color: var(--bg-primary);
transition: width 200ms ease;
z-index: 5;
}
.navbar-nav{
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
height: 100%;
}
.nav-item{
width: 100%;
display: flex;
white-space: nowrap;
}
.nav-item:last-child{
margin-top: auto;
}
.navbar:hover{
width: 17rem;
}
.link-text{
display: none;
margin-left: 1rem;
transition: 300ms;
white-space: nowrap;
}
.navbar:hover .link-text {
display: block;
}
.nav-link{
display: flex;
align-items: center;
height: 5rem;
color: var(--text-primary);
fill: var(--text-primary);
transition: 300ms;
text-decoration: none;
width: 17rem;
}
.nav-icon{
width: 3rem;
margin: 0 1rem;
white-space: nowrap;
align-self: left;
}
.nav-link:hover{
fill:var(--accent-color);
background-color: var(--bg-secondary);
}
.logo{
font-weight: bold;
text-transform: uppercase;
margin-bottom: 1rem;
text-align: center;
color: var(--text-secondary);
background: var(--bg-secondary);
font-size: 1.5rem;
letter-spacing: 0.2ch;
display: flex;
}
.logo svg {
transform: rotate(0deg);
transition: transform 300ms;
fill: var(--text-primary);
transition: 300ms;
}
.navbar:hover .logo svg {
transform: rotate(-180deg);
fill:var(--accent-color);
}
.navbar:hover .logo .link-text{
color:var(--accent-color);
transition: 300ms;
}
</style>

View File

@ -0,0 +1,75 @@
<script lang="ts">
import {instances, loading, navMargin, themecolor} from './global'
import {InstallVanilla, LaunchInstance, GetInstances, InstallForge, InstallQuilt, InstallFabric, CheckUpdate} from '../wailsjs/go/main/InstanceManager.js'
import {GetVersions} from '../wailsjs/go/main/App.js'
import {onMount, createEventDispatcher} from 'svelte'
var r = document.querySelector('main');
const dispatch = createEventDispatcher()
function changetheme(color){
$themecolor = color;
dispatch('change-theme');
}
function changethemeback(){
dispatch('change-theme-back');
}
</script>
<main>
<div class="container">
<button id="green" class="theme-button" on:click={() => changetheme('green')}>Green</button>
<button id="purple" class="theme-button" on:click={() => changetheme('purple')}>Purple</button>
<button id="pink" class="theme-button" on:click={() => changetheme('#ba2abf')}>Pink</button>
</div>
</main>
<style>
* {
box-sizing: border-box;
font-family: sans-serif;
--text-primary: #b6b6b6;
--text-secondary: #ececec;
--bg-primary: #23232e;
--bg-secondary: #141418;
}
.container{
display: flex;
justify-content: center;
margin: 3rem;
}
.theme-button{
background-color: var(--accent-color);
margin: 1rem;
color: var(--text-primary);
font-size: large;
border-radius: 4px;
border-style: solid;
width: 4rem;
justify-content: center;
height: 4rem;
transition: 100ms;
}
.theme-button:hover{
color: var(--text-secondary);
}
.theme-button:active{
opacity: 0.6;
}
#green{
background-color: green;
border-color: green;
}
#purple{
background-color: purple;
border-color: purple;
}
#pink{
background-color: #ba2abf;
border-color: #ba2abf;
}
</style>

View File

@ -0,0 +1,93 @@
Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

View File

@ -0,0 +1,8 @@
import { writable } from "svelte/store"
export const loading = writable(true)
export const addingInstance = writable(false)
export const instances = writable([])
export const navMargin = writable(3)
export const currentPage = writable(1)
export const themecolor = writable("purple")
export const adminLogin = writable(false)

View File

@ -0,0 +1,8 @@
import './style.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')
})
export default app

View File

@ -0,0 +1,26 @@
html {
background-color: rgba(27, 38, 54, 1);
text-align: center;
color: white;
}
body {
margin: 0;
color: white;
font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
}
@font-face {
font-family: "Nunito";
font-style: normal;
font-weight: 400;
src: local(""),
url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2");
}
#app {
height: 100vh;
text-align: center;
}

2
fclauncher/frontend/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

View File

@ -0,0 +1,7 @@
import sveltePreprocess from 'svelte-preprocess'
export default {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: sveltePreprocess()
}

View File

@ -0,0 +1,30 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
"baseUrl": ".",
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": [
"src/**/*.d.ts",
"src/**/*.ts",
"src/**/*.js",
"src/**/*.svelte"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node"
},
"include": [
"vite.config.ts"
]
}

View File

@ -0,0 +1,7 @@
import {defineConfig} from 'vite'
import {svelte} from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()]
})

View File

@ -0,0 +1,8 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function CheckPrerequisites():Promise<void>;
export function GetVersions():Promise<Array<string>>;
export function Status(arg1:string):Promise<void>;

View File

@ -0,0 +1,15 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function CheckPrerequisites() {
return window['go']['main']['App']['CheckPrerequisites']();
}
export function GetVersions() {
return window['go']['main']['App']['GetVersions']();
}
export function Status(arg1) {
return window['go']['main']['App']['Status'](arg1);
}

View File

@ -0,0 +1,5 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {main} from '../models';
export function GetFabricVersions(arg1:string):Promise<Array<main.FabricVersion>>;

View File

@ -0,0 +1,7 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function GetFabricVersions(arg1) {
return window['go']['main']['Fabric']['GetFabricVersions'](arg1);
}

View File

@ -0,0 +1,5 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {main} from '../models';
export function GetForgeVersions(arg1:string):Promise<Array<main.ForgeVersion>>;

View File

@ -0,0 +1,7 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function GetForgeVersions(arg1) {
return window['go']['main']['Forge']['GetForgeVersions'](arg1);
}

View File

@ -0,0 +1,32 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {main} from '../models';
import {io} from '../models';
export function CheckUpdate(arg1:main.Instance):Promise<void>;
export function DeleteInstance(arg1:string):Promise<void>;
export function GetInstance(arg1:string):Promise<main.Instance>;
export function GetInstances():Promise<Array<string>>;
export function ImportModpack(arg1:main.Modpack,arg2:string):Promise<void>;
export function ImportMrpack(arg1:io.Reader,arg2:string):Promise<void>;
export function InstallFabric(arg1:string,arg2:string):Promise<void>;
export function InstallForge(arg1:string,arg2:string):Promise<void>;
export function InstallModpack(arg1:main.Modpack,arg2:string):Promise<void>;
export function InstallQuilt(arg1:string,arg2:string):Promise<void>;
export function InstallVanilla(arg1:string,arg2:string):Promise<void>;
export function LaunchInstance(arg1:string):Promise<void>;
export function OpenInstanceFolder(arg1:string):Promise<void>;
export function SearchInstances():Promise<void>;

View File

@ -0,0 +1,59 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function CheckUpdate(arg1) {
return window['go']['main']['InstanceManager']['CheckUpdate'](arg1);
}
export function DeleteInstance(arg1) {
return window['go']['main']['InstanceManager']['DeleteInstance'](arg1);
}
export function GetInstance(arg1) {
return window['go']['main']['InstanceManager']['GetInstance'](arg1);
}
export function GetInstances() {
return window['go']['main']['InstanceManager']['GetInstances']();
}
export function ImportModpack(arg1, arg2) {
return window['go']['main']['InstanceManager']['ImportModpack'](arg1, arg2);
}
export function ImportMrpack(arg1, arg2) {
return window['go']['main']['InstanceManager']['ImportMrpack'](arg1, arg2);
}
export function InstallFabric(arg1, arg2) {
return window['go']['main']['InstanceManager']['InstallFabric'](arg1, arg2);
}
export function InstallForge(arg1, arg2) {
return window['go']['main']['InstanceManager']['InstallForge'](arg1, arg2);
}
export function InstallModpack(arg1, arg2) {
return window['go']['main']['InstanceManager']['InstallModpack'](arg1, arg2);
}
export function InstallQuilt(arg1, arg2) {
return window['go']['main']['InstanceManager']['InstallQuilt'](arg1, arg2);
}
export function InstallVanilla(arg1, arg2) {
return window['go']['main']['InstanceManager']['InstallVanilla'](arg1, arg2);
}
export function LaunchInstance(arg1) {
return window['go']['main']['InstanceManager']['LaunchInstance'](arg1);
}
export function OpenInstanceFolder(arg1) {
return window['go']['main']['InstanceManager']['OpenInstanceFolder'](arg1);
}
export function SearchInstances() {
return window['go']['main']['InstanceManager']['SearchInstances']();
}

View File

@ -0,0 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function CheckJavaVer(arg1:number):Promise<boolean>;
export function InstallJavaVer(arg1:number):Promise<void>;

View File

@ -0,0 +1,11 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function CheckJavaVer(arg1) {
return window['go']['main']['JavaManager']['CheckJavaVer'](arg1);
}
export function InstallJavaVer(arg1) {
return window['go']['main']['JavaManager']['InstallJavaVer'](arg1);
}

View File

@ -0,0 +1,9 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {main} from '../models';
export function GetModpack(arg1:string):Promise<main.Modpack>;
export function GetModpacks():Promise<Array<main.Modpack>>;
export function QuerryModpacks():Promise<void>;

View File

@ -0,0 +1,15 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function GetModpack(arg1) {
return window['go']['main']['ModpackManager']['GetModpack'](arg1);
}
export function GetModpacks() {
return window['go']['main']['ModpackManager']['GetModpacks']();
}
export function QuerryModpacks() {
return window['go']['main']['ModpackManager']['QuerryModpacks']();
}

View File

@ -0,0 +1,13 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {main} from '../models';
export function CheckInstalled():Promise<boolean>;
export function GetInstanceDir():Promise<string>;
export function ImportModpack(arg1:string):Promise<void>;
export function Install():Promise<void>;
export function LaunchInstance(arg1:main.Instance):Promise<void>;

View File

@ -0,0 +1,23 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function CheckInstalled() {
return window['go']['main']['Prism']['CheckInstalled']();
}
export function GetInstanceDir() {
return window['go']['main']['Prism']['GetInstanceDir']();
}
export function ImportModpack(arg1) {
return window['go']['main']['Prism']['ImportModpack'](arg1);
}
export function Install() {
return window['go']['main']['Prism']['Install']();
}
export function LaunchInstance(arg1) {
return window['go']['main']['Prism']['LaunchInstance'](arg1);
}

View File

@ -0,0 +1,5 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {main} from '../models';
export function GetQuiltVersions(arg1:string):Promise<Array<main.QuiltVersion>>;

View File

@ -0,0 +1,7 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function GetQuiltVersions(arg1) {
return window['go']['main']['Quilt']['GetQuiltVersions'](arg1);
}

View File

@ -0,0 +1,404 @@
export namespace main {
export class FabricDefinition {
Separator: string;
Build: number;
Maven: string;
Version: string;
Stable: boolean;
static createFrom(source: any = {}) {
return new FabricDefinition(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Separator = source["Separator"];
this.Build = source["Build"];
this.Maven = source["Maven"];
this.Version = source["Version"];
this.Stable = source["Stable"];
}
}
export class FabricLibrary {
Name: string;
Url: string;
Sha1: string;
static createFrom(source: any = {}) {
return new FabricLibrary(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Name = source["Name"];
this.Url = source["Url"];
this.Sha1 = source["Sha1"];
}
}
export class FabricLibraries {
Client: FabricLibrary[];
Common: FabricLibrary[];
Server: FabricLibrary[];
static createFrom(source: any = {}) {
return new FabricLibraries(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Client = this.convertValues(source["Client"], FabricLibrary);
this.Common = this.convertValues(source["Common"], FabricLibrary);
this.Server = this.convertValues(source["Server"], FabricLibrary);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class FabricMeta {
Version: number;
Libraries: FabricLibraries;
MainClass: Record<string, string>;
static createFrom(source: any = {}) {
return new FabricMeta(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Version = source["Version"];
this.Libraries = this.convertValues(source["Libraries"], FabricLibraries);
this.MainClass = source["MainClass"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class FabricVersion {
Loader: FabricDefinition;
Intermediary: FabricDefinition;
LauncherMeta: FabricMeta;
static createFrom(source: any = {}) {
return new FabricVersion(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Loader = this.convertValues(source["Loader"], FabricDefinition);
this.Intermediary = this.convertValues(source["Intermediary"], FabricDefinition);
this.LauncherMeta = this.convertValues(source["LauncherMeta"], FabricMeta);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class ForgeVersion {
Version: string;
Time: string;
Url: string;
static createFrom(source: any = {}) {
return new ForgeVersion(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Version = source["Version"];
this.Time = source["Time"];
this.Url = source["Url"];
}
}
export class Instance {
InstanceName: string;
ModpackId: string;
ModpackVersion: string;
MinecraftVersion: string;
ForgeVersion: string;
NeoForgeVersion: string;
FabricVersion: string;
QuiltVersion: string;
JavaVersion: number;
Libraries: string[];
MainClass: string;
static createFrom(source: any = {}) {
return new Instance(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.InstanceName = source["InstanceName"];
this.ModpackId = source["ModpackId"];
this.ModpackVersion = source["ModpackVersion"];
this.MinecraftVersion = source["MinecraftVersion"];
this.ForgeVersion = source["ForgeVersion"];
this.NeoForgeVersion = source["NeoForgeVersion"];
this.FabricVersion = source["FabricVersion"];
this.QuiltVersion = source["QuiltVersion"];
this.JavaVersion = source["JavaVersion"];
this.Libraries = source["Libraries"];
this.MainClass = source["MainClass"];
}
}
export class Version {
Version: string;
// Go type: time
Data: any;
File: string;
static createFrom(source: any = {}) {
return new Version(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Version = source["Version"];
this.Data = this.convertValues(source["Data"], null);
this.File = source["File"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class Modpack {
Name: string;
Id: string;
Last_updated: string;
Versions: Version[];
static createFrom(source: any = {}) {
return new Modpack(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Name = source["Name"];
this.Id = source["Id"];
this.Last_updated = source["Last_updated"];
this.Versions = this.convertValues(source["Versions"], Version);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class QuiltDefinition {
Separator: string;
Build: number;
Maven: string;
Version: string;
Stable: boolean;
static createFrom(source: any = {}) {
return new QuiltDefinition(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Separator = source["Separator"];
this.Build = source["Build"];
this.Maven = source["Maven"];
this.Version = source["Version"];
this.Stable = source["Stable"];
}
}
export class QuiltLibrary {
Name: string;
Url: string;
Sha1: string;
static createFrom(source: any = {}) {
return new QuiltLibrary(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Name = source["Name"];
this.Url = source["Url"];
this.Sha1 = source["Sha1"];
}
}
export class QuiltLibraries {
Client: QuiltLibrary[];
Common: QuiltLibrary[];
Server: QuiltLibrary[];
static createFrom(source: any = {}) {
return new QuiltLibraries(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Client = this.convertValues(source["Client"], QuiltLibrary);
this.Common = this.convertValues(source["Common"], QuiltLibrary);
this.Server = this.convertValues(source["Server"], QuiltLibrary);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class QuiltMeta {
Version: number;
Libraries: QuiltLibraries;
MainClass: Record<string, string>;
static createFrom(source: any = {}) {
return new QuiltMeta(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Version = source["Version"];
this.Libraries = this.convertValues(source["Libraries"], QuiltLibraries);
this.MainClass = source["MainClass"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class QuiltVersion {
Loader: QuiltDefinition;
Intermediary: QuiltDefinition;
Hashed: QuiltDefinition;
LauncherMeta: QuiltMeta;
static createFrom(source: any = {}) {
return new QuiltVersion(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Loader = this.convertValues(source["Loader"], QuiltDefinition);
this.Intermediary = this.convertValues(source["Intermediary"], QuiltDefinition);
this.Hashed = this.convertValues(source["Hashed"], QuiltDefinition);
this.LauncherMeta = this.convertValues(source["LauncherMeta"], QuiltMeta);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}

View File

@ -0,0 +1,24 @@
{
"name": "@wailsapp/runtime",
"version": "2.0.0",
"description": "Wails Javascript runtime library",
"main": "runtime.js",
"types": "runtime.d.ts",
"scripts": {
},
"repository": {
"type": "git",
"url": "git+https://github.com/wailsapp/wails.git"
},
"keywords": [
"Wails",
"Javascript",
"Go"
],
"author": "Lea Anthony <lea.anthony@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/wails/issues"
},
"homepage": "https://github.com/wailsapp/wails#readme"
}

View File

@ -0,0 +1,249 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export interface Position {
x: number;
y: number;
}
export interface Size {
w: number;
h: number;
}
export interface Screen {
isCurrent: boolean;
isPrimary: boolean;
width : number
height : number
}
// Environment information such as platform, buildtype, ...
export interface EnvironmentInfo {
buildType: string;
platform: string;
arch: string;
}
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
// emits the given event. Optional data may be passed with the event.
// This will trigger any event listeners.
export function EventsEmit(eventName: string, ...data: any): void;
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
// sets up a listener for the given event name, but will only trigger a given number times.
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
// sets up a listener for the given event name, but will only trigger once.
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
// unregisters the listener for the given event name.
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
// unregisters all listeners.
export function EventsOffAll(): void;
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
// logs the given message as a raw message
export function LogPrint(message: string): void;
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
// logs the given message at the `trace` log level.
export function LogTrace(message: string): void;
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
// logs the given message at the `debug` log level.
export function LogDebug(message: string): void;
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
// logs the given message at the `error` log level.
export function LogError(message: string): void;
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
// logs the given message at the `fatal` log level.
// The application will quit after calling this method.
export function LogFatal(message: string): void;
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
// logs the given message at the `info` log level.
export function LogInfo(message: string): void;
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
// logs the given message at the `warning` log level.
export function LogWarning(message: string): void;
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
// Forces a reload by the main application as well as connected browsers.
export function WindowReload(): void;
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
// Reloads the application frontend.
export function WindowReloadApp(): void;
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
// Sets the window AlwaysOnTop or not on top.
export function WindowSetAlwaysOnTop(b: boolean): void;
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
// *Windows only*
// Sets window theme to system default (dark/light).
export function WindowSetSystemDefaultTheme(): void;
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
// *Windows only*
// Sets window to light theme.
export function WindowSetLightTheme(): void;
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
// *Windows only*
// Sets window to dark theme.
export function WindowSetDarkTheme(): void;
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
// Centers the window on the monitor the window is currently on.
export function WindowCenter(): void;
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
// Sets the text in the window title bar.
export function WindowSetTitle(title: string): void;
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
// Makes the window full screen.
export function WindowFullscreen(): void;
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
// Restores the previous window dimensions and position prior to full screen.
export function WindowUnfullscreen(): void;
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
export function WindowIsFullscreen(): Promise<boolean>;
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window.
export function WindowSetSize(width: number, height: number): void;
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
// Gets the width and height of the window.
export function WindowGetSize(): Promise<Size>;
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMaxSize(width: number, height: number): void;
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMinSize(width: number, height: number): void;
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
// Sets the window position relative to the monitor the window is currently on.
export function WindowSetPosition(x: number, y: number): void;
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
// Gets the window position relative to the monitor the window is currently on.
export function WindowGetPosition(): Promise<Position>;
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
// Hides the window.
export function WindowHide(): void;
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
// Shows the window, if it is currently hidden.
export function WindowShow(): void;
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
// Maximises the window to fill the screen.
export function WindowMaximise(): void;
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
// Toggles between Maximised and UnMaximised.
export function WindowToggleMaximise(): void;
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
// Restores the window to the dimensions and position prior to maximising.
export function WindowUnmaximise(): void;
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
// Returns the state of the window, i.e. whether the window is maximised or not.
export function WindowIsMaximised(): Promise<boolean>;
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
// Minimises the window.
export function WindowMinimise(): void;
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
// Restores the window to the dimensions and position prior to minimising.
export function WindowUnminimise(): void;
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
// Returns the state of the window, i.e. whether the window is minimised or not.
export function WindowIsMinimised(): Promise<boolean>;
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
// Returns the state of the window, i.e. whether the window is normal or not.
export function WindowIsNormal(): Promise<boolean>;
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
export function ScreenGetAll(): Promise<Screen[]>;
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
// Opens the given URL in the system browser.
export function BrowserOpenURL(url: string): void;
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
// Returns information about the environment
export function Environment(): Promise<EnvironmentInfo>;
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
// Quits the application.
export function Quit(): void;
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
// Hides the application.
export function Hide(): void;
// [Show](https://wails.io/docs/reference/runtime/intro#show)
// Shows the application.
export function Show(): void;
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
// Returns the current text stored on clipboard
export function ClipboardGetText(): Promise<string>;
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
// Sets a text on the clipboard
export function ClipboardSetText(text: string): Promise<boolean>;
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
// OnFileDropOff removes the drag and drop listeners and handlers.
export function OnFileDropOff() :void
// Check if the file path resolver is available
export function CanResolveFilePaths(): boolean;
// Resolves file paths for an array of files
export function ResolveFilePaths(files: File[]): void

View File

@ -0,0 +1,238 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export function LogPrint(message) {
window.runtime.LogPrint(message);
}
export function LogTrace(message) {
window.runtime.LogTrace(message);
}
export function LogDebug(message) {
window.runtime.LogDebug(message);
}
export function LogInfo(message) {
window.runtime.LogInfo(message);
}
export function LogWarning(message) {
window.runtime.LogWarning(message);
}
export function LogError(message) {
window.runtime.LogError(message);
}
export function LogFatal(message) {
window.runtime.LogFatal(message);
}
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
}
export function EventsOn(eventName, callback) {
return EventsOnMultiple(eventName, callback, -1);
}
export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}
export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
export function EventsEmit(eventName) {
let args = [eventName].slice.call(arguments);
return window.runtime.EventsEmit.apply(null, args);
}
export function WindowReload() {
window.runtime.WindowReload();
}
export function WindowReloadApp() {
window.runtime.WindowReloadApp();
}
export function WindowSetAlwaysOnTop(b) {
window.runtime.WindowSetAlwaysOnTop(b);
}
export function WindowSetSystemDefaultTheme() {
window.runtime.WindowSetSystemDefaultTheme();
}
export function WindowSetLightTheme() {
window.runtime.WindowSetLightTheme();
}
export function WindowSetDarkTheme() {
window.runtime.WindowSetDarkTheme();
}
export function WindowCenter() {
window.runtime.WindowCenter();
}
export function WindowSetTitle(title) {
window.runtime.WindowSetTitle(title);
}
export function WindowFullscreen() {
window.runtime.WindowFullscreen();
}
export function WindowUnfullscreen() {
window.runtime.WindowUnfullscreen();
}
export function WindowIsFullscreen() {
return window.runtime.WindowIsFullscreen();
}
export function WindowGetSize() {
return window.runtime.WindowGetSize();
}
export function WindowSetSize(width, height) {
window.runtime.WindowSetSize(width, height);
}
export function WindowSetMaxSize(width, height) {
window.runtime.WindowSetMaxSize(width, height);
}
export function WindowSetMinSize(width, height) {
window.runtime.WindowSetMinSize(width, height);
}
export function WindowSetPosition(x, y) {
window.runtime.WindowSetPosition(x, y);
}
export function WindowGetPosition() {
return window.runtime.WindowGetPosition();
}
export function WindowHide() {
window.runtime.WindowHide();
}
export function WindowShow() {
window.runtime.WindowShow();
}
export function WindowMaximise() {
window.runtime.WindowMaximise();
}
export function WindowToggleMaximise() {
window.runtime.WindowToggleMaximise();
}
export function WindowUnmaximise() {
window.runtime.WindowUnmaximise();
}
export function WindowIsMaximised() {
return window.runtime.WindowIsMaximised();
}
export function WindowMinimise() {
window.runtime.WindowMinimise();
}
export function WindowUnminimise() {
window.runtime.WindowUnminimise();
}
export function WindowSetBackgroundColour(R, G, B, A) {
window.runtime.WindowSetBackgroundColour(R, G, B, A);
}
export function ScreenGetAll() {
return window.runtime.ScreenGetAll();
}
export function WindowIsMinimised() {
return window.runtime.WindowIsMinimised();
}
export function WindowIsNormal() {
return window.runtime.WindowIsNormal();
}
export function BrowserOpenURL(url) {
window.runtime.BrowserOpenURL(url);
}
export function Environment() {
return window.runtime.Environment();
}
export function Quit() {
window.runtime.Quit();
}
export function Hide() {
window.runtime.Hide();
}
export function Show() {
window.runtime.Show();
}
export function ClipboardGetText() {
return window.runtime.ClipboardGetText();
}
export function ClipboardSetText(text) {
return window.runtime.ClipboardSetText(text);
}
/**
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
*
* @export
* @callback OnFileDropCallback
* @param {number} x - x coordinate of the drop
* @param {number} y - y coordinate of the drop
* @param {string[]} paths - A list of file paths.
*/
/**
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
*
* @export
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
*/
export function OnFileDrop(callback, useDropTarget) {
return window.runtime.OnFileDrop(callback, useDropTarget);
}
/**
* OnFileDropOff removes the drag and drop listeners and handlers.
*/
export function OnFileDropOff() {
return window.runtime.OnFileDropOff();
}
export function CanResolveFilePaths() {
return window.runtime.CanResolveFilePaths();
}
export function ResolveFilePaths(files) {
return window.runtime.ResolveFilePaths(files);
}

41
fclauncher/go.mod Normal file
View File

@ -0,0 +1,41 @@
module fclauncher
go 1.23.0
toolchain go1.24.2
require github.com/wailsapp/wails/v2 v2.10.1
require github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
require (
github.com/bep/debounce v1.2.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/labstack/echo/v4 v4.13.3 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/gosod v1.0.4 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // 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.49.1 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.19 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/zhyee/zipstream v0.0.0-20230625125559-133d8d1afaa0
golang.org/x/crypto v0.38.0
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
)
// replace github.com/wailsapp/wails/v2 v2.9.2 => /home/piwalker/go/pkg/mod

85
fclauncher/go.sum Normal file
View File

@ -0,0 +1,85 @@
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
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/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=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
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/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI=
github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
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=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
github.com/wailsapp/go-webview2 v1.0.19/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/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY=
github.com/zhyee/zipstream v0.0.0-20230625125559-133d8d1afaa0 h1:BcjUUYzMORs7sJtOCWLXaelG7woHMN1QEs4yCB1QZ48=
github.com/zhyee/zipstream v0.0.0-20230625125559-133d8d1afaa0/go.mod h1:aaGtAo3dTqYtHjcliPNlyXMIIodvGm8y6uK2KMTYHrk=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

38
fclauncher/https.go Normal file
View File

@ -0,0 +1,38 @@
package main
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
const BlockSize = 1024 * 64
func HttpDownload(path string, out io.Writer, ctx context.Context) error {
path = strings.ReplaceAll(path, "\\", "/")
res, err := http.Get("https://gitea.piwalker.net/fclauncher/" + path)
if err != nil {
return err
}
defer res.Body.Close()
var read int64 = 0
for (read < res.ContentLength) || res.ContentLength == -1 {
count, err := io.CopyN(out, res.Body, BlockSize)
read += count
if err != nil {
break
}
if ctx != nil {
runtime.EventsEmit(ctx, "download", read, res.ContentLength)
}
}
if ctx != nil {
runtime.EventsEmit(ctx, "download_complete")
}
fmt.Printf("Downloaded %d Bytes from %s, expected %d Bytes\n", read, path, res.ContentLength)
return nil
}

BIN
fclauncher/level.dat Normal file

Binary file not shown.

44
fclauncher/main.go Normal file
View File

@ -0,0 +1,44 @@
package main
import (
"embed"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
var assets embed.FS
func main() {
// Create an instance of the app structure
app := NewApp()
// Create application with options
err := wails.Run(&options.App{
Title: "fclauncher",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{
app,
&app.Instance,
&app.PrismLauncher,
&app.Java,
&app.Modpacks,
&Fabric{},
&Quilt{},
&Forge{},
&Admin{},
},
})
if err != nil {
println("Error:", err.Error())
}
}

631
fclauncher/minecraft.go Normal file
View File

@ -0,0 +1,631 @@
package main
import (
"bytes"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
"github.com/zhyee/zipstream"
)
type McVersionManifestEntry struct {
Id string
Type string
Url string
Time time.Time
ReleaseTime time.Time
Sha1 string
ComplianceLevel int
}
type McLatestEntry struct {
Release string
Snapshot string
}
type McVersionManifest struct {
Latest McLatestEntry
Versions []McVersionManifestEntry
}
type McArguments struct {
Game []string
Jvm []string
}
type McAssetIndex struct {
Id string
Sha1 string
Size int
TotalSize int
Url string
}
type McJavaVersion struct {
Component string
MajorVersion int
}
type McLibraryArtifact struct {
Path string
Sha1 string
Size int
Url string
}
type McLibraryDownload struct {
Artifact McLibraryArtifact
Classifiers map[string]interface{}
}
type McRuleOs struct {
Name string
Version string
ARch string
}
type McRule struct {
Action string
Features map[string]bool
Os McRuleOs
}
type McLibrary struct {
Downloads McLibraryDownload
Name string
Rules []McRule
Natives map[string]string
}
type McDownload struct {
Sha1 string
Size int
Url string
}
type McLogging struct {
Client McLoggingClient
}
type McLoggingClient struct {
Argument string
File McDownload
}
type McMetadata struct {
Arguments McArguments
AssetIndex McAssetIndex
Assets string
ComplianceLevel int
Downloads map[string]McDownload
Id string
JavaVersion McJavaVersion
Libraries []McLibrary
MainClass string
MinimumLauncherVersion int
ReleaseTime time.Time
Time time.Time
Type string
MinecraftArguments string
Logging McLogging
}
func GetVersionManifest() (McVersionManifest, error) {
resp, err := http.Get("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json")
var returnError error = nil
if err == nil {
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err == nil {
versionManifest := McVersionManifest{}
err = json.Unmarshal(data, &versionManifest)
if err == nil {
dir, err := os.UserConfigDir()
if err != nil {
return versionManifest, nil
}
err = os.MkdirAll(filepath.Join(dir, "FCLauncher", "cache"), 0755)
f, err := os.OpenFile(filepath.Join(dir, "FCLauncher", "cache", "versionManifest.json"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
f.Write(data)
return versionManifest, nil
} else {
returnError = fmt.Errorf("Unable to parse Json: %e\n", err)
}
} else {
returnError = fmt.Errorf("Unable to read version manifest: %e\n", err)
}
} else {
returnError = fmt.Errorf("Unable to download version manifest: %e\n", err)
}
dir, err := os.UserConfigDir()
if err != nil {
return McVersionManifest{}, returnError
}
path := filepath.Join(dir, "FCLauncher", "cache", "versionManifest.json")
if _, err = os.Stat(path); err != nil {
return McVersionManifest{}, returnError
}
f, err := os.OpenFile(path, os.O_RDONLY, 0755)
if err != nil {
return McVersionManifest{}, returnError
}
data, _ := io.ReadAll(f)
if err != nil {
return McVersionManifest{}, returnError
}
versionManifest := McVersionManifest{}
err = json.Unmarshal(data, &versionManifest)
if err != nil {
return McVersionManifest{}, returnError
}
return versionManifest, nil
}
func GetVersionMetadata(wantedVersion string) (McMetadata, error) {
manifest, err := GetVersionManifest()
if err != nil {
return McMetadata{}, fmt.Errorf("GetVersionMetadata: %e\n", err)
}
for _, version := range manifest.Versions {
if wantedVersion == version.Id {
//found it
dir, err := os.UserConfigDir()
if err == nil {
path := filepath.Join(dir, "FCLauncher", "cache", "versionMetadata", wantedVersion+".json")
if _, err := os.Stat(path); err == nil {
if f, err := os.OpenFile(path, os.O_RDONLY, 0755); err == nil {
defer f.Close()
if data, err := io.ReadAll(f); err == nil {
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == version.Sha1 {
metadata := McMetadata{}
json.Unmarshal(data, &metadata)
return metadata, nil
}
}
}
}
}
resp, err := http.Get(version.Url)
if err != nil {
return McMetadata{}, fmt.Errorf("Unable to download metadata: %e\n", err)
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return McMetadata{}, fmt.Errorf("Unable to download metadata: %e\n", err)
}
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) != version.Sha1 {
return McMetadata{}, fmt.Errorf("GetMetadata: Sha1 hash does not match\n")
}
metadata := McMetadata{}
err = json.Unmarshal(data, &metadata)
dir, err = os.UserConfigDir()
if err == nil {
path := filepath.Join(dir, "FCLauncher", "cache", "versionMetadata")
if os.MkdirAll(path, 0755) == nil {
if f, err := os.OpenFile(filepath.Join(path, wantedVersion+".json"), os.O_CREATE|os.O_RDWR, 0755); err == nil {
defer f.Close()
f.Write(data)
}
}
}
return metadata, nil
}
}
return McMetadata{}, fmt.Errorf("Unable to find version %s\n", wantedVersion)
}
func GetAssetIndex(mcVersion string) ([]string, error) {
found := false
var data []byte
metadata, err := GetVersionMetadata(mcVersion)
if err != nil {
return []string{}, fmt.Errorf("Unable to pull manifest: %e\n", err)
}
dir, _ := os.UserConfigDir()
path := filepath.Join(dir, "FCLauncher", "assets", "indexes")
if _, err := os.Stat(filepath.Join(path, metadata.Assets+".json")); err == nil {
//cache file exists
if f, err := os.OpenFile(filepath.Join(path, metadata.Assets+".json"), os.O_RDONLY, 0755); err == nil {
defer f.Close()
if data, err = io.ReadAll(f); err == nil {
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == metadata.AssetIndex.Sha1 {
found = true
}
}
}
}
if !found {
//no cache file
data = []byte{}
resp, err := http.Get(metadata.AssetIndex.Url)
if err != nil {
return []string{}, fmt.Errorf("Unable to pull asset index: %e\n", err)
}
defer resp.Body.Close()
data, err = io.ReadAll(resp.Body)
if err != nil {
return []string{}, fmt.Errorf("Unable to pull asset index: %e\n", err)
}
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) != metadata.AssetIndex.Sha1 {
return []string{}, fmt.Errorf("Sha mismatch!\n")
}
os.MkdirAll(path, 0755)
f, _ := os.OpenFile(filepath.Join(path, metadata.Assets+".json"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
f.Write(data)
}
//at this point data is populated
var index map[string]interface{}
json.Unmarshal(data, &index)
index = index["objects"].(map[string]interface{})
hashes := []string{}
for _, val := range index {
index_map := val.(map[string]interface{})
hashes = append(hashes, index_map["hash"].(string))
}
return hashes, nil
}
func DownloadAssets(mcVersion string, assetPath string, a App) error {
a.Status("Downloading Minecraft Assets")
assets, err := GetAssetIndex(mcVersion)
if err != nil {
return fmt.Errorf("Unable to get asset index: %e\n", err)
}
metadata, err := GetVersionMetadata(mcVersion)
if err != nil {
return fmt.Errorf("Unable to get version metadata: %e\n", err)
}
total := metadata.AssetIndex.TotalSize
downloaded := 0
wruntime.EventsEmit(a.Ctx, "download", downloaded, total)
for _, hash := range assets {
if _, err := os.Stat(filepath.Join(assetPath, "objects", hash[:2], hash)); err == nil {
f, _ := os.OpenFile(filepath.Join(assetPath, "objects", hash[:2], hash), os.O_RDONLY, 0755)
defer f.Close()
data, _ := io.ReadAll(f)
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == hash {
downloaded += len(data)
wruntime.EventsEmit(a.Ctx, "download", downloaded, total)
continue
}
}
resp, err := http.Get(fmt.Sprintf("https://resources.download.minecraft.net/%s/%s", hash[:2], hash))
if err != nil {
return fmt.Errorf("unable to download assets: %e\n", err)
}
defer resp.Body.Close()
buff := new(bytes.Buffer)
for {
count, err := io.CopyN(buff, resp.Body, BlockSize)
if err == io.EOF {
downloaded += int(count)
break
}
if err != nil {
return fmt.Errorf("Error Downloading assets: %e\n", err)
}
downloaded += int(count)
wruntime.EventsEmit(a.Ctx, "download", downloaded, total)
}
data := buff.Bytes()
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) != hash {
return fmt.Errorf("unable to download assets: Sha1 Mismatch\n")
}
err = os.MkdirAll(filepath.Join(assetPath, "objects", hash[:2]), 0755)
if err != nil {
return fmt.Errorf("unable to download assets: Unable to create directory\n")
}
f, err := os.OpenFile(filepath.Join(assetPath, "objects", hash[:2], hash), os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
return fmt.Errorf("unable to download assets: Unable to open file\n")
}
defer f.Close()
f.Write(data)
wruntime.EventsEmit(a.Ctx, "download", downloaded, total)
}
wruntime.EventsEmit(a.Ctx, "download_complete")
return nil
}
func DownloadLibraries(mcVersion string, libPath string, a App) error {
metadata, err := GetVersionMetadata(mcVersion)
if err != nil {
return fmt.Errorf("unable to pull version metadata: %e\n", err)
}
for _, lib := range metadata.Libraries {
a.Status(fmt.Sprintf("Checking %s\n", lib.Name))
if _, err := os.Stat(filepath.Join(libPath, lib.Downloads.Artifact.Path)); err == nil {
f, _ := os.OpenFile(filepath.Join(libPath, lib.Downloads.Artifact.Path), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
data, _ := io.ReadAll(f)
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == lib.Downloads.Artifact.Sha1 {
continue
}
}
a.Status(fmt.Sprintf("Downloading %s\n", lib.Name))
resp, err := http.Get(lib.Downloads.Artifact.Url)
if err != nil {
return fmt.Errorf("unable to download libs: %e\n", err)
}
defer resp.Body.Close()
buff := new(bytes.Buffer)
downloaded := 0
for {
count, err := io.CopyN(buff, resp.Body, BlockSize)
if err == io.EOF {
downloaded += int(count)
break
}
if err != nil {
return fmt.Errorf("Error Downloading libs: %e\n", err)
}
downloaded += int(count)
wruntime.EventsEmit(a.Ctx, "download", downloaded, resp.ContentLength)
}
data := buff.Bytes()
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) != lib.Downloads.Artifact.Sha1 {
return fmt.Errorf("unable to download libs: Sha1 Mismatch\n")
}
path := ""
tokens := strings.Split(lib.Downloads.Artifact.Path, "/")
for ind, token := range tokens {
if ind != len(tokens)-1 {
path = filepath.Join(path, token)
}
}
err = os.MkdirAll(filepath.Join(libPath, path), 0755)
if err != nil {
return fmt.Errorf("unable to download libs: Unable to create directory\n")
}
f, err := os.OpenFile(filepath.Join(libPath, lib.Downloads.Artifact.Path), os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
return fmt.Errorf("unable to download libs: Unable to open file\n")
}
defer f.Close()
f.Write(data)
wruntime.EventsEmit(a.Ctx, "download_complete")
}
return nil
}
func InstallNatives(mcVersion string, nativesDir string) {
metadata, _ := GetVersionMetadata(mcVersion)
for _, lib := range metadata.Libraries {
if lib.Natives != nil {
glob := lib.Natives[runtime.GOOS]
fmt.Printf("glob is: %s\n", glob)
if lib.Downloads.Classifiers[glob] != nil {
artifact := lib.Downloads.Classifiers[glob].(map[string]interface{})
resp, _ := http.Get(artifact["url"].(string))
defer resp.Body.Close()
zr := zipstream.NewReader(resp.Body)
for {
e, err := zr.GetNextEntry()
if err == io.EOF {
break
}
if e.IsDir() {
os.MkdirAll(filepath.Join(nativesDir, e.Name), 0755)
} else {
zc, _ := e.Open()
f, _ := os.OpenFile(filepath.Join(nativesDir, e.Name), os.O_CREATE|os.O_RDWR, 0755)
defer zc.Close()
defer f.Close()
io.Copy(f, zc)
}
}
}
}
}
}
func DownloadLoggingConfig(mcVersion string, gameDir string) {
metadata, _ := GetVersionMetadata(mcVersion)
resp, err := http.Get(metadata.Logging.Client.File.Url)
if err != nil {
fmt.Printf("Error downloading logging config: %s\n", err)
return
}
defer resp.Body.Close()
os.MkdirAll(gameDir, 0755)
f, _ := os.OpenFile(filepath.Join(gameDir, "log4j2.xml"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
io.Copy(f, resp.Body)
}
func DownloadExecutable(mcVersion string, binDir string, a App) error {
metadata, err := GetVersionMetadata(mcVersion)
if err != nil {
return fmt.Errorf("unable to pull version metadata: %e\n", err)
}
a.Status(fmt.Sprintf("Checking Minecraft %s Executable\n", mcVersion))
if _, err := os.Stat(filepath.Join(binDir, mcVersion, "client.jar")); err == nil {
f, _ := os.OpenFile(filepath.Join(binDir, mcVersion, "client.jar"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
data, _ := io.ReadAll(f)
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == metadata.Downloads["client"].Sha1 {
return nil
}
}
a.Status(fmt.Sprintf("Downloading Minecraft %s Executable\n", mcVersion))
resp, err := http.Get(metadata.Downloads["client"].Url)
if err != nil {
return fmt.Errorf("unable to download executable: %e\n", err)
}
defer resp.Body.Close()
buff := new(bytes.Buffer)
downloaded := 0
for {
count, err := io.CopyN(buff, resp.Body, BlockSize)
if err == io.EOF {
downloaded += int(count)
break
}
if err != nil {
return fmt.Errorf("Error Downloading executable: %e\n", err)
}
downloaded += int(count)
wruntime.EventsEmit(a.Ctx, "download", downloaded, resp.ContentLength)
}
data := buff.Bytes()
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) != metadata.Downloads["client"].Sha1 {
return fmt.Errorf("unable to download executable: Sha1 Mismatch\n")
}
err = os.MkdirAll(filepath.Join(binDir, mcVersion), 0755)
if err != nil {
return fmt.Errorf("unable to download executable: Unable to create directory\n")
}
f, err := os.OpenFile(filepath.Join(binDir, mcVersion, "client.jar"), os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
return fmt.Errorf("unable to download executable: Unable to open file\n")
}
defer f.Close()
f.Write(data)
wruntime.EventsEmit(a.Ctx, "download_complete")
return nil
}
func GetBaseLaunchArgs(mcVersion string, instance Instance, libDir string, binDir string, assetDir string, gameDir string) ([]string, error) {
args := []string{}
metadata, err := GetVersionMetadata(mcVersion)
if err != nil {
return args, fmt.Errorf("GetLaunchArgs: %e\n", err)
}
searchArgs := []string{}
if metadata.MinecraftArguments != "" {
searchArgs = strings.Split(metadata.MinecraftArguments, " ")
} else {
searchArgs = metadata.Arguments.Game
}
args = append(args, "-Djava.library.path="+filepath.Join(gameDir, "natives"))
args = append(args, "-Djna.tmpdir="+filepath.Join(gameDir, "natives"))
args = append(args, "-Dorg.lwjgl.system.SharedLibraryExtractpath="+filepath.Join(gameDir, "natives"))
args = append(args, "-Dio.netty.native.workdir="+filepath.Join(gameDir, "natives"))
loggingArg := strings.ReplaceAll(metadata.Logging.Client.Argument, "${path}", filepath.Join(gameDir, "log4j2.xml"))
if loggingArg != "" {
args = append(args, loggingArg)
}
args = append(args, "-Xms512m")
args = append(args, "-Xmx4096m")
args = append(args, "-cp")
arg := ""
separater := ":"
if runtime.GOOS == "windows" {
separater = ";"
}
for _, lib := range instance.Libraries {
arg += filepath.Join(libDir, lib) + separater
if _, err := os.Stat(filepath.Join(libDir, lib)); err != nil {
fmt.Printf("Error: missing library: %s\n", lib)
}
}
arg += filepath.Join(binDir, mcVersion, "client.jar")
args = append(args, arg)
args = append(args, instance.MainClass)
for _, val := range searchArgs {
switch val {
case "${version_name}":
args = append(args, mcVersion)
case "${game_directory}":
args = append(args, gameDir)
case "${assets_root}":
args = append(args, assetDir)
case "${clientid}":
args = append(args, client_id)
case "${version_type}":
args = append(args, metadata.Type)
case "${user_type}":
args = append(args, "mojang")
case "${assets_index_name}":
args = append(args, metadata.Assets)
case "${user_properties}":
args = append(args, "{}")
default:
args = append(args, val)
}
}
return args, nil
}
func GetOfflineLaunchArgs(mcVersion string, instance Instance, libDir string, binDir string, assetDir string, gameDir string, playerName string) ([]string, error) {
args, err := GetBaseLaunchArgs(mcVersion, instance, libDir, binDir, assetDir, gameDir)
if err != nil {
return []string{}, fmt.Errorf("GatOfflineLaunchArgs: %e\n", err)
}
for ind, val := range args {
switch val {
case "${auth_player_name}":
args[ind] = playerName
case "${auth_uuid}":
args[ind] = "null"
case "${auth_access_token}":
args[ind] = "null"
case "${auth_xuid}":
args[ind] = "null"
default:
}
}
return args, nil
}
func GetOnlineLaunchArgs(mcVersion string, instance Instance, libDir string, binDir string, assetDir string, gameDir string, auth LauncherAuth) ([]string, error) {
args, err := GetBaseLaunchArgs(mcVersion, instance, libDir, binDir, assetDir, gameDir)
if err != nil {
return []string{}, fmt.Errorf("GatOfflineLaunchArgs: %e\n", err)
}
for ind, val := range args {
switch val {
case "${auth_player_name}":
args[ind] = auth.Name
case "${auth_uuid}":
args[ind] = auth.Id
case "${auth_access_token}":
args[ind] = auth.Token
case "${auth_xuid}":
args[ind] = auth.Id
default:
}
}
return args, nil
}

134
fclauncher/quilt.go Normal file
View File

@ -0,0 +1,134 @@
package main
import (
"bytes"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)
type Quilt struct {
}
type QuiltDefinition struct {
Separator string
Build int
Maven string
Version string
Stable bool
}
type QuiltLibrary struct {
Name string
Url string
Sha1 string
}
type QuiltLibraries struct {
Client []QuiltLibrary
Common []QuiltLibrary
Server []QuiltLibrary
}
type QuiltMeta struct {
Version int
Libraries QuiltLibraries
MainClass map[string]string
}
type QuiltVersion struct {
Loader QuiltDefinition
Intermediary QuiltDefinition
Hashed QuiltDefinition
LauncherMeta QuiltMeta
}
func (Quilt) GetQuiltVersions(mcVersion string) ([]QuiltVersion, error) {
resp, err := http.Get("https://meta.quiltmc.org/v3/versions/loader/" + mcVersion)
if err != nil {
return []QuiltVersion{}, fmt.Errorf("Unable to pull quilt version manifest: %s\n", err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
versions := []QuiltVersion{}
json.Unmarshal(data, &versions)
return versions, nil
}
func GetQuiltMetadata(mcVersion string, quiltVersion string) (QuiltVersion, error) {
versions, err := Quilt{}.GetQuiltVersions(mcVersion)
if err != nil {
return QuiltVersion{}, fmt.Errorf("unable to download versions manifest: %e\n", err)
}
for _, version := range versions {
if version.Loader.Version == quiltVersion {
return version, nil
}
}
return QuiltVersion{}, fmt.Errorf("Unable to find requested version.\n")
}
func InstallQuiltLib(lib QuiltLibrary, libDir string, a *App) {
a.Status(fmt.Sprintf("Checking %s\n", lib.Name))
path := filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name))
if _, err := os.Stat(filepath.Join(libDir, path)); err == nil {
f, _ := os.OpenFile(filepath.Join(libDir, path), os.O_RDONLY, 0755)
defer f.Close()
data, _ := io.ReadAll(f)
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == lib.Sha1 {
return
}
}
a.Status(fmt.Sprintf("Downloading %s\n", lib.Name))
resp, err := http.Get(lib.Url + path)
if err != nil {
fmt.Printf("unable to find library: %s\n", lib.Url+path)
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
fmt.Printf("unable to find library: %s\n", lib.Url+path)
return
}
buff := new(bytes.Buffer)
downloaded := 0
for {
count, err := io.CopyN(buff, resp.Body, BlockSize)
if err == io.EOF {
downloaded += int(count)
break
}
if err != nil {
fmt.Printf("Error Downloading libs: %e\n", err)
return
}
downloaded += int(count)
wruntime.EventsEmit(a.Ctx, "download", downloaded, resp.ContentLength)
}
os.MkdirAll(filepath.Join(libDir, ProcessMavenPath(lib.Name)), 0755)
f, _ := os.OpenFile(filepath.Join(libDir, path), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
io.Copy(f, buff)
wruntime.EventsEmit(a.Ctx, "download_complete")
}
func InstallQuiltLibs(mcVersion string, quiltVersion string, libDir string, a *App) {
metadata, _ := GetQuiltMetadata(mcVersion, quiltVersion)
for _, lib := range metadata.LauncherMeta.Libraries.Client {
InstallQuiltLib(lib, libDir, a)
}
for _, lib := range metadata.LauncherMeta.Libraries.Common {
InstallQuiltLib(lib, libDir, a)
}
InstallQuiltLib(QuiltLibrary{Name: metadata.Loader.Maven, Sha1: "", Url: "https://maven.quiltmc.org/repository/release/"}, libDir, a)
InstallQuiltLib(QuiltLibrary{Name: metadata.Intermediary.Maven, Sha1: "", Url: "https://maven.fabricmc.net/"}, libDir, a)
InstallQuiltLib(QuiltLibrary{Name: metadata.Hashed.Maven, Sha1: "", Url: "https://maven.quiltmc.org/repository/release/"}, libDir, a)
}

View File

@ -1,3 +0,0 @@
fn main() {
println!("Hello, world!");
}

13
fclauncher/wails.json Normal file
View File

@ -0,0 +1,13 @@
{
"$schema": "https://wails.io/schemas/config.v2.json",
"name": "fclauncher",
"outputfilename": "fclauncher",
"frontend:install": "npm install",
"frontend:build": "npm run build",
"frontend:dev:watcher": "npm run dev",
"frontend:dev:serverUrl": "auto",
"author": {
"name": "Samuel Walker",
"email": "swalker@piwalker.net"
}
}