Compare commits

...

107 Commits

Author SHA1 Message Date
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
138 changed files with 7133 additions and 0 deletions

4
.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,6 @@ Cargo.lock
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
**/*.log

View File

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

View File

Before

Width:  |  Height:  |  Size: 367 KiB

After

Width:  |  Height:  |  Size: 367 KiB

View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 367 KiB

After

Width:  |  Height:  |  Size: 367 KiB

View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 943 B

After

Width:  |  Height:  |  Size: 943 B

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 995 B

After

Width:  |  Height:  |  Size: 995 B

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

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

View File

@ -0,0 +1,615 @@
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)
}
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)
}
return
}
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 = "Java.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) {
i.app.Status("Installing Fabric")
_, 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"))
}

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"
"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 {
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`.

161
fclauncher/app.go Normal file
View File

@ -0,0 +1,161 @@
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.3"
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)
if err != nil {
fmt.Printf("Authentication Error: %s\n", err)
return
}
}
err = os.Remove(filepath.Join(dir, "FCLauncher", "authentication.json"))
if err != nil {
fmt.Printf("Unable to delete auth file\n")
}
os.MkdirAll(filepath.Join(dir, "FCLauncher"), 0755)
f, _ := os.OpenFile(filepath.Join(dir, "FCLauncher", "authentication.json"), os.O_CREATE|os.O_RDWR, 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)
}

224
fclauncher/auth.go Normal file
View File

@ -0,0 +1,224 @@
package main
import (
"bytes"
"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 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})
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 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 request 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 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: 20 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,114 @@
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
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,249 @@
# DO NOT EDIT - Generated automatically by `wails build`
!include "x64.nsh"
!include "WinVer.nsh"
!include "FileFunc.nsh"
!ifndef INFO_PROJECTNAME
!define INFO_PROJECTNAME "{{.Name}}"
!endif
!ifndef INFO_COMPANYNAME
!define INFO_COMPANYNAME "{{.Info.CompanyName}}"
!endif
!ifndef INFO_PRODUCTNAME
!define INFO_PRODUCTNAME "{{.Info.ProductName}}"
!endif
!ifndef INFO_PRODUCTVERSION
!define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}"
!endif
!ifndef INFO_COPYRIGHT
!define INFO_COPYRIGHT "{{.Info.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
{{range .Info.FileAssociations}}
!insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
File "..\{{.IconName}}.ico"
{{end}}
!macroend
!macro wails.unassociateFiles
; Delete app associations
{{range .Info.FileAssociations}}
!insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}"
Delete "$INSTDIR\{{.IconName}}.ico"
{{end}}
!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
{{range .Info.Protocols}}
!insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
{{end}}
!macroend
!macro wails.unassociateCustomProtocols
; Delete app custom protocol associations
{{range .Info.Protocols}}
!insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}"
{{end}}
!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,134 @@
<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';
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>
{/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,195 @@
<script lang="ts">
import {instances, loading, navMargin} from './global'
import {OpenInstanceFolder, 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 testArray = ["test","test2","test3"];
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
})
}
</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="launch-button" on:click={launchclick}>Launch {radio}</button>
<button class="option-button" on:click={() => {OpenInstanceFolder(radio)}}>Open Instance Folder</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;
}
.launch-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;
}
.launch-button:hover{
color: var(--text-secondary);
}
.launch-button:active{
opacity: 0.6;
}
.option-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: medium;
max-width: 10rem;
max-height: fit-content;
}
.option-button:hover{
color: var(--text-secondary);
}
.option-button:active{
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,155 @@
<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>
<br/>
<div transition:slide="{{duration:300}}">
<select id="pack" on:change={onchange} bind:value={pack} name="pack">Select a Modpack:
{#each modpacks as pack}
<option value={pack}>{pack}</option>
{/each}
</select>
<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}
<br/>
<button on:click={install}>Install</button>
<button on:click={() => {$addingInstance = false}}>Cancel</button>
</div>
</main>
<style>
main{
margin-top: 16rem;
margin-left: 3rem;
}
</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,163 @@
<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>
</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.

Some files were not shown because too many files have changed in this diff Show More